2019年10月21日 星期一

[ASP.NET Core] Custom exception handler


 ASP.NET Core   Web Api   Custom Exception Handler  


Introduction


We can use some default ASP.NET Core patterns for error handling, such as

·      IApplicationBuilder.UseDeveloperExceptionPage() (Supported by Microsoft.AspNetCore.Diagnostics) for showing error details in development mode.
·      IApplicationBuilder.UseExceptionHandler("/Home/Error") for using a default error template-page (the page will be created in default in some dotnet core templates, such as in MVC, Blazor App … templates).


Must times we have to customize Error Handler to log the detail errors, hack the error for client or something else.

Here are my tips to handle the error in ASP.NET Core Web API:

1.  Create an Exception attribute filter to intercept and modify the error
2.  Create a Global exception handler to decide how/what an error will be displayed




Environment


ASP.NET Core 3.0.100



Implement



Exception Handler Filter

Create an Exception handler filter as following,

ApiExceptionFilter.cs

 public class ApiExceptionFilter : AttributeIExceptionFilter
 {
        public void OnException(ExceptionContext context)
        {
            var exceptionType = context.Exception.GetType();

            if (exceptionType.Equals(typeof(MyException)))
            {
                // Do nothing
            }
            else if (exceptionType.Equals(typeof(ValidationException)))
            {
                context.Exception = new MyException(ErrorCodeEnum.ModelValidateFail"Model validation error"context.Exception.Message);
            }
            else if (exceptionType.Equals(typeof(NullReferenceException)))
            {
                context.Exception = new MyException(ErrorCodeEnum.NullReference"The object cannot be null"context.Exception.Message);
            }
            else if (exceptionType.Equals(typeof(DbUpdateException)))
            {
                context.Exception = new MyException(ErrorCodeEnum.DatabaseError"DB error"context.Exception.Message);
            }
            else
            {
                context.Exception = new MyException(ErrorCodeEnum.Other"Server error"context.Exception.Message);
            }
        }
 }


MyExceptionFilter.cs

 [Serializable]
 public class MyException : Exception
 {
        private ErrorCodeEnum errorCode = ErrorCodeEnum.Other;

        public MyException()
        {
        }

        public MyException(ErrorCodeEnum errCodestring messagestring detailMessage)
            : base(message)
        {
            this.errorCode = errCode;
            this.DetailMessage = detailMessage;
        }

        public MyException(string messageException inner)
            : base(messageinner)
        {
        }

        protected MyException(
          System.Runtime.Serialization.SerializationInfo info,
          System.Runtime.Serialization.StreamingContext context)
            : base(infocontext)
        {
        }

        public string DetailMessage { getset; }

        public override string Message
        {
            get
            {
                return $"{{\"errorCode\":\"{(int)this.errorCode}\",\"message\":\"{base.Message}\"}}";
            }
        }
 }



Then we can use it on our other API controller or actions, for example,

 [HttpGet("GetAll")]
 [ApiExceptionFilter]
 public async Task<string>> Get()
 {
     // …
 }


Or register it globally.

MvcBuilderExtensions.cs

 public static class MvcBuilderExtensions
 {
        public static IMvcBuilder AddMvcOptions(this IMvcBuilder builder)
        {
            builder.Services.Configure<MvcOptions>(options =>
            {
                options.Filters.Add(typeof(ApiExceptionFilter));

                //…Add more filter here
            });
            return builder;
        }
 }



Startup.cs: ConfigureServices

 public void ConfigureServices(IServiceCollection services)
 {
     services.AddMvc()
                .AddMvcOptions()
 }



Global Exception Handler: Sample 1

Create an exception-handling configuration for the global error handling, and use ExceptionHandlerExtensions.UseExceptionHandler to register it.


ExceptionHandlerConfig.cs

 public class ExceptionHandlerConfig
 {
        public ExceptionHandlerOptions CustomOptions
        {
            get
            {
                return new ExceptionHandlerOptions()
                {
                    ExceptionHandler = async context =>
                    {
                        var feature = context.Features.Get<IExceptionHandlerFeature>();
                        var error = feature?.Error ?? new Exception("Internal Server Error");
                        context.Response.ContentType = "application/json";
                        var json = $"{{\"error\":\"{error?.Message}\"}}";
                        await context.Response.WriteAsync(json);
                        return;
                    }
                };
            }
        }
 }



Startup.cs : Configure

 public void Configure(IApplicationBuilder app)
 {
    app.UseExceptionHandler(new ExceptionHandlerConfig().CustomOptions);
 }



Global Exception Handler: Sample 2

Here is another example for creating ApplicationBuilderExtensions so that we can pass necessary service(s), such as ILoggerFactory, to our Custom Error Handler.



ApplicationBuilderExtensions.cs

 public static class ApplicationBuilderExtensions
 {
        public static void ConfigureExceptionHandler(this IApplicationBuilder appILoggerFactory loggerFactory)
        {
            var logger = loggerFactory.CreateLogger("My Error Handler");
            app.UseExceptionHandler(configure =>
            {
                configure.Run(async context =>
                {
                    // Get exception
                    var feature = context.Features.Get<IExceptionHandlerFeature>();
                    var exception = feature?.Error ?? new Exception("Internal Server Error");

                    // Logging
                    if (exception.GetType().Equals(typeof(MyException)))
                    {
                        logger.LogError((exception as MyException).DetailMessage);
                    }
                    else
                    {
                        logger.LogError(exception.Message);
                    }

                    // Custom response
                    context.Response.ContentType = "application/json";
                    var json = $"{{\"error\":\"{exception?.Message}\"}}";
                    await context.Response.WriteAsync(json);
                    return;
                });
            });
        }
 }


Startup.cs : Configure

 public void Configure(
            IApplicationBuilder app,
            ILoggerFactory loggerFactory)
 {
            app.ConfigureExceptionHandler(loggerFactory);
 }



Result:





Reference