• .NET Core中过滤器的用法
  • 发布于 2个月前
  • 211 热度
    0 评论
  • pckillers
  • 0 粉丝 36 篇博客
  •   
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 。
一. 创建例子项目
我们通过一个例子来了解过滤器, 在Visual Studio创建一个MVC项目,命名为AspNetCore.Filters。

二. 内置过滤器-[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()
    {
        // 堆代码 duidaima.com
        return "This is the Index action";
    }
    public string List()
    {
        return "This is the List action";
    }
    public string Hello()
    {
        return "This is the Hello action";
    }
}
三. ASP.NET Core MVC中常用过滤器
在.NET Core中有多少类型的过滤器呢?在.NET Core最常用的由4中类型过滤器Authorization, Action, Result 和 Exception,每个过滤器可以同步和异步两种工作方式,下面表格描述他们详细 

细 

过滤器

接口

描述

Authorization

IAuthorizationFilterIAsyncAuthorizationFilter

使用它申请授权和安全策略

Action

IActionFilter, IAsyncActionFilter

在action方法之前或者之后指定执行的具体工作

Result

IResultFilter, IAsyncResultFilter

用于在操作方法的结果之前或之后立即执行指定工作

Exception

IExceptionFilter, IAsyncExceptionFilter

使用它来处理异常


如何在.NET Core中创建自定义过滤器?我们创建一个自定义过滤器继承自Attribute与此同时还必须继承与其匹配的过滤器接口,接下来,我们将过滤器应用到Controller或者Action方法,注意每个过滤器只能够实现Synchronous或者Asynchronous两个接口中的一个。

四. 过滤器执行顺序
过滤器按照下面顺序执行:
1 Authorization 过滤器第一个执行
2 Action过滤器其次 
3 Result过滤器最后执行

Exception过滤器只有在发生异常时执行。

五. 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 过滤器。


六. 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);

        }
    }
}
七.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();
}
八. 混合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视图将被调用,并且显示执行时间

因此,使用混合过滤器我们能在单独的文件中实现两个功能。
九. 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

如下图所示:

用户评论