在MVC中,AOP是很常用的功能,我们经常会使用如 ActionFilter
,IAuthorizeFilter
等描述对Controller
和Action
进行约束和扩展,一般做法如下:
public class TestActionFilterAttribute : Attribute, IActionFilter
{
public void OnActionExecuted(ActionExecutedContext context)
{
if (context.HttpContext.Request.Query.TryGetValue("id", out StringValues value))
{
Console.WriteLine(value.First());
}
else
{
context.HttpContext.Response.Redirect("/Error/404");
}
}
public void OnActionExecuting(ActionExecutingContext context)
{ }
}
上面的代码很简单,就是判断请求中是否包含id
参数,如果有,则打印id
;如果没有,则跳转到错误页面。用法也很简单,在需要约束的Action
上添加[TestActionFilter]
即可。
[TestActionFilter]
public IActionResult Index()
{
return View();
}
这是Filter
最基本的用法,但是,如果我们需要在Filter
中使用注入的服务怎么办?比如说修改下 TestActionFilterAttribute
:
public class TestActionFilterAttribute : Attribute, IActionFilter
{
private readonly ILogger _logger;
public TestActionFilterAttribute(ILoggerFactory logger)
{
_logger = logger.CreateLogger("TestActionFilterAttribute");
}
public void OnActionExecuted(ActionExecutedContext context)
{
var path = context.HttpContext.Request.Path;
_logger.LogDebug($"{path} 开始运行了");
}
public void OnActionExecuting(ActionExecutingContext context)
{ }
}
我们在Filter
的构造函数中注入ILoggerFactory
参数,这是系统默认提供的日志工厂服务,用来在控制台打印消息。
回到Controller
文件,发现[TestActionFilter]
报错:未提供与"TestActionFilterAttribute
"的必需形参logger
对应的实参。好吧,下面我们尝试构造一个logger
对象
public class HomeController : Controller
{
private readonly ILoggerFactory _loggerFactory;
public HomeController(ILoggerFactory factory)
{
_loggerFactory = factory;
}
[TestActionFilter(_loggerFactory)]
public IActionResult Index()
{
return View();
}
}
修改过后,继续报错:特性构造函数参数"logger
"具有类型ILoggerFactory
,这不是有效特性参数类型。由此可见,如果在Filter
中需要注入服务,常规的方式是无法实现的。
如果一定需要调用注入服务该怎么实现呢?其实框架已经为我们提供了两种途径:TypeFilter
和ServiceFilter
public class TestTypeFilter : IActionFilter
{
private readonly ILogger _logger;
public TestTypeFilter(ILoggerFactory logger)
{
_logger = logger.CreateLogger("TestTypeFilter");
}
public void OnActionExecuted(ActionExecutedContext context)
{
var path = context.HttpContext.Request.Path;
_logger.LogDebug($"{path} 开始运行了");
}
public void OnActionExecuting(ActionExecutingContext context)
{ }
}
这里的代码和上面修改过的TestActionFilterAttribute
一模一样,修改下Controller
文件:
[TypeFilter(typeof(TestTypeFilter))]
public IActionResult Index()
{
return View();
}
运行测试,效果如下:
可以看到,代码运行正常。
下面再看看ServiceFilter
的用法,新建文件 TestServiceFilter
public class TestServiceFilter : IActionFilter
{
private readonly ILogger _logger;
public TestServiceFilter(ILoggerFactory logger)
{
_logger = logger.CreateLogger("TestServiceFilter");
}
public void OnActionExecuted(ActionExecutedContext context)
{
var path = context.HttpContext.Request.Path;
_logger.LogDebug($"{path} 开始运行了");
}
public void OnActionExecuting(ActionExecutingContext context)
{ }
}
修改Controller
文件:
//[TypeFilter(typeof(TestTypeFilter))]
[ServiceFilter(typeof(TestServiceFilter))]
public IActionResult Index()
{
return View();
}
仅仅这样是不够的,顾名思义,ServiceFilter
(服务过滤器),我们需要到startup.cs
的ConfiguraionServices
中注册TestServiceFilter
:
services.AddSingleton<TestServiceFilter>();
运行测试,效果如下:
OK,运行正常!
下面是补充内容,添加一个全局异常过滤器:
新建文件 MvcGlobalExceptionFilter.cs
public class MvcGlobalExceptionFilter : IExceptionFilter
{
private readonly ILogger _logger;
public MvcGlobalExceptionFilter(ILoggerFactory logger)
{
_logger = logger.CreateLogger("MvcGlobalExceptionFilter");
}
public void OnException(ExceptionContext context)
{
// 全局异常的错误处理
_logger.LogError(context.Exception, "全局异常");
}
}
修改Startup.cs
中的ConfigurationServices
:
services.AddMvc(options =>
{
// 添加全局异常
options.Filters.Add<MvcGlobalExceptionFilter>();
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
修改Controller
文件,手动抛出异常:
[ServiceFilter(typeof(TestServiceFilter))]
public IActionResult Index()
{
throw new Exception("异常测试,这是手动抛出的异常");
return View();
}
运行测试,效果如下:
可以看到,我们定义的过滤器捕获并打印了异常信息。
ASP.NET Core-ActionFilter
实现依赖注入(ServiceFilterAttribute
、TypeFilterAttribute
)
一、简介
前几篇文章都是讲ASP.NET Core MVC中的依赖注入(DI)与扩展点的,也许大家都发现在ASP.NET CORE中所有的组件都是通过依赖注入来扩展的,而且面向一组功能就会有一组接口或抽象工厂来扩展功能,就如IControllerActivator
这样的功能点在上篇文章(查看.NET Core源代码通过Autofac
实现依赖注入到Controller
属性)中也提到了,今天我们主要介绍一个大类似的扩展点,ASP.NET Core MVC中为我们提供了新的机制为Action Filters
(也就是过滤器)进行依赖注入的扩展。
二、过滤器依赖注入
在ASP.NET Core MVC中,框架中为我们提供了类型为 IFilter
的 Attributes
来装饰Action
,用于拦截Action
请求,这有在以前的版本中就有了,但是如果我们想结合依赖注入使用的话要使用IFilterFactory
接口来扩展Action Filter
的创建过程。
2.1 IFilterFactory
接口定义
public interface IFilterFactory : IFilter
{
IFilter CreateInstance([NotNull] IServiceProvider serviceProvider);
}
我们想要创建一个Filter Attribute
并需要依赖注入的话一般想要的代码为:
public class FilterClass : ActionFilterAttribute
{
public FilterClass(IDependency1 dependency1, IDependency2 dependency2)
{
// ...use dependencies
}
}
ASP.NET Core MVC中为我们提供了两种简单的IFilterFactory
: ServiceFilterAttribute
和 TypeFilterAttribute
。来个例子看看怎么使用。
public class HomeController: Controller
{
[TypeFilter(typeof(FilterClass))]
[ServiceFilter(typeof(FilterClass))]
public IActionResult Index()
{
return View();
}
}
2.2 ServiceFilterAttribute
其实看到名字,有些朋友就能想到了,它是基于依赖注入的一个IFilterFactory
,Service
这个词强化了它是一个通过获取服务来实现依赖注入的,大家想到了什么?是不是GetService()
? 没错,其实它的机制就是这个。
要使用ServiceFilter
就必须在依赖注入容器里注册对应的类型,比如下面的例子就要先将FilterClass
类型注册到IOC
容器里。
public void ConfigureServices(IServiceCollection services)
{
services.AddSingleton<FilterClass>();
services.AddMvc()
}
当然如果FilterClass
类型的构造器需要注入类型时,也需要在IOC
容器里进行注册才可以使用。
我们来看下ServiceFilterAttribute
的源代码:
public class ServiceFilterAttribute : Attribute, IFilterFactory, IOrderedFilter
{
public ServiceFilterAttribute([NotNull] Type type)
{
ServiceType = type;
}
public Type ServiceType { get; private set; }
public int Order { get; set; }
public IFilter CreateInstance([NotNull] IServiceProvider serviceProvider)
{
var service = serviceProvider.GetRequiredService(ServiceType);
var filter = service as IFilter;
if (filter == null)
{
throw new InvalidOperationException(Resources.FormatFilterFactoryAttribute_TypeMustImplementIFilter(
typeof(ServiceFilterAttribute).Name,
typeof(IFilter).Name));
}
return filter;
}
}
2.3 TypeFilterAttribute
当然你也可以选择使用这个类似于ServiceFilter
过滤器的TypeFilter
过滤器,它也同样实现了IFilterFactory
接口,并可以通过它创建出可使用依赖注入的过滤器来。之所以叫TypeFilter
就是因为它并不需要在依赖注入容器里注册类型就能创建出过滤器, 我们来看下它的代码:
public class TypeFilterAttribute : Attribute, IFilterFactory, IOrderedFilter
{
private ObjectFactory factory;
public TypeFilterAttribute([NotNull] Type type)
{
ImplementationType = type;
}
public object[] Arguments { get; set; }
public Type ImplementationType { get; private set; }
public int Order { get; set; }
public IFilter CreateInstance([NotNull] IServiceProvider serviceProvider)
{
if (this.factory == null)
{
var argumentTypes = Arguments?.Select(a => a.GetType())?.ToArray();
this.factory = ActivatorUtilities.CreateFactory(ImplementationType, argumentTypes ?? Type.EmptyTypes);
}
return (IFilter)this.factory(serviceProvider, Arguments);
}
}
三、结语
相信看过上一篇文章的朋友都注意到了ServiceProvider
和ActivatorUtilities
的不同,本文中的ServiceFilterAttribute
和 TypeFilterAttribute
原理上也是通过它们来创建Filter
的,所以使用场景就看大家如何来使用。其实最近看.NET Core的源代码,看到的到处都是接口、工厂使用依赖注入形成扩展点的例子,其实微软以前代码的扩展点也挺多的,只是API并不那么开放,ASP.NET Core中我们看到了一个"开放"的框架。