ASP.NET Core 中的过滤器运行在请求处理管道特定阶段前或者后,ASP.NET Core内置了很多过滤器,例如:授权,日志,缓存,异常处理等等,过滤器可以避免我们项目中出现重复的代码
ASP.NET Core 中间件和过滤器有什么不同呢?中间件可以操作进入.NET Core应用程序的每一个请求,另外过滤器仅仅操作进入 MVC 管道的请求,中间件不能访问HttpContext,除非我们预先添加进去值,请查看该链接https://www.thetechplatform.com/
post/middleware-and-filters-power-in-asp-net-core
1 创建例子项目
我们通过一个例子来了解过滤器, 在Visual Studio创建一个MVC项目,命名为AspNetCore.Filters
2 内置过滤器-[RequireHttps]
[RequireHttps] 特性是内置的Filter,使用在控制器或者方法上阻止非HTTPS的请求,创建一个MVC模版的应用程序,它自动将所有向非https端点发出的请求重定向到它们各自的https端点,在Properties目录下打开lauchSettings.json文件,在applicationUrl属性下有2个urls,第二个url是非https的,在我们应用程序中为http://localhost:5192
现在,运行应用程序并且在浏览器打开一个非https的url地址,我们将看到浏览器立即导航我们到一个https的url地址,为了理解这个[RequireHttps]过滤器,我们打开Program.cs文件,并且注释掉如下代码:
//app.UseHttpsRedirection();
现在,重新运行应用程序并且打开非https的url在浏览器,这次它不会跳转,接下来,我们修改Home控制器中的Index方法返回如下字符串:
public string Index()
{
return "This is the Index action on the Home controller";
}
现在,重新运行应用程序并且打开非https的url在浏览器,这次它不会跳转,我们将看到下面消息显示在浏览器上:
现在让这个方法仅仅能接收HTTPS的请求,我们将[RequireHttps]特性添加到方法上
[RequireHttps]
public string Index()
{
return "This is the Index action on the Home controller";
}
现在我们限制这个方法仅仅能访问HTTPS的请求,我们将[RequireHttps]特性添加到方法上,重新运行应用程序并且再次打开非https url,我们将获取如下页面
我们尝试调用action方法使用非https请求,但是[RequireHttps]阻止了该行为
了解如何进行跳转并且我们如何使用RequireHttps过滤我们的请求,[RequireHttps]内置过滤器也能应用到Controllers类上, 在这种情况下所有控制器的方法将获取这个特性并且将阻止所有非https请求,在下面例子中,所有的3个action方法将获取RequireHttps特性
[RequireHttps]
public class HomeController : Controller
{
public string Index()
{
return "This is the Index action";
}
public string List()
{
return "This is the List action";
}
public string Hello()
{
return "This is the Hello action";
}
}
3 ASP.NET Core MVC中常用过滤器
在.NET Core中有多少类型的过滤器呢?在.NET Core最常用的由4中类型过滤器Authorization, Action, Result 和 Exception,每个过滤器可以同步和异步两种工作方式,下面表格描述他们详细
过滤器 | 接口 | 描述 |
Authorization | IAuthorizationFilter IAsyncAuthorizationFilter | 使用它申请授权和安全策略 |
Action | IActionFilter, IAsyncActionFilter | 在action方法之前或者之后指定执行的具体工作 |
Result | IResultFilter, IAsyncResultFilter | 用于在操作方法的结果之前或之后立即执行指定工作 |
Exception | IExceptionFilter, IAsyncExceptionFilter | 使用它来处理异常 |
如何在.NET Core中创建自定义过滤器?我们创建一个自定义过滤器继承自Attribute与此同时还必须继承与其匹配的过滤器接口,接下来,我们将过滤器应用到Controller或者Action方法,注意每个过滤器只能够实现Synchronous或者Asynchronous两个接口中的一个
4 过滤器执行顺序
过滤器按照下面顺序执行:
1 Authorization 过滤器第一个执行
2 Action过滤器其次
3 Result过滤器最后执行
Exception过滤器只有在发生异常时执行
5 ASP.NET Core MVC Authorization 过滤器
什么是Authorization过滤器? Authorization 过滤器被使用针对授权和创建安全策略,在这些过滤器中,它是第一个在管道中运行的过滤器,授权过滤器实现了 IAuthorizationFilter或者IAsyncAuthorizationFilter 接口
IAuthorizationFilter 接口定义
public interface IAuthorizationFilter : IFilterMetadata
{
void OnAuthorization(AuthorizationFilterContext context);
}
IAsyncAuthorizationFilter 接口定义如下:
public interface IAsyncAuthorizationFilter : IFilterMetadata
{
Task OnAuthorizationAsync(AuthorizationFilterContext context);
}
IAsyncAuthorizationFilter接口使用创建异步的授权过滤器,接口的方法是- OnAuthorization() 和 OnAuthorizationAsync() 写代码授权进入的请求,参数-AuthorizationFilterContext 表示接收的描述请求的上下文数据,AuthorizationFilterContext 包含一个属性名字为Result的属性(类型是IActionResult),授权过滤器会设置该属性
5.1 自定义Authorization过滤器
让我们创建一个自定义授权过滤器,例如限制非https协议的请求,这个过滤器能被应用到任何Action方法或者Controller类上,创建一个文件夹叫CustomFilters 在应用程序的根目录下并且添加一个新类命名为HttpOnly.cs,接下来添加如下代码:
namespace AspNetCore.Filters.CustomFilters
{
public class HttpsOnly : Attribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationFilterContext context)
{
if (!context.HttpContext.Request.IsHttps)
context.Result = new StatusCodeResult(StatusCodes.Status403Forbidden);
}
}
}
注意HttpsOnly类继承自Attribute类,为了能够在Controllers和Action方法上使用该特性,同时也继承了IAuthorizationFilter接口为了创建一个Authorization过滤器
在OnAuthorization方法内实现我们想要做的工作,判断进入的请求是否是https,当请求是非https,我们将设置context.Result为403 forbidden,这将阻止Action或者Controller执行,用户将在浏览器中看到forbidden错误消息
现在应用HttpsOnly特性在HomeControllers.cs的Index方法:
[HttpsOnly]
public string Index()
{
return "This is the Index action on the Home controller";
}
最后,运行应用程序并且打开非https地址,我们将会看到HTTP ERROR 403, 这是因为我们请求只允许非https url
当我们在浏览器中打开https的url时,Authorization过滤器不会阻止执行,我们不会设置AuthorizationFilterContext的Result属性,Index方法内部的代码将会执行,这个例子很好解释了授权过滤器的工作原理,接下来我们将创建一个Action 过滤器
6 ASP.NET Core MVC Action 过滤器
什么是Action过滤器? Action过滤器在一个Action 方法的前或者后执行,该过滤器位于管道的第二位执行,在授权过滤器之后执行,Action过滤器继承自IActionFilter或者IAsyncActionFilter接口的任何一个
IActionFilter接口定义如下:
public interface IActionFilter : IFilterMetadata
{
void OnActionExecuting(ActionExecutingContext context);
void OnActionExecuted(ActionExecutedContext context);
}
我们在Action方法上使用Action 过滤器时,OnActionExecuting在Action方法执行之前被调用,OnActionExecuted在Action方法执行之后被调用
OnActionExecuting 方法有一个ActionExecutingContext的参数类型,ActionExecutingContext对象有重要的属性如下:
名称 | 描述 |
Controller | 将要调用的Action方法的控制器的名称 |
Result | 当这个属性设置为IActionResult的值时,框架会呈现IActionResult,阻止调用Action方法 |
OnActionExecuted 方法有一个ActionExecutedContext 类型的参数,ActionExecutedContext 重要的属性如下:
名称 | 描述 |
Controller | 将要调用的Action方法的控制器的名称 |
Exception | 包含了在Action方法中发生的异常 |
ExceptionHandled | 将该属性设置为true时,阻止异常传播 |
Result | 返回IActionResult,我们可以用自己的业务逻辑来修改或者替换它 |
6.1 自定义Action过滤器
现在将创建一个Action的过滤器,用他们测量Action方法的执行时间,我们在OnActionExecuting方法中启动一个timer并且在OnActionExecuted停止该方法
创建一个TimeElapsed.cs类在CustomFilters文件夹下并且添加如下代码:
namespace AspNetCore.Filters.CustomFilters
{
public class TimeElapsed : Attribute, IActionFilter
{
private Stopwatch timer;
public void OnActionExecuting(ActionExecutingContext context)
{
timer.Start();
}
public void OnActionExecuted(ActionExecutedContext context)
{
timer.Stop();
string result = " Elapsed time: " + $" {timer.Elapsed.TotalMilliseconds} ms";
IActionResult iActionResult = context.Result;
((ObjectResult)iActionResult).Value += result;
}
}
}
创建Stopwatch类计算方法的执行时间, 在OnActionExecuting方法中开始执行,在OnActionExecuted方法中结束执行,接下使用ActionExecutedContext对象的Result属性获取到Action方法的执行结果,他包含了一个我们之前在Home控制器的Index方法设置的字符串,最后,我们转换它到ObjectResult类型并且添加时间到它的Value属性,为了使用filter,添加[TimerElapsed]特性到Home控制器的Index 方法,显示如下:
namespace AspNetCore.Filters.Controllers
{
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
public HomeController(ILogger<HomeController> logger)
{
_logger = logger;
}
[HttpsOnly]
[TimeElapsed]
public string Index()
{
return "This is the Index action on the Home controller";
}
}
}
运行结果如下:
Action过滤器也能通过继承自IAsyncActionFilter接口创建,接口定义如下:
public interface IAsyncActionFilter : IFilterMetadata
{
Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next);
}
我们看到该接口中只有一个方法,ActionExecutingContext对象提供了上下文对象,ActionExectionDelegate 表示一个action方法(或者下一个Filter)
现在我们使用异步版本的接口重新创建一个TimeElapsed过滤器,代码如下:
namespace AspNetCore.Filters.CustomFilters
{
public class TimeElapsedAsync : Attribute, IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context,
ActionExecutionDelegate next)
{
Stopwatch stopwatch = Stopwatch.StartNew();
await next();
stopwatch.Stop();
string result = "<div>Elapsed time: "
+ $"{stopwatch.Elapsed.TotalMilliseconds} ms</div>";
byte[] bytes = Encoding.ASCII.GetBytes(result);
await context.HttpContext.Response.Body.WriteAsync(bytes, 0, bytes.Length);
}
}
}
7 ASP.NET Core Result 过滤器
在Action方法成功执行前或者后会执行Result过滤器,Result过滤器位于过滤器管道中的第三位置,在action过滤器之后执行,通过继承自IResultFilter和IAsyncResultFilter接口创建Result过滤器
IResultFilter接口定义如下:
public interface IResultFilter : IFilterMetadata
{
void OnResultExecuting(ResultExecutingContext context);
void OnResultExecuted(ResultExecutedContext context);
}
注意:Result过滤器和Action过滤器定义类似
IResultFilter接口有2个方法-OnResultExecuting和OnResultExecuted,OnResultExecuting在action方法的结果处理之前调用,OnResultExecuted 在action方法的结果处理之后调用,OnResultExecuting 方法有ResultExecutingContext 的参数类型,该参数的属性列表如下:
名称 | 描述 |
Controller | 被调用action方法的控制器的名称 |
Result | 这是一个IActionResult类型的属性,包含了action方法返回的IActionResult对象 |
Cancel | 设置这个属性为true将会阻止action结果的处理并且返回404错误 |
OnResultExecuted方法有一个ResultExecutedContext类型的参数,属性列表如下:
名称 | 描述 |
Controller | 被调用action方法的控制器的名称 |
Canceled | 只读属性表示请求是否被取消 |
Exception | 包含在action方法中抛出的异常 |
ExceptionHandled | 将该属性设置为true时,异常不会传播 |
Result | IActionResult只读属性由含Action方法生成 |
7.1 Result 过滤器例子
我们现在创建一个Result过滤器例子,当调用Action方法时改变View的呈现方式,因此创建一个ChangeView.cs的类在CustomFilters文件夹内使用下面代码:
namespace AspNetCore.Filters.CustomFilters
{
public class ChangeView : Attribute, IResultFilter
{
public void OnResultExecuted(ResultExecutedContext context)
{
}
public void OnResultExecuting(ResultExecutingContext context)
{
context.Result = new ViewResult
{
ViewName = "List"
};
}
}
}
我们在OnResultExecuting()方法方法中设置ViewName为List 在, 当我们把这个过滤器使用到action方法时,List试图会替换默认试图来呈现,让我们做个测试,创建一个新的Action方法叫Message在Home控制器中,如下所示,我们把[ChangeView]过滤器使用到该Action方法上:
[ChangeView]
public IActionResult Message()
{
return View();
}
该Action方法默认调用Message.cshtml,现在我们在Views->Shared目录下添加两个试图:
Message.cshtml
<h2>Message</h2>
<p>This is Message View</p>
List.cshtml
<h2>List</h2>
<p>This is Message View</p>
运行应用程序测试一下该特性,运行应用程序并且进入URL - /Home/Message 我们将看到List试图被调用,如下图所示
我们通过实现IAsyncResultFilter异步接口创建ChangView Result异步过滤器
IAsyncResultFilter定义如下:
public interface IAsyncResultFilter : IFilterMetadata
{
Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next);
}
我们在CustomFilters文件下添加一个ChangeViewAsync.cs类:
namespace AspNetCore.Filters.CustomFilters
{
public class ChangeViewAsync : Attribute, IAsyncResultFilter
{
public async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
context.Result = new ViewResult()
{
ViewName = "List"
};
await next();
}
}
}
IAsyncResultFilter只有一个方法OnResultExecutionAsync,参数有一个ResultExecutingContext类型我们使用它设置ViewName为List
ResultExecutionDelegate参数是一个异步代理,异步返回一个ResultExecutedContext对象,该对象表示Action方法执行结果或者下一个中间件执行结果,我们手动调用代理使用await next()以至于action结果能被呈现,现在进入Home控制器将Message方法的特性为[ChangeViewAsync]
[ChangeViewAsync]
public IActionResult Message()
{
return View();
}
8 混合Action/Result 过滤器
混合过滤器能容易得共享数据从Action到Result阶段,创建混合过滤器最简单的方式是继承ActionFilterAttribute类,该类实现这两个过滤器类型的接口,创建一个新的类文件叫HybridActRes.cs在CustomFilters文件夹下:
public class HybridActRes: ActionFilterAttribute
{
Stopwatch stopwatch;
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
stopwatch=Stopwatch.StartNew();
await next();
}
public override async Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
stopwatch.Stop();
context.Result = new ViewResult()
{
ViewName = "ShowTime",
ViewData = new Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary(
new EmptyModelMetadataProvider(),
new ModelStateDictionary())
{
Model = "Elapsed time: " + $"{stopwatch.Elapsed.TotalMilliseconds} ms"
}
};
await next();
}
}
混合过滤器有2个方法:
1 OnActionExecutionAsync - 在该方法中开始Stopwatch
2 OnResultExecutionAsync - 在该方法中停止Stopwatch并且将View切换到ShowTime并且Model被赋值为一个字符串显示执行的时间
接下来,在Home控制器中创建一个List方法使用下面代码:
[HybridActRes]
public IActionResult List()
{
return View();
}
最后在Views->Shared目录下创建一个ShowTime的视图,代码如下:
@model string
<h2>Show Time</h2>
@Model
接下来我们测试一下该功能并且导航到URL- /Home/List,我们将看到ShowTime视图将被调用,并且显示执行时间
因此,使用混合过滤器我们能在单独的文件中实现两个功能
9 Exception 过滤器
Exception 过滤器允许在不同写try & catch代码块的情况下来捕获异常,实现IExceptionFilter或者IAsyncExceptionFilter接口IAsyncExceptionFilter接口使用创建异步异常过滤器
IExceptionFilter接口的定义
public interface IExceptionFilter : IFilterMetadata
{
void OnException(ExceptionContext context);
}
IAsyncExceptionFilter接口的定义
public interface IAsyncExceptionFilter : IFilterMetadata
{
Task OnExceptionAsync(ExceptionContext context);
}
在这两个接口,在OnException & OnExceptionAsynccontext 方法参数中提供了ExceptionContext对象,ExceptionContext 类有如下属性
名称 | 描述 |
Exception | 这个属性包含抛出的异常 |
ExceptionDispatchInfo | 包含了异常堆栈的详细 |
ExceptionHandled | 只读的属性,异常是否处理 |
Result | 这个属性设置IActionResult生成response |
9.1 Exception 过滤器例子
在CustomFilters文件夹下创建一个CatchError.cs类, 添加下面代码:
public class CatchError : Attribute, IExceptionFilter
{
public void OnException(ExceptionContext context)
{
context.Result = new ViewResult
{
ViewData = new Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary(
new EmptyModelMetadataProvider(),
new ModelStateDictionary())
{
Model = context.Exception.Message
}
};
}
}
我们实现了一个CatchError的异常过滤器,我们将方法应用[CatchError]特性,该方法发生异常时,过滤器会自动捕获异常,在OnException()方法内我们给了Model属性赋值为context.Exception.Message,Model值将会显示在View,现在在Home控制器中创建一个新的Action方法叫Exception并且添加CatchError特性:
[CatchError]
public IActionResult Exception(int? id)
{
if (id == null)
throw new Exception("Error Id cannot be null");
else
return View($"The value is {id}");
}
如果id为空时会触发一个异常否则将会在View中显示id的值,最后在View->Home文件夹下创建一个Exception试图
@model string
<h2>Exception</h2>
@Model
接下来我们验证Exception过滤器,运行应用程序并且进入URL- /Home/Exception,你将会看到显示如下信息-Error Id cannot be null as an exception is raised ,该异常信息通过过滤器捕获到,现在进入URL- /Home/Exception/5 没有异常发生,你将会看到如下信息The value is 5
如下图所示:
源代码地址
https://github.com/bingbing-gui/Asp.Net-Core-Skill/tree/master/Fundamentals/AspNetCore.Filters/AspNetCore.Filters
参考文献
https://www.yogihosting.com/aspnet-core-filters/