In ASP.NET core, Middleware and Filters are two concepts which are very useful & powerful but often confuses also on which one to choose.
Middleware and Filters serves similar purpose and both can be used to achieve the common tasks like exception handling, localizations, authentications etc… but both has certain difference in a way it works with the pipeline.
Lets understand the difference and use of Middleware and Filters through an example of Exception Handling.
Middleware allows in the pipeline to handle requests and responses Where you can choose if the component “is to pass the request to the next component in the pipeline” or “can perform work before and after the next component in the pipeline”. i.e. Perform request validation, localization, exception handling or logging each request and response etc.
Filters run within the ASP.NET Core action invocation pipeline and allows to perform certain operation before or after specific stages in the request processing pipeline. i.e. Perform authentication or authorization to validate requested action processing.
Now lets understand it with the example of Exception Handling generically using both the way.
Scenario: Handle all unhandled exception in code to safeguard the crucial information about code to be delivered to the end user.
Note: ASP.NET Core has already predefined middleware to achieve the same but we are doing it manually to get more control on it.
1. Using Middleware.
Here we try to handle all uncaught exception through out the application through a middleware we create. Middleware is a normal class so you don’t have to inherit any interface/specific class but your class must have the method called Invoke “public async Task Invoke(HttpContext context)”.
using Microsoft.AspNetCore.Http;
using System;
using System.Net;
using System.Threading.Tasks;namespace CoreWebAPIDemo.Middleware
{
public class ExceptionHandlingMiddleware
{
public RequestDelegate requestDelegate;
public ExceptionHandlingMiddleware(RequestDelegate requestDelegate)
{
this.requestDelegate = requestDelegate;
}
public async Task Invoke(HttpContext context)
{
try
{
await requestDelegate(context);
}
catch (Exception ex)
{
await HandleException(context, ex);
}
}
private static Task HandleException(HttpContext context, Exception ex)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;return context.Response.WriteAsync(ex.Message);
}
}
}
In above code here I have RequestDelegate dependency resolving code to handle the http request because my requirement to catch the unhandled exception in the pipeline . Now lets add an extension method for IApplicationBuilder to simplify the call for this middleware.
using Microsoft.AspNetCore.Builder;namespace CoreWebAPIDemo.Middleware
{
public static class ExceptionHandlerMiddlewareExtension
{
public static IApplicationBuilder UseExceptionHandlerMiddleware(
this IApplicationBuilder builder)
{
return builder.UseMiddleware<ExceptionHandlingMiddleware>();
}
}
}
Our Middleware to catch the uncaught exception is ready and now we need to register it to request pipeline which will be from Startup.Configure method. Here we have to be very careful as the pipeline order is driven through the order of middleware services you add. In this we need to catch the exception while processing the request hence I’ll be adding this middleware at the end just before the UseEndpoints middleware call.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection(); app.UseRouting(); app.UseStaticFiles(); app.UseCors("AllowAll"); app.UseExceptionHandlerMiddleware(); app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
and finally to test the code, lets create a control which will throw the exception.
using Microsoft.AspNetCore.Mvc;
using System;namespace CoreWebAPIDemo.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class HomeController : ControllerBase
{
[HttpGet]
public IActionResult Index(int value)
{
if (value == 1)
return Ok("I'm Happy");
else
throw new Exception("I didn't like your number");
}
}
}
output would be:
Now lets do the exception handling through action filter.
2. Using Filter.
Here we will create a action filter which inherits IActionFilter.
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Net;namespace CoreWebAPIDemo.Middleware
{
public class ExceptionHandlerFilter : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context) { }public void OnActionExecuted(ActionExecutedContext context)
{
if (context.Exception is Exception exception)
{
context.Result = new ObjectResult(exception.Message)
{
StatusCode = (int)HttpStatusCode.InternalServerError,
};
context.ExceptionHandled = true;
}
}
}
}
In this case, we are adding code to handle exception OnActionExecute method which will get executed after the action is executed.
Now we need to register it through Startup.ConfigureServices method if we want to apply it forcefully to all the controller’s action execution and here we can add this anywhere in the method without worrying about the order.
services.AddControllers(options => options.Filters.Add(new ExceptionHandlerFilter()));
Or we can also restrict it to a specific controller or action method and this is one big advantage over using Middleware concept to handle exceptions like in this scenario. Also when we reach here our MVC context is fully ready like model binding and all is done and available with the context.
And this is the reason, I have also inherited Attribute class while creating ExceptionHandlerFilter. Now here is the code to use it with the specific controller only.
using CoreWebAPIDemo.Middleware;
using Microsoft.AspNetCore.Mvc;
using System;namespace CoreWebAPIDemo.Controllers
{
[ApiController]
[Route("api/[controller]")]
[ExceptionHandlerFilter]
public class HomeController : ControllerBase
{
[HttpGet]
public IActionResult Index(int value)
{
if (value == 1)
return Ok("I'm Happy");
else
throw new Exception("I didn't like your number");
}
}
}
Note: Don’t forget to comment the earlier middleware code for registering “app.UseExceptionHandlerMiddleware();” in case if you are in same project like me while testing.
Bonus Reading
ASP.NET core also provides predefined middleware for exception handing which you can use to control the information flowing to the use in case of uncaught exception or error and you can achieve that by doing this:
- Create an ErrorController and two action method as below to handle development and production scenario.
using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;namespace CoreWebAPIDemo.Controllers
{[ApiController]
[Route("api/[controller]")]
public class ErrorController : ControllerBase
{
/// <summary>
/// The preceding Error action sends an RFC 7807-compliant payload to the client.
/// </summary>
/// <returns></returns>
[Route("/error")]
public IActionResult Error() => Problem("Oops there is a problem. Excuse me and smile!");[Route("/error-local-development")]
public IActionResult ErrorLocalDevelopment(
[FromServices] IWebHostEnvironment webHostEnvironment)
{
if (webHostEnvironment.EnvironmentName != "Development")
{
throw new InvalidOperationException("Ugggh! This is not for Production, go away.");
}var context = HttpContext.Features.Get<IExceptionHandlerFeature>();return Problem(
detail: context.Error.StackTrace,
title: context.Error.Message);
}
}
}
2. Enable ASP.NET Core predefined ExceptionHandler middleware from Startup.Configure method.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseExceptionHandler("/error-local-development");
}
else
{
app.UseExceptionHandler("/error");
} app.UseHttpsRedirection(); app.UseRouting(); app.UseStaticFiles(); app.UseCors("AllowAll"); app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Thank you for reading. Hope you enjoyed it and learn the use of Middleware and Filter concept of ASP.NET Core. Don’t forget to clap if you like it and follow me to receive such article updates from me.
No comments:
Post a Comment