top of page

Middleware and Filters power in ASP.NET Core

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

It 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

It 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.


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
    {
        private readonly RequestDelegate _requestDelegate;
        public ExceptionHandlingMiddleware(RequestDelegate 
                                                requestDelegate)
        {
            _requestDelegate = requestDelegate;
        }
        public async Task Invoke(HttpContext context)
        {
            try
            {
                await _requestDelegate(context);
            }
            catch (Exception ex)
            {
                await HandleException(context, ex);
            }
        }
        private 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:


1. 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();
            });
}



Middleware vs Filters

The first step is to consider why you would choose to use middleware over filters, or vice versa. Both are designed to handle cross-cutting concerns of your application and both are used in a 'pipeline', so in some cases you could choose either successfully.


The main difference between them is their scope. Filters are a part of MVC, so they are scoped entirely to the MVC middleware. Middleware only has access to the HttpContext and anything added by preceding middleware. In contrast, filters have access to the wider MVC context, so can access routing data and model binding information for example.


Generally speaking, if you have a cross cutting concern that is independent of MVC then using middleware makes sense, if your cross cutting concern relies on MVC concepts, or must run midway through the MVC pipeline, then filters make sense.


First, you have some middleware that already does what you want, but you now need the behaviour to occur midway through the MVC middleware. You could rewrite your middleware as a filter, but it would be nicer to just be able to plug it in as-is. This is especially true if you are using a piece of third-party middleware and you don't have access to the source code.


Second, you have functionality that needs to logically run as both middleware and a filter. In that case you can just have the one implementation that is used in both places.


Resource: andrewlock.net/, Medium - Binod Mahto


The Tech Platform

0 comments

Comments


bottom of page