{"id":578,"date":"2025-04-05T03:25:37","date_gmt":"2025-04-04T19:25:37","guid":{"rendered":"https:\/\/www.hyy.net\/?p=578"},"modified":"2025-04-05T03:25:37","modified_gmt":"2025-04-04T19:25:37","slug":"asp-net-core-in-action-5-creating-a-json-api-with-minimal-apis","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=578","title":{"rendered":"ASP.NET Core in Action 5 Creating a JSON API with minimal APIs"},"content":{"rendered":"<p>5 Creating a JSON API with minimal APIs<br \/>\n5 \u4f7f\u7528\u6700\u5c11\u7684 API \u521b\u5efa JSON API<\/p>\n<h2>This chapter covers<\/h2>\n<h2>\u672c\u7ae0\u6db5\u76d6<\/h2>\n<p>\u2022   Creating a minimal API application to return JSON to clients<br \/>\n\u521b\u5efa\u6700\u5c0f API \u5e94\u7528\u7a0b\u5e8f\u4ee5\u5c06 JSON \u8fd4\u56de\u7ed9\u5ba2\u6237\u7aef<\/p>\n<p>\u2022   Generating responses with IResult<br \/>\n\u751f\u6210\u54cd\u5e94IResult<\/p>\n<p>\u2022   Using filters to perform common actions like validation<br \/>\n\u4f7f\u7528\u7b5b\u9009\u5668\u6267\u884c\u5e38\u89c1\u4f5c\uff0c\u5982\u9a8c\u8bc1<\/p>\n<p>\u2022   Organizing your APIs with route groups<br \/>\n\u4f7f\u7528\u8def\u7531\u7ec4\u7ec4\u7ec7 API<\/p>\n<p>So far in this book you\u2019ve seen several examples of minimal API applications that return simple Hello World! responses. These examples are great for getting started, but you can also use minimal APIs to build full-featured HTTP API applications. In this chapter you\u2019ll learn about HTTP APIs, see how they differ from a server-rendered application, and find out when to use them.<\/p>\n<p>\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u5728\u672c\u4e66\u4e2d\uff0c\u60a8\u5df2\u7ecf\u770b\u5230\u4e86\u51e0\u4e2a\u6700\u5c0f API \u5e94\u7528\u7a0b\u5e8f\u7684\u793a\u4f8b\uff0c\u8fd9\u4e9b\u5e94\u7528\u7a0b\u5e8f\u8fd4\u56de\u7b80\u5355\u7684 Hello World\uff01\u3002\u8fd9\u4e9b\u793a\u4f8b\u975e\u5e38\u9002\u5408\u5165\u95e8\uff0c\u4f46\u60a8\u4e5f\u53ef\u4ee5\u4f7f\u7528\u6700\u5c11\u7684 API \u6765\u6784\u5efa\u529f\u80fd\u9f50\u5168\u7684 HTTP API \u5e94\u7528\u7a0b\u5e8f\u3002\u5728\u672c\u7ae0\u4e2d\uff0c\u60a8\u5c06\u4e86\u89e3 HTTP API\uff0c\u4e86\u89e3\u5b83\u4eec\u4e0e\u670d\u52a1\u5668\u5448\u73b0\u7684\u5e94\u7528\u7a0b\u5e8f\u6709\u4f55\u4e0d\u540c\uff0c\u5e76\u4e86\u89e3\u4f55\u65f6\u4f7f\u7528\u5b83\u4eec\u3002<\/p>\n<p>Section 5.2 starts by expanding on the minimal API applications you\u2019ve already seen. You\u2019ll explore some basic routing concepts and show how values can be extracted from the URL automatically. Then you\u2019ll learn how to handle additional HTTP verbs such as POST and PUT, and explore various ways to define your APIs.<\/p>\n<p>\u57285.2 \u4e2d\u8282\uff0c\u9996\u5148\u6269\u5c55\u4e86\u60a8\u5df2\u7ecf\u89c1\u8fc7\u7684\u6700\u5c0f API \u5e94\u7528\u7a0b\u5e8f\u3002\u60a8\u5c06\u63a2\u7d22\u4e00\u4e9b\u57fa\u672c\u7684\u8def\u7531\u6982\u5ff5\uff0c\u5e76\u5c55\u793a\u5982\u4f55\u4ece URL \u4e2d\u81ea\u52a8\u63d0\u53d6\u503c\u3002\u7136\u540e\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u5904\u7406\u5176\u4ed6 HTTP \u52a8\u8bcd\uff08\u5982 POST \u548c PUT\uff09\uff0c\u5e76\u63a2\u7d22\u5b9a\u4e49 API \u7684\u5404\u79cd\u65b9\u6cd5\u3002<\/p>\n<p>In section 5.3 you\u2019ll learn about the different return types you can use with minimal APIs. You\u2019ll see how to use the Results and TypedResults helper classes to easily create HTTP responses that use status codes like 201 Created and 404 Not Found. You\u2019ll also learn how to follow web standards for describing your errors by using the built-in support for Problem Details.<\/p>\n<p>\u57285.3 \u4e2d\u8282\uff0c\u60a8\u5c06\u4e86\u89e3\u53ef\u4ee5\u4e0e\u6700\u5c11\u7684 API \u4e00\u8d77\u4f7f\u7528\u7684\u4e0d\u540c\u8fd4\u56de\u7c7b\u578b\u3002\u60a8\u5c06\u770b\u5230\u5982\u4f55\u4f7f\u7528Results \u548c TypedResults \u5e2e\u52a9\u7a0b\u5e8f\u7c7b\u8f7b\u677e\u521b\u5efa\u4f7f\u7528\u72b6\u6001\u4ee3\u7801\uff08\u5982 201 Created \u548c 404 Not Found\uff09\u7684 HTTP \u54cd\u5e94\u3002\u60a8\u8fd8\u5c06\u5b66\u4e60\u5982\u4f55\u4f7f\u7528\u5bf9 Problem Details \u7684\u5185\u7f6e\u652f\u6301\u6765\u9075\u5faa Web \u6807\u51c6\u6765\u63cf\u8ff0\u9519\u8bef\u3002<\/p>\n<p>Section 5.4 introduces one of the big features added to minimal APIs in .NET 7: filters. You can use filters to build a mini pipeline (similar to the middleware pipeline from chapter 4) for each of your endpoints. Like middleware, filters are great for extracting common code from your endpoint handlers, making your handlers easier to read.<\/p>\n<p>\u57285.3 \u4e2d\u8282\uff0c\u4ecb\u7ecd\u4e86 .NET 7 \u4e2d\u6dfb\u52a0\u5230\u6700\u5c0f API \u7684\u91cd\u8981\u529f\u80fd\u4e4b\u4e00\uff1a\u8fc7\u6ee4\u5668\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u8fc7\u6ee4\u5668\u4e3a\u6bcf\u4e2a\u7ec8\u7aef\u8282\u70b9\u6784\u5efa\u4e00\u4e2a\u5fae\u578b\u7ba1\u9053\uff08\u7c7b\u4f3c\u4e8e\u7b2c 4 \u7ae0\u4e2d\u7684\u4e2d\u95f4\u4ef6\u7ba1\u9053\uff09\u3002\u4e0e\u4e2d\u95f4\u4ef6\u4e00\u6837\uff0c\u7b5b\u9009\u5668\u975e\u5e38\u9002\u5408\u4ece\u7ec8\u7ed3\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u63d0\u53d6\u5e38\u89c1\u4ee3\u7801\uff0c\u4ece\u800c\u4f7f\u5904\u7406\u7a0b\u5e8f\u66f4\u6613\u4e8e\u9605\u8bfb\u3002<\/p>\n<p>You\u2019ll learn about the other big .NET 7 feature for minimal APIs in section 5.5: route groups. You can use route groups to reduce the duplication in your minimal APIs, extracting common routing prefixes and filters, making your APIs easier to read, and reducing boilerplate. In conjunction with filters, route groups address many of the common complaints raised against minimal APIs when they were released in .NET 6.<\/p>\n<p>\u60a8\u5c06\u5728 5.5\uff1a\u8def\u7531\u7ec4 \u90e8\u5206\u4e86\u89e3\u6700\u5c0f API \u7684\u5176\u4ed6\u91cd\u8981 .NET 7 \u529f\u80fd\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u8def\u7531\u7ec4\u6765\u51cf\u5c11\u6700\u5c0f API \u4e2d\u7684\u91cd\u590d\uff0c\u63d0\u53d6\u5e38\u89c1\u7684\u8def\u7531\u524d\u7f00\u548c\u7b5b\u9009\u6761\u4ef6\uff0c\u4f7f\u60a8\u7684 API \u66f4\u6613\u4e8e\u9605\u8bfb\uff0c\u5e76\u51cf\u5c11\u6837\u677f\u6587\u4ef6\u3002\u4e0e\u7b5b\u9009\u5668\u7ed3\u5408\u4f7f\u7528\uff0c\u8def\u7531\u7ec4\u53ef\u4ee5\u89e3\u51b3\u5728 .NET 6 \u4e2d\u53d1\u5e03\u6700\u5c0f API \u65f6\u9488\u5bf9\u6700\u5c0f API \u63d0\u51fa\u7684\u8bb8\u591a\u5e38\u89c1\u6295\u8bc9\u3002<\/p>\n<p>One great aspect of ASP.NET Core is the variety of applications you can create with it. The ability to easily build a generalized HTTP API presents the possibility of using ASP.NET Core in a greater range of situations than can be achieved with traditional web apps alone. But should you build an HTTP API, and if so, why? In the first section of this chapter, I\u2019ll go over some of the reasons why you may\u2014or may not\u2014want to create a web API.<\/p>\n<p>ASP.NET Core \u7684\u4e00\u4e2a\u91cd\u8981\u65b9\u9762\u662f\u60a8\u53ef\u4ee5\u4f7f\u7528\u5b83\u521b\u5efa\u7684\u5404\u79cd\u5e94\u7528\u7a0b\u5e8f\u3002\u4e0e\u5355\u72ec\u4f7f\u7528\u4f20\u7edf Web \u5e94\u7528\u7a0b\u5e8f\u76f8\u6bd4\uff0c\u8f7b\u677e\u6784\u5efa\u901a\u7528 HTTP API \u7684\u80fd\u529b\u4e3a\u5728\u66f4\u5927\u8303\u56f4\u5185\u4f7f\u7528 ASP.NET Core \u63d0\u4f9b\u4e86\u53ef\u80fd\u6027\u3002\u4f46\u662f\uff0c\u60a8\u5e94\u8be5\u6784\u5efa HTTP API\uff0c\u5982\u679c\u662f\u8fd9\u6837\uff0c\u4e3a\u4ec0\u4e48\uff1f\u5728\u672c\u7ae0\u7684\u7b2c\u4e00\u90e8\u5206\u4e2d\uff0c\u6211\u5c06\u4ecb\u7ecd\u60a8\u53ef\u80fd\u5e0c\u671b\uff08\u4e5f\u53ef\u80fd\u4e0d\u5e0c\u671b\uff09\u521b\u5efa Web API \u7684\u4e00\u4e9b\u539f\u56e0\u3002<\/p>\n<h2>5.1 What is an HTTP API, and when should you use one?<\/h2>\n<h2>5.1 \u4ec0\u4e48\u662f HTTP API\uff0c\u4f55\u65f6\u5e94\u4f7f\u7528 HTTP API\uff1f<\/h2>\n<p>Traditional web applications handle requests by returning HTML, which is displayed to the user in a web browser. You can easily build applications like that by using Razor Pages to generate HTML with Razor templates, as you\u2019ll learn in part 2 of this book. This approach is common and well understood, but the modern application developer has other possibilities to consider (figure 5.1), as you first saw in chapter 2.<br \/>\n\u4f20\u7edf\u7684 Web \u5e94\u7528\u7a0b\u5e8f\u901a\u8fc7\u8fd4\u56de HTML \u6765\u5904\u7406\u8bf7\u6c42\uff0cHTML \u5728 Web \u6d4f\u89c8\u5668\u4e2d\u663e\u793a\u7ed9\u7528\u6237\u3002\u901a\u8fc7\u4f7f\u7528 Razor Pages \u901a\u8fc7 Razor \u6a21\u677f\u751f\u6210 HTML\uff0c\u53ef\u4ee5\u8f7b\u677e\u6784\u5efa\u6b64\u7c7b\u5e94\u7528\u7a0b\u5e8f\uff0c\u60a8\u5c06\u5728\u672c\u4e66\u7684\u7b2c 2 \u90e8\u5206\u4e2d\u5b66\u4e60\u3002\u8fd9\u79cd\u65b9\u6cd5\u5f88\u5e38\u89c1\uff0c\u4e5f\u5f88\u5bb9\u6613\u7406\u89e3\uff0c\u4f46\u73b0\u4ee3\u5e94\u7528\u7a0b\u5e8f\u5f00\u53d1\u4eba\u5458\u8fd8\u6709\u5176\u4ed6\u53ef\u80fd\u6027\u9700\u8981\u8003\u8651\uff08\u56fe 5.1\uff09\uff0c\u6b63\u5982\u60a8\u5728\u7b2c 2 \u7ae0\u4e2d\u7b2c\u4e00\u6b21\u770b\u5230\u7684\u90a3\u6837\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0501.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 5.1 Modern developers have to consider several consumers of their applications. As well as traditional users with web browsers, these users could be single-page applications, mobile applications, or other apps.<br \/>\n\u56fe 5.1 \u73b0\u4ee3\u5f00\u53d1\u4eba\u5458\u5fc5\u987b\u8003\u8651\u5176\u5e94\u7528\u7a0b\u5e8f\u7684\u591a\u4e2a\u4f7f\u7528\u8005\u3002\u9664\u4e86\u4f7f\u7528 Web \u6d4f\u89c8\u5668\u7684\u4f20\u7edf\u7528\u6237\u5916\uff0c\u8fd9\u4e9b\u7528\u6237\u8fd8\u53ef\u4ee5\u662f\u5355\u9875\u5e94\u7528\u7a0b\u5e8f\u3001\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\u6216\u5176\u4ed6\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>Client-side single-page applications (SPAs) have become popular in recent years with the development of frameworks such as Angular, React, and Vue. These frameworks typically use JavaScript running in a web browser to generate the HTML that users see and interact with. The server sends this initial JavaScript to the browser when the user first reaches the app. The user\u2019s browser loads the JavaScript and initializes the SPA before loading any application data from the server.<\/p>\n<p>\u8fd1\u5e74\u6765\uff0c\u968f\u7740 Angular\u3001React \u548c Vue \u7b49\u6846\u67b6\u7684\u53d1\u5c55\uff0c\u5ba2\u6237\u7aef\u5355\u9875\u5e94\u7528\u7a0b\u5e8f \uff08SPA\uff09 \u53d8\u5f97\u6d41\u884c\u8d77\u6765\u3002\u8fd9\u4e9b\u6846\u67b6\u901a\u5e38\u4f7f\u7528\u5728 Web \u6d4f\u89c8\u5668\u4e2d\u8fd0\u884c\u7684 JavaScript \u6765\u751f\u6210\u7528\u6237\u770b\u5230\u5e76\u4e0e\u4e4b\u4ea4\u4e92\u7684 HTML\u3002\u5f53\u7528\u6237\u9996\u6b21\u8bbf\u95ee\u5e94\u7528\u7a0b\u5e8f\u65f6\uff0c\u670d\u52a1\u5668\u4f1a\u5c06\u6b64\u521d\u59cb JavaScript \u53d1\u9001\u5230\u6d4f\u89c8\u5668\u3002\u7528\u6237\u7684\u6d4f\u89c8\u5668\u5728\u4ece\u670d\u52a1\u5668\u52a0\u8f7d\u4efb\u4f55\u5e94\u7528\u7a0b\u5e8f\u6570\u636e\u4e4b\u524d\u52a0\u8f7d JavaScript \u5e76\u521d\u59cb\u5316 SPA\u3002<\/p>\n<p><b>NOTE<\/b>  Blazor WebAssembly is an exciting new SPA framework. Blazor lets you write an SPA that runs in the browser like other SPAs, but it uses C# and Razor templates instead of JavaScript by using the new web standard, WebAssembly. I don\u2019t cover Blazor in this book, so to find out more, I recommend Blazor in Action, by Chris Sainty (Manning, 2022).<\/p>\n<p>\u6ce8\u610f\uff1aBlazor WebAssembly \u662f\u4e00\u4e2a\u4ee4\u4eba\u5174\u594b\u7684\u65b0 SPA \u6846\u67b6\u3002Blazor \u5141\u8bb8\u60a8\u7f16\u5199\u4e00\u4e2a\u5728\u6d4f\u89c8\u5668\uff0c\u4f46\u5b83\u4f7f\u7528\u65b0\u7684 Web \u6807\u51c6 WebAssembly \u4f7f\u7528 C# \u548c Razor \u6a21\u677f\uff0c\u800c\u4e0d\u662f JavaScript\u3002\u6211\u5728\u8fd9\u672c\u4e66\u4e2d\u6ca1\u6709\u4ecb\u7ecd Blazor\uff0c\u56e0\u6b64\u8981\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\uff0c\u6211\u63a8\u8350 Chris Sainty \u7684 Blazor in Action\uff08\u66fc\u5b81\uff0c2022 \u5e74\uff09\u3002<\/p>\n<p>Once the SPA is loaded in the browser, communication with a server still occurs over HTTP, but instead of sending HTML directly to the browser in response to requests, the server-side application sends data\u2014normally, in the ubiquitous JavaScript Object Notation (JSON) format\u2014to the client-side application. Then the SPA parses the data and generates the appropriate HTML to show to a user, as shown in figure 5.2. The server-side application endpoint that the client communicates with is sometimes called an HTTP API, a JSON API, or a REST API, depending on the specifics of the API\u2019s design.<\/p>\n<p>\u5728\u6d4f\u89c8\u5668\u4e2d\u52a0\u8f7d SPA \u540e\uff0c\u4e0e\u670d\u52a1\u5668\u7684\u901a\u4fe1\u4ecd\u901a\u8fc7 HTTP \u8fdb\u884c\uff0c\u4f46\u670d\u52a1\u5668\u7aef\u5e94\u7528\u7a0b\u5e8f\u4e0d\u662f\u76f4\u63a5\u5411\u6d4f\u89c8\u5668\u53d1\u9001 HTML \u4ee5\u54cd\u5e94\u8bf7\u6c42\uff0c\u800c\u662f\u5c06\u6570\u636e\uff08\u901a\u5e38\u4ee5\u65e0\u5904\u4e0d\u5728\u7684 JavaScript \u5bf9\u8c61\u8868\u793a\u6cd5 \uff08JSON\uff09 \u683c\u5f0f\uff09\u53d1\u9001\u5230\u5ba2\u6237\u7aef\u5e94\u7528\u7a0b\u5e8f\u3002\u7136\u540e\uff0cSPA \u89e3\u6790\u6570\u636e\u5e76\u751f\u6210\u76f8\u5e94\u7684 HTML \u4ee5\u5411\u7528\u6237\u663e\u793a\uff0c\u5982\u56fe 5.2 \u6240\u793a\u3002\u5ba2\u6237\u7aef\u4e0e\u4e4b\u901a\u4fe1\u7684\u670d\u52a1\u5668\u7aef\u5e94\u7528\u7a0b\u5e8f\u7ec8\u7aef\u8282\u70b9\u6709\u65f6\u79f0\u4e3a HTTP API\u3001JSON API \u6216 REST API\uff0c\u5177\u4f53\u53d6\u51b3\u4e8e API \u8bbe\u8ba1\u7684\u5177\u4f53\u60c5\u51b5\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0502.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 5.2 A sample client-side SPA using Blazor WebAssembly. The initial requests load the SPA files into the browser, and subsequent requests fetch data from a web API, formatted as JSON.<br \/>\n\u56fe 5.2 \u4f7f\u7528 Blazor WebAssembly \u7684\u5ba2\u6237\u7aef SPA \u793a\u4f8b\u3002\u521d\u59cb\u8bf7\u6c42\u5c06 SPA \u6587\u4ef6\u52a0\u8f7d\u5230\u6d4f\u89c8\u5668\u4e2d\uff0c\u540e\u7eed\u8bf7\u6c42\u4ece Web API \u83b7\u53d6\u6570\u636e\uff0c\u683c\u5f0f\u4e3a JSON\u3002<\/p>\n<p><b>DEFINITION<\/b> An HTTP API exposes multiple URLs via HTTP that can be used to access or change data on a server. It typically returns data using the JSON format. HTTP APIs are sometimes called web APIs, but as web API refers to a specific technology in ASP.NET Core, in this book I use HTTP API to refer to the generic concept.<br \/>\n\u5b9a\u4e49\uff1a HTTP API \u901a\u8fc7 HTTP \u516c\u5f00\u591a\u4e2a URL\uff0c\u53ef\u7528\u4e8e\u8bbf\u95ee\u6216\u66f4\u6539\u670d\u52a1\u5668\u4e0a\u7684\u6570\u636e\u3002\u5b83\u901a\u5e38\u4f7f\u7528 JSON \u683c\u5f0f\u8fd4\u56de\u6570\u636e\u3002HTTP API \u6709\u65f6\u4e5f\u79f0\u4e3a Web API\uff0c\u4f46\u7531\u4e8e Web API \u6307\u7684\u662f ASP.NET Core \u4e2d\u7684\u7279\u5b9a\u6280\u672f\uff0c\u56e0\u6b64\u5728\u672c\u4e66\u4e2d\uff0c\u6211\u4f7f\u7528 HTTP API \u6765\u6307\u4ee3\u901a\u7528\u6982\u5ff5\u3002<\/p>\n<p>These days, mobile applications are common and, from the server application\u2019s point of view, similar to client-side SPAs. A mobile application typically communicates with a server application by using an HTTP API, receiving data in JSON format, just like an SPA. Then it modifies the application\u2019s UI depending on the data it receives.<\/p>\n<p>\u5982\u4eca\uff0c\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\u5f88\u5e38\u89c1\uff0c\u4ece\u670d\u52a1\u5668\u5e94\u7528\u7a0b\u5e8f\u7684\u89d2\u5ea6\u6765\u770b\uff0c\u5b83\u7c7b\u4f3c\u4e8e\u5ba2\u6237\u7aef SPA\u3002\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\u901a\u5e38\u4f7f\u7528 HTTP API \u4e0e\u670d\u52a1\u5668\u5e94\u7528\u7a0b\u5e8f\u901a\u4fe1\uff0c\u4ee5 JSON \u683c\u5f0f\u63a5\u6536\u6570\u636e\uff0c\u5c31\u50cf SPA \u4e00\u6837\u3002\u7136\u540e\uff0c\u5b83\u6839\u636e\u63a5\u6536\u5230\u7684\u6570\u636e\u4fee\u6539\u5e94\u7528\u7a0b\u5e8f\u7684 UI\u3002<\/p>\n<p>One final use case for an HTTP API is where your application is designed to be partially or solely consumed by other backend services. Imagine that you\u2019ve built a web application to send emails. By creating an HTTP API, you can allow other application developers to use your email service by sending you an email address and a message. Virtually all languages and platforms have access to an HTTP library they could use to access your service from code.<\/p>\n<p>HTTP API \u7684\u6700\u540e\u4e00\u4e2a\u7528\u4f8b\u662f\uff0c\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u88ab\u8bbe\u8ba1\u4e3a\u90e8\u5206\u6216\u5168\u90e8\u7531\u5176\u4ed6\u540e\u7aef\u670d\u52a1\u4f7f\u7528\u3002\u5047\u8bbe\u60a8\u5df2\u7ecf\u6784\u5efa\u4e86\u4e00\u4e2a\u7528\u4e8e\u53d1\u9001\u7535\u5b50\u90ae\u4ef6\u7684 Web \u5e94\u7528\u7a0b\u5e8f\u3002\u901a\u8fc7\u521b\u5efa HTTP API\uff0c\u60a8\u53ef\u4ee5\u5141\u8bb8\u5176\u4ed6\u5e94\u7528\u7a0b\u5e8f\u5f00\u53d1\u4eba\u5458\u901a\u8fc7\u5411\u60a8\u53d1\u9001\u7535\u5b50\u90ae\u4ef6\u5730\u5740\u548c\u6d88\u606f\u6765\u4f7f\u7528\u60a8\u7684\u7535\u5b50\u90ae\u4ef6\u670d\u52a1\u3002\u51e0\u4e4e\u6240\u6709\u8bed\u8a00\u548c\u5e73\u53f0\u90fd\u53ef\u4ee5\u8bbf\u95ee HTTP \u5e93\uff0c\u5b83\u4eec\u53ef\u4ee5\u4f7f\u7528\u8be5\u5e93\u4ece\u4ee3\u7801\u8bbf\u95ee\u60a8\u7684\u670d\u52a1\u3002<\/p>\n<p>That\u2019s all there is to an HTTP API: it exposes endpoints (URLs) that client applications can send requests to and retrieve data from. These endpoints are used to power the behavior of the client apps, as well as to provide all the data the client apps need to display the correct interface to a user.<\/p>\n<p>\u8fd9\u5c31\u662f HTTP API \u7684\u5168\u90e8\u5185\u5bb9\uff1a\u5b83\u516c\u5f00\u4e86\u5ba2\u6237\u7aef\u5e94\u7528\u7a0b\u5e8f\u53ef\u4ee5\u5411\u5176\u53d1\u9001\u8bf7\u6c42\u548c\u68c0\u7d22\u6570\u636e\u7684\u7aef\u70b9 \uff08URL\uff09\u3002\u8fd9\u4e9b\u7aef\u70b9\u7528\u4e8e\u652f\u6301\u5ba2\u6237\u7aef\u5e94\u7528\u7a0b\u5e8f\u7684\u884c\u4e3a\uff0c\u4ee5\u53ca\u63d0\u4f9b\u5ba2\u6237\u7aef\u5e94\u7528\u7a0b\u5e8f\u5411\u7528\u6237\u663e\u793a\u6b63\u786e\u754c\u9762\u6240\u9700\u7684\u6240\u6709\u6570\u636e\u3002<\/p>\n<p><b>NOTE<\/b>  You have even more options when it comes to creating APIs in ASP.NET Core. You can create remote procedure call APIs using gRPC, for example, or provide an alternative style of HTTP API using the GraphQL standard. I don\u2019t cover those technologies in this book, but you can read about gRPC at <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/grpc\">https:\/\/docs.microsoft.com\/aspnet\/core\/grpc<\/a> and find out about GraphQL in Building Web APIs with ASP.NET Core, by Valerio De Sanctis (Manning, 2023).<br \/>\n\u6ce8\u610f\uff1a\u5728 ASP.NET Core \u4e2d\u521b\u5efa API \u65f6\uff0c\u60a8\u6709\u66f4\u591a\u9009\u62e9\u3002\u4f8b\u5982\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528 gRPC \u521b\u5efa\u8fdc\u7a0b\u8fc7\u7a0b\u8c03\u7528 API\uff0c\u6216\u4f7f\u7528 GraphQL \u6807\u51c6\u63d0\u4f9b HTTP API \u7684\u66ff\u4ee3\u6837\u5f0f\u3002\u6211\u4e0d\u4f1a\u5728\u672c\u4e66\u4e2d\u4ecb\u7ecd\u8fd9\u4e9b\u6280\u672f\uff0c\u4f46\u60a8\u53ef\u4ee5\u5728 <a href=\"https:\/\/docs.microsoft.com\/aspnet\/core\/grpc\">https:\/\/docs.microsoft.com\/aspnet\/core\/grpc<\/a> \u9605\u8bfb\u6709\u5173 gRPC \u7684\u4fe1\u606f \uff0c\u5e76\u5728 Valerio De Sanctis\uff08Manning\uff0c2023 \u5e74\uff09\u64b0\u5199\u7684\u4f7f\u7528 ASP.NET Core \u6784\u5efa Web API \u4e2d\u4e86\u89e3 GraphQL\u3002<\/p>\n<p>Whether you need or want to create an HTTP API for your ASP.NET Core application depends on the type of application you want to build. Perhaps you\u2019re familiar with client-side frameworks, or maybe you need to develop a mobile application, or you already have an SPA build pipeline configured. In each case, you\u2019ll most likely want to add HTTP APIs for the client apps to access your application.<\/p>\n<p>\u60a8\u662f\u5426\u9700\u8981\u6216\u60f3\u8981\u4e3a ASP.NET Core \u5e94\u7528\u7a0b\u5e8f\u521b\u5efa HTTP API \u53d6\u51b3\u4e8e\u8981\u6784\u5efa\u7684\u5e94\u7528\u7a0b\u5e8f\u7c7b\u578b\u3002\u4e5f\u8bb8\u60a8\u719f\u6089\u5ba2\u6237\u7aef\u6846\u67b6\uff0c\u6216\u8005\u60a8\u9700\u8981\u5f00\u53d1\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\uff0c\u6216\u8005\u60a8\u5df2\u7ecf\u914d\u7f6e\u4e86 SPA \u6784\u5efa\u7ba1\u9053\u3002\u5728\u6bcf\u79cd\u60c5\u51b5\u4e0b\uff0c\u60a8\u5f88\u53ef\u80fd\u5e0c\u671b\u4e3a\u5ba2\u6237\u7aef\u5e94\u7528\u7a0b\u5e8f\u6dfb\u52a0 HTTP API \u4ee5\u8bbf\u95ee\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>One selling point for using an HTTP API is that it can serve as a generalized backend for all your client applications. You could start by building a client-side application that uses an HTTP API. Later, you could add a mobile app that uses the same HTTP API, making little or no modification to your ASP.NET Core code.<\/p>\n<p>\u4f7f\u7528 HTTP API \u7684\u4e00\u4e2a\u5356\u70b9\u662f\u5b83\u53ef\u4ee5\u7528\u4f5c\u6240\u6709\u5ba2\u6237\u7aef\u5e94\u7528\u7a0b\u5e8f\u7684\u901a\u7528\u540e\u7aef\u3002\u60a8\u53ef\u4ee5\u4ece\u6784\u5efa\u4f7f\u7528 HTTP API \u7684\u5ba2\u6237\u7aef\u5e94\u7528\u7a0b\u5e8f\u5f00\u59cb\u3002\u7a0d\u540e\uff0c\u60a8\u53ef\u4ee5\u6dfb\u52a0\u4f7f\u7528\u76f8\u540c HTTP API \u7684\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\uff0c\u5bf9 ASP.NET Core \u4ee3\u7801\u8fdb\u884c\u5c11\u91cf\u4fee\u6539\u6216\u4e0d\u8fdb\u884c\u4fee\u6539\u3002<\/p>\n<p>If you\u2019re new to web development, HTTP APIs can also be easier to understand initially, as they typically return only JSON. Part 1 of this book focuses on minimal APIs so that you can focus on the mechanics of ASP.NET Core without needing to write HTML or CSS.<\/p>\n<p>\u5982\u679c\u60a8\u4e0d\u719f\u6089 Web \u5f00\u53d1\uff0cHTTP API \u6700\u521d\u4e5f\u66f4\u5bb9\u6613\u7406\u89e3\uff0c\u56e0\u4e3a\u5b83\u4eec\u901a\u5e38\u53ea\u8fd4\u56de JSON\u3002\u672c\u4e66\u7684\u7b2c 1 \u90e8\u5206\u91cd\u70b9\u4ecb\u7ecd\u6700\u5c11\u7684 API\uff0c\u4ee5\u4fbf\u60a8\u53ef\u4ee5\u4e13\u6ce8\u4e8e ASP.NET Core \u7684\u673a\u5236\uff0c\u800c\u65e0\u9700\u7f16\u5199 HTML \u6216 CSS\u3002<\/p>\n<p>In part 3, you\u2019ll learn how to use Razor Pages to create server-rendered applications instead of minimal APIs. Server-rendered applications can be highly productive. They\u2019re generally recommended when you have no need to call your application from outside a web browser or when you don\u2019t want or need to make the effort of configuring a client-side application.<\/p>\n<p>\u5728\u7b2c 3 \u90e8\u5206\u4e2d\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u4f7f\u7528 Razor Pages \u521b\u5efa\u670d\u52a1\u5668\u5448\u73b0\u7684\u5e94\u7528\u7a0b\u5e8f\uff0c\u800c\u4e0d\u662f\u6700\u5c11\u7684 API\u3002\u670d\u52a1\u5668\u6e32\u67d3\u7684\u5e94\u7528\u7a0b\u5e8f\u53ef\u4ee5\u975e\u5e38\u9ad8\u6548\u3002\u5f53\u60a8\u4e0d\u9700\u8981\u4ece Web \u6d4f\u89c8\u5668\u5916\u90e8\u8c03\u7528\u5e94\u7528\u7a0b\u5e8f\uff0c\u6216\u8005\u5f53\u60a8\u4e0d\u60f3\u6216\u4e0d\u9700\u8981\u914d\u7f6e\u5ba2\u6237\u7aef\u5e94\u7528\u7a0b\u5e8f\u65f6\uff0c\u901a\u5e38\u5efa\u8bae\u4f7f\u7528\u5b83\u4eec\u3002<\/p>\n<p><b>NOTE<\/b>  Although there\u2019s been an industry shift toward client-side frameworks, server-side rendering using Razor is still relevant. Which approach you choose depends largely on your preference for building HTML applications in the traditional manner versus using JavaScript (or Blazor!) on the client.<br \/>\n\u6ce8\u610f\uff1a \u5c3d\u7ba1\u884c\u4e1a\u5df2\u7ecf\u8f6c\u5411\u5ba2\u6237\u7aef\u6846\u67b6\uff0c\u4f46\u4f7f\u7528 Razor \u7684\u670d\u52a1\u5668\u7aef\u6e32\u67d3\u4ecd\u7136\u5f88\u91cd\u8981\u3002\u9009\u62e9\u54ea\u79cd\u65b9\u6cd5\u5728\u5f88\u5927\u7a0b\u5ea6\u4e0a\u53d6\u51b3\u4e8e\u60a8\u5728\u4f20\u7edf\u65b9\u5f0f\u4e0e\u5728\u5ba2\u6237\u7aef\u4e0a\u4f7f\u7528 JavaScript\uff08\u6216 Blazor\uff09\u7684\u6bd4\u8f83\u3002<\/p>\n<p>Having said that, whether to use HTTP APIs in your application isn\u2019t something you necessarily have to worry about ahead of time. You can always add them to an ASP.NET Core app later in development, as the need arises.<\/p>\n<p>\u8bdd\u867d\u5982\u6b64\uff0c\u662f\u5426\u5728\u5e94\u7528\u7a0b\u5e8f\u4e2d\u4f7f\u7528 HTTP API \u5e76\u4e0d\u662f\u60a8\u5fc5\u987b\u63d0\u524d\u62c5\u5fc3\u7684\u4e8b\u60c5\u3002\u60a8\u59cb\u7ec8\u53ef\u4ee5\u5728\u4ee5\u540e\u7684\u5f00\u53d1\u8fc7\u7a0b\u4e2d\u6839\u636e\u9700\u8981\u5c06\u5b83\u4eec\u6dfb\u52a0\u5230 ASP.NET Core \u5e94\u7528\u7a0b\u5e8f\u4e2d\u3002<\/p>\n<blockquote>\n<p>SPAs with ASP.NET Core<br \/>\n\u5177\u6709 ASP.NET Core \u7684 SPA<br \/>\nThe cross-platform, lightweight design of ASP.NET Core means that it lends itself well to acting as a backend for your SPA framework of choice. Given the focus of this book and the broad scope of SPAs in general, I won\u2019t be looking at Angular, React, or other SPAs here. Instead, I suggest checking out the resources appropriate to your chosen SPA. Books are available from Manning for all the common client-side JavaScript frameworks, as well as Blazor:<br \/>\nASP.NET Core \u7684\u8de8\u5e73\u53f0\u8f7b\u91cf\u7ea7\u8bbe\u8ba1\u610f\u5473\u7740\u5b83\u975e\u5e38\u9002\u5408\u5145\u5f53\u6240\u9009 SPA \u6846\u67b6\u7684\u540e\u7aef\u3002\u9274\u4e8e\u672c\u4e66\u7684\u91cd\u70b9\u548c SPA \u7684\u5e7f\u6cdb\u8303\u56f4\uff0c\u6211\u4e0d\u4f1a\u5728\u8fd9\u91cc\u8ba8\u8bba Angular\u3001React \u6216\u5176\u4ed6 SPA\u3002\u76f8\u53cd\uff0c\u6211\u5efa\u8bae\u67e5\u770b\u9002\u7528\u4e8e\u60a8\u9009\u62e9\u7684 SPA \u7684\u8d44\u6e90\u3002Manning \u63d0\u4f9b\u4e86\u9002\u7528\u4e8e\u6240\u6709\u5e38\u89c1\u5ba2\u6237\u7aef JavaScript \u6846\u67b6\u4ee5\u53ca Blazor \u7684\u4e66\u7c4d\uff1a<br \/>\n\u00b7   React in Action, by Mark Tielens Thomas (Manning, 2018)<br \/>\nReact in Action\uff0c\u4f5c\u8005\uff1aMark Tielens Thomas\uff08\u66fc\u5b81\u51fa\u7248\u793e\uff0c2018 \u5e74\uff09<br \/>\n\u00b7   Angular in Action, by Jeremy Wilken (Manning, 2018)<br \/>\nAngular in Action\uff0c\u4f5c\u8005\uff1aJeremy Wilken\uff08\u66fc\u5b81\uff0c2018 \u5e74\uff09<br \/>\n\u00b7   Vue.js in Action, by Erik Hanchett with Benjamin Listwon (Manning, 2018)<br \/>\nVue.js in Action\uff0c\u57c3\u91cc\u514b\u00b7\u6c49\u5207\u7279 \uff08Erik Hanchett\uff09 \u548c\u672c\u6770\u660e\u00b7\u5229\u65af\u7279\u65fa \uff08Benjamin Listwon\uff09 \u8457\uff08\u66fc\u5b81\u51fa\u7248\u793e\uff0c2018 \u5e74\uff09<br \/>\n\u00b7   Blazor in Action, by Chris Sainty (Manning, 2022)<br \/>\nBlazor in Action\uff0c\u4f5c\u8005\uff1aChris Sainty\uff08\u66fc\u5b81\uff0c2022 \u5e74\uff09<\/p>\n<\/blockquote>\n<p>After you\u2019ve established that you need an HTTP API for your application, creating one is easy, as it\u2019s the default application type in ASP.NET Core! In the next section we look at various ways you can create minimal API endpoints and ways to handle multiple HTTP verbs.<\/p>\n<p>\u5728\u60a8\u786e\u5b9a\u5e94\u7528\u7a0b\u5e8f\u9700\u8981 HTTP API \u540e\uff0c\u521b\u5efa\u4e00\u4e2a API \u5f88\u5bb9\u6613\uff0c\u56e0\u4e3a\u5b83\u662f ASP.NET Core \u4e2d\u7684\u9ed8\u8ba4\u5e94\u7528\u7a0b\u5e8f\u7c7b\u578b\uff01\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u521b\u5efa\u6700\u5c0f API \u7aef\u70b9\u7684\u5404\u79cd\u65b9\u6cd5\u4ee5\u53ca\u5904\u7406\u591a\u4e2a HTTP \u52a8\u8bcd\u7684\u65b9\u6cd5\u3002<\/p>\n<h2>5.2 Defining minimal API endpoints<\/h2>\n<h2>5.2 \u5b9a\u4e49\u6700\u5c0f API \u7aef\u70b9<\/h2>\n<p>Chapters 3 and 4 gave you an introduction to basic minimal API endpoints. In this section, we\u2019ll build on those basic apps to show how you can handle multiple HTTP verbs and explore various ways to write your endpoint handlers.<\/p>\n<p>\u7b2c 3 \u7ae0\u548c\u7b2c 4 \u7ae0\u4ecb\u7ecd\u4e86\u57fa\u672c\u7684\u6700\u5c0f API \u7aef\u70b9\u3002\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4ee5\u8fd9\u4e9b\u57fa\u672c\u5e94\u7528\u7a0b\u5e8f\u4e3a\u57fa\u7840\uff0c\u5c55\u793a\u5982\u4f55\u5904\u7406\u591a\u4e2a HTTP \u52a8\u8bcd\uff0c\u5e76\u63a2\u7d22\u7f16\u5199\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u7684\u5404\u79cd\u65b9\u6cd5\u3002<\/p>\n<h2>5.2.1 Extracting values from the URL with routing<\/h2>\n<h2>5.2.1 \u4f7f\u7528\u8def\u7531 \u4ece URL \u4e2d\u63d0\u53d6\u503c<\/h2>\n<p>You\u2019ve seen several minimal API applications in this book, but so far, all the examples have used fixed paths to define the APIs, as in this example:<\/p>\n<p>\u60a8\u5728\u672c\u4e66\u4e2d\u5df2\u7ecf\u770b\u5230\u4e86\u51e0\u4e2a\u6700\u5c0f\u7684 API \u5e94\u7528\u7a0b\u5e8f\uff0c\u4f46\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u6240\u6709\u793a\u4f8b\u90fd\u4f7f\u7528\u56fa\u5b9a\u8def\u5f84\u6765\u5b9a\u4e49 API\uff0c\u5982\u4ee5\u4e0b\u793a\u4f8b\u6240\u793a\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">app.MapGet(&quot;\/&quot;, () =&gt; &quot;Hello World!&quot;);\napp.MapGet(&quot;\/person&quot;, () =&gt; new Person(&quot;Andrew&quot;, &quot;Lock&quot;));\n<\/pre>\n<p>These two APIs correspond to the paths \/ and \/person, respectively. This basic functionality is useful, but typically you need some of your APIs to be more dynamic. It\u2019s unlikely, for example, that the \/person API would be useful in practice, as it always returns the same Person object. What might be more useful is an API to which you can provide the user\u2019s first name, and the API returns all the users with that name.<\/p>\n<p>\u8fd9\u4e24\u4e2a API \u5206\u522b\u5bf9\u5e94\u4e8e\u8def\u5f84 \/ \u548c \/person\u3002\u6b64\u57fa\u672c\u529f\u80fd\u5f88\u6709\u7528\uff0c\u4f46\u901a\u5e38\u9700\u8981\u67d0\u4e9b API \u66f4\u52a0\u52a8\u6001\u3002\u4f8b\u5982\uff0c\/person API \u5728\u5b9e\u8df5\u4e2d\u4e0d\u592a\u53ef\u80fd\u6709\u7528\uff0c\u56e0\u4e3a\u5b83\u603b\u662f\u8fd4\u56de\u76f8\u540c\u7684 Person \u5bf9\u8c61\u3002\u53ef\u80fd\u66f4\u6709\u7528\u7684\u662f API\uff0c\u60a8\u53ef\u4ee5\u5411\u5176\u63d0\u4f9b\u7528\u6237\u7684\u540d\u5b57\uff0c\u5e76\u4e14 API \u4f1a\u8fd4\u56de\u5177\u6709\u8be5\u540d\u79f0\u7684\u6240\u6709\u7528\u6237\u3002<\/p>\n<p>You can achieve this goal by using parameterized routes for your API definitions. You can create a parameter in a minimal API route using the expression {someValue}, where someValue is any name you choose. The value will be extracted from the request URL\u2019s path and can be used in the lambda function endpoint.<\/p>\n<p>\u60a8\u53ef\u4ee5\u901a\u8fc7\u5bf9 API \u5b9a\u4e49\u4f7f\u7528\u53c2\u6570\u5316\u8def\u7531\u6765\u5b9e\u73b0\u6b64\u76ee\u6807\u3002\u60a8\u53ef\u4ee5\u5728\u6700\u5c0f\u4f7f\u7528\u8868\u8fbe\u5f0f {someValue} \u7684 API \u8def\u7531\uff0c\u5176\u4e2d someValue \u662f\u60a8\u9009\u62e9\u7684\u4efb\u4f55\u540d\u79f0\u3002\u8be5\u503c\u5c06\u4ece\u8bf7\u6c42 URL \u7684\u8def\u5f84\u4e2d\u63d0\u53d6\uff0c\u5e76\u53ef\u5728 lambda \u51fd\u6570\u7ec8\u7aef\u8282\u70b9\u4e2d\u4f7f\u7528\u3002<\/p>\n<p><b>NOTE<\/b>  I introduce only the basics of extracting values from routes in this chapter. You\u2019ll learn a lot more about routing in chapter 6, including why we use routing and how it fits into the ASP.NET Core pipeline, as well as the syntax you can use.<br \/>\n\u6ce8\u610f\uff1a \u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u53ea\u4ecb\u7ecd\u4e86\u4ece routes \u4e2d\u63d0\u53d6\u503c\u7684\u57fa\u7840\u77e5\u8bc6\u3002\u5728\u7b2c 6 \u7ae0\u4e2d\uff0c\u60a8\u5c06\u4e86\u89e3\u6709\u5173 routing \u7684\u66f4\u591a\u4fe1\u606f\uff0c\u5305\u62ec\u6211\u4eec\u4e3a\u4ec0\u4e48\u4f7f\u7528 routing\u3001\u5b83\u5982\u4f55\u9002\u5e94 ASP.NET Core \u7ba1\u9053\uff0c\u4ee5\u53ca\u60a8\u53ef\u4ee5\u4f7f\u7528\u7684\u8bed\u6cd5\u3002<\/p>\n<p>If you create an API using the route template \/person\/{name}, for example, and send a request to the path \/person\/Andrew, the name parameter will have the value &quot;Andrew&quot;. You can use this feature to build more useful APIs, such as the one shown in the following listing.<\/p>\n<p>\u5982\u679c\u60a8\u4f7f\u7528\u8def\u7531\u6a21\u677f\u521b\u5efa API \/person\/{name} \u5e76\u5411\u8def\u5f84 \/person\/Andrew \u53d1\u9001\u8bf7\u6c42\uff0c\u5219 name \u53c2\u6570\u5c06\u5177\u6709\u503c \u201cAndrew\u201d\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u6b64\u529f\u80fd\u6765\u6784\u5efa\u66f4\u6709\u7528\u7684 API\uff0c\u4f8b\u5982\u4ee5\u4e0b\u6e05\u5355\u4e2d\u6240\u793a\u7684 API\u3002<\/p>\n<p>Listing 5.1 A minimal API that uses a value from the URL<br \/>\n\u6e05\u5355 5.1 \u4e00\u4e2a\u6700\u5c0f\u7684 API\uff0c\u5b83\u4f7f\u7528\u7f51\u5740<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\nvar people = new List&lt;Person&gt;                                    \/\/ \u2776\n{                                                                \/\/ \u2776\n    new(&quot;Tom&quot;, &quot;Hanks&quot;),                                         \/\/ \u2776\n    new(&quot;Denzel&quot;, &quot;Washington&quot;),                                 \/\/ \u2776\n    new(&quot;Leondardo&quot;, &quot;DiCaprio&quot;),                                \/\/ \u2776\n    new(&quot;Al&quot;, &quot;Pacino&quot;),                                         \/\/ \u2776\n    new(&quot;Morgan&quot;, &quot;Freeman&quot;),                                    \/\/ \u2776\n};                                                               \/\/ \u2776\napp.MapGet(&quot;\/person\/{name}&quot;, (string name) =&gt;                    \/\/ \u2777\npeople.Where(p =&gt; p.FirstName.StartsWith(name)));                \/\/ \u2778\n\napp.Run();\n\npublic record Person(string FirstName, string LastName);\n<\/pre>\n<p>\u2776 Creates a list of people as the data for the API<br \/>\n\u521b\u5efa\u4eba\u5458\u5217\u8868\u4f5c\u4e3a API \u7684\u6570\u636e<\/p>\n<p>\u2777 The route is parameterized to extract the name from the URL.<br \/>\n\u8def\u7531\u53c2\u6570\u5316\u4ee5\u4ece URL \u4e2d\u63d0\u53d6\u540d\u79f0\u3002<\/p>\n<p>\u2778 The extracted value can be injected into the lambda handler.<br \/>\n\u63d0\u53d6\u7684\u503c\u53ef\u4ee5\u6ce8\u5165\u5230 lambda \u5904\u7406\u7a0b\u5e8f\u4e2d\u3002<\/p>\n<p>If you send a request to \/person\/Al for the app defined in listing 5.1, the name parameter will have the value &quot;Al&quot;, and the API will return the following JSON:<br \/>\n\u5982\u679c\u60a8\u5411 \/person\/Al \u53d1\u9001\u5217\u8868 5.1 \u4e2d\u5b9a\u4e49\u7684\u5e94\u7528\u7a0b\u5e8f\u7684\u8bf7\u6c42\uff0c\u5219 name \u53c2\u6570\u7684\u503c\u5c06\u4e3a \u201cAl\u201d\uff0c\u5e76\u4e14 API \u5c06\u8fd4\u56de\u4ee5\u4e0b JSON\uff1a<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n&#x5B;{&quot;firstName&quot;:&quot;Al&quot;,&quot;lastName&quot;:&quot;Pacino&quot;}]\n<\/pre>\n<p><b>NOTE<\/b>  By default, minimal APIs serialize C# objects to JSON. You\u2019ll see how to return other types of results in section 5.3.<br \/>\n\u6ce8\u610f\uff1a \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u6700\u5c0f API \u5c06 C# \u5bf9\u8c61\u5e8f\u5217\u5316\u4e3a JSON\u3002\u60a8\u5c06\u5728 5.3 \u8282 \u4e2d\u770b\u5230\u5982\u4f55\u8fd4\u56de\u5176\u4ed6\u7c7b\u578b\u7684\u7ed3\u679c\u3002<\/p>\n<p>The ASP.NET Core routing system is quite powerful, and we\u2019ll explore it in more detail in chapter 6. But with this simple capability, you can already build more complex applications.<br \/>\nASP.NET Core \u8def\u7531\u7cfb\u7edf\u975e\u5e38\u5f3a\u5927\uff0c\u6211\u4eec\u5c06\u5728\u7b2c 6 \u7ae0\u4e2d\u66f4\u8be6\u7ec6\u5730\u63a2\u8ba8\u5b83\u3002\u4f46\u501f\u52a9\u8fd9\u4e2a\u7b80\u5355\u7684\u529f\u80fd\uff0c\u60a8\u5df2\u7ecf\u53ef\u4ee5\u6784\u5efa\u66f4\u590d\u6742\u7684\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<h3>5.2.2 Mapping verbs to endpoints<\/h3>\n<h3>5.2.2 \u5c06\u52a8\u8bcd\u6620\u5c04\u5230\u7aef\u70b9<\/h3>\n<p>So far in this book we\u2019ve defined all our minimal API endpoints by using the MapGet() function. This function matches requests that use the GET HTTP verb. GET is the most-used verb; it\u2019s what a browser uses when you enter a URL in the address bar of your browser or follow a link on a web page.<\/p>\n<p>\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u5728\u672c\u4e66\u4e2d\uff0c\u6211\u4eec\u5df2\u7ecf\u4f7f\u7528 MapGet\uff08\uff09 \u51fd\u6570\u5b9a\u4e49\u4e86\u6240\u6709\u6700\u5c0f API \u7aef\u70b9 \u3002\u6b64\u51fd\u6570\u5339\u914d\u4f7f\u7528 GET HTTP \u52a8\u8bcd\u7684\u8bf7\u6c42\u3002GET \u662f\u6700\u5e38\u7528\u7684\u52a8\u8bcd;\u5f53\u60a8\u5728\u6d4f\u89c8\u5668\u7684\u5730\u5740\u680f\u4e2d\u8f93\u5165 URL \u6216\u70b9\u51fb\u7f51\u9875\u4e0a\u7684\u94fe\u63a5\u65f6\uff0c\u6d4f\u89c8\u5668\u4f1a\u4f7f\u7528\u5b83\u3002<\/p>\n<p>You should use GET only to get data from the server, however. You should never use it to send data or to change data on the server. Instead, you should use an HTTP verb such as POST or DELETE. You generally can\u2019t use these verbs by navigating web pages in the browser, but they\u2019re easy to send from a client-side SPA or mobile app.<\/p>\n<p>\u4f46\u662f\uff0c\u60a8\u5e94\u8be5\u53ea\u4f7f\u7528 GET \u4ece\u670d\u52a1\u5668\u83b7\u53d6\u6570\u636e\u3002\u5207\u52ff\u4f7f\u7528\u5b83\u6765\u53d1\u9001\u6570\u636e\u6216\u66f4\u6539\u670d\u52a1\u5668\u4e0a\u7684\u6570\u636e\u3002 \u76f8\u53cd\uff0c\u60a8\u5e94\u8be5\u4f7f\u7528 HTTP \u52a8\u8bcd\uff0c\u4f8b\u5982 POST \u6216 DELETE\u3002\u60a8\u901a\u5e38\u4e0d\u80fd\u4f7f\u7528\u8fd9\u4e9b\u52a8\u8bcd\uff0c\u4f46\u5b83\u4eec\u5f88\u5bb9\u6613\u4ece\u5ba2\u6237\u7aef SPA \u6216\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\u53d1\u9001\u3002<\/p>\n<p><b>TIP<\/b>  If you\u2019re new to web programming or are looking for a refresher, Mozilla Developer Network (MDN), maker of the Firefox web browser, has a good introduction to HTTP at <a href=\"http:\/\/mng.bz\/KeMK\">http:\/\/mng.bz\/KeMK<\/a>.<br \/>\n\u63d0\u793a\uff1a \u5982\u679c\u60a8\u662f Web \u7f16\u7a0b\u7684\u65b0\u624b\u6216\u6b63\u5728\u5bfb\u627e\u590d\u4e60\u8005\uff0cFirefox Web \u6d4f\u89c8\u5668\u7684\u5236\u9020\u5546 Mozilla Developer Network \uff08MDN\uff09 \u5728 <a href=\"http:\/\/mng.bz\/KeMK\">http:\/\/mng.bz\/KeMK<\/a> \u4e0a\u5bf9 HTTP \u8fdb\u884c\u4e86\u5f88\u597d\u7684\u4ecb\u7ecd\u3002<\/p>\n<p>In theory, each of the HTTP verbs has a well-defined purpose, but in practice, you may see apps that only ever use POST and GET. This is often fine for server-rendered applications like Razor Pages, as it\u2019s typically simpler, but if you\u2019re creating an API, I recommend that you use the HTTP verbs with the appropriate semantics wherever possible.<\/p>\n<p>\u7406\u8bba\u4e0a\uff0c\u6bcf\u4e2a HTTP \u52a8\u8bcd\u90fd\u6709\u4e00\u4e2a\u660e\u786e\u5b9a\u4e49\u7684\u7528\u9014\uff0c\u4f46\u5728\u5b9e\u8df5\u4e2d\uff0c\u60a8\u53ef\u80fd\u4f1a\u770b\u5230\u4ec5\u4f7f\u7528 POST \u548c GET \u7684\u5e94\u7528\u7a0b\u5e8f\u3002\u8fd9\u901a\u5e38\u9002\u7528\u4e8e\u670d\u52a1\u5668\u5448\u73b0\u7684\u5e94\u7528\u7a0b\u5e8f\uff08\u5982 Razor Pages\uff09\uff0c\u56e0\u4e3a\u5b83\u901a\u5e38\u66f4\u7b80\u5355\uff0c\u4f46\u5982\u679c\u60a8\u6b63\u5728\u521b\u5efa API\uff0c\u6211\u5efa\u8bae\u60a8\u5c3d\u53ef\u80fd\u4f7f\u7528\u5177\u6709\u9002\u5f53\u8bed\u4e49\u7684 HTTP \u52a8\u8bcd\u3002<\/p>\n<p>You can define endpoints for other verbs with minimal APIs by using the appropriate Map<em> functions. To map a POST endpoint, for example, you\u2019d use MapPost(). Table 5.1 shows the minimal API Map<\/em> methods available, the corresponding HTTP verbs, and the typical semantic expectations of each verb on the types of operations that the API performs.<\/p>\n<p>\u60a8\u53ef\u4ee5\u4f7f\u7528\u9002\u5f53\u7684 Map<em> \u51fd\u6570\uff0c\u4f7f\u7528\u6700\u5c11\u7684 API \u4e3a\u5176\u4ed6\u8c13\u8bcd\u5b9a\u4e49\u7aef\u70b9\u3002\u4f8b\u5982\uff0c\u8981\u6620\u5c04 POST \u7ec8\u7aef\u8282\u70b9\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528 MapPost\uff08\uff09\u3002\u8868 5.1 \u663e\u793a\u4e86\u53ef\u7528\u7684\u6700\u5c0f API Map<\/em> \u65b9\u6cd5\u3001\u76f8\u5e94\u7684 HTTP \u52a8\u8bcd\u4ee5\u53ca\u6bcf\u4e2a\u52a8\u8bcd\u5bf9 API \u6267\u884c\u7684\u4f5c\u7c7b\u578b\u7684\u5178\u578b\u8bed\u4e49\u671f\u671b\u3002<\/p>\n<p>Table 5.1 The minimal API map endpoints and the corresponding HTML verbs<br \/>\n\u8868 5.1 \u6700\u5c0f API \u6620\u5c04\u7aef\u70b9\u548c\u76f8\u5e94\u7684 HTML \u52a8\u8bcd<\/p>\n<table>\n<thead>\n<tr>\n<th>Method<\/th>\n<th>HTTP verb<\/th>\n<th>Expected operation<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>MapGet(path, handler)<\/td>\n<td>GET<\/td>\n<td>Fetch data only; no modification of state. May be safe to cache. <br \/>\u4ec5\u83b7\u53d6\u6570\u636e;\u4e0d\u4fee\u6539\u72b6\u6001\u3002\u53ef\u4ee5\u5b89\u5168\u5730\u7f13\u5b58\u3002<\/td>\n<\/tr>\n<tr>\n<td>MapPost(path, handler)<\/td>\n<td>POST<\/td>\n<td>Create a new resource. <br \/>\u521b\u5efa\u65b0\u8d44\u6e90\u3002<\/td>\n<\/tr>\n<tr>\n<td>MapPut(path, handler)<\/td>\n<td>PUT<\/td>\n<td>Create or replace an existing resource.<br \/>\u521b\u5efa\u6216\u66ff\u6362\u73b0\u6709\u8d44\u6e90\u3002<\/td>\n<\/tr>\n<tr>\n<td>MapDelete(path, handler)<\/td>\n<td>DELETE<\/td>\n<td>Delete the given resource.  <br \/>\u5220\u9664\u7ed9\u5b9a\u7684\u8d44\u6e90\u3002<\/td>\n<\/tr>\n<tr>\n<td>MapPatch(path, handler)<\/td>\n<td>PATCH<\/td>\n<td>Modify the given resource. <br \/>\u4fee\u6539\u7ed9\u5b9a\u7684\u8d44\u6e90\u3002<\/td>\n<\/tr>\n<tr>\n<td>MapMethods(path, methods,handler)<\/td>\n<td>Multiple verbs<\/td>\n<td>Multiple operations <br \/>\u591a\u4e2a\u52a8\u4f5c<\/td>\n<\/tr>\n<tr>\n<td>Map(path, handler)<\/td>\n<td>All verbs<\/td>\n<td>Multiple operations <br \/>\u591a\u4e2a\u52a8\u4f5c<\/td>\n<\/tr>\n<tr>\n<td>MapFallback(handler)<\/td>\n<td>All verbs<\/td>\n<td>Useful for SPA fallback routes <br \/>\u5bf9 SPA \u56de\u9000\u8def\u7531\u5f88\u6709\u7528\u3002<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>RESTful applications (as described in chapter 2) typically stick close to these verb uses where possible, but some of the actual implementations can differ, and people can easily get caught up in pedantry. Generally, if you stick to the expected operations described in table 5.1, you\u2019ll create a more understandable interface for consumers of the API.<\/p>\n<p>RESTful \u5e94\u7528\u7a0b\u5e8f\uff08\u5982\u7b2c 2 \u7ae0\u6240\u8ff0\uff09\u901a\u5e38\u5c3d\u53ef\u80fd\u5730\u4f7f\u7528\u8fd9\u4e9b\u52a8\u8bcd\u7528\u6cd5\uff0c\u4f46\u4e00\u4e9b\u5b9e\u9645\u5b9e\u73b0\u53ef\u80fd\u4f1a\u6709\u6240\u4e0d\u540c\uff0c\u4eba\u4eec\u5f88\u5bb9\u6613\u9677\u5165\u8fc2\u8150\u3002\u901a\u5e38\uff0c\u5982\u679c\u60a8\u575a\u6301\u4f7f\u7528 \u8868 5.1 \u4e2d\u63cf\u8ff0\u7684\u9884\u671f\u4f5c\uff0c\u60a8\u5c06\u4e3a API \u7684\u4f7f\u7528\u8005\u521b\u5efa\u4e00\u4e2a\u66f4\u6613\u4e8e\u7406\u89e3\u7684\u754c\u9762\u3002<\/p>\n<p><b>NOTE<\/b>  You may notice that if you use the MapMethods() and Map() methods listed in table 5.1, your API probably doesn\u2019t correspond to the expected operations of the HTTP verbs it supports, so I avoid these methods where possible. MapFallback() doesn\u2019t have a path and is called only if no other endpoint matches. Fallback routes can be useful when you have a SPA that uses client-side routing. See <a href=\"http:\/\/mng.bz\/9DMl\">http:\/\/mng.bz\/9DMl<\/a> for a description of the problem and an alternative solution.<br \/>\n\u6ce8\u610f\uff1a \u60a8\u53ef\u80fd\u4f1a\u6ce8\u610f\u5230\uff0c\u5982\u679c\u4f7f\u7528\u8868 5.1 \u4e2d\u5217\u51fa\u7684 MapMethods\uff08\uff09 \u548c Map\uff08\uff09 \u65b9\u6cd5\uff0c\u5219 API \u53ef\u80fd\u4e0e\u5b83\u652f\u6301\u7684 HTTP \u52a8\u8bcd\u7684\u9884\u671f\u4f5c\u4e0d\u5bf9\u5e94\uff0c\u56e0\u6b64\u6211\u5c3d\u53ef\u80fd\u907f\u514d\u4f7f\u7528\u8fd9\u4e9b\u65b9\u6cd5\u3002MapFallback\uff08\uff09 \u6ca1\u6709\u8def\u5f84\uff0c\u4ec5\u5f53\u6ca1\u6709\u5176\u4ed6\u7ec8\u7aef\u8282\u70b9\u5339\u914d\u65f6\u624d\u4f1a\u8c03\u7528\u3002\u5f53\u60a8\u62e5\u6709\u4f7f\u7528\u5ba2\u6237\u7aef\u8def\u7531\u7684 SPA \u65f6\uff0c\u56de\u9000\u8def\u7531\u53ef\u80fd\u5f88\u6709\u7528\u3002\u6709\u5173\u95ee\u9898\u7684\u8bf4\u660e\u548c\u66ff\u4ee3\u89e3\u51b3\u65b9\u6848\uff0c\u8bf7\u53c2\u9605 <a href=\"http:\/\/mng.bz\/9DMl\">http:\/\/mng.bz\/9DMl<\/a>\u3002<\/p>\n<p>As I mentioned at the start of section 5.2.2, testing APIs that use verbs other than GET is tricky in the browser. You need to use a tool that allows sending arbitrary requests such as Postman (<a href=\"https:\/\/www.postman.com\">https:\/\/www.postman.com<\/a>) or the HTTP Client plugin in JetBrains Rider. In chapter 11 you\u2019ll learn how to use a tool called Swagger UI to visualize and test your APIs.<\/p>\n<p>\u6b63\u5982\u6211\u5728 5.2.2 \u8282\u5f00\u5934\u63d0\u5230\u7684\uff0c\u5728\u6d4f\u89c8\u5668\u4e2d\u6d4b\u8bd5\u4f7f\u7528 GET \u4ee5\u5916\u7684\u52a8\u8bcd\u7684 API \u662f\u5f88\u68d8\u624b\u7684\u3002\u60a8\u9700\u8981\u4f7f\u7528\u5141\u8bb8\u53d1\u9001\u4efb\u610f\u8bf7\u6c42\u7684\u5de5\u5177\uff0c\u4f8b\u5982 Postman \uff08<a href=\"https:\/\/www.postman.com\">https:\/\/www.postman.com<\/a>\uff09 \u6216 JetBrains Rider \u4e2d\u7684 HTTP Client \u63d2\u4ef6\u3002\u5728\u7b2c 11 \u7ae0\u4e2d\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u4f7f\u7528\u540d\u4e3a Swagger UI \u7684\u5de5\u5177\u6765\u53ef\u89c6\u5316\u548c\u6d4b\u8bd5\u60a8\u7684 API\u3002<\/p>\n<p><b>TIP<\/b>  The HTTP client plugin in JetBrains Rider makes it easy to craft HTTP requests from inside your API, and even discovers all the endpoints in your application automatically, making them easier to test. You can read more about it at <a href=\"https:\/\/www.jetbrains.com\/help\/rider\/Http_client_in_product__code_editor.html\">https:\/\/www.jetbrains.com\/help\/rider\/Http_client_in_product__code_editor.html<\/a>.<br \/>\n\u63d0\u793a\uff1a JetBrains Rider \u4e2d\u7684 HTTP \u5ba2\u6237\u7aef\u63d2\u4ef6\u53ef\u4ee5\u8f7b\u677e\u5730\u4ece API \u5185\u90e8\u6784\u5efa HTTP \u8bf7\u6c42\uff0c\u751a\u81f3\u53ef\u4ee5\u81ea\u52a8\u53d1\u73b0\u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u6240\u6709\u7aef\u70b9\uff0c\u4f7f\u5176\u66f4\u6613\u4e8e\u6d4b\u8bd5\u3002\u60a8\u53ef\u4ee5\u5728 <a href=\"https:\/\/www.jetbrains.com\/help\/rider\/Http_client_in_product__code_editor.html\">https:\/\/www.jetbrains.com\/help\/rider\/Http_client_in_product__code_editor.html<\/a> \u4e0a\u9605\u8bfb\u66f4\u591a\u76f8\u5173\u4fe1\u606f\u3002<\/p>\n<p>As a final note before we move on, it\u2019s worth mentioning the behavior you get when you call a method with the wrong HTTP verb. If you define an API like the one in listing 5.1,and call it by using a POST request to \/person\/Al instead of a GET request, the handler won\u2019t run, and the response you get will have status code 405 Method Not Allowed.<br \/>\n\u5728\u6211\u4eec\u7ee7\u7eed\u4e4b\u524d\uff0c\u6700\u540e\u8981\u6ce8\u610f\u7684\u662f\uff0c\u5f53\u4f60\u4f7f\u7528\u9519\u8bef\u7684 HTTP \u52a8\u8bcd\u8c03\u7528\u65b9\u6cd5\u65f6\uff0c\u4f60\u5f97\u5230\u7684\u884c\u4e3a\u662f\u503c\u5f97\u4e00\u63d0\u7684\u3002\u5982\u679c\u4f60\u5b9a\u4e49\u4e86\u4e00\u4e2a\u7c7b\u4f3c\u4e8e\u6e05\u5355 5.1 \u4e2d\u7684 API\u3002\u5e76\u4f7f\u7528\u5bf9 \/person\/Al \u7684 POST \u8bf7\u6c42\u800c\u4e0d\u662f GET \u8bf7\u6c42\u6765\u8c03\u7528\u5b83\uff0c\u5219\u5904\u7406\u7a0b\u5e8f\u4e0d\u4f1a\u8fd0\u884c\uff0c\u5e76\u4e14\u60a8\u83b7\u5f97\u7684\u54cd\u5e94\u5c06\u5177\u6709\u72b6\u6001\u4ee3\u7801 405 Method Not Allowed\u3002<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapGet(&quot;\/person\/{name}&quot;, (string name) =&gt;\n    people.Where(p =&gt; p.FirstName.StartsWith(name)));\n<\/pre>\n<p><b>TIP<\/b>  You should never see this response when you\u2019re calling the API correctly, so if you receive a 405 response, make sure to check that you\u2019re using the right HTTP verb and the right path. Often when I see a 405, I\u2019ve used the correct verb but made a typo in the URL!<br \/>\n\u63d0\u793a\uff1a \u6b63\u786e\u8c03\u7528 API \u65f6\uff0c\u60a8\u5e94\u8be5\u6c38\u8fdc\u4e0d\u4f1a\u770b\u5230\u6b64\u54cd\u5e94\uff0c\u56e0\u6b64\uff0c\u5982\u679c\u60a8\u6536\u5230 405 \u54cd\u5e94\uff0c\u8bf7\u52a1\u5fc5\u68c0\u67e5\u60a8\u662f\u5426\u4f7f\u7528\u4e86\u6b63\u786e\u7684 HTTP \u52a8\u8bcd\u548c\u6b63\u786e\u7684\u8def\u5f84\u3002\u901a\u5e38\uff0c\u5f53\u6211\u770b\u5230 405 \u65f6\uff0c\u6211\u4f7f\u7528\u4e86\u6b63\u786e\u7684\u52a8\u8bcd\uff0c\u4f46\u5728 URL \u4e2d\u6253\u9519\u4e86\u5b57\uff01<\/p>\n<p>In all the examples in this book so far, you provide a lambda function as the handler for an endpoint. But in section 5.2.3, you\u2019ll see that there are many ways to define the handler.<\/p>\n<p>\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u5728\u672c\u4e66\u7684\u6240\u6709\u793a\u4f8b\u4e2d\uff0c\u60a8\u90fd\u63d0\u4f9b\u4e86\u4e00\u4e2a lambda \u51fd\u6570\u4f5c\u4e3a\u7ec8\u7aef\u8282\u70b9\u7684\u5904\u7406\u7a0b\u5e8f\u3002\u4f46\u662f\u5728 5.2.3 \u8282 \u4e2d\uff0c\u4f60\u4f1a\u770b\u5230\u6709\u5f88\u591a\u65b9\u6cd5\u53ef\u4ee5\u5b9a\u4e49\u5904\u7406\u7a0b\u5e8f\u3002<\/p>\n<h3>5.2.3 Defining route handlers with functions<\/h3>\n<h3>5.2.3 \u4f7f\u7528\u51fd\u6570\u5b9a\u4e49\u8def\u7531\u5904\u7406\u7a0b\u5e8f<\/h3>\n<p>For basic examples, using a lambda function as the handler for an endpoint is often the simplest approach, but you can take many approaches, as shown in listing 5.2. This listing also demonstrates creating a simple CRUD (Create, Read, Update, Delete) API using different HTTP verbs, as discussed in section 5.2.1.<\/p>\n<p>\u5bf9\u4e8e\u57fa\u672c\u793a\u4f8b\uff0c\u4f7f\u7528 lambda \u51fd\u6570\u4f5c\u4e3a\u7ec8\u7aef\u8282\u70b9\u7684\u5904\u7406\u7a0b\u5e8f\u901a\u5e38\u662f\u6700\u7b80\u5355\u7684\u65b9\u6cd5\uff0c\u4f46\u60a8\u53ef\u4ee5\u91c7\u7528\u591a\u79cd\u65b9\u6cd5\uff0c\u5982\u6e05\u5355 5.2 \u6240\u793a\u3002\u6b64\u6e05\u5355\u8fd8\u6f14\u793a\u4e86\u4f7f\u7528\u4e0d\u540c\u7684 HTTP \u52a8\u8bcd\u521b\u5efa\u7b80\u5355\u7684 CRUD\uff08\u521b\u5efa\u3001\u8bfb\u53d6\u3001\u66f4\u65b0\u3001\u5220\u9664\uff09API\uff0c\u5982 5.2.1 \u8282\u6240\u8ff0\u3002<\/p>\n<p>Listing 5.2 Creating route handlers for a simple CRUD API<br \/>\n\u6e05\u5355 5.2 \u4e3a\u7b80\u5355\u7684 CRUD API \u521b\u5efa\u8def\u7531\u5904\u7406\u7a0b\u5e8f<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\napp.MapGet(&quot;\/fruit&quot;, () =&gt; Fruit.All);                             \/\/ \u2776\n\nvar getFruit = (string id) =&gt; Fruit.All&#x5B;id];                       \/\/ \u2777\napp.MapGet(&quot;\/fruit\/{id}&quot;, getFruit);                               \/\/ \u2777\n\napp.MapPost(&quot;\/fruit\/{id}&quot;, Handlers.AddFruit);                     \/\/ \u2778\n\nHandlers handlers = new();                                         \/\/ \u2779\napp.MapPut(&quot;\/fruit\/{id}&quot;, handlers.ReplaceFruit);                  \/\/ \u2779\n\napp.MapDelete(&quot;\/fruit\/{id}&quot;, DeleteFruit);                         \/\/ \u277a\n\napp.Run();\n\nvoid DeleteFruit(string id)                                        \/\/ \u277a\n{\n    Fruit.All.Remove(id);\n}\n\nrecord Fruit(string Name, int Stock)\n{\n    public static readonly Dictionary&lt;string, Fruit&gt; All = new();\n};\n\nclass Handlers\n{\n    public void ReplaceFruit(string id, Fruit fruit)                \/\/ \u277b\n    {\n        Fruit.All&#x5B;id] = fruit;\n    }\npublic static void AddFruit(string id, Fruit fruit)                 \/\/ \u277c\n    {\n    Fruit.All.Add(id, fruit);\n}\n}\n<\/pre>\n<p>\u2776 Lambda expressions are the simplest but least descriptive way to create a handler.<br \/>\nLambda \u8868\u8fbe\u5f0f\u662f\u521b\u5efa\u5904\u7406\u7a0b\u5e8f\u7684\u6700\u7b80\u5355\u4f46\u63cf\u8ff0\u6027\u6700\u5dee\u7684\u65b9\u6cd5\u3002<\/p>\n<p>\u2777 Storing the lambda expression as a variable means you can name it\u2014getFruit in this case.<br \/>\n\u5c06 lambda \u8868\u8fbe\u5f0f\u5b58\u50a8\u4e3a\u53d8\u91cf\u610f\u5473\u7740\u60a8\u53ef\u4ee5\u5c06\u5176\u547d\u540d\u4e3a getFruit\uff0c\u5728\u672c\u4f8b\u4e2d\u4e3a getFruit\u3002<\/p>\n<p>\u2778 Handlers can be static methods in any class.<br \/>\n\u5904\u7406\u7a0b\u5e8f\u53ef\u4ee5\u662f\u4efb\u4f55\u7c7b\u4e2d\u7684\u9759\u6001\u65b9\u6cd5\u3002<\/p>\n<p>\u2779 Handlers can also be instance methods.<br \/>\n\u5904\u7406\u7a0b\u5e8f\u4e5f\u53ef\u4ee5\u662f\u5b9e\u4f8b\u65b9\u6cd5\u3002<\/p>\n<p>\u277a You can also use local functions, introduced in C# 7.0, as handler methods.<br \/>\n\u8fd8\u53ef\u4ee5\u4f7f\u7528 C# 7.0 \u4e2d\u5f15\u5165\u7684\u672c\u5730\u51fd\u6570\u4f5c\u4e3a\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u3002<\/p>\n<p>\u277b Handlers can also be instance methods.<br \/>\n\u5904\u7406\u7a0b\u5e8f\u4e5f\u53ef\u4ee5\u662f\u5b9e\u4f8b\u65b9\u6cd5\u3002<\/p>\n<p>\u277c Converts the response to a JsonObject<br \/>\n\u5c06\u54cd\u5e94\u8f6c\u6362\u4e3a JsonObject<\/p>\n<p>Listing 5.2 demonstrates the various ways you can pass handlers to an endpoint by simulating a simple API for interacting with a collection of Fruit items:<br \/>\n\u6e05\u5355 5.2 \u901a\u8fc7\u6a21\u62df\u4e00\u4e2a\u7b80\u5355\u7684 API \u6765\u4e0e Fruit \u9879\u76ee\u96c6\u5408\u4ea4\u4e92\uff0c\u6f14\u793a\u4e86\u5c06\u5904\u7406\u7a0b\u5e8f\u4f20\u9012\u7ed9\u7aef\u70b9\u7684\u5404\u79cd\u65b9\u6cd5\uff1a<\/p>\n<p>\u2022   A lambda expression, as in the MapGet(&quot;\/fruit&quot;) endpoint<br \/>\nlambda \u8868\u8fbe\u5f0f\uff0c\u5982\u7ec8\u7aef\u8282\u70b9\u4e2d\u6240\u793aMapGet(&quot;\/fruit&quot;)<\/p>\n<p>\u2022   A <code>Func&lt;T, TResult&gt;<\/code> variable, as in the MapGet(&quot;\/fruit\/{id}&quot;) endpoint<br \/>\n\u4e00\u4e2aFunc&lt;T, TResult&gt;`\u53d8\u91cf,\u5982\u7aef\u70b9,MapGet(&quot;\/fruit\/{id}&quot;)<\/p>\n<p>\u2022   A static method, as in the MapPost endpoint<br \/>\n\u9759\u6001\u65b9\u6cd5\uff0c\u5982\u7aef\u70b9MapPost<\/p>\n<p>\u2022   A method on an instance variable, as in the MapPut endpoint<br \/>\n\u5b9e\u4f8b\u53d8\u91cf\u4e0a\u7684\u65b9\u6cd5\uff0c\u5982\u7aef\u70b9\u4e2d\u6240\u793aMapPut<\/p>\n<p>\u2022   A local function, as in the MapDelete endpoint<br \/>\n\u672c\u5730\u51fd\u6570\uff0c\u5982\u7aef\u70b9MapDelete<\/p>\n<p>All these approaches are functionally identical, so you can use whichever pattern works best for you.<br \/>\n\u6240\u6709\u8fd9\u4e9b\u65b9\u6cd5\u5728\u529f\u80fd\u4e0a\u90fd\u662f\u76f8\u540c\u7684\uff0c\u56e0\u6b64\u60a8\u53ef\u4ee5\u4f7f\u7528\u6700\u9002\u5408\u60a8\u7684\u6a21\u5f0f\u3002<\/p>\n<p>Each Fruit record in listing 5.2 has a Name and a Stock level and is stored in a dictionary with an id. You call the API by using different HTTP verbs to perform the CRUD operations against the dictionary.<\/p>\n<p>\u6e05\u5355 5.2 \u4e2d\u7684\u6bcf\u4e2a Fruit \u8bb0\u5f55\u90fd\u6709\u4e00\u4e2a Name \u548c\u4e00\u4e2a Stock \u7ea7\u522b\uff0c\u5e76\u5b58\u50a8\u5728\u4e00\u4e2a\u5e26\u6709 id \u7684\u5b57\u5178\u4e2d\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u4e0d\u540c\u7684 HTTP \u52a8\u8bcd\u8c03\u7528 API\uff0c\u4ee5\u5bf9\u5b57\u5178\u6267\u884c CRUD\u4f5c\u3002<\/p>\n<p><b>WARNING<\/b> This API is simple. It isn\u2019t thread-safe, doesn\u2019t validate user input, and doesn\u2019t handle edge cases. We\u2019ll remedy some of those deficiencies in section 5.3.<br \/>\n\u8b66\u544a: \u6b64 API \u5f88\u7b80\u5355\u3002\u5b83\u4e0d\u662f\u7ebf\u7a0b\u5b89\u5168\u7684\uff0c\u4e0d\u9a8c\u8bc1\u7528\u6237\u8f93\u5165\uff0c\u4e5f\u4e0d\u5904\u7406\u8fb9\u7f18\u60c5\u51b5\u3002\u6211\u4eec\u5c06\u5728\u7b2c 5.3 \u8282\u4e2d\u7ea0\u6b63\u5176\u4e2d\u4e00\u4e9b\u7f3a\u9677\u3002<\/p>\n<p>The handlers for the POST and PUT endpoints in listing 5.2 accept both an id parameter and a Fruit parameter, showing another important feature of minimal APIs. Complex types\u2014that is, types that can\u2019t be extracted from the URL by means of route parameters\u2014are created by deserializing the JSON body of a request.<\/p>\n<p>\u6e05\u5355 5.2 \u4e2d POST \u548c PUT \u7aef\u70b9\u7684\u5904\u7406\u7a0b\u5e8f\u540c\u65f6\u63a5\u53d7 id \u53c2\u6570\u548c Fruit \u53c2\u6570\uff0c\u8fd9\u663e\u793a\u4e86\u6700\u5c0f API \u7684\u53e6\u4e00\u4e2a\u91cd\u8981\u7279\u6027\u3002\u590d\u6742\u7c7b\u578b\uff08\u5373\u65e0\u6cd5\u901a\u8fc7\u8def\u7531\u53c2\u6570\u4ece URL \u4e2d\u63d0\u53d6\u7684\u7c7b\u578b\uff09\u662f\u901a\u8fc7\u53cd\u5e8f\u5217\u5316\u8bf7\u6c42\u7684 JSON \u6b63\u6587\u6765\u521b\u5efa\u7684\u3002<\/p>\n<p><b>NOTE<\/b>  By contrast with APIs built using ASP.NET and ASP.NET Core web API controllers (which we cover in chapter 20), minimal APIs can bind only to JSON bodies and always use the System.Text.Json library for JSON deserialization.<br \/>\n\u6ce8\u610f: \u4e0e\u4f7f\u7528 ASP.NET \u548c ASP.NET Core Web API \u63a7\u5236\u5668\u6784\u5efa\u7684 API\uff08\u6211\u4eec\u5c06\u5728\u7b2c 20 \u7ae0\u4e2d\u4ecb\u7ecd\uff09\u76f8\u6bd4\uff0c\u6700\u5c0f\u7684 API \u53ea\u80fd\u7ed1\u5b9a\u5230 JSON \u6b63\u6587\uff0c\u5e76\u59cb\u7ec8\u4f7f\u7528 System.Text.Json \u5e93\u8fdb\u884c JSON \u53cd\u5e8f\u5217\u5316\u3002<\/p>\n<p>Figure 5.3 shows an example of a POST request sent with Postman. Postman sends the request body as JSON, which the minimal API automatically deserializes into a Fruit instance before calling the endpoint handler. You can bind only a single object in your endpoint handler to the request body in this way. I cover model binding in detail in chapter 7.<\/p>\n<p>\u56fe 5.3 \u663e\u793a\u4e86\u4f7f\u7528 Postman \u53d1\u9001\u7684 POST \u8bf7\u6c42\u7684\u793a\u4f8b\u3002Postman \u4ee5 JSON \u683c\u5f0f\u53d1\u9001\u8bf7\u6c42\u6b63\u6587\uff0c\u6700\u5c0f API \u4f1a\u81ea\u52a8\u5c06\u5176\u53cd\u5e8f\u5217\u5316\u4e3a Fruit<br \/>\n\u5b9e\u4f8b\u3002\u901a\u8fc7\u8fd9\u79cd\u65b9\u5f0f\uff0c\u60a8\u53ea\u80fd\u5c06\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u7684\u5355\u4e2a\u5bf9\u8c61\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6b63\u6587\u3002\u6211\u5728\u7b2c 7 \u7ae0\u4e2d\u8be6\u7ec6\u4ecb\u7ecd\u4e86\u6a21\u578b\u7ed1\u5b9a\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0503.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 5.3 Sending a POST request with Postman. The minimal API automatically deserializes the JSON in the request body to a Fruit instance before calling the endpoint handler.<br \/>\n\u56fe 5.3 \u4f7f\u7528 Postman \u53d1\u9001 POST \u8bf7\u6c42\u3002\u5728\u8c03\u7528\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e4b\u524d\uff0c\u6700\u5c0f API \u4f1a\u81ea\u52a8\u5c06\u8bf7\u6c42\u6b63\u6587\u4e2d\u7684 JSON \u53cd\u5e8f\u5217\u5316\u4e3a Fruit \u5b9e\u4f8b\u3002<\/p>\n<p>Minimal APIs leave you free to organize your endpoints any way you choose. That flexibility is often cited as a reason to not use them, due to the fear that developers will keep all the functionality in a single file, as in most examples (such as listing 5.2). In practice, you\u2019ll likely want to extract your endpoints to separate files so as to modularize them and make them easier to understand. Embrace that urge; that\u2019s the way they were intended to be used!<\/p>\n<p>\u6700\u5c11\u7684 API \u8ba9\u60a8\u53ef\u4ee5\u81ea\u7531\u5730\u6309\u7167\u60a8\u9009\u62e9\u7684\u4efb\u4f55\u65b9\u5f0f\u7ec4\u7ec7\u7ec8\u7aef\u8282\u70b9\u3002\u8fd9\u79cd\u7075\u6d3b\u6027\u7ecf\u5e38\u88ab\u5f15\u7528\u4e3a\u4e0d\u4f7f\u7528\u5b83\u4eec\u7684\u7406\u7531\uff0c\u56e0\u4e3a\u62c5\u5fc3\u5f00\u53d1\u4eba\u5458\u4f1a\u4fdd\u7559\u6240\u6709\u529f\u80fd\u5728\u5355\u4e2a\u6587\u4ef6\u4e2d\uff0c\u5c31\u50cf\u5927\u591a\u6570\u793a\u4f8b\u4e00\u6837\uff08\u6bd4\u5982\u6e05\u5355 5.2\uff09\u3002\u5728\u5b9e\u8df5\u4e2d\uff0c\u60a8\u53ef\u80fd\u5e0c\u671b\u5c06\u7ec8\u7aef\u8282\u70b9\u63d0\u53d6\u5230\u5355\u72ec\u7684\u6587\u4ef6\u4e2d\uff0c\u4ee5\u4fbf\u5c06\u5b83\u4eec\u6a21\u5757\u5316\u5e76\u4f7f\u5176\u66f4\u6613\u4e8e\u7406\u89e3\u3002\u62e5\u62b1\u8fd9\u79cd\u51b2\u52a8;\u8fd9\u5c31\u662f\u5b83\u4eec\u7684\u4f7f\u7528\u65b9\u5f0f\uff01<\/p>\n<p>Now you have a simple API, but if you try it out, you\u2019ll quickly run into scenarios in which your API seems to break. In section 5.3 you learn how to handle some of these scenarios by returning status codes.<\/p>\n<p>\u73b0\u5728\u4f60\u6709\u4e00\u4e2a\u7b80\u5355\u7684 API\uff0c\u4f46\u5982\u679c\u4f60\u5c1d\u8bd5\u4e00\u4e0b\uff0c\u4f60\u5f88\u5feb\u5c31\u4f1a\u9047\u5230 API \u4f3c\u4e4e\u5d29\u6e83\u7684\u60c5\u51b5\u3002\u5728 Section 5.3 \u4e2d\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u901a\u8fc7\u8fd4\u56de\u72b6\u6001\u4ee3\u7801\u6765\u5904\u7406\u5176\u4e2d\u4e00\u4e9b\u573a\u666f\u3002<\/p>\n<h2>5.3 Generating responses with IResult<\/h2>\n<h2>5.3 \u4f7f\u7528 IResult \u751f\u6210\u54cd\u5e94<\/h2>\n<p>You\u2019ve seen the basics of minimal APIs, but so far, we\u2019ve looked only at the happy path, where you can handle the request successfully and return a response. In this section we look at how to handle bad requests and other errors by returning different status codes from your API.<\/p>\n<p>\u60a8\u5df2\u7ecf\u4e86\u89e3\u4e86\u6700\u5c0f API \u7684\u57fa\u7840\u77e5\u8bc6\uff0c\u4f46\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u6211\u4eec\u53ea\u4e86\u89e3\u4e86 Happy Path\uff0c\u60a8\u53ef\u4ee5\u5728\u5176\u4e2d\u6210\u529f\u5904\u7406\u8bf7\u6c42\u5e76\u8fd4\u56de\u54cd\u5e94\u3002\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u901a\u8fc7\u4ece API \u8fd4\u56de\u4e0d\u540c\u7684\u72b6\u6001\u4ee3\u7801\u6765\u5904\u7406\u9519\u8bef\u8bf7\u6c42\u548c\u5176\u4ed6\u9519\u8bef\u3002<\/p>\n<p>The API in listing 5.2 works well as long as you perform only operations that are valid for the current state of the application. If you send a GET request to \/fruit, for example, you\u2019ll always get a 200 success response, but if you send a GET request to \/fruit\/f1 before you create a Fruit with the id f1, you\u2019ll get an exception and a 500 Internal Server Error response, as shown in figure 5.4.<\/p>\n<p>\u6e05\u5355 5.2 \u4e2d\u7684 API \u8fd0\u884c\u826f\u597d\uff0c\u53ea\u8981\u60a8\u53ea\u6267\u884c\u5bf9\u5e94\u7528\u7a0b\u5e8f\u5f53\u524d\u72b6\u6001\u6709\u6548\u7684\u4f5c\u3002\u4f8b\u5982\uff0c\u5982\u679c\u5411 \/fruit \u53d1\u9001 GET \u8bf7\u6c42\uff0c\u5219\u59cb\u7ec8\u4f1a\u6536\u5230 200 \u6210\u529f\u54cd\u5e94\uff0c\u4f46\u5982\u679c\u5728\u521b\u5efa ID \u4e3a f1 \u7684 Fruit \u4e4b\u524d\u5411 \/fruit\/f1 \u53d1\u9001 GET \u8bf7\u6c42\uff0c\u5219\u4f1a\u6536\u5230\u5f02\u5e38\u548c 500 Internal Server Error \u54cd\u5e94\uff0c\u5982\u56fe 5.4 \u6240\u793a\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0504.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 5.4 If you try to retrieve a fruit by using a nonexistent id for the simplistic API in listing 5.2, the endpoint throws an exception. This exception is handled by the DeveloperExceptionPageMiddleware but provides a poor experience.<br \/>\n\u56fe 5.4 \u5982\u679c\u4f60\u5c1d\u8bd5\u4f7f\u7528\u6e05\u5355 5.2 \u4e2d\u7b80\u5355 API \u7684\u4e0d\u5b58\u5728\u7684 id \u6765\u68c0\u7d22\u6c34\u679c\uff0c\u7aef\u70b9\u4f1a\u629b\u51fa\u4e00\u4e2a\u5f02\u5e38\u3002\u6b64\u5f02\u5e38\u7531 DeveloperExceptionPage-Middleware \u5904\u7406\uff0c\u4f46\u63d0\u4f9b\u7684\u4f53\u9a8c\u5f88\u5dee\u3002<\/p>\n<p>Throwing an exception whenever a user requests an id that doesn\u2019t exist clearly makes for a poor experience all round. A better approach is to return a status code indicating the problem, such as 404 Not Found or 400 Bad Request. The most declarative way to do this with minimal APIs is to return an IResult instance.<\/p>\n<p>\u6bcf\u5f53\u7528\u6237\u8bf7\u6c42\u4e0d\u5b58\u5728\u7684 ID \u65f6\u5f15\u53d1\u5f02\u5e38\uff0c\u663e\u7136\u4f1a\u5bfc\u81f4\u6574\u4f53\u4f53\u9a8c\u4e0d\u4f73\u3002\u66f4\u597d\u7684\u65b9\u6cd5\u662f\u8fd4\u56de\u6307\u793a\u95ee\u9898\u7684\u72b6\u6001\u4ee3\u7801\uff0c\u4f8b\u5982 404 Not Found \u6216 400 Bad Request\u3002\u4f7f\u7528\u6700\u5c11\u7684 API \u6267\u884c\u6b64\u4f5c\u7684\u6700\u58f0\u660e\u6027\u65b9\u6cd5\u662f\u8fd4\u56de IResult \u5b9e\u4f8b\u3002<\/p>\n<p>All the endpoint handlers you\u2019ve seen so far in this book have returned void, a string, or a plain old CLR object (POCO) such as Person or Fruit. There is one other type of object you can return from an endpoint: an IResult implementation. In summary, the endpoint middleware handles each return type as follows:<\/p>\n<p>\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u60a8\u5728\u672c\u4e66\u4e2d\u770b\u5230\u7684\u6240\u6709\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u90fd\u8fd4\u56de\u4e86 void\u3001\u5b57\u7b26\u4e32\u6216\u666e\u901a\u7684\u65e7 CLR \u5bf9\u8c61 \uff08POCO\uff09\uff0c\u4f8b\u5982 Person \u6216 Fruit\u3002\u60a8\u53ef\u4ee5\u4ece\u7aef\u70b9\u8fd4\u56de\u53e6\u4e00\u79cd\u7c7b\u578b\u7684\u5bf9\u8c61\uff1aIResult \u5b9e\u73b0\u3002\u603b\u4e4b\uff0c\u7ec8\u7aef\u8282\u70b9\u4e2d\u95f4\u4ef6\u6309\u5982\u4e0b\u65b9\u5f0f\u5904\u7406\u6bcf\u4e2a\u8fd4\u56de\u7c7b\u578b\uff1a<\/p>\n<ul>\n<li>\n<p>void or Task\u2014The endpoint returns a 200 response with no body.<br \/>\nvoid\u6216Task200 \u2014 \u7ec8\u7aef\u8282\u70b9\u8fd4\u56de\u6ca1\u6709\u6b63\u6587\u7684\u54cd\u5e94\u3002<\/p>\n<\/li>\n<li>\n<p>string or Task<string>\u2014The endpoint returns a 200 response with the string serialized to the body as text\/plain.<br \/>\nstring\u6216 Task\u2014 \u7ec8\u7aef\u8282\u70b9\u8fd4\u56de\u4e00\u4e2a200\u54cd\u5e94\uff0c\u5176\u4e2d\u5b57\u7b26\u4e32\u5e8f\u5217\u5316\u4e3a\u6b63\u6587text\/plain\u3002<\/p>\n<\/li>\n<li>\n<p>IResult or <code>Task&lt;IResult&gt;<\/code>\u2014The endpoint executes the IResult.ExecuteAsync method. Depending on the implementation, this type can customize the response, returning any status code.<br \/>\nIResult\u6216<code>Task&lt;IResult&gt;<\/code> - \u7aef\u70b9\u6267\u884cIResult.ExecuteAsync\u65b9\u6cd5\u3002\u6839\u636e\u5b9e\u73b0\uff0c\u6b64\u7c7b\u578b\u53ef\u4ee5\u81ea\u5b9a\u4e49\u54cd\u5e94\uff0c\u8fd4\u56de\u4efb\u4f55\u72b6\u6001\u4ee3\u7801\u3002<\/p>\n<\/li>\n<li>\n<p>T or <code>Task&lt;T&gt;<\/code>\u2014All other types (such as POCO objects) are serialized to JSON and returned in the body of a 200 response as application\/json.<br \/>\nT\u6216<code>Task&lt;T&gt;<\/code> \u2014 \u6240\u6709\u5176\u4ed6\u7c7b\u578b\u7684\uff08\u5982 POCO \u5bf9\u8c61\uff09\u90fd\u5e8f\u5217\u5316\u4e3a JSON\uff0c\u5e76\u5728\u54cd\u5e94\u6b63\u6587\u4e2d\u4f5c\u4e3aapplication\/json .<\/p>\n<\/li>\n<\/ul>\n<p>The IResult implementations provide much of the flexibility in minimal APIs, as you\u2019ll see in section 5.3.1.<\/p>\n<p>IResult \u5b9e\u73b0\u5728\u6700\u5c0f\u7684 API \u4e2d\u63d0\u4f9b\u4e86\u5f88\u5927\u7684\u7075\u6d3b\u6027\uff0c\u6b63\u5982\u60a8\u5c06\u5728 5.3.1 \u8282\u4e2d\u770b\u5230\u7684\u90a3\u6837\u3002<\/p>\n<h3>5.3.1 Returning status codes with Results and TypedResults<\/h3>\n<h3>5.3.1 \u4f7f\u7528 Results \u548c TypedResults \u8fd4\u56de\u72b6\u6001\u4ee3\u7801<\/h3>\n<p>A well-designed API uses status codes to indicate to a client what went wrong when a request failed, as well as potentially provide more descriptive codes when a request is successful. You should anticipate common problems that may occur when clients call your API and return appropriate status codes to indicate the causes to users.<\/p>\n<p>\u8bbe\u8ba1\u826f\u597d\u7684 API \u4f7f\u7528\u72b6\u6001\u4ee3\u7801\u5411\u5ba2\u6237\u7aef\u6307\u793a\u8bf7\u6c42\u5931\u8d25\u65f6\u51fa\u4e86\u4ec0\u4e48\u95ee\u9898\uff0c\u5e76\u53ef\u80fd\u5728\u8bf7\u6c42\u6210\u529f\u65f6\u63d0\u4f9b\u66f4\u5177\u63cf\u8ff0\u6027\u7684\u4ee3\u7801\u3002\u60a8\u5e94\u8be5\u9884\u89c1\u5230\u5ba2\u6237\u7aef\u8c03\u7528 API \u65f6\u53ef\u80fd\u51fa\u73b0\u7684\u5e38\u89c1\u95ee\u9898\uff0c\u5e76\u8fd4\u56de\u9002\u5f53\u7684\u72b6\u6001\u4ee3\u7801\u4ee5\u5411\u7528\u6237\u6307\u793a\u539f\u56e0\u3002<\/p>\n<p>ASP.NET Core exposes the simple static helper types Results and TypedResults in the namespace Microsoft.AspNetCore.Http. You can use these helpers to create a response with common status codes, optionally including a JSON body. Each of the methods on Results and TypedResults returns an implementation of IResult, which the endpoint middleware executes to generate the final response.<\/p>\n<p>ASP.NET Core \u5728\u547d\u540d\u7a7a\u95f4 Microsoft.AspNetCore.Http \u4e2d\u516c\u5f00\u4e86\u7b80\u5355\u7684\u9759\u6001\u5e2e\u52a9\u7a0b\u5e8f\u7c7b\u578b Results \u548c TypedResults\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u8fd9\u4e9b\u5e2e\u52a9\u7a0b\u5e8f\u521b\u5efa\u5177\u6709\u5e38\u89c1\u72b6\u6001\u4ee3\u7801\u7684\u54cd\u5e94\uff0c\u53ef\u4ee5\u9009\u62e9\u5305\u62ec JSON \u6b63\u6587\u3002Results \u548c TypedResults \u4e0a\u7684\u6bcf\u4e2a\u65b9\u6cd5\u90fd\u8fd4\u56de IResult \u7684\u5b9e\u73b0\uff0c\u7aef\u70b9\u4e2d\u95f4\u4ef6\u6267\u884c\u8be5\u5b9e\u73b0\u4ee5\u751f\u6210\u6700\u7ec8\u54cd\u5e94\u3002<\/p>\n<p><b>NOTE<\/b>  Results and TypedResults perform the same function, as helpers for generating common status codes. The only difference is that the Results methods return an IResult, whereas TypedResults return a concrete generic type, such as Ok<T>. There\u2019s no difference in terms of functionality, but the generic types are easier to use in unit tests and in OpenAPI documentation, as you\u2019ll see in chapters 36 and 11. TypedResults were added in .NET 7.<br \/>\n\u6ce8\u610f: Results \u548c TypedResults \u6267\u884c\u76f8\u540c\u7684\u529f\u80fd\uff0c\u4f5c\u4e3a\u751f\u6210\u5e38\u89c1\u72b6\u6001\u4ee3\u7801\u7684\u5e2e\u52a9\u7a0b\u5e8f\u3002\u552f\u4e00\u7684\u533a\u522b\u662f Results \u65b9\u6cd5\u8fd4\u56de IResult\uff0c\u800c TypedResults \u8fd4\u56de\u5177\u4f53\u7684\u6cdb\u578b\u7c7b\u578b\uff0c\u4f8b\u5982 <code>Ok&lt;T&gt;<\/code>\u3002\u5728\u529f\u80fd\u65b9\u9762\u6ca1\u6709\u533a\u522b\uff0c\u4f46\u6cdb\u578b\u7c7b\u578b\u66f4\u6613\u4e8e\u4f7f\u7528\u5355\u5143\u6d4b\u8bd5\u548c OpenAPI \u6587\u6863\uff0c\u5982\u7b2c 36 \u7ae0\u548c\u7b2c 11 \u7ae0\u6240\u793a\u3002TypedResults \u5df2\u6dfb\u52a0\u5230 .NET 7 \u4e2d\u3002<\/p>\n<p>The following listing shows an updated version of listing 5.2, in which we address some of the deficiencies in the API and use Results and TypedResults to return different status codes to clients.<\/p>\n<p>\u4e0b\u9762\u7684\u6e05\u5355\u663e\u793a\u4e86\u6e05\u5355 5.2 \u7684\u66f4\u65b0\u7248\u672c\uff0c\u5176\u4e2d\u6211\u4eec\u89e3\u51b3\u4e86 API \u4e2d\u7684\u4e00\u4e9b\u7f3a\u9677\uff0c\u5e76\u4f7f\u7528 Results \u548c TypedResults \u5411\u5ba2\u6237\u7aef\u8fd4\u56de\u4e0d\u540c\u7684\u72b6\u6001\u4ee3\u7801\u3002<\/p>\n<p>Listing 5.3 Using Results and TypedResults in a minimal API<br \/>\n\u6e05\u5355 5.3 \u5728\u6700\u5c0f API \u4e2d\u4f7f\u7528 \u548cResultsTypedResults<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing System.Collections.Concurrent;\n\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\nvar _fruit = new ConcurrentDictionary&lt;string, Fruit&gt;();                        \/\/ \u2776\n\napp.MapGet(&quot;\/fruit&quot;, () =&gt; _fruit);\n\napp.MapGet(&quot;\/fruit\/{id}&quot;, (string id) =&gt;\n    _fruit.TryGetValue(id, out var fruit)                                      \/\/ \u2777\n        ? TypedResults.Ok(fruit)                                               \/\/ \u2778\n        : Results.NotFound());                                                 \/\/ \u2779\n\napp.MapPost(&quot;\/fruit\/{id}&quot;, (string id, Fruit fruit) =&gt;\n    _fruit.TryAdd(id, fruit)                                                   \/\/ \u277a\n        ? TypedResults.Created($&quot;\/fruit\/{id}&quot;, fruit)                          \/\/ \u277b\n        : Results.BadRequest(new                                               \/\/ \u277c\n            { id = &quot;A fruit with this id already exists&quot; }));                  \/\/ \u277c\n\napp.MapPut(&quot;\/fruit\/{id}&quot;, (string id, Fruit fruit) =&gt;\n{\n    _fruit&#x5B;id] = fruit;\n    return Results.NoContent();                                                \/\/ \u277d\n});\n\napp.MapDelete(&quot;\/fruit\/{id}&quot;, (string id) =&gt;\n{\n    _fruit.TryRemove(id, out _);                                               \/\/ \u277e\n    return Results.NoContent();                                                \/\/ \u277e\n});\n\napp.Run();\n\nrecord Fruit(string Name, int stock);\n<\/pre>\n<p>\u2776 Uses a concurrent dictionary to make the API thread-safe<br \/>\n\u4f7f\u7528\u5e76\u53d1\u5b57\u5178\u4f7f API \u7ebf\u7a0b\u5b89\u5168<\/p>\n<p>\u2777 Tries to get the fruit from the dictionary. If the ID exists in the dictionary, this returns true . . .<br \/>\n\u5c1d\u8bd5\u4ece\u5b57\u5178\u4e2d\u83b7\u53d6 fruit\u3002\u5982\u679c\u5b57\u5178\u4e2d\u5b58\u5728 ID\uff0c\u5219\u8fd4\u56de true . . .<\/p>\n<p>\u2778 . . . and we return a 200 OK response, serializing the fruit in the body as JSON.<br \/>\n. . . .\u7136\u540e\u6211\u4eec\u8fd4\u56de\u4e00\u4e2a 200 OK \u54cd\u5e94\uff0c\u5c06 body \u4e2d\u7684\u6c34\u679c\u5e8f\u5217\u5316\u4e3a JSON\u3002<\/p>\n<p>\u2779 If the ID doesn\u2019t exist, returns a 404 Not Found response<br \/>\n\u5982\u679c ID \u4e0d\u5b58\u5728\uff0c\u5219\u8fd4\u56de 404 Not Found \u54cd\u5e94<\/p>\n<p>\u277a Tries to add the fruit to the dictionary. If the ID hasn\u2019t been added yet. this returns true . . .<br \/>\n\u5c1d\u8bd5\u5c06 fruit \u6dfb\u52a0\u5230\u5b57\u5178\u4e2d\u3002\u5982\u679c\u5c1a\u672a\u6dfb\u52a0 ID\u3002\u8fd9\u5c06\u8fd4\u56de true . . .<\/p>\n<p>\u277b . . . and we return a 201 response with a JSON body and set the Location<br \/>\nheader to the given path.<br \/>\n. . . .\u6211\u4eec\u8fd4\u56de\u4e00\u4e2a\u5e26\u6709 JSON \u6b63\u6587\u7684 201 \u54cd\u5e94\uff0c\u5e76\u5c06 Location \u6807\u5934\u8bbe\u7f6e\u4e3a\u7ed9\u5b9a\u8def\u5f84\u3002<\/p>\n<p>\u277c If the ID already exists, returns a 400 Bad Request response with an error message<br \/>\n\u5982\u679c ID \u5df2\u5b58\u5728\uff0c\u5219\u8fd4\u56de 400 Bad Request \u54cd\u5e94\uff0c\u5e76\u663e\u793a\u9519\u8bef\u6d88\u606f<\/p>\n<p>\u277d After adding or replacing the fruit, returns a 204 No Content response<br \/>\n\u6dfb\u52a0\u6216\u66ff\u6362\u6c34\u679c\u540e\uff0c\u8fd4\u56de 204 No Content \u54cd\u5e94<\/p>\n<p>\u277e After deleting the fruit, always returns a 204 No Content response<br \/>\n\u5220\u9664\u6c34\u679c\u540e\uff0c\u59cb\u7ec8\u8fd4\u56de 204 No Content \u54cd\u5e94<\/p>\n<p>Listing 5.3 demonstrates several status codes, some of which you may not be familiar with:<\/p>\n<p>\u6e05\u5355 5.3 \u6f14\u793a\u4e86\u51e0\u4e2a\u72b6\u6001\u4ee3\u7801\uff0c\u5176\u4e2d\u4e00\u4e9b\u4f60\u53ef\u80fd\u4e0d\u719f\u6089\uff1a<\/p>\n<p>\u2022   200 OK\u2014The standard successful response. It often includes content in the body of the response but doesn\u2019t have to.<br \/>\n200 OK  - \u6807\u51c6\u6210\u529f\u54cd\u5e94\u3002\u5b83\u901a\u5e38\u5728\u54cd\u5e94\u6b63\u6587\u4e2d\u5305\u542b\u5185\u5bb9\uff0c\u4f46\u5e76\u975e\u5fc5\u987b\u3002<\/p>\n<p>\u2022   201 Created\u2014Often returned when you successfully created an entity on the server. The Created result in listing 5.3 also includes a Location header to describe the URL where the entity can be found, as well as the JSON entity itself in the body of the response.<br \/>\n201 Created \uff08\u5df2\u521b\u5efa\uff09 \u2013 \u5f53\u60a8\u5728\u670d\u52a1\u5668\u4e0a\u6210\u529f\u521b\u5efa\u5b9e\u4f53\u65f6\uff0c\u901a\u5e38\u4f1a\u8fd4\u56de\u3002\u6e05\u5355 5.3 \u4e2d\u7684 Created \u7ed3\u679c\u8fd8\u5305\u62ec\u4e00\u4e2a Location \u6807\u5934\uff0c\u7528\u4e8e\u63cf\u8ff0\u53ef\u4ee5\u627e\u5230\u5b9e\u4f53\u7684 URL\uff0c\u4ee5\u53ca\u54cd\u5e94\u6b63\u6587\u4e2d\u7684 JSON \u5b9e\u4f53\u672c\u8eab\u3002<\/p>\n<p>\u2022   204 No Content\u2014Similar to a 200 response but without any content in the response body.<br \/>\n204 \u65e0\u5185\u5bb9 \u2014 \u7c7b\u4f3c\u4e8e 200 \u54cd\u5e94\uff0c\u4f46\u54cd\u5e94\u6b63\u6587\u4e2d\u6ca1\u6709\u4efb\u4f55\u5185\u5bb9\u3002<\/p>\n<p>\u2022   400 Bad Request\u2014Indicates that the request was invalid in some way; often used to indicate data validation failures<br \/>\n400 Bad Request \u2014 \u8868\u793a\u8bf7\u6c42\u5728\u67d0\u79cd\u7a0b\u5ea6\u4e0a\u65e0\u6548;\u901a\u5e38\u7528\u4e8e\u8868\u793a\u6570\u636e\u9a8c\u8bc1\u5931\u8d25\u3002<\/p>\n<p>\u2022   404 Not Found\u2014Indicates that the requested entity could not be found<br \/>\n404 Not Found - \u8868\u793a\u627e\u4e0d\u5230\u8bf7\u6c42\u7684\u5b9e\u4f53\u3002<\/p>\n<p>These status codes more accurately describe your API and can make an API easier to use. That said, if you use only 200 OK responses for all your successful responses, few people will mind or think less of you! You can see a summary of all the possible status codes and their expected uses at <a href=\"http:\/\/mng.bz\/jP4x\">http:\/\/mng.bz\/jP4x<\/a>.<\/p>\n<p>\u8fd9\u4e9b\u72b6\u6001\u4ee3\u7801\u53ef\u4ee5\u66f4\u51c6\u786e\u5730\u63cf\u8ff0\u60a8\u7684 API\uff0c\u5e76\u4f7f API \u66f4\u6613\u4e8e\u4f7f\u7528\u3002\u4e5f\u5c31\u662f\u8bf4\uff0c\u5982\u679c\u4f60\u53ea\u4f7f\u7528 200 \u4e2a OK \u56de\u590d\u6765\u83b7\u5f97\u6240\u6709\u6210\u529f\u7684\u56de\u590d\uff0c\u90a3\u4e48\u5f88\u5c11\u6709\u4eba\u4f1a\u4ecb\u610f\u6216\u5c11\u770b\u4f60\uff01\u60a8\u53ef\u4ee5\u5728 <a href=\"http:\/\/mng.bz\/jP4x\">http:\/\/mng.bz\/jP4x<\/a> \u4e0a\u67e5\u770b\u6240\u6709\u53ef\u80fd\u7684\u72b6\u6001\u4ee3\u7801\u53ca\u5176\u9884\u671f\u7528\u9014\u7684\u6458\u8981\u3002<\/p>\n<p><b>NOTE<\/b>  The 404 status code in particular causes endless debate in online forums. Should it be only used if the request didn\u2019t match an endpoint? Is it OK to use 404 to indicate a missing entity (as in the previous example)? There are endless proponents in both camps, so take your pick!<br \/>\n\u6ce8\u610f: \u5c24\u5176\u662f 404 \u72b6\u6001\u4ee3\u7801\u5728\u5728\u7ebf\u8bba\u575b\u4e2d\u5f15\u8d77\u4e86\u65e0\u4f11\u6b62\u7684\u4e89\u8bba\u3002\u662f\u5426\u5e94\u4ec5\u5728\u8bf7\u6c42\u4e0e\u7ec8\u7aef\u8282\u70b9\u4e0d\u5339\u914d\u65f6\u624d\u4f7f\u7528\u5b83\uff1f\u662f\u5426\u53ef\u4ee5\u4f7f\u7528 404 \u6765\u8868\u793a\u7f3a\u5c11\u7684\u5b9e\u4f53\uff08\u5982\u524d\u9762\u7684\u793a\u4f8b\u6240\u793a\uff09\uff1f\u4e24\u4e2a\u9635\u8425\u90fd\u6709\u65e0\u7a77\u65e0\u5c3d\u7684\u652f\u6301\u8005\uff0c\u6240\u4ee5\u4efb\u4f60\u9009\u62e9\u5427\uff01<\/p>\n<p>Results and TypedResults include methods for all the common status code results you could need, but if you don\u2019t want to use them for some reason, you can always set the status code yourself directly on the HttpResponse, as in listing 5.4. In fact, the listing shows how to define the entire response manually, including the status code, the content type, and the response body. You won\u2019t need to take this manual approach often, but it can be useful in some situations.<\/p>\n<p>Results \u548c TypedResults \u5305\u542b\u4f60\u53ef\u80fd\u9700\u8981\u7684\u6240\u6709\u5e38\u89c1\u72b6\u6001\u7801\u7ed3\u679c\u7684\u65b9\u6cd5\uff0c\u4f46\u662f\u5982\u679c\u4f60\u7531\u4e8e\u67d0\u79cd\u539f\u56e0\u4e0d\u60f3\u4f7f\u7528\u5b83\u4eec\uff0c\u4f60\u603b\u662f\u53ef\u4ee5\u76f4\u63a5\u5728 HttpResponse \u4e0a\u81ea\u5df1\u8bbe\u7f6e\u72b6\u6001\u7801\uff0c\u5982\u6e05\u5355 5.4 \u6240\u793a\u3002\u4e8b\u5b9e\u4e0a\uff0c\u8be5\u6e05\u5355\u663e\u793a\u4e86\u5982\u4f55\u624b\u52a8\u5b9a\u4e49\u6574\u4e2a\u54cd\u5e94\uff0c\u5305\u62ec\u72b6\u6001\u4ee3\u7801\u3001\u5185\u5bb9\u7c7b\u578b\u548c\u54cd\u5e94\u6b63\u6587\u3002\u60a8\u4e0d\u9700\u8981\u7ecf\u5e38\u91c7\u7528\u8fd9\u79cd\u624b\u52a8\u65b9\u6cd5\uff0c\u4f46\u5b83\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\u53ef\u80fd\u5f88\u6709\u7528\u3002<\/p>\n<p>Listing 5.4 Writing the response manually using HttpResponse<br \/>\n\u6e05\u5355 5.4 \u4f7f\u7528HttpResponse<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\napp.MapGet(&quot;\/teapot&quot;, (HttpResponse response) =&gt;                           \/\/ \u2776\n{\n    response.StatusCode = 418;                                             \/\/ \u2777\n    response.ContentType = System.Net.Mime.MediaTypeNames.Text.Plain;      \/\/ \u2778\n    return response.WriteAsync(&quot;I&#039;m a teapot!&quot;);                           \/\/ \u2779\n});\n\napp.Run();\n<\/pre>\n<p>\u2776 Accesses the HttpResponse by including it as a parameter in your endpoint<br \/>\nhandler<br \/>\n\u901a\u8fc7\u5c06 HttpResponse \u4f5c\u4e3a\u53c2\u6570\u5305\u542b\u5728\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u6765\u8bbf\u95ee HttpResponse<\/p>\n<p>\u2777 You can set the status code directly on the response.<br \/>\n\u60a8\u53ef\u4ee5\u76f4\u63a5\u5728\u54cd\u5e94\u4e2d\u8bbe\u7f6e\u72b6\u6001\u4ee3\u7801\u3002<\/p>\n<p>\u2778 Defines the content type that will be sent in the response<br \/>\n\u5b9a\u4e49\u5c06\u5728\u54cd\u5e94\u4e2d\u53d1\u9001\u7684\u5185\u5bb9\u7c7b\u578b<\/p>\n<p>\u2779 You can write data to the response stream manually.<br \/>\n\u60a8\u53ef\u4ee5\u624b\u52a8\u5c06\u6570\u636e\u5199\u5165\u54cd\u5e94\u6d41\u3002<\/p>\n<p>HttpResponse represents the response that will be sent to the client and is one of the special types that minimal APIs know to inject into your endpoint handlers (instead of trying to create it by deserializing from the request body). You\u2019ll learn about the other types you can use in your endpoint handlers in chapter 7.<\/p>\n<p>HttpResponse \u8868\u793a\u5c06\u53d1\u9001\u5230\u5ba2\u6237\u7aef\u7684\u54cd\u5e94\uff0c\u5e76\u4e14\u662f\u6700\u5c0f API \u77e5\u9053\u6ce8\u5165\u5230\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u7684\u7279\u6b8a\u7c7b\u578b\u4e4b\u4e00\uff08\u800c\u4e0d\u662f\u5c1d\u8bd5\u901a\u8fc7\u4ece\u8bf7\u6c42\u6b63\u6587\u53cd\u5e8f\u5217\u5316\u6765\u521b\u5efa\u5b83\uff09\u3002\u60a8\u5c06\u5728\u7b2c 7 \u7ae0\u4e2d\u4e86\u89e3\u53ef\u4ee5\u5728\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u4f7f\u7528\u7684\u5176\u4ed6\u7c7b\u578b\u3002<\/p>\n<h3>5.3.2 Returning useful errors with Problem Details<\/h3>\n<h3>5.3.2 \u4f7f\u7528 Problem Details \u8fd4\u56de\u6709\u7528\u7684\u9519\u8bef<\/h3>\n<p>In the MapPost endpoint of listing 5.3, we checked to see whether an entity with the given id already existed. If it did, we returned a 400 response with a description of the error. The problem with this approach is that the client\u2014typically, a mobile app or SPA\u2014must know how to read and parse that response. If each of your APIs has a different format for errors, that arrangement can make for a confusing API. Luckily, a web standard called Problem Details describes a consistent format to use.<\/p>\n<p>\u5728\u6e05\u5355 5.3 \u7684 MapPost \u7aef\u70b9\u4e2d\uff0c\u6211\u4eec\u68c0\u67e5\u4e86\u662f\u5426\u5df2\u7ecf\u5b58\u5728\u5177\u6709\u7ed9\u5b9a id \u7684\u5b9e\u4f53\u3002\u5982\u679c\u662f\u8fd9\u6837\uff0c\u6211\u4eec\u5c06\u8fd4\u56de 400 \u54cd\u5e94\uff0c\u5176\u4e2d\u5305\u542b\u9519\u8bef\u8bf4\u660e\u3002\u200c\u8fd9\u79cd\u65b9\u6cd5\u7684\u95ee\u9898\u5728\u4e8e\uff0c\u5ba2\u6237\u7aef\uff08\u901a\u5e38\u662f\u79fb\u52a8\u5e94\u7528\u7a0b\u5e8f\u6216 SPA\uff09\u5fc5\u987b\u77e5\u9053\u5982\u4f55\u8bfb\u53d6\u548c\u89e3\u6790\u8be5\u54cd\u5e94\u3002\u5982\u679c\u6bcf\u4e2a API \u7684\u9519\u8bef\uff0c\u8fd9\u79cd\u5b89\u6392\u53ef\u80fd\u4f1a\u5bfc\u81f4 API \u6df7\u4e71\u3002\u5e78\u8fd0\u7684\u662f\uff0c\u4e00\u4e2a\u540d\u4e3a Problem Details \u7684 Web \u6807\u51c6\u63cf\u8ff0\u4e86\u8981\u4f7f\u7528\u7684\u4e00\u81f4\u683c\u5f0f\u3002<\/p>\n<p><b>DEFINITION<\/b> Problem Details is a web specification (<a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc7807.html\">https:\/\/www.rfc-editor.org\/rfc\/rfc7807.html<\/a>) for providing machine-readable errors for HTTP APIs. It defines the required and optional fields that should be in the JSON body for errors.<br \/>\n\u5b9a\u4e49: \u95ee\u9898\u8be6\u7ec6\u4fe1\u606f \u662f\u4e00\u4e2a Web \u89c4\u8303 \uff08<a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc7807.xhtml\uff09\uff0c\u7528\u4e8e\u4e3a\">https:\/\/www.rfc-editor.org\/rfc\/rfc7807.xhtml\uff09\uff0c\u7528\u4e8e\u4e3a<\/a> HTTP API \u63d0\u4f9b\u673a\u5668\u53ef\u8bfb\u7684\u9519\u8bef\u3002\u5b83\u5b9a\u4e49\u4e86 JSON \u6b63\u6587\u4e2d\u5e94\u5305\u542b\u7684 errors \u5fc5\u586b\u5b57\u6bb5\u548c\u53ef\u9009\u5b57\u6bb5\u3002<\/p>\n<p>ASP.NET Core includes two helper methods for generating Problem Details responses from minimal APIs: Results.Problem() and Results.ValidationProblem() (plus their TypedResults counterparts). Both of these methods return Problem Details JSON. The only difference is that Problem() defaults to a 500 status code, whereas ValidationProblem() defaults to a 400 status and requires you to pass in a Dictionary of validation errors, as shown in the following listing.<\/p>\n<p>ASP.NET Core \u5305\u542b\u4e24\u4e2a\u5e2e\u52a9\u7a0b\u5e8f\u65b9\u6cd5\uff0c\u7528\u4e8e\u4ece\u6700\u5c0f API \u751f\u6210\u95ee\u9898\u8be6\u7ec6\u4fe1\u606f\u54cd\u5e94\uff1aResults.Problem\uff08\uff09 \u548c Results.ValidationProblem\uff08\uff09\uff08\u4ee5\u53ca\u5b83\u4eec\u7684 TypedResults \u5bf9\u5e94\u9879\uff09\u3002\u8fd9\u4e24\u79cd\u65b9\u6cd5\u90fd\u8fd4\u56de Problem Details JSON\u3002\u552f\u4e00\u7684\u533a\u522b\u662f Problem\uff08\uff09 \u9ed8\u8ba4\u4e3a 500 \u72b6\u6001\u4ee3\u7801\uff0c\u800c ValidationProblem\uff08\uff09 \u9ed8\u8ba4\u4e3a 400 \u72b6\u6001\uff0c\u5e76\u8981\u6c42\u60a8\u4f20\u5165\u9a8c\u8bc1\u9519\u8bef\u7684 Dictionary\uff0c\u5982\u4e0b\u9762\u7684\u6e05\u5355\u6240\u793a\u3002<\/p>\n<p>Listing 5.5 Returning Problem Details using Results.Problem<br \/>\n\u6e05\u5355 5.5 \u4f7f\u7528Results.Problem<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\nvar _fruit = new System.Collections.Concurrent.ConcurrentDictionary&lt;string, Fruit&gt;();\n\napp.MapGet(&quot;\/fruit&quot;, () =&gt; _fruit);\n\napp.MapGet(&quot;\/fruit\/{id}&quot;, (string id) =&gt;\n    _fruit.TryGetValue(id, out var fruit)\n        ? TypedResults.Ok(fruit)\n        : Results.Problem(statusCode: 404));                              \/\/ \u2776\n\napp.MapPost(&quot;\/fruit\/{id}&quot;, (string id, Fruit fruit) =&gt;\n    _fruit.TryAdd(id, fruit)\n     ? TypedResults.Created($&quot;\/fruit\/{id}&quot;, fruit)\n     : Results.ValidationProblem(new Dictionary&lt;string, string&#x5B;]&gt;         \/\/ \u2777\n        {                                                                 \/\/ \u2777\n            { &quot;id&quot;, new&#x5B;] { &quot;A fruit with this id already exists&quot; }}      \/\/ \u2777\n        }));\n\nrecord Fruit(string Name, int stock);\n<\/pre>\n<p>\u2776 Returns a Problem Details object with a 404 status code<br \/>\n\u8fd4\u56de\u72b6\u6001\u4ee3\u7801\u4e3a 404 \u7684 Problem Details \u5bf9\u8c61<\/p>\n<p>\u2777 Returns a Problem Details object with a 400 status code and includes the<br \/>\nvalidation errors<br \/>\n\u8fd4\u56de\u5177\u6709 400 \u72b6\u6001\u4ee3\u7801\u7684 Problem Details \u5bf9\u8c61\uff0c\u5e76\u5305\u542b\u9a8c\u8bc1\u9519\u8bef<\/p>\n<p>The ProblemHttpResult returned by these methods takes care of including the correct title and description based on the status code, and generates the appropriate JSON, as shown in figure 5.5. You can override the default title and description by passing additional arguments to the Problem() and ValidationProblem() methods.<br \/>\n\u8fd9\u4e9b\u65b9\u6cd5\u8fd4\u56de\u7684 ProblemHttpResult \u8d1f\u8d23\u6839\u636e\u72b6\u6001\u4ee3\u7801\u5305\u542b\u6b63\u786e\u7684\u6807\u9898\u548c\u63cf\u8ff0\uff0c\u5e76\u751f\u6210\u9002\u5f53\u7684 JSON\uff0c\u5982\u56fe 5.5 \u6240\u793a\u3002\u60a8\u53ef\u4ee5\u901a\u8fc7\u5411 Problem\uff08\uff09 \u548c ValidationProblem\uff08\uff09 \u65b9\u6cd5\u4f20\u9012\u5176\u4ed6\u53c2\u6570\u6765\u8986\u76d6\u9ed8\u8ba4\u7684\u6807\u9898\u548c\u63cf\u8ff0\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0505.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 5.5 You can return a Problem Details response by using the Problem and ValidationProblem methods. The ValidationProblem response shown here includes a description of the error, along with the validation errors in a standard format. This example shows the response when you try to create a fruit with an id that has already been used.<br \/>\n\u56fe 5.5 \u60a8\u53ef\u4ee5\u4f7f\u7528 Problem \u548c ValidationProblem \u65b9\u6cd5\u8fd4\u56de Problem Details \u54cd\u5e94\u3002\u6b64\u5904\u663e\u793a\u7684 ValidationProblem \u54cd\u5e94\u5305\u62ec\u9519\u8bef\u8bf4\u660e\uff0c\u4ee5\u53ca\u6807\u51c6\u683c\u5f0f\u7684\u9a8c\u8bc1\u9519\u8bef\u3002\u6b64\u793a\u4f8b\u663e\u793a\u4e86\u5f53\u60a8\u5c1d\u8bd5\u521b\u5efa\u5177\u6709\u5df2\u4f7f\u7528\u7684 id \u7684\u6c34\u679c\u65f6\u7684\u54cd\u5e94\u3002<\/p>\n<p>Deciding on an error format is an important step whenever you create an API, and as Problem Details is already a web standard, it should be your go-to approach, especially for validation errors. Next, you\u2019ll learn how to ensure that all your error responses are Problem Details.<\/p>\n<p>\u65e0\u8bba\u4f55\u65f6\u521b\u5efa API\uff0c\u786e\u5b9a\u9519\u8bef\u683c\u5f0f\u90fd\u662f\u4e00\u4e2a\u91cd\u8981\u7684\u6b65\u9aa4\uff0c\u7531\u4e8e Problem Details \u5df2\u7ecf\u662f\u4e00\u4e2a Web \u6807\u51c6\uff0c\u56e0\u6b64\u5b83\u5e94\u8be5\u662f\u60a8\u7684\u9996\u9009\u65b9\u6cd5\uff0c\u5c24\u5176\u662f\u5bf9\u4e8e\u9a8c\u8bc1\u9519\u8bef\u3002\u63a5\u4e0b\u6765\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u786e\u4fdd\u6240\u6709\u9519\u8bef\u54cd\u5e94\u90fd\u662f Problem Details\u3002<\/p>\n<h3>5.3.3 Converting all your responses to Problem Details<\/h3>\n<h3>5.3.3 \u5c06\u60a8\u7684\u6240\u6709\u54cd\u5e94\u8f6c\u6362\u4e3a Problem Details<\/h3>\n<p>In section 5.3.2 you saw how to use the Results.Problem() and Results.ValidationProblem() methods in your minimal API endpoints to return Problem Details JSON. The only catch is that your minimal API endpoints aren\u2019t the only thing that could generate errors. In this section you\u2019ll learn how to make sure that all your errors return Problem Details JSON, keeping the error responses consistent across your application.<\/p>\n<p>\u5728\u7b2c 5.3.2 \u8282\u4e2d\uff0c\u60a8\u4e86\u89e3\u4e86\u5982\u4f55\u5728\u6700\u5c0f API \u7aef\u70b9\u4e2d\u4f7f\u7528 Results.Problem\uff08\uff09 \u548c Results.ValidationProblem\uff08\uff09 \u65b9\u6cd5\u6765\u8fd4\u56de\u95ee\u9898\u8be6\u7ec6\u4fe1\u606f JSON\u3002\u552f\u4e00\u7684\u95ee\u9898\u662f\uff0c\u60a8\u7684\u6700\u5c0f API \u7ec8\u7aef\u8282\u70b9\u5e76\u4e0d\u662f\u552f\u4e00\u53ef\u80fd\u4ea7\u751f\u9519\u8bef\u7684\u4e1c\u897f\u3002\u5728\u672c\u8282\u4e2d\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u786e\u4fdd\u6240\u6709\u9519\u8bef\u90fd\u8fd4\u56de Problem Details JSON\uff0c\u4ece\u800c\u5728\u6574\u4e2a\u5e94\u7528\u7a0b\u5e8f\u4e2d\u4fdd\u6301\u9519\u8bef\u54cd\u5e94\u7684\u4e00\u81f4\u6027\u3002<\/p>\n<p>A minimal API application could generate an error response in several ways:<\/p>\n<p>\u6700\u5c0f\u7684 API \u5e94\u7528\u7a0b\u5e8f\u53ef\u4ee5\u901a\u8fc7\u591a\u79cd\u65b9\u5f0f\u751f\u6210\u9519\u8bef\u54cd\u5e94\uff1a<\/p>\n<p>\u2022   Returning an error status code from an endpoint handler<br \/>\n\u4ece\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u8fd4\u56de\u9519\u8bef\u72b6\u6001\u4ee3\u7801<\/p>\n<p>\u2022   Throwing an exception in an endpoint handler, which is caught by the ExceptionHandlerMiddleware or the DeveloperExceptionPageMiddleware and converted to an error response.<br \/>\n\u5728\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u629b\u51fa\u5f02\u5e38\uff0c\u8be5\u5f02\u5e38\u88ab ExceptionHandlerMiddleware \u6216 DeveloperExceptionPageMiddleware \u6355\u83b7\u5e76\u8f6c\u6362\u4e3a\u9519\u8bef\u54cd\u5e94<\/p>\n<p>\u2022   The middleware pipeline returning a 404 response because a request isn\u2019t handled by an endpoint<br \/>\n\u4e2d\u95f4\u4ef6\u7ba1\u9053\u8fd4\u56de 404 \u54cd\u5e94\uff0c\u56e0\u4e3a\u8bf7\u6c42\u672a\u7531\u7ec8\u7aef\u8282\u70b9\u5904\u7406<\/p>\n<p>\u2022   A middleware component in the pipeline throwing an exception<br \/>\n\u7ba1\u9053\u4e2d\u5f15\u53d1\u5f02\u5e38\u7684\u4e2d\u95f4\u4ef6\u7ec4\u4ef6<\/p>\n<p>\u2022   A middleware component returning an error response because a request requires authentication, and no credentials were provided<br \/>\n\u4e2d\u95f4\u4ef6\u7ec4\u4ef6\u8fd4\u56de\u9519\u8bef\u54cd\u5e94\uff0c\u56e0\u4e3a\u8bf7\u6c42\u9700\u8981\u8eab\u4efd\u9a8c\u8bc1\uff0c\u5e76\u4e14\u672a\u63d0\u4f9b\u51ed\u636e<\/p>\n<p>There are essentially two classes of errors, which are handled differently: exceptions and error status code responses. To create a consistent API for consumers, we need to make sure that both error types return Problem Details JSON in the response.<br \/>\n\u57fa\u672c\u4e0a\u6709\u4e24\u7c7b\u9519\u8bef\uff0c\u5b83\u4eec\u7684\u5904\u7406\u65b9\u5f0f\u4e0d\u540c\uff1a\u5f02\u5e38\u548c\u9519\u8bef\u72b6\u6001\u4ee3\u7801\u54cd\u5e94\u3002\u8981\u4e3a\u4f7f\u7528\u8005\u521b\u5efa\u4e00\u81f4\u7684 API\uff0c\u6211\u4eec\u9700\u8981\u786e\u4fdd\u4e24\u79cd\u9519\u8bef\u7c7b\u578b\u5728\u54cd\u5e94\u4e2d\u90fd\u8fd4\u56de Problem Details JSON\u3002<\/p>\n<h2>Converting exceptions to Problem Details<\/h2>\n<p>\u5c06\u5f02\u5e38\u8f6c\u6362\u4e3a Problem Details<\/p>\n<p>In chapter 4 you learned how to handle exceptions with the ExceptionHandlerMiddleware. You saw that the middleware catches any exceptions from later middleware and generates an error response by executing an error-handling path. You could add the middleware to your pipeline with an error-handling path of &quot;\/error&quot;:<\/p>\n<p>\u5728\u7b2c 4 \u7ae0\u4e2d\uff0c\u60a8\u5b66\u4e60\u4e86\u5982\u4f55\u4f7f\u7528 ExceptionHandlerMiddleware \u5904\u7406\u5f02\u5e38\u3002\u60a8\u770b\u5230\u4e2d\u95f4\u4ef6\u4ece\u540e\u9762\u7684\u4e2d\u95f4\u4ef6\u4e2d\u6355\u83b7\u4efb\u4f55\u5f02\u5e38\uff0c\u5e76\u901a\u8fc7\u6267\u884c\u9519\u8bef\u5904\u7406\u8def\u5f84\u751f\u6210\u9519\u8bef\u54cd\u5e94\u3002\u60a8\u53ef\u4ee5\u5c06\u4e2d\u95f4\u4ef6\u6dfb\u52a0\u5230\u9519\u8bef\u5904\u7406\u8def\u5f84\u4e3a \u201c\/error\u201d \u7684\u7ba1\u9053\u4e2d\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.UseExceptionHandler(&quot;\/error&quot;);\n<\/pre>\n<p>ExceptionHandlerMiddleware invokes this path after it captures an exception to generate the final response. The trouble with this approach for minimal APIs is that you need a dedicated error endpoint, the sole purpose of which is to generate a Problem Details response.<\/p>\n<p>ExceptionHandlerMiddleware \u5728\u6355\u83b7\u5f02\u5e38\u540e\u8c03\u7528\u6b64\u8def\u5f84\u4ee5\u751f\u6210\u6700\u7ec8\u54cd\u5e94\u3002\u5bf9\u4e8e\u6700\u5c0f API\uff0c\u8fd9\u79cd\u65b9\u6cd5\u7684\u95ee\u9898\u5728\u4e8e\uff0c\u60a8\u9700\u8981\u4e00\u4e2a\u4e13\u7528\u7684\u9519\u8bef\u7ec8\u7aef\u8282\u70b9\uff0c\u5176\u552f\u4e00\u76ee\u7684\u662f\u751f\u6210 Problem Details \u54cd\u5e94\u3002<\/p>\n<p>Luckily, in .NET 7, you can configure the ExceptionHandlerMiddleware (and DeveloperExceptionPageMiddleware) to convert an exception to a Problem Details response automatically. In .NET 7, you can add the new IProblemDetailsService to your app by calling AddProblemDetails() on WebApplicationBuilder.Services. When the ExceptionHandlerMiddleware is configured without an error-handling path, it automatically uses the IProblemDetailsService to generate the response, as shown in figure 5.6.<\/p>\n<p>\u5e78\u8fd0\u7684\u662f\uff0c\u5728 .NET 7 \u4e2d\uff0c\u60a8\u53ef\u4ee5\u914d\u7f6e ExceptionHandlerMiddleware\uff08\u548c DeveloperExceptionPageMiddleware\uff09\u4ee5\u81ea\u52a8\u5c06\u5f02\u5e38\u8f6c\u6362\u4e3a Problem Details \u54cd\u5e94\u3002\u5728.NET 7 \u4e2d\uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7\u5728 WebApplicationBuilder.Services \u4e0a\u8c03\u7528 AddProblemDetails\uff08\uff09 \u5c06\u65b0\u7684 IProblemDetailsService \u6dfb\u52a0\u5230\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u3002\u5f53 ExceptionHandlerMiddleware \u914d\u7f6e\u4e3a\u6ca1\u6709\u9519\u8bef\u5904\u7406\u8def\u5f84\u65f6\uff0c\u5b83\u4f1a\u81ea\u52a8\u4f7f\u7528 IProblemDetailsService \u6765\u751f\u6210\u54cd\u5e94\uff0c\u5982\u56fe 5.6 \u6240\u793a\u3002<\/p>\n<p><b>WARNING<\/b> Calling AddProblemDetails() registers the IProblemDetailsService service in the dependency injection container so that other services and middleware can use it. If you configure ExceptionHandlerMiddleware without an error-handling path but forget to call AddProblemDetails(), you\u2019ll get an exception when your app starts. You\u2019ll learn more about dependency injection in chapters 8 and 9.<br \/>\n\u8b66\u544a: \u8c03\u7528 AddProblemDetails\uff08\uff09 \u4f1a\u5728\u4f9d\u8d56\u9879\u6ce8\u5165\u5bb9\u5668\u4e2d\u6ce8\u518c IProblemDetailsService \u670d\u52a1\uff0c\u4ee5\u4fbf\u5176\u4ed6\u670d\u52a1\u548c\u4e2d\u95f4\u4ef6\u53ef\u4ee5\u4f7f\u7528\u5b83\u3002\u5982\u679c\u4f60\u5728\u914d\u7f6e ExceptionHandlerMiddleware \u65f6\u6ca1\u6709\u9519\u8bef\u5904\u7406\u8def\u5f84\uff0c\u4f46\u5fd8\u8bb0\u8c03\u7528 AddProblemDetails\uff08\uff09\uff0c\u90a3\u4e48\u5f53\u5e94\u7528\u542f\u52a8\u65f6\u4f1a\u51fa\u73b0\u5f02\u5e38\u3002\u60a8\u5c06\u5728\u7b2c 8 \u7ae0\u548c\u7b2c 9 \u7ae0\u4e2d\u4e86\u89e3\u6709\u5173\u4f9d\u8d56\u5173\u7cfb\u6ce8\u5165\u7684\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0506.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 5.6. The ExceptionHandlerMiddleware catches exceptions that occur later in the middleware pipeline. If the middleware isn\u2019t configured to reexecute the pipeline, it generates a Problem Details response by using the IProblemDetailsService.<br \/>\n\u56fe 5.6 ExceptionHandlerMiddleware \u6355\u83b7\u4e2d\u95f4\u4ef6\u7ba1\u9053\u4e2d\u7a0d\u540e\u53d1\u751f\u7684\u5f02\u5e38\u3002\u5982\u679c\u4e2d\u95f4\u4ef6\u672a\u914d\u7f6e\u4e3a\u91cd\u65b0\u6267\u884c\u7ba1\u9053\uff0c\u5b83\u5c06\u4f7f\u7528 IProblemDetailsService \u751f\u6210 Problem Details \u54cd\u5e94\u3002<\/p>\n<p>Listing 5.6 shows how to configure Problem Details generation in your exception handlers. Add the required IProblemDetailsService service to your app, and call UseExceptionHandler() without providing an error-handling path, and the middleware will generate a Problem Details response automatically when it catches an exception.<\/p>\n<p>\u6e05\u5355 5.6 \u5c55\u793a\u4e86\u5982\u4f55\u5728\u5f02\u5e38\u5904\u7406\u7a0b\u5e8f\u4e2d\u914d\u7f6e Problem Details \u751f\u6210\u3002\u5c06\u6240\u9700\u7684 IProblemDetailsService \u670d\u52a1\u6dfb\u52a0\u5230\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\uff0c\u5e76\u5728\u4e0d\u63d0\u4f9b\u9519\u8bef\u5904\u7406\u8def\u5f84\u7684\u60c5\u51b5\u4e0b\u8c03\u7528 UseExceptionHandler\uff08\uff09\uff0c\u4e2d\u95f4\u4ef6\u5c06\u5728\u6355\u83b7\u5f02\u5e38\u65f6\u81ea\u52a8\u751f\u6210 Problem Details \u54cd\u5e94\u3002<\/p>\n<p>Listing 5.6 Configuring ExceptionHandlerMiddleware to use Problem Details<br \/>\n\u6e05\u5355 5.6 \u914d\u7f6eExceptionHandlerMiddleware\u4f7f\u7528 Problem Details<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddProblemDetails();                   \/\/\u2776\n\nWebApplication app = builder.Build();\n\nif (!app.Environment.IsDevelopment())\n{\n    app.UseExceptionHandler();                           \/\/\u2777\n}\n\napp.MapGet(&quot;\/&quot;, void () =&gt; throw new Exception());       \/\/\u2778\n\napp.Run();\n<\/pre>\n<p>\u2776 Adds the IProblemDetailsService implementation<br \/>\n\u6dfb\u52a0 IProblemDetailsService \u5b9e\u73b0<\/p>\n<p>\u2777 Configures the ExceptionHandlerMiddleware without a path so that it uses the IProblemDetailsService<br \/>\n\u914d\u7f6e\u4e0d\u5e26\u8def\u5f84\u7684 ExceptionHandlerMiddleware\uff0c\u4ee5\u4fbf\u5b83\u4f7f\u7528 IProblemDetailsService<\/p>\n<p>\u2778 Throws an exception to demonstrate the behavior<br \/>\n\u5f15\u53d1\u5f02\u5e38\u4ee5\u6f14\u793a\u884c\u4e3a<\/p>\n<p>As discussed in chapter 4, WebApplication automatically adds the DeveloperExceptionPageMiddleware to your app in the development environment. This middleware similarly supports returning Problem Details when two conditions are satisfied:<\/p>\n<p>\u5982\u7b2c 4 \u7ae0\u6240\u8ff0\uff0cWebApplication \u4f1a\u81ea\u52a8\u5c06 DeveloperExceptionPageMiddleware \u6dfb\u52a0\u5230\u5f00\u53d1\u73af\u5883\u4e2d\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\u3002\u6b64\u4e2d\u95f4\u4ef6\u540c\u6837\u652f\u6301\u5728\u6ee1\u8db3\u4e24\u4e2a\u6761\u4ef6\u65f6\u8fd4\u56de Problem Details\uff1a<\/p>\n<p>\u2022   You\u2019ve registered an IProblemDetailsService with the app (by calling AddProblemDetails() in Program.cs)<br \/>\n\u60a8\u5df2\u5411\u5e94\u7528\u7a0b\u5e8f\u6ce8\u518c\u4e86 IProblemDetailsService\uff08\u901a\u8fc7\u5728 Program.cs \u4e2d\u8c03\u7528 AddProblemDetails\uff08\uff09\uff09\u3002<\/p>\n<p>\u2022   The request indicates that it doesn\u2019t support HTML. If the client supports HTML, middleware uses the HTML developer exception page from chapter 4 instead.<br \/>\n\u8be5\u8bf7\u6c42\u6307\u793a\u5b83\u4e0d\u652f\u6301 HTML\u3002\u5982\u679c\u5ba2\u6237\u7aef\u652f\u6301 HTML\uff0c\u4e2d\u95f4\u4ef6\u4f1a\u6539\u7528\u7b2c 4 \u7ae0\u4e2d\u7684 HTML \u5f00\u53d1\u4eba\u5458\u4f8b\u5916\u9875\u9762\u3002<\/p>\n<p>The ExceptionHandlerMiddleware and DeveloperExceptionPageMiddleware take care of converting all your exceptions to Problem Details responses, but you still need to think about nonexception errors, such as the automatic 404 response generated when a request doesn\u2019t match any endpoints.<\/p>\n<p>ExceptionHandlerMiddleware \u548c DeveloperExceptionPageMiddleware \u8d1f\u8d23\u5c06\u6240\u6709\u5f02\u5e38\u8f6c\u6362\u4e3a Problem Details \u54cd\u5e94\uff0c\u4f46\u60a8\u4ecd\u7136\u9700\u8981\u8003\u8651\u975e\u5f02\u5e38\u9519\u8bef\uff0c\u4f8b\u5982\u5f53\u8bf7\u6c42\u4e0e\u4efb\u4f55\u7aef\u70b9\u4e0d\u5339\u914d\u65f6\u751f\u6210\u7684\u81ea\u52a8 404 \u54cd\u5e94\u3002<\/p>\n<h2>Converting error status codes to Problem Details<\/h2>\n<p>C\u5c06\u9519\u8bef\u72b6\u6001\u4ee3\u7801\u53cd\u8f6c\u4e3a PROBLEM DETAILS<\/p>\n<p>Returning error status codes is the common way to communicate errors to a client with minimal APIs. To ensure a consistent API for consumers, you should return a Problem Details response whenever you return an error. Unfortunately, as already mentioned, you don\u2019t control all the places where an error code may be created. The middleware pipeline automatically returns a 404 response when an unmatched request reaches the end of the pipeline, for example.<\/p>\n<p>\u8fd4\u56de\u9519\u8bef\u72b6\u6001\u4ee3\u7801\u662f\u4f7f\u7528\u6700\u5c11 API \u5c06\u9519\u8bef\u4f20\u8fbe\u7ed9\u5ba2\u6237\u7aef\u7684\u5e38\u7528\u65b9\u6cd5\u3002\u4e3a\u4e86\u786e\u4fdd\u4f7f\u7528\u8005\u7684 API \u4e00\u81f4\uff0c\u60a8\u5e94\u8be5\u5728\u8fd4\u56de\u9519\u8bef\u65f6\u8fd4\u56de Problem Details \u54cd\u5e94\u3002\u9057\u61be\u7684\u662f\uff0c\u5982\u524d\u6240\u8ff0\uff0c\u60a8\u65e0\u6cd5\u63a7\u5236\u53ef\u80fd\u521b\u5efa\u9519\u8bef\u4ee3\u7801\u7684\u6240\u6709\u4f4d\u7f6e\u3002\u4f8b\u5982\uff0c\u5f53\u4e0d\u5339\u914d\u7684\u8bf7\u6c42\u5230\u8fbe\u7ba1\u9053\u672b\u5c3e\u65f6\uff0c\u4e2d\u95f4\u4ef6\u7ba1\u9053\u4f1a\u81ea\u52a8\u8fd4\u56de 404 \u54cd\u5e94\u3002<\/p>\n<p>Instead of generating a Problem Details response in your endpoint handlers, you can add middleware to convert responses to Problem Details automatically by using the StatusCodePagesMiddleware, as shown in figure 5.7. Any response that reaches the middleware with an error status code and doesn\u2019t already have a body has a Problem Details body added by the middleware. The middleware converts all error responses automatically, regardless of whether they were generated by an endpoint or from other middleware.<\/p>\n<p>\u60a8\u53ef\u4ee5\u6dfb\u52a0\u4e2d\u95f4\u4ef6\u6765\u8f6c\u6362\u4f7f\u7528 StatusCodePagesMiddleware \u81ea\u52a8\u54cd\u5e94 Problem Details\uff0c\u5982\u56fe 5.7 \u6240\u793a\u3002\u4efb\u4f55\u5230\u8fbe\u4e2d\u95f4\u4ef6\u65f6\u5e26\u6709\u9519\u8bef\u72b6\u6001\u4ee3\u7801\u4e14\u5c1a\u672a\u5177\u6709\u6b63\u6587\u7684\u54cd\u5e94\u90fd\u4f1a\u7531\u4e2d\u95f4\u4ef6\u6dfb\u52a0 Problem Details \u6b63\u6587\u3002\u4e2d\u95f4\u4ef6\u4f1a\u81ea\u52a8\u8f6c\u6362\u6240\u6709\u9519\u8bef\u54cd\u5e94\uff0c\u65e0\u8bba\u5b83\u4eec\u662f\u7531\u7ec8\u7aef\u8282\u70b9\u8fd8\u662f\u6765\u81ea\u5176\u4ed6\u4e2d\u95f4\u4ef6\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0507.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 5.7 The StatusCodePagesMiddleware intercepts responses with an error status code that have no response body and adds a Problem Details response body.<\/p>\n<p>\u56fe 5.7 StatusCodePagesMiddleware \u62e6\u622a\u5e26\u6709\u9519\u8bef\u72b6\u6001\u7801\u4e14\u6ca1\u6709\u54cd\u5e94\u4f53\u7684\u54cd\u5e94\uff0c\u5e76\u6dfb\u52a0 Problem Details \u54cd\u5e94\u4f53\u3002<\/p>\n<p><b>NOTE<\/b>  You can also use the StatusCodePagesMiddleware to reexecute the middleware pipeline with an error handling path, as you can with the ExceptionHandlerMiddleware (chapter 4). This technique is most useful for Razor Pages applications when you want to have a different error page for specific status codes, as you\u2019ll see in chapter 15.<br \/>\n\u6ce8\u610f: \u60a8\u8fd8\u53ef\u4ee5\u4f7f\u7528 StatusCodePagesMiddleware \u901a\u8fc7\u9519\u8bef\u5904\u7406\u8def\u5f84\u91cd\u65b0\u6267\u884c\u4e2d\u95f4\u4ef6\u7ba1\u9053\uff0c\u5c31\u50cf\u4f7f\u7528 ExceptionHandlerMiddleware\uff08\u7b2c 4 \u7ae0\uff09\u4e00\u6837\u3002\u5f53\u5e0c\u671b\u4e3a\u7279\u5b9a\u72b6\u6001\u4ee3\u7801\u4f7f\u7528\u4e0d\u540c\u7684\u9519\u8bef\u9875\u65f6\uff0c\u6b64\u6280\u672f\u5bf9 Razor Pages \u5e94\u7528\u7a0b\u5e8f\u6700\u6709\u7528\uff0c\u5982\u7b2c 15 \u7ae0\u6240\u793a\u3002<\/p>\n<p>Add the StatusCodePagesMiddleware to your app by using the UseStatusCodePages() extension method, as shown in listing 5.7. Ensure that you also add the IProblemDetailsService to your app by using AddProblemDetails().<\/p>\n<p>\u4f7f\u7528 UseStatusCodePages\uff08\uff09 \u6269\u5c55\u65b9\u6cd5\u5c06 StatusCodePagesMiddleware \u6dfb\u52a0\u5230\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u5982\u4e0b\u9762\u7684\u6e05\u5355\u6240\u793a\u3002\u786e\u4fdd\u8fd8\u4f7f\u7528 AddProblemDetails\uff08\uff09 \u5c06 IProblemDetailsService \u6dfb\u52a0\u5230\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u3002<\/p>\n<p>Listing 5.7 Using StatusCodePagesMiddleware to return Problem Details<br \/>\n\u6e05\u5355 5.7 \u4f7f\u7528 StatusCodePagesMiddleware \u8fd4\u56de\u95ee\u9898\u8be6\u60c5<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nbuilder.Services.AddProblemDetails();            \/\/ \u2776\n\nWebApplication app = builder.Build();\n\nif (!app.Environment.IsDevelopment())\n{\n    app.UseExceptionHandler();\n}\n\napp.UseStatusCodePages();                        \/\/ \u2777\n\napp.MapGet(&quot;\/&quot;, () =&gt; Results.NotFound());       \/\/ \u2778\n\napp.Run();\n<\/pre>\n<p>\u2776 Adds the IProblemDetailsService implementation<br \/>\n\u6dfb\u52a0 IProblemDetailsService \u5b9e\u73b0<\/p>\n<p>\u2777 Adds the StatusCodePagesMiddleware<br \/>\n\u6dfb\u52a0\u4e86 StatusCodePagesMiddleware<\/p>\n<p>\u2778 The StatusCodePagesMiddleware automatically adds a Problem Details body to the 404 response.<br \/>\nStatusCodePagesMiddleware \u4f1a\u81ea\u52a8\u5c06 Problem Details \u6b63\u6587\u6dfb\u52a0\u5230 404 \u54cd\u5e94\u4e2d\u3002<\/p>\n<p>The StatusCodePagesMiddleware, coupled with exception-handling middleware, ensures that your API returns a Problem Details response for all error responses.<\/p>\n<p>StatusCodePagesMiddleware \u4e0e\u5f02\u5e38\u5904\u7406\u4e2d\u95f4\u4ef6\u76f8\u7ed3\u5408\uff0c\u53ef\u786e\u4fdd API \u4e3a\u6240\u6709\u9519\u8bef\u54cd\u5e94\u8fd4\u56de Problem Details \u54cd\u5e94\u3002<\/p>\n<p><b>TIP<\/b>  You can also customize how the Problem Details response is generated by passing parameters to the AddProblemDetails() method or by implementing your own IProblemDetailsService.<br \/>\n\u63d0\u793a: \u60a8\u8fd8\u53ef\u4ee5\u901a\u8fc7\u5411 AddProblemDetails\uff08\uff09 \u65b9\u6cd5\u4f20\u9012\u53c2\u6570\u6216\u901a\u8fc7\u5b9e\u73b0\u60a8\u81ea\u5df1\u7684 IProblemDetailsService \u6765\u81ea\u5b9a\u4e49\u751f\u6210\u95ee\u9898\u8be6\u7ec6\u4fe1\u606f\u54cd\u5e94\u7684\u65b9\u5f0f\u3002<\/p>\n<p>So far in section 5.3, I\u2019ve described returning objects as JSON, returning strings as text, and returning custom status codes and Problem Details by using Results. Sometimes, however, you need to return something bigger, such as a file or a binary. Luckily, you can use the convenient Results class for that task too.<br \/>\n\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u5728\u7b2c 5.3 \u8282\u4e2d\uff0c\u6211\u5df2\u7ecf\u4ecb\u7ecd\u4e86\u4ee5 JSON \u683c\u5f0f\u8fd4\u56de\u5bf9\u8c61\u3001\u4ee5\u6587\u672c\u683c\u5f0f\u8fd4\u56de\u5b57\u7b26\u4e32\u4ee5\u53ca\u4f7f\u7528 Results \u8fd4\u56de\u81ea\u5b9a\u4e49\u72b6\u6001\u4ee3\u7801\u548c\u95ee\u9898\u8be6\u7ec6\u4fe1\u606f\u3002\u4f46\u662f\uff0c\u6709\u65f6\u60a8\u9700\u8981\u8fd4\u56de\u66f4\u5927\u7684\u5185\u5bb9\uff0c\u4f8b\u5982\u6587\u4ef6\u6216\u4e8c\u8fdb\u5236\u6587\u4ef6\u3002\u5e78\u8fd0\u7684\u662f\uff0c\u60a8\u4e5f\u53ef\u4ee5\u4f7f\u7528\u65b9\u4fbf\u7684 Results \u7c7b\u6765\u5b8c\u6210\u8be5\u4efb\u52a1\u3002<\/p>\n<h3>5.3.4 Returning other data types<\/h3>\n<h3>5.3.4 \u8fd4\u56de\u5176\u4ed6\u6570\u636e\u7c7b\u578b<\/h3>\n<p>The methods on Results and TypedResults are convenient ways of returning common responses, so it\u2019s only natural that they include helpers for other common scenarios, such as returning a file or binary data:<br \/>\nResults \u548c TypedResults \u4e0a\u7684\u65b9\u6cd5\u662f\u8fd4\u56de\u5e38\u89c1\u54cd\u5e94\u7684\u4fbf\u6377\u65b9\u6cd5\uff0c\u56e0\u6b64\u5b83\u4eec\u5305\u542b\u5176\u4ed6\u5e38\u89c1\u65b9\u6848\uff08\u4f8b\u5982\u8fd4\u56de\u6587\u4ef6\u6216\u4e8c\u8fdb\u5236\u6570\u636e\uff09\u7684\u5e2e\u52a9\u7a0b\u5e8f\u662f\u5f88\u81ea\u7136\u7684\uff1a<\/p>\n<p>\u2022   Results.File()\u2014Pass in the path of the file to return, and ASP.NET Core takes care of streaming it to the client.<br \/>\nResults.File\uff08\uff09 - \u4f20\u5165\u8981\u8fd4\u56de\u7684\u6587\u4ef6\u7684\u8def\u5f84\uff0cASP.NET Core \u8d1f\u8d23\u5c06\u5176\u6d41\u5f0f\u4f20\u8f93\u5230\u5ba2\u6237\u7aef\u3002<\/p>\n<p>\u2022   Results.Byte()\u2014For returning binary data, you can pass this method a byte[] to return.<br \/>\nResults.Byte\uff08\uff09 \u2014 \u8981\u8fd4\u56de\u4e8c\u8fdb\u5236\u6570\u636e\uff0c\u60a8\u53ef\u4ee5\u5411\u6b64\u65b9\u6cd5\u4f20\u9012\u4e00\u4e2a byte[] \u4ee5\u8fd4\u56de\u3002<\/p>\n<p>\u2022   Results.Stream()\u2014You can send data to the client asynchronously by using a Stream.<br \/>\nResults.Stream\uff08\uff09 \u2014 \u60a8\u53ef\u4ee5\u4f7f\u7528 Stream \u5c06\u6570\u636e\u5f02\u6b65\u53d1\u9001\u5230\u5ba2\u6237\u7aef\u3002<\/p>\n<p>In each of these cases, you can provide a content type for the data, and a filename to be used by the client. Browsers offer to save binary data files using the suggested filename. The File and Byte methods even support range requests by specifying enableRangeProcessing as true.<br \/>\n\u5728\u4e0a\u8ff0\u6bcf\u79cd\u60c5\u51b5\u4e0b\uff0c\u60a8\u90fd\u53ef\u4ee5\u4e3a\u6570\u636e\u63d0\u4f9b\u5185\u5bb9\u7c7b\u578b\uff0c\u4ee5\u53ca\u5ba2\u6237\u7aef\u8981\u4f7f\u7528\u7684\u6587\u4ef6\u540d\u3002\u6d4f\u89c8\u5668\u63d0\u4f9b\u4f7f\u7528\u5efa\u8bae\u7684\u6587\u4ef6\u540d\u4fdd\u5b58\u4e8c\u8fdb\u5236\u6570\u636e\u6587\u4ef6\u3002File \u548c Byte \u65b9\u6cd5\u751a\u81f3\u901a\u8fc7\u5c06 enableRangeProcessing \u6307\u5b9a\u4e3a true \u6765\u652f\u6301\u8303\u56f4\u8bf7\u6c42\u3002<\/p>\n<p><b>DEFINITION<\/b> Clients can create range requests using the Range header to request a specific range of bytes from the server instead of the whole file, reducing the bandwidth required for a request. When range requests are enabled for Results.File() or Results.Byte(), ASP.NET Core automatically handles generating an appropriate response. You can read more about range requests at <a href=\"http:\/\/mng.bz\/Wzd0\">http:\/\/mng.bz\/Wzd0<\/a>.<br \/>\n\u5b9a\u4e49: \u5ba2\u6237\u7aef\u53ef\u4ee5\u4f7f\u7528 Range \u6807\u5934\u521b\u5efa\u8303\u56f4\u8bf7\u6c42\uff0c\u4ee5\u4ece\u670d\u52a1\u5668\u800c\u4e0d\u662f\u6574\u4e2a\u6587\u4ef6\u8bf7\u6c42\u7279\u5b9a\u8303\u56f4\u7684\u5b57\u8282\uff0c\u4ece\u800c\u51cf\u5c11\u8bf7\u6c42\u6240\u9700\u7684\u5e26\u5bbd\u3002\u5f53\u4e3a Results.File\uff08\uff09 \u6216 Results.Byte\uff08\uff09 \u542f\u7528\u8303\u56f4\u8bf7\u6c42\u65f6\uff0cASP.NET Core \u4f1a\u81ea\u52a8\u5904\u7406\u751f\u6210\u9002\u5f53\u7684\u54cd\u5e94\u3002\u60a8\u53ef\u4ee5\u5728 <a href=\"http:\/\/mng.bz\/Wzd0\">http:\/\/mng.bz\/Wzd0<\/a> \u4e2d\u9605\u8bfb\u6709\u5173\u8303\u56f4\u8bf7\u6c42\u7684\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<p>If the built-in Results helpers don\u2019t provide the functionality you need, you can always fall back to creating a response manually, as in listing 5.4. If you find yourself creating the same manual response several times, you could consider creating a custom IResult type to encapsulate this logic. I show how to create a custom IResult that returns XML and registers it as an extension in this blog post: <a href=\"http:\/\/mng.bz\/8rNP\">http:\/\/mng.bz\/8rNP<\/a>.<\/p>\n<p>\u5982\u679c\u5185\u7f6e\u7684 Results \u5e2e\u52a9\u7a0b\u5e8f\u6ca1\u6709\u63d0\u4f9b\u4f60\u9700\u8981\u7684\u529f\u80fd\uff0c\u4f60\u603b\u662f\u53ef\u4ee5\u56de\u9000\u5230\u624b\u52a8\u521b\u5efa\u54cd\u5e94\uff0c\u5982\u6e05\u5355 5.4 \u6240\u793a\u3002\u5982\u679c\u60a8\u53d1\u73b0\u81ea\u5df1\u591a\u6b21\u521b\u5efa\u76f8\u540c\u7684\u624b\u52a8\u54cd\u5e94\uff0c\u5219\u53ef\u4ee5\u8003\u8651\u521b\u5efa\u81ea\u5b9a\u4e49 IResult \u7c7b\u578b\u6765\u5c01\u88c5\u6b64\u903b\u8f91\u3002\u5728\u4ee5\u4e0b\u535a\u5ba2\u6587\u7ae0\u4e2d\uff0c\u6211\u5c06\u4ecb\u7ecd\u5982\u4f55\u521b\u5efa\u8fd4\u56de XML \u5e76\u5c06\u5176\u6ce8\u518c\u4e3a\u6269\u5c55\u7684\u81ea\u5b9a\u4e49 IResult\uff1a<a href=\"http:\/\/mng.bz\/8rNP\">http:\/\/mng.bz\/8rNP<\/a>\u3002<\/p>\n<h2>5.4 Running common code with endpoint filters<\/h2>\n<h2>\u4f7f\u7528\u7ec8\u7aef\u8282\u70b9\u7b5b\u9009\u5668\u8fd0\u884c\u901a\u7528\u4ee3\u7801<\/h2>\n<p>In section 5.3 you learned how to use Results to return different responses when the request isn\u2019t valid. We\u2019ll look at validation in more detail in chapter 7, but in this section, you\u2019ll learn how to use filters to extract common code that executes before (or after) an endpoint executes.<\/p>\n<p>\u5728\u7b2c 5.3 \u8282\u4e2d\uff0c\u60a8\u5b66\u4e60\u4e86\u5982\u4f55\u5728\u8bf7\u6c42\u65e0\u6548\u65f6\u4f7f\u7528 Results \u8fd4\u56de\u4e0d\u540c\u7684\u54cd\u5e94\u3002\u6211\u4eec\u5c06\u5728\u7b2c 7 \u7ae0\u4e2d\u66f4\u8be6\u7ec6\u5730\u4ecb\u7ecd\u9a8c\u8bc1\uff0c\u4f46\u5728\u672c\u8282\u4e2d\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u4f7f\u7528\u8fc7\u6ee4\u5668\u6765\u63d0\u53d6\u5728\u7aef\u70b9\u6267\u884c\u4e4b\u524d\uff08\u6216\u4e4b\u540e\uff09\u6267\u884c\u7684\u5e38\u89c1\u4ee3\u7801\u3002<\/p>\n<p>Let\u2019s start by adding some extra validation to the fruit API from listing 5.5. The following listing adds an additional check to the MapGet endpoint to ensure that the provided id isn\u2019t empty and that it starts with the letter f.<\/p>\n<p>\u8ba9\u6211\u4eec\u9996\u5148\u5411\u6e05\u5355 5.5 \u4e2d\u7684 fruit API \u6dfb\u52a0\u4e00\u4e9b\u989d\u5916\u7684\u9a8c\u8bc1\u3002\u4e0b\u9762\u7684\u6e05\u5355\u5411 MapGet \u7aef\u70b9\u6dfb\u52a0\u4e86\u4e00\u9879\u989d\u5916\u7684\u68c0\u67e5\uff0c\u4ee5\u786e\u4fdd\u63d0\u4f9b\u7684 id \u4e0d\u4e3a\u7a7a\uff0c\u5e76\u4e14\u5b83\u4ee5\u5b57\u6bcd f \u5f00\u5934\u3002<\/p>\n<p>Listing 5.8 Adding basic validation to minimal API endpoints<br \/>\n\u6e05\u5355 5.8 \u5411\u6700\u5c0fAPI\u6dfb\u52a0\u57fa\u672c\u9a8c\u8bc1\u7aef\u70b9<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\nvar _fruit = new System.Collections.Concurrent.ConcurrentDictionary&lt;string, Fruit&gt;();\n\napp.MapGet(&quot;\/fruit\/{id}&quot;, (string id) =&gt;\n{\n    if (string.IsNullOrEmpty(id) || !id.StartsWith(&#039;f&#039;))                   \/\/ \u2776\n    {\n        return Results.ValidationProblem(new Dictionary&lt;string, string&#x5B;]&gt;\n        {\n            {&quot;id&quot;, new&#x5B;] {&quot;Invalid format. Id must start with &#039;f&#039;&quot;}}\n        });\n    }\n    return _fruit.TryGetValue(id, out var fruit)\n        ? TypedResults.Ok(fruit)\n        : Results.Problem(statusCode: 404);\n});\n\napp.Run();\nrecord Fruit(string Name, int stock);\n<\/pre>\n<p>\u2776 Adds extra validation that the provided id has the required format<br \/>\n\u6dfb\u52a0\u989d\u5916\u7684\u9a8c\u8bc1\uff0c\u8bc1\u660e\u63d0\u4f9b\u7684 ID \u5177\u6709\u6240\u9700\u7684\u683c\u5f0f<\/p>\n<p>Even though this check is basic, it starts to clutter our endpoint handler, making it harder to read what the endpoint is doing. One improvement would be to move the validation code to a helper function. But you\u2019re still inevitably going to clutter your endpoint handlers with calls to methods that are tangential to the main function of your endpoint.<\/p>\n<p>\u5c3d\u7ba1\u6b64\u68c0\u67e5\u662f\u57fa\u672c\u7684\uff0c\u4f46\u5b83\u5f00\u59cb\u4f7f\u6211\u4eec\u7684 endpoint \u5904\u7406\u7a0b\u5e8f\u53d8\u5f97\u6df7\u4e71\uff0c\u4ece\u800c\u66f4\u96be\u8bfb\u53d6 endpoint \u6b63\u5728\u505a\u4ec0\u4e48\u3002\u4e00\u9879\u6539\u8fdb\u662f\u5c06\u9a8c\u8bc1\u4ee3\u7801\u79fb\u52a8\u5230 helper \u51fd\u6570\u3002\u4f46\u662f\uff0c\u60a8\u4ecd\u7136\u4e0d\u53ef\u907f\u514d\u5730\u4f1a\u56e0\u4e3a\u5bf9\u4e0e\u7ec8\u7aef\u8282\u70b9\u7684\u4e3b\u51fd\u6570\u76f8\u5207\u7684\u65b9\u6cd5\u7684\u8c03\u7528\u800c\u4f7f\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u53d8\u5f97\u6df7\u4e71\u3002<\/p>\n<p><b>NOTE<\/b>  Chapter 7 discusses additional validation patterns in detail.<br \/>\n\u6ce8\u610f: \u7b2c 7 \u7ae0\u8be6\u7ec6\u8ba8\u8bba\u4e86\u5176\u4ed6\u9a8c\u8bc1\u6a21\u5f0f\u3002<\/p>\n<p>It\u2019s common to perform various cross-cutting activities for every endpoint. I\u2019ve already mentioned validation; other cross-cutting activities include logging, authorization, and auditing. ASP.NET Core has built-in support for some of these features, such as authorization (chapter 24), but you\u2019re likely to have some common code that doesn\u2019t fit into the specific pigeonholes of validation or authorization.<\/p>\n<p>\u901a\u5e38\u4e3a\u6bcf\u4e2a\u7aef\u70b9\u6267\u884c\u5404\u79cd\u6a2a\u5207\u6d3b\u52a8\u3002\u6211\u5df2\u7ecf\u63d0\u5230\u4e86\u9a8c\u8bc1;\u5176\u4ed6\u6a2a\u5207\u6d3b\u52a8\u5305\u62ec\u65e5\u5fd7\u8bb0\u5f55\u3001\u6388\u6743\u548c\u5ba1\u8ba1\u3002ASP.NET Core \u5185\u7f6e\u4e86\u5bf9\u5176\u4e2d\u4e00\u4e9b\u529f\u80fd\u7684\u652f\u6301\uff0c\u4f8b\u5982\u6388\u6743\uff08\u7b2c 24 \u7ae0\uff09\uff0c\u4f46\u60a8\u53ef\u80fd\u6709\u4e00\u4e9b\u901a\u7528\u4ee3\u7801\u4e0d\u9002\u5408\u9a8c\u8bc1\u6216\u6388\u6743\u7684\u7279\u5b9a\u5206\u7c7b\u3002<\/p>\n<p>Luckily, ASP.NET Core includes a feature in minimal APIs for running these tangential concerns: endpoint filters. You can specify a filter for an endpoint by calling AddEndpointFilter()on the result of a call to MapGet (or similar) and passing in a function to execute. You can even add multiple calls to AddEndpointFilter(), which builds up an endpoint filter pipeline, analogous to the middleware pipeline. Figure 5.8 shows that the pipeline is functionally identical to the middleware pipeline in figure 4.3.<\/p>\n<p>\u5e78\u8fd0\u7684\u662f\uff0cASP.NET Core \u5728\u6700\u5c0f\u7684 API \u4e2d\u5305\u542b\u4e00\u4e2a\u529f\u80fd\uff0c\u7528\u4e8e\u8fd0\u884c\u8fd9\u4e9b\u65e0\u5173\u7d27\u8981\u7684\u95ee\u9898\uff1a\u7ec8\u7aef\u8282\u70b9\u7b5b\u9009\u5668\u3002\u60a8\u53ef\u4ee5\u901a\u8fc7\u5bf9 MapGet\uff08\u6216\u7c7b\u4f3c\uff09\u7684\u8c03\u7528\u7ed3\u679c\u8c03\u7528 AddEndpointFilter\uff08\uff09 \u5e76\u4f20\u5165\u8981\u6267\u884c\u7684\u51fd\u6570\u6765\u4e3a\u7ec8\u7aef\u8282\u70b9\u6307\u5b9a\u8fc7\u6ee4\u5668\u3002\u60a8\u751a\u81f3\u53ef\u4ee5\u6dfb\u52a0\u5bf9 AddEndpointFilter\uff08\uff09 \u7684\u591a\u4e2a\u8c03\u7528\uff0c\u8fd9\u5c06\u6784\u5efa\u4e00\u4e2a\u7aef\u70b9\u8fc7\u6ee4\u5668\u7ba1\u9053\uff0c\u7c7b\u4f3c\u4e8e\u4e2d\u95f4\u4ef6\u7ba1\u9053\u3002\u56fe 5.8 \u663e\u793a\uff0c\u8be5\u7ba1\u9053\u5728\u529f\u80fd\u4e0a\u4e0e\u56fe 4.3 \u4e2d\u7684\u4e2d\u95f4\u4ef6\u7ba1\u9053\u76f8\u540c\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0508.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 5.8. The endpoint filter pipeline. Filters execute code and then call next(context) to invoke the next filter in the pipeline. If there are no more filters in the pipeline, the endpoint handler is invoked. After the handler has executed, the filters may run further code.<br \/>\n\u56fe 5.8 \u7aef\u70b9\u8fc7\u6ee4\u5668\u7ba1\u9053\u3002\u7b5b\u9009\u5668\u6267\u884c\u4ee3\u7801\uff0c\u7136\u540e\u8c03\u7528 next\uff08context\uff09 \u4ee5\u8c03\u7528\u7ba1\u9053\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7b5b\u9009\u5668\u3002\u5982\u679c\u7ba1\u9053\u4e2d\u6ca1\u6709\u66f4\u591a\u7b5b\u9009\u5668\uff0c\u5219\u8c03\u7528\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u3002\u5904\u7406\u7a0b\u5e8f\u6267\u884c\u540e\uff0c\u8fc7\u6ee4\u5668\u53ef\u4ee5\u8fd0\u884c\u66f4\u591a\u4ee3\u7801\u3002<\/p>\n<p>Each endpoint filter has two parameters: a context parameter, which provides details about the selected endpoint handler, and the next parameter, which represents the filter pipeline. When you invoke the methodlike next parameter by calling next(context), you invoke the remainder of the filter pipeline. If there are no more filters in the pipeline, you invoke the endpoint handler, as shown in figure 5.8.<\/p>\n<p>\u6bcf\u4e2a\u7ec8\u7aef\u8282\u70b9\u7b5b\u9009\u6761\u4ef6\u90fd\u6709\u4e24\u4e2a\u53c2\u6570\uff1a\u4e00\u4e2a context \u53c2\u6570\uff08\u63d0\u4f9b\u6709\u5173\u6240\u9009\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u7684\u8be6\u7ec6\u4fe1\u606f\uff09\u548c next \u53c2\u6570\uff08\u8868\u793a\u7b5b\u9009\u6761\u4ef6\u7ba1\u9053\uff09\u3002\u5f53\u60a8\u901a\u8fc7\u8c03\u7528 next\uff08context\uff09 \u8c03\u7528\u7c7b\u4f3c next \u53c2\u6570\u7684 methodlike \u65f6\uff0c\u5c06\u8c03\u7528\u7b5b\u9009\u7ba1\u9053\u7684\u5176\u4f59\u90e8\u5206\u3002\u5982\u679c\u7ba1\u9053\u4e2d\uff0c\u60a8\u53ef\u4ee5\u8c03\u7528\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\uff0c\u5982\u56fe 5.8 \u6240\u793a\u3002<\/p>\n<p>Listing 5.9 shows how to run the same validation logic you saw in listing 5.8 in an endpoint filter. The filter function accesses the endpoint method arguments by using the context.<code>GetArgument&lt;T&gt;()<\/code> function, passing in a position; 0 is the first argument of your endpoint handler, 1 is the second argument, and so on. If the argument isn\u2019t valid, the filter function returns an IResult object response. If the argument is valid, the filter calls await next(context) instead, executing the endpoint handler.<\/p>\n<p>\u6e05\u5355 5.9 \u5c55\u793a\u4e86\u5982\u4f55\u5728\u7aef\u70b9\u8fc7\u6ee4\u5668\u4e2d\u8fd0\u884c\u6e05\u5355 5.8 \u4e2d\u770b\u5230\u7684\u76f8\u540c\u9a8c\u8bc1\u903b\u8f91\u3002filter \u51fd\u6570\u4f7f\u7528\u4e0a\u4e0b\u6587\u8bbf\u95ee\u7aef\u70b9\u65b9\u6cd5\u53c2\u6570\u3002GetArgument<T>\uff08\uff09 \u51fd\u6570\uff0c\u4f20\u5165\u4e00\u4e2a\u4f4d\u7f6e;0 \u662f\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u7684\u7b2c\u4e00\u4e2a\u53c2\u6570\uff0c1 \u662f\u7b2c\u4e8c\u4e2a\u53c2\u6570\uff0c\u4f9d\u6b64\u7c7b\u63a8\u3002\u5982\u679c\u53c2\u6570\u65e0\u6548\uff0c\u5219 filter \u51fd\u6570\u5c06\u8fd4\u56de IResult \u5bf9\u8c61\u54cd\u5e94\u3002\u5982\u679c\u53c2\u6570\u6709\u6548\uff0c\u5219\u7b5b\u9009\u5668\u5c06\u6539\u4e3a\u8c03\u7528 await next\uff08context\uff09\uff0c\u5e76\u6267\u884c\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u3002<\/p>\n<p>Listing 5.9 Using AddEndpointFilter to extract common code<br \/>\n\u6e05\u5355 5.9 \u4f7f\u7528 AddEndpointFilter \u63d0\u53d6\u901a\u7528\u4ee3\u7801<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\nvar _fruit = new System.Collections.Concurrent.ConcurrentDictionary&lt;string, Fruit&gt;();\n\napp.MapGet(&quot;\/fruit\/{id}&quot;, (string id) =&gt;\n    _fruit.TryGetValue(id, out var fruit)\n        ? TypedResults.Ok(fruit)\n        : Results.Problem(statusCode: 404))\n    .AddEndpointFilter(ValidationHelper.ValidateId);                       \/\/ \u2776\n\napp.Run();\n\nclass ValidationHelper\n{\n    internal static async ValueTask&lt;object?&gt; ValidateId(                   \/\/ \u2777\n        EndpointFilterInvocationContext context,                           \/\/ \u2778\n        EndpointFilterDelegate next)                                       \/\/ \u2779\n    {\n        var id = context.GetArgument&lt;string&gt;(0);                           \/\/ \u277a\n        if (string.IsNullOrEmpty(id) || !id.StartsWith(&#039;f&#039;))\n        {\n            return Results.ValidationProblem(\n                new Dictionary&lt;string, string&#x5B;]&gt;\n                {\n                    {&quot;id&quot;, new&#x5B;]{&quot;Invalid format. Id must start with &#039;f&#039;&quot;}}\n                });\n        }\n\n        return await next(context);                                        \/\/ \u277b\n    }\n}  \nrecord Fruit(string Name, int stock);\n<\/pre>\n<p>\u2776 Adds the filter to the endpoint using AddEndpointFilter<br \/>\n\u4f7f\u7528 AddEndpointFilter \u5c06\u7b5b\u9009\u5668\u6dfb\u52a0\u5230\u7aef\u70b9<\/p>\n<p>\u2777 The method must return a ValueTask.<br \/>\n\u8be5\u65b9\u6cd5\u5fc5\u987b\u8fd4\u56de ValueTask\u3002<\/p>\n<p>\u2778 context exposes the endpoint method arguments and the HttpContext.<br \/>\ncontext \u516c\u5f00\u7aef\u70b9\u65b9\u6cd5\u53c2\u6570\u548c HttpContext\u3002<\/p>\n<p>\u2779 next represents the filter method (or endpoint) that will be called next.<br \/>\nnext \u8868\u793a\u63a5\u4e0b\u6765\u5c06\u8c03\u7528\u7684 filter \u65b9\u6cd5 \uff08\u6216 endpoint\uff09\u3002<\/p>\n<p>\u277a You can retrieve the method arguments from the context.<br \/>\n\u60a8\u53ef\u4ee5\u4ece\u4e0a\u4e0b\u6587\u4e2d\u68c0\u7d22\u65b9\u6cd5\u53c2\u6570\u3002<\/p>\n<p>\u277b Calling next executes the remaining filters in the pipeline.<br \/>\n\u8c03\u7528 next \u5c06\u6267\u884c\u7ba1\u9053\u4e2d\u7684\u5269\u4f59\u8fc7\u6ee4\u5668\u3002<\/p>\n<p><b>NOTE<\/b>  The EndpointFilterDelegate is a named delegate type. It\u2019s effectively a <code>Func&lt;EndpointFilterInvocationContext, ValueTask&lt;object?&gt;&gt;<\/code>.<br \/>\n\u6ce8\u610f: EndpointFilterDelegate \u662f\u4e00\u79cd\u547d\u540d\u59d4\u6258\u7c7b\u578b\u3002\u5b83\u5b9e\u9645\u4e0a\u662f <code>Func&lt;EndpointFilterInvocationContext\uff0c ValueTask&lt;object\uff1f&gt;&gt;<\/code>\u3002<\/p>\n<p>There are many parallels between the middleware pipeline and the filter endpoint pipeline, and we\u2019ll explore them in section 5.4.1.<br \/>\nmiddleware pipeline \u548c filter endpoint pipeline \u4e4b\u95f4\u6709\u8bb8\u591a\u76f8\u4f3c\u4e4b\u5904\uff0c\u6211\u4eec\u5c06\u5728 Section 5.4.1 \u4e2d\u63a2\u8ba8\u5b83\u4eec\u3002<\/p>\n<h3>5.4.1 Adding multiple filters to an endpoint<\/h3>\n<h3>5.4.1 \u5411\u7ec8\u7aef\u8282\u70b9 \u6dfb\u52a0\u591a\u4e2a\u7b5b\u9009\u6761\u4ef6<\/h3>\n<p>The middleware pipeline is typically the best place for handling cross-cutting concerns such as logging, authentication, and authorization, as these functions apply to all requests. Nevertheless, it can be common to have additional cross-cutting concerns that are endpoint-specific, as we\u2019ve already discussed. If you need many endpoint-specific operations, you might consider using multiple endpoint filters.<\/p>\n<p>\u4e2d\u95f4\u4ef6\u7ba1\u9053\u901a\u5e38\u662f\u5904\u7406\u6a2a\u5207\u5173\u6ce8\u70b9\uff08\u5982\u65e5\u5fd7\u8bb0\u5f55\u3001\u8eab\u4efd\u9a8c\u8bc1\u548c\u6388\u6743\uff09\u7684\u6700\u4f73\u4f4d\u7f6e\uff0c\u56e0\u4e3a\u8fd9\u4e9b\u529f\u80fd\u9002\u7528\u4e8e\u6240\u6709\u8bf7\u6c42\u3002\u5c3d\u7ba1\u5982\u6b64\uff0c\u6b63\u5982\u6211\u4eec\u5df2\u7ecf\u8ba8\u8bba\u8fc7\u7684\uff0c\u901a\u5e38\u8fd8\u4f1a\u6709\u7279\u5b9a\u4e8e\u7aef\u70b9\u7684\u5176\u4ed6\u6a2a\u5207\u5173\u6ce8\u70b9\u3002\u5982\u679c\u60a8\u9700\u8981\u8bb8\u591a\u7279\u5b9a\u4e8e\u7ec8\u7aef\u8282\u70b9\u7684\u4f5c\uff0c\u5219\u53ef\u4ee5\u8003\u8651\u4f7f\u7528\u591a\u4e2a\u7ec8\u7aef\u8282\u70b9\u7b5b\u9009\u6761\u4ef6\u3002<\/p>\n<p>As you saw in figure 5.8, adding multiple filters to an endpoint builds up a pipeline. Like the middleware pipeline, the endpoint filter pipeline can execute code both before and after the rest of the pipeline executes. Similarly, the filter pipeline can short-circuit in the same way as the middleware pipeline by returning a result and not calling next.<\/p>\n<p>\u5982\u56fe 5.8 \u6240\u793a\uff0c\u5411\u7aef\u70b9\u6dfb\u52a0\u591a\u4e2a\u8fc7\u6ee4\u5668\u4f1a\u6784\u5efa\u4e00\u4e2a\u7ba1\u9053\u3002\u4e0e\u4e2d\u95f4\u4ef6\u7ba1\u9053\u4e00\u6837\uff0c\u7ec8\u7aef\u8282\u70b9\u7b5b\u9009\u5668\u7ba1\u9053\u53ef\u4ee5\u5728\u7ba1\u9053\u7684\u5176\u4f59\u90e8\u5206\u6267\u884c\u4e4b\u524d\u548c\u4e4b\u540e\u6267\u884c\u4ee3\u7801\u3002\u540c\u6837\uff0cfilter \u7ba1\u9053\u53ef\u4ee5\u50cf\u4e2d\u95f4\u4ef6\u7ba1\u9053\u4e00\u6837\u77ed\u8def\uff0c\u65b9\u6cd5\u662f\u8fd4\u56de result \u800c\u4e0d\u8c03\u7528 next\u3002<\/p>\n<p><b>NOTE<\/b>  You\u2019ve already seen an example of a short circuit in the filter pipeline. In listing 5.9 we short-circuit the pipeline if the id is invalid by returning a Problem Details object instead of calling next(context).<br \/>\n\u6ce8\u610f: \u60a8\u5df2\u7ecf\u770b\u5230\u4e86 filter pipeline \u4e2d\u77ed\u8def\u7684\u793a\u4f8b\u3002\u5728\u793a\u4f8b 5.9 \u4e2d\uff0c\u5982\u679c id \u65e0\u6548\uff0c\u6211\u4eec\u901a\u8fc7\u8fd4\u56de Problem Details \u5bf9\u8c61\u800c\u4e0d\u662f\u8c03\u7528 next\uff08context\uff09 \u6765\u77ed\u8def\u7ba1\u9053\u3002<\/p>\n<p>As with middleware, the order in which you add filters to the endpoint filter pipeline is important. The filters you add first are called first in the pipeline, and filters you add last are called last. On the return journey through the pipeline, after the endpoint handler is invoked, the filters are called in reverse order, as with the middleware pipeline. As an example, consider the following listing, which adds an extra filter to the endpoint shown in listing 5.9.<\/p>\n<p>\u4e0e\u4e2d\u95f4\u4ef6\u4e00\u6837\uff0c\u5c06\u7b5b\u9009\u5668\u6dfb\u52a0\u5230\u7ec8\u7aef\u8282\u70b9\u7b5b\u9009\u5668\u7ba1\u9053\u7684\u987a\u5e8f\u4e5f\u5f88\u91cd\u8981\u3002\u60a8\u9996\u5148\u6dfb\u52a0\u7684\u8fc7\u6ee4\u5668\u5728\u7ba1\u9053\u4e2d\u79f0\u4e3a first\uff0c\u60a8\u6700\u540e\u6dfb\u52a0\u7684\u8fc7\u6ee4\u5668\u79f0\u4e3a last\u3002\u5728\u901a\u8fc7\u7ba1\u9053\u7684\u8fd4\u56de\u65c5\u7a0b\u4e2d\uff0c\u8c03\u7528\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u540e\uff0c\u8fc7\u6ee4\u5668\u5c06\u6309\u76f8\u53cd\u7684\u987a\u5e8f\u8c03\u7528\uff0c\u5c31\u50cf\u4e2d\u95f4\u4ef6\u7ba1\u9053\u4e00\u6837\u3002\u4f8b\u5982\uff0c\u8003\u8651\u4e0b\u9762\u7684\u6e05\u5355\uff0c\u5b83\u5411\u6e05\u5355 5.9 \u4e2d\u6240\u793a\u7684\u7aef\u70b9\u6dfb\u52a0\u4e86\u4e00\u4e2a\u989d\u5916\u7684\u8fc7\u6ee4\u5668\u3002<\/p>\n<p>Listing 5.10 Adding multiple filters to the endpoint filter pipeline<br \/>\n\u5217\u8868 5.10 \u5411\u7aef\u70b9\u6dfb\u52a0\u591a\u4e2a\u8fc7\u6ee4\u5668\u8fc7\u6ee4\u7ba1\u9053<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\nvar _fruit = new System.Collections.Concurrent.ConcurrentDictionary&lt;string, Fruit&gt;();\n\napp.MapGet(&quot;\/fruit\/{id}&quot;, (string id) =&gt;\n    _fruit.TryGetValue(id, out var fruit)\n        ? TypedResults.Ok(fruit)\n        : Results.Problem(statusCode: 404))\n    .AddEndpointFilter(ValidationHelper.ValidateId)                 \/\/ \u2776 \u8fd9\u4e2aValidationHelper.ValidateId\u5728\u4e0b\u9762\u5b9a\u4e49\u4e86\n    .AddEndpointFilter(async (context, next) =&gt;                     \/\/ \u2777\n    {\n    app.Logger.LogInformation(&quot;Executing filter...&quot;);               \/\/ \u2778\n        object? result = await next(context);                       \/\/ \u2779\n        app.Logger.LogInformation($&quot;Handler result: {result}&quot;);     \/\/ \u277a\n        return result;                                              \/\/ \u277b\n    });\n\napp.Run();\nrecord Fruit(string Name, int stock);\n\n\nclass ValidationHelper\n{\n    internal static async ValueTask&lt;object?&gt; ValidateId(\n        EndpointFilterInvocationContext context, EndpointFilterDelegate next)\n    {\n        var id = context.GetArgument&lt;string&gt;(0);\n        if (string.IsNullOrEmpty(id) || !id.StartsWith(&#039;f&#039;))\n        {\n            return Results.ValidationProblem(new Dictionary&lt;string, string&#x5B;]&gt;\n            {\n                { &quot;id&quot;, new&#x5B;] { &quot;Invalid format. Id must start with &#039;f&#039;&quot; } }\n            });\n        }\n        return await next(context);\n    }\n\n    internal static EndpointFilterDelegate ValidateIdFactory(\n        EndpointFilterFactoryContext context, EndpointFilterDelegate next)\n    {\n        System.Reflection.ParameterInfo&#x5B;] parameters = context.MethodInfo.GetParameters();\n        int? idPosition = null;\n        for (int i = 0; i &lt; parameters.Length; i++)\n        {\n            if (parameters&#x5B;i].Name == &quot;id&quot; &amp;&amp;\n                parameters&#x5B;i].ParameterType == typeof(string))\n            {\n                idPosition = i;\n                break;\n            }\n        }\n\n        if (!idPosition.HasValue)\n        {\n            return next;\n        }\n\n        return async (invocationContext) =&gt;\n        {\n            var id = invocationContext.GetArgument&lt;string&gt;(idPosition.Value);\n            if (string.IsNullOrEmpty(id) || !id.StartsWith(&#039;f&#039;))\n            {\n                return Results.ValidationProblem(new Dictionary&lt;string, string&#x5B;]&gt;\n                {\n                    { &quot;id&quot;, new&#x5B;] { &quot;Invalid format. Id must start with &#039;f&#039;&quot; } }\n                });\n            }\n            return await next(invocationContext);\n        };\n    }\n}\nclass IdValidationFilter : IEndpointFilter\n{\n    public async ValueTask&lt;object?&gt; InvokeAsync(EndpointFilterInvocationContext context, EndpointFilterDelegate next)\n    {\n        var id = context.GetArgument&lt;string&gt;(0);\n        if (string.IsNullOrEmpty(id) || !id.StartsWith(&#039;f&#039;))\n        {\n            return Results.ValidationProblem(new Dictionary&lt;string, string&#x5B;]&gt;\n        {\n            { &quot;id&quot;, new&#x5B;] { &quot;Invalid format. Id must start with &#039;f&#039;&quot; } }\n        });\n        }\n        return await next(context);\n    }\n}\n\n<\/pre>\n<p>\u2776 Adds the validation filter as before<br \/>\n\u50cf\u4ee5\u524d\u4e00\u6837\u6dfb\u52a0\u9a8c\u8bc1\u8fc7\u6ee4\u5668<\/p>\n<p>\u2777 Adds a new filter using a lambda function<br \/>\n\u4f7f\u7528 lambda \u51fd\u6570\u6dfb\u52a0\u65b0\u7684\u7b5b\u9009\u6761\u4ef6<\/p>\n<p>\u2778 Logs a message before executing the rest of the pipeline<br \/>\n\u5728\u6267\u884c\u7ba1\u9053\u7684\u5176\u4f59\u90e8\u5206\u4e4b\u524d\u8bb0\u5f55\u4e00\u6761\u6d88\u606f<\/p>\n<p>\u2779 Executes the remainder of the pipeline and the endpoint handler<br \/>\n\u6267\u884c\u7ba1\u9053\u7684\u5176\u4f59\u90e8\u5206\u548c\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f<\/p>\n<p>\u277a Logs the result returned by the rest of the pipeline<br \/>\n\u8bb0\u5f55\u7ba1\u9053\u5176\u4f59\u90e8\u5206\u8fd4\u56de\u7684\u7ed3\u679c<\/p>\n<p>\u277b Returns the result unmodified<br \/>\n\u8fd4\u56de\u672a\u7ecf\u4fee\u6539\u7684\u7ed3\u679c<\/p>\n<p>The extra filter is implemented as a lambda function and simply writes a log message when it executes. Then it runs the rest of the filter pipeline (which contains only the endpoint handler in this example) and logs the result returned by the pipeline. Chapter 26 covers logging in detail. For this example, we\u2019ll look at the logs written to the console.<\/p>\n<p>\u989d\u5916\u7684\u7b5b\u9009\u6761\u4ef6\u4f5c\u4e3a lambda \u51fd\u6570\u5b9e\u73b0\uff0c\u53ea\u9700\u5728\u6267\u884c\u65f6\u5199\u5165\u65e5\u5fd7\u6d88\u606f\u3002\u7136\u540e\uff0c\u5b83\u8fd0\u884c\u7b5b\u9009\u6761\u4ef6\u7ba1\u9053\u7684\u5176\u4f59\u90e8\u5206\uff08\u5728\u672c\u4f8b\u4e2d\u4ec5\u5305\u542b\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\uff09\u5e76\u8bb0\u5f55\u7ba1\u9053\u8fd4\u56de\u7684\u7ed3\u679c\u3002\u7b2c 26 \u7ae0\u8be6\u7ec6\u4ecb\u7ecd\u4e86\u65e5\u5fd7\u8bb0\u5f55\u3002\u5728\u6b64\u793a\u4f8b\u4e2d\uff0c\u6211\u4eec\u5c06\u67e5\u770b\u5199\u5165\u63a7\u5236\u53f0\u7684\u65e5\u5fd7\u3002<\/p>\n<p>Figure 5.9 shows the log messages written when we send two requests to the API in listing 5.10. The first request is for an entry that exists, so it returns a 200 OK result. The second request uses an invalid id format, so the first filter rejects it. Figure 5.9 shows that neither the second filter nor the endpoint handler runs in this case; the filter pipeline has been short-circuited.<br \/>\n\u56fe 5.9 \u663e\u793a\u4e86\u6211\u4eec\u5411 API \u53d1\u9001\u4e24\u4e2a\u8bf7\u6c42\u65f6\u5199\u5165\u7684\u65e5\u5fd7\u6d88\u606f 5.10.\u7b2c\u4e00\u4e2a\u8bf7\u6c42\u662f\u9488\u5bf9\u5b58\u5728\u7684\u6761\u76ee\uff0c\u56e0\u6b64\u5b83\u8fd4\u56de 200 OK \u7ed3\u679c\u3002\u7b2c\u4e8c\u4e2a\u8bf7\u6c42\u4f7f\u7528\u65e0\u6548\u7684 id \u683c\u5f0f\uff0c\u56e0\u6b64\u7b2c\u4e00\u4e2a\u7b5b\u9009\u6761\u4ef6\u4f1a\u62d2\u7edd\u5b83\u3002\u56fe 5.9 \u663e\u793a\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u7b2c\u4e8c\u4e2a\u8fc7\u6ee4\u5668\u548c\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u90fd\u6ca1\u6709\u8fd0\u884c;\u8fc7\u6ee4\u5668\u7ba1\u9053\u5df2\u77ed\u8def\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0509.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 5.9 Sending two requests to the API from listing 5.10. The first request is valid, so both filters execute. An invalid id is provided in the second request, so the first filter short-circuits the requests, and the second filter doesn\u2019t execute.<br \/>\n\u56fe 5.9 \u5411\u6e05\u5355 5.10 \u4e2d\u7684 API \u53d1\u9001\u4e24\u4e2a\u8bf7\u6c42.\u7b2c\u4e00\u4e2a\u8bf7\u6c42\u6709\u6548\uff0c\u56e0\u6b64\u4e24\u4e2a\u7b5b\u9009\u6761\u4ef6\u90fd\u6267\u884c\u3002\u7b2c\u4e8c\u4e2a\u8bf7\u6c42\u4e2d\u63d0\u4f9b\u4e86\u65e0\u6548\u7684 ID\uff0c\u56e0\u6b64\u7b2c\u4e00\u4e2a\u7b5b\u9009\u6761\u4ef6\u4f1a\u4f7f\u8bf7\u6c42\u77ed\u8def\uff0c\u7b2c\u4e8c\u4e2a\u7b5b\u9009\u6761\u4ef6\u4e0d\u4f1a\u6267\u884c\u3002<\/p>\n<p>By adding calls to AddEndpointFilter, you can create arbitrarily large endpoint filter pipelines, but the fact that you can doesn\u2019t mean you should. Moving code to filters can reduce clutter in your endpoints, but it makes the flow of your application harder to understand. I suggest that you avoid using filters unless you find duplicated code in multiple endpoints, and then favor a filter over a simple method call only if it significantly simplifies the code required.<\/p>\n<p>\u901a\u8fc7\u6dfb\u52a0\u5bf9 AddEndpointFilter \u7684\u8c03\u7528\uff0c\u60a8\u53ef\u4ee5\u521b\u5efa\u4efb\u610f\u5927\u7684\u7ec8\u7aef\u8282\u70b9\u7b5b\u9009\u5668\u7ba1\u9053\uff0c\u4f46\u60a8\u53ef\u4ee5\u521b\u5efa\u7684\u4e8b\u5b9e\u5e76\u4e0d\u610f\u5473\u7740\u60a8\u5e94\u8be5\u8fd9\u6837\u505a\u3002\u5c06\u4ee3\u7801\u79fb\u52a8\u5230\u7b5b\u9009\u5668\u53ef\u4ee5\u51cf\u5c11\u7ec8\u7aef\u8282\u70b9\u4e2d\u7684\u6df7\u4e71\uff0c\u4f46\u4f1a\u4f7f\u5e94\u7528\u7a0b\u5e8f\u6d41\u66f4\u96be\u7406\u89e3\u3002\u6211\u5efa\u8bae\u60a8\u907f\u514d\u4f7f\u7528\u7b5b\u9009\u5668\uff0c\u9664\u975e\u60a8\u5728\u591a\u4e2a\u7ec8\u7aef\u8282\u70b9\u4e2d\u53d1\u73b0\u91cd\u590d\u7684\u4ee3\u7801\uff0c\u7136\u540e\u53ea\u6709\u5728\u5b83\u663e\u8457\u7b80\u5316\u4e86\u6240\u9700\u7684\u4ee3\u7801\u65f6\uff0c\u624d\u4f7f\u7528\u7b5b\u9009\u5668\u800c\u4e0d\u662f\u7b80\u5355\u7684\u65b9\u6cd5\u8c03\u7528\u3002<\/p>\n<h3>5.4.2 Filters or middleware: Which should you choose?<\/h3>\n<h3>5.4.2 \u8fc7\u6ee4\u5668\u6216\u4e2d\u95f4\u4ef6\uff1a\u60a8\u5e94\u8be5\u9009\u62e9\u54ea\u4e2a\uff1f<\/h3>\n<p>The endpoint filter pipeline is similar to the middleware pipeline in many ways, but you should consider several subtle differences when deciding which approach to use. The similarities include three main parallels:<\/p>\n<p>\u7ec8\u7aef\u8282\u70b9\u7b5b\u9009\u7ba1\u9053\u5728\u8bb8\u591a\u65b9\u9762\u4e0e\u4e2d\u95f4\u4ef6\u7ba1\u9053\u76f8\u4f3c\uff0c\u4f46\u5728\u51b3\u5b9a\u4f7f\u7528\u54ea\u79cd\u65b9\u6cd5\u65f6\uff0c\u5e94\u8003\u8651\u51e0\u4e2a\u7ec6\u5fae\u7684\u5dee\u5f02\u3002\u76f8\u4f3c\u4e4b\u5904\u5305\u62ec\u4e09\u4e2a\u4e3b\u8981\u7684\u76f8\u4f3c\u4e4b\u5904\uff1a<\/p>\n<p>\u2022   Requests pass through a middleware component on the way in, and responses pass through again on the way out. Similarly, endpoint filters can run code before calling the next filter in the pipeline and can run code after the response is generated, as shown in figure 5.8.<br \/>\n\u8bf7\u6c42\u5728\u4f20\u5165\u65f6\u901a\u8fc7\u4e2d\u95f4\u4ef6\u7ec4\u4ef6\uff0c\u54cd\u5e94\u5728\u4f20\u51fa\u65f6\u518d\u6b21\u4f20\u9012\u3002\u540c\u6837\uff0c\u7ec8\u7aef\u8282\u70b9\u7b5b\u9009\u5668\u53ef\u4ee5\u5728\u8c03\u7528\u7ba1\u9053\u4e2d\u7684\u4e0b\u4e00\u4e2a\u7b5b\u9009\u5668\u4e4b\u524d\u8fd0\u884c\u4ee3\u7801\uff0c\u5e76\u4e14\u53ef\u4ee5\u5728\u751f\u6210\u54cd\u5e94\u540e\u8fd0\u884c\u4ee3\u7801\uff0c\u5982\u56fe 5.8 \u6240\u793a\u3002<\/p>\n<p>\u2022   Middleware can short-circuit a request by returning a response instead of passing it on to later middleware. Filters can also short-circuit the filter pipeline by returning a response.<br \/>\n\u4e2d\u95f4\u4ef6\u53ef\u4ee5\u901a\u8fc7\u8fd4\u56de\u54cd\u5e94\u800c\u4e0d\u662f\u5c06\u5176\u4f20\u9012\u7ed9\u540e\u7eed\u4e2d\u95f4\u4ef6\u6765\u4f7f\u8bf7\u6c42\u77ed\u8def\u3002\u7b5b\u9009\u5668\u8fd8\u53ef\u4ee5\u901a\u8fc7\u8fd4\u56de\u54cd\u5e94\u6765\u4f7f\u7b5b\u9009\u5668\u7ba1\u9053\u77ed\u8def\u3002<\/p>\n<p>\u2022   Middleware is often used for cross-cutting application concerns, such as logging, performance profiling, and exception handling. Filters also lend themselves to cross-cutting concerns.<br \/>\n\u4e2d\u95f4\u4ef6\u901a\u5e38\u7528\u4e8e\u6a2a\u5207\u5e94\u7528\u7a0b\u5e8f\u95ee\u9898\uff0c\u4f8b\u5982\u65e5\u5fd7\u8bb0\u5f55\u3001\u6027\u80fd\u5206\u6790\u548c\u5f02\u5e38\u5904\u7406\u3002\u8fc7\u6ee4\u5668\u8fd8\u9002\u7528\u4e8e\u6a2a\u5207\u5173\u6ce8\u70b9\u3002<\/p>\n<p>By contrast, there are three main differences between middleware and filters:<\/p>\n<p>\u76f8\u6bd4\u4e4b\u4e0b\uff0c\u4e2d\u95f4\u4ef6\u548c\u8fc7\u6ee4\u5668\u4e4b\u95f4\u6709\u4e09\u4e2a\u4e3b\u8981\u533a\u522b\uff1a<\/p>\n<p>\u2022   Middleware can run for all requests; filters will run only for requests that reach the EndpointMiddleware and execute the associated endpoint.<br \/>\nFilters have access to additional details about the endpoint that will execute, such as the return value of the endpoint, such as an IResult.<br \/>\n\u4e2d\u95f4\u4ef6\u53ef\u4ee5\u9488\u5bf9\u6240\u6709\u8bf7\u6c42\u8fd0\u884cfilters \u5c06\u4ec5\u9488\u5bf9\u5230\u8fbe EndpointMiddleware \u5e76\u6267\u884c\u5173\u8054\u7aef\u70b9\u7684\u8bf7\u6c42\u8fd0\u884c\u3002\u7b5b\u9009\u5668\u53ef\u4ee5\u8bbf\u95ee\u6709\u5173\u5c06\u8981\u6267\u884c\u7684\u7ec8\u7ed3\u70b9\u7684\u5176\u4ed6\u8be6\u7ec6\u4fe1\u606f\uff0c\u4f8b\u5982\u7ec8\u7ed3\u70b9\u7684\u8fd4\u56de\u503c\uff0c\u4f8b\u5982 IResult\u3002<\/p>\n<p>\u2022   Middleware in general won\u2019t see these intermediate steps, so it sees only the generated response.<br \/>\n\u4e2d\u95f4\u4ef6\u901a\u5e38\u4e0d\u4f1a\u770b\u5230\u8fd9\u4e9b\u4e2d\u95f4\u6b65\u9aa4\uff0c\u56e0\u6b64\u5b83\u53ea\u80fd\u770b\u5230\u751f\u6210\u7684\u54cd\u5e94\u3002<\/p>\n<p>\u2022   Filters can easily be restricted to a subset of requests, such as a single endpoint or a group of endpoints. Middleware generally applies to all requests (though you can achieve something similar with custom middleware components).<br \/>\n\u7b5b\u9009\u5668\u53ef\u4ee5\u8f7b\u677e\u5730\u9650\u5236\u4e3a\u8bf7\u6c42\u7684\u5b50\u96c6\uff0c\u4f8b\u5982\u5355\u4e2a\u7ec8\u7aef\u8282\u70b9\u6216\u4e00\u7ec4\u7ec8\u7aef\u8282\u70b9\u3002\u4e2d\u95f4\u4ef6\u901a\u5e38\u9002\u7528\u4e8e\u6240\u6709\u8bf7\u6c42\uff08\u5c3d\u7ba1\u60a8\u53ef\u4ee5\u4f7f\u7528\u81ea\u5b9a\u4e49\u4e2d\u95f4\u4ef6\u7ec4\u4ef6\u5b9e\u73b0\u7c7b\u4f3c\u7684\u529f\u80fd\uff09\u3002<\/p>\n<p>That\u2019s all well and good, but how should we interpret these differences? When should we choose one over the other?<br \/>\n\u8fd9\u4e00\u5207\u90fd\u5f88\u597d\uff0c\u4f46\u6211\u4eec\u5e94\u8be5\u5982\u4f55\u89e3\u91ca\u8fd9\u4e9b\u5dee\u5f02\u5462\uff1f\u6211\u4eec\u4ec0\u4e48\u65f6\u5019\u5e94\u8be5\u9009\u62e9\u4e00\u4e2a\u800c\u4e0d\u662f\u53e6\u4e00\u4e2a\uff1f<\/p>\n<p>I like to think of middleware versus filters as a question of specificity. Middleware is the more general concept, operating on lower-level primitives such as HttpContext, so it has wider reach. If the functionality you need has no endpoint-specific requirements, you should use a middleware component. Exception handling is a great example; exceptions could happen anywhere in your application, and you need to handle them, so using exception-handling middleware makes sense.<\/p>\n<p>\u6211\u559c\u6b22\u5c06\u4e2d\u95f4\u4ef6\u4e0e\u8fc7\u6ee4\u5668\u89c6\u4e3a\u4e00\u4e2a\u7279\u5f02\u6027\u95ee\u9898\u3002\u4e2d\u95f4\u4ef6\u662f\u66f4\u901a\u7528\u7684\u6982\u5ff5\uff0c\u5b83\u5728 HttpContext \u7b49\u8f83\u4f4e\u7ea7\u522b\u7684\u539f\u8bed\u4e0a\u8fd0\u884c\uff0c\u56e0\u6b64\u5b83\u7684\u8303\u56f4\u66f4\u5e7f\u3002\u5982\u679c\u60a8\u9700\u8981\u7684\u529f\u80fd\u6ca1\u6709\u7279\u5b9a\u4e8e\u7aef\u70b9\u7684\u8981\u6c42\uff0c\u5219\u5e94\u4f7f\u7528\u4e2d\u95f4\u4ef6\u7ec4\u4ef6\u3002\u5f02\u5e38\u5904\u7406\u5c31\u662f\u4e00\u4e2a\u5f88\u597d\u7684\u4f8b\u5b50;\u5f02\u5e38\u53ef\u80fd\u53d1\u751f\u5728\u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u4efb\u4f55\u4f4d\u7f6e\uff0c\u60a8\u9700\u8981\u5904\u7406\u5b83\u4eec\uff0c\u56e0\u6b64\u4f7f\u7528\u5f02\u5e38\u5904\u7406\u4e2d\u95f4\u4ef6\u662f\u6709\u610f\u4e49\u7684\u3002<\/p>\n<p>On the other hand, if you do need access to endpoint details, or if you want to behave differently for some requests, you should consider using a filter. Validation is a good example. Not all requests need the same validation. Requests for static files, for example, don\u2019t need parameter validation, the way requests to an API endpoint do. Applying validation to the endpoints via filters makes sense in this case.<\/p>\n<p>\u53e6\u4e00\u65b9\u9762\uff0c\u5982\u679c\u60a8\u786e\u5b9e\u9700\u8981\u8bbf\u95ee\u7ec8\u7aef\u8282\u70b9\u8be6\u7ec6\u4fe1\u606f\uff0c\u6216\u8005\u60a8\u5e0c\u671b\u5bf9\u67d0\u4e9b\u8bf7\u6c42\u91c7\u53d6\u4e0d\u540c\u7684\u884c\u4e3a\uff0c\u5219\u5e94\u8003\u8651\u4f7f\u7528\u7b5b\u9009\u6761\u4ef6\u3002\u9a8c\u8bc1\u5c31\u662f\u4e00\u4e2a\u5f88\u597d\u7684\u4f8b\u5b50\u3002\u5e76\u975e\u6240\u6709\u8bf7\u6c42\u90fd\u9700\u8981\u76f8\u540c\u7684\u9a8c\u8bc1\u3002\u4f8b\u5982\uff0c\u5bf9\u9759\u6001\u6587\u4ef6\u7684\u8bf7\u6c42\u4e0d\u9700\u8981\u53c2\u6570\u9a8c\u8bc1\uff0c\u5bf9 API \u7ec8\u7aef\u8282\u70b9\u7684\u8bf7\u6c42\u65b9\u5f0f\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u901a\u8fc7\u8fc7\u6ee4\u5668\u5bf9\u7ec8\u7aef\u8282\u70b9\u5e94\u7528\u9a8c\u8bc1\u662f\u6709\u610f\u4e49\u7684\u3002<\/p>\n<p><b>TIP<\/b>  Where possible, consider using middleware for cross-cutting concerns. Use filters when you need different behavior for different endpoints or where the functionality relies on endpoint concepts such as IResult objects.<br \/>\n\u63d0\u793a\uff1a \u5728\u53ef\u80fd\u7684\u60c5\u51b5\u4e0b\uff0c\u8003\u8651\u5c06\u4e2d\u95f4\u4ef6\u7528\u4e8e\u6a2a\u5207\u5173\u6ce8\u70b9\u3002\u5f53\u60a8\u9700\u8981\u5bf9\u4e0d\u540c\u7684\u7ec8\u7aef\u8282\u70b9\u8fdb\u884c\u4e0d\u540c\u7684\u884c\u4e3a\u65f6\uff0c\u6216\u8005\u5f53\u529f\u80fd\u4f9d\u8d56\u4e8e\u7ec8\u7aef\u8282\u70b9\u6982\u5ff5\uff08\u5982 IResult \u5bf9\u8c61\uff09\u65f6\uff0c\u8bf7\u4f7f\u7528\u8fc7\u6ee4\u5668\u3002<\/p>\n<p>So far, the filters we\u2019ve looked at have been specific to a single endpoint. In section 5.4.3 we look at creating generic filters that you can apply to multiple endpoints.<\/p>\n<p>\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u6211\u4eec\u67e5\u770b\u7684\u7b5b\u9009\u5668\u7279\u5b9a\u4e8e\u5355\u4e2a\u7ec8\u7ed3\u70b9\u3002\u5728\u7b2c 5.4.3 \u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u5982\u4f55\u521b\u5efa\u53ef\u5e94\u7528\u4e8e\u591a\u4e2a\u7ec8\u7aef\u8282\u70b9\u7684\u901a\u7528\u8fc7\u6ee4\u5668\u3002<\/p>\n<h3>5.4.3 Generalizing your endpoint filters<\/h3>\n<h3>5.4.3 \u901a\u7528\u5316\u7ec8\u7aef\u8282\u70b9\u7b5b\u9009\u6761\u4ef6<\/h3>\n<p>One common problem with filters is that they end up closely tied to the implementation of your endpoint handlers. Listing 5.9, for example, assumes that the id parameter is the first parameter in the method. In this section you\u2019ll learn how to create generalized versions of filters that work with multiple endpoint handlers.<\/p>\n<p>\u7b5b\u9009\u5668\u7684\u4e00\u4e2a\u5e38\u89c1\u95ee\u9898\u662f\uff0c\u5b83\u4eec\u6700\u7ec8\u4e0e\u7ec8\u7ed3\u70b9\u5904\u7406\u7a0b\u5e8f\u7684\u5b9e\u73b0\u5bc6\u5207\u76f8\u5173\u3002\u4f8b\u5982\uff0c\u6e05\u5355 5.9 \u5047\u8bbe id \u53c2\u6570\u662f\u65b9\u6cd5\u4e2d\u7684\u7b2c\u4e00\u4e2a\u53c2\u6570\u3002\u5728\u672c\u8282\u4e2d\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u521b\u5efa\u4f7f\u7528\u591a\u4e2a\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u7684 filters \u7684\u901a\u7528\u7248\u672c\u3002<\/p>\n<p>The fruit API we\u2019ve been working with in this chapter contains several endpoint handlers that take multiple parameters. The MapPost handler, for example, takes a string id parameter and a Fruit fruit parameter:<\/p>\n<p>\u6211\u4eec\u5728\u672c\u7ae0\u4e2d\u4f7f\u7528\u7684 fruit API \u5305\u542b\u51e0\u4e2a\u91c7\u7528\u591a\u4e2a\u53c2\u6570\u7684\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u3002\u4f8b\u5982\uff0cMapPost \u5904\u7406\u7a0b\u5e8f\u91c7\u7528\u5b57\u7b26\u4e32 id \u53c2\u6570\u548c Fruit fruit \u53c2\u6570\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapPost(&quot;\/fruit\/{id}&quot;, (string id, Fruit fruit) =&gt; { \/* *\/ });\n<\/pre>\n<p>In this example, the id parameter is listed first, but there\u2019s no requirement for that to be the case. The parameters to the handler could be reversed, and the endpoint would be functionally identical:<br \/>\n\u5728\u6b64\u793a\u4f8b\u4e2d\uff0c\u9996\u5148\u5217\u51fa id \u53c2\u6570\uff0c\u4f46\u4e0d\u9700\u8981\u8fd9\u6837\u505a\u3002\u5904\u7406\u7a0b\u5e8f\u7684\u53c2\u6570\u53ef\u4ee5\u53cd\u8f6c\uff0c\u5e76\u4e14\u7aef\u70b9\u5728\u529f\u80fd\u4e0a\u662f\u76f8\u540c\u7684\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapPost(&quot;\/fruit\/{id}&quot;, (Fruit fruit, string id) =&gt; { \/* *\/ });\n<\/pre>\n<p>Unfortunately, with this order, the ValidateId filter described in listing 5.9 won\u2019t work. The ValidateId filter assumes that the first parameter to the handler is id, which isn\u2019t the case in our revised MapPost implementation.<\/p>\n<p>\u9057\u61be\u7684\u662f\uff0c\u6309\u7167\u8fd9\u4e2a\u987a\u5e8f\uff0c\u6e05\u5355 5.9 \u4e2d\u63cf\u8ff0\u7684 ValidateId \u8fc7\u6ee4\u5668\u5c06\u4e0d\u8d77\u4f5c\u7528\u3002ValidateId \u7b5b\u9009\u5668\u5047\u5b9a\u5904\u7406\u7a0b\u5e8f\u7684\u7b2c\u4e00\u4e2a\u53c2\u6570\u662f id\uff0c\u800c\u5728\u6211\u4eec\u4fee\u8ba2\u540e\u7684 MapPost \u5b9e\u73b0\u4e2d\uff0c\u60c5\u51b5\u5e76\u975e\u5982\u6b64\u3002<\/p>\n<p>ASP.NET Core provides a solution that uses a factory pattern for filters. You can register a filter factory by using the AddEndpointFilterFactory() method. A filter factory is a method that returns a filter function. ASP.NET Core executes the filter factory when it\u2019s building your app and incorporates the returned filter into the filter pipeline for the app, as shown in figure 5.10. You can use the same filter-factory function to emit a different filter for each endpoint, with each filter tailored to the endpoint\u2019s parameters.<\/p>\n<p>ASP.NET Core \u63d0\u4f9b\u4e86\u4e00\u79cd\u5bf9\u7b5b\u9009\u5668\u4f7f\u7528\u5de5\u5382\u6a21\u5f0f\u7684\u89e3\u51b3\u65b9\u6848\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528 AddEndpointFilterFactory\uff08\uff09 \u65b9\u6cd5\u6ce8\u518c\u8fc7\u6ee4\u5668\u5de5\u5382\u3002\u8fc7\u6ee4\u5668\u5de5\u5382\u662f\u4e00\u79cd\u8fd4\u56de\u8fc7\u6ee4\u5668\u51fd\u6570\u7684\u65b9\u6cd5\u3002 ASP.NET Core \u5728\u6784\u5efa\u5e94\u7528\u7a0b\u5e8f\u65f6\u6267\u884c\u8fc7\u6ee4\u5668\u5de5\u5382\uff0c\u5e76\u5c06\u8fd4\u56de\u7684\u8fc7\u6ee4\u5668\u5408\u5e76\u5230\u5e94\u7528\u7a0b\u5e8f\u7684\u8fc7\u6ee4\u5668\u7ba1\u9053\u4e2d\uff0c\u5982\u56fe 5.10 \u6240\u793a\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u76f8\u540c\u7684 filter- factory \u51fd\u6570\u4e3a\u6bcf\u4e2a\u7ec8\u7aef\u8282\u70b9\u53d1\u51fa\u4e0d\u540c\u7684\u8fc7\u6ee4\u5668\uff0c\u6bcf\u4e2a\u8fc7\u6ee4\u5668\u90fd\u6839\u636e\u7ec8\u7aef\u8282\u70b9\u7684\u53c2\u6570\u8fdb\u884c\u5b9a\u5236\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0510.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 5.10 A filter factory is a generalized way to add endpoint filters. The factory reads details about the endpoint, such as its method signature, and builds a filter function. This function is incorporated into the final filter pipeline for the endpoint. The build step means that a single filter factory can create filters for multiple endpoints with different method signatures.<br \/>\n\u56fe 5.10 \u8fc7\u6ee4\u5668\u5de5\u5382\u662f\u6dfb\u52a0\u7aef\u70b9\u8fc7\u6ee4\u5668\u7684\u4e00\u79cd\u901a\u7528\u65b9\u6cd5\u3002\u5de5\u5382\u8bfb\u53d6\u6709\u5173\u7aef\u70b9\u7684\u8be6\u7ec6\u4fe1\u606f\uff0c\u4f8b\u5982\u5176\u65b9\u6cd5\u7b7e\u540d\uff0c\u5e76\u6784\u5efa\u4e00\u4e2a filter \u51fd\u6570\u3002\u6b64\u51fd\u6570\u5c06\u5408\u5e76\u5230\u7ec8\u7aef\u8282\u70b9\u7684\u6700\u7ec8\u7b5b\u9009\u7ba1\u9053\u4e2d\u3002\u6784\u5efa\u6b65\u9aa4\u610f\u5473\u7740\u5355\u4e2a\u8fc7\u6ee4\u5668\u5de5\u5382\u53ef\u4ee5\u4e3a\u5177\u6709\u4e0d\u540c\u65b9\u6cd5\u7b7e\u540d\u7684\u591a\u4e2a\u7aef\u70b9\u521b\u5efa\u8fc7\u6ee4\u5668\u3002<\/p>\n<p>Listing 5.11 shows an example of the factory pattern in practice. The filter factory is applied to multiple endpoints. For each endpoint, the factory first checks for a parameter called id; if it doesn\u2019t exist, the factory returns next and doesn\u2019t add a filter to the pipeline. If the id parameter exists, the factory returns a filter function, which is virtually identical to the filter function in listing 5.9; the main difference is that this filter handles a variable location of the id parameter.<\/p>\n<p>\u6e05\u5355 5.11 \u663e\u793a\u4e86\u5b9e\u8df5\u4e2d\u5de5\u5382\u6a21\u5f0f\u7684\u4e00\u4e2a\u4f8b\u5b50\u3002\u8fc7\u6ee4\u5668\u5de5\u5382\u5e94\u7528\u4e8e\u591a\u4e2a\u7aef\u70b9\u3002\u5bf9\u4e8e\u6bcf\u4e2a\u7ec8\u7aef\u8282\u70b9\uff0c\u5de5\u5382\u9996\u5148\u68c0\u67e5\u540d\u4e3a ;\u5982\u679c\u4e0d\u5b58\u5728\uff0c\u5219 Factory \u5c06\u8fd4\u56de\u5e76\u4e14\u4e0d\u4f1a\u5411\u7ba1\u9053\u6dfb\u52a0\u7b5b\u9009\u5668\u3002\u5982\u679c\u53c2\u6570\u5b58\u5728\uff0c\u5de5\u5382\u5c06\u8fd4\u56de\u4e00\u4e2a filter \u51fd\u6570\uff0c\u8be5\u51fd\u6570\u4e0e\u6e05\u5355 5.9 \u4e2d\u7684 filter \u51fd\u6570\u51e0\u4e4e\u76f8\u540c;\u4e3b\u8981\u533a\u522b\u5728\u4e8e\u6b64\u7b5b\u9009\u5668\u5904\u7406\u53c2\u6570\u7684\u53ef\u53d8\u4f4d\u7f6e.<\/p>\n<p>Listing 5.11 Using a filter factory to create an endpoint filter<br \/>\nListing 5.11 \u4f7f\u7528\u8fc7\u6ee4\u5668\u5de5\u5382\u521b\u5efa\u7aef\u70b9\u8fc7\u6ee4\u5668<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\nusing System.Collections.Concurrent;\nusing System.Reflection;\n\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\nvar _fruit = new ConcurrentDictionary&lt;string, Fruit&gt;();\n\napp.MapGet(&quot;\/fruit\/{id}&quot;, (string id) =&gt;\n    _fruit.TryGetValue(id, out var fruit)\n        ? TypedResults.Ok(fruit)\n        : Results.Problem(statusCode: 404))\n    .AddEndpointFilterFactory(ValidationHelper.ValidateIdFactory);               \/\/ \u2776\n\napp.MapPost(&quot;\/fruit\/{id}&quot;, (Fruit fruit, string id) =&gt;\n    _fruit.TryAdd(id, fruit)\n        ? TypedResults.Created($&quot;\/fruit\/{id}&quot;, fruit)\n        : Results.ValidationProblem(new Dictionary&lt;string, string&#x5B;]&gt;\n            {\n                { &quot;id&quot;, new&#x5B;] { &quot;A fruit with this id already exists&quot; } }\n            }))\n    .AddEndpointFilterFactory(ValidationHelper.ValidateIdFactory);               \/\/ \u2776\napp.Run();\n\nrecord Fruit(string Name, int Stock);\nclass ValidationHelper\n{\n    internal static EndpointFilterDelegate ValidateIdFactory(\n        EndpointFilterFactoryContext context,                                    \/\/ \u2777\n        EndpointFilterDelegate next)\n    {\n        ParameterInfo&#x5B;] parameters =                                             \/\/ \u2778\n            context.MethodInfo.GetParameters();                                  \/\/ \u2778\n        int? idPosition = null;\n        for (int i = 0; i &lt; parameters.Length; i++)                              \/\/ \u2779\n        {                                                                        \/\/ \u2779\n            if (parameters&#x5B;i].Name == &quot;id&quot; &amp;&amp;                                    \/\/ \u2779\n             parameters&#x5B;i].ParameterType == typeof(string))                      \/\/ \u2779\n            {                                                                    \/\/ \u2779\n                idPosition = i;                                                  \/\/ \u2779\n                break;                                                           \/\/ \u2779\n            }                                                                    \/\/ \u2779\n        }                                                                        \/\/ \u2779\n\n        if (!idPosition.HasValue)                                                \/\/ \u277a\n        {                                                                        \/\/ \u277a\n            return next;                                                         \/\/ \u277a\n        }                                                                        \/\/ \u277a\n\n        return async (invocationContext) =&gt;                                      \/\/ \u277b\n        {\n            var id = invocationContext                                           \/\/ \u277c\n                .GetArgument&lt;string&gt;(idPosition.Value);                          \/\/ \u277c\n            if (string.IsNullOrEmpty(id) || !id.StartsWith(&#039;f&#039;))                 \/\/ \u277c\n            {                                                                    \/\/ \u277c\n                return Results.ValidationProblem(                                \/\/ \u277c\n                    new Dictionary&lt;string, string&#x5B;]&gt;                             \/\/ \u277c\n                { { &quot;id&quot;, new&#x5B;] { &quot;Id must start with &#039;f&#039;&quot; }} });                \/\/ \u277c\n            }                                                                    \/\/ \u277c\n\n            return await next(invocationContext);                                \/\/ \u277d\n        }\n        ;\n    }\n}\n\n<\/pre>\n<p>\u2776 The filter factory can handle endpoints with different method signatures.<br \/>\n\u8fc7\u6ee4\u5668\u5de5\u5382\u53ef\u4ee5\u5904\u7406\u5177\u6709\u4e0d\u540c\u65b9\u6cd5\u7b7e\u540d\u7684\u7aef\u70b9\u3002<\/p>\n<p>\u2777 The context parameter provides details about the endpoint handler method.<br \/>\ncontext \u53c2\u6570\u63d0\u4f9b\u6709\u5173\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u7684\u8be6\u7ec6\u4fe1\u606f\u3002<\/p>\n<p>\u2778 GetParameters() provides details about the parameters of the handler being called.<br \/>\nGetParameters\uff08\uff09 \u63d0\u4f9b\u6709\u5173\u6b63\u5728\u8c03\u7528\u7684\u5904\u7406\u7a0b\u5e8f\u7684\u53c2\u6570\u7684\u8be6\u7ec6\u4fe1\u606f\u3002<\/p>\n<p>\u2779 Loops through the parameters to find the string id parameter and record its position<br \/>\n\u904d\u5386\u53c2\u6570\u4ee5\u627e\u5230\u5b57\u7b26\u4e32 id \u53c2\u6570\u5e76\u8bb0\u5f55\u5176\u4f4d\u7f6e<\/p>\n<p>\u277a If the id parameter isn\u2019t not found, doesn\u2019t add a filter, but returns the remainder of the pipeline<br \/>\n\u5982\u679c\u672a\u627e\u5230 id \u53c2\u6570\uff0c\u5219\u4e0d\u6dfb\u52a0\u7b5b\u9009\u5668\uff0c\u4f46\u8fd4\u56de\u7ba1\u9053\u7684\u5176\u4f59\u90e8\u5206<\/p>\n<p>\u277b If the id parameter exists, returns a filter function (the filter executed for the endpoint)<br \/>\n\u5982\u679c id \u53c2\u6570\u5b58\u5728\uff0c\u5219\u8fd4\u56de\u4e00\u4e2a filter \u51fd\u6570\uff08\u4e3a\u7ec8\u7aef\u8282\u70b9\u6267\u884c\u7684 filter\uff09<\/p>\n<p>\u277c If the id isn\u2019t valid, returns a Problem Details result<br \/>\n\u5982\u679c ID \u65e0\u6548\uff0c\u5219\u8fd4\u56de Problem Details \u7ed3\u679c<\/p>\n<p>\u277d If the id is valid, executes the next filter in the pipeline<br \/>\n\u5982\u679c id \u6709\u6548\uff0c\u5219\u6267\u884c\u7ba1\u9053\u4e2d\u7684\u4e0b\u4e00\u4e2a\u8fc7\u6ee4\u5668<\/p>\n<p>The code in listing 5.11 is more complex than anything else we\u2019ve seen so far, as it has an extra layer of abstraction. The endpoint middleware passes an EndpointFilterFactoryContext object to the factory function, which contains extra details about the endpoint in comparison to the context passed to a normal filter function. Specifically, it includes a MethodInfo property and an EndpointMetadata property.<\/p>\n<p>\u6e05\u5355 5.11 \u4e2d\u7684\u4ee3\u7801\u6bd4\u6211\u4eec\u76ee\u524d\u770b\u5230\u7684\u4efb\u4f55\u5176\u4ed6\u4ee3\u7801\u90fd\u8981\u590d\u6742\uff0c\u56e0\u4e3a\u5b83\u6709\u4e00\u4e2a\u989d\u5916\u7684\u62bd\u8c61\u5c42\u3002\u7aef\u70b9\u4e2d\u95f4\u4ef6\u5c06 EndpointFilterFactoryContext \u5bf9\u8c61\u4f20\u9012\u7ed9\u5de5\u5382\u51fd\u6570\uff0c\u4e0e\u4f20\u9012\u7ed9\u666e\u901a filter \u51fd\u6570\u7684\u4e0a\u4e0b\u6587\u76f8\u6bd4\uff0c\u8be5\u5bf9\u8c61\u5305\u542b\u6709\u5173\u7aef\u70b9\u7684\u989d\u5916\u8be6\u7ec6\u4fe1\u606f\u3002\u5177\u4f53\u6765\u8bf4\uff0c\u5b83\u5305\u62ec MethodInfo \u5c5e\u6027\u548c EndpointMetadata \u5c5e\u6027\u3002<\/p>\n<p><b>NOTE<\/b>  You\u2019ll learn about endpoint metadata in chapter 6.<br \/>\n\u6ce8\u610f\uff1a \u60a8\u5c06\u5728\u7b2c 6 \u7ae0\u4e2d\u4e86\u89e3\u7ec8\u7aef\u8282\u70b9\u5143\u6570\u636e\u3002<\/p>\n<p>The MethodInfo property can be used to control how the filter is created based on the definition of the endpoint handler. Listing 5.11 shows how you can loop through the parameters to check for the details you need\u2014a string id parameter, in this case\u2014and customize the filter function you return.<br \/>\nMethodInfo \u5c5e\u6027\u53ef\u7528\u4e8e\u63a7\u5236\u5982\u4f55\u6839\u636e\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u7684\u5b9a\u4e49\u521b\u5efa\u8fc7\u6ee4\u5668\u3002\u6e05\u5355 5.11 \u5c55\u793a\u4e86\u5982\u4f55\u904d\u5386\u53c2\u6570\u6765\u68c0\u67e5\u6240\u9700\u7684\u7ec6\u8282 \u2014 \u5728\u672c\u4f8b\u4e2d\u4e3a string id \u53c2\u6570 \u2014 \u5e76\u81ea\u5b9a\u4e49\u8fd4\u56de\u7684 filter \u51fd\u6570\u3002<\/p>\n<p>If you find all these method signatures to be confusing, I don\u2019t blame you. Remembering the difference between an EndpointFilterFactoryContext and EndpointFilterInvocationContext and then trying to satisfy the compiler with your lambda methods can be annoying. Sometimes, you yearn for a good ol\u2019 interface to implement. Let\u2019s do that now.<\/p>\n<p>\u5982\u679c\u60a8\u53d1\u73b0\u6240\u6709\u8fd9\u4e9b\u65b9\u6cd5\u7b7e\u540d\u90fd\u4ee4\u4eba\u56f0\u60d1\uff0c\u6211\u4e0d\u602a\u60a8\u3002\u8bb0\u4f4f EndpointFilterFactoryContext \u548c EndpointFilterInvocationContext \u4e4b\u95f4\u7684\u533a\u522b\uff0c\u7136\u540e\u5c1d\u8bd5\u4f7f\u7528 lambda \u65b9\u6cd5\u6ee1\u8db3\u7f16\u8bd1\u5668\u53ef\u80fd\u4f1a\u5f88\u70e6\u4eba\u3002\u6709\u65f6\uff0c\u60a8\u6e34\u671b\u5b9e\u73b0\u4e00\u4e2a\u597d\u7684 ol' \u63a5\u53e3\u3002\u6211\u4eec\u73b0\u5728\u5c31\u5f00\u59cb\u5427\u3002<\/p>\n<h3>5.4.4 Implementing the IEndpointFilter interface<\/h3>\n<h3>5.4.4 \u5b9e\u73b0 IEndpointFilter \u63a5\u53e3<\/h3>\n<p>Creating a lambda method for AddEndpointFilter() that satisfies the compiler can be a frustrating experience, depending on the level of support your integrated development environment (IDE) provides. In this section you\u2019ll learn how to sidestep the issue by defining a class that implements IEndpointFilter instead.<\/p>\n<p>\u4e3a AddEndpointFilter\uff08\uff09 \u521b\u5efa\u6ee1\u8db3\u7f16\u8bd1\u5668\u7684 lambda \u65b9\u6cd5\u53ef\u80fd\u662f\u4e00\u79cd\u4ee4\u4eba\u6cae\u4e27\u7684\u4f53\u9a8c\uff0c\u5177\u4f53\u53d6\u51b3\u4e8e\u96c6\u6210\u5f00\u53d1\u73af\u5883 \uff08IDE\uff09 \u63d0\u4f9b\u7684\u652f\u6301\u7ea7\u522b\u3002\u5728\u672c\u8282\u4e2d\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u901a\u8fc7\u5b9a\u4e49\u4e00\u4e2a\u5b9e\u73b0 IEndpointFilter \u7684\u7c7b\u6765\u56de\u907f\u8fd9\u4e2a\u95ee\u9898\u3002<\/p>\n<p>You can implement IEndpointFilter by defining a class with an InvokeAsync() that has the same signature as the lambda defined in listing 5.9. The advantage of using IEndpointFilter is that you get IntelliSense and autocompletion for the method signature. The following listing shows how to implement an IEndpointFilter class that\u2019s equivalent to listing 5.9.<\/p>\n<p>\u60a8\u53ef\u4ee5\u901a\u8fc7\u4f7f\u7528 InvokeAsync\uff08\uff09 \u5b9a\u4e49\u4e00\u4e2a\u7c7b\u6765\u5b9e\u73b0 IEndpointFilter\uff0c\u8be5\u7c7b\u4e0e\u6e05\u5355 5.9 \u4e2d\u5b9a\u4e49\u7684 lambda \u5177\u6709\u76f8\u540c\u7684\u7b7e\u540d\u3002\u4f7f\u7528 IEndpointFilter \u7684\u4f18\u70b9\u662f\u60a8\u53ef\u4ee5\u83b7\u5f97\u65b9\u6cd5\u7b7e\u540d\u7684 IntelliSense \u548c\u81ea\u52a8\u5b8c\u6210\u3002\u4e0b\u9762\u7684\u6e05\u5355\u663e\u793a\u4e86\u5982\u4f55\u5b9e\u73b0\u4e00\u4e2a\u7b49\u6548\u4e8e\u6e05\u5355 5.9 \u7684 IEndpointFilter \u7c7b\u3002<\/p>\n<p>Listing 5.12 Implementing IEndpointFilter<br \/>\n\u6e05\u5355 5.12 \u5b9e\u73b0IEndpointFilter<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\nvar _fruit = new System.Collections.Concurrent.ConcurrentDictionary&lt;string, Fruit&gt;();\n\napp.MapGet(&quot;\/fruit\/{id}&quot;, (string id) =&gt;\n    _fruit.TryGetValue(id, out var fruit)\n        ? TypedResults.Ok(fruit)\n        : Results.Problem(statusCode: 404))\n    .AddEndpointFilter&lt;IdValidationFilter&gt;();                \/\/ \u2776\n\napp.Run();\n\nrecord Fruit(string Name, int Stock);\nclass IdValidationFilter : IEndpointFilter                   \/\/ \u2777\n{\n    public async ValueTask&lt;object?&gt; InvokeAsync(             \/\/ \u2778\n        EndpointFilterInvocationContext context,             \/\/ \u2778\n        EndpointFilterDelegate next)                         \/\/ \u2778\n    {\n        var id = context.GetArgument&lt;string&gt;(0);\n        if (string.IsNullOrEmpty(id) || !id.StartsWith(&#039;f&#039;))\n        {\n            return Results.ValidationProblem(\n                new Dictionary&lt;string, string&#x5B;]&gt;\n                {\n                    {&quot;id&quot;, new&#x5B;]{&quot;Invalid format. Id must start with &#039;f&#039;&quot;}}\n                });\n        }\n\n    return await next(context);\n    }\n}\n\n<\/pre>\n<p>\u2776 Adds the filter using the generic AddEndpointFilter method<br \/>\n\u4f7f\u7528\u6cdb\u578b AddEndpointFilter \u65b9\u6cd5\u6dfb\u52a0\u7b5b\u9009\u5668<\/p>\n<p>\u2777 The filter must implement IEndpointFilter . . .<br \/>\n\u7b5b\u9009\u5668\u5fc5\u987b\u5b9e\u73b0 IEndpointFilter . . . .<\/p>\n<p>\u2778 . . . which requires implementing a single method.<br \/>\n. . . .\u8fd9\u9700\u8981\u5b9e\u73b0\u5355\u4e2a\u65b9\u6cd5\u3002<\/p>\n<p>Implementing IEndpointFilter is a good option when your filters become more complex, but note that there\u2019s no equivalent interface for the filter-factory pattern shown in section 5.4.3. If you want to generalize your filters with a filter factory, you\u2019ll have to stick to the lambda (or helper-method) approach shown in listing 5.11.<\/p>\n<p>\u5f53\u7b5b\u9009\u5668\u53d8\u5f97\u66f4\u52a0\u590d\u6742\u65f6\uff0c\u5b9e\u73b0 IEndpointFilter \u662f\u4e00\u4e2a\u4e0d\u9519\u7684\u9009\u62e9\uff0c\u4f46\u8bf7\u6ce8\u610f\uff0c\u7b2c 5.4.3 \u8282\u4e2d\u6240\u793a\u7684\u7b5b\u9009\u5668\u5de5\u5382\u6a21\u5f0f\u6ca1\u6709\u7b49\u6548\u7684\u63a5\u53e3\u3002\u5982\u679c\u4f60\u60f3\u7528\u4e00\u4e2a\u8fc7\u6ee4\u5668\u5de5\u5382\u6765\u63a8\u5e7f\u4f60\u7684\u8fc7\u6ee4\u5668\uff0c\u4f60\u5c31\u5fc5\u987b\u575a\u6301\u4f7f\u7528 lambda\uff08\u6216\u8f85\u52a9\u65b9\u6cd5\uff09\u65b9\u6cd5\uff0c\u5982\u6e05\u5355 5.11 \u6240\u793a\u3002<\/p>\n<h3>5.5 Organizing your APIs with route groups<\/h3>\n<h3>\u4f7f\u7528\u8def\u7531\u7ec4\u7ec4\u7ec7 API<\/h3>\n<p>One criticism levied against minimal APIs in .NET 6 was that they were necessarily quite verbose, required a lot of duplicated code, and often led to large endpoint handler methods. .NET 7 introduced two new mechanisms to address these critiques:<\/p>\n<p>\u5bf9 .NET 6 \u4e2d\u6700\u5c0f API \u7684\u4e00\u4e2a\u6279\u8bc4\u662f\uff0c\u5b83\u4eec\u5fc5\u7136\u975e\u5e38\u5197\u957f\uff0c\u9700\u8981\u5927\u91cf\u91cd\u590d\u7684\u4ee3\u7801\uff0c\u5e76\u4e14\u7ecf\u5e38\u5bfc\u81f4\u5927\u578b\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u3002.NET 7 \u5f15\u5165\u4e86\u4e24\u79cd\u65b0\u673a\u5236\u6765\u89e3\u51b3\u8fd9\u4e9b\u6279\u8bc4\uff1a<\/p>\n<p>\u2022   Filters\u2014Introduced in section 5.4, filters help separate validation checks and cross-cutting functions such as logging from the important logic in your endpoint handler functions.<br \/>\n\u8fc7\u6ee4\u5668 \u2014 \u5728\u7b2c 5.4 \u8282\u4e2d\u4ecb\u7ecd\uff0c\u8fc7\u6ee4\u5668\u6709\u52a9\u4e8e\u5c06\u9a8c\u8bc1\u68c0\u67e5\u548c\u6a2a\u5207\u51fd\u6570\uff08\u5982\u65e5\u5fd7\u8bb0\u5f55\uff09\u4e0e\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u51fd\u6570\u4e2d\u7684\u91cd\u8981\u903b\u8f91\u5206\u5f00\u3002<\/p>\n<p>\u2022   Route groups\u2014Described in this section, route groups help reduce duplication by applying filters and routing to multiple handlers at the same time.<br \/>\n\u8def\u7531\u7ec4 \u2014 \u672c\u8282\u4ecb\u7ecd\u4e86\u8def\u7531\u7ec4\uff0c\u901a\u8fc7\u540c\u65f6\u5c06\u7b5b\u9009\u6761\u4ef6\u548c\u8def\u7531\u5e94\u7528\u4e8e\u591a\u4e2a\u5904\u7406\u7a0b\u5e8f\u6765\u5e2e\u52a9\u51cf\u5c11\u91cd\u590d\u3002<\/p>\n<p>When designing APIs, it\u2019s important to maintain consistency in the routes you use for your endpoints, which often means duplicating part of the route pattern across multiple APIs. As an example, all the endpoints in the fruit API described throughout this chapter (such as in listing 5.3) start with the route prefix \/fruit:<br \/>\n\u5728\u8bbe\u8ba1 API \u65f6\uff0c\u4fdd\u6301\u7528\u4e8e\u7ec8\u7aef\u8282\u70b9\u7684\u8def\u7531\u7684\u4e00\u81f4\u6027\u975e\u5e38\u91cd\u8981\uff0c\u8fd9\u901a\u5e38\u610f\u5473\u7740\u8de8\u591a\u4e2a API \u590d\u5236\u90e8\u5206\u8def\u7531\u6a21\u5f0f\u3002\u4f8b\u5982\uff0c\u672c\u7ae0\u4e2d\u63cf\u8ff0\u7684 fruit API \u4e2d\u7684\u6240\u6709\u7aef\u70b9\uff08\u4f8b\u5982\u6e05\u5355 5.3\uff09\u90fd\u4ee5\u8def\u7531\u524d\u7f00 \/fruit \u5f00\u5934\uff1a<\/p>\n<p>\u2022   <code>MapGet(&quot;\/fruit&quot;, () =&gt; {\/* *\/})<\/code><\/p>\n<p>\u2022   <code>MapGet(&quot;\/fruit\/{id}&quot;, (string id) =&gt; {\/* *\/})<\/code><\/p>\n<p>\u2022   <code>MapPost(&quot;\/fruit\/{id}&quot;, (Fruit fruit, string id) =&gt; {\/* *\/})<\/code><\/p>\n<p>\u2022   <code>MapPut(&quot;\/fruit\/{id}&quot;, (Fruit fruit, string id) =&gt; {\/* *\/})<\/code><\/p>\n<p>\u2022   <code>MapDelete(&quot;\/fruit\/{id}&quot;, (string id) =&gt; {\/* *\/})<\/code><\/p>\n<p>Additionally, the last four endpoints need to validate the id parameter. This validation can be extracted to a helper method and applied as a filter, but you still need to remember to apply the filter when you add a new endpoint.<\/p>\n<p>\u6b64\u5916\uff0c\u6700\u540e\u56db\u4e2a\u7aef\u70b9\u9700\u8981\u9a8c\u8bc1 id \u53c2\u6570\u3002\u6b64\u9a8c\u8bc1\u53ef\u4ee5\u63d0\u53d6\u5230\u5e2e\u52a9\u7a0b\u5e8f\u65b9\u6cd5\u5e76\u4f5c\u4e3a\u7b5b\u9009\u5668\u5e94\u7528\uff0c\u4f46\u60a8\u4ecd\u9700\u8981\u8bb0\u4f4f\u5728\u6dfb\u52a0\u65b0\u7ec8\u7aef\u8282\u70b9\u65f6\u5e94\u7528\u7b5b\u9009\u5668\u3002<\/p>\n<p>All this duplication can be removed by using route groups. You can use route groups to extract common path segments or filters to a single location, reducing the duplication in your endpoint definitions. You create a route group by calling MapGroup(&quot;\/fruit&quot;) on the WebApplication instance, providing a route prefix for the group (&quot;\/fruit&quot;, in this case), and MapGroup() returns a RouteGroupBuilder.<\/p>\n<p>\u6709\u8fd9\u4e9b\u91cd\u590d\u90fd\u53ef\u4ee5\u901a\u8fc7\u4f7f\u7528\u8def\u7531\u7ec4\u6765\u5220\u9664\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u8def\u7531\u7ec4\u5c06\u516c\u5171\u8def\u5f84\u6bb5\u6216\u7b5b\u9009\u6761\u4ef6\u63d0\u53d6\u5230\u5355\u4e2a\u4f4d\u7f6e\uff0c\u4ece\u800c\u51cf\u5c11\u7ec8\u7aef\u8282\u70b9\u5b9a\u4e49\u3002\u901a\u8fc7\u5728 WebApplication \u5b9e\u4f8b\u4e0a\u8c03\u7528 MapGroup\uff08\u201c\/fruit\u201d\uff09 \u6765\u521b\u5efa\u8def\u7531\u7ec4\uff0c\u4e3a\u8be5\u7ec4\u63d0\u4f9b\u8def\u7531\u524d\u7f00\uff08\u5728\u672c\u4f8b\u4e2d\u4e3a \u201c\/fruit\u201d\uff09\uff0cMapGroup\uff08\uff09 \u5c06\u8fd4\u56de RouteGroupBuilder\u3002<\/p>\n<p>When you have a RouteGroupBuilder, you can call the same Map* extension methods on RouteGroupBuilder as you do on WebApplication. The only difference is that all the endpoints you define on the group will have the prefix &quot;\/fruit&quot; applied to each endpoint you define, as shown in figure 5.11. Similarly, you can call AddEndpointFilter() on a route group, and all the endpoints on the group will also use the filter.<\/p>\n<p>\u5f53\u60a8\u62e5\u6709 RouteGroupBuilder \u65f6\uff0c\u60a8\u53ef\u4ee5\u5728 RouteGroupBuilder \u4e0a\u8c03\u7528\u4e0e\u5728 WebApplication \u4e0a\u76f8\u540c\u7684 Map* \u6269\u5c55\u65b9\u6cd5\u3002\u552f\u4e00\u7684\u533a\u522b\u662f\uff0c\u60a8\u5728\u7ec4\u4e0a\u5b9a\u4e49\u7684\u6240\u6709\u7aef\u70b9\u90fd\u5c06\u5c06\u524d\u7f00 \u201c\/fruit\u201d \u5e94\u7528\u4e8e\u60a8\u5b9a\u4e49\u7684\u6bcf\u4e2a\u7aef\u70b9\uff0c\u5982\u56fe 5.11 \u6240\u793a\u3002\u540c\u6837\uff0c\u60a8\u53ef\u4ee5\u5728\u8def\u7531\u7ec4\u4e0a\u8c03\u7528 AddEndpointFilter\uff08\uff09\uff0c\u8be5\u7ec4\u4e0a\u7684\u6240\u6709\u7aef\u70b9\u4e5f\u5c06\u4f7f\u7528\u8be5\u8fc7\u6ee4\u5668\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0511.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 5.11 Using route groups to simplify the definition of endpoints. You can create a route group by calling MapGroup() and providing a prefix. Any endpoints created on the route group inherit the route template prefix, as well as any filters added to the group.<br \/>\n\u56fe 5.11 \u4f7f\u7528\u8def\u7531\u7ec4\u7b80\u5316\u7ec8\u7aef\u8282\u70b9\u7684\u5b9a\u4e49\u3002\u60a8\u53ef\u4ee5\u901a\u8fc7\u8c03\u7528 MapGroup\uff08\uff09 \u5e76\u63d0\u4f9b\u524d\u7f00\u6765\u521b\u5efa\u8def\u7531\u7ec4\u3002\u5728\u8def\u7531\u7ec4\u4e0a\u521b\u5efa\u7684\u4efb\u4f55\u7ec8\u7aef\u8282\u70b9\u90fd\u4f1a\u7ee7\u627f\u8def\u7531\u6a21\u677f\u524d\u7f00\uff0c\u4ee5\u53ca\u6dfb\u52a0\u5230\u7ec4\u7684\u4efb\u4f55\u7b5b\u9009\u6761\u4ef6\u3002<\/p>\n<p>You can even create nested groups by calling MapGroup() on a group. The prefixes are applied to your endpoints in order, so the first MapGroup() call defines the prefix used at the start of the route. app.MapGroup(&quot;\/fruit&quot;).MapGroup(&quot;\/citrus&quot;), for example, would have the prefix &quot;\/fruit\/citrus&quot;.<\/p>\n<p>\u60a8\u751a\u81f3\u53ef\u4ee5\u901a\u8fc7\u5bf9\u7ec4\u8c03\u7528 MapGroup\uff08\uff09 \u6765\u521b\u5efa\u5d4c\u5957\u7ec4\u3002\u524d\u7f00\u6309\u987a\u5e8f\u5e94\u7528\u4e8e\u60a8\u7684\u7ec8\u7aef\u8282\u70b9\uff0c\u56e0\u6b64\u7b2c\u4e00\u4e2a MapGroup\uff08\uff09 \u8c03\u7528\u5b9a\u4e49\u4f7f\u7528\u7684\u524d\u7f00\u5728\u8def\u7ebf\u7684\u8d77\u70b9\u5904\u3002\u5e94\u7528\u7a0b\u5e8f\u3002MapGroup\uff08\u201c\/fruit\u201d\uff09 \u7684\u4f8b\u5982\uff0cMapGroup\uff08\u201c\/citrus\u201d\uff09 \u5c06\u5177\u6709\u524d\u7f00 \u201c\/fruit\/citrus\u201d\u3002<\/p>\n<p><strong>Tip<\/strong> If you don\u2019t want to add a prefix but still want to use the route group for applying filters, you can pass the prefix &quot;\/&quot; to MapGroup().<br \/>\n<strong>\u63d0\u793a<\/strong> \u5982\u679c\u60a8\u4e0d\u60f3\u6dfb\u52a0\u524d\u7f00\uff0c\u4f46\u4ecd\u60f3\u4f7f\u7528\u8def\u7531\u7ec4\u6765\u5e94\u7528\u7b5b\u9009\u6761\u4ef6\uff0c\u5219\u53ef\u4ee5\u5c06\u524d\u7f00\u201c\/\u201d\u4f20\u9012\u7ed9 MapGroup\uff08\uff09\u3002<\/p>\n<p>Listing 5.13 shows an example of rewriting the fruit API to use route groups. It creates a top-level fruitApi, which applies the &quot;\/fruit&quot; prefix, and creates a nested route group called fruitApiWithValidation for the endpoints that require a filter. You can find the complete example comparing the versions with and without route groups in the source code for this chapter.<\/p>\n<p>\u6e05\u5355 5.13 \u5c55\u793a\u4e86\u4e00\u4e2a\u91cd\u5199 fruit API \u4ee5\u4f7f\u7528\u8def\u7531\u7ec4\u7684\u793a\u4f8b\u3002\u5b83\u521b\u5efa\u4e00\u4e2a\u9876\u7ea7 fruitApi\uff0c\u8be5 fruitApi \u5e94\u7528\u201c\/fruit\u201d\u524d\u7f00\uff0c\u5e76\u4e3a\u9700\u8981\u7b5b\u9009\u6761\u4ef6\u7684\u7ec8\u7aef\u8282\u70b9\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a fruitApiWithValidation \u7684\u5d4c\u5957\u8def\u7531\u7ec4\u3002\u60a8\u53ef\u4ee5\u5728\u672c\u7ae0\u7684\u6e90\u4ee3\u7801\u4e2d\u627e\u5230\u6bd4\u8f83\u5e26\u548c\u4e0d\u5e26\u8def\u7531\u7ec4\u7684\u7248\u672c\u7684\u5b8c\u6574\u793a\u4f8b\u3002<\/p>\n<p>Listing 5.13 Reducing duplication with route groups<br \/>\n\u6e05\u5355 5.13 \u51cf\u5c11\u8def\u7531\u7ec4\u7684\u91cd\u590d<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\nvar _fruit = new System.Collections.Concurrent.ConcurrentDictionary&lt;string, Fruit&gt;();\n\nRouteGroupBuilder fruitApi = app.MapGroup(&quot;\/fruit&quot;);                               \/\/ \u2776\n\nfruitApi.MapGet(&quot;\/&quot;, () =&gt; _fruit);                                                \/\/ \u2777\n\nRouteGroupBuilder fruitApiWithValidation = fruitApi.MapGroup(&quot;\/&quot;)                  \/\/ \u2778\n    .AddEndpointFilter(ValidationHelper.ValidateIdFactory);                        \/\/ \u2779\n\n\/\/ \u8fd8\u6709\u5176\u4ed6\u4ee3\u7801\uff0c\u8be6\u7ec6\u7684\u67e5\u770b\u6e90\u4ee3\u7801\n\nfruitApiWithValidation.MapGet(&quot;\/{id}&quot;, (string id) =&gt;                              \/\/ \u277a\n    _fruit.TryGetValue(id, out var fruit)\n        ? TypedResults.Ok(fruit)\n        : Results.Problem(statusCode: 404));\n\nfruitApiWithValidation.MapPost(&quot;\/{id}&quot;, (Fruit fruit, string id) =&gt;                \/\/ \u277a\n    _fruit.TryAdd(id, fruit)\n        ? TypedResults.Created($&quot;\/fruit\/{id}&quot;, fruit)\n        : Results.ValidationProblem(new Dictionary&lt;string, string&#x5B;]&gt;\n            {\n                { &quot;id&quot;, new&#x5B;] { &quot;A fruit with this id already exists&quot; } }\n        }));\n\nfruitApiWithValidation.MapPut(&quot;\/{id}&quot;, (string id, Fruit fruit) =&gt;                 \/\/ \u277a\n{\n    _fruit&#x5B;id] = fruit;\n    return Results.NoContent();\n});\n\nfruitApiWithValidation.MapDelete(&quot;\/fruit\/{id}&quot;, (string id) =&gt;                     \/\/ \u277a\n{\n    _fruit.TryRemove(id, out _);\n    return Results.NoContent();\n});\n\napp.Run();\nrecord Fruit(string Name, int Stock);\n\n<\/pre>\n<p>\u2776 Creates a route group by calling MapGroup and providing a prefix<br \/>\n\u901a\u8fc7\u8c03\u7528 MapGroup \u5e76\u63d0\u4f9b\u524d\u7f00\u6765\u521b\u5efa\u8def\u7531\u7ec4<\/p>\n<p>\u2777 Endpoints defined on the route group will have the group prefix prepended to the route.<br \/>\n\u5728\u8def\u7531\u7ec4\u4e0a\u5b9a\u4e49\u7684\u7ec8\u7aef\u8282\u70b9\u5c06\u5728\u8def\u7531\u524d\u9762\u52a0\u4e0a\u7ec4\u524d\u7f00\u3002<\/p>\n<p>\u2778 You can create nested route groups with multiple prefixes.<br \/>\n\u60a8\u53ef\u4ee5\u521b\u5efa\u5177\u6709\u591a\u4e2a\u524d\u7f00\u7684\u5d4c\u5957\u8def\u7531\u7ec4\u3002<\/p>\n<p>\u2779 You can add filters to the route group . . .<br \/>\n\u60a8\u53ef\u4ee5\u5411\u8def\u7531\u7ec4\u6dfb\u52a0\u8fc7\u6ee4\u5668 . . .<\/p>\n<p>\u277a . . . and the filter will be applied to all the endpoints defined on the route group.<br \/>\n. . . .\u7b5b\u9009\u6761\u4ef6\u5c06\u5e94\u7528\u4e8e\u8def\u7531\u7ec4\u4e0a\u5b9a\u4e49\u7684\u6240\u6709\u7ec8\u7aef\u8282\u70b9\u3002<\/p>\n<p>In .NET 6, minimal APIs were a bit too verbose to be generally recommended, but with the addition of route groups and filters, minimal APIs have come into their own. In chapter 6 you\u2019ll learn more about routing and route template syntax, as well as how to generate links to other endpoints.<br \/>\n\u5728 .NET 6 \u4e2d\uff0c\u6700\u5c0f API \u6709\u70b9\u8fc7\u4e8e\u5197\u957f\uff0c\u901a\u5e38\u4e0d\u63a8\u8350\u4f7f\u7528\uff0c\u4f46\u968f\u7740\u8def\u7531\u7ec4\u548c\u7b5b\u9009\u5668\u7684\u6dfb\u52a0\uff0c\u6700\u5c0f API \u5df2\u7ecf\u6709\u4e86\u81ea\u5df1\u7684\u529f\u80fd\u3002\u5728\u7b2c 6 \u7ae0\u4e2d\uff0c\u60a8\u5c06\u4e86\u89e3\u6709\u5173\u8def\u7531\u548c\u8def\u7531\u6a21\u677f\u8bed\u6cd5\u7684\u66f4\u591a\u4fe1\u606f\uff0c\u4ee5\u53ca\u5982\u4f55\u751f\u6210\u6307\u5411\u5176\u4ed6\u7ec8\u7aef\u8282\u70b9\u7684\u94fe\u63a5\u3002<\/p>\n<h2>5.6 Summary<\/h2>\n<h2>5.6 \u603b\u7ed3<\/h2>\n<p>HTTP verbs define the semantic expectation for a request. GET is used to fetch data, POST creates a resource, PUT creates or replaces a resource, and DELETE removes a resource. Following these conventions will make your API easier to consume.<br \/>\nHTTP \u52a8\u8bcd\u5b9a\u4e49\u8bf7\u6c42\u7684\u8bed\u4e49\u671f\u671b\u3002GET \u7528\u4e8e\u83b7\u53d6\u6570\u636e\uff0cPOST \u521b\u5efa\u8d44\u6e90\uff0cPUT \u521b\u5efa\u6216\u66ff\u6362\u8d44\u6e90\uff0cDELETE \u5220\u9664\u8d44\u6e90\u3002\u9075\u5faa\u8fd9\u4e9b\u7ea6\u5b9a\u5c06\u4f7f API \u66f4\u6613\u4e8e\u4f7f\u7528\u3002<\/p>\n<p>Each HTTP response includes a status code. Common codes include 200 OK, 201 Created, 400 Bad Request, and 404 Not Found. It\u2019s important to use the correct status code, as clients use these status codes to infer the behavior of your API.<br \/>\n\u6bcf\u4e2a HTTP \u54cd\u5e94\u90fd\u5305\u542b\u4e00\u4e2a\u72b6\u6001\u4ee3\u7801\u3002\u5e38\u89c1\u4ee3\u7801\u5305\u62ec 200 OK\u3001201 Created\u3001400 \u9519\u8bef\u8bf7\u6c42\uff0c404 \u672a\u627e\u5230\u3002\u4f7f\u7528\u6b63\u786e\u7684\u72b6\u6001\u4ee3\u7801\u975e\u5e38\u91cd\u8981\uff0c\u56e0\u4e3a\u5ba2\u6237\u7aef\u4f7f\u7528\u8fd9\u4e9b\u72b6\u6001\u4ee3\u7801\u6765\u63a8\u65ad API \u7684\u884c\u4e3a\u3002<\/p>\n<p>An HTTP API exposes methods or endpoints that you can use to access or change data on a server using the HTTP protocol. An HTTP API is typically called by mobile or client-side web applications.<br \/>\nHTTP API \u516c\u5f00\u53ef\u7528\u4e8e\u4f7f\u7528 HTTP \u534f\u8bae\u8bbf\u95ee\u6216\u66f4\u6539\u670d\u52a1\u5668\u4e0a\u7684\u6570\u636e\u7684\u65b9\u6cd5\u6216\u7aef\u70b9\u3002HTTP API \u901a\u5e38\u7531\u79fb\u52a8\u6216\u5ba2\u6237\u7aef Web \u5e94\u7528\u7a0b\u5e8f\u8c03\u7528\u3002<\/p>\n<p>You define minimal API endpoints by calling Map functions on the WebApplication instance, passing in a route pattern to match and a handler function. The handler functions runs in response to matching requests.<br \/>\n\u901a\u8fc7\u5728 WebApplication \u5b9e\u4f8b\u4e0a\u8c03\u7528 Map \u51fd\u6570\uff0c\u4f20\u5165\u8981\u5339\u914d\u7684\u8def\u7531\u6a21\u5f0f\u548c\u5904\u7406\u7a0b\u5e8f\u51fd\u6570\uff0c\u53ef\u4ee5\u5b9a\u4e49\u6700\u5c0f\u7684 API \u7aef\u70b9\u3002\u5904\u7406\u7a0b\u5e8f\u51fd\u6570\u8fd0\u884c\u4ee5\u54cd\u5e94\u5339\u914d\u7684\u8bf7\u6c42\u3002<\/p>\n<p>There are different extension methods for each HTTP verb. MapGet handles GET requests, for example, and MapPost maps POST requests. You use these extension methods to define how your app handles a given route and HTTP verb.<br \/>\n\u6bcf\u4e2a HTTP \u52a8\u8bcd\u90fd\u6709\u4e0d\u540c\u7684\u6269\u5c55\u65b9\u6cd5\u3002\u4f8b\u5982\uff0cMapGet \u5904\u7406 GET \u8bf7\u6c42\uff0c\u800c MapPost \u6620\u5c04 POST \u8bf7\u6c42\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u8fd9\u4e9b\u6269\u5c55\u65b9\u6cd5\u6765\u5b9a\u4e49\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u5982\u4f55\u5904\u7406\u7ed9\u5b9a\u7684\u8def\u7531\u548c HTTP \u52a8\u8bcd\u3002<\/p>\n<p>You can define your endpoint handlers as lambda expressions, <code>Func&lt;T, TResult&gt;<\/code> and <code>Action&lt;T&gt;<\/code> variables, local functions, instance methods, or static methods. The best approach depends on how complex your handler is, as well as personal preference.<br \/>\n\u60a8\u53ef\u4ee5\u5c06\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u5b9a\u4e49\u4e3a lambda \u8868\u8fbe\u5f0f\u3001Func<T>\u3001TResult \u548c Action<T> \u53d8\u91cf\u3001\u672c\u5730\u51fd\u6570\u3001\u5b9e\u4f8b\u65b9\u6cd5\u6216\u9759\u6001\u65b9\u6cd5\u3002\u6700\u597d\u7684\u65b9\u6cd5\u53d6\u51b3\u4e8e\u60a8\u7684\u5904\u7406\u7a0b\u5e8f\u7684\u590d\u6742\u7a0b\u5ea6\uff0c\u4ee5\u53ca\u4e2a\u4eba\u559c\u597d\u3002<\/p>\n<p>Returning void from your endpoint handler generates a 200 response with no body by default. Returning a string generates a text\/plain response. Returning an IResult instance can generate any response. Any other object returned from your endpoint handler is serialized to JSON. This convention helps keep your endpoint handlers succinct.<br \/>\n\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u4ece\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u8fd4\u56de void \u4f1a\u751f\u6210\u4e00\u4e2a\u6ca1\u6709\u6b63\u6587\u7684 200 \u54cd\u5e94\u3002\u8fd4\u56de\u5b57\u7b26\u4e32\u4f1a\u751f\u6210 text\/plain \u54cd\u5e94\u3002\u8fd4\u56de IResult \u5b9e\u4f8b\u53ef\u4ee5\u751f\u6210\u4efb\u4f55\u54cd\u5e94\u3002\u4ece\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u8fd4\u56de\u7684\u4efb\u4f55\u5176\u4ed6\u5bf9\u8c61\u90fd\u5c06\u5e8f\u5217\u5316\u4e3a JSON\u3002\u6b64\u7ea6\u5b9a\u6709\u52a9\u4e8e\u4fdd\u6301\u7ec8\u7ed3\u70b9\u5904\u7406\u7a0b\u5e8f\u7684\u7b80\u6d01\u6027\u3002<\/p>\n<p>You can customize the response by injecting an HttpResponse object into your endpoint handler and then setting the status code and response body. This approach can be useful if you have complex requirements for an endpoint.<br \/>\n\u60a8\u53ef\u4ee5\u901a\u8fc7\u5c06 HttpResponse \u5bf9\u8c61\u6ce8\u5165\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\uff0c\u7136\u540e\u8bbe\u7f6e\u72b6\u6001\u4ee3\u7801\u548c\u54cd\u5e94\u6b63\u6587\u6765\u81ea\u5b9a\u4e49\u54cd\u5e94\u3002\u5982\u679c\u60a8\u5bf9\u7ec8\u7aef\u8282\u70b9\u6709\u590d\u6742\u7684\u8981\u6c42\uff0c\u5219\u6b64\u65b9\u6cd5\u53ef\u80fd\u5f88\u6709\u7528\u3002<\/p>\n<p>The Results and TypedResults helpers contain static methods for generating common responses, such as a 404 Not Found response using Results.NotFound(). These helpers simplifying returning common status codes.<br \/>\nResults \u548c TypedResults \u5e2e\u52a9\u7a0b\u5e8f\u5305\u542b\u7528\u4e8e\u751f\u6210\u5e38\u89c1\u54cd\u5e94\u7684\u9759\u6001\u65b9\u6cd5\uff0c\u4f8b\u5982\u4f7f\u7528 Results.NotFound\uff08\uff09 \u7684 404 Not Found \u54cd\u5e94\u3002\u8fd9\u4e9b\u5e2e\u52a9\u7a0b\u5e8f\u7b80\u5316\u4e86\u8fd4\u56de\u5e38\u89c1\u72b6\u6001\u4ee3\u7801\u7684\u8fc7\u7a0b\u3002<\/p>\n<p>You can return a standard Problem Details object by using Results.Problem() and Results.ValiationProblem(). Problem() generates a 500 response by default (which can be changed), and ValidationProblem() generates a 400 response, with a list of validation errors. These methods make returning Problem Details objects more concise than generating the response manually.<br \/>\n\u60a8\u53ef\u4ee5\u4f7f\u7528 Results.Problem\uff08\uff09 \u548c Results.ValiationProblem\uff08\uff09 \u8fd4\u56de\u6807\u51c6 Problem Details \u5bf9\u8c61\u3002\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cProblem\uff08\uff09 \u4f1a\u751f\u6210 500 \u54cd\u5e94\uff08\u53ef\u4ee5\u66f4\u6539\uff09\uff0c\u800c ValidationProblem\uff08\uff09 \u4f1a\u751f\u6210 400 \u54cd\u5e94\uff0c\u5176\u4e2d\u5305\u542b\u9a8c\u8bc1\u9519\u8bef\u5217\u8868\u3002\u8fd9\u4e9b\u65b9\u6cd5\u4f7f\u8fd4\u56de Problem Details \u5bf9\u8c61\u6bd4\u624b\u52a8\u751f\u6210\u54cd\u5e94\u66f4\u7b80\u6d01\u3002<\/p>\n<p>You can use helper methods to generate other common result types on Results, such as File() for returning a file from disk, Bytes() for returning arbitrary binary data, and Stream() for returning an arbitrary stream.<br \/>\n\u60a8\u53ef\u4ee5\u4f7f\u7528\u5e2e\u52a9\u7a0b\u5e8f\u65b9\u6cd5\u5728 Results \u4e0a\u751f\u6210\u5176\u4ed6\u5e38\u89c1\u7684\u7ed3\u679c\u7c7b\u578b\uff0c\u4f8b\u5982\u7528\u4e8e\u4ece\u78c1\u76d8\u8fd4\u56de\u6587\u4ef6\u7684 File\uff08\uff09\u3001\u7528\u4e8e\u8fd4\u56de\u4efb\u610f\u4e8c\u8fdb\u5236\u6570\u636e\u7684 Bytes\uff08\uff09 \u548c\u7528\u4e8e\u8fd4\u56de\u4efb\u610f\u6d41\u7684 Stream\uff08\uff09\u3002<\/p>\n<p>You can extract common or tangential code from your endpoint handlers by using endpoint filters, which can keep your endpoint handlers easy to read.<br \/>\n\u60a8\u53ef\u4ee5\u4f7f\u7528\u7ec8\u7aef\u8282\u70b9\u7b5b\u9009\u6761\u4ef6\u4ece\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u63d0\u53d6\u5e38\u89c1\u6216\u65e0\u5173\u4ee3\u7801\uff0c\u8fd9\u53ef\u4ee5\u4f7f\u60a8\u7684\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u6613\u4e8e\u9605\u8bfb\u3002<\/p>\n<p>Add a filter to an endpoint by calling AddEndpointFilter() and providing the lambda function to run (or use a static\/instance method). You can also implement IEndpointFilter and call AddEndpointFilter<T>(), where T is the name of your implementing class.<br \/>\n\u901a\u8fc7\u8c03\u7528 AddEndpointFilter\uff08\uff09 \u5e76\u63d0\u4f9b\u8981\u8fd0\u884c\u7684 lambda \u51fd\u6570\uff08\u6216\u4f7f\u7528 static\/instance \u65b9\u6cd5\uff09\uff0c\u5411\u7ec8\u7aef\u8282\u70b9\u6dfb\u52a0\u7b5b\u9009\u6761\u4ef6\u3002\u60a8\u8fd8\u53ef\u4ee5\u5b9e\u73b0 IEndpointFilter \u5e76\u8c03\u7528 AddEndpointFilter<T>\uff08\uff09\uff0c\u5176\u4e2d T \u662f\u5b9e\u73b0\u7c7b\u7684\u540d\u79f0\u3002<\/p>\n<p>You can generalize your filter functions by creating a factory, using the overload of AddEndpointFilter() that takes an EndpointFilterFactoryContext. You can use this approach to support endpoint handlers with various method signatures.<br \/>\n\u4f60\u53ef\u4ee5\u901a\u8fc7\u521b\u5efa\u4e00\u4e2a\u5de5\u5382\u6765\u901a\u7528\u4f60\u7684\u8fc7\u6ee4\u5668\u51fd\u6570\uff0c\u4f7f\u7528\u91c7\u7528EndpointFilterFactoryContext\u7684AddEndpointFilter\uff08\uff09\u7684\u91cd\u8f7d\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u6b64\u65b9\u6cd5\u6765\u652f\u6301\u5177\u6709\u5404\u79cd\u65b9\u6cd5\u7b7e\u540d\u7684\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u3002<\/p>\n<p>You can reduce duplication in your endpoint routes and filter configuration by using route groups. Call MapGroup() on WebApplication, and provide a prefix. All endpoints created on the returned RouteGroupBuilder will use the prefix in their route templates.<br \/>\n\u60a8\u53ef\u4ee5\u4f7f\u7528\u8def\u7531\u7ec4\u51cf\u5c11\u7ec8\u7aef\u8282\u70b9\u8def\u7531\u548c\u7b5b\u9009\u6761\u4ef6\u914d\u7f6e\u4e2d\u7684\u91cd\u590d\u3002\u5728 WebApplication \u4e0a\u8c03\u7528 MapGroup\uff08\uff09 \u5e76\u63d0\u4f9b\u524d\u7f00\u3002\u5728\u8fd4\u56de\u7684 RouteGroupBuilder \u4e0a\u521b\u5efa\u7684\u6240\u6709\u7ec8\u7aef\u8282\u70b9\u90fd\u5c06\u5728\u5176\u8def\u7531\u6a21\u677f\u4e2d\u4f7f\u7528\u8be5\u524d\u7f00\u3002<\/p>\n<p>You can also call AddEndpointFilter() on route groups. Any endpoints defined on the group will also have the filter, as though you defined them on the endpoint directly, removing the need to duplicate the call on each endpoint.<br \/>\n\u60a8\u8fd8\u53ef\u4ee5\u5728\u8def\u7531\u7ec4\u4e0a\u8c03\u7528 AddEndpointFilter\uff08\uff09\u3002 \u5728\u7ec4\u4e0a\u5b9a\u4e49\u7684\u4efb\u4f55\u7ec8\u7aef\u8282\u70b9\u4e5f\u5c06\u5177\u6709\u7b5b\u9009\u6761\u4ef6\uff0c\u5c31\u50cf\u60a8\u76f4\u63a5\u5728\u7ec8\u7aef\u8282\u70b9\u4e0a\u5b9a\u4e49\u5b83\u4eec\u4e00\u6837\uff0c\u65e0\u9700\u5728\u6bcf\u4e2a\u7ec8\u7aef\u8282\u70b9\u4e0a\u590d\u5236\u8c03\u7528\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>5 Creating a JSON API with minimal APIs 5 \u4f7f\u7528\u6700\u5c11\u7684 API \u521b\u5efa JSON API This chapter covers \u672c\u7ae0\u6db5\u76d6 \u2022 Creating a minimal API application to return JSON to clients \u521b\u5efa\u6700\u5c0f API \u5e94\u7528\u7a0b\u5e8f\u4ee5\u5c06 JSON \u8fd4\u56de\u7ed9\u5ba2\u6237\u7aef \u2022 Generating responses with IResult \u751f\u6210\u54cd\u5e94IResult \u2022 Using filters to perform common actions like validation \u4f7f\u7528\u7b5b\u9009\u5668\u6267\u884c\u5e38\u89c1\u4f5c\uff0c\u5982\u9a8c\u8bc1 \u2022 Organizing your APIs with [&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":[19],"class_list":["post-578","post","type-post","status-publish","format-standard","hentry","category-csharp","tag-asp-net-core-in-action"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/578","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=578"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/578\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=578"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=578"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=578"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}