这节我们讨论过滤器高级话题 - 过滤器依赖,全局过滤器,过滤器执行顺序&改变过滤器顺序,在上一节我们介绍了在ASP.NET Core过滤器的基本使用方式
1 ASP.NET Core 过滤器依赖注入
如果一个过滤器有一个依赖,它不能使用特性的方式应用到action 方法上 , 而是使用TypeFilter特性,代码如下:
[TypeFilter(typeof(FilterName))]
通过一个例子理解,创建一个新的类FilterDependency.cs在你的项目CustomFilters文件夹下,IExceptionFilterMessage的接口中定义一个message字符串存储在action方法中发生的错误 ,代码如下:
public interface IExceptionFilterMessage
{
IEnumerable<string> Messages { get; }
void AddMessage(string message);
}
在FilterDependency.cs 文件添加ExceptionFilterMessage类并且继承IExceptionFilterMessage 接口
public interface IExceptionFilterMessage
{
IEnumerable<string> Messages { get; }
void AddMessage(string message);
}
public class ExceptionFilterMessage : IExceptionFilterMessage
{
private List<string> messages = new List<string>();
public IEnumerable<string> Messages => messages;
public void AddMessage(string message) => messages.Add(message);
}
现在,添加一个异常过滤器叫CatchErrorMessage,这个过滤器依赖于IExceptionFilterMessage接口并且通过依赖注入特性解析
public interface IExceptionFilterMessage
{
IEnumerable<string> Messages { get; }
void AddMessage(string message);
}
public class ExceptionFilterMessage : IExceptionFilterMessage
{
private List<string> messages = new List<string>();
public IEnumerable<string> Messages => messages;
public void AddMessage(string message) => messages.Add(message);
}
public class CatchErrorMessage : IExceptionFilter
{
private IExceptionFilterMessage _exceptionFilterMessage;
public CatchErrorMessage(IExceptionFilterMessage exceptionFilterMessage)
{
_exceptionFilterMessage = exceptionFilterMessage;
}
public void OnException(ExceptionContext context)
{
_exceptionFilterMessage.AddMessage("Exception Filter is called. ");
_exceptionFilterMessage.AddMessage("Error Message is given below. ");
_exceptionFilterMessage.AddMessage(context.Exception.Message);
string allMessage = "";
foreach (string message in _exceptionFilterMessage.Messages)
{
allMessage += message;
}
context.Result = new ViewResult()
{
ViewData = new ViewDataDictionary(
new EmptyModelMetadataProvider(),
new ModelStateDictionary())
{
Model = allMessage
}
};
}
}
注意CatchErrorMessage 过滤器没有实现Attribute类,我们在方法上使用[TypeFilter]特性时不需要实现该类
CatchErrorMessage 过滤器的工作比较简单,将字符串赋值给model,针对添加消息我们使用IExceptionFilterMessage接口
现在,我们在Program.cs类中注册IExceptionFilterMessage服务,在AddControllersWithViews() 方法之后添加下面代码:
builder.Services.AddScoped<IExceptionFilterMessage, ExceptionFilterMessage>();
现在我们将这个[TypeFilter]特性应用到Exception方法
[TypeFilter(typeof(CatchErrorMessage))]
public IActionResult Exception(int? id)
{
if (id == null)
throw new Exception("Error Id cannot be null");
else
return View((Object)$"The value is {id}");
}
现在,运行应用程序并且进入URL - /Home/Exception , 将会生成一个异常,我们将会在浏览器中看到3个错误的消息,具体显示如下:
2 ASP.NET Core 全局过滤器
全局过滤器可以自动应用到每个控制器的每个方法上面,我们不需要将全局过滤器应用到每个action方法,我们可以在Program类中将过滤器设置为全局过滤器
我们已经创建了一个名字为TimeElapsed的Action过滤器在前面的文章中,现在我们将它设置为全局过滤器,我们需要在Program.cs中做两个配置:
第一:将过滤器设置为服务,因此添加下面代码:
builder.Services.AddScoped<TimeElapsed>();
第二:使用MvcOptions.Filters.AddService方法注册全局过滤器
builder.Services.AddMvc().AddMvcOptions(options => {
options.Filters.AddService(typeof(TimeElapsed));
});
现在创建一个新的控制器ShowController并且添加Index方法,返回一个字符串:
namespace AspNetCore.Filters.Controllers
{
public class ShowController : Controller
{
public string Index()
{
return "This is the Index action on the Show Controller";
}
}
}
我们能看到我们没有应用TimeElapsed过滤器到该方法,运行应用程序并进入URL- /Show, 我们会看到一个string字符串显示在浏览器中,这表示全局过滤器已经工作
3 ASP.NET Core 过滤器执行顺序
过滤器按照指定的顺序运行:authorization、action、result,如果多个类型的过滤器应用到Controller和action方法上,首先执行Controller的过滤器,接着执行Action的过滤器,如果有Global过滤器,在Controller过滤器之前执行,让我们通过例子来演示,我们在CustomFilters文件夹下创建一个新的Result过滤器ShowMessage
namespace AspNetCore.Filters.CustomFilters
{
public class ShowMessage : Attribute, IResultFilter
{
private string message;
public ShowMessage(string msg)
{
message = msg;
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
public void OnResultExecuting(ResultExecutingContext context)
{
WriteMessage(context, message);
}
private void WriteMessage(FilterContext context, string msg)
{
byte[] bytes = Encoding.ASCII.GetBytes($"<div>{msg}</div>");
context.HttpContext.Response.Body.WriteAsync(bytes, 0, bytes.Length);
}
}
}
这个过滤器有一个字符串类型的消息并且使用构造函数初始化该属性,过滤器的工作相对简单,写一段HTML到浏览器,现在,我们添加下面代码将其设置为global的过滤器
builder.Services.AddMvc().AddMvcOptions(options => {
options.Filters.Add(new ShowMessage("Global"));
});
接下来,创建一个新的控制器叫OrderController并且将该特性添加到Controller和Action方法:
namespace AspNetCore.Filters.Controllers
{
[ShowMessage("Controller")]
public class OrderController : Controller
{
[ShowMessage("Action")]
public IActionResult Index()
{
return View();
}
}
}
现在运行你项目并且进入URL - /Order. 现在我们将看到Global字符串在第一位,接着是Controller和Action,这是因为Global过滤器是特殊类型的过滤器,在第一个执行,接着过滤器应用到Controller ,再接着应用到action方法
4 修改过滤器执行顺序
特定类型过滤器的执行顺序能够通过IOrderedFilter接口实现
namespace Microsoft.AspNetCore.Mvc.Filters
{
public interface IOrderedFilter : IFilterMetadata
{
int Order { get; }
}
}
这个接口包含Order的公共属性,我们可以通过修改该属性来设置顺序,Order值最小的先执行, 现在修改ShowMessage过滤器实现IOrderedFilter接口并且添加一个公共int属性:
现在给Order值给[ShowMessage]特性在Order控制器中:
[ShowMessage("Controller", Order = 2)]
public class OrderController : Controller
{
[ShowMessage("Action", Order = -1)]
public IActionResult Index()
{
return View();
}
}
我们给Action方法设置最小值,现在当我们运行应用程序并且进入url-/Order,我们将看到Action过滤器首先被执行,接着是Global过滤器,最后是Controller过滤器
源代码地址
https://github.com/bingbing-gui/Asp.Net-Core-Skill/tree/master/Fundamentals/AspNetCore.Filters/AspNetCore.Filters
参考文献
https://www.yogihosting.com/advanced-filters-topics-aspnet-core/