Advanced Topics on Filters in ASP.NET Core – Filters with Dependencies, Global Filters, Order of Execution of Filters & Changing Filter Order

Advanced Topics on Filters in ASP.NET Core – Filters with Dependencies, Global Filters, Order of Execution of Filters & Changing Filter Order

Hope you have read my previous tutorial – Learn to use Filters in ASP.NET core from beginning to expert level.

Let’s start with these advanced filter topics – Filters with Dependencies, Global Filters, Order of Execution of Filters & Changing Filter Order.

Filters with Dependencies

So far you have seen Filter that do not have a dependency. If a filter has a dependency then it cannot be applied to the action method in the attribute manner. Instead the TypeFilter attribute is applied and is configured with the typeof filter. This is shown below:

[TypeFilter(typeof(FilterName))]

Let me show you this by an example.

Create a new class file called ‘FilterDependency.cs’ inside the ‘CustomFilters’ folder, and add to it an Interface called ‘IExceptionFilterMessage’ to store some message string for errors encountered in the action method:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;

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

Now, to the same FilterDependency.cs class add a new class called ‘ExceptionFilterMessage’ which implements the ‘IExceptionFilterMessage’ interface:

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;

namespace Filters.CustomFilters
{
    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 finally to the same FilterDependency.cs class, create an Exception filter called ‘CatchErrorMessage’. This Filter has a dependency on the IExceptionFilterMessage interface, and is resolved by the Dependency Injection feature.

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;

namespace Filters.CustomFilters
{
    // IExceptionFilterMessage
    
    //ExceptionFilterMessage

    public class CatchErrorMessage : IExceptionFilter
    {
        private IExceptionFilterMessage iExFilter;
        public CatchErrorMessage(IExceptionFilterMessage ex)
        {
            iExFilter = ex;
        }

        public void OnException(ExceptionContext context)
        {
            iExFilter.AddMessage("Exception Filter is called. ");
            iExFilter.AddMessage("Error Message is given below. ");
            iExFilter.AddMessage(context.Exception.Message);

            string allMessage = "";
            foreach (string message in iExFilter.Messages)
                allMessage += message;

            context.Result = new ViewResult()
            {
                ViewData = new ViewDataDictionary(new EmptyModelMetadataProvider(), new ModelStateDictionary())
                {
                    Model = allMessage
                }
            };
        }
    }
}

Notice that the CatchErrorMessage Filter does not implement the Attribute class. As there is no need for it since for applying it to the action method, I will use TypeFilter attribute.

The work of the CatchErrorMessage Filter is fairly simple, it adds some string messages to the Actions model.

For adding messages I have used the IExceptionFilterMessage interface.

Now, I have updated the Startup class to configure the service provider with the new Interface (IExceptionFilterMessage) and its implementation (ExceptionFilterMessage).

using Filters.CustomFilters;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

namespace Filters
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IExceptionFilterMessage, ExceptionFilterMessage>();
            services.AddMvc();
        }

        // removed for clarity
    }
}

I used the AddScoped extension method to configure the service provider, which means that when the request comes from the same PC then all the filters will receive the same ExceptionFilterMessage object.

All you have to do now is to apply this filter to the ‘Exception’ action method using the [[TypeFilter]] attribute:

[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 your application and go to the URL – ‘/Home/Exception’, which will generate an exception, and you will see the 3 error messages on your browser. See the below image:

filters with dependencies

Global Filters

Global Filters are those that are automatically applied to very action method of every controller. You don’t need to apply Global Filters as attributes to the action methods.

A filter can be made Global through the Startup.cs class.

I had created an Action Filter called ‘TimeElapsed’ in the above section.

To make it a global filter, go to the Startup.cs class and do the following 2 configurations in the ConfigureServices method:

1. Make the filter as a service.

<span class="code">services.AddScoped<TimeElapsed>();</span>.

2. Register it Globally using the MvcOptions.Filters.AddService method.

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

Other than following the 2 above steps, you can simple use the Add() method of the Filters class and in this case you don’t need to add the filter as a service.

The below code will make your filter a global one.

services.AddMvc().AddMvcOptions(options => {
    options.Filters.Add(new TimeElapsed());
});

Now create a new controller ‘Show’ and add an Index action method that returns a string:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;

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

You can clearly see that I haven’t applied the ‘TimeElapsed’ filter to it. But since the filter is global therefore when you run the application and go to the URL – ‘/Show’ then you will see the string ‘Elapsed time: 138.7952 ms’ added to the response (see below image). This shows the global filter has done it’s work.

Order of Execution of Filters

Filters run in a specific sequence: authorization, action, and then result. If multiple filters of a given type are applied to the Controller and also its action method then by default first the ‘Filter of the Controller’ will execute then the ‘Filter of the Action method’ will execute.

If the filter is also made global then the Global Filter will be executed before the Filter of the Controller.

Let me demonstrate this by an example. I create a new Result Filter (inside the ‘CustomFilters’ folder) by implementing the IResultFilter interface.

This filter has a message property of type string, and a constructor through which this property can be initialized with a string message. The work of this Filter is fairly simple, to write fragments of HTML to the response.

The filter code is:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Filters.CustomFilters
{
    public class ShowMessage : Attribute, IResultFilter
    {
        private string message;

        public ShowMessage(string msg)
        {
            message = msg;
        }

        public void OnResultExecuting(ResultExecutingContext context)
        {
            WriteMessage(context, message);
        }
        public void OnResultExecuted(ResultExecutedContext context)
        {
        }

        private void WriteMessage(FilterContext context, string msg)
        {
            byte[] bytes = Encoding.ASCII.GetBytes($"<div>{msg}</div>");
            context.HttpContext.Response.Body.Write(bytes, 0, bytes.Length);
        }
    }
}

Now, make this filter a global one by adding the below line code to the ConfigureServices method of the Startup.cs class:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddMvcOptions(options => {
        options.Filters.Add(new ShowMessage("Global"));
    });
}

Next, create a new controller called ‘OrderController’ and add this filter as an attribute to both the controller and the index action method:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Filters.CustomFilters;
using Microsoft.AspNetCore.Mvc;

namespace 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’. You will see the ‘Global’ string at the first, followed by ‘Controller’ and then comes the ‘Action’.

This is because the Global filter of a particular type executes at the first, then the filter applied on the Controller followed by the filter applied on the action method.

See the below image:

Filter Order

Changing Filter Order of Execution

The Order of execution of a particular type of filter can be changed by implementing the IOrderedFilter interface.

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

This interface contains a public property called ‘Order’ which is provided with an int value for setting the order.

The Filter having least Order value is executed first and so on.

Change the ‘ShowMessage’ filter to implement the IOrderedFilter interface, and add a public int Order property:

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Filters.CustomFilters
{
    public class ShowMessage : Attribute, IResultFilter, IOrderedFilter
    {
        public int Order { get; set; }

        // remains the same as before
    }
}

Now give the Order value to the ShowMessage attribute in the Order Controller:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Filters.CustomFilters;
using Microsoft.AspNetCore.Mvc;

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

I have given the Action method the lease order value.

Now when you run your application you will see the filter on the Action will be executed first, followed by global filter and lastly comes the controller filter.

See the below image to confirm:

changing filter order

You can download the source code using the below link:

Download

Conclusion

I hope you liked this tutorial on Filters. Please provide a thumbs-up by sharing this tutorial on Facebook, twitter and instagram.

Share this article -

yogihosting

ABOUT THE AUTHOR

This article has been written by the Technical Staff of YogiHosting. Check out other articles on "ASP.NET Core, jQuery, EF Core, SEO, jQuery, HTML" and more.