Filters: A Beginner’s Guide

ASP.NET filters are a powerful feature in the ASP.NET MVC framework that allow you to add pre-processing and post-processing logic to action methods. Filters can be used to perform tasks such as authentication, caching, logging, and exception handling. In this article, we will discuss the different types of filters available in ASP.NET and provide examples of how to use them.

Authorization Filters

Authorization filters are used to check if the user is authorized to access a particular action or controller. In other words, they are used to determine if the user has the necessary permissions to perform a specific action. For example, you can create an authorization filter that checks if the user is authenticated and, if not, redirects them to the login page.

public class AuthFilter : IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationFilterContext context)
    {
        if (!context.HttpContext.User.Identity.IsAuthenticated)
        {
            context.Result = new RedirectToActionResult("Login", "Account", null);
        }
    }
}

You can apply this filter to the action method using the [Authorize] attribute.

[TypeFilter(typeof(AuthFilter))]
public IActionResult CreatePost()
{
    return View();
}

This way, the AuthFilter will be executed before the CreatePost action method is executed and will prevent unauthenticated users from accessing the page.

Action Filters

Action filters are used to perform logic before or after an action method is executed. For example, you can create an action filter that logs the execution time of an action method.

public class LogExecutionTimeAttribute : ActionFilterAttribute
{
    private readonly ILogger _logger;
    private Stopwatch _stopwatch;

    public LogExecutionTimeAttribute(ILogger<LogExecutionTimeAttribute> logger)
    {
        _logger = logger;
    }

    public override void OnActionExecuting(ActionExecutingContext context)
    {
        _stopwatch = Stopwatch.StartNew();
    }

    public override void OnActionExecuted(ActionExecutedContext context)
    {
        _stopwatch.Stop();
        var elapsed = _stopwatch.ElapsedMilliseconds;
        _logger.LogInformation($"Execution time for {context.ActionDescriptor.DisplayName} is {elapsed}ms.");
    }
}

You can apply this filter to an action method using the [LogExecutionTime] attribute.

[TypeFilter(typeof(LogExecutionTimeAttribute))]
public IActionResult Index()
{
    // Action logic here
    return View();
}

Result Filters

Result filters are used to perform logic before or after a view is rendered. For example, you can create a result filter that adds a custom header to the response for all views.

    public class AddHeaderAttribute : ResultFilterAttribute
    {
        private readonly string _name;
        private readonly string _value;

        public AddHeaderAttribute(string name, string value)
        {
            _name = name;
            _value = value;
        }
        public override void OnResultExecuting(ResultExecutingContext context)
        {
            context.HttpContext.Response.Headers.Add(_name, _value);
        }
    }

You can apply this filter to a view using the [AddHeader] attribute.

[AddHeader("X-Powered-By", "MyBlog")]
public IActionResult About()
{
    return View();
}

When the About action is executed, and the view is rendered, the OnResultExecuting method is called, which adds the header X-Powered-By: MyBlog to the response. This header will be included in the response for all views, which can be useful for adding custom metadata or tracking information to the response.

Exception Filters

Exception filters are used to handle exceptions that are thrown by an action method. For example, you can create an exception filter that logs unhandled exceptions and returns a custom error page.

public class LogErrorAttribute : ExceptionFilterAttribute
{
    private readonly ILogger<LogErrorAttribute> _logger;

    public LogErrorAttribute(ILogger<LogErrorAttribute> logger)
    {
        _logger = logger;
    }

    public override void OnException(ExceptionContext context)
    {
        _logger.LogError(context.Exception, "An unhandled exception occurred.");
        context.Result = new ViewResult
        {
            ViewName = "Error",
            ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
            {
                {"ErrorMessage", "An unhandled exception occurred. Please try again later."},
            }
        };
        context.ExceptionHandled = true;
    }
}

You can apply this filter globally for all actions by adding it to the ConfigureServices method in the Program.cs file

builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<LogErrorAttribute>();
});

or you can add this filter to a specific action or controller using the [LogError] attribute

[TypeFilter(typeof(LogErrorAttribute))]
public IActionResult Index()
{
    throw new Exception("An error occurred.");
}

In this way, if an exception is thrown by the Index action, the OnException method is called and the exception is logged and handled by returning a custom error page. This filter can be very helpful in a production environment where you want to handle and log unhandled exceptions and show a user-friendly message to the client, rather than showing the default error page.

How to create an ASP.NET Core MVC application that uses filters

  1. Create a new ASP.NET Core MVC project using Visual Studio or the dotnet CLI.
  2. In the Program.cs file, add the following code to configure the services and views globally for your application if you want to:
builder.Services.AddControllersWithViews(options =>
{
    options.Filters.Add<AuthFilter>();
    options.Filters.Add<LogErrorAttribute>();
});

builder.Services.AddLogging(loggingBuilder =>
{
    loggingBuilder.AddConsole();
    loggingBuilder.AddDebug();
});

This code adds the AuthFilter and LogErrorAttribute to all actions, and also configures the logging services to output log messages to the console and the debug window.

  1. In the Configure method, you need to wire the application with the MVC middleware, add the following code:
if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Home/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

app.UseRouting();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

This code sets up the routing and handles exceptions. The UseDeveloperExceptionPage middleware is added only in the development environment, it will show the detailed error page with stack trace and the inner exception, while in production the middleware UseExceptionHandler is used instead, it will redirect the request to the Error action of the Home controller.

  1. Create views needed for our example as below :
  1. Create the associate controller :

Add the following code to the HomeController class, using the TypeFilter attribute to deal case by case the usage of filters :

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;

namespace MyBlog.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        [TypeFilter(typeof(AuthFilter))]
        public IActionResult CreatePost()
        {
            return View();
        }

        [TypeFilter(typeof(LogExecutionTimeAttribute))]
        public IActionResult Index()
        {
            return View();
        }

        [AddHeader("X-Powered-By", "MyBlog")]
        public IActionResult Privacy()
        {
            return View();
        }
        
        [TypeFilter(typeof(LogErrorAttribute))]
        public IActionResult ThrowError()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
  1. Run the application, you should see « Hello World » in the browser.
  2. You can test the filters by trying to access the CreatePost action without logging in, it should redirect you to the login page. Also, you can test the LogError filter by trying to access the Error action, it should redirect you to the Error view, and you should find a log message in the console or the debug window.

Conclusion

In conclusion, ASP.NET filters are a powerful feature in the ASP.NET MVC framework that allows you to add pre-processing and post-processing logic to action methods. They can be used to perform tasks such as authentication, caching, logging, and exception handling. With the examples provided in this article, you should now have a good understanding of how to use filters in your ASP.NET MVC application.

Retrieve full code on my GitHub.

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *