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 : Attribute, IExceptionFilter
{
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 errCode, string message, string detailMessage)
: base(message)
{
this.errorCode = errCode;
this.DetailMessage = detailMessage;
}
public MyException(string message, Exception inner)
: base(message, inner)
{
}
protected MyException(
System.Runtime.Serialization.SerializationInfo info,
System.Runtime.Serialization.StreamingContext context)
: base(info, context)
{
}
public string DetailMessage { get; set; }
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 app, ILoggerFactory 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