Exception handling is one of the most important features of any application. To handle exceptions, we can use Try-Catch block in our code base. If we want to extract all the exception handling in one location .NET core provides Exception handler/middleware.
This article will refer to the following sample code on GitHub:
Exceptions with Try-Catch-Finally
The simplest form of a try-catch block
try
{
// try something here
} catch (Exception ex)
{
// catch an exception here
}
finally
{
// always run this code
}
You can have multiple catch blocks to handle specific exceptions like ArgumentOutOfRangeException, NullReferenceException etc. Finally block is optional, whether or not an exception has occurred, the finally block will always be executed.
Generally different approach is followed for error handling for front end application (using MVC, Razor) vs WebAPI.
Error handling for MVC/Razor
MVC actions are typically executed as a result of a user action in the browser so returning an error page to the browser is ideal. With MVC, failure to display a friendly error page is not a good practice in a production ready application. The default template uses the DeveloperExceptionPage middleware in a development environment but redirects to a shared Error view in non-development scenarios. Logic is implemented in the Configure() method of the Startup.cs class.
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
MVC Pages
In MVC project’s Home Controller, the Error() action method sets the RequestId to the current Activity.Current.Id if available or else it uses HttpContext.TraceIdentifier. These values will be useful during debugging.
For non-dev scenarios, the shared Error view can be further customized by updating the Error.cshtml view in the Shared subfolder.
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public IActionResult Error()
{
return View(new ErrorViewModel {
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier
});
}
Razor Pages
In Razor projects for the non-dev scenario is slightly different for Razor Pages. Instead of pointing to the Home controller’s Error() action method, it points to the to the /Error page route. This Error.cshtml Razor Page found in the root level of the Pages folder.
[ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
public class ErrorModel : PageModel
{
public string RequestId { get; set; }
public bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
public void OnGet()
{
RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier;
}
}
WebAPI handling error using Built-In Middleware
UseExceptionHandler middleware is a built-in middleware that we can use to handle exceptions in our ASP.NET Core Web API application
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler(appError =>
{
appError.Run(async context =>
{
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
context.Response.ContentType = "application/json";
var contextFeature = context.Features.Get<IExceptionHandlerFeature>();
if (contextFeature != null)
{
await context.Response.WriteAsync(new Error
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error."
}.ToString());
}
});
});
}
WebAPI handling error using Custom Middleware
The above example works for simple scenarios if you want more control or logic you can write your custom middleware and can flush logic associated to how we need to handle errors.
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_logger = logger;
_next = next;
}
public async Task InvokeAsync(HttpContext httpContext)
{
try
{
await _next(httpContext);
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong: {ex}");
await HandleExceptionAsync(httpContext, ex);
}
}
private Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = StatusCodes.Status500InternalServerError;
return context.Response.WriteAsync(new Error
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error from the custom middleware."
}.ToString());
}
}
WebAPI handling error using Exception Handling Path (.NET Core 2.1/2.2)
1. In Startup Configure, invoke UseExceptionHandler to use the middleware
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseExceptionHandler("/error-local-development");
}
else
{
app.UseExceptionHandler("/error");
}
app.UseMvc();
}
2. Configure a controller action to respond to the /error route
[AllowAnonymous]
[Route("/error")]
public IActionResult Error([FromServices] IHostingEnvironment webHostEnvironment)
{
var feature = HttpContext.Features.Get<IExceptionHandlerPathFeature>();
var ex = feature?.Error;
var isDev = webHostEnvironment.IsDevelopment();
var problemDetails = new ProblemDetails
{
Status = (int)HttpStatusCode.InternalServerError,
Instance = feature?.Path,
Title = isDev ? $"{ex.GetType().Name}: {ex.Message}" : "An error occurred.",
Detail = isDev ? ex.StackTrace : null,
};
return StatusCode(problemDetails.Status.Value, problemDetails);
}
WebAPI handling error using Exception Handling Path (.NET Core 3.1)
1. In Startup Configure, invoke UseExceptionHandler to use the middleware
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseExceptionHandler("/error-local-development");
}
else
{
app.UseExceptionHandler("/error");
}
app.UseMvc();
}
2. Configure a controller action to respond to the /error route
[Route("/error")]
public IActionResult Error() => Problem();
Comments
Post a Comment