{"id":615,"date":"2025-04-05T11:42:27","date_gmt":"2025-04-05T03:42:27","guid":{"rendered":"https:\/\/www.hyy.net\/?p=615"},"modified":"2025-04-05T11:42:27","modified_gmt":"2025-04-05T03:42:27","slug":"asp-net-core-in-action-22-creating-custom-mvc-and-razor-page-%ef%ac%81lters","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=615","title":{"rendered":"ASP.NET Core in Action 22 Creating custom MVC and Razor Page \ufb01lters"},"content":{"rendered":"<p>22 Creating custom MVC and Razor Page filters<br \/>\n22 \u521b\u5efa\u81ea\u5b9a\u4e49 MVC \u548c Razor \u9875\u9762\u7b5b\u9009\u5668<\/p>\n<p>This chapter covers<br \/>\n\u672c\u7ae0\u6db5\u76d6<\/p>\n<p>\u2022  Creating custom filters to refactor complex action methods<br \/>\n\u521b\u5efa\u81ea\u5b9a\u4e49\u7b5b\u9009\u5668\u4ee5\u91cd\u6784\u590d\u6742\u7684\u4f5c\u65b9\u6cd5<br \/>\n\u2022  Using authorization filters to protect your action methods and Razor Pages<br \/>\n\u4f7f\u7528\u6388\u6743\u7b5b\u9009\u5668\u4fdd\u62a4\u4f5c\u65b9\u6cd5\u548c Razor \u9875\u9762<br \/>\n\u2022  Short-circuiting the filter pipeline to bypass action and page handler execution<br \/>\n\u4f7f\u7b5b\u9009\u5668\u7ba1\u9053\u77ed\u8def\u4ee5\u7ed5\u8fc7\u4f5c\u548c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u6267\u884c<br \/>\n\u2022  Injecting dependencies into filters<br \/>\n\u5c06\u4f9d\u8d56\u9879\u6ce8\u5165\u7b5b\u9009\u5668<\/p>\n<p>In chapter 21 I introduced the Model-View-Controller (MVC) and Razor Pages filter pipeline and showed where it fits into the life cycle of a request. You learned how to apply filters to your action method, controllers, and Razor Pages, and the effect of scope on the filter execution order.<br \/>\n\u5728\u7b2c 21 \u7ae0\u4e2d\uff0c\u6211\u4ecb\u7ecd\u4e86\u6a21\u578b-\u89c6\u56fe-\u63a7\u5236\u5668 \uff08MVC\uff09 \u548c Razor Pages \u8fc7\u6ee4\u5668\u7ba1\u9053\uff0c\u5e76\u5c55\u793a\u4e86\u5b83\u5728\u8bf7\u6c42\u751f\u547d\u5468\u671f\u4e2d\u7684\u4f4d\u7f6e\u3002\u4f60\u4e86\u89e3\u4e86\u5982\u4f55\u5c06\u7b5b\u9009\u5668\u5e94\u7528\u4e8e\u4f5c\u65b9\u6cd5\u3001\u63a7\u5236\u5668\u548c Razor Pages\uff0c\u4ee5\u53ca\u8303\u56f4\u5bf9\u7b5b\u9009\u5668\u6267\u884c\u987a\u5e8f\u7684\u5f71\u54cd\u3002<\/p>\n<p>In this chapter you\u2019ll take that knowledge and apply it to a concrete example. You\u2019ll learn to create custom filters that you can use in your own apps and how to use them to reduce duplicate code in your action methods.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u60a8\u5c06\u5229\u7528\u8fd9\u4e9b\u77e5\u8bc6\u5e76\u5c06\u5176\u5e94\u7528\u4e8e\u5177\u4f53\u793a\u4f8b\u3002\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u521b\u5efa\u53ef\u5728\u81ea\u5df1\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\u4f7f\u7528\u7684\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u5668\uff0c\u4ee5\u53ca\u5982\u4f55\u4f7f\u7528\u5b83\u4eec\u6765\u51cf\u5c11\u4f5c\u65b9\u6cd5\u4e2d\u7684\u91cd\u590d\u4ee3\u7801\u3002<\/p>\n<p>In section 22.1 I take you through the filter types in detail, how they fit into the MVC pipeline, and what to use them for. For each one, I\u2019ll provide example implementations that you might use in your own application and describe the built-in options available.<br \/>\n\u5728 Section 22.1 \u4e2d\uff0c\u6211\u5c06\u5411\u60a8\u8be6\u7ec6\u4ecb\u7ecd\u8fc7\u6ee4\u5668\u7c7b\u578b\uff0c\u5b83\u4eec\u5982\u4f55\u9002\u5e94 MVC \u7ba1\u9053\uff0c\u4ee5\u53ca\u4f7f\u7528\u5b83\u4eec\u7684\u7528\u9014\u3002\u5bf9\u4e8e\u6bcf\u4e2a\u9009\u9879\uff0c\u6211\u5c06\u63d0\u4f9b\u60a8\u53ef\u4ee5\u5728\u81ea\u5df1\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\u4f7f\u7528\u7684\u793a\u4f8b\u5b9e\u73b0\uff0c\u5e76\u63cf\u8ff0\u53ef\u7528\u7684\u5185\u7f6e\u9009\u9879\u3002<\/p>\n<p>A key feature of filters is the ability to short-circuit a request by generating a response and halting progression through the filter pipeline. This is similar to the way short-circuiting works in middleware, but there are subtle differences for MVC filters. On top of that, the exact behavior is slightly different for each filter, and I cover that in section 22.2.<br \/>\n\u8fc7\u6ee4\u5668\u7684\u4e00\u4e2a\u5173\u952e\u529f\u80fd\u662f\u80fd\u591f\u901a\u8fc7\u751f\u6210\u54cd\u5e94\u5e76\u505c\u6b62\u901a\u8fc7\u8fc7\u6ee4\u5668\u7ba1\u9053\u7684\u8fdb\u7a0b\u6765\u77ed\u8def\u8bf7\u6c42\u3002\u8fd9\u7c7b\u4f3c\u4e8e\u4e2d\u95f4\u4ef6\u4e2d\u77ed\u8def\u7684\u5de5\u4f5c\u65b9\u5f0f\uff0c\u4f46 MVC \u7b5b\u9009\u5668\u5b58\u5728\u7ec6\u5fae\u7684\u5dee\u5f02\u3002\u6700\u91cd\u8981\u7684\u662f\uff0c\u6bcf\u4e2a\u8fc7\u6ee4\u5668\u7684\u786e\u5207\u884c\u4e3a\u7565\u6709\u4e0d\u540c\uff0c\u6211\u5728 22.2 \u8282\u4e2d\u4ecb\u7ecd\u4e86\u8fd9\u4e00\u70b9\u3002<\/p>\n<p>You typically add MVC filters to the pipeline by implementing them as attributes added to your controller classes, action methods, and Razor Pages. Unfortunately, you can\u2019t easily use dependency injection (DI) with attributes due to the limitations of C#. In section 22.3 I show you how to use the ServiceFilterAttribute and TypeFilterAttribute base classes to enable DI in your filters.<br \/>\n\u901a\u5e38\uff0c\u901a\u8fc7\u5c06 MVC \u7b5b\u9009\u5668\u5b9e\u73b0\u4e3a\u6dfb\u52a0\u5230\u63a7\u5236\u5668\u7c7b\u3001\u4f5c\u65b9\u6cd5\u548c Razor Pages \u7684\u5c5e\u6027\uff0c\u5c06 MVC \u7b5b\u9009\u5668\u6dfb\u52a0\u5230\u7ba1\u9053\u4e2d\u3002\u9057\u61be\u7684\u662f\uff0c\u7531\u4e8e C# \u7684\u9650\u5236\uff0c\u4f60\u4e0d\u80fd\u8f7b\u677e\u5730\u5c06\u4f9d\u8d56\u9879\u6ce8\u5165 \uff08DI\uff09 \u4e0e\u5c5e\u6027\u4e00\u8d77\u4f7f\u7528\u3002\u5728\u7b2c 22.3 \u8282\u4e2d\uff0c\u6211\u5c06\u5411\u60a8\u5c55\u793a\u5982\u4f55\u4f7f\u7528 ServiceFilterAttribute \u548c TypeFilterAttribute \u57fa\u7c7b\u5728\u8fc7\u6ee4\u5668\u4e2d\u542f\u7528 DI\u3002<\/p>\n<p>We covered all the background for filters in chapter 21, so in the next section we jump straight into the code and start creating custom MVC filters.<br \/>\n\u6211\u4eec\u5728\u7b2c 21 \u7ae0\u4e2d\u4ecb\u7ecd\u4e86\u8fc7\u6ee4\u5668\u7684\u6240\u6709\u80cc\u666f\uff0c\u56e0\u6b64\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u76f4\u63a5\u8fdb\u5165\u4ee3\u7801\u5e76\u5f00\u59cb\u521b\u5efa\u81ea\u5b9a\u4e49 MVC \u8fc7\u6ee4\u5668\u3002<\/p>\n<h2>22.1 Creating custom filters for your application<\/h2>\n<p>22.1 \u4e3a\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u521b\u5efa\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u5668<\/p>\n<p>ASP.NET Core includes several filters that you can use out of the box, but often the most useful filters are the custom ones that are specific to your own apps. In this section we\u2019ll work through each of the six types of filters I covered in chapter 21. I\u2019ll explain in more detail what they\u2019re for and when you should use them. I\u2019ll point out examples of these filters that are part of ASP.NET Core itself, and you\u2019ll see how to create custom filters for an example application.<br \/>\nASP.NET Core \u5305\u542b\u591a\u4e2a\u5f00\u7bb1\u5373\u7528\u7684\u7b5b\u9009\u5668\uff0c\u4f46\u901a\u5e38\u6700\u6709\u7528\u7684\u7b5b\u9009\u5668\u662f\u7279\u5b9a\u4e8e\u4f60\u81ea\u5df1\u7684\u5e94\u7528\u7a0b\u5e8f\u7684\u81ea\u5b9a\u4e49\u7b5b\u9009\u5668\u3002\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u6211\u5728\u7b2c 21 \u7ae0\u4e2d\u4ecb\u7ecd\u7684\u516d\u79cd\u7c7b\u578b\u7684\u8fc7\u6ee4\u5668\u4e2d\u7684\u6bcf\u4e00\u79cd\u3002\u6211\u5c06\u66f4\u8be6\u7ec6\u5730\u89e3\u91ca\u5b83\u4eec\u7684\u7528\u9014\u4ee5\u53ca\u4f55\u65f6\u5e94\u8be5\u4f7f\u7528\u5b83\u4eec\u3002\u6211\u5c06\u6307\u51fa\u8fd9\u4e9b\u7b5b\u9009\u5668\u7684\u793a\u4f8b\uff0c\u8fd9\u4e9b\u7b5b\u9009\u5668\u662f ASP.NET Core \u672c\u8eab\u7684\u4e00\u90e8\u5206\uff0c\u60a8\u5c06\u4e86\u89e3\u5982\u4f55\u4e3a\u793a\u4f8b\u5e94\u7528\u7a0b\u5e8f\u521b\u5efa\u81ea\u5b9a\u4e49\u7b5b\u9009\u5668\u3002<\/p>\n<p>To give you something realistic to work with, we\u2019ll start with a web API controller for accessing the recipe application from chapter 12. This controller contains two actions: one for fetching a RecipeDetailViewModel and another for updating a Recipe with new values. The following listing shows your starting point for this chapter, including both action methods.<br \/>\n\u4e3a\u4e86\u7ed9\u4f60\u4e00\u4e9b\u5b9e\u9645\u7684\u4f7f\u7528\uff0c\u6211\u4eec\u5c06\u4ece\u4e00\u4e2a Web API \u63a7\u5236\u5668\u5f00\u59cb\uff0c\u7528\u4e8e\u8bbf\u95ee\u7b2c 12 \u7ae0\u4e2d\u7684\u914d\u65b9\u5e94\u7528\u7a0b\u5e8f\u3002\u6b64\u63a7\u5236\u5668\u5305\u542b\u4e24\u4e2a\u4f5c\uff1a\u4e00\u4e2a\u7528\u4e8e\u83b7\u53d6 RecipeDetailViewModel\uff0c\u53e6\u4e00\u4e2a\u7528\u4e8e\u4f7f\u7528\u65b0\u503c\u66f4\u65b0 Recipe\u3002\u4e0b\u9762\u7684\u6e05\u5355\u663e\u793a\u4e86\u672c\u7ae0\u7684\u8d77\u70b9\uff0c\u5305\u62ec\u4e24\u79cd\u4f5c\u65b9\u6cd5\u3002<\/p>\n<p>Listing 22.1 Recipe web API controller before refactoring to use filters<br \/>\n\u5217\u8868 22.1 \u91cd\u6784\u4ee5\u4f7f\u7528\u8fc7\u6ee4\u5668\u4e4b\u524d\u7684\u914d\u65b9 Web API \u63a7\u5236\u5668<\/p>\n<pre><code>[Route(&quot;api\/recipe&quot;)]\npublic class RecipeApiController : ControllerBase\n{\n    private readonly bool IsEnabled = true;            #A\n    public RecipeService _service; \n    public RecipeApiController(RecipeService service) \n    { \n        _service = service;\n    } \n\n    [HttpGet(&quot;{id}&quot;)]\n    public IActionResult Get(int id)\n    {\n        if (!IsEnabled) { return BadRequest(); }   #B\n        try\n        {\n            if (!_service.DoesRecipeExist(id))   #C\n            {                                    #C\n                return NotFound();               #C\n            }                                    #C\n            var detail = _service.GetRecipeDetail(id);    #D\n            Response.GetTypedHeaders().LastModified =     #E\n                detail.LastModified;                      #E\n            return Ok(detail);    #F\n        }\n        catch (Exception ex)                #G\n        {                                   #G\n            return GetErrorResponse(ex);    #G\n        }                                   #G\n    }\n\n    [HttpPost(&quot;{id}&quot;)]\n    public IActionResult Edit(\n        int id, [FromBody] UpdateRecipeCommand command)\n    {\n        if (!IsEnabled) { return BadRequest(); }     #H\n        try\n        {\n            if (!ModelState.IsValid)             #I\n            {                                    #I\n                return BadRequest(ModelState);   #I\n            }                                    #I\n            if (!_service.DoesRecipeExist(id))    #J\n            {                                     #J\n                return NotFound();                #J\n            }                                     #J\n            _service.UpdateRecipe(command);    #K\n            return Ok();                       #K\n        }\n        catch (Exception ex)               #L\n        {                                  #L\n            return GetErrorResponse(ex);   #L\n        }                                  #L\n    }\n\n    private static IActionResult GetErrorResponse(Exception ex)\n    {\n        var error = new ProblemDetails         \n        {\n            Title = &quot;An error occurred&quot;,         \n            Detail = context.Exception.Message,\n            Status = 500,                      \n            Type = &quot;https:\/\/httpstatuses.com\/500&quot;\n        };                                              \n\n        return new ObjectResult(error)\n        {\n            StatusCode = 500\n        };\n    }\n}<\/code><\/pre>\n<p>\u2776 This field would be passed in as configuration and is used to control access to actions.<br \/>\n\u6b64\u5b57\u6bb5\u5c06\u4f5c\u4e3a\u914d\u7f6e\u4f20\u5165\uff0c\u7528\u4e8e\u63a7\u5236\u5bf9\u4f5c\u7684\u8bbf\u95ee\u3002<br \/>\n\u2777 If the API isn\u2019t enabled, blocks further execution<br \/>\n\u5982\u679c\u672a\u542f\u7528 API\uff0c\u5219\u963b\u6b62\u8fdb\u4e00\u6b65\u6267\u884c<br \/>\n\u2778 If the requested Recipe doesn\u2019t exist, returns a 404 response<br \/>\n\u5982\u679c\u8bf7\u6c42\u7684\u914d\u65b9\u4e0d\u5b58\u5728\uff0c\u5219\u8fd4\u56de 404 \u54cd\u5e94<br \/>\n\u2779 Fetches RecipeDetailViewModel<br \/>\n\u83b7\u53d6 RecipeDetailViewModel<br \/>\n\u277a Sets the Last-Modified response header to the value in the model<br \/>\n\u5c06 Last-Modified \u54cd\u5e94\u6807\u5934\u8bbe\u7f6e\u4e3a\u6a21\u578b\u4e2d\u7684\u503c<br \/>\n\u277b Returns the view model with a 200 response<br \/>\n\u8fd4\u56de\u54cd\u5e94\u4e3a 200 \u7684\u89c6\u56fe\u6a21\u578b<br \/>\n\u277c If an exception occurs, catches it and returns the error in an expected format, as a 500 error<br \/>\n\u5982\u679c\u53d1\u751f\u5f02\u5e38\uff0c\u5219\u6355\u83b7\u5b83\u5e76\u4ee5\u9884\u671f\u7684\u683c\u5f0f\u8fd4\u56de\u9519\u8bef\uff0c \u4f5c\u4e3a 500 \u9519\u8bef<br \/>\n\u277d If the API isn\u2019t enabled, blocks further execution<br \/>\n\u5982\u679c\u672a\u542f\u7528 API\uff0c\u5219\u963b\u6b62\u8fdb\u4e00\u6b65\u6267\u884c<br \/>\n\u277e Validates the binding model and returns a 400 response if there are errors<br \/>\n\u9a8c\u8bc1\u7ed1\u5b9a\u6a21\u578b\u5e76\u5728\u51fa\u73b0\u9519\u8bef\u65f6\u8fd4\u56de 400 \u54cd\u5e94<br \/>\n\u277f If the requested Recipe doesn\u2019t exist, returns a 404 response<br \/>\n\u5982\u679c\u8bf7\u6c42\u7684\u914d\u65b9\u4e0d\u5b58\u5728\uff0c\u5219\u8fd4\u56de 404 \u54cd\u5e94<br \/>\n\u24eb Updates the Recipe from the command and returns a 200 response<br \/>\n\u4ece\u547d\u4ee4\u66f4\u65b0\u914d\u65b9\u5e76\u8fd4\u56de 200 \u54cd\u5e94<br \/>\n\u24ec If an exception occurs, catches it and returns the error in an expected format, as a 500 error<br \/>\n\u5982\u679c\u53d1\u751f\u5f02\u5e38\uff0c\u5219\u6355\u83b7\u5b83\u5e76\u4ee5\u9884\u671f\u7684\u683c\u5f0f\u8fd4\u56de\u9519\u8bef\uff0c \u4f5c\u4e3a 500 \u9519\u8bef<\/p>\n<p>These action methods currently have a lot of code to them, which hides the intent of each action. There\u2019s also quite a lot of duplication between the methods, such as checking that the Recipe entity exists and formatting exceptions.<br \/>\n\u8fd9\u4e9b action method \u76ee\u524d\u6709\u5f88\u591a\u4ee3\u7801\uff0c\u8fd9\u9690\u85cf\u4e86\u6bcf\u4e2a action \u7684\u610f\u56fe\u3002\u65b9\u6cd5\u4e4b\u95f4\u4e5f\u5b58\u5728\u76f8\u5f53\u591a\u7684\u91cd\u590d\uff0c\u4f8b\u5982\u68c0\u67e5 Recipe \u5b9e\u4f53\u662f\u5426\u5b58\u5728\u548c\u683c\u5f0f\u5316\u5f02\u5e38\u3002<\/p>\n<p>In this section you\u2019re going to refactor this controller to use filters for all the code in the methods that\u2019s unrelated to the intent of each action. By the end of the chapter you\u2019ll have a much simpler controller controller that\u2019s far easier to understand, as shown here.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u60a8\u5c06\u91cd\u6784\u6b64\u63a7\u5236\u5668\uff0c\u4ee5\u4fbf\u5bf9\u65b9\u6cd5\u4e2d\u4e0e\u6bcf\u4e2a\u4f5c\u7684\u610f\u56fe\u65e0\u5173\u7684\u6240\u6709\u4ee3\u7801\u4f7f\u7528\u8fc7\u6ee4\u5668\u3002\u5728\u672c\u7ae0\u7ed3\u675f\u65f6\uff0c\u60a8\u5c06\u62e5\u6709\u4e00\u4e2a\u66f4\u7b80\u5355\u7684\u63a7\u5236\u5668\u63a7\u5236\u5668\uff0c\u5b83\u66f4\u5bb9\u6613\u7406\u89e3\uff0c\u5982\u4e0b\u6240\u793a\u3002<\/p>\n<p>Listing 22.2 Recipe web API controller after refactoring to use filters<br \/>\n\u5217\u8868 22.2 \u91cd\u6784\u4e3a\u4f7f\u7528\u8fc7\u6ee4\u5668\u540e\u7684\u914d\u65b9 Web API \u63a7\u5236\u5668<\/p>\n<pre><code>[Route(&quot;api\/recipe&quot;)]\n[ValidateModel]      #A\n[HandleException]    #A\n[FeatureEnabled(IsEnabled = true)]     #A\npublic class RecipeApiController : ControllerBase\n{\n    public RecipeService _service;\n    public RecipeApiController(RecipeService service)\n    {\n        _service = service;\n    }\n\n    [HttpGet(&quot;{id}&quot;)]\n    [EnsureRecipeExists]    #B\n    [AddLastModifiedHeader]    #B\n    public IActionResult Get(int id)\n    {\n        var detail = _service.GetRecipeDetail(id);     #C\n        return Ok(detail);                             #C\n    }\n\n    [HttpPost(&quot;{id}&quot;)]\n    [EnsureRecipeExists]        #D\n    public IActionResult Edit(\n        int id, [FromBody] UpdateRecipeCommand command)\n    {\n        _service.UpdateRecipe(command);    #E\n        return Ok();                       #E\n    }\n}<\/code><\/pre>\n<p>\u2776 The filters encapsulate the majority of logic common to multiple action methods.<br \/>\n\u8fc7\u6ee4\u5668\u5c01\u88c5\u4e86\u591a\u4e2a\u65b9\u6cd5\u901a\u7528\u7684\u5927\u591a\u6570\u903b\u8f91\u3002<br \/>\n\u2777 Placing filters at the action level limits them to a single action.<br \/>\n\u5c06\u8fc7\u6ee4\u5668\u653e\u5728\u4f5c\u7ea7\u522b\u4f1a\u5c06\u5b83\u4eec\u9650\u5236\u4e3a\u5355\u4e2a\u4f5c\u3002<br \/>\n\u2778 The intent of the action, return a Recipe view model, is much clearer.<br \/>\n\u4f5c\u7684\u610f\u56fe\uff08\u8fd4\u56de\u914d\u65b9\u89c6\u56fe\u6a21\u578b\uff09\u8981\u6e05\u6670\u5f97\u591a\u3002<br \/>\n\u2779 Placing filters at the action level can control the order in which they execute.<br \/>\n\u5c06\u8fc7\u6ee4\u5668\u653e\u5728\u4f5c\u7ea7\u522b\u53ef\u4ee5\u63a7\u5236\u5b83\u4eec\u7684\u6267\u884c\u987a\u5e8f\u3002<br \/>\n\u277a The intent of the action, update a Recipe, is much clearer.<br \/>\n\u4f5c update a Recipe \u7684\u610f\u56fe\u8981\u660e\u786e\u5f97\u591a\u3002<\/p>\n<p>I think you\u2019ll have to agree that the controller in listing 22.2 is much easier to read! In this section you\u2019ll refactor the controller bit by bit, removing cross-cutting code to get to something more manageable. All the filters we\u2019ll create in this section will use the sync filter interfaces. I\u2019ll leave it to you, as an exercise, to create their async counterparts. We\u2019ll start by looking at authorization filters and how they relate to security in ASP.NET Core.<br \/>\n\u6211\u60f3\u4f60\u5f97\u540c\u610f\uff0c\u6e05\u5355 22.2 \u4e2d\u7684\u63a7\u5236\u5668\u8981\u5bb9\u6613\u5f97\u591a\uff01\u5728\u672c\u8282\u4e2d\uff0c\u60a8\u5c06\u9010\u6b65\u91cd\u6784\u63a7\u5236\u5668\uff0c\u5220\u9664\u6a2a\u5207\u4ee3\u7801\u4ee5\u83b7\u5f97\u66f4\u6613\u4e8e\u7ba1\u7406\u7684\u5185\u5bb9\u3002\u6211\u4eec\u5c06\u5728\u672c\u8282\u4e2d\u521b\u5efa\u7684\u6240\u6709\u8fc7\u6ee4\u5668\u90fd\u5c06\u4f7f\u7528 sync \u8fc7\u6ee4\u5668\u63a5\u53e3\u3002\u4f5c\u4e3a\u7ec3\u4e60\uff0c\u6211\u5c06\u7559\u7ed9\u60a8\u521b\u5efa\u5b83\u4eec\u7684\u5f02\u6b65\u5bf9\u5e94\u9879\u3002\u9996\u5148\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u6388\u6743\u7b5b\u9009\u5668\u4ee5\u53ca\u5b83\u4eec\u4e0e ASP.NET Core \u4e2d\u7684\u5b89\u5168\u6027\u6709\u4f55\u5173\u7cfb\u3002<\/p>\n<h3>22.1.1 Authorization filters: Protecting your APIs<\/h3>\n<p>22.1.1 \u6388\u6743\u8fc7\u6ee4\u5668\uff1a\u4fdd\u62a4\u60a8\u7684 API<\/p>\n<p>Authentication and authorization are related, fundamental concepts in security that we\u2019ll be looking at in detail in chapters 23 and 24.<br \/>\n\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u662f\u5b89\u5168\u4e2d\u76f8\u5173\u7684\u57fa\u672c\u6982\u5ff5\uff0c\u6211\u4eec\u5c06\u5728\u7b2c 23 \u7ae0\u548c\u7b2c 24 \u7ae0\u4e2d\u8be6\u7ec6\u4ecb\u7ecd\u3002<\/p>\n<p><b>DEFINITION<\/b> Authentication is concerned with determining who made a request. Authorization is concerned with what a user is allowed to access.<br \/>\n\u5b9a\u4e49:\u8eab\u4efd\u9a8c\u8bc1\u4e0e\u786e\u5b9a\u8c01\u53d1\u51fa\u4e86\u8bf7\u6c42\u6709\u5173\u3002\u6388\u6743\u4e0e\u5141\u8bb8\u7528\u6237\u8bbf\u95ee\u7684\u5185\u5bb9\u6709\u5173\u3002<\/p>\n<p>Authorization filters run first in the MVC filter pipeline, before any other filters. They control access to the action method by immediately short-circuiting the pipeline when a request doesn\u2019t meet the necessary requirements.<br \/>\n\u6388\u6743\u7b5b\u9009\u5668\u9996\u5148\u5728 MVC \u7b5b\u9009\u5668\u7ba1\u9053\u4e2d\u8fd0\u884c\uff0c\u7136\u540e\u518d\u8fd0\u884c\u4efb\u4f55\u5176\u4ed6\u7b5b\u9009\u5668\u3002\u5f53\u8bf7\u6c42\u4e0d\u6ee1\u8db3\u5fc5\u8981\u7684\u8981\u6c42\u65f6\uff0c\u5b83\u4eec\u901a\u8fc7\u7acb\u5373\u4f7f\u7ba1\u9053\u77ed\u8def\u6765\u63a7\u5236\u5bf9\u4f5c\u65b9\u6cd5\u7684\u8bbf\u95ee\u3002<\/p>\n<p>ASP.NET Core has a built-in authorization framework that you should use when you need to protect your MVC application or your web APIs. You can configure this framework with custom policies that let you finely control access to your actions.<br \/>\nASP.NET Core \u6709\u4e00\u4e2a\u5185\u7f6e\u7684\u6388\u6743\u6846\u67b6\uff0c\u5f53\u60a8\u9700\u8981\u4fdd\u62a4 MVC \u5e94\u7528\u7a0b\u5e8f\u6216 Web API \u65f6\uff0c\u60a8\u5e94\u8be5\u4f7f\u7528\u8be5\u6846\u67b6\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u81ea\u5b9a\u4e49\u7b56\u7565\u914d\u7f6e\u6b64\u6846\u67b6\uff0c\u4ee5\u4fbf\u7cbe\u7ec6\u63a7\u5236\u5bf9\u4f5c\u7684\u8bbf\u95ee\u3002<\/p>\n<p><b>Tip<\/b> It\u2019s possible to write your own authorization filters by implementing IAuthorizationFilter or IAsyncAuthorizationFilter, but I strongly advise against it. The ASP.NET Core authorization framework is highly configurable and should meet all your needs.<br \/>\n\u63d0\u793a:\u901a\u8fc7\u5b9e\u73b0 IAuthorizationFilter \u6216 IAsyncAuthorizationFilter \u53ef\u4ee5\u7f16\u5199\u81ea\u5df1\u7684\u6388\u6743\u7b5b\u9009\u5668\uff0c\u4f46\u6211\u5f3a\u70c8\u5efa\u8bae\u4e0d\u8981\u8fd9\u6837\u505a\u3002ASP.NET Core \u6388\u6743\u6846\u67b6\u662f\u9ad8\u5ea6\u53ef\u914d\u7f6e\u7684\uff0c\u5e94\u8be5\u53ef\u4ee5\u6ee1\u8db3\u60a8\u7684\u6240\u6709\u9700\u6c42\u3002<\/p>\n<p>At the heart of MVC authorization is an authorization filter, AuthorizeFilter, which you can add to the filter pipeline by decorating your actions or controllers with the [Authorize] attribute. In its simplest form, adding the [Authorize] attribute to an action, as in the following listing, means that the request must be made by an authenticated user to be allowed to continue. If you\u2019re not logged in, it will short-circuit the pipeline, returning a 401 Unauthorized response to the browser.<br \/>\nMVC \u6388\u6743\u7684\u6838\u5fc3\u662f\u6388\u6743\u7b5b\u9009\u5668 AuthorizeFilter\uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7\u4f7f\u7528 [Authorize] \u5c5e\u6027\u4fee\u9970\u4f5c\u6216\u63a7\u5236\u5668\u6765\u5c06\u5176\u6dfb\u52a0\u5230\u7b5b\u9009\u5668\u7ba1\u9053\u4e2d\u3002\u5728\u6700\u7b80\u5355\u7684\u5f62\u5f0f\u4e2d\uff0c\u5c06 [Authorize] \u5c5e\u6027\u6dfb\u52a0\u5230\u4f5c\u4e2d\uff0c\u5982\u4e0b\u9762\u7684\u6e05\u5355\u6240\u793a\uff0c\u610f\u5473\u7740\u8bf7\u6c42\u5fc5\u987b\u7531\u7ecf\u8fc7\u8eab\u4efd\u9a8c\u8bc1\u7684\u7528\u6237\u53d1\u51fa\u624d\u80fd\u7ee7\u7eed\u3002\u5982\u679c\u60a8\u672a\u767b\u5f55\uff0c\u5b83\u5c06\u4f7f\u7ba1\u9053\u77ed\u8def\uff0c\u5411\u6d4f\u89c8\u5668\u8fd4\u56de 401 Unauthorized \u54cd\u5e94\u3002<\/p>\n<p>Listing 22.3 Adding [Authorize] to an action method<br \/>\n\u6e05\u5355 22.3 \u5411\u4f5c\u65b9\u6cd5\u6dfb\u52a0 [Authorize]<\/p>\n<pre><code>public class RecipeApiController : ControllerBase\n{\n    public IActionResult Get(int id)    #A\n    {\n        \/\/ method body\n    }\n\n    [Authorize]                  #B\n    public IActionResult Edit(                             #C\n        int id, [FromBody] UpdateRecipeCommand command)    #C\n    {\n        \/\/ method body\n    }\n}<\/code><\/pre>\n<p>\u2776 The Get method has no [Authorize] attribute, so it can be executed by anyone.<br \/>\nGet \u65b9\u6cd5\u6ca1\u6709 [Authorize] \u5c5e\u6027\uff0c\u56e0\u6b64\u4efb\u4f55\u4eba\u90fd\u53ef\u4ee5\u6267\u884c\u5b83\u3002<br \/>\n\u2777 Adds the AuthorizeFilter to the filter pipeline using [Authorize]<br \/>\n\u4f7f\u7528 [Authorize]\u5c06 AuthorizeFilter \u6dfb\u52a0\u5230\u7b5b\u9009\u5668\u7ba1\u9053<br \/>\n\u2778 The Edit method can be executed only if you\u2019re logged in.<br \/>\n\u53ea\u6709\u5728\u60a8\u767b\u5f55\u540e\u624d\u80fd\u6267\u884c Edit \u65b9\u6cd5\u3002<\/p>\n<p>As with all filters, you can apply the [Authorize] attribute at the controller level to protect all the actions on a controller, to a Razor Page to protect all the page handler methods in a page, or even globally to protect every endpoint in your app.<br \/>\n\u4e0e\u6240\u6709\u7b5b\u9009\u5668\u4e00\u6837\uff0c\u53ef\u4ee5\u5728\u63a7\u5236\u5668\u7ea7\u522b\u5e94\u7528 [Authorize] \u5c5e\u6027\u4ee5\u4fdd\u62a4\u63a7\u5236\u5668\u4e0a\u7684\u6240\u6709\u4f5c\uff0c\u5c06 [Authorize] \u5c5e\u6027\u5e94\u7528\u4e8e Razor \u9875\u9762\u4ee5\u4fdd\u62a4\u9875\u9762\u4e2d\u7684\u6240\u6709\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\uff0c\u751a\u81f3\u53ef\u4ee5\u5168\u5c40\u5e94\u7528\u4ee5\u4fdd\u62a4\u5e94\u7528\u4e2d\u7684\u6bcf\u4e2a\u7ec8\u7ed3\u70b9\u3002<\/p>\n<p><b>NOTE<\/b> We\u2019ll explore authorization in detail in chapter 24, including how to add more detailed requirements so that only specific sets of users can execute an action.<br \/>\n\u6ce8\u610f:\u6211\u4eec\u5c06\u5728\u7b2c 24 \u7ae0\u4e2d\u8be6\u7ec6\u63a2\u8ba8\u6388\u6743\uff0c\u5305\u62ec\u5982\u4f55\u6dfb\u52a0\u66f4\u8be6\u7ec6\u7684\u8981\u6c42\uff0c\u4ee5\u4fbf\u53ea\u6709\u7279\u5b9a\u7684\u7528\u6237\u96c6\u624d\u80fd\u6267\u884c\u4f5c\u3002<\/p>\n<p>The next filters in the pipeline are resource filters. In the next section you\u2019ll extract some of the common code from RecipeApiController and see how easy it is to create a short-circuiting filter.<br \/>\n\u7ba1\u9053\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7b5b\u9009\u5668\u662f\u8d44\u6e90\u7b5b\u9009\u5668\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u60a8\u5c06\u4ece RecipeApiController \u4e2d\u63d0\u53d6\u4e00\u4e9b\u901a\u7528\u4ee3\u7801\uff0c\u5e76\u4e86\u89e3\u521b\u5efa\u77ed\u8def\u8fc7\u6ee4\u5668\u662f\u591a\u4e48\u5bb9\u6613\u3002<\/p>\n<h3>22.1.2 Resource filters: Short-circuiting your action methods<\/h3>\n<p>22.1.2 \u8d44\u6e90\u8fc7\u6ee4\u5668\uff1a\u4f7f\u4f5c\u65b9\u6cd5\u77ed\u8def<\/p>\n<p>Resource filters are the first general-purpose filters in the MVC filter pipeline. In chapter 21 you saw minimal examples of both sync and async resource filters, which logged to the console. In your own apps, you can use resource filters for a wide range of purposes, thanks to the fact that they execute so early (and late) in the filter pipeline.<br \/>\n\u8d44\u6e90\u7b5b\u9009\u5668\u662f MVC \u7b5b\u9009\u5668\u7ba1\u9053\u4e2d\u7684\u7b2c\u4e00\u4e2a\u901a\u7528\u7b5b\u9009\u5668\u3002\u5728\u7b2c 21 \u7ae0\u4e2d\uff0c\u60a8\u770b\u5230\u4e86 sync \u548c async \u8d44\u6e90\u8fc7\u6ee4\u5668\u7684\u6700\u5c0f\u793a\u4f8b\uff0c\u5b83\u4eec\u8bb0\u5f55\u5230\u63a7\u5236\u53f0\u4e2d\u3002\u5728\u60a8\u81ea\u5df1\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u60a8\u53ef\u4ee5\u5c06\u8d44\u6e90\u7b5b\u9009\u6761\u4ef6\u7528\u4e8e\u591a\u79cd\u7528\u9014\uff0c\u8fd9\u8981\u5f52\u529f\u4e8e\u5b83\u4eec\u5728\u7b5b\u9009\u7ba1\u9053\u4e2d\u6267\u884c\u5f97\u5982\u6b64\u65e9\uff08\u548c\u5ef6\u8fdf\uff09\u7684\u4e8b\u5b9e\u3002<\/p>\n<p>The ASP.NET Core framework includes a few implementations of resource filters you can use in your apps:<br \/>\nASP.NET Core \u6846\u67b6\u5305\u542b\u4e00\u4e9b\u53ef\u5728\u5e94\u7528\u4e2d\u4f7f\u7528\u7684\u8d44\u6e90\u7b5b\u9009\u5668\u5b9e\u73b0\uff1a<\/p>\n<p>\u2022  ConsumesAttribute\u2014Can be used to restrict the allowed formats an action method can accept. If your action is decorated with [Consumes(&quot;application\/json&quot;)], but the client sends the request as Extensible Markup Language (XML), the resource filter will short-circuit the pipeline and return a 415 Unsupported Media Type response.<br \/>\nConsumesAttribute - \u53ef\u7528\u4e8e\u9650\u5236\u4f5c\u65b9\u6cd5\u53ef\u4ee5\u63a5\u53d7\u7684\u5141\u8bb8\u683c\u5f0f\u3002\u5982\u679c\u60a8\u7684\u4f5c\u4f7f\u7528 [Consumes\uff08\u201capplication\/json\u201d\uff09] \u4fee\u9970\uff0c\u4f46\u5ba2\u6237\u7aef\u4ee5\u53ef\u6269\u5c55\u6807\u8bb0\u8bed\u8a00 \uff08XML\uff09 \u7684\u5f62\u5f0f\u53d1\u9001\u8bf7\u6c42\uff0c\u5219\u8d44\u6e90\u7b5b\u9009\u5668\u5c06\u4f7f\u7ba1\u9053\u77ed\u8def\u5e76\u8fd4\u56de 415 \u4e0d\u652f\u6301\u7684\u5a92\u4f53\u7c7b\u578b\u54cd\u5e94\u3002<br \/>\n\u2022  SkipStatusCodePagesAttribute\u2014This filter prevents the StatusCodePagesMiddleware from running for the response. This can be useful if, for example, you have both web API controllers and Razor Pages in the same application. You can apply this attribute to the controllers to ensure that API error responses are passed untouched, but all error responses from Razor Pages are handled by the middleware.<br \/>\nSkipStatusCodePagesAttribute - \u6b64\u7b5b\u9009\u5668\u53ef\u9632\u6b62 StatusCodePagesMiddleware \u9488\u5bf9\u54cd\u5e94\u8fd0\u884c\u3002\u4f8b\u5982\uff0c\u5982\u679c\u540c\u4e00\u5e94\u7528\u7a0b\u5e8f\u4e2d\u540c\u65f6\u5177\u6709 Web API \u63a7\u5236\u5668\u548c Razor Pages\uff0c\u8fd9\u53ef\u80fd\u975e\u5e38\u6709\u7528\u3002\u53ef\u4ee5\u5c06\u6b64\u5c5e\u6027\u5e94\u7528\u4e8e\u63a7\u5236\u5668\uff0c\u4ee5\u786e\u4fdd API \u9519\u8bef\u54cd\u5e94\u539f\u5c01\u4e0d\u52a8\u5730\u4f20\u9012\uff0c\u4f46\u6765\u81ea Razor Pages \u7684\u6240\u6709\u9519\u8bef\u54cd\u5e94\u90fd\u7531\u4e2d\u95f4\u4ef6\u5904\u7406\u3002<\/p>\n<p>Resource filters are useful when you want to ensure that the filter runs early in the pipeline, before model binding. They provide an early hook into the pipeline for your logic so you can quickly short-circuit the request if you need to.<br \/>\n\u5f53\u60a8\u60f3\u8981\u786e\u4fdd\u7b5b\u9009\u6761\u4ef6\u5728\u6a21\u578b\u7ed1\u5b9a\u4e4b\u524d\u5728\u7ba1\u9053\u4e2d\u5c3d\u65e9\u8fd0\u884c\u65f6\uff0c\u8d44\u6e90\u7b5b\u9009\u6761\u4ef6\u975e\u5e38\u6709\u7528\u3002\u5b83\u4eec\u4e3a\u60a8\u7684 logic \u63d0\u4f9b\u4e86\u7ba1\u9053\u7684\u65e9\u671f\u94a9\u5b50\uff0c\u56e0\u6b64\u60a8\u53ef\u4ee5\u5728\u9700\u8981\u65f6\u5feb\u901f\u77ed\u8def\u8bf7\u6c42\u3002<\/p>\n<p>Look back at listing 22.1 and see whether you can refactor any of the code into a resource filter. One candidate line appears at the start of both the Get and Edit methods:<br \/>\n\u56de\u987e\u4e00\u4e0b\u6e05\u5355 22.1\uff0c\u770b\u770b\u4f60\u662f\u5426\u53ef\u4ee5\u5c06\u4efb\u4f55\u4ee3\u7801\u91cd\u6784\u4e3a\u8d44\u6e90\u8fc7\u6ee4\u5668\u3002\u4e00\u4e2a\u5019\u9009\u884c\u51fa\u73b0\u5728 Get \u548c Edit \u65b9\u6cd5\u7684\u5f00\u5934\uff1a<\/p>\n<pre><code>if (!IsEnabled) { return BadRequest(); }<\/code><\/pre>\n<p>This line of code is a feature toggle that you can use to disable the availability of the whole API, based on the IsEnabled field. In practice, you\u2019d probably load the IsEnabled field from a database or configuration file so you could control the availability dynamically at runtime, but for this example I\u2019m using a hardcoded value.<br \/>\n\u8fd9\u884c\u4ee3\u7801\u662f\u4e00\u4e2a\u529f\u80fd\u5207\u6362\uff0c\u53ef\u7528\u4e8e\u6839\u636e IsEnabled \u5b57\u6bb5\u7981\u7528\u6574\u4e2a API \u7684\u53ef\u7528\u6027\u3002\u5728\u5b9e\u8df5\u4e2d\uff0c\u60a8\u53ef\u80fd\u4f1a\u4ece\u6570\u636e\u5e93\u6216\u914d\u7f6e\u6587\u4ef6\u52a0\u8f7d IsEnabled \u5b57\u6bb5\uff0c\u4ee5\u4fbf\u60a8\u53ef\u4ee5\u5728\u8fd0\u884c\u65f6\u52a8\u6001\u63a7\u5236\u53ef\u7528\u6027\uff0c\u4f46\u5bf9\u4e8e\u6b64\u793a\u4f8b\uff0c\u6211\u4f7f\u7528\u7684\u662f\u786c\u7f16\u7801\u503c\u3002<\/p>\n<p><b>Tip<\/b> To read more about using feature toggles in your applications, see my series \u201cAdding feature flags to an ASP.NET Core app\u201d at <a href=\"http:\/\/mng.bz\/2e40\">http:\/\/mng.bz\/2e40<\/a>.<br \/>\n\u63d0\u793a:\u8981\u4e86\u89e3\u6709\u5173\u5728\u5e94\u7528\u7a0b\u5e8f\u4e2d\u4f7f\u7528\u529f\u80fd\u5207\u6362\u7684\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605\u6211\u5728 <a href=\"http:\/\/mng.bz\/2e40\">http:\/\/mng.bz\/2e40<\/a> \u4e0a\u7684\u7cfb\u5217\u6587\u7ae0\u201c\u5411 ASP.NET Core \u5e94\u7528\u7a0b\u5e8f\u6dfb\u52a0\u529f\u80fd\u6807\u5fd7\u201d\u3002<\/p>\n<p>This piece of code is self-contained cross-cutting logic, which is somewhat orthogonal to the main intent of each action method\u2014a perfect candidate for a filter. You want to execute the feature toggle early in the pipeline, before any other logic, so a resource filter makes sense.<br \/>\n\u8fd9\u6bb5\u4ee3\u7801\u662f\u81ea\u5305\u542b\u7684\u6a2a\u5207\u903b\u8f91\uff0c\u5b83\u4e0e\u6bcf\u4e2a\u4f5c\u65b9\u6cd5\u7684\u4e3b\u8981\u610f\u56fe\u5728\u67d0\u79cd\u7a0b\u5ea6\u4e0a\u6b63\u4ea4\uff0c\u662f\u8fc7\u6ee4\u5668\u7684\u5b8c\u7f8e\u5019\u9009\u8005\u3002\u60a8\u5e0c\u671b\u5728\u7ba1\u9053\u7684\u65e9\u671f\u3001\u4efb\u4f55\u5176\u4ed6\u903b\u8f91\u4e4b\u524d\u6267\u884c\u529f\u80fd\u5207\u6362\uff0c\u56e0\u6b64\u8d44\u6e90\u7b5b\u9009\u6761\u4ef6\u662f\u6709\u610f\u4e49\u7684\u3002<\/p>\n<p><b>Tip<\/b> Technically, you could also use an authorization filter for this example, but I\u2019m following my own advice of \u201cDon\u2019t write your own authorization filters!\u201d<br \/>\n\u63d0\u793a:\u4ece\u6280\u672f\u4e0a\u8bb2\uff0c\u60a8\u4e5f\u53ef\u4ee5\u5bf9\u6b64\u793a\u4f8b\u4f7f\u7528\u6388\u6743\u8fc7\u6ee4\u5668\uff0c\u4f46\u6211\u9075\u5faa\u6211\u81ea\u5df1\u7684\u5efa\u8bae\uff0c\u5373\u201c\u4e0d\u8981\u7f16\u5199\u81ea\u5df1\u7684\u6388\u6743\u8fc7\u6ee4\u5668\uff01<\/p>\n<p>The next listing shows an implementation of FeatureEnabledAttribute, which extracts the logic from the action methods and moves it into the filter. I\u2019ve also exposed the IsEnabled field as a property on the filter.<br \/>\n\u4e0b\u4e00\u4e2a\u6e05\u5355\u663e\u793a\u4e86 FeatureEnabledAttribute \u7684\u5b9e\u73b0\uff0c\u5b83\u4ece\u4f5c\u65b9\u6cd5\u4e2d\u63d0\u53d6\u903b\u8f91\u5e76\u5c06\u5176\u79fb\u52a8\u5230\u8fc7\u6ee4\u5668\u4e2d\u3002\u6211\u8fd8\u5c06 IsEnabled \u5b57\u6bb5\u4f5c\u4e3a\u7b5b\u9009\u5668\u4e0a\u7684\u5c5e\u6027\u516c\u5f00\u3002<\/p>\n<p>Listing 22.4 The FeatureEnabledAttribute resource filter<br \/>\n\u5217\u8868 22.4 FeatureEnabledAttribute \u8d44\u6e90\u8fc7\u6ee4\u5668<\/p>\n<pre><code>public class FeatureEnabledAttribute : Attribute, IResourceFilter\n{\n    public bool IsEnabled { get; set; }   #A\n    public void OnResourceExecuting(        #B\n        ResourceExecutingContext context)   #B\n    {\n        if (!IsEnabled)                                #C\n        {                                              #C\n            context.Result = new BadRequestResult();   #C\n        }                                              #C\n    }\n    public void OnResourceExecuted(             #D\n        ResourceExecutedContext context) { }    #D\n}<\/code><\/pre>\n<p>\u2776 Defines whether the feature is enabled<br \/>\n\u5b9a\u4e49\u662f\u5426\u542f\u7528\u8be5\u529f\u80fd<br \/>\n\u2777 Executes before model binding, early in the filter pipeline<br \/>\n\u5728\u6a21\u578b\u7ed1\u5b9a\u4e4b\u524d\u6267\u884c\uff0c\u5728\u8fc7\u6ee4\u5668\u7ba1\u9053\u7684\u65e9\u671f<br \/>\n\u2778 If the feature isn\u2019t enabled, short-circuits the pipeline by setting the context.Result property<br \/>\n\u5982\u679c\u672a\u542f\u7528\u8be5\u529f\u80fd\uff0c\u5219\u901a\u8fc7\u8bbe\u7f6e\u4e0a\u4e0b\u6587\u6765\u77ed\u8def\u7ba1\u9053\u3002\u7ed3\u679c\u5c5e\u6027<br \/>\n\u2779 Must be implemented to satisfy IResourceFilter, but not needed in this case<br \/>\n\u5fc5\u987b\u5b9e\u73b0\u4ee5\u6ee1\u8db3 IResourceFilter\uff0c\u4f46\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\u4e0d\u9700\u8981<\/p>\n<p>This simple resource filter demonstrates a few important concepts, which are applicable to most filter types:<br \/>\n\u8fd9\u4e2a\u7b80\u5355\u7684\u8d44\u6e90\u7b5b\u9009\u6761\u4ef6\u6f14\u793a\u4e86\u51e0\u4e2a\u91cd\u8981\u7684\u6982\u5ff5\uff0c\u8fd9\u4e9b\u6982\u5ff5\u9002\u7528\u4e8e\u5927\u591a\u6570\u7b5b\u9009\u6761\u4ef6\u7c7b\u578b\uff1a<\/p>\n<p>\u2022  The filter is an attribute as well as a filter. This lets you decorate your controller, action methods, and Razor Pages with it using [FeatureEnabled(IsEnabled = true)].<br \/>\n\u8fc7\u6ee4\u5668\u65e2\u662f\u5c5e\u6027\u53c8\u662f\u8fc7\u6ee4\u5668\u3002\u8fd9\u6837\uff0c\u60a8\u5c31\u53ef\u4ee5\u4f7f\u7528 [FeatureEnabled\uff08IsEnabled = true\uff09] \u4f7f\u7528\u5b83\u6765\u88c5\u9970\u63a7\u5236\u5668\u3001\u4f5c\u65b9\u6cd5\u548c Razor \u9875\u9762\u3002<\/p>\n<p>\u2022  The filter interface consists of two methods: <em>Executing, which runs before model binding, and <\/em>Executed, which runs after the result has executed. You must implement both, even if you only need one for your use case.<br \/>\n\u7b5b\u9009\u5668\u63a5\u53e3\u7531\u4e24\u79cd\u65b9\u6cd5\u7ec4\u6210\uff1aExecuting \uff08\u5728\u6a21\u578b\u7ed1\u5b9a\u4e4b\u524d\u8fd0\u884c\uff09\u548c Executed \uff08\u5728\u7ed3\u679c\u6267\u884c\u4e4b\u540e\u8fd0\u884c\uff09\u3002\u60a8\u5fc5\u987b\u540c\u65f6\u5b9e\u65bd\u8fd9\u4e24\u4e2a\u9879\u76ee\uff0c\u5373\u4f7f\u60a8\u7684\u7528\u4f8b\u53ea\u9700\u8981\u4e00\u4e2a\u9879\u76ee\u3002<\/p>\n<p>\u2022  The filter execution methods provide a context object. This provides access to, among other things, the HttpContext for the request and metadata about the action method that was selected.<br \/>\n\u8fc7\u6ee4\u5668\u6267\u884c\u65b9\u6cd5\u63d0\u4f9b\u4e0a\u4e0b\u6587\u5bf9\u8c61\u3002\u8fd9\u63d0\u4f9b\u4e86\u5bf9\u8bf7\u6c42\u7684 HttpContext \u548c\u6709\u5173\u6240\u9009\u4f5c\u65b9\u6cd5\u7684\u5143\u6570\u636e\u7b49\u7684\u8bbf\u95ee\u3002<\/p>\n<p>\u2022  To short-circuit the pipeline, set the context.Result property to an IactionResult instance. The framework will execute this result to generate the response, bypassing any remaining filters in the pipeline and skipping the action method (or page handler) entirely. In this example, if the feature isn\u2019t enabled, you bypass the pipeline by returning BadRequestResult, which returns a 400 error to the client.<br \/>\n\u8981\u4f7f\u7ba1\u9053\u77ed\u8def\uff0c\u8bf7\u8bbe\u7f6e\u4e0a\u4e0b\u6587\u3002Result \u5c5e\u6027\u8bbe\u7f6e\u4e3a IactionResult \u5b9e\u4f8b\u3002\u6846\u67b6\u5c06\u6267\u884c\u6b64\u7ed3\u679c\u4ee5\u751f\u6210\u54cd\u5e94\uff0c\u7ed5\u8fc7\u7ba1\u9053\u4e2d\u7684\u4efb\u4f55\u5269\u4f59\u7b5b\u9009\u5668\uff0c\u5e76\u5b8c\u5168\u8df3\u8fc7\u4f5c\u65b9\u6cd5\uff08\u6216\u9875\u9762\u5904\u7406\u7a0b\u5e8f\uff09\u3002\u5728\u6b64\u793a\u4f8b\u4e2d\uff0c\u5982\u679c\u672a\u542f\u7528\u8be5\u529f\u80fd\uff0c\u5219\u901a\u8fc7\u8fd4\u56de BadRequestResult \u6765\u7ed5\u8fc7\u7ba1\u9053\uff0c\u8fd9\u4f1a\u5411\u5ba2\u6237\u7aef\u8fd4\u56de 400 \u9519\u8bef\u3002<\/p>\n<p>By moving this logic into the resource filter, you can remove it from your action methods and instead decorate the whole API controller with a simple attribute:<br \/>\n\u901a\u8fc7\u5c06\u6b64\u903b\u8f91\u79fb\u52a8\u5230\u8d44\u6e90\u8fc7\u6ee4\u5668\u4e2d\uff0c\u60a8\u53ef\u4ee5\u5c06\u5176\u4ece action \u65b9\u6cd5\u4e2d\u5220\u9664\uff0c\u800c\u662f\u4f7f\u7528 simple \u5c5e\u6027\u88c5\u9970\u6574\u4e2a API \u63a7\u5236\u5668\uff1a<\/p>\n<pre><code>[FeatureEnabled(IsEnabled = true)]\n[Route(&quot;api\/recipe&quot;)]\npublic class RecipeApiController : ControllerBase<\/code><\/pre>\n<p>You\u2019ve extracted only two lines of code from your action methods so far, but you\u2019re on the right track. In the next section we\u2019ll move on to action filters and extract two more filters from the action method code.<br \/>\n\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u60a8\u53ea\u4ece action \u65b9\u6cd5\u4e2d\u63d0\u53d6\u4e86\u4e24\u884c\u4ee3\u7801\uff0c\u4f46\u60a8\u8d70\u5728\u6b63\u786e\u7684\u8f68\u9053\u4e0a\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u7ee7\u7eed\u8ba8\u8bba\u4f5c\u7b5b\u9009\u5668\uff0c\u5e76\u4ece\u4f5c\u65b9\u6cd5\u4ee3\u7801\u4e2d\u63d0\u53d6\u53e6\u5916\u4e24\u4e2a\u7b5b\u9009\u5668\u3002<\/p>\n<h3>22.1.3 Action filters: Customizing model binding and action results<\/h3>\n<p>22.1.3\u4f5c\u8fc7\u6ee4\u5668\uff1a\u81ea\u5b9a\u4e49\u6a21\u578b\u7ed1\u5b9a\u548c\u4f5c\u7ed3\u679c<\/p>\n<p>Action filters run just after model binding, before the action method executes. Thanks to this positioning, action filters can access all the arguments that will be used to execute the action method, which makes them a powerful way of extracting common logic out of your actions.<br \/>\nAction\u7b5b\u9009\u5668\u5728\u6a21\u578b\u7ed1\u5b9a\u4e4b\u540e\uff0c\u5728\u4f5c\u65b9\u6cd5\u6267\u884c\u4e4b\u524d\u8fd0\u884c\u3002\u7531\u4e8e\u8fd9\u79cd\u5b9a\u4f4d\uff0c\u52a8\u4f5c\u8fc7\u6ee4\u5668\u53ef\u4ee5\u8bbf\u95ee\u5c06\u7528\u4e8e\u6267\u884c\u52a8\u4f5c\u65b9\u6cd5\u7684\u6240\u6709\u53c2\u6570\uff0c\u8fd9\u4f7f\u5b83\u4eec\u6210\u4e3a\u4ece\u52a8\u4f5c\u4e2d\u63d0\u53d6\u901a\u7528\u903b\u8f91\u7684\u5f3a\u5927\u65b9\u5f0f\u3002<\/p>\n<p>On top of this, they run after the action method has executed and can completely change or replace the IActionResult returned by the action if you want. They can even handle exceptions thrown in the action.<br \/>\n\u6700\u91cd\u8981\u7684\u662f\uff0c\u5b83\u4eec\u5728\u4f5c\u65b9\u6cd5\u6267\u884c\u540e\u8fd0\u884c\uff0c\u5e76\u4e14\u53ef\u4ee5\u6839\u636e\u9700\u8981\u5b8c\u5168\u66f4\u6539\u6216\u66ff\u6362\u4f5c\u8fd4\u56de\u7684 IActionResult\u3002\u5b83\u4eec\u751a\u81f3\u53ef\u4ee5\u5904\u7406\u4f5c\u4e2d\u5f15\u53d1\u7684\u5f02\u5e38\u3002<\/p>\n<p><b>NOTE<\/b> Action filters don\u2019t execute for Razor Pages. Similarly, page filters don\u2019t execute for action methods.<br \/>\n\u6ce8\u610f:\u4e0d\u4f1a\u5bf9 Razor Pages \u6267\u884c\u4f5c\u7b5b\u9009\u5668\u3002\u540c\u6837\uff0c\u9875\u9762\u8fc7\u6ee4\u5668\u4e0d\u4f1a\u5bf9\u4f5c\u65b9\u6cd5\u6267\u884c\u3002<\/p>\n<p>The ASP.NET Core framework includes several action filters out of the box. One of these commonly used filters is ResponseCacheFilter, which sets HTTP caching headers on your action-method responses.<br \/>\nASP.NET Core \u6846\u67b6\u5305\u542b\u591a\u4e2a\u73b0\u6210\u7684\u4f5c\u7b5b\u9009\u5668\u3002\u5176\u4e2d\u4e00\u4e2a\u5e38\u7528\u7684\u8fc7\u6ee4\u5668\u662f ResponseCacheFilter\uff0c\u5b83\u5728\u4f5c\u65b9\u6cd5\u54cd\u5e94\u4e0a\u8bbe\u7f6e HTTP \u7f13\u5b58\u6807\u5934\u3002<\/p>\n<p><b>NOTE<\/b> I have described filters as being attributes, but that\u2019s not always the case. For example, the action filter is called ResponseCacheFilter, but this type is internal to the ASP.NET Core framework. To apply the filter, you use the public [ResponseCache] attribute instead, and the framework automatically configures the ResponseCacheFilter as appropriate. This separation between attribute and filter is largely an artifact of the internal design, but it can be useful, as you\u2019ll see in section 22.3.<br \/>\n\u6ce8\u610f:\u6211\u66fe\u5c06\u8fc7\u6ee4\u5668\u63cf\u8ff0\u4e3a\u5c5e\u6027\uff0c\u4f46\u60c5\u51b5\u5e76\u975e\u603b\u662f\u5982\u6b64\u3002\u4f8b\u5982\uff0c\u4f5c\u7b5b\u9009\u5668\u79f0\u4e3a ResponseCacheFilter\uff0c\u4f46\u6b64\u7c7b\u578b\u662f ASP.NET Core \u6846\u67b6\u7684\u5185\u90e8\u7c7b\u578b\u3002\u82e5\u8981\u5e94\u7528\u7b5b\u9009\u5668\uff0c\u8bf7\u6539\u7528\u516c\u5171 [ResponseCache] \u5c5e\u6027\uff0c\u6846\u67b6\u4f1a\u6839\u636e\u9700\u8981\u81ea\u52a8\u914d\u7f6e ResponseCacheFilter\u3002attribute \u548c filter \u4e4b\u95f4\u7684\u8fd9\u79cd\u5206\u79bb\u5728\u5f88\u5927\u7a0b\u5ea6\u4e0a\u662f\u5185\u90e8\u8bbe\u8ba1\u7684\u4ea7\u7269\uff0c\u4f46\u5b83\u53ef\u80fd\u5f88\u6709\u7528\uff0c\u6b63\u5982\u60a8\u5c06\u5728 Section 22.3 \u4e2d\u770b\u5230\u7684\u90a3\u6837\u3002<\/p>\n<blockquote>\n<p>Response caching vs. output caching<br \/>\n\u54cd\u5e94\u7f13\u5b58\u4e0e\u8f93\u51fa\u7f13\u5b58<\/p>\n<p>Caching is a broad topic that aims to improve the performance of an application over the naive approach. But caching can also make debugging issues difficult and may even be undesirable in some situations. Consequently, I often apply ResponseCacheFilter to my action methods to set HTTP caching headers that disable caching! You can read about this and other approaches to caching in Microsoft\u2019s \u201cResponse caching in ASP.NET Core\u201d documentation at <a href=\"http:\/\/mng.bz\/2eGd\">http:\/\/mng.bz\/2eGd<\/a>.<br \/>\n\u7f13\u5b58\u662f\u4e00\u4e2a\u5e7f\u6cdb\u7684\u4e3b\u9898\uff0c\u65e8\u5728\u901a\u8fc7\u7b80\u5355\u7684\u65b9\u6cd5\u63d0\u9ad8\u5e94\u7528\u7a0b\u5e8f\u7684\u6027\u80fd\u3002\u4f46\u662f\uff0c\u7f13\u5b58\u4e5f\u4f1a\u4f7f\u8c03\u8bd5\u95ee\u9898\u53d8\u5f97\u56f0\u96be\uff0c\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\u751a\u81f3\u53ef\u80fd\u662f\u4e0d\u53ef\u53d6\u7684\u3002\u56e0\u6b64\uff0c\u6211\u7ecf\u5e38\u5c06 ResponseCacheFilter \u5e94\u7528\u4e8e\u6211\u7684\u4f5c\u65b9\u6cd5\uff0c\u4ee5\u8bbe\u7f6e\u7981\u7528\u7f13\u5b58\u7684 HTTP \u7f13\u5b58\u6807\u5934\uff01\u60a8\u53ef\u4ee5\u5728 <a href=\"http:\/\/mng.bz\/2eGd\">http:\/\/mng.bz\/2eGd<\/a> \u7684 Microsoft \u7684\u201cASP.NET Core \u4e2d\u7684\u54cd\u5e94\u7f13\u5b58\u201d\u6587\u6863\u4e2d\u9605\u8bfb\u6709\u5173\u6b64\u65b9\u6cd5\u548c\u5176\u4ed6\u7f13\u5b58\u65b9\u6cd5\u7684\u4fe1\u606f\u3002<\/p>\n<p>Note that the ResponseCacheFilter applies cache control headers only to your outgoing responses; it doesn\u2019t cache the response on the server. These headers tell the client (such as a browser) whether it can skip sending a request and reuse the response. If you have relatively static endpoints, this can massively reduce the load on your app.<br \/>\n\u8bf7\u6ce8\u610f\uff0cResponseCacheFilter \u4ec5\u5c06\u7f13\u5b58\u63a7\u5236\u6807\u5934\u5e94\u7528\u4e8e\u60a8\u7684\u4f20\u51fa\u54cd\u5e94;\u5b83\u4e0d\u4f1a\u5728\u670d\u52a1\u5668\u4e0a\u7f13\u5b58\u54cd\u5e94\u3002\u8fd9\u4e9b\u6807\u5934\u544a\u8bc9\u5ba2\u6237\u7aef\uff08\u4f8b\u5982\u6d4f\u89c8\u5668\uff09\u662f\u5426\u53ef\u4ee5\u8df3\u8fc7\u53d1\u9001\u8bf7\u6c42\u5e76\u91cd\u7528\u54cd\u5e94\u3002\u5982\u679c\u60a8\u6709\u76f8\u5bf9\u9759\u6001\u7684\u7ec8\u7aef\u8282\u70b9\uff0c\u8fd9\u53ef\u4ee5\u5927\u5927\u51cf\u5c11\u5e94\u7528\u7a0b\u5e8f\u7684\u8d1f\u8f7d\u3002<\/p>\n<p>This is different from output caching, introduced in .NET 7. Output caching involves storing a generated response on the server and reusing it for subsequent requests. In the simplest case, the response is stored in memory and reused for appropriate requests, but you can configure ASP.NET Core to store the output elsewhere, such as a database.<br \/>\n\u8fd9\u4e0e .NET 7 \u4e2d\u5f15\u5165\u7684\u8f93\u51fa\u7f13\u5b58\u4e0d\u540c\u3002\u8f93\u51fa\u7f13\u5b58\u6d89\u53ca\u5c06\u751f\u6210\u7684\u54cd\u5e94\u5b58\u50a8\u5728\u670d\u52a1\u5668\u4e0a\uff0c\u5e76\u5c06\u5176\u91cd\u65b0\u7528\u4e8e\u540e\u7eed\u8bf7\u6c42\u3002\u5728\u6700\u7b80\u5355\u7684\u60c5\u51b5\u4e0b\uff0c\u54cd\u5e94\u5b58\u50a8\u5728\u5185\u5b58\u4e2d\u5e76\u91cd\u590d\u7528\u4e8e\u9002\u5f53\u7684\u8bf7\u6c42\uff0c\u4f46\u60a8\u53ef\u4ee5\u5c06 ASP.NET Core \u914d\u7f6e\u4e3a\u5c06\u8f93\u51fa\u5b58\u50a8\u5728\u5176\u4ed6\u4f4d\u7f6e\uff0c\u4f8b\u5982\u6570\u636e\u5e93\u3002<\/p>\n<p>Output caching is generally more configurable than response caching, as you can choose exactly what to cache and when to invalidate it, but it is also much more resource-heavy. For details on how to enable output caching for an endpoint, see the documentation at <a href=\"http:\/\/mng.bz\/Bmlv\">http:\/\/mng.bz\/Bmlv<\/a>.<br \/>\n\u8f93\u51fa\u7f13\u5b58\u901a\u5e38\u6bd4\u54cd\u5e94\u7f13\u5b58\u66f4\u6613\u4e8e\u914d\u7f6e\uff0c\u56e0\u4e3a\u60a8\u53ef\u4ee5\u51c6\u786e\u9009\u62e9\u8981\u7f13\u5b58\u7684\u5185\u5bb9\u4ee5\u53ca\u4f55\u65f6\u4f7f\u5176\u5931\u6548\uff0c\u4f46\u5b83\u7684\u8d44\u6e90\u6d88\u8017\u4e5f\u8981\u5927\u5f97\u591a\u3002\u6709\u5173\u5982\u4f55\u4e3a\u7ec8\u7aef\u8282\u70b9\u542f\u7528\u8f93\u51fa\u7f13\u5b58\u7684\u8be6\u7ec6\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605 <a href=\"http:\/\/mng.bz\/Bmlv\">http:\/\/mng.bz\/Bmlv<\/a> \u4e2d\u7684\u6587\u6863\u3002<\/p>\n<\/blockquote>\n<p>The real power of action filters comes when you build filters tailored to your own apps by extracting common code from your action methods. To demonstrate, I\u2019m going to create two custom filters for RecipeApiController:<br \/>\n\u5f53\u60a8\u901a\u8fc7\u4ece\u4f5c\u65b9\u6cd5\u4e2d\u63d0\u53d6\u901a\u7528\u4ee3\u7801\u6765\u6784\u5efa\u9488\u5bf9\u81ea\u5df1\u7684\u5e94\u7528\u7a0b\u5e8f\u91cf\u8eab\u5b9a\u5236\u7684\u8fc7\u6ee4\u5668\u65f6\uff0c\u4f5c\u8fc7\u6ee4\u5668\u7684\u771f\u6b63\u5f3a\u5927\u4e4b\u5904\u5728\u4e8e\u3002\u4e3a\u4e86\u6f14\u793a\uff0c\u6211\u5c06\u4e3a RecipeApiController \u521b\u5efa\u4e24\u4e2a\u81ea\u5b9a\u4e49\u8fc7\u6ee4\u5668\uff1a<\/p>\n<p>\u2022  ValidateModelAttribute\u2014This will return BadRequestResult if the model state indicates that the binding model is invalid and will short-circuit the action execution. This attribute used to be a staple of my web API applications, but the [ApiController] attribute now handles this (and more) for you. Nevertheless, I think it\u2019s useful to understand what\u2019s going on behind the scenes.<br \/>\nValidateModelAttribute - \u5982\u679c\u6a21\u578b\u72b6\u6001\u6307\u793a\u7ed1\u5b9a\u6a21\u578b\u65e0\u6548\uff0c\u5e76\u4e14\u5c06\u4f7f\u4f5c\u6267\u884c\u77ed\u8def\uff0c\u5219\u5c06\u8fd4\u56de BadRequestResult\u3002\u6b64\u5c5e\u6027\u66fe\u7ecf\u662f\u6211\u7684 Web API \u5e94\u7528\u7a0b\u5e8f\u7684\u4e3b\u8981\u5185\u5bb9\uff0c\u4f46 [ApiController] \u5c5e\u6027\u73b0\u5728\u4e3a\u60a8\u5904\u7406\u6b64 \uff08\u4ee5\u53ca\u66f4\u591a\uff09 \u3002\u5c3d\u7ba1\u5982\u6b64\uff0c\u6211\u8ba4\u4e3a\u4e86\u89e3\u5e55\u540e\u53d1\u751f\u7684\u4e8b\u60c5\u662f\u6709\u7528\u7684\u3002<\/p>\n<p>\u2022  EnsureRecipeExistsAttribute\u2014This uses each action method\u2019s id argument to validate that the requested Recipe entity exists before the action method runs. If the Recipe doesn\u2019t exist, the filter returns NotFoundResult and short-circuits the pipeline.<br \/>\nEnsureRecipeExistsAttribute - \u5728\u4f5c\u65b9\u6cd5\u8fd0\u884c\u4e4b\u524d\uff0c\u4f7f\u7528\u6bcf\u4e2a\u4f5c\u65b9\u6cd5\u7684 id \u53c2\u6570\u6765\u9a8c\u8bc1\u8bf7\u6c42\u7684\u914d\u65b9\u5b9e\u4f53\u662f\u5426\u5b58\u5728\u3002\u5982\u679c Recipe \u4e0d\u5b58\u5728\uff0c\u5219\u7b5b\u9009\u6761\u4ef6\u5c06\u8fd4\u56de NotFoundResult \u5e76\u4f7f\u7ba1\u9053\u77ed\u8def\u3002<\/p>\n<p>As you saw in chapter 16, the MVC framework automatically validates your binding models before executing your actions and Razor Page handlers, but it\u2019s up to you to decide what to do about it. For web API controllers, it\u2019s common to return a 400 Bad Request response containing a list of the errors, as shown in figure 22.1.<br \/>\n\u6b63\u5982\u60a8\u5728\u7b2c 16 \u7ae0\u4e2d\u6240\u770b\u5230\u7684\uff0cMVC \u6846\u67b6\u4f1a\u5728\u6267\u884c\u60a8\u7684\u4f5c\u548c Razor Page \u5904\u7406\u7a0b\u5e8f\u4e4b\u524d\u81ea\u52a8\u9a8c\u8bc1\u60a8\u7684\u7ed1\u5b9a\u6a21\u578b\uff0c\u4f46\u7531\u60a8\u51b3\u5b9a\u5982\u4f55\u5904\u7406\u5b83\u3002\u5bf9\u4e8e Web API \u63a7\u5236\u5668\uff0c\u901a\u5e38\u4f1a\u8fd4\u56de\u5305\u542b\u9519\u8bef\u5217\u8868\u7684 400 Bad Request \u54cd\u5e94\uff0c\u5982\u56fe 22.1 \u6240\u793a\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/2201.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 22.1 Posting data to a web API using Postman. The data is bound to the action method\u2019s binding model and validated. If validation fails, it\u2019s common to return a 400 Bad Request response with a list of the validation errors.<br \/>\n\u56fe 22.1 \u4f7f\u7528 Postman \u5c06\u6570\u636e\u53d1\u5e03\u5230 Web API\u3002\u6570\u636e\u5c06\u7ed1\u5b9a\u5230\u4f5c\u65b9\u6cd5\u7684\u7ed1\u5b9a\u6a21\u578b\u5e76\u8fdb\u884c\u9a8c\u8bc1\u3002\u5982\u679c\u9a8c\u8bc1\u5931\u8d25\uff0c\u901a\u5e38\u4f1a\u8fd4\u56de 400 Bad Request \u54cd\u5e94\uff0c\u5176\u4e2d\u5305\u542b\u9a8c\u8bc1\u9519\u8bef\u5217\u8868\u3002<\/p>\n<p>You should ordinarily use the [ApiController] attribute on your web API controllers, which gives you this behavior (and uses Problem Details responses) automatically. But if you can\u2019t or don\u2019t want to use that attribute, you can create a custom action filter instead. The following listing shows a basic implementation that is similar to the behavior you get with the [ApiController] attribute.<br \/>\n\u901a\u5e38\u5e94\u5728 Web API \u63a7\u5236\u5668\u4e0a\u4f7f\u7528 [ApiController] \u5c5e\u6027\uff0c\u8be5\u5c5e\u6027\u4f1a\u81ea\u52a8\u63d0\u4f9b\u6b64\u884c\u4e3a \uff08\u5e76\u4f7f\u7528\u95ee\u9898\u8be6\u7ec6\u4fe1\u606f\u54cd\u5e94\uff09 \u3002\u4f46\u662f\uff0c\u5982\u679c\u60a8\u4e0d\u80fd\u6216\u4e0d\u60f3\u4f7f\u7528\u8be5\u5c5e\u6027\uff0c\u5219\u53ef\u4ee5\u6539\u4e3a\u521b\u5efa\u81ea\u5b9a\u4e49\u4f5c\u7b5b\u9009\u6761\u4ef6\u3002\u4e0b\u9762\u7684\u5217\u8868\u663e\u793a\u4e86\u4e00\u4e2a\u57fa\u672c\u5b9e\u73b0\uff0c\u5b83\u7c7b\u4f3c\u4e8e\u60a8\u4f7f\u7528 [ApiController] \u5c5e\u6027\u83b7\u5f97\u7684\u884c\u4e3a\u3002<\/p>\n<p>Listing 22.5 The action filter for validating ModelState<br \/>\n\u5217\u8868 22.5 \u7528\u4e8e\u9a8c\u8bc1 ModelState \u7684\u52a8\u4f5c\u8fc7\u6ee4\u5668<\/p>\n<pre><code>public class ValidateModelAttribute : ActionFilterAttribute      #A\n{\n    public override void OnActionExecuting(    #B\n        ActionExecutingContext context)        #B\n    {\n        if (!context.ModelState.IsValid)    #C\n        {\n            context.Result =                                      #D\n                new BadRequestObjectResult(context.ModelState);   #D\n        }\n    }\n}<\/code><\/pre>\n<p>\u2776 For convenience, you derive from the ActionFilterAttribute base class.<br \/>\n\u4e3a\u65b9\u4fbf\u8d77\u89c1\uff0c\u60a8\u53ef\u4ee5\u4ece ActionFilterAttribute \u57fa\u7c7b\u6d3e\u751f\u3002<br \/>\n\u2777 Overrides the Executing method to run the filter before the Action executes<br \/>\n\u91cd\u5199 Executing \u65b9\u6cd5\u4ee5\u5728 Action \u6267\u884c\u4e4b\u524d\u8fd0\u884c\u8fc7\u6ee4\u5668<br \/>\n\u2778 Model binding and validation have already run at this point, so you can check the state.<br \/>\n\u6b64\u65f6\u6a21\u578b\u7ed1\u5b9a\u548c\u9a8c\u8bc1\u5df2\u7ecf\u8fd0\u884c\uff0c\u56e0\u6b64\u60a8\u53ef\u4ee5\u68c0\u67e5\u72b6\u6001\u3002<br \/>\n\u2779 If the model isn\u2019t valid, sets the Result property, which short-circuits the action execution<br \/>\n\u5982\u679c\u6a21\u578b\u65e0\u6548\uff0c\u5219\u8bbe\u7f6e Result \u5c5e\u6027\uff0c\u8fd9\u5c06\u4f7f\u4f5c\u6267\u884c\u77ed\u8def<\/p>\n<p>This attribute is self-explanatory and follows a similar pattern to the resource filter in section 22.1.2, but with a few interesting points:<br \/>\n\u6b64\u5c5e\u6027\u662f\u4e0d\u8a00\u81ea\u660e\u7684\uff0c\u5e76\u4e14\u9075\u5faa\u4e0e\u7b2c 22.1.2 \u8282\u4e2d\u7684\u8d44\u6e90\u8fc7\u6ee4\u5668\u7c7b\u4f3c\u7684\u6a21\u5f0f\uff0c\u4f46\u6709\u4e00\u4e9b\u6709\u8da3\u7684\u70b9\uff1a<\/p>\n<p>\u2022  I have derived from the abstract ActionFilterAttribute. This class implements IActionFilter and IResultFilter, as well as their async counterparts, so you can override the methods you need as appropriate. This prevents needing to add an unused OnActionExecuted() method, but using the base class is entirely optional and a matter of preference.<br \/>\n\u6211\u4ece\u62bd\u8c61\u7684 ActionFilterAttribute \u6d3e\u751f\u800c\u6765\u3002\u6b64\u7c7b\u5b9e\u73b0 IActionFilter \u548c IResultFilter \u4ee5\u53ca\u5b83\u4eec\u7684\u5f02\u6b65\u5bf9\u5e94\u9879\uff0c\u56e0\u6b64\u60a8\u53ef\u4ee5\u6839\u636e\u9700\u8981\u91cd\u5199\u6240\u9700\u7684\u65b9\u6cd5\u3002\u8fd9\u6837\u5c31\u4e0d\u9700\u8981\u6dfb\u52a0\u672a\u4f7f\u7528\u7684 OnActionExecuted\uff08\uff09 \u65b9\u6cd5\uff0c\u4f46\u4f7f\u7528\u57fa\u7c7b\u5b8c\u5168\u662f\u53ef\u9009\u7684\uff0c\u5e76\u4e14\u662f\u9996\u9009\u9879\u95ee\u9898\u3002<\/p>\n<p>\u2022  Action filters run after model binding has taken place, so context.ModelState contains the validation errors if validation failed.<br \/>\nAction\u7b5b\u9009\u5668\u5728\u6a21\u578b\u7ed1\u5b9a\u53d1\u751f\u540e\u8fd0\u884c\uff0c\u56e0\u6b64 context.ModelState \u5305\u542b\u9a8c\u8bc1\u9519\u8bef\uff08\u5982\u679c\u9a8c\u8bc1\u5931\u8d25\uff09\u3002<\/p>\n<p>\u2022  Setting the Result property on context short-circuits the pipeline. But due to the position of the action filter stage, only the action method execution and later action filters are bypassed; all the other stages of the pipeline run as though the action executed as normal.<br \/>\n\u5728\u4e0a\u4e0b\u6587\u4e0a\u8bbe\u7f6e Result \u5c5e\u6027\u4f1a\u4f7f\u7ba1\u9053\u77ed\u8def\u3002\u4f46\u662f\uff0c\u7531\u4e8e\u4f5c\u7b5b\u9009\u5668\u9636\u6bb5\u7684\u4f4d\u7f6e\uff0c\u4ec5\u7ed5\u8fc7\u4f5c\u65b9\u6cd5\u6267\u884c\u548c\u4ee5\u540e\u7684\u4f5c\u7b5b\u9009\u5668;\u7ba1\u9053\u7684\u6240\u6709\u5176\u4ed6\u9636\u6bb5\u90fd\u50cf\u6b63\u5e38\u6267\u884c\u4f5c\u4e00\u6837\u8fd0\u884c\u3002<\/p>\n<p>If you apply this action filter to your RecipeApiController, you can remove this code from the start of both the action methods, as it will run automatically in the filter pipeline:<br \/>\n\u5982\u679c\u4f60\u5c06\u6b64\u4f5c\u7b5b\u9009\u5668\u5e94\u7528\u4e8e RecipeApiController\uff0c\u5219\u53ef\u4ee5\u4ece\u4e24\u4e2a\u4f5c\u65b9\u6cd5\u7684\u5f00\u5934\u5220\u9664\u6b64\u4ee3\u7801\uff0c\u56e0\u4e3a\u5b83\u5c06\u5728\u7b5b\u9009\u5668\u7ba1\u9053\u4e2d\u81ea\u52a8\u8fd0\u884c\uff1a<\/p>\n<pre><code>if (!ModelState.IsValid)\n{\n    return BadRequest(ModelState);\n}<\/code><\/pre>\n<p>You\u2019ll use a similar approach to remove the duplicate code that checks whether the id provided as an argument to the action methods corresponds to an existing Recipe entity.<br \/>\n\u60a8\u5c06\u4f7f\u7528\u7c7b\u4f3c\u7684\u65b9\u6cd5\u6765\u5220\u9664\u91cd\u590d\u4ee3\u7801\uff0c\u8be5\u4ee3\u7801\u68c0\u67e5\u4f5c\u4e3a\u4f5c\u65b9\u6cd5\u7684\u53c2\u6570\u63d0\u4f9b\u7684 id \u662f\u5426\u5bf9\u5e94\u4e8e\u73b0\u6709 Recipe \u5b9e\u4f53\u3002<\/p>\n<p>The following listing shows the EnsureRecipeExistsAttribute action filter. This uses an instance of RecipeService to check whether the Recipe exists and returns a 404 Not Found if it doesn\u2019t.<br \/>\n\u4ee5\u4e0b\u6e05\u5355\u663e\u793a\u4e86 EnsureRecipeExistsAttribute\u4f5c\u7b5b\u9009\u5668\u3002\u8fd9\u5c06\u4f7f\u7528 RecipeService \u7684\u5b9e\u4f8b\u6765\u68c0\u67e5 Recipe \u662f\u5426\u5b58\u5728\uff0c\u5982\u679c\u4e0d\u5b58\u5728\uff0c\u5219\u8fd4\u56de 404 Not Found\u3002<\/p>\n<p>Listing 22.6 An action filter to check whether a Recipe exists<br \/>\n\u6e05\u5355 22.6 \u7528\u4e8e\u68c0\u67e5 Recipe \u662f\u5426\u5b58\u5728\u7684 action filter<\/p>\n<pre><code>public class EnsureRecipeExistsAtribute : ActionFilterAttribute\n{\n    public override void OnActionExecuting(\n        ActionExecutingContext context)\n    {\n        var service = context.HttpContext.RequestServices    #A\n            .GetService&lt;RecipeService&gt;();    #A\n        var recipeId = (int) context.ActionArguments[&quot;id&quot;];  #B\n        if (!service.DoesRecipeExist(recipeId))    #C\n        {\n            context.Result = new NotFoundResult();    #D\n        }\n    }\n}<\/code><\/pre>\n<p>\u2776 Fetches an instance of RecipeService from the DI container<br \/>\n\u4ece DI \u5bb9\u5668\u4e2d\u83b7\u53d6 RecipeService \u7684\u5b9e\u4f8b<br \/>\n\u2777 Retrieves the id parameter that will be passed to the action method when it executes<br \/>\n\u68c0\u7d22\u6267\u884c\u65f6\u5c06\u4f20\u9012\u7ed9\u4f5c\u65b9\u6cd5\u7684 id \u53c2\u6570<br \/>\n\u2778 Checks whether a Recipe entity with the given RecipeId exists<br \/>\n\u68c0\u67e5\u5177\u6709\u7ed9\u5b9a RecipeId \u7684 Recipe \u5b9e\u4f53\u662f\u5426\u5b58\u5728<br \/>\n\u2779 If it doesn\u2019t exist, returns a 404 Not Found result and short-circuits the pipeline<br \/>\n\u5982\u679c\u4e0d\u5b58\u5728\uff0c\u5219\u8fd4\u56de 404 Not Found \u7ed3\u679c\u5e76\u77ed\u8def\u7ba1\u9053<\/p>\n<p>As before, you\u2019ve derived from ActionFilterAttribute for simplicity and overridden the OnActionExecuting method. The main functionality of the filter relies on the DoesRecipeExist() method of RecipeService, so the first step is to obtain an instance of RecipeService. The context parameter provides access to the HttpContext for the request, which in turn lets you access the DI container and use RequestServices.GetService() to return an instance of RecipeService.<br \/>\n\u4e0e\u4ee5\u524d\u4e00\u6837\uff0c\u4e3a\u4e86\u7b80\u5355\u8d77\u89c1\uff0c\u60a8\u4ece ActionFilterAttribute \u6d3e\u751f\u5e76\u91cd\u5199\u4e86 OnActionExecuting \u65b9\u6cd5\u3002\u8fc7\u6ee4\u5668\u7684\u4e3b\u8981\u529f\u80fd\u4f9d\u8d56\u4e8e RecipeService \u7684 DoesRecipeExist\uff08\uff09 \u65b9\u6cd5\uff0c\u56e0\u6b64\u7b2c\u4e00\u6b65\u662f\u83b7\u53d6 RecipeService \u7684\u5b9e\u4f8b\u3002context \u53c2\u6570\u63d0\u4f9b\u5bf9\u8bf7\u6c42\u7684 HttpContext \u7684\u8bbf\u95ee\uff0c\u8fd9\u53cd\u8fc7\u6765\u53c8\u5141\u8bb8\u60a8\u8bbf\u95ee DI \u5bb9\u5668\u5e76\u4f7f\u7528 RequestServices.GetService\uff08\uff09 \u8fd4\u56de RecipeService \u7684\u5b9e\u4f8b\u3002<\/p>\n<p><b>Warning<\/b> This technique for obtaining dependencies is known as service location and is generally considered to be an antipattern. In section 22.3 I\u2019ll show you a better way to use the DI container to inject dependencies into your filters.<br \/>\n\u8b66\u544a:\u8fd9\u79cd\u7528\u4e8e\u83b7\u53d6\u4f9d\u8d56\u9879\u7684\u6280\u672f\u79f0\u4e3a \u670d\u52a1\u5b9a\u4f4d\uff0c\u901a\u5e38\u88ab\u89c6\u4e3a\u53cd\u6a21\u5f0f\u3002\u5728 Section 22.3 \u4e2d\uff0c\u6211\u5c06\u5411\u60a8\u5c55\u793a\u4e00\u79cd\u66f4\u597d\u7684\u65b9\u6cd5 \u4f7f\u7528 DI \u5bb9\u5668\u5c06\u4f9d\u8d56\u9879\u6ce8\u5165\u8fc7\u6ee4\u5668\u3002<\/p>\n<p>As well as RecipeService, the other piece of information you need is the id argument of the Get and Edit action methods. In action filters, model binding has already occurred, so the arguments that the framework will use to execute the action method are already known and are exposed on context.ActionArguments.<br \/>\n\u9664\u4e86 RecipeService \u4e4b\u5916\uff0c\u60a8\u8fd8\u9700\u8981\u7684\u53e6\u4e00\u6761\u4fe1\u606f\u662f Get \u548c Edit\u4f5c\u65b9\u6cd5\u7684 id \u53c2\u6570\u3002\u5728\u4f5c\u7b5b\u9009\u5668\u4e2d\uff0c\u6a21\u578b\u7ed1\u5b9a\u5df2\u7ecf\u53d1\u751f\uff0c\u56e0\u6b64\u6846\u67b6\u5c06\u7528\u4e8e\u6267\u884c\u4f5c\u65b9\u6cd5\u7684\u53c2\u6570\u662f\u5df2\u77e5\u7684\uff0c\u5e76\u5728\u4e0a\u4e0b\u6587\u4e2d\u516c\u5f00\u3002ActionArguments \u7684 API \u53c2\u6570\u3002<\/p>\n<p>The action arguments are exposed as <code>Dictionary&lt;string, object&gt;<\/code>, so you can obtain the id parameter using the &quot;id&quot; string key. Remember to cast the object to the correct type.<br \/>\naction\u53c2\u6570\u516c\u5f00\u4e3a <code>Dictionary&lt;string, object&gt;<\/code>\uff0c\u56e0\u6b64\u60a8\u53ef\u4ee5\u4f7f\u7528 \u201cid\u201d \u5b57\u7b26\u4e32\u952e\u83b7\u53d6 id \u53c2\u6570\u3002\u8bf7\u8bb0\u4f4f\u5c06\u5bf9\u8c61\u5f3a\u5236\u8f6c\u6362\u4e3a\u6b63\u786e\u7684\u7c7b\u578b\u3002<\/p>\n<p><b>Tip<\/b> Whenever I see magic strings like this, I always try to replace them by using the nameof operator. Unfortunately, nameof won\u2019t work for method arguments like this, so be careful when refactoring your code. I suggest explicitly applying the action filter to the action method (instead of globally, or to a controller) to remind you about that implicit coupling.<br \/>\n\u63d0\u793a:\u6bcf\u5f53\u6211\u770b\u5230\u8fd9\u6837\u7684\u9b54\u672f\u5b57\u7b26\u4e32\u65f6\uff0c\u6211\u603b\u662f\u5c1d\u8bd5\u4f7f\u7528 nameof \u8fd0\u7b97\u7b26\u66ff\u6362\u5b83\u4eec\u3002\u4e0d\u5e78\u7684\u662f\uff0cnameof \u4e0d\u9002\u7528\u4e8e\u8fd9\u6837\u7684\u65b9\u6cd5\u53c2\u6570\uff0c\u56e0\u6b64\u5728\u91cd\u6784\u4ee3\u7801\u65f6\u8981\u5c0f\u5fc3\u3002\u6211\u5efa\u8bae\u5c06 action \u8fc7\u6ee4\u5668\u663e\u5f0f\u5730\u5e94\u7528\u4e8e action \u65b9\u6cd5\uff08\u800c\u4e0d\u662f\u5168\u5c40\u5e94\u7528\uff0c\u6216\u5e94\u7528\u4e8e\u63a7\u5236\u5668\uff09\uff0c\u4ee5\u63d0\u9192\u60a8\u8fd9\u79cd\u9690\u5f0f\u8026\u5408\u3002<\/p>\n<p>With RecipeService and id in place, it\u2019s a case of checking whether the identifier corresponds to an existing Recipe entity and if not, setting context.Result to NotFoundResult. This short-circuits the pipeline and bypasses the action method altogether.<br \/>\n\u6709\u4e86 RecipeService \u548c id\uff0c\u5c31\u53ef\u4ee5\u68c0\u67e5\u6807\u8bc6\u7b26\u662f\u5426\u5bf9\u5e94\u4e8e\u73b0\u6709\u7684 Recipe \u5b9e\u4f53\uff0c\u5982\u679c\u4e0d\u662f\uff0c\u5219\u8bbe\u7f6e context\u3002Result \u8bbe\u7f6e\u4e3a NotFoundResult\u3002\u8fd9\u4f1a\u4f7f\u7ba1\u9053\u77ed\u8def\u5e76\u5b8c\u5168\u7ed5\u8fc7 action \u65b9\u6cd5\u3002<\/p>\n<p><b>NOTE<\/b> Remember that you can have multiple action filters running in a single stage. Short-circuiting the pipeline by setting context.Result prevents later filters in the stage from running and bypasses the action method execution.<br \/>\n\u6ce8\u610f:\u8bf7\u8bb0\u4f4f\uff0c\u60a8\u53ef\u4ee5\u5728\u5355\u4e2a\u9636\u6bb5\u4e2d\u8fd0\u884c\u591a\u4e2a\u4f5c\u7b5b\u9009\u5668\u3002\u901a\u8fc7\u8bbe\u7f6e context \u4f7f\u7ba1\u9053\u77ed\u8def\u3002Result \u4f1a\u963b\u6b62\u9636\u6bb5\u4e2d\u4ee5\u540e\u7684\u8fc7\u6ee4\u5668\u8fd0\u884c\uff0c\u5e76\u7ed5\u8fc7\u4f5c\u65b9\u6cd5\u7684\u6267\u884c\u3002<\/p>\n<p>Before we move on, it\u2019s worth mentioning a special case for action filters. The ControllerBase base class implements IActionFilter and IAsyncActionFilter itself. If you find yourself creating an action filter for a single controller and want to apply it to every action in that controller, you can override the appropriate methods on your controller instead, as in the following listing.<br \/>\n\u5728\u6211\u4eec\u7ee7\u7eed\u4e4b\u524d\uff0c\u503c\u5f97\u4e00\u63d0\u7684\u662f\u4f5c\u8fc7\u6ee4\u5668\u7684\u4e00\u4e2a\u7279\u6b8a\u60c5\u51b5\u3002ControllerBase \u57fa\u7c7b\u5b9e\u73b0 IActionFilter \u548c IAsyncActionFilter \u672c\u8eab\u3002\u5982\u679c\u60a8\u53d1\u73b0\u81ea\u5df1\u4e3a\u5355\u4e2a\u63a7\u5236\u5668\u521b\u5efa\u4e86\u4e00\u4e2a\u4f5c\u8fc7\u6ee4\u5668\uff0c\u5e76\u5e0c\u671b\u5c06\u5176\u5e94\u7528\u4e8e\u8be5\u63a7\u5236\u5668\u4e2d\u7684\u6bcf\u4e2a\u4f5c\uff0c\u5219\u53ef\u4ee5\u6539\u4e3a\u8986\u76d6\u63a7\u5236\u5668\u4e0a\u7684\u76f8\u5e94\u65b9\u6cd5\uff0c\u5982\u4e0b\u9762\u7684\u6e05\u5355\u6240\u793a\u3002<\/p>\n<p>Listing 22.7 Overriding action filter methods directly on ControllerBase<br \/>\n\u6e05\u5355 22.7 \u76f4\u63a5\u5728 ControllerBase \u4e0a\u8986\u76d6\u52a8\u4f5c\u8fc7\u6ee4\u5668\u65b9\u6cd5<\/p>\n<pre><code>public class HomeController : ControllerBase      #A\n{\n    public override void OnActionExecuting(     #B\n        ActionExecutingContext context)         #B\n    { }                                         #B\n    public override void OnActionExecuted(    #C\n        ActionExecutedContext context)        #C\n    { }                                       #C\n}<\/code><\/pre>\n<p>\u2776 Derives from the ControllerBase class<br \/>\n\u6d3e\u751f\u81ea ControllerBase \u7c7b<br \/>\n\u2777 Runs before any other action filters for every action in the controller<br \/>\n\u5728\u63a7\u5236\u5668\u4e2d\u6bcf\u4e2a\u52a8\u4f5c\u7684\u4efb\u4f55\u5176\u4ed6\u52a8\u4f5c\u8fc7\u6ee4\u5668\u4e4b\u524d\u8fd0\u884c<br \/>\n\u2778 Runs after all other action filters for every action in the controller<br \/>\n\u5728\u63a7\u5236\u5668\u4e2d\u6bcf\u4e2a\u52a8\u4f5c\u7684\u6240\u6709\u5176\u4ed6\u52a8\u4f5c\u8fc7\u6ee4\u5668\u4e4b\u540e\u8fd0\u884c<\/p>\n<p>If you override these methods on your controller, they\u2019ll run in the action filter stage of the filter pipeline for every action on the controller. The OnActionExecuting method runs before any other action filters, regardless of ordering or scope, and the OnActionExecuted method runs after all other action filters.<br \/>\n\u5982\u679c\u60a8\u5728\u63a7\u5236\u5668\u4e0a\u8986\u76d6\u8fd9\u4e9b\u65b9\u6cd5\uff0c\u5219\u5b83\u4eec\u5c06\u5728\u63a7\u5236\u5668\u4e0a\u6bcf\u4e2a\u4f5c\u7684\u8fc7\u6ee4\u5668\u7ba1\u9053\u7684\u4f5c\u8fc7\u6ee4\u5668\u9636\u6bb5\u4e2d\u8fd0\u884c\u3002OnActionExecuting \u65b9\u6cd5\u5728\u4efb\u4f55\u5176\u4ed6\u4f5c\u7b5b\u9009\u5668\u4e4b\u524d\u8fd0\u884c\uff0c\u800c\u4e0d\u7ba1\u987a\u5e8f\u6216\u8303\u56f4\u5982\u4f55\uff0c\u800c OnActionExecuted \u65b9\u6cd5\u5728\u6240\u6709\u5176\u4ed6\u4f5c\u7b5b\u9009\u5668\u4e4b\u540e\u8fd0\u884c\u3002<\/p>\n<p><b>Tip<\/b> The controller implementation can be useful in some cases, but you can\u2019t control the ordering related to other filters. Personally, I generally prefer to break logic into explicit, declarative filter attributes, but it depends on the situation, and as always, the choice is yours.<br \/>\n\u63d0\u793a:\u63a7\u5236\u5668\u5b9e\u73b0\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\u53ef\u80fd\u5f88\u6709\u7528\uff0c\u4f46\u60a8\u65e0\u6cd5\u63a7\u5236\u4e0e\u5176\u4ed6\u8fc7\u6ee4\u5668\u76f8\u5173\u7684 Sequences\u3002\u5c31\u4e2a\u4eba\u800c\u8a00\uff0c\u6211\u901a\u5e38\u66f4\u559c\u6b22\u5c06 logic \u5206\u89e3\u4e3a\u663e\u5f0f\u7684\u58f0\u660e\u6027 filter \u5c5e\u6027\uff0c\u4f46\u8fd9\u53d6\u51b3\u4e8e\u5177\u4f53\u60c5\u51b5\uff0c\u5e76\u4e14\u4e00\u5982\u65e2\u5f80\uff0c\u9009\u62e9\u6743\u5728\u60a8\u624b\u4e2d\u3002<\/p>\n<p>With the resource and action filters complete, your controller is looking much tidier, but there\u2019s one aspect in particular that would be nice to remove: the exception handling. In the next section we\u2019ll look at how to create a custom exception filter for your controller and why you might want to do this instead of using exception handling middleware.<br \/>\n\u5b8c\u6210\u8d44\u6e90\u548c\u4f5c\u7b5b\u9009\u5668\u540e\uff0c\u60a8\u7684\u63a7\u5236\u5668\u770b\u8d77\u6765\u66f4\u52a0\u6574\u6d01\uff0c\u4f46\u6709\u4e00\u4e2a\u65b9\u9762\u7279\u522b\u503c\u5f97\u5220\u9664\uff1a\u5f02\u5e38\u5904\u7406\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u4e3a\u63a7\u5236\u5668\u521b\u5efa\u81ea\u5b9a\u4e49\u5f02\u5e38\u7b5b\u9009\u5668\uff0c\u4ee5\u53ca\u4e3a\u4ec0\u4e48\u60a8\u53ef\u80fd\u5e0c\u671b\u6267\u884c\u6b64\u4f5c\u800c\u4e0d\u662f\u4f7f\u7528\u5f02\u5e38\u5904\u7406\u4e2d\u95f4\u4ef6\u3002<\/p>\n<h3>22.1.4 Exception filters: Custom exception handling for your action methods<\/h3>\n<p>22.1.4 \u5f02\u5e38\u8fc7\u6ee4\u5668\uff1a\u4f5c\u65b9\u6cd5\u7684\u81ea\u5b9a\u4e49\u5f02\u5e38\u5904\u7406<\/p>\n<p>In chapter 4 I went into some depth about types of error-handling middleware you can add to your apps. These let you catch exceptions thrown from any later middleware and handle them appropriately. If you\u2019re using exception handling middleware, you may be wondering why we need exception filters at all.<br \/>\n\u5728\u7b2c 4 \u7ae0\u4e2d\uff0c\u6211\u6df1\u5165\u63a2\u8ba8\u4e86\u60a8\u53ef\u4ee5\u6dfb\u52a0\u5230\u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u9519\u8bef\u5904\u7406\u4e2d\u95f4\u4ef6\u7684\u7c7b\u578b\u3002\u8fd9\u4e9b\u5141\u8bb8\u60a8\u6355\u83b7\u4efb\u4f55\u540e\u7eed\u4e2d\u95f4\u4ef6\u5f15\u53d1\u7684\u5f02\u5e38\u5e76\u9002\u5f53\u5730\u5904\u7406\u5b83\u4eec\u3002\u5982\u679c\u4f60\u6b63\u5728\u4f7f\u7528\u5f02\u5e38\u5904\u7406\u4e2d\u95f4\u4ef6\uff0c\u4f60\u53ef\u80fd\u60f3\u77e5\u9053\u4e3a\u4ec0\u4e48\u6211\u4eec\u9700\u8981\u5f02\u5e38\u8fc7\u6ee4\u5668\u3002<\/p>\n<p>The answer to this is pretty much the same as I outlined in chapter 21: filters are great for cross-cutting concerns, when you need behavior that\u2019s specific to MVC or that should only apply to certain routes.<br \/>\n\u8fd9\u4e2a\u95ee\u9898\u7684\u7b54\u6848\u4e0e\u6211\u5728\u7b2c 21 \u7ae0\u4e2d\u6982\u8ff0\u7684\u51e0\u4e4e\u76f8\u540c\uff1a\u5f53\u60a8\u9700\u8981\u7279\u5b9a\u4e8e MVC \u7684\u884c\u4e3a\u6216\u5e94\u4ec5\u9002\u7528\u4e8e\u67d0\u4e9b\u8def\u7531\u7684\u884c\u4e3a\u65f6\uff0c\u8fc7\u6ee4\u5668\u975e\u5e38\u9002\u5408\u6a2a\u5207\u5173\u6ce8\u70b9\u3002<\/p>\n<p>Both of these can apply in exception handling. Exception filters are part of the MVC framework, so they have access to the context in which the error occurred, such as the action or Razor Page that was executing. This can be useful for logging additional details when errors occur, such as the action parameters that caused the error.<br \/>\n\u8fd9\u4e24\u8005\u90fd\u53ef\u4ee5\u5e94\u7528\u4e8e\u5f02\u5e38\u5904\u7406\u3002\u5f02\u5e38\u7b5b\u9009\u5668\u662f MVC \u6846\u67b6\u7684\u4e00\u90e8\u5206\uff0c\u56e0\u6b64\u5b83\u4eec\u6709\u6743\u8bbf\u95ee\u53d1\u751f\u9519\u8bef\u7684\u4e0a\u4e0b\u6587\uff0c\u4f8b\u5982\u6b63\u5728\u6267\u884c\u7684\u4f5c\u6216 Razor Page\u3002\u8fd9\u5bf9\u4e8e\u5728\u53d1\u751f\u9519\u8bef\u65f6\u8bb0\u5f55\u5176\u4ed6\u8be6\u7ec6\u4fe1\u606f\uff08\u5982\u5bfc\u81f4\u9519\u8bef\u7684\u4f5c\u53c2\u6570\uff09\u975e\u5e38\u6709\u7528\u3002<\/p>\n<p><b>Warning<\/b> If you use exception filters to record action method arguments, make sure you\u2019re not storing sensitive data in your logs, such as passwords or credit card details.<br \/>\n\u8b66\u544a:\u5982\u679c\u60a8\u4f7f\u7528\u5f02\u5e38\u7b5b\u9009\u6761\u4ef6\u6765\u8bb0\u5f55\u4f5c\u65b9\u6cd5\u53c2\u6570\uff0c\u8bf7\u786e\u4fdd\u60a8\u6ca1\u6709\u5728\u65e5\u5fd7\u4e2d\u5b58\u50a8\u654f\u611f\u6570\u636e\uff0c\u4f8b\u5982\u5bc6\u7801\u6216\u4fe1\u7528\u5361\u8be6\u7ec6\u4fe1\u606f\u3002<\/p>\n<p>You can also use exception filters to handle errors from different routes in different ways. Imagine you have both Razor Pages and web API controllers in your app, as we do in the recipe app. What happens when an exception is thrown by a Razor Page?<br \/>\n\u60a8\u8fd8\u53ef\u4ee5\u4f7f\u7528\u5f02\u5e38\u7b5b\u9009\u6761\u4ef6\u4ee5\u4e0d\u540c\u65b9\u5f0f\u5904\u7406\u6765\u81ea\u4e0d\u540c\u8def\u7531\u7684\u9519\u8bef\u3002\u5047\u8bbe\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\u540c\u65f6\u6709 Razor Pages \u548c Web API \u63a7\u5236\u5668\uff0c\u5c31\u50cf\u6211\u4eec\u5728\u914d\u65b9\u5e94\u7528\u7a0b\u5e8f\u4e2d\u6240\u505a\u7684\u90a3\u6837\u3002\u5f53 Razor Page \u5f15\u53d1\u5f02\u5e38\u65f6\u4f1a\u53d1\u751f\u4ec0\u4e48\u60c5\u51b5\uff1f<\/p>\n<p>As you saw in chapter 4, the exception travels back up the middleware pipeline and is caught by exception handler middleware. The exception handler middleware reexecutes the pipeline and generates an HTML error page.<br \/>\n\u6b63\u5982\u60a8\u5728\u7b2c 4 \u7ae0\u4e2d\u770b\u5230\u7684\uff0c\u5f02\u5e38\u6cbf\u4e2d\u95f4\u4ef6\u7ba1\u9053\u5411\u4e0a\u4f20\u8f93\uff0c\u5e76\u88ab\u5f02\u5e38\u5904\u7406\u7a0b\u5e8f\u4e2d\u95f4\u4ef6\u6355\u83b7\u3002\u5f02\u5e38\u5904\u7406\u7a0b\u5e8f\u4e2d\u95f4\u4ef6\u5c06\u91cd\u65b0\u6267\u884c\u7ba1\u9053\u5e76\u751f\u6210 HTML \u9519\u8bef\u9875\u3002<\/p>\n<p>That\u2019s great for your Razor Pages, but what about exceptions in your web API controllers? If your API throws an exception and consequently returns HTML generated by the exception handler middleware, that\u2019s going to break a client that called the API expecting a JavaScript Object Notation (JSON) response!<br \/>\n\u8fd9\u5bf9 Razor Pages \u6765\u8bf4\u975e\u5e38\u6709\u7528\uff0c\u4f46 Web API \u63a7\u5236\u5668\u4e2d\u7684\u5f02\u5e38\u5462\uff1f\u5982\u679c\u60a8\u7684 API \u5f15\u53d1\u5f02\u5e38\uff0c\u5e76\u56e0\u6b64\u8fd4\u56de\u5f02\u5e38\u5904\u7406\u7a0b\u5e8f\u4e2d\u95f4\u4ef6\u751f\u6210\u7684 HTML\uff0c\u8fd9\u5c06\u7834\u574f\u8c03\u7528 API \u7684\u5ba2\u6237\u7aef\uff0c\u8be5\u5ba2\u6237\u7aef\u9700\u8981 JavaScript \u5bf9\u8c61\u8868\u793a\u6cd5 \uff08JSON\uff09 \u54cd\u5e94\uff01<\/p>\n<p><b>Tip<\/b> The added complexity introduced by having to handle these two very different clients is the reason I prefer to create separate applications for APIs and server-rendered apps.<br \/>\n\u63d0\u793a:\u5fc5\u987b\u5904\u7406\u8fd9\u4e24\u4e2a\u622a\u7136\u4e0d\u540c\u7684\u5ba2\u6237\u7aef\u6240\u5e26\u6765\u7684\u590d\u6742\u6027\u589e\u52a0\u4e86\uff0c\u8fd9\u5c31\u662f\u6211\u66f4\u559c\u6b22\u4e3a API \u548c\u670d\u52a1\u5668\u5448\u73b0\u7684\u5e94\u7528\u7a0b\u5e8f\u521b\u5efa\u5355\u72ec\u7684\u5e94\u7528\u7a0b\u5e8f\u7684\u539f\u56e0\u3002<\/p>\n<p>Instead, exception filters let you handle the exception in the filter pipeline and generate an appropriate response body for API clients. The exception handler middleware intercepts only errors without a body, so it will let the modified web API response pass untouched.<br \/>\n\u76f8\u53cd\uff0c\u5f02\u5e38\u7b5b\u9009\u6761\u4ef6\u5141\u8bb8\u60a8\u5904\u7406\u7b5b\u9009\u6761\u4ef6\u7ba1\u9053\u4e2d\u7684\u5f02\u5e38\uff0c\u5e76\u4e3a API \u5ba2\u6237\u7aef\u751f\u6210\u9002\u5f53\u7684\u54cd\u5e94\u6b63\u6587\u3002\u5f02\u5e38\u5904\u7406\u7a0b\u5e8f\u4e2d\u95f4\u4ef6\u4ec5\u62e6\u622a\u6ca1\u6709\u6b63\u6587\u7684\u9519\u8bef\uff0c\u56e0\u6b64\u5b83\u5c06\u5141\u8bb8\u4fee\u6539\u540e\u7684 Web API \u54cd\u5e94\u539f\u5c01\u4e0d\u52a8\u5730\u901a\u8fc7\u3002<\/p>\n<p><b>NOTE<\/b> The [ApiController] attribute converts error StatusCodeResults to a ProblemDetails object, but it doesn\u2019t catch exceptions.<br \/>\n\u6ce8\u610f:[ApiController] \u5c5e\u6027\u5c06\u9519\u8bef StatusCodeResults \u8f6c\u6362\u4e3a ProblemDetails \u5bf9\u8c61\uff0c\u4f46\u5b83\u4e0d\u4f1a\u6355\u83b7\u5f02\u5e38\u3002<\/p>\n<p>Exception filters can catch exceptions from more than your action methods and page handlers. They\u2019ll run if an exception occurs at these times:<br \/>\n\u5f02\u5e38\u7b5b\u9009\u5668\u53ef\u4ee5\u6355\u83b7\u6765\u81ea\u591a\u4e2a\u4f5c\u65b9\u6cd5\u548c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u7684\u5f02\u5e38\u3002\u5982\u679c\u5728\u4ee5\u4e0b\u65f6\u95f4\u53d1\u751f\u5f02\u5e38\uff0c\u5b83\u4eec\u5c06\u8fd0\u884c\uff1a<\/p>\n<p>\u2022  During model binding or validation<br \/>\n\u5728\u6a21\u578b\u7ed1\u5b9a\u6216\u9a8c\u8bc1\u671f\u95f4<br \/>\n\u2022  When the action method or page handler is executing<br \/>\n\u5f53\u4f5c\u65b9\u6cd5\u6216\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u6b63\u5728\u6267\u884c\u65f6<br \/>\n\u2022  When an action filter or page filter is executing<br \/>\n\u5f53\u4f5c\u8fc7\u6ee4\u5668\u6216\u9875\u9762\u8fc7\u6ee4\u5668\u6b63\u5728\u6267\u884c\u65f6<\/p>\n<p>You should note that exception filters won\u2019t catch exceptions thrown in any filters other than action and page filters, so it\u2019s important that your resource and result filters don\u2019t throw exceptions. Similarly, they won\u2019t catch exceptions thrown when executing an IActionResult, such as when rendering a Razor view to HTML.<br \/>\n\u60a8\u5e94\u8be5\u6ce8\u610f\uff0c\u5f02\u5e38\u7b5b\u9009\u5668\u4e0d\u4f1a\u6355\u83b7\u9664\u4f5c\u548c\u9875\u9762\u7b5b\u9009\u5668\u4e4b\u5916\u7684\u4efb\u4f55\u7b5b\u9009\u5668\u4e2d\u5f15\u53d1\u7684\u5f02\u5e38\uff0c\u56e0\u6b64\u60a8\u7684\u8d44\u6e90\u548c\u7ed3\u679c\u7b5b\u9009\u5668\u4e0d\u4f1a\u5f15\u53d1\u5f02\u5e38\u975e\u5e38\u91cd\u8981\u3002\u540c\u6837\uff0c\u5b83\u4eec\u4e0d\u4f1a\u6355\u83b7\u5728\u6267\u884c IActionResult \u65f6\u5f15\u53d1\u7684\u5f02\u5e38\uff0c\u4f8b\u5982\u5728\u5c06 Razor \u89c6\u56fe\u5448\u73b0\u4e3a HTML \u65f6\u3002<\/p>\n<p>Now that you know why you might want an exception filter, go ahead and implement one for RecipeApiController, as shown next. This lets you safely remove the try-catch block from your action methods, knowing that your filter will catch any errors.<br \/>\n\u73b0\u5728\uff0c\u60a8\u77e5\u9053\u4e3a\u4ec0\u4e48\u53ef\u80fd\u9700\u8981\u5f02\u5e38\u8fc7\u6ee4\u5668\uff0c\u8bf7\u7ee7\u7eed\u4e3a RecipeApiController \u5b9e\u73b0\u4e00\u4e2a\u5f02\u5e38\u8fc7\u6ee4\u5668\uff0c\u5982\u4e0b\u6240\u793a\u3002\u8fd9\u6837\uff0c\u60a8\u5c31\u53ef\u4ee5\u5b89\u5168\u5730\u4ece\u4f5c\u65b9\u6cd5\u4e2d\u5220\u9664 try-catch \u5757\uff0c\u56e0\u4e3a\u60a8\u77e5\u9053\u8fc7\u6ee4\u5668\u5c06\u6355\u83b7\u4efb\u4f55\u9519\u8bef\u3002<\/p>\n<p>Listing 22.8 The HandleExceptionAttribute exception filter<br \/>\n\u793a\u4f8b 22.8 HandleExceptionAttribute \u5f02\u5e38\u8fc7\u6ee4\u5668<\/p>\n<pre><code>public class HandleExceptionAttribute : ExceptionFilterAttribute      #A\n{\n    public override void OnException(ExceptionContext context)      #B\n    {\n        var error = new ProblemDetails               #C\n        {                                            #C\n            Title = &quot;An error occurred&quot;,             #C\n            Detail = context.Exception.Message,      #C\n            Status = 500,                            #C\n            Type = &quot; https:\/\/httpwg.org\/specs\/rfc9110.html#status.500&quot;  #C\n        };                                           #C\n\n        context.Result = new ObjectResult(error)    #D\n        {                                           #D\n            StatusCode = 500                        #D\n        };                                          #D\n        context.ExceptionHandled = true;    #E\n    }\n}<\/code><\/pre>\n<p>\u2776 ExceptionFilterAttribute is an abstract base class that implements IExceptionFilter.<br \/>\nExceptionFilterAttribute \u662f\u5b9e\u73b0IExceptionFilter \u7684\u62bd\u8c61\u57fa\u7c7b\u3002<\/p>\n<p>\u2777 There\u2019s only a single method to override for IExceptionFilter.<br \/>\n\u53ea\u6709\u4e00\u4e2a\u65b9\u6cd5\u53ef\u4ee5\u8986\u76d6 IExceptionFilter\u3002<\/p>\n<p>\u2778 Building a problem details object to return in the response<br \/>\n\u6784\u5efa\u8981\u5728\u54cd\u5e94\u4e2d\u8fd4\u56de\u7684\u95ee\u9898\u8be6\u7ec6\u4fe1\u606f\u5bf9\u8c61<\/p>\n<p>\u2779 Creates an ObjectResult to serialize the ProblemDetails and to set the response status code<br \/>\n\u521b\u5efa\u4e00\u4e2a ObjectResult \u6765\u5e8f\u5217\u5316 ProblemDetails \u5e76\u8bbe\u7f6e\u54cd\u5e94\u72b6\u6001\u4ee3\u7801<\/p>\n<p>\u277a Marks the exception as handled to prevent it propagating into the middleware pipeline<br \/>\n\u5c06\u5f02\u5e38\u6807\u8bb0\u4e3a\u5df2\u5904\u7406\uff0c\u4ee5\u9632\u6b62\u5176\u4f20\u64ad\u5230\u4e2d\u95f4\u4ef6\u7ba1\u9053\u4e2d<\/p>\n<p>It\u2019s quite common to have an exception filter in your application if you are mixing API controllers and Razor Pages in your application, but they\u2019re not always necessary. If you can handle all the exceptions in your application with a single piece of middleware, ditch the exception filters and go with that instead.<br \/>\n\u5982\u679c\u5728\u5e94\u7528\u7a0b\u5e8f\u4e2d\u6df7\u5408\u4f7f\u7528 API \u63a7\u5236\u5668\u548c Razor Pages\uff0c\u5219\u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u5f02\u5e38\u7b5b\u9009\u5668\u5f88\u5e38\u89c1\uff0c\u4f46\u5b83\u4eec\u5e76\u4e0d\u603b\u662f\u5fc5\u8981\u7684\u3002\u5982\u679c\u53ef\u4ee5\u4f7f\u7528\u5355\u4e2a\u4e2d\u95f4\u4ef6\u5904\u7406\u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u6240\u6709\u5f02\u5e38\uff0c\u8bf7\u653e\u5f03\u5f02\u5e38\u7b5b\u9009\u5668\uff0c\u6539\u7528\u5b83\u3002<\/p>\n<p>You\u2019re almost done refactoring your RecipeApiController. You have one more filter type to add: result filters. Custom result filters tend to be relatively rare in the apps I\u2019ve written, but they have their uses, as you\u2019ll see.<br \/>\n\u60a8\u51e0\u4e4e\u5b8c\u6210\u4e86 RecipeApiController \u7684\u91cd\u6784\u3002\u60a8\u8fd8\u9700\u8981\u6dfb\u52a0\u4e00\u79cd\u7b5b\u9009\u6761\u4ef6\u7c7b\u578b\uff1a\u7ed3\u679c\u7b5b\u9009\u6761\u4ef6\u3002\u81ea\u5b9a\u4e49\u7ed3\u679c\u8fc7\u6ee4\u5668\u5728\u6211\u7f16\u5199\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\u5f80\u5f80\u76f8\u5bf9\u8f83\u5c11\uff0c\u4f46\u6b63\u5982\u60a8\u5c06\u770b\u5230\u7684\uff0c\u5b83\u4eec\u6709\u5176\u7528\u9014\u3002<\/p>\n<h3>22.1.5 Result filters: Customizing action results before they execute<\/h3>\n<p>22.1.5 \u7ed3\u679c\u8fc7\u6ee4\u5668\uff1a\u5728\u6267\u884c\u4f5c\u7ed3\u679c\u4e4b\u524d\u81ea\u5b9a\u4e49\u4f5c\u7ed3\u679c<\/p>\n<p>If everything runs successfully in the pipeline, and there\u2019s no short-circuiting, the next stage of the pipeline after action filters is result filters. These run before and after the IActionResult returned by the action method (or action filters) is executed.<br \/>\n\u5982\u679c\u7ba1\u9053\u4e2d\u7684\u6240\u6709\u5185\u5bb9\u90fd\u6210\u529f\u8fd0\u884c\uff0c\u5e76\u4e14\u6ca1\u6709\u77ed\u8def\uff0c\u5219\u4f5c\u7b5b\u9009\u5668\u540e\u7ba1\u9053\u7684\u4e0b\u4e00\u9636\u6bb5\u662f\u7ed3\u679c\u7b5b\u9009\u5668\u3002\u8fd9\u4e9b\u4f5c\u5728\u6267\u884c\u4f5c\u65b9\u6cd5\uff08\u6216\u4f5c\u7b5b\u9009\u5668\uff09\u8fd4\u56de\u7684 IActionResult \u4e4b\u524d\u548c\u4e4b\u540e\u8fd0\u884c\u3002<\/p>\n<p><b>Warning<\/b> If the pipeline is short-circuited by setting context.Result, the result filter stage won\u2019t run, but the IActionResult will still be executed to generate the response. The exceptions to this rule are action and page filters, which only short-circuit the action execution, as you saw in chapter 21. Result filters run as normal, as though the action or page handler itself generated the response.<br \/>\n\u8b66\u544a:\u5982\u679c\u901a\u8fc7\u8bbe\u7f6e context \u4f7f pipeline \u77ed\u8def\u3002Result\uff0c\u5219\u7ed3\u679c\u7b5b\u9009\u9636\u6bb5\u4e0d\u4f1a\u8fd0\u884c\uff0c\u4f46\u4ecd\u4f1a\u6267\u884c IActionResult \u4ee5\u751f\u6210\u54cd\u5e94\u3002\u6b64\u89c4\u5219\u7684\u4f8b\u5916\u60c5\u51b5\u662f action \u548c page \u8fc7\u6ee4\u5668\uff0c\u5b83\u4eec\u53ea\u4f1a\u4f7f action \u6267\u884c\u77ed\u8def\uff0c\u5982\u7b2c 21 \u7ae0\u6240\u793a\u3002\u7ed3\u679c\u7b5b\u9009\u5668\u7167\u5e38\u8fd0\u884c\uff0c\u5c31\u50cf\u4f5c\u6216\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u672c\u8eab\u751f\u6210\u54cd\u5e94\u4e00\u6837\u3002<\/p>\n<p>Result filters run immediately after action filters, so many of their use cases are similar, but you typically use result filters to customize the way the IActionResult executes. For example, ASP.NET Core has several result filters built into its framework:<br \/>\n\u7ed3\u679c\u7b5b\u9009\u6761\u4ef6\u5728\u4f5c\u7b5b\u9009\u6761\u4ef6\u4e4b\u540e\u7acb\u5373\u8fd0\u884c\uff0c\u56e0\u6b64\u5b83\u4eec\u7684\u8bb8\u591a\u4f7f\u7528\u6848\u4f8b\u76f8\u4f3c\uff0c\u4f46\u60a8\u901a\u5e38\u4f7f\u7528\u7ed3\u679c\u7b5b\u9009\u6761\u4ef6\u6765\u81ea\u5b9a\u4e49 IActionResult \u7684\u6267\u884c\u65b9\u5f0f\u3002\u4f8b\u5982\uff0cASP.NET Core \u7684\u6846\u67b6\u4e2d\u5185\u7f6e\u4e86\u591a\u4e2a\u7ed3\u679c\u7b5b\u9009\u5668\uff1a<\/p>\n<p>\u2022  ProducesAttribute\u2014This forces a web API result to be serialized to a specific output format. For example, decorating your action method with [Produces (&quot;application\/xml&quot;)] forces the formatters to try to format the response as XML, even if the client doesn\u2019t list XML in its Accept header.<br \/>\nProducesAttribute - \u5f3a\u5236\u5c06 Web API \u7ed3\u679c\u5e8f\u5217\u5316\u4e3a\u7279\u5b9a\u8f93\u51fa\u683c\u5f0f\u3002\u4f8b\u5982\uff0c\u4f7f\u7528 [Produces \uff08\u201capplication\/xml\u201d\uff09] \u4fee\u9970\u4f5c\u65b9\u6cd5\u4f1a\u5f3a\u5236\u683c\u5f0f\u5316\u7a0b\u5e8f\u5c1d\u8bd5\u5c06\u54cd\u5e94\u683c\u5f0f\u5316\u4e3a XML\uff0c\u5373\u4f7f\u5ba2\u6237\u7aef\u672a\u5728\u5176 Accept \u6807\u5934\u4e2d\u5217\u51fa XML\u3002<\/p>\n<p>\u2022  FormatFilterAttribute\u2014Decorating an action method with this filter tells the formatter to look for a route value or query string parameter called format and to use that to determine the output format. For example, you could call \/api\/recipe\/11?format=json and FormatFilter will format the response as JSON or call api\/recipe\/11?format=xml and get the response as XML.<br \/>\nFormatFilterAttribute - \u4f7f\u7528\u6b64\u8fc7\u6ee4\u5668\u4fee\u9970\u4f5c\u65b9\u6cd5\u4f1a\u544a\u77e5\u683c\u5f0f\u5316\u7a0b\u5e8f\u67e5\u627e\u540d\u4e3a format \u7684\u8def\u7531\u503c\u6216\u67e5\u8be2\u5b57\u7b26\u4e32\u53c2\u6570\uff0c\u5e76\u4f7f\u7528\u5b83\u6765\u786e\u5b9a\u8f93\u51fa\u683c\u5f0f\u3002\u4f8b\u5982\uff0c\u60a8\u53ef\u4ee5\u8c03\u7528 \/api\/recipe\/11\uff1fformat=json\uff0cFormatFilter \u4f1a\u5c06\u54cd\u5e94\u683c\u5f0f\u5316\u4e3a JSON\uff0c\u6216\u8005\u8c03\u7528 api\/recipe\/11\uff1fformat=xml \u5e76\u83b7\u53d6 XML \u5f62\u5f0f\u7684\u54cd\u5e94\u3002<\/p>\n<p><b>NOTE<\/b>  Remember that you need to explicitly configure the XML formatters if you want to serialize to XML, as described in chapter 20. For details on formatting results based on the URL, see my blog entry on the topic: <a href=\"http:\/\/mng.bz\/1rYV\">http:\/\/mng.bz\/1rYV<\/a>.<br \/>\n\u6ce8\u610f:\u8bf7\u8bb0\u4f4f\uff0c\u5982\u679c\u8981\u5e8f\u5217\u5316\u4e3a XML\uff0c\u5219\u9700\u8981\u663e\u5f0f\u914d\u7f6e XML \u683c\u5f0f\u5316\u7a0b\u5e8f\uff0c\u5982\u7b2c 20 \u7ae0\u6240\u8ff0\u3002\u6709\u5173\u57fa\u4e8e URL \u8bbe\u7f6e\u7ed3\u679c\u683c\u5f0f\u7684\u8be6\u7ec6\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605\u6211\u5173\u4e8e\u4e3b\u9898 <a href=\"http:\/\/mng.bz\/1rYV\">http:\/\/mng.bz\/1rYV<\/a> \u7684\u535a\u5ba2\u6587\u7ae0\u3002<\/p>\n<p>As well as controlling the output formatters, you can use result filters to make any last-minute adjustments before IActionResult is executed and the response is generated.<br \/>\n\u9664\u4e86\u63a7\u5236\u8f93\u51fa\u683c\u5f0f\u5316\u7a0b\u5e8f\u5916\uff0c\u60a8\u8fd8\u53ef\u4ee5\u4f7f\u7528\u7ed3\u679c\u7b5b\u9009\u5668\u5728\u6267\u884c IActionResult \u5e76\u751f\u6210\u54cd\u5e94\u4e4b\u524d\u8fdb\u884c\u4efb\u4f55\u6700\u540e\u4e00\u523b\u7684\u8c03\u6574\u3002<\/p>\n<p>As an example of the kind of flexibility available, in the following listing I demonstrate setting the LastModified header, based on the object returned from the action. This is a somewhat contrived example\u2014it\u2019s specific enough to a single action that it likely doesn\u2019t warrant being moved to a result filter\u2014but I hope you get the idea.<br \/>\n\u4f5c\u4e3a\u53ef\u7528\u7075\u6d3b\u6027\u7c7b\u578b\u7684\u4e00\u4e2a\u793a\u4f8b\uff0c\u5728\u4e0b\u9762\u7684\u6e05\u5355\u4e2d\uff0c\u6211\u6f14\u793a\u4e86\u5982\u4f55\u6839\u636e\u4ece\u4f5c\u8fd4\u56de\u7684\u5bf9\u8c61\u8bbe\u7f6e LastModified \u6807\u5934\u3002\u8fd9\u662f\u4e00\u4e2a\u6709\u70b9\u4eba\u4e3a\u7684\u793a\u4f8b \u2014 \u5b83\u5bf9\u5355\u4e2a\u4f5c\u8db3\u591f\u5177\u4f53\uff0c\u4ee5\u81f3\u4e8e\u5b83\u4e0d\u4e00\u5b9a\u9700\u8981\u79fb\u52a8\u5230\u7ed3\u679c\u7b5b\u9009\u5668 \u2014 \u4f46\u6211\u5e0c\u671b\u60a8\u80fd\u7406\u89e3\u3002<\/p>\n<p>Listing 22.9 Setting a response header in a result filter<br \/>\n\u6e05\u5355 22.9 \u5728\u7ed3\u679c\u8fc7\u6ee4\u5668\u4e2d\u8bbe\u7f6e\u54cd\u5e94\u5934<\/p>\n<pre><code>public class AddLastModifedHeaderAttribute : ResultFilterAttribute    #A\n{\n    public override void OnResultExecuting(     #B\n        ResultExecutingContext context)         #B\n    {\n        if (context.Result is OkObjectResult result          #C\n            &amp;&amp; result.Value is RecipeDetailViewModel detail)    #D\n        {\n            var viewModelDate = detail.LastModified;            #E\n            context.HttpContext.Response                        #E\n              .GetTypedHeaders().LastModified = viewModelDate;  #E\n        }\n    }\n}<\/code><\/pre>\n<p>\u2776 ResultFilterAttribute provides a useful base class you can override.<br \/>\nResultFilterAttribute \u63d0\u4f9b\u4e86\u4e00\u4e2a\u53ef\u4ee5\u91cd\u5199\u7684\u6709\u7528\u57fa\u7c7b\u3002<br \/>\n\u2777 You could also override the Executed method, but the response would already be sent by then.<br \/>\n\u4f60\u4e5f\u53ef\u4ee5\u91cd\u5199 Executed \u65b9\u6cd5\uff0c\u4f46\u90a3\u65f6\u54cd\u5e94\u5df2\u7ecf\u53d1\u9001\u4e86\u3002<br \/>\n\u2778 Checks whether the action result returned a 200 Ok result with a view model.<br \/>\n\u68c0\u67e5\u4f5c\u7ed3\u679c\u662f\u5426\u8fd4\u56de\u4e86\u89c6\u56fe\u6a21\u578b\u7684 200 Ok \u7ed3\u679c\u3002<br \/>\n\u2779 Checks whether the view model type is RecipeDetailViewModel . . .<br \/>\n\u68c0\u67e5\u89c6\u56fe\u6a21\u578b\u7c7b\u578b\u662f\u5426\u4e3a RecipeDetailViewModel . . .<br \/>\n\u277a . . . and if it is, fetches the LastModified property and sets the Last-Modified header in the response<br \/>\n. . . .\u5982\u679c\u662f\uff0c\u5219\u83b7\u53d6 LastModified \u5c5e\u6027\u5e76\u5728\u54cd\u5e94\u4e2d\u8bbe\u7f6e Last-Modified \u6807\u5934<\/p>\n<p>I\u2019ve used another helper base class here, ResultFilterAttribute, so you need to override only a single method to implement the filter. Fetch the current IActionResult, exposed on context.Result, and check that it\u2019s an OkObjectResult instance with a RecipeDetailViewModel value. If it is, fetch the LastModified field from the view model and add a Last-Modified header to the response.<br \/>\n\u6211\u5728\u8fd9\u91cc\u4f7f\u7528\u4e86\u53e6\u4e00\u4e2a\u5e2e\u52a9\u7a0b\u5e8f\u57fa\u7c7b ResultFilterAttribute\uff0c\u56e0\u6b64\u60a8\u53ea\u9700\u91cd\u5199\u4e00\u4e2a\u65b9\u6cd5\u5373\u53ef\u5b9e\u73b0\u7b5b\u9009\u5668\u3002\u63d0\u53d6\u5728\u4e0a\u4e0b\u6587\u4e2d\u516c\u5f00\u7684\u5f53\u524d IActionResult\u3002Result\uff0c\u5e76\u68c0\u67e5\u5b83\u662f\u5426\u662f\u5177\u6709 RecipeDetailViewModel \u503c\u7684 OkObjectResult \u5b9e\u4f8b\u3002\u5982\u679c\u662f\uff0c\u8bf7\u4ece\u89c6\u56fe\u6a21\u578b\u4e2d\u63d0\u53d6 LastModified \u5b57\u6bb5\uff0c\u5e76\u5c06 Last-Modified \u6807\u5934\u6dfb\u52a0\u5230\u54cd\u5e94\u4e2d\u3002<\/p>\n<p><b>Tip<\/b> GetTypedHeaders() is an extension method that provides strongly typed access to request and response headers. It takes care of parsing and formatting the values for you. You can find it in the Microsoft.AspNetCore.Http namespace.<br \/>\n\u63d0\u793aGetTypedHeaders\uff08\uff09 \u662f\u4e00\u79cd\u6269\u5c55\u65b9\u6cd5\uff0c\u5b83\u63d0\u4f9b\u5bf9\u8bf7\u6c42\u548c\u54cd\u5e94\u6807\u5934\u7684\u5f3a\u7c7b\u578b\u8bbf\u95ee\u3002\u5b83\u8d1f\u8d23\u4e3a\u60a8\u89e3\u6790\u548c\u683c\u5f0f\u5316\u503c\u3002\u60a8\u53ef\u4ee5\u5728 Microsoft.AspNetCore.Http \u547d\u540d\u7a7a\u95f4\u4e2d\u627e\u5230\u5b83\u3002<\/p>\n<p>As with resource and action filters, result filters can implement a method that runs after the result has executed: OnResultExecuted. You can use this method, for example, to inspect exceptions that happened during the execution of IActionResult.<br \/>\n\u4e0e\u8d44\u6e90\u548c\u4f5c\u7b5b\u9009\u5668\u4e00\u6837\uff0c\u7ed3\u679c\u7b5b\u9009\u5668\u53ef\u4ee5\u5b9e\u73b0\u5728\u7ed3\u679c\u6267\u884c\u540e\u8fd0\u884c\u7684\u65b9\u6cd5\uff1aOnResultExecuted\u3002\u4f8b\u5982\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528\u6b64\u65b9\u6cd5\u68c0\u67e5\u5728\u6267\u884c IActionResult \u671f\u95f4\u53d1\u751f\u7684\u5f02\u5e38\u3002<\/p>\n<p><b>Warning<\/b> Generally, you can\u2019t modify the response in the OnResultExecuted method, as you may have already started streaming the response to the client.<br \/>\n\u8b66\u544a:\u901a\u5e38\uff0c\u60a8\u65e0\u6cd5\u5728 OnResultExecuted \u65b9\u6cd5\u4e2d\u4fee\u6539\u54cd\u5e94\uff0c\u56e0\u4e3a\u60a8\u53ef\u80fd\u5df2\u7ecf\u5f00\u59cb\u5c06\u54cd\u5e94\u6d41\u5f0f\u4f20\u8f93\u5230\u5ba2\u6237\u7aef\u3002<\/p>\n<p>We\u2019ve finished simplifying the RecipeApiController now. By extracting various pieces of functionality to filters, the original controller in listing 22.1 has been simplified to the version in listing 22.2. This is obviously a somewhat extreme and contrived demonstration, and I\u2019m not advocating that filters should always be your go-to option.<br \/>\n\u6211\u4eec\u73b0\u5728\u5df2\u7ecf\u5b8c\u6210\u4e86 RecipeApiController \u7684\u7b80\u5316\u3002\u901a\u8fc7\u5c06\u5404\u79cd\u529f\u80fd\u63d0\u53d6\u5230\u8fc7\u6ee4\u5668\u4e2d\uff0c\u6e05\u5355 22.1 \u4e2d\u7684\u539f\u59cb\u63a7\u5236\u5668\u5df2\u7b80\u5316\u4e3a\u6e05\u5355 22.2 \u4e2d\u7684\u7248\u672c\u3002\u8fd9\u663e\u7136\u662f\u4e00\u4e2a\u6709\u70b9\u6781\u7aef\u548c\u505a\u4f5c\u7684\u6f14\u793a\uff0c\u6211\u5e76\u4e0d\u662f\u63d0\u5021\u8fc7\u6ee4\u5668\u5e94\u8be5\u59cb\u7ec8\u662f\u60a8\u7684\u9996\u9009\u3002<\/p>\n<p><b>Tip<\/b> Filters should be a last resort in most cases. Where possible, it is often preferable to use a simple private method in a controller, or to push functionality into the domain instead of using filters. Filters should generally be used to extract repetitive, HTTP-related, or common cross-cutting code from your controllers.<br \/>\n\u63d0\u793a:\u5728\u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u8fc7\u6ee4\u5668\u5e94\u8be5\u662f\u6700\u540e\u7684\u624b\u6bb5\u3002\u5728\u53ef\u80fd\u7684\u60c5\u51b5\u4e0b\uff0c\u901a\u5e38\u6700\u597d\u5728\u63a7\u5236\u5668\u4e2d\u4f7f\u7528\u7b80\u5355\u7684\u79c1\u6709\u65b9\u6cd5\uff0c\u6216\u8005\u5c06\u529f\u80fd\u63a8\u9001\u5230\u57df\u4e2d\u800c\u4e0d\u662f\u4f7f\u7528\u8fc7\u6ee4\u5668\u3002\u8fc7\u6ee4\u5668\u901a\u5e38\u7528\u4e8e\u4ece\u63a7\u5236\u5668\u4e2d\u63d0\u53d6\u91cd\u590d\u7684\u3001\u4e0e HTTP \u76f8\u5173\u7684\u6216\u5e38\u89c1\u7684\u6a2a\u5207\u4ee3\u7801\u3002<\/p>\n<p>There\u2019s still one more filter we haven\u2019t looked at yet, because it applies only to Razor Pages: page filters.<br \/>\n\u8fd8\u6709\u4e00\u4e2a\u7b5b\u9009\u5668\u6211\u4eec\u8fd8\u6ca1\u6709\u67e5\u770b\uff0c\u56e0\u4e3a\u5b83\u4ec5\u9002\u7528\u4e8e Razor Pages\uff1a\u9875\u9762\u7b5b\u9009\u5668\u3002<\/p>\n<h3>22.1.6 Page filters: Customizing model binding for Razor Pages<\/h3>\n<p>22.1.6 \u9875\u9762\u7b5b\u9009\u5668\uff1a\u81ea\u5b9a\u4e49 Razor \u9875\u9762\u7684\u6a21\u578b\u7ed1\u5b9a<\/p>\n<p>As already discussed, action filters apply only to controllers and actions; they have no effect on Razor Pages. Similarly, page filters have no effect on controllers and actions. Nevertheless, page filters and action filters fulfill similar roles.<br \/>\n\u5982\u524d\u6240\u8ff0\uff0c\u4f5c\u7b5b\u9009\u5668\u4ec5\u9002\u7528\u4e8e\u63a7\u5236\u5668\u548c\u4f5c;\u5b83\u4eec\u5bf9 Razor Pages \u6ca1\u6709\u5f71\u54cd\u3002\u540c\u6837\uff0c\u9875\u9762\u8fc7\u6ee4\u5668\u5bf9\u63a7\u5236\u5668\u548c\u4f5c\u4e5f\u6ca1\u6709\u5f71\u54cd\u3002\u4e0d\u8fc7\uff0c\u9875\u9762\u8fc7\u6ee4\u5668\u548c\u4f5c\u8fc7\u6ee4\u5668\u7684\u4f5c\u7528\u76f8\u4f3c\u3002<\/p>\n<p>As is the case for action filters, the ASP.NET Core framework includes several page filters out of the box. One of these is the Razor Page equivalent of the caching action filter, ResponseCacheFilter, called PageResponseCacheFilter. This works identically to the action-filter equivalent I described in section 22.1.3, setting HTTP caching headers on your Razor Page responses.<br \/>\n\u4e0e\u4f5c\u7b5b\u9009\u5668\u4e00\u6837\uff0cASP.NET Core \u6846\u67b6\u5305\u542b\u591a\u4e2a\u73b0\u6210\u7684\u9875\u9762\u7b5b\u9009\u5668\u3002\u5176\u4e2d\u4e00\u4e2a\u662f\u7f13\u5b58\u4f5c\u7b5b\u9009\u5668\u7684 Razor Page \u7b49\u6548\u9879 ResponseCacheFilter\uff0c\u79f0\u4e3a PageResponseCacheFilter\u3002\u8fd9\u4e0e\u6211\u5728\u7b2c 22.1.3 \u8282 \u5728 Razor Page \u54cd\u5e94\u4e0a\u8bbe\u7f6e HTTP \u7f13\u5b58\u6807\u5934\u4e2d\u63cf\u8ff0\u7684\u7b49\u6548\u4f5c\u8fc7\u6ee4\u5668\u7684\u5de5\u4f5c\u539f\u7406\u76f8\u540c\u3002<\/p>\n<p>Page filters are somewhat unusual, as they implement three methods, as discussed in section 22.1.2. In practice, I\u2019ve rarely seen a page filter that implements all three. It\u2019s unusual to need to run code immediately after page handler selection and before model validation. It\u2019s far more common to perform a role directly analogous to action filters. The following listing shows a page filter equivalent to the EnsureRecipeExistsAttribute action filter.<br \/>\n\u9875\u9762\u8fc7\u6ee4\u5668\u6709\u4e9b\u4e0d\u5bfb\u5e38\uff0c\u56e0\u4e3a\u5b83\u4eec\u5b9e\u73b0\u4e86\u4e09\u79cd\u65b9\u6cd5\uff0c\u5982 Section 22.1.2 \u4e2d\u6240\u8ff0\u3002\u5728\u5b9e\u8df5\u4e2d\uff0c\u6211\u5f88\u5c11\u89c1\u8fc7\u5b9e\u73b0\u6240\u6709\u8fd9\u4e09\u4e2a\u7684\u9875\u9762\u8fc7\u6ee4\u5668\u3002\u5728\u9009\u62e9\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u4e4b\u540e\u548c\u6a21\u578b\u9a8c\u8bc1\u4e4b\u524d\u9700\u8981\u7acb\u5373\u8fd0\u884c\u4ee3\u7801\u662f\u4e0d\u5e38\u89c1\u7684\u3002\u6267\u884c\u76f4\u63a5\u7c7b\u4f3c\u4e8e\u4f5c\u7b5b\u9009\u5668\u7684\u89d2\u8272\u66f4\u4e3a\u5e38\u89c1\u3002\u4ee5\u4e0b\u6e05\u5355\u663e\u793a\u4e86\u4e0e EnsureRecipeExistsAttribute\u4f5c\u7b5b\u9009\u5668\u7b49\u6548\u7684\u9875\u9762\u7b5b\u9009\u5668\u3002<\/p>\n<p>Listing 22.10 A page filter to check whether a Recipe exists<br \/>\n\u6e05\u5355 22.10 \u7528\u4e8e\u68c0\u67e5 Recipe \u662f\u5426\u5b58\u5728\u7684\u9875\u9762\u8fc7\u6ee4\u5668<\/p>\n<pre><code>public class PageEnsureRecipeExistsAttribute : Attribute, IPageFilter  #A\n{\n    public void OnPageHandlerSelected(          #B\n        PageHandlerSelectedContext context)     #B\n    {}                                          #B\n\n    public void OnPageHandlerExecuting(         #C\n        PageHandlerExecutingContext context)    #C\n    {\n        var service = context.HttpContext.RequestServices        #D\n            .GetService&lt;RecipeService&gt;();  #D\n        var recipeId = (int) context.HandlerArguments[&quot;id&quot;];     #E\n        if (!service.DoesRecipeExist(recipeId))        #F\n        {\n            context.Result = new NotFoundResult();   #G\n        }\n    }\n\n    public void OnPageHandlerExecuted(        #H\n        PageHandlerExecutedContext context)   #H\n    { }                                       #H\n}<\/code><\/pre>\n<p>\u2776 Implements IPageFilter and as an attribute so you can decorate the Razor Page PageModel<br \/>\n\u5b9e\u73b0 IPageFilter \u5e76\u4f5c\u4e3a\u5c5e\u6027\uff0c\u4ee5\u4fbf\u60a8\u53ef\u4ee5\u88c5\u9970 Razor Page PageModel<\/p>\n<p>\u2777 Executed after handler selection and before model binding\u2014not used in this example<br \/>\n\u5728\u5904\u7406\u7a0b\u5e8f\u9009\u62e9\u4e4b\u540e\u548c\u6a21\u578b\u7ed1\u5b9a\u4e4b\u524d\u6267\u884c - \u672c\u4f8b\u4e2d\u672a\u4f7f\u7528<\/p>\n<p>\u2778 Executed after model binding and validation, and before page handler execution<br \/>\n\u5728\u6a21\u578b\u7ed1\u5b9a\u548c\u9a8c\u8bc1\u4e4b\u540e\u4ee5\u53ca\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u6267\u884c\u4e4b\u524d\u6267\u884c <\/p>\n<p>\u2779 Fetches an instance of RecipeService from the DI container<br \/>\n\u4ece DI \u5bb9\u5668\u4e2d\u83b7\u53d6 RecipeService \u7684\u5b9e\u4f8b<\/p>\n<p>\u277a Retrieves the id parameter that will be passed to the page handler method when it executes<br \/>\n\u68c0\u7d22 id \u53c2\u6570\uff0c\u8be5\u53c2\u6570\u5c06\u5728\u6267\u884c\u65f6\u4f20\u9012\u7ed9\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5<\/p>\n<p>\u277b Checks whether a Recipe entity with the given RecipeId exists . . .<br \/>\n\u68c0\u67e5\u662f\u5426\u5b58\u5728\u5177\u6709\u7ed9\u5b9a RecipeId \u7684 Recipe \u5b9e\u4f53 . . .<\/p>\n<p>\u277c . . . and if it doesn\u2019t exist, returns a 404 Not Found result and short-circuits the pipeline<br \/>\n. . . .\u5982\u679c\u4e0d\u5b58\u5728\uff0c\u5219\u8fd4\u56de 404 Not Found \u7ed3\u679c\uff0c\u5e76\u5728\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u6267\u884c\uff08\u6216\u77ed\u8def\uff09\u540e\u5c06\u7ba1\u9053<\/p>\n<p>\u277d Executed after page handler execution (or short-circuiting)\u2014not used in this example<br \/>\nExecuted \u77ed\u8def \u2014 \u672c\u4f8b\u4e2d\u672a\u4f7f\u7528<\/p>\n<p>The page filter is similar to the action filter equivalent. The most obvious difference is the need to implement three methods to satisfy the IPageFilter interface. You\u2019ll commonly want to implement the OnPageHandlerExecuting method, which runs after model binding and validation, and before the page handler executes.<br \/>\n\u9875\u9762\u8fc7\u6ee4\u5668\u7c7b\u4f3c\u4e8e\u7b49\u6548\u7684\u52a8\u4f5c\u8fc7\u6ee4\u5668\u3002\u6700\u660e\u663e\u7684\u533a\u522b\u662f\u9700\u8981\u5b9e\u73b0\u4e09\u79cd\u65b9\u6cd5\u6765\u6ee1\u8db3 IPageFilter \u63a5\u53e3\u3002\u60a8\u901a\u5e38\u9700\u8981\u5b9e\u73b0 OnPageHandlerExecuting \u65b9\u6cd5\uff0c\u8be5\u65b9\u6cd5\u5728\u6a21\u578b\u7ed1\u5b9a\u548c\u9a8c\u8bc1\u4e4b\u540e\u3001\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u6267\u884c\u4e4b\u524d\u8fd0\u884c\u3002<\/p>\n<p>A subtle difference between the action filter code and the page filter code is that the action filter accesses the model-bound action arguments using context.ActionArguments. The page filter uses context.HandlerArguments in the example, but there\u2019s also another option.<br \/>\n\u4f5c\u7b5b\u9009\u6761\u4ef6\u4ee3\u7801\u548c\u9875\u9762\u7b5b\u9009\u6761\u4ef6\u4ee3\u7801\u4e4b\u95f4\u7684\u7ec6\u5fae\u5dee\u522b\u662f\uff0c\u4f5c\u7b5b\u9009\u6761\u4ef6\u4f7f\u7528\u4e0a\u4e0b\u6587\u8bbf\u95ee\u6a21\u578b\u7ed1\u5b9a\u7684\u4f5c\u53c2\u6570\u3002ActionArguments \u7684 API \u53c2\u6570\u3002\u9875\u9762\u8fc7\u6ee4\u5668\u4f7f\u7528 context\u3002HandlerArguments \u7684 HandlerArguments \u8fdb\u884c\u5339\u914d\uff0c\u4f46\u8fd8\u6709\u53e6\u4e00\u4e2a\u9009\u9879\u3002<\/p>\n<p>Remember from chapter 16 that Razor Pages often bind to public properties on the PageModel using the [BindProperty] attribute. You can access those properties directly instead of using magic strings by casting a HandlerInstance property to the correct PageModel type and accessing the property directly, as in this example:<br \/>\n\u8bf7\u8bb0\u4f4f\uff0c\u5728\u7b2c 16 \u7ae0\u4e2d\uff0cRazor Pages \u901a\u5e38\u4f7f\u7528 [BindProperty] \u5c5e\u6027\u7ed1\u5b9a\u5230 PageModel \u4e0a\u7684\u516c\u5171\u5c5e\u6027\u3002\u4f60\u53ef\u4ee5\u901a\u8fc7\u5c06 HandlerInstance \u5c5e\u6027\u5f3a\u5236\u8f6c\u6362\u4e3a\u6b63\u786e\u7684 PageModel \u7c7b\u578b\u5e76\u76f4\u63a5\u8bbf\u95ee\u8be5\u5c5e\u6027\uff0c\u76f4\u63a5\u8bbf\u95ee\u8fd9\u4e9b\u5c5e\u6027\uff0c\u800c\u4e0d\u662f\u4f7f\u7528\u9b54\u672f\u5b57\u7b26\u4e32\uff0c\u5982\u4e0b\u4f8b\u6240\u793a\uff1a<\/p>\n<pre><code>var recipeId = ((ViewRecipePageModel)context.HandlerInstance).Id<\/code><\/pre>\n<p>This is similar to the way the ControllerBase class implements IActionFilter and PageModel implements IPageFilter and IAsyncPageFilterT. If you want to create an action filter for a single Razor Page, you could save yourself the trouble of creating a separate page filter and override these methods directly in your Razor Page.<br \/>\n\u8fd9\u7c7b\u4f3c\u4e8e ControllerBase \u7c7b\u5b9e\u73b0 IActionFilter \u548c PageModel \u5b9e\u73b0 IPageFilter \u548c IAsyncPageFilterT \u7684\u65b9\u5f0f\u3002\u5982\u679c\u8981\u4e3a\u5355\u4e2a Razor \u9875\u9762\u521b\u5efa\u4f5c\u7b5b\u9009\u5668\uff0c\u5219\u53ef\u4ee5\u7701\u53bb\u521b\u5efa\u5355\u72ec\u9875\u9762\u7b5b\u9009\u5668\u7684\u9ebb\u70e6\uff0c\u5e76\u76f4\u63a5\u5728 Razor \u9875\u9762\u4e2d\u91cd\u5199\u8fd9\u4e9b\u65b9\u6cd5\u3002<\/p>\n<p><b>Tip<\/b> I generally find it\u2019s not worth the hassle of using page filters unless you have a common requirement. The extra level of indirection that page filters add, coupled with the typically bespoke nature of individual Razor Pages, means that I normally find they aren\u2019t worth using. Your mileage may vary, of course, but don\u2019t jump to them as a first option.<br \/>\n\u63d0\u793a\uff1a\u6211\u901a\u5e38\u53d1\u73b0\u4e0d\u503c\u5f97\u4f7f\u7528\u9875\u9762\u8fc7\u6ee4\u5668\u7684\u9ebb\u70e6\uff0c\u9664\u975e\u4f60\u6709\u5171\u540c\u7684\u8981\u6c42\u3002\u9875\u9762\u8fc7\u6ee4\u5668\u6dfb\u52a0\u7684\u989d\u5916\u95f4\u63a5\u7ea7\u522b\uff0c\u518d\u52a0\u4e0a\u5355\u4e2a Razor \u9875\u9762\u7684\u5178\u578b\u5b9a\u5236\u6027\u8d28\uff0c\u610f\u5473\u7740\u6211\u901a\u5e38\u4f1a\u53d1\u73b0\u5b83\u4eec\u4e0d\u503c\u5f97\u4f7f\u7528\u3002\u5f53\u7136\uff0c\u60a8\u7684\u91cc\u7a0b\u53ef\u80fd\u4f1a\u6709\u6240\u4e0d\u540c\uff0c\u4f46\u4e0d\u8981\u5c06\u5b83\u4eec\u4f5c\u4e3a\u9996\u9009\u3002<\/p>\n<p>That brings us to the end of this detailed look at each of the filters in the MVC pipeline. Looking back and comparing listings 22.1 and 22.2, you can see filters allowed us to refactor the controllers and make the intent of each action method much clearer. Writing your code in this way makes it easier to reason about, as each filter and action has a single responsibility.<br \/>\n\u8fd9\u6837\uff0c\u6211\u4eec\u5c31\u7ed3\u675f\u4e86\u5bf9 MVC \u7ba1\u9053\u4e2d\u6bcf\u4e2a\u8fc7\u6ee4\u5668\u7684\u8be6\u7ec6\u4ecb\u7ecd\u3002\u56de\u987e\u5e76\u6bd4\u8f83\u6e05\u5355 22.1 \u548c 22.2\uff0c\u60a8\u53ef\u4ee5\u770b\u5230\u8fc7\u6ee4\u5668\u5141\u8bb8\u6211\u4eec\u91cd\u6784\u63a7\u5236\u5668\uff0c\u5e76\u4f7f\u6bcf\u4e2a\u4f5c\u65b9\u6cd5\u7684\u610f\u56fe\u66f4\u52a0\u6e05\u6670\u3002\u4ee5\u8fd9\u79cd\u65b9\u5f0f\u7f16\u5199\u4ee3\u7801\u53ef\u4ee5\u66f4\u8f7b\u677e\u5730\u8fdb\u884c\u63a8\u7406\uff0c\u56e0\u4e3a\u6bcf\u4e2a\u8fc7\u6ee4\u5668\u548c\u4f5c\u90fd\u6709\u5355\u4e00\u7684\u8d23\u4efb\u3002<\/p>\n<p>In the next section we\u2019ll take a slight detour into exactly what happens when you short-circuit a filter. I\u2019ve described how to do this, by setting the context.Result property on a filter, but I haven\u2019t described exactly what happens. For example, what if there are multiple filters in the stage when it\u2019s short-circuited? Do those still run?<br \/>\n\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u7a0d\u5fae\u7ed5\u9053\u4ecb\u7ecd\u4e00\u4e0b\u5f53 filter \u77ed\u8def\u65f6\u4f1a\u53d1\u751f\u4ec0\u4e48\u3002\u6211\u5df2\u7ecf\u4ecb\u7ecd\u4e86\u5982\u4f55\u901a\u8fc7\u8bbe\u7f6e\u4e0a\u4e0b\u6587\u6765\u6267\u884c\u6b64\u4f5c\u3002Result \u5c5e\u6027\uff0c\u4f46\u6211\u8fd8\u6ca1\u6709\u5177\u4f53\u63cf\u8ff0\u4f1a\u53d1\u751f\u4ec0\u4e48\u3002\u4f8b\u5982\uff0c\u5982\u679c stage \u4e2d\u6709\u591a\u4e2a filter \u77ed\u8def\u600e\u4e48\u529e\uff1f\u90a3\u4e9b\u8fd8\u5728\u8fd0\u884c\u5417\uff1f<\/p>\n<h2>22.2 Understanding pipeline short-circuiting<\/h2>\n<p>22.2 \u4e86\u89e3\u7ba1\u9053\u77ed\u8def<\/p>\n<p>In this short section you\u2019ll learn about the details of filter-pipeline short-circuiting. You\u2019ll see what happens to the other filters in a stage when the pipeline is short-circuited and how to short-circuit each type of filter.<br \/>\n\u5728\u8fd9\u4e2a\u7b80\u77ed\u7684\u90e8\u5206\u4e2d\uff0c\u60a8\u5c06\u4e86\u89e3 filter-pipeline \u77ed\u8def\u7684\u8be6\u7ec6\u4fe1\u606f\u3002\u60a8\u5c06\u770b\u5230\u5f53\u7ba1\u9053\u77ed\u8def\u65f6\uff0c\u67d0\u4e2a\u9636\u6bb5\u4e2d\u7684\u5176\u4ed6\u8fc7\u6ee4\u5668\u4f1a\u53d1\u751f\u4ec0\u4e48\u60c5\u51b5\uff0c\u4ee5\u53ca\u5982\u4f55\u4f7f\u6bcf\u79cd\u7c7b\u578b\u7684\u8fc7\u6ee4\u5668\u77ed\u8def\u3002<\/p>\n<p>A brief warning: the topic of filter short-circuiting can be a little confusing. Unlike middleware short-circuiting, which is cut-and-dried, the filter pipeline is a bit more nuanced. Luckily, you won\u2019t often need to dig into it, but when you do, you\u2019ll be glad for the detail.<br \/>\n\u4e00\u4e2a\u7b80\u77ed\u7684\u8b66\u544a\uff1a\u6ee4\u6ce2\u5668\u77ed\u8def\u7684\u8bdd\u9898\u53ef\u80fd\u6709\u70b9\u4ee4\u4eba\u56f0\u60d1\u3002\u4e0e\u4e2d\u95f4\u4ef6\u77ed\u8def\u4e0d\u540c\uff0c\u7b5b\u9009\u5668\u7ba1\u9053\u66f4\u52a0\u5fae\u5999\u3002\u5e78\u8fd0\u7684\u662f\uff0c\u60a8\u901a\u5e38\u4e0d\u9700\u8981\u6df1\u5165\u7814\u7a76\u5b83\uff0c\u4f46\u5f53\u60a8\u8fd9\u6837\u505a\u65f6\uff0c\u60a8\u4f1a\u4e3a\u7ec6\u8282\u611f\u5230\u9ad8\u5174\u3002<\/p>\n<p>You short-circuit the authorization, resource, action, page, and result filters by setting context.Result to IActionResult. Setting an action result in this way causes some or all of the remaining pipeline to be bypassed. But the filter pipeline isn\u2019t entirely linear, as you saw in chapter 21, so short-circuiting doesn\u2019t always do an about-face back down the pipeline. For example, short-circuited action filters bypass only action method execution; the result filters and result execution stages still run.<br \/>\n\u60a8\u53ef\u4ee5\u901a\u8fc7\u8bbe\u7f6e context \u6765\u77ed\u8def authorization\u3001resource\u3001action\u3001page \u548c result \u8fc7\u6ee4\u5668\u3002Result \u8bbe\u7f6e\u4e3a IActionResult\u3002\u4ee5\u8fd9\u79cd\u65b9\u5f0f\u8bbe\u7f6e\u4f5c\u7ed3\u679c\u4f1a\u5bfc\u81f4\u7ed5\u8fc7\u90e8\u5206\u6216\u5168\u90e8\u5269\u4f59\u7ba1\u9053\u3002\u4f46\u662f filter pipeline \u5e76\u4e0d\u662f\u5b8c\u5168\u7ebf\u6027\u7684\uff0c\u6b63\u5982\u4f60\u5728\u7b2c 21 \u7ae0\u4e2d\u770b\u5230\u7684\u90a3\u6837\uff0c\u6240\u4ee5\u77ed\u8def\u5e76\u4e0d\u603b\u662f\u5728\u7ba1\u9053\u4e0a\u505a\u4e00\u4e2a\u53cd\u8f6c\u3002\u4f8b\u5982\uff0c\u77ed\u8def\u7684\u52a8\u4f5c\u8fc7\u6ee4\u5668\u4ec5\u7ed5\u8fc7\u52a8\u4f5c\u65b9\u6cd5\u7684\u6267\u884c;\u7ed3\u679c\u7b5b\u9009\u6761\u4ef6\u548c\u7ed3\u679c\u6267\u884c\u9636\u6bb5\u4ecd\u5728\u8fd0\u884c\u3002<\/p>\n<p>The other difficultly is what happens if you have more than one filter in a stage. Let\u2019s say you have three resource filters executing in a pipeline. What happens if the second filter causes a short circuit? Any remaining filters are bypassed, but the first resource filter has already run its <em>Executing command, as shown in figure 22.2. This earlier filter gets to run its <\/em>Executed command too, with context.Cancelled = true, indicating that a filter in that stage (the resource filter stage) short-circuited the pipeline.<br \/>\n\u53e6\u4e00\u4e2a\u56f0\u96be\u662f\u5728\u4e00\u4e2a\u9636\u6bb5\u4e2d\u6709\u591a\u4e2a\u8fc7\u6ee4\u5668\u65f6\u4f1a\u53d1\u751f\u4ec0\u4e48\u3002\u5047\u8bbe\u60a8\u5728\u4e00\u4e2a\u7ba1\u9053\u4e2d\u6267\u884c\u4e86\u4e09\u4e2a\u8d44\u6e90\u7b5b\u9009\u5668\u3002\u5982\u679c\u7b2c\u4e8c\u4e2a\u6ee4\u6ce2\u5668\u5bfc\u81f4\u77ed\u8def\u600e\u4e48\u529e\uff1f\u4efb\u4f55\u5269\u4f59\u7684\u8fc7\u6ee4\u5668\u90fd\u5c06\u88ab\u7ed5\u8fc7\uff0c\u4f46\u7b2c\u4e00\u4e2a\u8d44\u6e90\u8fc7\u6ee4\u5668\u5df2\u7ecf\u8fd0\u884c\u4e86\u5176 Executing \u547d\u4ee4\uff0c\u5982\u56fe 22.2 \u6240\u793a\u3002\u8fd9\u4e2a\u524d\u9762\u7684\u8fc7\u6ee4\u5668\u4e5f\u53ef\u4ee5\u8fd0\u884c\u5b83\u7684 Executed \u547d\u4ee4\uff0c\u5176\u4e2d\u5305\u542b context\u3002Cancelled = true\uff0c\u6307\u793a\u8be5\u9636\u6bb5\uff08\u8d44\u6e90\u7b5b\u9009\u5668\u9636\u6bb5\uff09\u4e2d\u7684\u7b5b\u9009\u5668\u4f7f\u7ba1\u9053\u77ed\u8def\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/2202.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 22.2 The effect of short-circuiting a resource filter on other resource filters in that stage. Later filters in the stage won\u2019t run at all, but earlier filters run their OnResourceExecuted function.<br \/>\n\u56fe 22.2 \u5728\u8be5\u9636\u6bb5\u4e2d\uff0c\u5c06\u8d44\u6e90\u8fc7\u6ee4\u5668\u77ed\u8def\u5bf9\u5176\u4ed6\u8d44\u6e90\u8fc7\u6ee4\u5668\u7684\u5f71\u54cd\u3002\u9636\u6bb5\u4e2d\u8f83\u665a\u7684\u7b5b\u9009\u5668\u6839\u672c\u4e0d\u4f1a\u8fd0\u884c\uff0c\u4f46\u8f83\u65e9\u7684\u7b5b\u9009\u5668\u4f1a\u8fd0\u884c\u5176 OnResourceExecuted \u51fd\u6570\u3002<\/p>\n<blockquote>\n<p>Running result filters after short-circuits with IAlwaysRunResultFilter<br \/>\n\u4f7f\u7528 IAlwaysRunResultFilter<\/p>\n<p>Result filters are designed to wrap the execution of an IActionResult returned by an action method or action filter so that you can customize how the action result is executed. However, this customization doesn\u2019t apply to the IActionResult set when you short-circuit the filter pipeline by setting context.Result in an authorization filter, resource filter, or exception filter.<br \/>\n\u5728\u77ed\u8def\u540e\u8fd0\u884c\u7ed3\u679c\u7b5b\u9009\u5668 \u7ed3\u679c\u7b5b\u9009\u5668\u65e8\u5728\u5305\u88c5\u4f5c\u65b9\u6cd5\u6216\u4f5c\u7b5b\u9009\u5668\u8fd4\u56de\u7684 IActionResult \u7684\u6267\u884c\uff0c\u4ee5\u4fbf\u60a8\u53ef\u4ee5\u81ea\u5b9a\u4e49\u4f5c\u7ed3\u679c\u7684\u6267\u884c\u65b9\u5f0f\u3002\u4f46\u662f\uff0c\u5f53\u60a8\u901a\u8fc7\u8bbe\u7f6e context \u4f7f\u7b5b\u9009\u5668\u7ba1\u9053\u77ed\u8def\u65f6\uff0c\u6b64\u81ea\u5b9a\u4e49\u4e0d\u9002\u7528\u4e8e IActionResult \u96c6\u3002\u751f\u6210\u6388\u6743\u7b5b\u9009\u6761\u4ef6\u3001\u8d44\u6e90\u7b5b\u9009\u6761\u4ef6\u6216\u5f02\u5e38\u7b5b\u9009\u6761\u4ef6\u3002<\/p>\n<p>That\u2019s often not a problem, as many result filters are designed to handle \u201chappy path\u201d transformations. But sometimes you want to make sure that a transformation is always applied to an IActionResult, regardless of whether it was returned by an action method or a short-circuiting filter.<br \/>\n\u8fd9\u901a\u5e38\u4e0d\u662f\u95ee\u9898\uff0c\u56e0\u4e3a\u8bb8\u591a\u7ed3\u679c\u7b5b\u9009\u5668\u90fd\u65e8\u5728\u5904\u7406 \u201chappy path\u201d \u8f6c\u6362\u3002\u4f46\u6709\u65f6\u4f60\u5e0c\u671b\u786e\u4fdd\u8f6c\u6362\u59cb\u7ec8\u5e94\u7528\u4e8e IActionResult\uff0c\u800c\u4e0d\u7ba1\u5b83\u662f\u7531\u4f5c\u65b9\u6cd5\u8fd8\u662f\u77ed\u8def\u7b5b\u9009\u5668\u8fd4\u56de\u7684\u3002<\/p>\n<p>For those cases, you can implement IAlwaysRunResultFilter or IAsyncAlwaysRunResultFilter. These interfaces extend (and are identical) to the standard result filter interfaces, so they run like normal result filters in the filter pipeline. But these interfaces mark the filter to also run after an authorization filter, resource filter, or exception filter short-circuits the pipeline, where standard result filters won\u2019t run.<br \/>\n\u5bf9\u4e8e\u8fd9\u4e9b\u60c5\u51b5\uff0c\u60a8\u53ef\u4ee5\u5b9e\u73b0 IAlwaysRunResultFilter \u6216 IAsyncAlwaysRunResultFilter\u3002\u8fd9\u4e9b\u63a5\u53e3\u6269\u5c55\uff08\u5e76\u4e14\u76f8\u540c\uff09\u5230\u6807\u51c6\u7ed3\u679c\u7b5b\u9009\u5668\u63a5\u53e3\uff0c\u56e0\u6b64\u5b83\u4eec\u50cf\u7b5b\u9009\u5668\u7ba1\u9053\u4e2d\u7684\u666e\u901a\u7ed3\u679c\u7b5b\u9009\u5668\u4e00\u6837\u8fd0\u884c\u3002\u4f46\u662f\uff0c\u8fd9\u4e9b\u63a5\u53e3\u5c06\u7b5b\u9009\u5668\u6807\u8bb0\u4e3a\u5728\u6388\u6743\u7b5b\u9009\u5668\u3001\u8d44\u6e90\u7b5b\u9009\u5668\u6216\u5f02\u5e38\u7b5b\u9009\u5668\u4f7f\u7ba1\u9053\u77ed\u8def\u540e\u4e5f\u8fd0\u884c\uff0c\u5176\u4e2d\u6807\u51c6\u7ed3\u679c\u7b5b\u9009\u5668\u4e0d\u4f1a\u8fd0\u884c\u3002<\/p>\n<p>You can use IAlwaysRunResultFilter to ensure that certain action results are always updated. For example, the documentation shows how to use an IAlwaysRunResultFilter to convert a 415 StatusCodeResult to a 422 StatusCodeResult, regardless of the source of the action result. See the \u201cIAlwaysRunResultFilter and IAsyncAlwaysRunResultFilter\u201d section of Microsoft\u2019s \u201cFilters in ASP.NET Core\u201d documentation: <a href=\"http:\/\/mng.bz\/JDo0\">http:\/\/mng.bz\/JDo0<\/a>.<br \/>\n\u60a8\u53ef\u4ee5\u4f7f\u7528 IAlwaysRunResultFilter \u6765\u786e\u4fdd\u67d0\u4e9b\u4f5c\u7ed3\u679c\u59cb\u7ec8\u66f4\u65b0\u3002\u4f8b\u5982\uff0c\u8be5\u6587\u6863\u663e\u793a\u4e86\u5982\u4f55\u4f7f\u7528 IAlwaysRunResultFilter \u5c06 415 StatusCodeResult \u8f6c\u6362\u4e3a 422 StatusCodeResult\uff0c\u800c\u4e0d\u7ba1\u4f5c\u7ed3\u679c\u7684\u6765\u6e90\u5982\u4f55\u3002\u8bf7\u53c2\u9605 Microsoft \u7684\u201cASP.NET Core \u4e2d\u7684\u7b5b\u9009\u5668\u201d\u6587\u6863\u7684\u201cIAlwaysRunResultFilter \u548c IAsyncAlwaysRunResultFilter\u201d\u90e8\u5206\uff1a<a href=\"http:\/\/mng.bz\/JDo0\">http:\/\/mng.bz\/JDo0<\/a>\u3002<\/p>\n<\/blockquote>\n<p>Understanding which other filters run when you short-circuit a filter can be somewhat of a chore, but I\u2019ve summarized each filter in table 22.1. You\u2019ll also find it useful to refer to the pipeline diagrams in chapter 21 to visualize the shape of the pipeline when thinking about short circuits.<br \/>\n\u4e86\u89e3\u5728\u4f7f filter \u77ed\u8def\u65f6\u8fd0\u884c\u54ea\u4e9b\u5176\u4ed6 filters \u53ef\u80fd\u6709\u70b9\u9ebb\u70e6\uff0c\u4f46\u6211\u5728\u8868 22.1 \u4e2d\u603b\u7ed3\u4e86\u6bcf\u4e2a filter \u3002\u5728\u8003\u8651\u77ed\u8def\u65f6\uff0c\u60a8\u8fd8\u4f1a\u53d1\u73b0\u53c2\u8003\u7b2c 21 \u7ae0\u4e2d\u7684\u6d41\u6c34\u7ebf\u56fe\u4ee5\u53ef\u89c6\u5316\u6d41\u6c34\u7ebf\u7684\u5f62\u72b6\u5f88\u6709\u7528\u3002<\/p>\n<p>Table 22.1 The effect of short-circuiting filters on filter-pipeline execution<br \/>\n\u8868 22.1 \u77ed\u8def filters \u5bf9 filter-pipeline \u6267\u884c\u7684\u5f71\u54cd<\/p>\n<table>\n<thead>\n<tr>\n<th>Filter type<\/th>\n<th>How to short-circuit?<\/th>\n<th>What else runs?<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Authorization filters<\/td>\n<td>Set context.Result.<\/td>\n<td>Runs only IAlwaysRunResultFilters.<\/td>\n<\/tr>\n<tr>\n<td>Resource filters<\/td>\n<td>Set context.Result.<\/td>\n<td>Resource-filter *Executed functions from earlier filters run with context.Cancelled = true. Runs IAlwaysRunResultFilters before executing the IActionResult.<\/td>\n<\/tr>\n<tr>\n<td>Action filters<\/td>\n<td>Set context.Result.<\/td>\n<td>Bypasses only action method execution. Action filters earlier in the pipeline run their <em>Executed methods with context.Cancelled = true, then result filters, result execution, and resource filters\u2019 <\/em>Executed methods all run as normal.<\/td>\n<\/tr>\n<tr>\n<td>Page filters<\/td>\n<td>Set context.Result in OnPageHandlerSelected.<\/td>\n<td>Bypasses only page handler execution. Page filters earlier in the pipeline run their <em>Executed methods with context.Cancelled = true, then result filters, result execution, and resource filters\u2019 <\/em>Executed methods all run as normal.<\/td>\n<\/tr>\n<tr>\n<td>Exception filters<\/td>\n<td>Set context.Result and Exception.Handled = true.<\/td>\n<td>All resource-filter *Executed functions run. Runs IAlwaysRunResultFilters before executing the IActionResult.<\/td>\n<\/tr>\n<tr>\n<td>Result filters<\/td>\n<td>Set context.Cancelled = true.<\/td>\n<td>Result filters earlier in the pipeline run their <em>Executed functions with context.Cancelled = true. All resource-filter <\/em>Executed functions run as normal.<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>The most interesting point here is that short-circuiting an action filter (or a page filter) doesn\u2019t short-circuit much of the pipeline at all. In fact, it bypasses only later action filters and the action method execution itself. By building primarily action filters, you can ensure that other filters, such as result filters that define the output format, run as usual, even when your action filters short-circuit.<br \/>\n\u8fd9\u91cc\u6700\u6709\u8da3\u7684\u4e00\u70b9\u662f\uff0c\u77ed\u8def\u4f5c\u7b5b\u9009\u5668\uff08\u6216\u9875\u9762\u7b5b\u9009\u5668\uff09\u6839\u672c\u4e0d\u4f1a\u4f7f\u7ba1\u9053\u7684\u5927\u90e8\u5206\u77ed\u8def\u3002\u4e8b\u5b9e\u4e0a\uff0c\u5b83\u53ea\u7ed5\u8fc7\u540e\u9762\u7684\u4f5c\u7b5b\u9009\u5668\u548c\u4f5c\u65b9\u6cd5\u6267\u884c\u672c\u8eab\u3002\u901a\u8fc7\u4e3b\u8981\u6784\u5efa\u4f5c\u7b5b\u9009\u5668\uff0c\u60a8\u53ef\u4ee5\u786e\u4fdd\u5176\u4ed6\u7b5b\u9009\u5668\uff08\u4f8b\u5982\u5b9a\u4e49\u8f93\u51fa\u683c\u5f0f\u7684\u7ed3\u679c\u7b5b\u9009\u5668\uff09\u7167\u5e38\u8fd0\u884c\uff0c\u5373\u4f7f\u4f5c\u7b5b\u9009\u5668\u77ed\u8def\u65f6\u4e5f\u662f\u5982\u6b64\u3002<\/p>\n<p>The last thing I\u2019d like to talk about in this chapter is how to use DI with your filters. You saw in chapters 8 and 9 that DI is integral to ASP.NET Core, and in the next section you\u2019ll see how to design your filters so that the framework can inject service dependencies into them for you.<br \/>\n\u672c\u7ae0\u6211\u60f3\u8c08\u7684\u6700\u540e\u4e00\u4ef6\u4e8b\u662f\u5982\u4f55\u5c06 DI \u4e0e\u4f60\u7684\u8fc7\u6ee4\u5668\u4e00\u8d77\u4f7f\u7528\u3002\u60a8\u5728\u7b2c 8 \u7ae0\u548c\u7b2c 9 \u7ae0\u4e2d\u770b\u5230\u4e86 DI \u662f ASP.NET Core \u4e0d\u53ef\u6216\u7f3a\u7684\u4e00\u90e8\u5206\uff0c\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u60a8\u5c06\u4e86\u89e3\u5982\u4f55\u8bbe\u8ba1\u8fc7\u6ee4\u5668\uff0c\u4ee5\u4fbf\u6846\u67b6\u53ef\u4ee5\u4e3a\u60a8\u6ce8\u5165\u670d\u52a1\u4f9d\u8d56\u9879\u3002<\/p>\n<h2>22.3 Using dependency injection with filter attributes<\/h2>\n<p>22.3 \u5c06\u4f9d\u8d56\u9879\u6ce8\u5165\u4e0e filter \u5c5e\u6027\u4e00\u8d77\u4f7f\u7528<\/p>\n<p>In this section you\u2019ll learn how to inject services into your filters so you can take advantage of the simplicity of DI in your filters. You\u2019ll learn to use two helper filters to achieve this, TypeFilterAttribute and ServiceFilterAttribute, and you\u2019ll see how they can be used to simplify the action filter you defined in section 22.1.3.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u5c06\u670d\u52a1\u6ce8\u5165\u8fc7\u6ee4\u5668\uff0c\u4ee5\u4fbf\u60a8\u53ef\u4ee5\u5728\u8fc7\u6ee4\u5668\u4e2d\u5229\u7528 DI \u7684\u7b80\u5355\u6027\u3002\u60a8\u5c06\u5b66\u4e60\u4f7f\u7528\u4e24\u4e2a\u8f85\u52a9\u8fc7\u6ee4\u5668\u6765\u5b9e\u73b0\u6b64\u76ee\u7684\uff0cTypeFilterAttribute\u548cServiceFilterAttribute\uff0c\u5e76\u4e14\u60a8\u5c06\u4e86\u89e3\u5982\u4f55\u4f7f\u7528\u5b83\u4eec\u6765\u7b80\u5316\u60a8\u5728\u7b2c 22.1.3 \u8282\u4e2d\u5b9a\u4e49\u7684\u4f5c\u8fc7\u6ee4\u5668\u3002<\/p>\n<p>The filters we\u2019ve created so far have been created as attributes. This is useful for applying filters to action methods and controllers, but it means you can\u2019t use DI to inject services into the constructor. C# attributes don\u2019t let you pass dependencies into their constructors (other than constant values), and they\u2019re created as singletons, so there\u2019s only a single instance of an attribute for the lifetime of your app. So what happens if you need to access a transient or scoped service from inside the singleton attribute?<br \/>\n\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u6211\u4eec\u521b\u5efa\u7684\u8fc7\u6ee4\u5668\u5df2\u521b\u5efa\u4e3a attributes\u3002\u8fd9\u5bf9\u4e8e\u5c06\u7b5b\u9009\u5668\u5e94\u7528\u4e8e\u4f5c\u65b9\u6cd5\u548c\u63a7\u5236\u5668\u975e\u5e38\u6709\u7528\uff0c\u4f46\u8fd9\u610f\u5473\u7740\u60a8\u4e0d\u80fd\u4f7f\u7528 DI \u5c06\u670d\u52a1\u6ce8\u5165\u6784\u9020\u51fd\u6570\u3002C# \u5c5e\u6027\u4e0d\u5141\u8bb8\u5c06\u4f9d\u8d56\u9879\u4f20\u9012\u5230\u5176\u6784\u9020\u51fd\u6570\u4e2d\uff08\u5e38\u91cf\u503c\u9664\u5916\uff09\uff0c\u5e76\u4e14\u5b83\u4eec\u88ab\u521b\u5efa\u4e3a\u5355\u4e00\u5b9e\u4f8b\uff0c\u56e0\u6b64\u5728\u5e94\u7528\u7684\u751f\u547d\u5468\u671f\u5185\u53ea\u6709\u4e00\u4e2a\u5c5e\u6027\u5b9e\u4f8b\u3002\u90a3\u4e48\uff0c\u5982\u679c\u60a8\u9700\u8981\u4ece singleton \u5c5e\u6027\u5185\u90e8\u8bbf\u95ee\u4e34\u65f6\u6216\u8303\u56f4\u670d\u52a1\uff0c\u4f1a\u53d1\u751f\u4ec0\u4e48\u60c5\u51b5\u5462\uff1f<\/p>\n<p>Listing 22.6 showed one way of doing this, using a pseudo-service locator pattern to reach into the DI container and pluck out RecipeService at runtime. This works but is generally frowned upon as a pattern in favor of proper DI. So how can you add DI to your filters?<br \/>\n\u6e05\u5355 22.6 \u5c55\u793a\u4e86\u4e00\u79cd\u5b9e\u73b0\u6b64\u76ee\u7684\u7684\u65b9\u6cd5\uff0c\u4f7f\u7528\u4f2a\u670d\u52a1\u5b9a\u4f4d\u5668\u6a21\u5f0f\u8fdb\u5165 DI \u5bb9\u5668\u5e76\u5728\u8fd0\u884c\u65f6\u63d0\u53d6 RecipeService\u3002\u8fd9\u6709\u6548\uff0c\u4f46\u901a\u5e38\u4e0d\u88ab\u770b\u4f5c\u662f\u4e00\u79cd\u652f\u6301\u9002\u5f53 DI \u7684\u6a21\u5f0f\u3002\u90a3\u4e48\u5982\u4f55\u5c06 DI \u6dfb\u52a0\u5230\u8fc7\u6ee4\u5668\u4e2d\u5462\uff1f<\/p>\n<p>The key is to split the filter in two. Instead of creating a class that\u2019s both an attribute and a filter, create a filter class that contains the functionality and an attribute that tells the framework when and where to use the filter.<br \/>\n\u5173\u952e\u662f\u5c06\u8fc7\u6ee4\u5668\u4e00\u5206\u4e3a\u4e8c\u3002\u4e0d\u8981\u521b\u5efa\u4e00\u4e2a\u65e2\u662f\u5c5e\u6027\u53c8\u662f\u7b5b\u9009\u5668\u7684\u7c7b\uff0c\u800c\u5e94\u521b\u5efa\u4e00\u4e2a\u5305\u542b\u529f\u80fd\u548c\u5c5e\u6027\u7684\u7b5b\u9009\u5668\u7c7b\uff0c\u8be5\u7c7b\u544a\u8bc9\u6846\u67b6\u4f55\u65f6\u4f55\u5730\u4f7f\u7528\u7b5b\u9009\u5668\u3002<\/p>\n<p>Let\u2019s apply this to the action filter from listing 22.6. Previously, I derived from ActionFilterAttribute and obtained an instance of RecipeService from the context passed to the method. In the following listing I show two classes, EnsureRecipeExistsFilter and EnsureRecipeExistsAttribute. The filter class is responsible for the functionality and takes in RecipeService as a constructor dependency.<br \/>\n\u8ba9\u6211\u4eec\u5c06\u5176\u5e94\u7528\u4e8e\u6e05\u5355 22.6 \u4e2d\u7684 action filter\u3002\u4ee5\u524d\uff0c\u6211\u4ece ActionFilterAttribute \u6d3e\u751f\uff0c\u5e76\u4ece\u4f20\u9012\u7ed9\u8be5\u65b9\u6cd5\u7684\u4e0a\u4e0b\u6587\u4e2d\u83b7\u53d6 RecipeService \u7684\u5b9e\u4f8b\u3002\u5728\u4e0b\u9762\u7684\u6e05\u5355\u4e2d\uff0c\u6211\u663e\u793a\u4e86\u4e24\u4e2a\u7c7b\uff0cEnsureRecipeExistsFilter \u548c EnsureRecipeExistsAttribute\u3002filter \u7c7b\u8d1f\u8d23\u529f\u80fd\uff0c\u5e76\u5c06 RecipeService \u4f5c\u4e3a\u6784\u9020\u51fd\u6570\u4f9d\u8d56\u9879\u3002<\/p>\n<p>Listing 22.11 Using DI in a filter by not deriving from Attribute<br \/>\n\u6e05\u5355 22.11 \u5728\u8fc7\u6ee4\u5668\u4e2d\u4f7f\u7528 DI \u800c\u4e0d\u662f\u4ece Attribute \u6d3e\u751f<\/p>\n<pre><code>public class EnsureRecipeExistsFilter : IActionFilter    #A\n{\n    private readonly RecipeService _service;                #B\n    public EnsureRecipeExistsFilter(RecipeService service)  #B\n    {                                                       #B\n        _service = service;                                 #B\n    }                                                       #B\n    public void OnActionExecuting(ActionExecutingContext context)  #C\n    {                                                              #C\n        var recipeId = (int) context.ActionArguments[&quot;id&quot;];        #C\n        if (!_service.DoesRecipeExist(recipeId))                   #C\n        {                                                          #C\n            context.Result = new NotFoundResult();                 #C\n        }                                                          #C\n    }                                                              #C\n\n    public void OnActionExecuted(ActionExecutedContext context) { }   #D\n}\n\npublic class EnsureRecipeExistsAttribute : TypeFilterAttribute    #E\n{\n    public EnsureRecipeExistsAttribute()               #F\n        : base(typeof(EnsureRecipeExistsFilter)) {}    #F\n}<\/code><\/pre>\n<p>\u2776 Doesn\u2019t derive from an Attribute class<br \/>\n\u4e0d\u4ece Attribute \u7c7b\u6d3e\u751f<br \/>\n\u2777 RecipeService is injected into the constructor.<br \/>\nRecipeService \u88ab\u6ce8\u5165\u5230\u6784\u9020\u51fd\u6570\u4e2d\u3002<br \/>\n\u2778 The rest of the method remains the same.<br \/>\n\u65b9\u6cd5\u7684\u5176\u4f59\u90e8\u5206\u4fdd\u6301\u4e0d\u53d8\u3002<br \/>\n\u2779 You must implement the Executed action to satisfy the interface.<br \/>\n\u60a8\u5fc5\u987b\u5b9e\u73b0 Executed\u4f5c\u624d\u80fd\u6ee1\u8db3\u63a5\u53e3\u3002<br \/>\n\u277a Derives from TypeFilter, which is used to fill dependencies using the DI container<br \/>\n\u6d3e\u751f\u81ea TypeFilter\uff0c\u7528\u4e8e\u4f7f\u7528 DI \u5bb9\u5668\u586b\u5145\u4f9d\u8d56\u9879<br \/>\n\u277b Passes the type EnsureRecipeExistsFilter as an argument to the base TypeFilter constructor<br \/>\n\u5c06\u7c7b\u578b EnsureRecipeExistsFilter \u4f5c\u4e3a\u53c2\u6570\u4f20\u9012\u7ed9\u57fa\u672c TypeFilter \u6784\u9020\u51fd\u6570<\/p>\n<p>EnsureRecipeExistsFilter is a valid filter; you could use it on its own by adding it as a global filter (as global filters don\u2019t need to be attributes). But you can\u2019t use it directly by decorating controller classes and action methods, as it\u2019s not an attribute. That\u2019s where EnsureRecipeExistsAttribute comes in.<br \/>\nEnsureRecipeExistsFilter \u662f\u6709\u6548\u7684\u7b5b\u9009\u5668;\u60a8\u53ef\u4ee5\u901a\u8fc7\u5c06\u5176\u6dfb\u52a0\u4e3a\u5168\u5c40\u8fc7\u6ee4\u5668\u6765\u5355\u72ec\u4f7f\u7528\u5b83\uff08\u56e0\u4e3a\u5168\u5c40\u8fc7\u6ee4\u5668\u4e0d\u9700\u8981\u662f\u5c5e\u6027\uff09\u3002\u4f46\u662f\u4f60\u4e0d\u80fd\u901a\u8fc7\u88c5\u9970\u63a7\u5236\u5668\u7c7b\u548c\u4f5c\u65b9\u6cd5\u6765\u76f4\u63a5\u4f7f\u7528\u5b83\uff0c\u56e0\u4e3a\u5b83\u4e0d\u662f\u4e00\u4e2a\u5c5e\u6027\u3002\u8fd9\u5c31\u662f EnsureRecipeExistsAttribute \u7684\u7528\u6b66\u4e4b\u5730\u3002<\/p>\n<p>You can decorate your methods with EnsureRecipeExistsAttribute instead. This attribute inherits from TypeFilterAttribute and passes the Type of filter to create as an argument to the base constructor. This attribute acts as a factory for EnsureRecipeExistsFilter by implementing IFilterFactory.<br \/>\n\u60a8\u53ef\u4ee5\u6539\u7528 EnsureRecipeExistsAttribute \u6765\u4fee\u9970\u60a8\u7684\u65b9\u6cd5\u3002\u6b64\u5c5e\u6027\u7ee7\u627f\u81ea TypeFilterAttribute\uff0c\u5e76\u5c06\u8981\u521b\u5efa\u7684\u8fc7\u6ee4\u5668\u7684 Type \u4f5c\u4e3a\u53c2\u6570\u4f20\u9012\u7ed9\u57fa\u672c\u6784\u9020\u51fd\u6570\u3002\u6b64\u5c5e\u6027\u901a\u8fc7\u5b9e\u73b0 IFilterFactory \u5145\u5f53 EnsureRecipeExistsFilter \u7684\u5de5\u5382\u3002<\/p>\n<p>When ASP.NET Core initially loads your app, it scans your actions and controllers, looking for filters and filter factories. It uses these to form a filter pipeline for every action in your app, as shown in figure 22.3.<br \/>\n\u5f53 ASP.NET Core \u6700\u521d\u52a0\u8f7d\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u65f6\uff0c\u5b83\u4f1a\u626b\u63cf\u60a8\u7684\u4f5c\u548c\u63a7\u5236\u5668\uff0c\u67e5\u627e\u8fc7\u6ee4\u5668\u548c\u8fc7\u6ee4\u5668\u5de5\u5382\u3002\u5b83\u4f7f\u7528\u8fd9\u4e9b\u6570\u636e\u4e3a\u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u6bcf\u4e2a\u4f5c\u5f62\u6210\u4e00\u4e2a filter pipeline \uff0c\u5982\u56fe 22.3 \u6240\u793a\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/2203.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 22.3 The framework scans your app on startup to find both filters and attributes that implement IFilterFactory. At runtime, the framework calls CreateInstance() to get an instance of the filter<br \/>\n\u56fe 22.3 \u6846\u67b6\u5728\u542f\u52a8\u65f6\u626b\u63cf\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\uff0c\u4ee5\u67e5\u627e\u5b9e\u73b0 IFilterFactory \u7684\u8fc7\u6ee4\u5668\u548c\u5c5e\u6027\u3002\u5728\u8fd0\u884c\u65f6\uff0c\u6846\u67b6\u8c03\u7528 CreateInstance\uff08\uff09 \u6765\u83b7\u53d6\u8fc7\u6ee4\u5668\u7684\u5b9e\u4f8b<\/p>\n<p>When an action decorated with EnsureRecipeExistsAttribute is called, the framework calls CreateInstance() on the IFilterFactory attribute. This creates a new instance of EnsureRecipeExistsFilter and uses the DI container to populate its dependencies (RecipeService).<br \/>\n\u8c03\u7528\u4f7f\u7528 EnsureRecipeExistsAttribute \u4fee\u9970\u7684\u4f5c\u65f6\uff0c\u6846\u67b6\u5c06\u5bf9 IFilterFactory \u5c5e\u6027\u8c03\u7528 CreateInstance\uff08\uff09\u3002\u8fd9\u5c06\u521b\u5efa\u4e00\u4e2a\u65b0\u7684 EnsureRecipeExistsFilter \u5b9e\u4f8b\uff0c\u5e76\u4f7f\u7528 DI \u5bb9\u5668\u586b\u5145\u5176\u4f9d\u8d56\u9879 \uff08RecipeService\uff09\u3002<\/p>\n<p>By using this IFilterFactory approach, you get the best of both worlds: you can decorate your controllers and actions with attributes, and you can use DI in your filters. Out of the box, two similar classes provide this functionality, which have slightly different behaviors:<br \/>\n\u901a\u8fc7\u4f7f\u7528\u8fd9\u79cd IFilterFactory \u65b9\u6cd5\uff0c\u60a8\u53ef\u4ee5\u4e24\u5168\u5176\u7f8e\uff1a\u60a8\u53ef\u4ee5\u4f7f\u7528\u5c5e\u6027\u88c5\u9970\u63a7\u5236\u5668\u548c\u4f5c\uff0c\u5e76\u4e14\u53ef\u4ee5\u5728\u8fc7\u6ee4\u5668\u4e2d\u4f7f\u7528 DI\u3002\u5f00\u7bb1\u5373\u7528\uff0c\u4e24\u4e2a\u7c7b\u4f3c\u7684\u7c7b\u63d0\u4f9b\u4e86\u6b64\u529f\u80fd\uff0c\u5b83\u4eec\u7684\u884c\u4e3a\u7565\u6709\u4e0d\u540c\uff1a<\/p>\n<p>\u2022  TypeFilterAttribute\u2014Loads all the filter\u2019s dependencies from the DI container and uses them to create a new instance of the filter.<br \/>\nTypeFilterAttribute - \u4ece DI \u5bb9\u5668\u4e2d\u52a0\u8f7d\u6240\u6709\u7b5b\u9009\u5668\u7684\u4f9d\u8d56\u9879\uff0c\u5e76\u4f7f\u7528\u5b83\u4eec\u521b\u5efa\u7b5b\u9009\u5668\u7684\u65b0\u5b9e\u4f8b\u3002<\/p>\n<p>\u2022  ServiceFilterAttribute\u2014Loads the filter itself from the DI container. The DI container takes care of the service lifetime and building the dependency graph. Unfortunately, you must also explicitly register your filter with the DI container:<br \/>\nServiceFilterAttribute - \u4ece DI \u5bb9\u5668\u52a0\u8f7d\u7b5b\u9009\u5668\u672c\u8eab\u3002DI \u5bb9\u5668\u8d1f\u8d23\u670d\u52a1\u751f\u547d\u5468\u671f\u5e76\u6784\u5efa\u4f9d\u8d56\u9879\u5173\u7cfb\u56fe\u3002\u9057\u61be\u7684\u662f\uff0c\u60a8\u8fd8\u5fc5\u987b\u5411 DI \u5bb9\u5668\u663e\u5f0f\u6ce8\u518c\u8fc7\u6ee4\u5668\uff1a<\/p>\n<pre><code>builder.Services.AddTransient&lt;EnsureRecipeExistsFilter&gt;();<\/code><\/pre>\n<p><b>Tip<\/b> You can register your services with any lifetime you choose. If your service is registered as a singleton, you can consider setting the IsReusable flag, as described in the documentation: <a href=\"http:\/\/mng.bz\/d1JD\">http:\/\/mng.bz\/d1JD<\/a>.<br \/>\n\u63d0\u793a\u60a8\u53ef\u4ee5\u4f7f\u7528\u60a8\u9009\u62e9\u7684\u4efb\u4f55\u751f\u547d\u5468\u671f\u6765\u6ce8\u518c\u60a8\u7684\u670d\u52a1\u3002\u5982\u679c\u60a8\u7684\u670d\u52a1\u6ce8\u518c\u4e3a\u5355\u4e00\u5b9e\u4f8b\uff0c\u5219\u53ef\u4ee5\u8003\u8651\u8bbe\u7f6e IsReusable \u6807\u5fd7\uff0c\u5982\u6587\u6863\u4e2d\u6240\u8ff0\uff1a<a href=\"http:\/\/mng.bz\/d1JD\">http:\/\/mng.bz\/d1JD<\/a>\u3002<\/p>\n<p>If you choose to use ServiceFilterAttribute instead of TypeFilterAttribute, and register the EnsureRecipeExistsFilter as a service in the DI container, you can apply the ServiceFilterAttribute directly to an action method:<br \/>\n\u5982\u679c\u60a8\u9009\u62e9\u4f7f\u7528 ServiceFilterAttribute \u800c\u4e0d\u662f TypeFilterAttribute\uff0c\u5e76\u5728 DI \u5bb9\u5668\u4e2d\u5c06 EnsureRecipeExistsFilter \u6ce8\u518c\u4e3a\u670d\u52a1\uff0c\u5219\u53ef\u4ee5\u5c06 ServiceFilterAttribute \u76f4\u63a5\u5e94\u7528\u4e8e\u4f5c\u65b9\u6cd5\uff1a<\/p>\n<pre><code>[ServiceFilter(typeof(EnsureRecipeExistsFilter))]\npublic IActionResult Index() =&gt; Ok();<\/code><\/pre>\n<p>Whether you choose to use TypeFilterAttribute or ServiceFilterAttribute is somewhat a matter of preference, and you can always implement a custom IFilterFactory if you need to. The key takeaway is that you can now use DI in your filters. If you don\u2019t need to use DI for a filter, implement it as an attribute directly, for simplicity.<br \/>\n\u9009\u62e9\u4f7f\u7528 TypeFilterAttribute \u8fd8\u662f ServiceFilterAttribute \u5728\u67d0\u79cd\u7a0b\u5ea6\u4e0a\u662f\u4e00\u4e2a\u9996\u9009\u9879\u95ee\u9898\uff0c\u5982\u679c\u9700\u8981\uff0c\u60a8\u59cb\u7ec8\u53ef\u4ee5\u5b9e\u73b0\u81ea\u5b9a\u4e49 IFilterFactory\u3002\u5173\u952e\u8981\u70b9\u662f\u60a8\u73b0\u5728\u53ef\u4ee5\u5728\u8fc7\u6ee4\u5668\u4e2d\u4f7f\u7528 DI\u3002\u5982\u679c\u60a8\u4e0d\u9700\u8981\u5c06 DI \u7528\u4e8e\u8fc7\u6ee4\u5668\uff0c\u8bf7\u76f4\u63a5\u5c06\u5176\u4f5c\u4e3a\u5c5e\u6027\u5b9e\u73b0\uff0c\u4ee5\u4fbf\u7b80\u5355\u8d77\u89c1\u3002<\/p>\n<p><b>Tip<\/b> I like to create my filters as a nested class of the attribute class when using this pattern. This keeps all the code nicely contained in a single file and indicates the relationship between the classes.<br \/>\n\u63d0\u793a\uff1a\u4f7f\u7528\u6b64\u6a21\u5f0f\u65f6\uff0c\u6211\u559c\u6b22\u5c06\u8fc7\u6ee4\u5668\u521b\u5efa\u4e3a attribute \u7c7b\u7684\u5d4c\u5957\u7c7b\u3002\u8fd9\u5c06\u4f7f\u6240\u6709\u4ee3\u7801\u5f88\u597d\u5730\u5305\u542b\u5728\u5355\u4e2a\u6587\u4ef6\u4e2d\uff0c\u5e76\u6307\u793a\u7c7b\u4e4b\u95f4\u7684\u5173\u7cfb\u3002<\/p>\n<p>That brings us to the end of this chapter on the filter pipeline. Filters are a somewhat advanced topic, in that they aren\u2019t strictly necessary for building basic apps, but I find them extremely useful for ensuring that my controller and action methods are simple and easy to understand.<br \/>\n\u8fd9\u5c06\u6211\u4eec\u5e26\u5230\u4e86\u672c\u7ae0\u5173\u4e8e\u8fc7\u6ee4\u5668\u7ba1\u9053\u7684\u7ed3\u5c3e\u3002\u7b5b\u9009\u5668\u662f\u4e00\u4e2a\u6bd4\u8f83\u9ad8\u7ea7\u7684\u4e3b\u9898\uff0c\u56e0\u4e3a\u5b83\u4eec\u5bf9\u4e8e\u6784\u5efa\u57fa\u672c\u5e94\u7528\u7a0b\u5e8f\u5e76\u4e0d\u662f\u7edd\u5bf9\u5fc5\u8981\u7684\uff0c\u4f46\u6211\u53d1\u73b0\u5b83\u4eec\u5bf9\u4e8e\u786e\u4fdd\u6211\u7684\u63a7\u5236\u5668\u548c\u4f5c\u65b9\u6cd5\u7b80\u5355\u6613\u61c2\u975e\u5e38\u6709\u7528\u3002<\/p>\n<p>In the next chapter we\u2019ll take our first look at securing your app. We\u2019ll discuss the difference between authentication and authorization, the concept of identity in ASP.NET Core, and how you can use the ASP.NET Core Identity system to let users register and log in to your app.<br \/>\n\u5728\u4e0b\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u9996\u5148\u4e86\u89e3\u5982\u4f55\u4fdd\u62a4\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u3002\u6211\u4eec\u5c06\u8ba8\u8bba\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\u4e4b\u95f4\u7684\u533a\u522b\u3001ASP.NET Core \u4e2d\u7684\u8eab\u4efd\u6982\u5ff5\uff0c\u4ee5\u53ca\u5982\u4f55\u4f7f\u7528 ASP.NET Core Identity \u7cfb\u7edf\u8ba9\u7528\u6237\u6ce8\u518c\u548c\u767b\u5f55\u60a8\u7684\u5e94\u7528\u3002<\/p>\n<h2>22.4 Summary<\/h2>\n<p>22.4 \u603b\u7ed3<\/p>\n<p>The filter pipeline executes as part of the MVC or Razor Pages execution. It consists of authorization filters, resource filters, action filters, page filters, exception filters, and result filters.<br \/>\n\u7b5b\u9009\u5668\u7ba1\u9053\u4f5c\u4e3a MVC \u6216 Razor Pages \u6267\u884c\u7684\u4e00\u90e8\u5206\u6267\u884c\u3002\u5b83\u7531\u6388\u6743\u7b5b\u9009\u6761\u4ef6\u3001\u8d44\u6e90\u7b5b\u9009\u6761\u4ef6\u3001\u4f5c\u7b5b\u9009\u6761\u4ef6\u3001\u9875\u9762\u7b5b\u9009\u6761\u4ef6\u3001\u5f02\u5e38\u7b5b\u9009\u6761\u4ef6\u548c\u7ed3\u679c\u7b5b\u9009\u6761\u4ef6\u7ec4\u6210\u3002<\/p>\n<p>ASP.NET Core includes many built-in filters, but you can also create custom filters tailored to your application. You can use custom filters to extract common cross-cutting functionality out of your MVC controllers and Razor Pages, reducing duplication and ensuring consistency across your endpoints.<br \/>\nASP.NET Core \u5305\u542b\u8bb8\u591a\u5185\u7f6e\u7b5b\u9009\u5668\uff0c\u4f46\u60a8\u4e5f\u53ef\u4ee5\u521b\u5efa\u9488\u5bf9\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u5b9a\u5236\u7684\u81ea\u5b9a\u4e49\u7b5b\u9009\u5668\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u81ea\u5b9a\u4e49\u7b5b\u9009\u5668\u4ece MVC \u63a7\u5236\u5668\u548c Razor \u9875\u9762\u4e2d\u63d0\u53d6\u5e38\u89c1\u7684\u6a2a\u5207\u529f\u80fd\uff0c\u4ece\u800c\u51cf\u5c11\u91cd\u590d\u5e76\u786e\u4fdd\u7aef\u70b9\u4e4b\u95f4\u7684\u4e00\u81f4\u6027\u3002<\/p>\n<p>Authorization filters run first in the pipeline and control access to APIs. ASP.NET Core includes an [Authorization] attribute that you can apply to action methods so that only logged-in users can execute the action.<br \/>\n\u6388\u6743\u8fc7\u6ee4\u5668\u9996\u5148\u5728\u7ba1\u9053\u4e2d\u8fd0\u884c\u5e76\u63a7\u5236\u5bf9 API \u7684\u8bbf\u95ee\u3002ASP.NET Core \u5305\u542b\u4e00\u4e2a [Authorization] \u5c5e\u6027\uff0c\u60a8\u53ef\u4ee5\u5c06\u5176\u5e94\u7528\u4e8e\u4f5c\u65b9\u6cd5\uff0c\u4ee5\u4fbf\u53ea\u6709\u767b\u5f55\u7528\u6237\u624d\u80fd\u6267\u884c\u8be5\u4f5c\u3002<\/p>\n<p>Resource filters run after authorization filters and again after an IActionResult has been executed. They can be used to short-circuit the pipeline so that an action method is never executed. They can also be used to customize the model-binding process for an action method.<br \/>\n\u8d44\u6e90\u7b5b\u9009\u6761\u4ef6\u5728\u6388\u6743\u7b5b\u9009\u6761\u4ef6\u4e4b\u540e\u8fd0\u884c\uff0c\u5e76\u5728\u6267\u884c IActionResult \u540e\u518d\u6b21\u8fd0\u884c\u3002\u5b83\u4eec\u53ef\u7528\u4e8e\u4f7f\u7ba1\u9053\u77ed\u8def\uff0c\u4ee5\u4fbf\u6c38\u8fdc\u4e0d\u4f1a\u6267\u884c\u4f5c\u65b9\u6cd5\u3002\u5b83\u4eec\u8fd8\u53ef\u7528\u4e8e\u81ea\u5b9a\u4e49\u4f5c\u65b9\u6cd5\u7684\u6a21\u578b\u7ed1\u5b9a\u8fc7\u7a0b\u3002<\/p>\n<p>Action filters run after model binding has occurred and before an action method executes. They also run after the action method has executed. They can be used to extract common code out of an action method to prevent duplication. They don\u2019t execute for Razor Pages, only for MVC controllers.<br \/>\n\u4f5c\u7b5b\u9009\u5668\u5728\u6a21\u578b\u7ed1\u5b9a\u53d1\u751f\u4e4b\u540e\u548c\u4f5c\u65b9\u6cd5\u6267\u884c\u4e4b\u524d\u8fd0\u884c\u3002\u5b83\u4eec\u8fd8\u4f1a\u5728\u6267\u884c\u4f5c\u65b9\u6cd5\u540e\u8fd0\u884c\u3002\u5b83\u4eec\u53ef\u7528\u4e8e\u4ece action method \u4e2d\u63d0\u53d6\u516c\u5171\u4ee3\u7801\uff0c\u4ee5\u9632\u6b62\u91cd\u590d\u3002\u5b83\u4eec\u4e0d\u4e3a Razor Pages \u6267\u884c\uff0c\u53ea\u4e3a MVC \u63a7\u5236\u5668\u6267\u884c\u3002<\/p>\n<p>The ControllerBase base class also implements IActionFilter and IAsyncActionFilter. They run at the start and end of the action filter pipeline, regardless of the ordering or scope of other action filters. They can be used to create action filters that are specific to one controller.<br \/>\nControllerBase \u57fa\u7c7b\u8fd8\u5b9e\u73b0 IActionFilter \u548c IAsyncActionFilter\u3002\u5b83\u4eec\u5728\u4f5c\u7b5b\u9009\u5668\u7ba1\u9053\u7684\u5f00\u5934\u548c\u7ed3\u5c3e\u8fd0\u884c\uff0c\u800c\u4e0d\u7ba1\u5176\u4ed6\u4f5c\u7b5b\u9009\u5668\u7684\u987a\u5e8f\u6216\u8303\u56f4\u5982\u4f55\u3002\u5b83\u4eec\u53ef\u7528\u4e8e\u521b\u5efa\u7279\u5b9a\u4e8e\u4e00\u4e2a\u63a7\u5236\u5668\u7684\u4f5c\u7b5b\u9009\u5668\u3002<\/p>\n<p>Page filters run three times: after page handler selection, after model binding, and after the page handler method executes. You can use page filters for similar purposes as action filters. Page filters execute only for Razor Pages; they don\u2019t run for MVC controllers.<br \/>\n\u9875\u9762\u8fc7\u6ee4\u5668\u8fd0\u884c\u4e09\u6b21\uff1a\u9009\u62e9\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u540e\u3001\u6a21\u578b\u7ed1\u5b9a\u540e\u548c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u6267\u884c\u540e\u3002\u60a8\u53ef\u4ee5\u5c06\u9875\u9762\u8fc7\u6ee4\u5668\u7528\u4e8e\u4e0e\u4f5c\u8fc7\u6ee4\u5668\u7c7b\u4f3c\u7684\u76ee\u7684\u3002\u9875\u9762\u7b5b\u9009\u5668\u4ec5\u5bf9 Razor Pages \u6267\u884c;\u5b83\u4eec\u4e0d\u4e3a MVC \u63a7\u5236\u5668\u8fd0\u884c\u3002<\/p>\n<p>Razor Page PageModels implement IPageFilter and IAsyncPageFilter, so they can be used to implement page-specific page filters. These are rarely used, as you can typically achieve similar results with simple private methods.<br \/>\nRazor Page PageModel \u5b9e\u73b0 IPageFilter \u548c IAsyncPageFilter\uff0c\u56e0\u6b64\u5b83\u4eec\u53ef\u7528\u4e8e\u5b9e\u73b0\u7279\u5b9a\u4e8e\u9875\u9762\u7684\u9875\u9762\u7b5b\u9009\u5668\u3002\u8fd9\u4e9b\u65b9\u6cd5\u5f88\u5c11\u4f7f\u7528\uff0c\u56e0\u4e3a\u901a\u5e38\u53ef\u4ee5\u4f7f\u7528\u7b80\u5355\u7684\u79c1\u6709\u65b9\u6cd5\u83b7\u5f97\u7c7b\u4f3c\u7684\u7ed3\u679c\u3002<\/p>\n<p>Exception filters execute after action and page filters, when an action method or page handler has thrown an exception. They can be used to provide custom error handling specific to the action executed.<br \/>\n\u5f53\u4f5c\u65b9\u6cd5\u6216\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u5f15\u53d1\u5f02\u5e38\u65f6\uff0c\u5f02\u5e38\u7b5b\u9009\u5668\u5728\u4f5c\u548c\u9875\u9762\u7b5b\u9009\u5668\u4e4b\u540e\u6267\u884c\u3002\u5b83\u4eec\u53ef\u7528\u4e8e\u63d0\u4f9b\u7279\u5b9a\u4e8e\u6240\u6267\u884c\u4f5c\u7684\u81ea\u5b9a\u4e49\u9519\u8bef\u5904\u7406\u3002<\/p>\n<p>Generally, you should handle exceptions at the middleware level, but you can use exception filters to customize how you handle exceptions for specific actions, controllers, or Razor Pages.<br \/>\n\u901a\u5e38\uff0c\u60a8\u5e94\u8be5\u5728\u4e2d\u95f4\u4ef6\u7ea7\u522b\u5904\u7406\u5f02\u5e38\uff0c\u4f46\u60a8\u53ef\u4ee5\u4f7f\u7528\u5f02\u5e38\u7b5b\u9009\u5668\u6765\u81ea\u5b9a\u4e49\u5904\u7406\u7279\u5b9a\u4f5c\u3001\u63a7\u5236\u5668\u6216 Razor Pages \u7684\u5f02\u5e38\u7684\u65b9\u5f0f\u3002<\/p>\n<p>Result filters run before and after an IActionResult is executed. You can use them to control how the action result is executed or to completely change the action result that will be executed.<br \/>\n\u7ed3\u679c\u7b5b\u9009\u5668\u5728\u6267\u884c IActionResult \u4e4b\u524d\u548c\u4e4b\u540e\u8fd0\u884c\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u5b83\u4eec\u6765\u63a7\u5236\u4f5c\u7ed3\u679c\u7684\u6267\u884c\u65b9\u5f0f\uff0c\u6216\u5b8c\u5168\u66f4\u6539\u5c06\u8981\u6267\u884c\u7684\u4f5c\u7ed3\u679c\u3002<\/p>\n<p>All filters can short-circuit the pipeline by setting a response. This generally prevents the request progressing further in the filter pipeline, but the exact behavior varies with the type of filter that is short-circuited.<br \/>\n\u6240\u6709\u8fc7\u6ee4\u5668\u90fd\u53ef\u4ee5\u901a\u8fc7\u8bbe\u7f6e\u54cd\u5e94\u6765\u77ed\u8def\u7ba1\u9053\u3002\u8fd9\u901a\u5e38\u53ef\u4ee5\u9632\u6b62\u8bf7\u6c42\u5728\u7b5b\u9009\u6761\u4ef6\u7ba1\u9053\u4e2d\u8fdb\u4e00\u6b65\u8fdb\u884c\uff0c\u4f46\u5177\u4f53\u884c\u4e3a\u4f1a\u56e0\u77ed\u8def\u7684\u7b5b\u9009\u6761\u4ef6\u7c7b\u578b\u800c\u5f02\u3002<\/p>\n<p>Result filters aren\u2019t executed when you short-circuit the pipeline using authorization, resource, or exception filters. You can ensure that result filters also run for these short-circuit cases by implementing a result filter as IAlwaysRunResultFilter or IAsyncAlwaysRunResultFilter.<br \/>\n\u5f53\u60a8\u4f7f\u7528\u6388\u6743\u3001\u8d44\u6e90\u6216\u5f02\u5e38\u7b5b\u9009\u6761\u4ef6\u5bf9\u7ba1\u9053\u8fdb\u884c\u77ed\u8def\u65f6\uff0c\u4e0d\u4f1a\u6267\u884c\u7ed3\u679c\u7b5b\u9009\u6761\u4ef6\u3002\u60a8\u53ef\u4ee5\u901a\u8fc7\u5c06\u7ed3\u679c\u7b5b\u9009\u5668\u5b9e\u73b0\u4e3a IAlwaysRunResultFilter \u6216 IAsyncAlwaysRunResultFilter \u6765\u786e\u4fdd\u7ed3\u679c\u7b5b\u9009\u5668\u4e5f\u9488\u5bf9\u8fd9\u4e9b\u77ed\u8def\u60c5\u51b5\u8fd0\u884c\u3002<\/p>\n<p>You can use ServiceFilterAttribute and TypeFilterAttribute to allow dependency injection in your custom filters. ServiceFilterAttribute requires that you register your filter and all its dependencies with the DI container, whereas TypeFilterAttribute requires only that the filter\u2019s dependencies have been registered.<br \/>\n\u60a8\u53ef\u4ee5\u4f7f\u7528 ServiceFilterAttribute \u548c TypeFilterAttribute \u5728\u81ea\u5b9a\u4e49\u7b5b\u9009\u6761\u4ef6\u4e2d\u5141\u8bb8\u4f9d\u8d56\u9879\u6ce8\u5165\u3002ServiceFilterAttribute \u8981\u6c42\u60a8\u5411 DI \u5bb9\u5668\u6ce8\u518c\u8fc7\u6ee4\u5668\u53ca\u5176\u6240\u6709\u4f9d\u8d56\u9879\uff0c\u800c TypeFilterAttribute \u4ec5\u8981\u6c42\u5df2\u6ce8\u518c\u8fc7\u6ee4\u5668\u7684\u4f9d\u8d56\u9879\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>22 Creating custom MVC and Razor Page filters 22 \u521b\u5efa\u81ea\u5b9a\u4e49 MVC \u548c Razor \u9875\u9762\u7b5b\u9009\u5668 This chapter covers \u672c\u7ae0\u6db5\u76d6 \u2022 Creating custom filters to refactor complex action methods \u521b\u5efa\u81ea\u5b9a\u4e49\u7b5b\u9009\u5668\u4ee5\u91cd\u6784\u590d\u6742\u7684\u4f5c\u65b9\u6cd5 \u2022 Using authorization filters to protect your action methods and Razor Pages \u4f7f\u7528\u6388\u6743\u7b5b\u9009\u5668\u4fdd\u62a4\u4f5c\u65b9\u6cd5\u548c Razor \u9875\u9762 \u2022 Short-circuiting the filter pipeline to bypass action and page handler execution [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-615","post","type-post","status-publish","format-standard","hentry","category-csharp"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/615","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=615"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/615\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=615"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=615"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=615"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}