ASP.NET Core-Use dependency injection in ActionFilter

ASP.NET Core-Use dependency injection in ActionFilter

When our ActionFilter needs to use a Service, we usually inject it through the constructor. To demonstrate, first customize an ActionFilter and inject IMyService through the constructor:

    public interface IMyService
    {
        string GetServiceName(); 
    }

    public class MyService: IMyService
    {
        public MyService ()
        {
            Console.WriteLine("Service {0} created .", GetServiceName());
        }

        public string GetServiceName()
        {
            return "MyService";
        }
    }

    public class FilterInjectAttribute: ActionFilterAttribute
    {
        public FilterInjectAttribute(IMyService myService)
        {
            if (myService == null)
            {
                throw new ArgumentNullException("myService");
            }

            Console.WriteLine("Service {0} was injected .", myService.GetServiceName());
        }
    }

But when we use Attribute, VS will give a red prompt directly, we need to pass in the parameters of the constructor, otherwise it will not be compiled.

Of course, we can directly use a new MyService as a parameter, but obviously this will lose the benefits of injection.

Use dependency injection in ActionFilter

There are two main ways to use dependency injection in ASP.NET Core's ActionFilter:

  1. ServiceFilterAttribute
  2. TypeFilterAttribute

ServiceFilterAttribute

Use ServiceFilterAttribute to make your ActionFilter complete dependency injection. In fact, it is to register the ActionFilter you want to use as a Service in the DI container. Retrieve your ActionFilter from the container through ServiceFilter and inject it where needed. So the first step is to register your ActionFilter:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IMyService,MyService>();
            services.AddScoped(typeof(FilterInjectAttribute));

            services.AddControllers();
            services.AddRazorPages();
        }

Then create a new Controller and use ServiceFilter on Action:

        [ServiceFilter(typeof(FilterInjectAttribute))]
        public string DI()
        {
            Console.WriteLine("HomeController method DI running .");

            return "DI";
        }

Run it, visit the corresponding path in the browser, you can see that MyService has been injected into FilterInjectAttribute:

IsReusable attribute of ServiceFilterAttribute:

ServiceFilter has a property called IsReusable. The literal meaning is also easy to understand, that is, whether it is reusable. Obviously if this property is set to True, then multiple requests will reuse this ActionFilter, which is a bit like a singleton.

        [ServiceFilter(typeof(FilterInjectAttribute), IsReusable = true)]
        public string DI()
        {
            Console.WriteLine("HomeController method DI running .");

            return "DI";
        }

Run it and visit the path of the corresponding action in the browser several times. You can see that the constructor of FilterInjectAttribute will only be executed once.

Here is an important note, ASP.NET Core runtime does not guarantee that this filter is a true singleton . So don't try to use this attribute to implement a singleton, and the business system depends on this singleton.

TypeFilterAttribute

Using TypeFilterAttribute can also make your ActionFilter complete dependency injection. It is similar to ServiceFilterAttribute, but the ActionFilter injected with TypeFilterAttribute does not look up from the DI container, but directly instantiates the object through Microsoft.Extensions.DependencyInjection.ObjectFactory. So our FilterInjectAttribute does not need to be registered in the DI container in advance. First comment out the registration code of FilterInjectAttribute:

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IMyService,MyService>();

           //services.AddScoped(typeof(FilterInjectAttribute));

            services.AddControllers();
            services.AddRazorPages();
        }

Use TypeFilterAttribute instead:

        [TypeFilter(typeof(FilterInjectAttribute))]
        public string DI()
        {
            Console.WriteLine("HomeController method DI running .");

            return "DI";
        }

Run it, visit the corresponding path in the browser, you can see that MyService has been injected into FilterInjectAttribute:

IsReusable attribute of TypeFilterAttribute:

Like the ServiceFilter above, ASP.NET Core runtime does not guarantee that this filter is a true singleton , so I won't be too long-winded here.

Arguments attribute of TypeFilterAttribute:

The Arguments parameter is an important difference between TypeFilterAttribute and ServiceFilterAttribute. ServiceFilterAttribute does not have this attribute. The Arguments type is an object array. For the ActionFilter instantiated by TypeFilterAttribute, if the parameter type in its constructor cannot be found in the DI container, it will continue to be obtained in order in the Arguments parameter list. Change the FilterInjectAttribute constructor to add two more parameters, and ensure that these two parameters cannot be taken from DI:

    public class FilterInjectAttribute: ActionFilterAttribute
    {
        public FilterInjectAttribute(string arg1, IMyService myService, string arg2)
        {
            if (myService == null)
            {
                throw new ArgumentNullException("myService");
            }

            Console.WriteLine("Service {0} was injected .", myService.GetServiceName());
            Console.WriteLine("arg1 is {0} .", arg1);
            Console.WriteLine("arg2 is {0} .", arg2);

            Console.WriteLine("FilterInjectAttribute was created .");
        }
    }

When using, pass in two parameters:

        [TypeFilter(typeof(FilterInjectAttribute), Arguments = new object[] {"HAHA", "HOHO" })]
        public string DI()
        {
            Console.WriteLine("HomeController method DI running .");

            return "DI";
        }

Run it and see that two parameters are passed into the constructor of FilterInjectAttribute:

summary

  1. Dependency injection of ActionFilterAttribute can be achieved through ServiceFilterAttribute and TypeFilterAttribute
  2. ServiceFilterAttribute manages the ActionFilterAttribute through the DI container; TypeFilterAttribute is directly instantiated through a factory, so it does not need to be registered in the DI container before use.
  3. The IsReusable property can achieve a function similar to a singleton, but the runtime does not guarantee a unique singleton.
  4. The Arguments attribute of TypeFilterAttribute can be used as a parameter list. When the ActionFilterAttribute is instantiated, if the constructor parameter type is not registered in the DI container, it will try to get it from the Arguments list.
Reference: https://cloud.tencent.com/developer/article/1604695 ASP.NET Core-Use dependency injection in ActionFilter-Cloud + Community-Tencent Cloud