在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(); }
运行测试,效果如下:
可以看到,我们定义的过滤器捕获并打印了异常信息。