Skip to content

03 - Filters

Filters in MVC

  • The filter pipeline runs after ASP.NET Core selects the action to execute

Filters

  • Filters allow code to run before or after specific stages in the request processing pipeline.

Filters

Filter types

  • Authorization filters:
    • Run first.
    • Determine whether the user is authorized for the request.
    • Short-circuit the pipeline if the request is not authorized.
  • Resource filters:
    • Run after authorization.
    • OnResourceExecuting runs code before the rest of the filter pipeline. For example, OnResourceExecuting runs code before model binding.
    • OnResourceExecuted runs code after the rest of the pipeline has completed.
  • Action filters:
    • Run immediately before and after an action method is called.
    • Can change the arguments passed into an action.
    • Can change the result returned from the action.
  • Endpoint filters:
    • Run immediately before and after an action method is called.
    • Can change the arguments passed into an action.
    • Can change the result returned from the action.
    • Can be invoked on both actions and route handler-based endpoints.
  • Result filters:
    • Run immediately before and after the execution of action results.
    • Run only when the action method executes successfully.
    • Are useful for logic that must surround view or formatter execution.
  • Exception filters:
    • Apply global policies to unhandled exceptions that occur before the response body has been written to.

Action and Endpoint filer are not supported in Razor Pages.

Filters and Attributes

Both allow injecting filters into pipeline. Injection method differs:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
public class LogActionFilter : ActionFilterAttribute
{
    // Filter logic here
}

[LogActionFilter]
public IActionResult Index()
{
    // Controller action method logic here
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
public class LogActionFilter : IActionFilter
{}

services.AddMvc(options =>
  {
    // global filter
    options.Filters.Add(typeof(LogActionFilter));
  });

// controller level filter
[TypeFilter(typeof(LogActionFilter))]
public class HomeController : Controller
{
// Controller action methods here
}

Filter attributes to inherint from:

  • ActionFilterAttribute
  • ExceptionFilterAttribute
  • ResultFilterAttribute
  • FormatFilterAttribute
  • ServiceFilterAttribute (DI)
  • TypeFilterAttribute (DI)

Action filter

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class ActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
    {
        // Do something before the action executes.
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        // Do something after the action executes.
    }
}

public class AsyncActionFilter : IAsyncActionFilter
{
    public async Task OnActionExecutionAsync(
        ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // Do something before the action executes.
        await next();
        // Do something after the action executes.
    }
}
  • Implement only one type of filter. When both are present, only async version is called.
  • When using abstract class ActionFilterAttribute - override only one type

Exception filter

  • Implement IExceptionFilter or IAsyncExceptionFilter.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class ExceptionFilter : IExceptionFilter
{
    private readonly IHostEnvironment _hostEnvironment;

    public ExceptionFilter(IHostEnvironment hostEnvironment) =>
        _hostEnvironment = hostEnvironment;

    public void OnException(ExceptionContext context)
    {
        if (!_hostEnvironment.IsDevelopment())
        {
            // Don't display exception details unless running in Development.
            return;
        }

        context.Result = new ContentResult
        {
            Content = context.Exception.ToString()
        };
    }
}

To handle an exception,

  • set the ExceptionHandled property to true

OR

  • assign the Result property.

This stops propagation of the exception. An exception filter can't turn an exception into a "success". Only an action filter can do that.

Result filter

Implement an interface:

  • IResultFilter or IAsyncResultFilter
  • IAlwaysRunResultFilter or IAsyncAlwaysRunResultFilter
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
public class ResultFilter : IResultFilter
{
    public void OnResultExecuting(ResultExecutingContext context)
    {
        // Do something before the result executes.
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        // Do something after the result executes.
    }
}

Some examples

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;

public class LogActionFilter : IAsyncActionFilter
{
    private Stopwatch stopwatch

    public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        stopwatch = Stopwatch.StartNew();
        await next();
    }

    public async Task OnActionExecutedAsync(ActionExecutedContext context)
    {
        stopwatch.Stop();
        var elapsedMilliseconds = stopwatch.ElapsedMilliseconds;
        var message = $"Action took {elapsedMilliseconds} ms to execute.";
        Debug.WriteLine(message);
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
public class ResponseHeaderAttribute : ActionFilterAttribute
{
    private readonly string _name;
    private readonly string _value;

    public ResponseHeaderAttribute(string name, string value) =>
        (_name, _value) = (name, value);

    public override void OnResultExecuting(ResultExecutingContext context)
    {
        context.HttpContext.Response.Headers.Add(_name, _value);

        base.OnResultExecuting(context);
    }
}

[ResponseHeader("Filter-Header", "Filter Value")]
public class ResponseHeaderController : ControllerBase
{

}

DI example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class LoggingResponseHeaderFilterService : IResultFilter
{
    private readonly ILogger _logger;

    public LoggingResponseHeaderFilterService(
            ILogger<LoggingResponseHeaderFilterService> logger) =>
        _logger = logger;

    public void OnResultExecuting(ResultExecutingContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuting)}");

        context.HttpContext.Response.Headers.Add(
            nameof(OnResultExecuting), nameof(LoggingResponseHeaderFilterService));
    }

    public void OnResultExecuted(ResultExecutedContext context)
    {
        _logger.LogInformation(
            $"- {nameof(LoggingResponseHeaderFilterService)}.{nameof(OnResultExecuted)}");
    }
}


builder.Services.AddScoped<LoggingResponseHeaderFilterService>();


[ServiceFilter<LoggingResponseHeaderFilterService>]
public IActionResult WithServiceFilter() =>
    Content($"- {nameof(FilterDependenciesController)}.{nameof(WithServiceFilter)}");