ASP.NET Core Filter Advanced

In this section we discuss advanced topics of filters – filter dependencies, global filters, filter execution order & changing the filter order. In the previous section we introduced the basic use of filters in ASP.NET Core

1 ASP.NET Core filter dependency injection

If a filter has a dependency, it cannot be applied to the action method using attributes. Instead, the TypeFilter attribute is used. The code is as follows:

[TypeFilter(typeof(FilterName))]

To understand through an example, create a new class FilterDependency.cs in the CustomFilters folder of your project. Define a message string in the IExceptionFilterMessage interface to store errors that occur in the action method. The code is as follows:

public interface IExceptionFilterMessage
{
    IEnumerable<string> Messages { get; }
    void AddMessage(string message);
}

Add the ExceptionFilterMessage class in the FilterDependency.cs file and inherit the IExceptionFilterMessage interface

public interface IExceptionFilterMessage
{
   IEnumerable<string> Messages { get; }
   void AddMessage(string message);
}
public class ExceptionFilterMessage : IExceptionFilterMessage
{
   private List<string> messages = new List<string>();
   public IEnumerable<string> Messages => messages;
   public void AddMessage(string message) => messages.Add(message);
}

Now, add an exception filter called CatchErrorMessage. This filter depends on the IExceptionFilterMessage interface and is resolved through the dependency injection feature.

public interface IExceptionFilterMessage
{
    IEnumerable<string> Messages { get; }
    void AddMessage(string message);
}
public class ExceptionFilterMessage : IExceptionFilterMessage
{
    private List messages = new List();
    public IEnumerable Messages => messages;
    public void AddMessage(string message) => messages.Add(message);
}
public class CatchErrorMessage : IExceptionFilter
{
    private IExceptionFilterMessage _exceptionFilterMessage;


    public CatchErrorMessage(IExceptionFilterMessage exceptionFilterMessage)
    {
        _exceptionFilterMessage = exceptionFilterMessage;
    }
    public void OnException(ExceptionContext context)
    {
        _exceptionFilterMessage.AddMessage("Exception Filter is called. ");
        _exceptionFilterMessage.AddMessage("Error Message is given below. ");
        _exceptionFilterMessage.AddMessage(context.Exception.Message);


        string allMessage = "";
        foreach (string message in _exceptionFilterMessage.Messages)
        {
            allMessage + = message;
        }
        context.Result = new ViewResult()
        {
            ViewData = new ViewDataDictionary(
                new EmptyModelMetadataProvider(),
                new ModelStateDictionary())
            {
                Model=allMessage
            }
        };
    }
}

Note that the CatchErrorMessage filter does not implement the Attribute class. We do not need to implement this class when using the [TypeFilter] attribute on the method.

The work of the CatchErrorMessage filter is relatively simple. It assigns the string to the model. To add messages, we use the IExceptionFilterMessage interface.

Now, we register the IExceptionFilterMessage service in the Program.cs class and add the following code after the AddControllersWithViews() method:

builder.Services.AddScoped<IExceptionFilterMessage, ExceptionFilterMessage>();

Now we apply this [TypeFilter] attribute to the Exception method

[TypeFilter(typeof(CatchErrorMessage))]
public IActionResult Exception(int? id)
{
    if (id == null)
        throw new Exception("Error Id cannot be null");
    else
        return View((Object)$"The value is {id}");
}

Now, run the application and enter the URL – /Home/Exception , an exception will be generated and we will see 3 error messages in the browser, as shown below:

65203e0573ca3a1436081b3f847bc884.png

2 ASP.NET Core global filter

Global filters can be automatically applied to each method of each controller. We do not need to apply global filters to each action method. We can set the filter as a global filter in the Program class.

We have created an Action filter named TimeElapsed in the previous article. Now we set it as a global filter. We need to make two configurations in Program.cs:

First: Set the filter as a service, so add the following code:

builder.Services.AddScoped<TimeElapsed>();

Second: Use the MvcOptions.Filters.AddService method to register a global filter

builder.Services.AddMvc().AddMvcOptions(options => {
    options.Filters.AddService(typeof(TimeElapsed));
});

Now create a new controller ShowController and add an Index method that returns a string:

namespace AspNetCore.Filters.Controllers
{
    public class ShowController : Controller
    {
        public string Index()
        {
            return "This is the Index action on the Show Controller";
        }
    }
}

We can see that we have not applied the TimeElapsed filter to this method. If we run the application and enter the URL – /Show, we will see a string displayed in the browser, which indicates that the global filter has worked.

26a0a8f6f438c03717a983e36e8f8352.png

3 ASP.NET Core filter execution order

Filters run in the specified order: authorization, action, result. If multiple types of filters are applied to the Controller and action methods, the Controller filter is executed first, and then the Action filter is executed. If there is a Global filter, then Controller filter is executed before, let us demonstrate through example, we create a new Result filter ShowMessage under the CustomFilters folder

namespace AspNetCore.Filters.CustomFilters
{
    public class ShowMessage : Attribute, IResultFilter
    {
        private string message;
        public ShowMessage(string msg)
        {
            message = msg;
        }
        public void OnResultExecuted(ResultExecutedContext context)
        {
        }
        public void OnResultExecuting(ResultExecutingContext context)
        {
            WriteMessage(context, message);
        }
        private void WriteMessage(FilterContext context, string msg)
        {
            byte[] bytes = Encoding.ASCII.GetBytes($"<div>{msg}</div>");
            context.HttpContext.Response.Body.WriteAsync(bytes, 0, bytes.Length);
        }
    }
}

This filter has a string type message and uses the constructor to initialize the property. The job of the filter is relatively simple. Write a piece of HTML to the browser. Now, we add the following code to set it as a global filter.

builder.Services.AddMvc().AddMvcOptions(options => {
    options.Filters.Add(new ShowMessage("Global"));
});

Next, create a new controller called OrderController and add this attribute to the Controller and Action methods:

namespace AspNetCore.Filters.Controllers
{
    [ShowMessage("Controller")]
    public class OrderController : Controller
    {
        [ShowMessage("Action")]
        public IActionResult Index()
        {
            return View();
        }
    }
}

Now run your project and go to the URL – /Order. Now we will see the Global string first, followed by the Controller and Action, this is because the Global filter is a special type of filter that is executed first, and then The filter is applied to the Controller and then applied to the action method

81c58837b7b7be1bd738b252c853e46c.png

4 Modify filter execution order

The execution order of specific types of filters can be implemented through the IOrderedFilter interface

namespace Microsoft.AspNetCore.Mvc.Filters
{
    public interface IOrderedFilter : IFilterMetadata
    {
        int Order { get; }
    }
}

This interface contains the public properties of Order. We can set the order by modifying this property. The one with the smallest Order value will be executed first. Now modify the ShowMessage filter to implement the IOrderedFilter interface and add a public int property:

9e457e24eb7c2c19bdae7a63931490fe.png

Now give the Order value to the [ShowMessage] attribute in the Order controller:

[ShowMessage("Controller", Order = 2)]
public class OrderController : Controller
{
   [ShowMessage("Action", Order = -1)]
   public IActionResult Index()
   {
       return View();
   }
}

We set the minimum value for the Action method. Now when we run the application and enter url-/Order, we will see that the Action filter is executed first, then the Global filter, and finally the Controller filter.

03eab41b9d44d3209435d52ce431424d.png

Source code address

https://github.com/bingbing-gui/Asp.Net-Core-Skill/tree/master/Fundamentals/AspNetCore.Filters/AspNetCore.Filters

references

https://www.yogihosting.com/advanced-filters-topics-aspnet-core/