上次 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 中使用依赖注入主要有两种方式:
- ServiceFilterAttribute
- 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 的构造器:
总结
ActionFilterAttribute 的依赖注入可以通过 ServiceFilterAttribute,TypeFilterAttribute 来实现
ServiceFilterAttribute 是通过 DI 容器来管理 ActionFilterAttribute;TypeFilterAttribute 则是通过一个工厂直接实例化, 所以使用前不需要注册到 DI 容器中.
IsReusable 属性可以实现类似单例的功能, 但是运行时并不保证唯一单例.
TypeFilterAttribute 的 Arguments 属性可以作为参数列表. 当实例化 ActionFilterAttribute 的时候如果构造器参数类型没有在 DI 容器中注册那么会尝试从 Arguments 列表中取.
来源: https://www.cnblogs.com/kklldog/p/di-in-core-actionfilter.html