Filters in ASP.NET MVC are an implementation of AOP (aspect oriented programming) ideas that allow us to execute code at specific stages of the execution pipeline, Filters can realize short circuit requests, cache request results, unified log recording, parameter validity verification, unified exception processing, return value formatting, and so on. At the same time, the business code is more simple and simple, avoiding a lot of duplicate code.

Filter execution flow

After the MVC selects the Action method to execute, it executes the filter pipe, as shown below:

Filter scope

Filter scope Settings are very flexible and can be:

  1. Globally valid (every Action across the ENTIRE MVC application);

  2. Only for some controllers (all actions within the Controller);

  3. Only valid for certain actions;

Filter type

Filters are classified into Authorization Filter, Resource Filter, Action Filter, Exception Filter, and Result Filter. Each Filter has its own application scenarios. Different types of filters run at different stages of the pipeline, and we need to use them according to the actual situation.

Authorization Filter

Authorization filter first implemented in the filter pipe, usually is used to verify the legitimacy of the request (by implementing an interface IAuthorizationFilter or IAsyncAuthorizationFilter)

Resource Filter

Resource filters are executed second in the filter pipeline and are typically used to cache request results and short-circuit the filter pipeline (by implementing interfaces IResourceFilter or IAsyncResourceFilter)

Action Filter

Acioin filters can be set to execute code before and after Acioin methods are called, such as validation of request parameters (by implementing the interface IActionFilter or IAsyncActionFilter).

Exception Filter

Program exception information handling (by implementing interface IExceptionFilter or IAsyncExceptionFilter)

Result Filter

Action is executed after completion of execution, and the result of execution is formatted and processed (by implementing the interface IResultFilter or IAsyncResultFilter)

public class AddHeaderResultFilter : IResultFilter { public void OnResultExecuted(ResultExecutedContext context) { Console.WriteLine("AddHeaderResultFilter:OnResultExecuted"); } public void OnResultExecuting(ResultExecutingContext context) { context.HttpContext.Response.Headers.Add("ResultFilter", new string[] { "AddHeader" }); context.Result = new ObjectResult(new ApiResult<string>() { Code = Enum.ResultCode.Success, Data = "I am AddHeaderResultFilter modified value"}); }}Copy the code

Filter Attributes

It is very convenient to use the implementation of the filter interface in terms of Attributes, which can be applied to controllers and actions. The framework includes built-in feature-based filters that can be inherited directly or customized separately.

Inherit the built-in ExceptionFilterAttribute:

public class CustomExceptionFilterAttribute : ExceptionFilterAttribute { public override void OnException(ExceptionContext context) { context.Result = new ObjectResult(new ApiResult() { Code = Enum.ResultCode.Exception, ErrorMessage = $"CustomExceptionFilterAttribute: {context.Exception.Message}" }); }}Copy the code

[CustomExceptionFilter]
public ApiResult ExceptionAttributeTest()
{
	throw new Exception("Boom");
}
Copy the code

Custom ResourceFilterAttribute:

public class ShortCircuitingResourceFilterAttribute : Attribute, IResourceFilter { public void OnResourceExecuted(ResourceExecutedContext context) { } public void OnResourceExecuting(ResourceExecutingContext context) { context.Result = new ObjectResult(new ApiResult<string>() { Code = Enum. The ResultCode. Failed, Data = "I am ShortCircuitingResourceFilterAttribute return values"}); }}Copy the code

Filter actual operation

Question: There are always such scenarios in the development, method parameters need to add some if judgment, illegal return error message, method body add a try\catch, catch the exception after logging, and these are basically every interface method must, in the actual case we might be CTRL + C & CTRL + V, Fine-tuning part code, fix, will not take any time, but we can see the code, the entire file is mostly the same shelf, head, tail, calls in the middle of the main logic layer method, full of duplicate code, but if later to adjust the base frame, each method to come again, it must be egg painful death.

Solution: Action Filter + Exception Filter

  1. Create a Filter that implements both IAsyncActionFilter and IAsyncExceptionFilter:

    public class XXXActionFilter : IAsyncActionFilter, IAsyncExceptionFilter { private IDictionary<string, object> _actionArguments; Public Async Task OnActionExecutionAsync(ActionExecutionDelegate Next) {// If the parameter verification fails, Returns an error message if (! context.ModelState.IsValid) { var errorMessages = new List<string>(); foreach (var key in context.ModelState.Keys) { var state = context.ModelState[key]; var errorModel = state? .Errors? .First(); if (errorModel ! = null) errorMessages.Add($"{key}:{errorModel.ErrorMessage}"); } var result = new ApiResult() { Code = ResultCode.ArgumentError, Message = errorMessages.Join(",") }; context.Result = new ObjectResult(result); return; ActionArguments = context.ActionArguments; // Set ExceptionContext to ExceptionContext. await next(); } public async Task OnExceptionAsync(ExceptionContext context) {// Record exception logs // some code.......... var result = new ApiResult() { Code = ResultCode.Exception, Message = $"{context.ActionDescriptor.RouteValues["action"]} Exception") }; context.Result = new ObjectResult(result); await Task.CompletedTask; }}Copy the code
  2. Add XXXActionFilter to startup. cs ConfigureServices to make it globally valid:

    services.AddMvc(options =>
    {
    	options.Filters.Add<XXXActionFilter>();
    });
    Copy the code
  3. Add test method in Controller:

    public ApiResult ActionTest(XXXRequest request)
    {
    	if (request.Id == "1")
    	{
    		throw new Exception("xxxx");
    	}
    	return new ApiResult<string>()
    	{
    		Code = Enum.ResultCode.Success,
    		Data = "ActionTest"
    	};
    }
    Copy the code
    public class XXXRequest { [Required] public string Id { get; set; }}Copy the code

Call ActionTest test, if id no, will go in the context of OnActionExecutionAsync. ModelState. IsValid to false:

If id is 1, OnExceptionAsync is called:

Otherwise normal return:

Refer to the link