十二、过滤器

在本章中,我们将涵盖以下主题:

  • 使用策略、需求和过滤器管理身份验证和授权
  • 用过滤器管理依赖注入
  • 创建和使用操作筛选器
  • 创建和使用结果过滤器
  • 创建和使用资源筛选器
  • 创建并使用异常过滤器
  • 全局使用过滤器和使用中间件

介绍

过滤器是注入到请求处理中的代码。 过滤器可以有很多类别:授权、缓存、日志记录、异常等等。 过滤器在 ActionInvocation 之后执行,在中间件在 ASP 上执行之后执行.NET Core 管道。

这里是官方 ASP 的过滤管道.NET Core 文档:

它们可以作为属性在操作或控制器级别应用,也可以作为添加在Startup.cs中的全局过滤器列表中的应用级别的全局过滤器。

我们还可以控制在同一操作上执行过滤器的顺序。

我们可以创建同步和异步过滤器,但是我们不应该混合使用这两种过滤器以避免副作用。

有预定义的过滤器,也有我们创建的过滤器:

  • 预定义的过滤器(它们用于动作和控制器级别):

    • [AllowAnonymous]
    • [Authorize]
    • [FormatFilter]
    • [TypeFilter(typeof(MyFilterAttribute))]
    • [ServiceFilter(typeof(MyFilterAttribute))]
  • 我们创建的过滤器(全局或非全局):

    • 动作过滤器(源自IActionFilterIAsyncActionFilter,或IActionFilterAttribute)
    • 结果过滤器(源自IResultFilterIAsyncResultFilter,或IResultFilterAttribute)
    • 资源过滤器(源自IResourceFilterIAsyncResourceFilter,或IResourceFilterAttribute)
    • 异常过滤器(派生自IExceptionFilterIAsyncExceptionFilter,或IExceptionFilterAttribute)

然而,全局过滤器将首先执行; 然后是控制器级别的过滤器,最后是操作级别的过滤器。

对于异常过滤器,顺序是相反的:

  1. 全球。
  2. 控制器。
  3. 行动。

使用策略、需求和过滤器管理身份验证和授权

在本食谱中,您将学习如何在全局、控制器和操作级别应用授权和身份验证。

准备

让我们用 VS 2017 创建一个空的 web 应用。

怎么做……

授权过滤器的目标是将操作方法单独或按控制器限制为特定的用户、角色或声明。 它总是在动作执行之前运行:

  1. 使用Authorization筛选器的一个经典方法是在控制器级别添加这个筛选器,并在Action级别覆盖AllowAnonymous属性,如下所示:
[Authorize]
public class AccountController : Controller
{
  [HttpGet]
  [AllowAnonymous]
  public IActionResult Login(string returnUrl = null)
  {
    ViewData["ReturnUrl"] = returnUrl;
    return View();
  }
  [HttpGet]
  [AllowAnonymous]
  public IActionResult Register(string returnUrl = null)
  {
    ViewData["ReturnUrl"] = returnUrl;
    return View();
  }
  [HttpPost]
  [ValidateAntiForgeryToken]
  public async Task<IActionResult> LogOff()
  {
    await _signInManager.SignOutAsync();
    _logger.LogInformation(4, "User logged out.");
    return RedirectToAction(nameof(HomeController.Index), "Home");
  }
  [HttpGet]
  [AllowAnonymous]
  public IActionResult ForgotPassword()
  {
    return View();
  }
}

如果未经授权的用户试图访问resourceaction方法,Authorization筛选器将自动返回401状态码。

在 ASP.NET Core,可以通过继承IAuthorizationFilterAuthorizeAttribute来创建我们自己的Authorization属性。 使用 Core,你不能编写自己的Authorization过滤器; 但是,可以通过策略和AuthorizationPolicyBuilder对象定制Authorization过滤器。

  1. 让我们覆盖Authorize属性配置,并将其作为全局过滤器添加:
var authorizationPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(authorizationPolicy));
  1. 我们还可以添加一些策略来应用于Authorize过滤器,如下所示:
services.AddAuthorization(auth =>
auth.AddPolicy("AuthPolicyAdminRole", policy =>
policy.RequireRole("AdminRole")));
services.AddAuthorization(auth =>
auth.AddPolicy("AuthPolicyReviewerRole", policy =>
policy.RequireRole("ReviewerRole")));
services.AddAuthorization(auth =>
auth.AddPolicy("AuthPolicySpecificClaim", policy =>
policy.RequireClaim("SpecificClaim")));
  1. 这就是我们在控制器或动作级别应用这些策略的方式:
[Authorize(Policy = "AuthPolicyAdminRole")]
[Authorize(Policy = "AuthPolicyOtherRole")]
[Authorize(Policy = "AuthPolicySpecificClaim")]
public IActionResult AuthorizedRoleView()

Another great thing with policies is that they are testable. We can develop a Unit Test project, and test all the policies autonomously.

身份验证

通常的做法是同时使用AuthenticationAuthorization过滤器。 使用WebForms,我们可以在每个页面上以编程方式添加自定义Authentication,但这很费力。

MVC 为WebApplications认证带来了更大的粒度。 现在,我们可以根据我们在网站中操作的需要,在操作、控制器或全局级别上添加Authentication。 它首先在所有 MVC 过滤器之前执行。

Authentication过滤器在 ASP 中不再存在.NET MVC 6(可能还没有移植)。

在 ASP.NET Core 中,可以通过继承FilterAttributeIAuthenticationFilter来创建我们自己的Authorization属性。

例如,在未经身份验证的用户试图访问方法或控制器中的操作方法时,我们使用它返回特定的 HTTP 状态代码、操作结果或路由结果。 检查身份验证的方法有:

  1. 我们总是可以通过编程方式使用以下代码来运行身份验证:
public IActionResult About()
{
  if (User.Identity.IsAuthenticated)
  RedirectToAction("Index");
  return View();
}
  1. 我们可以合并AuthenticationAuthorization,方法是通过应用于Authorization过滤器的策略添加之前的身份验证检查,如下所示:
services.AddAuthorization(auth =>
auth.AddPolicy("AuthPolicyAuthentication", policy =>
policy.RequireAuthenticatedUser()));
  1. 有了这个政策,我们将能够替换以下代码:
if (User.Identity.IsAuthenticated)

我们可以使用Authorize属性将认证策略应用到操作中,如下所示:

[Authorize(Policy = "AuthPolicyAuthentication")]
 public IActionResult About()
{
  if (User.Identity.IsAuthenticated)
  RedirectToAction("Index");
  return View();
}

Barry Dorran has one of the nicest implementation of basic authentication middleware on his GitHub repository at https://github.com/blowdart/idunno.Authentication.

有更多的…

我们可以跟随 Barry Dorrans 在 ASP 安全方面的工作.NET 的核心。 他创建了一个非常好的工作室,这是食谱的主要基础。 您可以通过https://github.com/blowdart/AspNetAuthorizationWorkshop访问。

用过滤器管理依赖注入

在本食谱中,您将学习如何以及何时对过滤器使用依赖注入。

准备

让我们用 VS 2017 创建一个空的 web 项目。

怎么做……

我们将通过在控制器中应用依赖注入来理解IActionFilter接口、TypeFilterServiceFilter属性。

  1. 首先,让我们通过派生IActionFilter来创建一个ActionFilter类:
public class MyActionFilter : IActionFilter
{
  public void OnActionExecuting(ActionExecutingContext context)
  {
    // do something before the action executes
  }
  public void OnActionExecuted(ActionExecutedContext context)
  {
    // do something after the action executes
  }
}
  1. 要使用这个类作为属性,我们必须使用TypeFilter属性,并将这个类作为参数赋予它。 我们不能直接使用MyActionFilter作为属性,因为它不能从ActionFilterAttribute继承:
[TypeFilter(typeof(MyActionFilter))]
public IActionResult Index()
{
  return View();
}
  1. 如果我们测试,这个页面工作良好:

  1. 现在让我们尝试使用ServiceFilter属性:
[ServiceFilter(typeof(MyActionFilter))]
public IActionResult Index()
  1. 如果我们再次测试,我们将得到一个 HTTP 错误 500,并告诉我们过滤器没有注册:

  1. 让我们用Startup.csConfigureServices方法注册过滤器:
services.AddScoped<MyActionFilter>();
  1. 现在我们可以看到它工作得很好:

当我们使用TypeFilter属性时,我们的过滤器不是由 DI 容器解析的,而是由ObjectFactory实例化的,所以它不需要有生命周期。

  1. 如果我们有由 ASP 解析的依赖关系,可以将MyActionFilter作为子类添加到从TypeFilterAttribute继承的类中.NET Core DI 容器或过滤器构造函数中使用的另一个 DI 容器(即使TypeFilter属性没有被 DI 解析):
public class MyActionTypeFilterAttribute : TypeFilterAttribute
{
  Public MyActionTypeFilterAttribute():
  base(typeof(MyActionTypeFilterAttributeImpl))
  { }
  private class MyActionTypeFilterAttributeImpl : IActionFilter
  {
    private readonly IRepository _repository;
    public MyActionTypeFilterAttributeImpl(IRepository repository)
    {
      _repository = repository;
    }
    public void OnActionExecuting(ActionExecutingContext context)
    {
      // do something before the action executes
      var id = context.ActionArguments["id"] as int?;
      if (id.HasValue)
      {
        var product = _repository.GetProduct(id.Value);
        if (product != null)
        {
          context.Result = new NotFoundObjectResult(id.Value);
        }
      }
    }
    public void OnActionExecuted(ActionExecutedContext context)
    {
      // do something after the action executes
    }
  }
}
  1. 我们还可以使用MyActionTypeFilterAttribute,将其添加到动作之上,如下所示:
[MyActionTypeFilterAttribute]
public IActionResult Index()
instead of using the following code:
[TypeFilter(typeof(MyActionTypeFilterAttribute))]
public IActionResult Index()
  1. 另一件很棒的事情是有能力通过构造函数在过滤器中注入一些服务,就像一个存储库:
public class MyActionFilter : IActionFilter
{
  private readonly IAppRepository _repository;
  public MyActionFilter(IAppRepository repository)
  {
    _repository = repository ;
  }
  public void OnActionExecuting(ActionExecutingContext context)
  {
    // do something before the action executes
    if (context.ActionArguments.ContainsKey("id"))
    {
      var id = context.ActionArguments["id"] as int?;
      if (id.HasValue)
      {
        var product = _repository.GetProduct(id.Value);
        if (product != null)
       {
          context.Result = new NotFoundObjectResult(id.Value);
          return;
        }
      }
    }
  }
  public void OnActionExecuted(ActionExecutedContext context)
  {
    // do something after the action executes
  }
}
  1. 我们可以在动作过滤器中使用日志层而不是存储库层:
public class MyActionFilterLogger : IActionFilter
{
  private readonly ILogger _logger;
  public MyActionFilterLogger(ILoggerFactory loggerFactory)
  {
    _logger = loggerFactory.CreateLogger<MyActionFilterLogger>();
  }
  public void OnActionExecuted(ActionExecutedContext context)
  {
    _logger.LogInformation(
    "OnActionExecuted" + DateTime.Now.ToLongDateString());
  }
  public void OnActionExecuting(ActionExecutingContext context)
  {
    _logger.LogInformation(
    "OnActionExecuting" + DateTime.Now.ToLongDateString());
  }
}
  1. 现在我们将在 ASP 中注册这两个过滤器.NET Core IoC 容器:
services.AddScoped<MyActionFilter>();
services.AddScoped<MyActionFilterLogger>();
  1. 我们可以使用它们作为全局过滤器:
config.Filters.Add(typeof(MyActionFilter));
config.Filters.Add(typeof(MyActionFilterLogger));
  1. 我们也可以使用它们作为控制器过滤器:
[ServiceFilter(typeof(MyActionFilter))]
[ServiceFilter(typeof(MyActionFilterLogger))]
public class HomeController : Controller
  1. 或者,我们可以使用它们作为动作过滤器:
[ServiceFilter(typeof(MyActionFilter))]
[ServiceFilter(typeof(MyActionFilterLogger))]
public IActionResult Index()

它是如何工作的…

ServiceFilterAttribute是 ASP 的方式.NET Core 注入在构造函数中有依赖项的过滤器。 如果我们不使用ServiceFilterAttribute来使用过滤器属性,它们将由 ASP 自动管理.NET 的核心。

TypeFilter属性与ServiceFilter属性之间存在差异。 使用TypeFilter,参数中的过滤器类型将由ObjectFactory类解析。 在ServiceFilter中,参数中的过滤器类型由内部 DI 容器解析。

创建和使用操作筛选器

在本食谱中,您将学习如何创建和使用操作过滤器。

准备

让我们用 VS 2017 创建一个空的 web 项目。

怎么做……

动作过滤器在动作过滤器执行之前和/或之后注入业务逻辑。 它通常检查和/或修改请求的元素; 例如,参数和头文件。 动作过滤器还可以检查模型绑定。 模型绑定通过一个类的实例设置 UI 元素的值(如textboxcomboboxcheckbox等),并将 UI 元素的值获取到一个类的实例。 如果一个类用于获取/设置 UI 元素的值,则该类称为ViewModel。 模型绑定机制自动绑定ViewModel和 UI 层。

要创建我们自己的动作过滤器,我们必须创建一个派生于以下基类或接口的类:

  • IActionFilterIAsyncActionFilter,这取决于我们是要创建同步过滤器还是异步过滤器
  • ActionFilterAttribute

IActionFilterIAsyncActionFilter继承自IFilterMetadata接口。 所有实现这些接口的类都不能作为控制器或操作方法上面的属性使用。 类应该实现IActionFilterIAsyncActionFilterTypeFilterServiceFilter属性,将其添加到控制器或操作方法之上。 但它们可以被用作全局过滤器。

ActionFilterAttribute继承自AttributeIActionFilterIFilterMetadataIAsyncActionFilterIResultFilterIAsyncResultFilterIOrderedFilter

  1. 让我们创建一个ActionFilterAttribute的实现:
public class MyActionAttributeFilter : ActionFilterAttribute
{
  public override void OnActionExecuting(
  ActionExecutingContext context) {}
  public override void OnActionExecuted(
  ActionExecutedContext context) {}
  public override void OnResultExecuting(
  ResultExecutingContext context) {}
  public override void OnResultExecuted(
  ResultExecutedContext context) {}
}

我们可以在全局、控制器或操作级别使用该类作为过滤器。

Note: We can be notified by ActionExecutedResult. Canceled property when another filter as a resource filter short circuits the current action filter.

  1. 在全局级别上,将过滤器添加到Startup.cs的配置中:
services.AddMVC(
config =>
{
  config.Filters.Add(new MyActionAttributeFilter());
});
  1. 在控制器或操作级别,它可以通过将其添加到操作或控制器之上来使用:
[MyActionAttributeFilter]
public IActionResult Index()
  1. 或者,我们可以添加一个TypeFilter属性:
[TypeFilter(typeof(MyActionAttributeFilter))]
public IActionResult Index()
  1. 另一种选择是添加一个ServiceFilter属性,但是在这种情况下,它的生命周期将由 ASP 管理.NET Core DI 容器。 我们应该给它一个生命周期:
services.AddScoped<MyActionAttributeFilter>();

我们可以在控制器中使用它:

[ServiceFilter(typeof(MyActionAttributeFilter))]
public IActionResult Index()
  1. 接下来,让我们实现一个IActionFilter:
public class MyValidateModelStateActionFilter : IActionFilter
{
  public void OnActionExecuting(ActionExecutingContext context)
  {
    // do something before the action executes
    if (!context.ModelState.IsValid)
    {
      context.Result =
      new BadRequestObjectResult(context.ModelState);
    }
  }
  public void OnActionExecuted(ActionExecutedContext context)
  {
    // do something after the action executes
  }
}
  1. 现在,我们将实现一个IAsyncActionFilter:
public class myAsyncActionFilter : IAsyncActionFilter
{
  public async Task OnActionExecutionAsync(
  ActionExecutingContext context,
  ActionExecutionDelegate next)
  {
    // do something before the action executes
    await next();
    // do something after the action executes
  }
}
  1. 将它与TypeFilter一起使用(这样它就不会被 ASP 管理.NET Core DI 容器),我们只需要将它添加到动作或控制器之上:
[TypeFilter(typeof(MyActionAttributeFilter))]
public IEnumerable<string> Get()
  1. ServiceFilter一起使用,用 ASP 对其进行管理.NET Core DI 容器,我们必须给它一个生命周期:
services.AddScoped<MyActionAttributeFilter>();

我们将在控制器中使用它:

[ServiceFilter(typeof(MyActionAttributeFilter))]
public IEnumerable<string> Get()

我们也可以设置过滤器的顺序:

[ServiceFilter(typeof(MyActionFilter))]
[ServiceFilter(typeof(MyActionAttributeFilter))]
public IEnumerable<string> Get()

首先执行MyActionAttributeFilter,然后执行MyActionFilter

但是我们可以改变顺序:

[ServiceFilter(typeof(MyActionFilter), Order = 2)]
[ServiceFilter(typeof(MyActionAttributeFilter), Order = 1)]
public IEnumerable<string> Get()

The filter with the highest Order property value is executed first. In this example, MyActionFilter will be executed first, and MyActionAttributeFilter second.

创建和使用结果过滤器

在本食谱中,您将学习如何创建和使用结果过滤器。

准备

让我们用 VS 2017 创建一个空的 web 应用。

怎么做……

结果过滤器在执行结果之前或之后注入处理。 它可以检查和修改操作方法生成的结果。

一些常见的结果过滤器实现可能是添加或检查头,以及许多其他。

要创建我们自己的结果过滤器,我们必须创建一个派生于以下基类或接口的类:

  • IResultFilterIAsyncResultFilter,这取决于我们是要创建同步过滤器还是异步过滤器
  • ResultFilterAttribute

IActionResult/IAsyncActionResult接口一样,IResultFilterIResultActionFilter继承自IFilterMetadata。 在这些接口中实现的所有类都不能使用控制器或操作方法之上的属性。 一个类应该实现IResultFilter,或者IResultActionFilterTypeFilter,或者ServiceFilter属性来添加到控制器或动作方法之上; 但是,它们可以用作全局过滤器。

ResultFilterAttribute继承自AttributeIFilterMetadataIResultFilterIAsyncResultFilterIOrderedFilter,其实现如下:

  1. 让我们创建一个空的同步资源过滤器:
public class MyResultFilter : IResultFilter
{
  public void OnResultExecuted(ResultExecutedContext context)
  {
    // code executed before the filter executes
  }
  public void OnResultExecuting(ResultExecutingContext context)
  {
    // code executed after the filter executes
  }
}
  1. 让我们创建一个空的异步资源过滤器:
public class MyAsyncResultFilter : IAsyncResultFilter
{
  public async Task OnResultExecutionAsync(
  ResultExecutingContext context, ResultExecutionDelegate next)
  {
    // code executed before the filter executes
    await next();
    // code executed after the filter executes
  }
}
  1. 现在,我们将通过操作ViewResult并向ViewData字典中添加一个条目来实现一个结果过滤器:
public class MyAsyncResultFilter : IAsyncResultFilter
{
  public async Task OnResultExecutionAsync(
  ResultExecutingContext context, ResultExecutionDelegate next)
  {
    // do something before the filter executes
    ViewResult result = context.Result as ViewResult;}
    if (result != null)}
    {
      result.ViewData["messageOnResult"] =
      "This message comes from MyAsyncResultFilter" +
      DateTime.Now.ToLongTimeString();
    }
    await next();
    // do something after the filter executes
  }
}
  1. 我们可以和ServiceFilter一起使用。 在调用它之前,我们必须注册它:
services.AddScoped<MyAsyncResultFilter>();

我们将在控制器中使用它:

[ServiceFilter(typeof(MyAsyncResultFilter))]
 public IActionResult Index()
  1. 我们可以看到结果显示如下:

结果筛选器只在动作筛选器没有例外地被执行时才会被执行。

一个好的做法是只使用异步过滤器或同步过滤器,但是混合使用这两种过滤器会产生副作用。

创建和使用资源筛选器

在本食谱中,您将学习如何创建资源过滤器以及何时使用它。

准备

让我们用 VS 2017 创建一个空的 web 应用。

怎么做……

在授权过滤器之后,资源过滤器是过滤器管道上执行的第一个过滤器。 如果没有异常发生,此筛选器将在ActionFilter之后执行。 它也是最终执行的最后一个过滤器,离开过滤器管道。

一个常见的resource过滤器实现是缓存管理。 MVC 中的OutputCache属性就是一个资源筛选器的例子。

要创建我们自己的资源筛选器,我们必须创建一个派生自IResourceFilterIAsyncResourceFilter的类,这取决于我们是要创建同步筛选器还是异步筛选器。

  1. 首先,让我们创建一个空的同步资源过滤器:
public class MyResourceFilter : IResourceFilter
{
  public void OnResourceExecuted(ResourceExecutedContext context)
  {
    // code executed before the filter executes
  }
  public void OnResourceExecuting(ResourceExecutingContext context)
  {
    // code executed after the filter executes
  }
}
  1. 让我们创建一个空的异步资源过滤器:
public class MyAsyncResourceFilter : IAsyncResourceFilter
{
  public async Task OnResourceExecutionAsync(
  ResourceExecutingContext context, ResourceExecutionDelegate next)
  {
    // code executed before the filter executes
    await next();
    // code executed after the filter executes
  }
}
  1. 要在不使用 DI 的情况下使用它,我们将使用TypeFilter属性调用它:
[TypeFilter(typeof(MyResourceFilter))]
public IEnumerable<string> Get()
  1. 现在,让我们创建一个资源缓存过滤器,看看我们如何实现一个资源过滤器:
public class MyResourceCacheFilter : IResourceFilter
{
  private int _cacheDuration = 0;
  private bool _cacheEnabled = true;
  private readonly IConfigurationRoot _configuration;
  private readonly IMemoryCache _cache;
  private readonly ILogger _logger;
  public MyResourceCacheFilter(IConfigurationRoot configuration,
  IMemoryCache cache, ILoggerFactory loggerFactory)
  {
    _configuration = configuration;
    _cache = cache;
    _logger = loggerFactory.CreateLogger("MyResourceFilter");
    InitFilter();
  }
  public void OnResourceExecuted(ResourceExecutedContext context)
  {
    if (_cacheEnabled)
    {
      string _cachekey = string.Join(":", new string[]
      {
        context.RouteData.Values["controller"].ToString(),
        context.RouteData.Values["action"].ToString()
      });
      string cachedContent = string.Empty;
      if (_cache.TryGetValue(_cachekey, out cachedContent))
      {
        if (!String.IsNullOrEmpty(cachedContent))
        {
          context.Result = new ContentResult()
          {
            Content = cachedContent
          };
        }
      }
    }
  }
  public void OnResourceExecuting(ResourceExecutingContext context)
  {
    try
    {
      if (_cacheEnabled)
      {
        if (_cache != null)
        {
          string _cachekey = string.Join(":", new string[]
          {
            context.RouteData.Values["controller"].ToString(),
            context.RouteData.Values["action"].ToString()
          });
          var result = context.Result as ContentResult;
          if (result != null)
          {
            string cachedContent = string.Empty;
            _cache.Set(_cachekey, result.Content,
            DateTime.Now.AddSeconds(_cacheDuration));
          }
        }
      }
    }
    catch (Exception ex)
    {
      _logger.LogError("Error caching in MyResourceFilter", ex);
    }
  }
  private void InitFilter()
  {
    if (!Boolean.TryParse(
    _configuration.GetSection("CacheEnabled").Value,
    out _cacheEnabled))
    {
      _cacheEnabled = false;
    }
    if (!Int32.TryParse(
    _configuration.GetSection("CacheDuration").Value,
    out _cacheDuration))
    {
      _cacheDuration = 21600; // 6 hours cache by default
    }
  }
}
  1. 我们可以和ServiceFilter一起使用。 在调用它之前,我们必须注册它:
services.AddScoped<MyResourceCacheFilter>();

我们将在控制器中使用它:

[ServiceFilter(typeof(MyResourceCacheFilter))]
public IEnumerable<string> Get()

创建并使用异常过滤器

在本食谱中,您将学习如何在全局、控制器和操作级别创建和使用异常过滤器。

准备

让我们用 VS 2017 创建一个空的 web 应用。

怎么做……

ASP.NET Core MVC 与早期版本的 ASP 相比有很多优势。 asp.net MVC,如 asp.net.NET MVC 5:

  • 在全局、控制器或操作级别上不再有HandleError属性可用(因此,在web.config中不再有customErrors属性需要参数化)
  • global.asax不再有Application_Error事件
  • 从基类Controller中不再覆盖更多的OnException方法

当然,我们仍然可以使用try/catch块,但它被认为是对开发人员不友好的解决方案。 我们可以避免这种解决方案,并获得更清晰的代码。

现在的解决方案是:

  • 使用Exception过滤器(在操作方法中处理异常)
  • 使用Exception中间件(在应用级别处理更多的通用错误)

异常筛选器将处理来自任何引发异常的操作或筛选器的异常。 要成为异常过滤器,类必须实现以下类或接口之一:

  • ExceptionFilterAttribute
  • IExceptionFilterIAsyncExceptionFilter为异步过滤

可以使用异常过滤器捕获特定的异常类型(并应用特定的处理,或重定向到特定的错误页面)。

现在,在 ASP.NET Core,默认情况下,如果发生异常,不显示错误消息。 因此,重定向到错误页面的需要不是那么相关,但它更优雅; 然而,我们总是需要捕获所有异常,至少要在全局日志中记录异常:

  1. 让我们创建一个ExceptionFilterAttribute。 我们必须在控制器或动作级别的ServiceFilterTypeFilter属性中使用它:
public class MyExceptionAttributeFilter : ExceptionFilterAttribute
{
  public override void OnException(ExceptionContext context)
  {
    if (context.Exception is InvalidOperationException)
    {
      // Log the exception informations by ASP.NET Core
      // or custom logger
      context.Exception = null;
    }
  }
}

我们必须将 null 设置为context.Exception,以停止执行其他异常过滤器。 如果我们将context.Exception设置为 true,我们假设异常已经被处理了。

  1. ServiceFilter一起使用,用 ASP 对其进行管理.NET Core DI 容器,我们必须给它一个生命周期:
services.AddScoped<MyExceptionAttributeFilter>();
  1. 我们将在控制器中使用它:
[ServiceFilter(typeof(MyExceptionAttributeFilter))]
public IEnumerable<string> Get() { }
  1. 接下来,让我们创建一些全局异常过滤器:
public class MyGlobalExceptionFilter : IExceptionFilter
{
  public void OnException(ExceptionContext context)
  {
    //throw new NotImplementedException();
  }
}

public class MyGlobalAsyncExceptionFilter : IAsyncExceptionFilter
{
  public Task OnExceptionAsync(ExceptionContext context)
  {
    return Task.FromResult(0);
    //throw new NotImplementedException();
  }
}
  1. 要使用它作为全局过滤器,我们必须将它添加到配置中:
services.AddMVC(
config =>
{
  config.Filters.Add(new MyGlobalExceptionFilter());
});

这个过滤器会自动应用到所有控制器中的所有动作过滤器; 但是,如果全局异常过滤器中的代码不是特定于动作方法的,同样,我们倾向于使用中间件来做这件事。 我们将在第 16 章OWIN 和 Middleware中研究这个案例。

全局使用过滤器和使用中间件

在本菜谱中,您将学习如何全局使用过滤器,并了解全局使用过滤器和使用中间件之间的区别。

准备

让我们用 VS 2017 创建一个空的 web 应用。

怎么做……

  1. 首先,让我们通过派生IResultFilter来创建一个ResultFilter类:
public class MyResultFilter : IResultFilter
{
  public void OnResultExecuting(ActionExecutingContext context)
  {
    // do something before the action executes
  }
  public void OnResultExecuted(ActionExecutedContext context)
  {
    // do something after the action executes
    ViewResult result = context.Result as ViewResult;
    if (result != null)
    {
      result.ViewData["globalMessage"] =
      "Comes from MyActionAttributeFilter at " +
      DateTime.Now.ToLongTimeString();
    }
  }
}
  1. 接下来,让我们将该过滤器配置为Startup.cs中的全局过滤器。 我们可以通过两种方式做到这一点; 按类型或按实例:
services.AddMVC(config =>
{
  // FIRST WAY
  config.Filters.Add(typeof(MyResultFilter)); // by type
  // OR
  config.Filters.Add(new TypeFilterAttribute(
  typeof(MyResultFilter))); // by type
  // SECOND WAY
  config.Filters.Add(new MyResultFilter ()); // by instance
});

这个过滤器将应用于应用中的任何操作或控制器的任何地方,而不必将其用作控制器或操作之上的属性。

  1. 让我们在index.cshtml中添加一些代码,以确保我们的全局过滤器能够正常工作:
<!DOCTYPE html>
<html>
  <head>
    <title>Index</title>
  </head>
  <body>
    <div>
      <h1>Message</h1>
      Display message from Global Filter
    </div>
    <footer>
      Message: @ViewData["message"]
    </footer>
  </body>
</html>
  1. 最后,我们可以看到结果:

  1. 或者,我们可以通过派生ActionFilterAttribute来创建一个类。 注意,当继承ActionFilterAttribute时,我们还实现了IActionFilterIResultFilter接口:
public class MyActionAttributeFilter : ActionFilterAttribute
{
  public MyActionAttributeFilter() {}
  public override void OnActionExecuting(
  ActionExecutingContext context)
  { }
  public override void OnActionExecuted(ActionExecutedContext context)
  { }
  public override void OnResultExecuting(
  ResultExecutingContext context)
  { }
  public override void OnResultExecuted(ResultExecutedContext context)
  {
    ViewResult result = context.Result as ViewResult;
    if (result != null)
    {
      result.ViewData["globalMessage"] =
      "Comes from MyActionAttributeFilter at " +
      DateTime.Now.ToLongTimeString();
    }
  }
}
  1. 接下来,让我们将该过滤器配置为Startup.cs中的全局过滤器。 我们可以通过两种方式做到这一点; 按类型或按实例:
services.AddMVC(config =>
{
  // FIRST WAY
  config.Filters.Add(typeof(MyActionAttributeFilter)); // by type
  // OR
  config.Filters.Add(new TypeFilterAttribute(
  typeof(MyActionAttributeFilter))); // by type
  // SECOND WAY
  config.Filters.Add(new MyActionAttributeFilter()); // by instance
});

这个过滤器将应用于应用中的任何操作或控制器的任何地方,而不必将其用作控制器或操作之上的属性。

  1. 让我们在index.cshtml中添加一些代码,以确保我们的全局过滤器能够正常工作:
<!DOCTYPE html>
<html>
  <head>
    <title>Index</title>
  </head>
  <body>
    <div>
      <h1>Message</h1>
      Display message from Global Filter
    </div>
    <footer>
      Message: @ViewData["message"]
    </footer>
  </body>
</html>

过滤器是了解 MVC 的代码,而中间件是管道上一种不了解 MVC 的更通用的代码。 我们更倾向于使用Exception中间件,而不是全局异常过滤器。

有更多的…

从这些接口派生,我们可以创建全局过滤器:

  • 对于资源过滤器:IResourceFilterIAsyncResourceFilter
  • 动作过滤器:IActionFilterIAsyncActionFilter
  • 用于结果过滤器:IResultFilterIAsyncResultFilter
  • 对于异常过滤器:IExceptionFilterIAsyncExceptionFilter