1. 程式人生 > >ASP.NET Core - 在ActionFilter中使用依賴注入

ASP.NET Core - 在ActionFilter中使用依賴注入

上次ActionFilter引發的一個EF異常,本質上是對Core版本的ActionFilter的知識掌握不夠牢固造成的,所以花了點時間仔細閱讀了微軟的官方文件。發現除了IActionFilter、IAsyncActionFilter的問題,還有一個就是依賴注入在ActionFilter上的使用也是需要注意的地方。
當我們的ActionFilter需要使用某個Service的時候,我們一般會通過建構函式注入。
演示一下,首先自定義一個ActionFilter,通過建構函式注入IMyService:

    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());
        }
    }

但是我們在使用Attribute的時候VS直接給出紅色提示,需要傳入建構函式的引數,否則無法編譯過去。

當然我們可以直接new一個MyService來當做引數,但是很顯然這樣就失去了注入的那些好處了。

在ActionFilter中使用依賴注入

在ASP.NET Core的ActionFilter中使用依賴注入主要有兩種方式:

  1. ServiceFilterAttribute
  2. TypeFilterAttribute

    ServiceFilterAttribute

    使用ServiceFilterAttribute可以使你的ActionFilter完成依賴注入。其實就是把你要用的ActionFilter本身註冊為一個Service註冊到DI容器中。通過ServiceFilter從容器中檢索你的ActionFilter,並且注入到需要的地方。所以第一步就是要註冊你的ActionFilter:
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddScoped<IMyService,MyService>();
            services.AddScoped(typeof(FilterInjectAttribute));

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

然後新建一個Controller,在Action上使用ServiceFilter:

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

            return "DI";
        }

執行一下,在瀏覽器裡訪問下對應的path,可以看到MyService已經注入到FilterInjectAttribute中:

ServiceFilterAttribute的IsReusable屬性:

ServiceFilter有一個屬性叫IsReusable。從字面意思也很好理解,就是是否可重用的意思。顯而易見如果這個屬性設定為True,那麼多個請求就會複用這個ActionFilter,這就有點像是單例的意思了。

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

            return "DI";
        }

執行一下,多次在瀏覽器中訪問對應的action的path,可以看到FilterInjectAttribute的建構函式只會執行一次。

這裡有一個重要提示, ASP.NET Core runtime 並不保證這個filter是真正的單例。所以不要試圖使用這個屬性來實現單例,並且業務系統依賴這個單例。

TypeFilterAttribute

使用TypeFilterAttribute也可以使你的ActionFilter完成依賴注入。它跟ServiceFilterAttribute差不多,但是使用TypeFilterAttribute注入的ActionFilter並不從DI容器中查詢,而是直接通過Microsoft.Extensions.DependencyInjection.ObjectFactory來例項化物件。所以我們的FilterInjectAttribute不需要提前註冊到DI容器中。
首先註釋掉FilterInjectAttribute的註冊程式碼:

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

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

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

改用TypeFilterAttribute:

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

            return "DI";
        }

執行一下,在瀏覽器裡訪問下對應的path,可以看到MyService已經注入到FilterInjectAttribute中:

TypeFilterAttribute的IsReusable屬性:

跟上面的ServiceFilter一樣,ASP.NET Core runtime 並不保證這個filter是真正的單例,這裡就不多囉嗦了。

TypeFilterAttribute的Arguments屬性:

Arguments引數是TypeFilterAttribute跟ServiceFilterAttribute的一個重要區別,ServiceFilterAttribute並沒有這屬性。Arguments型別為object陣列。通過TypeFilterAttribute例項化的ActionFilter,如果它的構造器中的引數型別在DI容器中找不到,會繼續在Arguments引數列表裡按順序獲取。
改一下FilterInjectAttribute構造器多加入2個引數,並且保證這2個引數無法從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 .");
        }
    }

在使用的時候傳入兩個引數:

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

            return "DI";
        }

執行一下看到兩個引數被傳入了FilterInjectAttribute的構造器:

總結

  1. ActionFilterAttribute的依賴注入可以通過ServiceFilterAttribute,TypeFilterAttribute來實現
  2. ServiceFilterAttribute是通過DI容器來管理ActionFilterAttribute;TypeFilterAttribute則是通過一個工廠直接例項化,所以使用前不需要註冊到DI容器中。
  3. IsReusable屬性可以實現類似單例的功能,但是執行時並不保證唯一單例。
  4. TypeFilterAttribute的Arguments屬性可以作為引數列表。當例項化ActionFilterAttribute的時候如果構造器引數型別沒有在DI容器中註冊那麼會嘗試從Arguments列表中取。