上次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
列表中取。
在ASP.NET Core的过滤器中使用依赖注入
using Microsoft.Extensions.DependencyInjection;
public class ThrottleFilterAttribute : Attribute, IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
var cache = context.HttpContext.RequestServices.GetService<IDistributedCache>();
...
}
...
}