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

Let’s start with the Advanced filter TopicsFilters with Dependencies, Global Filters, Order of Execution of Filters & Changing Filter Order. I covered Filters on my previous tutorial called Learn to use Filters in ASP.NET core from beginning to expert level.

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 of your project. Next, add to it an Interface called IExceptionFilterMessage to store message strings for errors encountered in the action method. The code is given below.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

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;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

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;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
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);
    }

    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. The reason being there is no need for it since I will use [TypeFilter] attribute to apply it to the action method.

Do you want to learn everything about Views then my article on ASP.NET Core Views covers all about it. Do read it.

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

For adding messages I have used the IExceptionFilterMessage interface.

Now update the Startup class to configure the service provider with this new Interface called IExceptionFilterMessage and it’s implementation which is 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.AddControllersWithViews();
        }
 
        // removed for clarity
    }
}

I used the AddScoped Extension method to configure the service provider. This 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. You will see the 3 error messages on your browser as shown by the below image.

filters with dependencies

Next we check Global Filters.

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:

First : Make the filter as a service. So add the below code.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<TimeElapsed>();
    services.AddControllersWithViews();
}

Second : Register it Globally using the MvcOptions.Filters.AddService method.

public void ConfigureServices(IServiceCollection services)
{
    services.AddScoped<TimeElapsed>();
    services.AddMvc().AddMvcOptions(options => {
        options.Filters.AddService(typeof(TimeElapsed));
    });
    services.AddControllersWithViews();
}

Instead of following the 2 above steps, you can simple use the Add() method of the Filters class as shown below.

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc().AddMvcOptions(options => {
        options.Filters.Add(new TimeElapsed());
    });
    services.AddControllersWithViews();
}

Now create a new controller called ShowController 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 a global one, 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 it’s action methods then by default first the Filter of the Controller will execute then the Filter of the Action methods 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 called ShowMessage, 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 which is to write fragments of HTML to the response.

The filter code is:

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

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.WriteAsync(bytes, 0, bytes.Length);
        }
    }
}

Now, make this a global Filter 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.

In professional projects you need to harness the extreme power of Model Binding technique. I have covered all this in my separate article – Advanced Model Binding Concepts in ASP.NET Core

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.

Now 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 and go to the url – /Order. 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 Advanced Topics 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.