过滤器是 ASP.NET Core 中的特殊组件,允许我们在请求管道的特定阶段控制请求的执行。这些过滤器在中间件执行后以及 MVC 中间件匹配路由并调用特定操作时发挥作用。
简而言之,过滤器提供了一种在操作级别自定义应用程序行为的方法。它们就像检查点,允许我们执行特定任务,例如异常处理、缓存或添加自定义响应标头。
当请求到达某个操作时,过滤器可以收集有关已选择哪个操作以及关联的路由数据的信息。此信息可用于做出决策并执行特定于该特定操作或控制器的操作。
通过使用过滤器,我们在处理请求时拥有更多的控制权和灵活性。我们可以根据我们的要求实现自定义逻辑并将特定行为应用于不同的操作或控制器。这有助于在 ASP.NET Core 应用程序中实现更加定制和高效的请求处理流程。
过滤器的类型
ASP.NET Core 提供了一组预定义的过滤器类型,允许我们在操作或控制器的上下文中控制请求的执行。这些过滤器有不同的用途,可以根据我们的要求进行定制。
以下是 ASP.NET Core 中预定义过滤器的主要类型:
-
AuthorizationFilter:此过滤器在路由执行开始时执行。它确定是否允许用户访问该路由或执行请求的操作。
-
ResourceFilter:该过滤器在授权后运行,可用于绕过剩余管道的执行。如果预处理的响应可用,我们想要发送缓存的响应,从而跳过管道的其余部分,那么它非常有用。
-
ActionFilter:动作过滤器在动作执行之前和之后运行。它提供了一种用自定义逻辑包围操作或在执行操作之前和之后执行其他任务的方法。
-
ResultFilter:此过滤器在操作生成结果之前和之后执行。它允许我们用附加行为包围结果执行或根据结果执行某些操作。
-
ExceptionFilter:每当操作或控制器中抛出未捕获的异常时,异常过滤器就会运行。它提供了一种处理异常并设计特定于操作或控制器的自定义错误响应的方法。
-
ServiceFilter:此过滤器运行另一个过滤器,其类型在其自身内传递。当传递的过滤器类型注册为服务时使用它。它解决通过依赖项注入 (DI) 传递给过滤器类型的任何依赖项。
-
TypeFilter:与服务过滤器类似,类型过滤器也运行未注册为服务的过滤器类型。它允许我们应用自定义过滤器而无需注册。
这些预定义的过滤器类型使我们能够灵活地控制请求执行过程,并在 ASP.NET Core 应用程序的管道的不同阶段添加特定行为。
过滤器范围
ASP.NET Core 中过滤器的范围取决于它们附加到 MVC 管道的方式。这使我们能够控制过滤器执行的时间和地点。过滤器的主要范围分为三个:
-
特定于操作的范围:过滤器可以应用于控制器内的特定操作方法。通过使用过滤器属性修饰操作,过滤器将仅在选择该特定操作来处理传入请求时执行。
-
特定于控制器的范围:过滤器也可以应用于整个控制器类。当过滤器应用于控制器级别时,它将针对该控制器内的所有操作执行。
-
全局范围:全局过滤器应用于路由中间件匹配和拾取的每条路由。这些过滤器被注册为全局过滤器,并且无论特定操作或控制器如何都会被执行。但是,如果没有为请求选择端点,则不会执行任何过滤器。
要将过滤器注册为全局过滤器,可以将其添加到 Startup 类中的 ConfigureServices() 方法内的过滤器数组中。这是一个例子:
services.AddControllers(options => {
options.Filters.Add(typeof(ConsoleGlobalActionFilter));
});
在此示例中,使用 Add(typeof(...)) 方法将 ConsoleGlobalActionFilter 注册为全局筛选器。这确保过滤器将应用于路由中间件匹配和处理的每个路由。
通过控制过滤器的范围,我们可以精确地确定过滤器将在 ASP.NET Core 应用程序中应用的时间和位置,从而允许我们添加特定的行为并有效地控制请求执行过程。
如何创建简单的过滤器
有两种方法可以创建我们之前讨论的类型的过滤器。我们以创建ActionFilter为例:
-
实现接口:要创建根据操作执行进行操作的自定义操作过滤器,我们可以创建一个实现 IActionFilter 或 IAsyncActionFilter 接口的类。这些接口提供了我们可以重写以添加自定义逻辑的方法。通过实现这些接口,我们可以完全控制操作过滤器的行为。
-
扩展属性类:或者,我们可以创建一个扩展 ActionFilterAttribute 类的自定义操作过滤器类。ActionFilterAttribute 类已经实现了 IActionFilter 和 IAsyncActionFilter 接口。通过扩展此类,我们可以重写我们感兴趣的方法并添加自定义逻辑。这种方法提供了一种更方便的方法来创建操作过滤器,因为我们可以直接扩展基类并专注于实现必要的方法。
同样,对于提到的其他类型的过滤器(如授权过滤器、资源过滤器、结果过滤器、异常过滤器),也有相应的可以实现的接口或可以扩展的属性类。
以下是上述过滤器的接口和属性类:
-
IAuthorizationFilter:可以通过AuthorizationFilterAttribute实现或扩展。
-
IResourceFilter:可以通过ResourceFilterAttribute实现或扩展。
-
IActionFilter:可以通过ActionFilterAttribute实现或扩展。
-
IResultFilter:可以通过ResultFilterAttribute实现或扩展。
-
IExceptionFilter:可以通过ExceptionFilterAttribute实现或扩展。
其中一些过滤器附带有实现相应接口的属性类,使我们能够灵活地重写适合我们需要的特定方法。以下是一些过滤器的属性类:
-
ActionFilterAttribute:该属性类实现 IActionFilter 和 IAsyncActionFilter 接口。通过扩展这个类,我们可以直接继承这些接口的实现并重写我们感兴趣的方法。这使得我们可以根据我们的需求自定义动作过滤器的行为。
-
ExceptionFilterAttribute:该属性类实现 IExceptionFilter 接口。通过扩展此类并重写其方法,我们可以处理和自定义对操作或控制器中发生的异常的处理。这使我们能够控制异常的处理方式,并允许我们提供自定义错误响应。
-
ResultFilterAttribute:该属性类实现 IResultFilter 接口。通过扩展此类并重写其方法,我们可以在将操作生成的结果发送回客户端之前对其进行修改。这允许我们向结果添加额外的处理或转换。
-
FormatFilterAttribute:此属性类用于指定操作或控制器支持的响应格式。它有助于内容协商并根据客户的偏好选择适当的响应格式。
-
ServiceFilterAttribute:此属性类用于应用注册为服务的过滤器。它允许我们使用依赖项注入来解决过滤器所需的任何依赖项。当过滤器需要额外的服务或依赖项才能正常运行时,这会很有帮助。
-
TypeFilterAttribute:该属性类与ServiceFilterAttribute类似,但它允许我们应用未注册为服务的过滤器。我们可以直接指定过滤器类型及其依赖项。
例如,ActionFilterAttribute类已经为我们实现了IActionFilter和IAsyncActionFilter接口,因此我们可以直接扩展ActionFilterAttribute类并重写我们感兴趣的方法。
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Newtonsoft.Json;
public class AddResponseHeaderFilter : ActionFilterAttribute
{
// Async method which can surround the action execution
// Invoked before and after the action execution
public asyn coverride Task OnActionExecutionAsync( ActionExecutingContext context, ActionExecutionDelegate next)
{
// Access the request
context.HttpContext.Response.Headers.Add(
"X-MyCustomHeader", Guid.NewGuid().ToString());
var result = await next.Invoke();
// Access the response
Console.WriteLine(JsonConvert.SerializeObject(result.Result));
}
}
[Route("api/[controller]")]
[ApiController]
public class HomeController : ControllerBase
{
[AddResponseHeaderFilter]
[Route("")]
[HttpGet]
public IActionResult Index()
{
return Ok(new { Message = "I'm Alive" });
}
}
在此示例中,我们通过扩展ActionFilterAttribute创建了自定义AddResponseHeaderFilter类。此过滤器在执行操作之前和之后向 HTTP 响应添加自定义响应标头。它重写OnActionExecutionAsync方法,该方法允许我们自定义操作执行周围的行为。
然后,我们使用 [ AddResponseHeaderFilter ] 属性将AddResponseHeaderFilter过滤器应用到HomeController类的Index操作。这可确保针对该特定操作执行过滤器。
调用该操作时,过滤器使用Response.Headers.Add方法将自定义标头添加到响应中。然后它调用 next.Invoke() 方法来继续执行操作。执行操作后,它会访问响应结果并使用JsonConvert.SerializeObject 将其写入控制台。
通过利用ActionFilterAttribute并创建自定义过滤器类,我们可以扩展和自定义 ASP.NET Core 请求管道的行为,以添加其他功能并根据我们的要求修改响应。
动作过滤器的实现
示例 1:实现 IActionFilter 的同步操作过滤器
using Microsoft.AspNetCore.Mvc.Filters;
namespace ActionFilters.Filters
{
public class ActionFilterExample : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
// Code executed before the action method executes
}
public void OnActionExecuted(ActionExecutedContext context)
{
// Code executed after the action method executes
}
}
}
在此示例中,我们创建一个名为 ActionFilterExample 的类,该类实现 IActionFilter 接口。该接口要求我们实现两个方法:OnActionExecuting 和 OnActionExecuted。OnActionExecuting 方法包含将在操作方法之前执行的代码,而 OnActionExecuted 方法包含将在操作方法之后执行的代码。
示例 2:实现 IAsyncActionFilter 的异步操作过滤器
using Microsoft.AspNetCore.Mvc.Filters;
using System.Threading.Tasks;
namespace ActionFilters.Filters
{
public class AsyncActionFilterExample : IAsyncActionFilter
{
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// Code executed before the action method executesvar result = await next();
// Code executed after the action method executes
}
}
}
在此示例中,我们创建一个名为 AsyncActionFilterExample 的类,该类实现 IAsyncActionFilter 接口。该接口要求我们实现 OnActionExecutionAsync 方法,该方法是动作过滤器的异步版本。该方法接收一个 ActionExecutingContext 和一个 ActionExecutionDelegate。ActionExecutionDelegate 代表管道中的下一个操作,我们可以等待它执行操作方法。通过这样做,我们可以在操作方法执行之前和之后运行代码。
通过创建实现 IActionFilter 或 IAsyncActionFilter 的类,我们可以定义要在 ASP.NET Core 中的操作方法之前和之后执行的自定义逻辑。这些操作过滤器提供了一种向请求管道添加附加行为并根据需要修改请求或响应的方法。
操作过滤器的范围
可以在不同范围级别添加操作过滤器:全局、操作和控制器。
1. 全局范围
要全局使用操作过滤器,您可以在ConfigureServices方法的AddControllers()方法中注册它:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers(config =>
{
config.Filters.Add(new GlobalFilterExample());
});
}
在.NET 6及更高版本中,由于缺少 Startup 类,您可以使用 Program 类:
builder.Services.AddControllers(config =>
{
config.Filters.Add( new GlobalFilterExample());
});
通过将过滤器添加到全局范围,它将应用于整个应用程序中的所有操作方法。
2. 操作和控制器范围
如果要在操作或控制器级别使用操作过滤器,则需要在ConfigureServices方法中将其注册为IoC容器中的服务:
services.AddScoped<ActionFilterExample>();
services.AddScoped<ControllerFilterExample>();
在 .NET 6 及更高版本中:
builder.Services.AddScoped<ActionFilterExample>();
builder.Services.AddScoped<ControllerFilterExample>();
通过将操作过滤器注册为服务,您可以通过使用相应的过滤器属性装饰它们,有选择地将其应用到特定的操作方法或控制器。
namespace AspNetCore.Controllers
{
[ServiceFilter(typeof(ControllerFilterExample))]
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
[ServiceFilter(typeof(ActionFilterExample))]
public IEnumerable<string> Get()
{
return new string[] { "example", "data" };
}
}
}
通过使用[ServiceFilter]属性并指定过滤器类型,您可以将注册的过滤器(ControllerFilterExample 和 ActionFilterExample)分别与控制器和操作方法关联起来。
调用顺序
我们的过滤器的执行顺序如下:
我们可以通过向ServiceFilter属性添加一个名为Order 的附加属性来更改多个过滤器的调用顺序:
namespace AspNetCore.Controllers
{
[ServiceFilter(typeof(ControllerFilterExample), Order=2)]
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
[ServiceFilter(typeof(ActionFilterExample), Order=1)]
public IEnumerable<string> Get()
{
return new string[] { "example", "data" };
}
}
}
在本例中,ControllerFilterExample将在ActionFilterExample之前执行,因为它们的顺序值分别为 2 和 1。通过为过滤器分配不同的顺序值,您可以控制它们的执行顺序。
[HttpGet]
[ServiceFilter(typeof(ActionFilterExample), Order=2)]
[ServiceFilter(typeof(ActionFilterExample2), Order=1)]
public IEnumerable<string> Get()
{
return new string[] { "example", "data ” };
}
在这种情况下,ActionFilterExample2将根据其顺序值分别为 1 和 2,在ActionFilterExample之前执行。
使用操作过滤器改进代码
为了使用操作过滤器改进代码,让我们重点关注存储库中AppStart文件夹中起始项目的Controllers文件夹中的MoveController类。该控制器包含所有 CRUD 操作的实现。
尽管我们的操作已经干净且可读,但由于全局异常处理,我们可以进一步增强它们。
需要注意的一件重要事情是我们的Movie模型继承自IEntity接口:
[Table("Movie")]
public class Movie: IEntity
{
[Key]
public Guid Id { get; set; }
[Required(ErrorMessage = "Name is required")]
public string Name { get; set; }
[Required(ErrorMessage = "Genre is required")]
public string Genre { get; set; }
[Required(ErrorMessage = "Director is required")]
public string Director { get; set; }
}
现在,让我们关注POST和PUT操作的验证代码。通过将适当的操作过滤器应用于POST和PUT操作,我们可以消除这些操作中显式验证代码的需要,使它们更加简洁和可维护。
使用操作过滤器进行验证
为了改进POST和PUT操作中的验证代码,我们可以使用操作过滤器。通过将验证逻辑提取到自定义操作过滤器中,我们可以使代码更具可重用性并保持操作更清晰。
首先,我们在解决方案资源管理器中创建一个名为“ActionFilters”的新文件夹。在该文件夹中,创建一个名为ValidationFilterAttribute的新类:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.Linq;
namespace ActionFilters.ActionFilters
{
public class ValidationFilterAttribute : IActionFilter
{
public void OnActionExecuting(ActionExecutingContext context)
{
var param = context.ActionArguments.SingleOrDefault(p => p.Value is IEntity);
if (param.Value == null)
{
context.Result = new BadRequestObjectResult("Object is null");
return;
}
if (!context.ModelState.IsValid)
{
context.Result = new UnprocessableEntityObjectResult(context.ModelState);
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
}
在ValidationFilterAttribute类中,我们重写OnActionExecuting方法来执行验证逻辑。我们检查操作参数是否为 IEntity 类型,如果为 null,则返回BadRequestObjectResult。此外,如果 ModelState 无效,我们将返回UnprocessableEntityObjectResult。
接下来,让我们在ConfigureServices方法中将此操作过滤器注册为服务:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("sqlConString")));
services.AddScoped<ValidationFilterAttribute>();
services.AddControllers();
}
对于.NET 6,我们需要在 Program 类中使用构建器变量:
builder.Services.AddDbContext<MovieContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString( "sqlConString" )));
builder.Services.AddScoped<ValidationFilterAttribute>();
builder.Services.AddControllers();
最后,从 POST 和 PUT 操作中删除验证代码,并将ValidationFilterAttribute作为服务应用:
[HttpPost]
[ServiceFilter(typeof(ValidationFilterAttribute))]
public IActionResult Post([FromBody] Movie movie)
{
_context.Movies.Add(movie);
_context.SaveChanges();
return CreatedAtRoute("MovieById", new { id = movie.Id }, movie);
}
[HttpPut("{id}")]
[ServiceFilter(typeof(ValidationFilterAttribute))]
public IActionResult Put(Guid id, [FromBody] Movie movie)
{
var dbMovie = _context.Movies.SingleOrDefault(x => x.Id.Equals(id));
if (dbMovie == null)
{
return NotFound();
}
dbMovie.Map(movie);
_context.Movies.Update(dbMovie);
_context.SaveChanges();
return NoContent();
}
通过将 ValidationFilterAttribute 应用为服务过滤器,我们无需在操作中使用验证代码。代码现在更干净、更具可读性。此外,只要我们的模型类继承自 IEntity 接口,此验证逻辑就可以重用。
services.Configure<ApiBehaviorOptions>(options =>
{
options.SuppressModelStateInvalidFilter = true ;
});
此配置可确保返回操作过滤器的验证结果(例如,UnprocessableEntity),而不是返回验证错误的默认BadRequest结果。
动作过滤器中的依赖注入
为了消除通过 ID 从数据库中获取电影并在 GetById、DELETE 和 PUT 操作中检查其是否存在的代码重复,我们可以创建一个新的操作过滤器来执行此任务。我们还将使用依赖注入将 MovieContext 注入到动作过滤器中。
让我们在 ActionFilters 文件夹中创建一个名为 ValidateEntityExistsAttribute<T> 的新操作过滤器类:
using System;
using System.Linq;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace ActionFilters.ActionFilters
{
public class ValidateEntityExistsAttribute<T> : IActionFilter where T : class, IEntity
{
private readonly MovieContext _context;
public ValidateEntityExistsAttribute(MovieContext context)
{
_context = context;
}
public void OnActionExecuting(ActionExecutingContext context)
{
Guid id = Guid.Empty;
if (context.ActionArguments.ContainsKey("id"))
{
id = (Guid)context.ActionArguments["id"];
}
else
{
context.Result = new BadRequestObjectResult("Bad id parameter");
return;
}
var entity = _context.Set<T>().SingleOrDefault(x => x.Id.Equals(id));
if (entity == null)
{
context.Result = new NotFoundResult();
}
else
{
context.HttpContext.Items.Add("entity", entity);
}
}
public void OnActionExecuted(ActionExecutedContext context)
{
}
}
}
在ValidateEntityExistsAttribute<T>类中,我们使用依赖注入通过构造函数注入MovieContext 。该类是通用的,以便它可以重用于我们项目中的任何模型。在OnActionExecuting方法中,我们从操作上下文中获取 ID 参数并检查该实体是否存在于数据库中。如果找到实体,我们会将其存储在HttpContext .Items 集合中,以便稍后在操作方法中使用。
接下来,让我们在ConfigureServices方法中注册操作过滤器:
services.AddScoped<ValidateEntityExistsAttribute<Movie>>();
对于 .NET 6,我们在 Program 类中使用构建器变量:
builder.Services.AddScoped<ValidateEntityExistsAttribute<Movie>>();
最后,修改我们的操作以将 ValidateEntityExistsAttribute 应用为服务过滤器:
[HttpGet("{id}", Name = "MovieById")] [ServiceFilter(typeof(ValidateEntityExistsAttribute<Movie>))]
public IActionResult Get(Guid id)
{
var dbMovie = HttpContext.Items["entity"] as Movie;
return Ok(dbMovie);
}
[HttpPut("{id}")]
[ServiceFilter(typeof(ValidationFilterAttribute))]
[ServiceFilter(typeof(ValidateEntityExistsAttribute<Movie>))]
public IActionResult Put(Guid id, [FromBody]Movie movie)
{
var dbMovie = HttpContext.Items["entity"] as Movie;
dbMovie.Map(movie);
_context.Movies.Update(dbMovie);
_context.SaveChanges();
return NoContent();
}
[HttpDelete("{id}")]
[ServiceFilter(typeof(ValidateEntityExistsAttribute<Movie>))]
public IActionResult Delete(Guid id)
{
var dbMovie = HttpContext.Items["entity"] as Movie;
_context.Movies.Remove(dbMovie);
_context.SaveChanges();
return NoContent();
}
通过应用ValidateEntityExistsAttribute<Movie>作为服务过滤器,我们确保从数据库中获取电影实体并通过HttpContext.Items在操作方法中可用。这消除了在获取实体并检查其存在时重复代码的需要。
通过这些更改,我们的操作更加清晰、更具可读性,并且通过 ID 获取实体的代码现在可以重用。