{"id":585,"date":"2025-04-05T11:36:00","date_gmt":"2025-04-05T03:36:00","guid":{"rendered":"https:\/\/www.hyy.net\/?p=585"},"modified":"2025-04-05T11:36:00","modified_gmt":"2025-04-05T03:36:00","slug":"asp-net-core-in-action-7-model-binding-and-validation-in-minimal-apis","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=585","title":{"rendered":"ASP.NET Core in Action 7 Model binding and validation in minimal APIs"},"content":{"rendered":"<p>7 Model binding and validation in minimal APIs<br \/>\n7 \u6700\u5c0f API \u4e2d\u7684\u6a21\u578b\u7ed1\u5b9a\u548c\u9a8c\u8bc1<\/p>\n<h2>This chapter covers<\/h2>\n<h2>\u672c\u7ae0\u6db5\u76d6<\/h2>\n<ul>\n<li>\n<p>Using request values to create binding models<br \/>\n\u4f7f\u7528\u8bf7\u6c42\u503c\u521b\u5efa\u7ed1\u5b9a\u6a21\u578b<\/p>\n<\/li>\n<li>\n<p>Customizing the model-binding process<br \/>\n\u81ea\u5b9a\u4e49\u6a21\u578b\u7ed1\u5b9a\u8fc7\u7a0b<\/p>\n<\/li>\n<li>\n<p>Validating user input using DataAnnotations attributes<br \/>\n\u4f7f\u7528 DataAnnotations \u5c5e\u6027\u9a8c\u8bc1\u7528\u6237\u8f93\u5165<\/p>\n<\/li>\n<\/ul>\n<p>In chapter 6 I showed you how to define a route with parameters\u2014perhaps for the unique ID for a product API. But say a client sends a request to the product API. What then? How do you access the values provided in the request and read the JavaScript Object Notation (JSON) in the request body?<br \/>\n\u5728\u7b2c 6 \u7ae0\u4e2d\uff0c\u6211\u5411\u60a8\u5c55\u793a\u4e86\u5982\u4f55\u4f7f\u7528\u53c2\u6570\u5b9a\u4e49\u8def\u7531 \u2014 \u53ef\u80fd\u662f\u4e3a\u4e86\u4ea7\u54c1 API \u7684\u552f\u4e00 ID\u3002\u4f46\u662f\uff0c\u5047\u8bbe\u5ba2\u6237\u7aef\u5411\u4ea7\u54c1 API \u53d1\u9001\u8bf7\u6c42\u3002\u90a3\u53c8\u5982\u4f55\u5462\uff1f\u5982\u4f55\u8bbf\u95ee\u8bf7\u6c42\u4e2d\u63d0\u4f9b\u7684\u503c\u5e76\u8bfb\u53d6\u8bf7\u6c42\u6b63\u6587\u4e2d\u7684 JavaScript \u5bf9\u8c61\u8868\u793a\u6cd5 \uff08JSON\uff09\uff1f<\/p>\n<p>For most of this chapter, in sections 7.1-7.9, we\u2019ll look at model binding and how it simplifies reading data from a request in minimal APIs. You\u2019ll see how to take the data posted in the request body or in the URL and bind it to C# objects, which are then passed to your endpoint handler methods as arguments. When your handler executes, it can use these values to do something useful\u2014return a product\u2019s details or change a product\u2019s name, for example.<\/p>\n<p>\u5728\u672c\u7ae0\u7684\u5927\u90e8\u5206\u5185\u5bb9\u4e2d\uff0c\u5728 7.1-7.9 \u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u6a21\u578b\u7ed1\u5b9a\u4ee5\u53ca\u5b83\u5982\u4f55\u7b80\u5316\u5728\u6700\u5c0f API \u4e2d\u4ece\u8bf7\u6c42\u4e2d\u8bfb\u53d6\u6570\u636e\u7684\u8fc7\u7a0b\u3002\u60a8\u5c06\u4e86\u89e3\u5982\u4f55\u83b7\u53d6\u8bf7\u6c42\u6b63\u6587\u6216 URL \u4e2d\u53d1\u5e03\u7684\u6570\u636e\u5e76\u5c06\u5176\u7ed1\u5b9a\u5230 C# \u5bf9\u8c61\uff0c\u7136\u540e\u5c06\u8fd9\u4e9b\u5bf9\u8c61\u4f5c\u4e3a\u53c2\u6570\u4f20\u9012\u7ed9\u60a8\u7684\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u3002\u5f53\u60a8\u7684\u5904\u7406\u7a0b\u5e8f\u6267\u884c\u65f6\uff0c\u5b83\u53ef\u4ee5\u4f7f\u7528\u8fd9\u4e9b\u503c\u6765\u6267\u884c\u4e00\u4e9b\u6709\u7528\u7684\u4f5c\uff0c\u4f8b\u5982\uff0c\u8fd4\u56de\u4ea7\u54c1\u7684\u8be6\u7ec6\u4fe1\u606f\u6216\u66f4\u6539\u4ea7\u54c1\u7684\u540d\u79f0\u3002<\/p>\n<p>When your code is executing in an endpoint handler method, you might be forgiven for thinking that you can happily use the binding model without any further thought. Hold on, though. Where did that data come from? From a user\u2014and you know users can\u2019t be trusted! Section 7.10 focuses on how to make sure that the user-provided values are valid and make sense for your app.<\/p>\n<p>\u5f53\u60a8\u7684\u4ee3\u7801\u5728\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u4e2d\u6267\u884c\u65f6\uff0c\u60a8\u53ef\u80fd\u4f1a\u8ba4\u4e3a\u65e0\u9700\u4efb\u4f55\u8fdb\u4e00\u6b65\u8003\u8651\u5373\u53ef\u6109\u5feb\u5730\u4f7f\u7528\u7ed1\u5b9a\u6a21\u578b\uff0c\u8fd9\u662f\u53ef\u4ee5\u7406\u89e3\u7684\u3002\u4e0d\u8fc7\uff0c\u8bf7\u7a0d\u7b49\u3002\u8fd9\u4e9b\u6570\u636e\u4ece\u4f55\u800c\u6765\uff1f\u4ece\u7528\u6237 - \u4ee5\u53ca\u4f60\u77e5\u9053\u7528\u6237\u4e0d\u53ef\u4fe1\uff01\u7b2c 7.10 \u8282\u91cd\u70b9\u4ecb\u7ecd\u5982\u4f55\u786e\u4fdd\u7528\u6237\u63d0\u4f9b\u7684\u503c\u6709\u6548\u4e14\u5bf9\u60a8\u7684\u5e94\u7528\u6709\u610f\u4e49\u3002<\/p>\n<p>Model binding is the process of taking the user\u2019s raw HTTP request and making it available to your code by populating plain old CLR objects (POCOs), providing the input to your endpoint handlers. We start by looking at which values in the request are available for binding and where model binding fits in your running app.<\/p>\n<p>\u6a21\u578b\u7ed1\u5b9a\u662f\u901a\u8fc7\u586b\u5145\u666e\u901a\u65e7 CLR \u5bf9\u8c61 \uff08POCO\uff09 \u6765\u83b7\u53d6\u7528\u6237\u7684\u539f\u59cb HTTP \u8bf7\u6c42\u5e76\u5c06\u5176\u63d0\u4f9b\u7ed9\u60a8\u7684\u4ee3\u7801\u7684\u8fc7\u7a0b\uff0c\u4ece\u800c\u4e3a\u60a8\u7684\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u63d0\u4f9b\u8f93\u5165\u3002\u6211\u4eec\u9996\u5148\u67e5\u770b\u8bf7\u6c42\u4e2d\u7684\u54ea\u4e9b\u503c\u53ef\u7528\u4e8e\u7ed1\u5b9a\uff0c\u4ee5\u53ca\u6a21\u578b\u7ed1\u5b9a\u5728\u6b63\u5728\u8fd0\u884c\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\u9002\u5408\u7684\u4f4d\u7f6e\u3002<\/p>\n<h2>7.1 Extracting values from a request with model binding<\/h2>\n<h2>7.1 \u4f7f\u7528\u6a21\u578b\u7ed1\u5b9a\u4ece\u8bf7\u6c42\u4e2d\u63d0\u53d6\u503c<\/h2>\n<p>In chapters 5 and 6 you learned that route parameters can be extracted from the request\u2019s path and used to execute minimal API handlers. In this section we look in more detail at the process of extracting route parameters and the concept of model binding.<\/p>\n<p>\u5728\u7b2c 5 \u7ae0\u548c\u7b2c 6 \u7ae0\u4e2d\uff0c\u60a8\u4e86\u89e3\u4e86\u53ef\u4ee5\u4ece\u8bf7\u6c42\u7684\u8def\u5f84\u4e2d\u63d0\u53d6\u8def\u7531\u53c2\u6570\uff0c\u5e76\u7528\u4e8e\u6267\u884c\u6700\u5c0f\u7684 API \u5904\u7406\u7a0b\u5e8f\u3002\u5728\u672c\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u66f4\u8be6\u7ec6\u5730\u4e86\u89e3\u63d0\u53d6\u8def\u7531\u53c2\u6570\u7684\u8fc7\u7a0b\u548c\u6a21\u578b\u7ed1\u5b9a\u7684\u6982\u5ff5\u3002<\/p>\n<p>By now, you should be familiar with how ASP.NET Core handles a request by executing an endpoint handler. You\u2019ve also already seen several handlers, similar to<\/p>\n<p>\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u60a8\u5e94\u8be5\u719f\u6089 ASP.NET Core \u5982\u4f55\u901a\u8fc7\u6267\u884c\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u6765\u5904\u7406\u8bf7\u6c42\u3002\u60a8\u4e5f\u5df2\u7ecf\u770b\u5230\u4e86\u51e0\u4e2a\u5904\u7406\u7a0b\u5e8f\uff0c\u7c7b\u4f3c\u4e8e<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapPost(&quot;\/square\/{num}&quot;, (int num) =&gt; num * num);\n<\/pre>\n<p>Endpoint handlers are normal C# methods, so the ASP.NET Core framework needs to be able to call them in the usual way. When handlers accept parameters as part of their method signature, such as num in the preceding example, the framework needs a way to generate those objects. Where do they come from, exactly, and how are they created?<\/p>\n<p>\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u662f\u666e\u901a\u7684 C# \u65b9\u6cd5\uff0c\u56e0\u6b64 ASP.NET Core \u6846\u67b6\u9700\u8981\u80fd\u591f\u4ee5\u901a\u5e38\u7684\u65b9\u5f0f\u8c03\u7528\u5b83\u4eec\u9053\u8def\u3002\u5f53\u5904\u7406\u7a0b\u5e8f\u63a5\u53d7\u53c2\u6570\u4f5c\u4e3a\u5176\u65b9\u6cd5\u7b7e\u540d\u7684\u4e00\u90e8\u5206\u65f6\uff08\u4f8b\u5982\u524d\u9762\u793a\u4f8b\u4e2d\u7684 num\uff09\uff0c\u6846\u67b6\u9700\u8981\u4e00\u79cd\u65b9\u6cd5\u6765\u751f\u6210\u8fd9\u4e9b\u5bf9\u8c61\u3002\u5b83\u4eec\u7a76\u7adf\u6765\u81ea\u54ea\u91cc\uff0c\u53c8\u662f\u5982\u4f55\u4ea7\u751f\u7684\uff1f<\/p>\n<p>I\u2019ve already hinted that in most cases, these values come from the request itself. But the HTTP request that the server receives is a series of strings. How does ASP.NET Core turn that into a .NET object? This is where model binding comes in.<\/p>\n<p>\u6211\u5df2\u7ecf\u6697\u793a\u8fc7\uff0c\u5728\u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u8fd9\u4e9b\u503c\u6765\u81ea\u8bf7\u6c42\u672c\u8eab\u3002\u4f46\u662f\u670d\u52a1\u5668\u6536\u5230\u7684 HTTP \u8bf7\u6c42\u662f\u4e00\u7cfb\u5217\u5b57\u7b26\u4e32\u3002ASP.NET Core \u5982\u4f55\u5c06\u5176\u8f6c\u6362\u4e3a .NET \u5bf9\u8c61\uff1f\u8fd9\u5c31\u662f\u6a21\u578b\u7ed1\u5b9a\u7684\u7528\u6b66\u4e4b\u5730\u3002<\/p>\n<p><strong>Definition<\/strong> Model binding extracts values from a request and uses them to create .NET objects. These objects are passed as method parameters to the endpoint handler being executed.<br \/>\n<strong>DEFINITION<\/strong> \u6a21\u578b\u7ed1\u5b9a\u4ece\u8bf7\u6c42\u4e2d\u63d0\u53d6\u503c\uff0c\u5e76\u4f7f\u7528\u5b83\u4eec\u521b\u5efa .NET \u5bf9\u8c61\u3002\u8fd9\u4e9b\u5bf9\u8c61\u4f5c\u4e3a\u65b9\u6cd5\u53c2\u6570\u4f20\u9012\u7ed9\u6b63\u5728\u6267\u884c\u7684\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u3002<\/p>\n<p>The model binder is responsible for looking through the request that comes in and finding values to use. Then it creates objects of the appropriate type and assigns these values to your model in a process called binding.<\/p>\n<p>\u6a21\u578b\u7ed1\u5b9a\u8d1f\u8d23\u67e5\u770b\u4f20\u5165\u7684\u8bf7\u6c42\u5e76\u67e5\u627e\u8981\u4f7f\u7528\u7684\u503c\u3002\u7136\u540e\uff0c\u5b83\u4f1a\u521b\u5efa\u9002\u5f53\u7c7b\u578b\u7684\u5bf9\u8c61\uff0c\u5e76\u5728\u79f0\u4e3a binding \u7684\u8fc7\u7a0b\u4e2d\u5c06\u8fd9\u4e9b\u503c\u5206\u914d\u7ed9\u6a21\u578b\u3002<\/p>\n<p><strong>Note<\/strong> Model binding in minimal APIs (and in Razor Pages and Model-View-Controller [MVC]) is a one-way population of objects from the request, not the two-way data binding that desktop or mobile development sometimes uses.<br \/>\n<strong>\u6ce8\u610f<\/strong> \u6700\u5c0f API\uff08\u4ee5\u53ca Razor Pages \u548c Model-View-Controller [MVC]\uff09\u4e2d\u7684\u6a21\u578b\u7ed1\u5b9a\u662f\u6765\u81ea\u8bf7\u6c42\u7684\u5bf9\u8c61\u7684\u5355\u5411\u586b\u5145\uff0c\u800c\u4e0d\u662f\u684c\u9762\u6216\u79fb\u52a8\u5f00\u53d1\u6709\u65f6\u4f7f\u7528\u7684\u53cc\u5411\u6570\u636e\u7ed1\u5b9a\u3002<\/p>\n<p>ASP.NET Core automatically creates the arguments that are passed to your handler by using the request\u2019s properties, such as the request URL, any headers sent in the HTTP request, any data explicitly POSTed in the request body, and so on.<\/p>\n<p>ASP.NET Core \u4f7f\u7528\u8bf7\u6c42\u7684\u5c5e\u6027\uff08\u4f8b\u5982\u8bf7\u6c42 URL\u3001HTTP \u8bf7\u6c42\u4e2d\u53d1\u9001\u7684\u4efb\u4f55\u6807\u5934\u3001\u8bf7\u6c42\u6b63\u6587\u4e2d\u663e\u5f0f POST\u7f16\u8f91\u7684\u4efb\u4f55\u6570\u636e\u7b49\uff09\u81ea\u52a8\u521b\u5efa\u4f20\u9012\u7ed9\u5904\u7406\u7a0b\u5e8f\u7684\u53c2\u6570\u3002<\/p>\n<p>Model binding happens before the filter pipeline and your endpoint handler execute, in the EndpointMiddleware, as shown in figure 7.1. The RoutingMiddleware is responsible for matching an incoming request to an endpoint and for extracting the route parameter values, but all the values at that point are strings. It\u2019s only in the EndpointMiddleware that the string values are converted to the real argument types (such as int) needed to execute the endpoint handler.<\/p>\n<p>\u6a21\u578b\u7ed1\u5b9a\u53d1\u751f\u5728\u8fc7\u6ee4\u5668\u7ba1\u9053\u548c\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u6267\u884c\u4e4b\u524d\uff0c\u5728 EndpointMiddleware \u4e2d\uff0c\u5982\u56fe 7.1 \u6240\u793a\u3002RoutingMiddleware \u8d1f\u8d23\u5c06\u4f20\u5165\u8bf7\u6c42\u4e0e\u7ec8\u7aef\u8282\u70b9\u5339\u914d\u5e76\u63d0\u53d6\u8def\u7531\u53c2\u6570\u503c\uff0c\u4f46\u8be5\u70b9\u7684\u6240\u6709\u503c\u90fd\u662f\u5b57\u7b26\u4e32s\u3002\u53ea\u6709\u5728 EndpointMiddleware \u4e2d\uff0c\u5b57\u7b26\u4e32\u503c\u624d\u4f1a\u88ab\u8f6c\u6362\u4e3a\u6267\u884c\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u6240\u9700\u7684\u5b9e\u9645\u53c2\u6570\u7c7b\u578b\uff08\u6bd4\u5982 int\uff09\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0701.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 7.1 The RoutingMiddleware matches the incoming request to an endpoint and extracts the route parameters as strings. When the EndpointMiddleware executes the endpoint, the minimal API infrastructure uses model binding to create the arguments required to execute the endpoint handler, converting the string route values to real argument types such as int.<br \/>\n\u56fe 7.1 RoutingMiddleware \u5c06\u4f20\u5165\u7684\u8bf7\u6c42\u5339\u914d\u5230\u7ec8\u7aef\u8282\u70b9\uff0c\u5e76\u5c06\u8def\u7531\u53c2\u6570\u63d0\u53d6\u4e3a\u5b57\u7b26\u4e32s\u3002\u5f53 EndpointMiddleware \u6267\u884c\u7aef\u70b9\u65f6\uff0c\u6700\u5c0f\u7684 API \u57fa\u7840\u8bbe\u65bd\u4f7f\u7528\u6a21\u578b\u7ed1\u5b9a\u6765\u521b\u5efa\u6267\u884c\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u6240\u9700\u7684\u53c2\u6570\uff0c\u5c06\u5b57\u7b26\u4e32\u8def\u7531\u503c\u8f6c\u6362\u4e3a\u5b9e\u9645\u53c2\u6570\u7c7b\u578b\uff0c\u4f8b\u5982 int\u3002<\/p>\n<p>For every parameter in your minimal API endpoint handler, ASP.NET core must decide how to create the corresponding arguments. Minimal APIs can use six different binding sources to create the handler arguments:<\/p>\n<p>\u5bf9\u4e8e\u6700\u5c0f API \u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u7684\u6bcf\u4e2a\u53c2\u6570\uff0cASP.NET \u6838\u5fc3\u5fc5\u987b\u51b3\u5b9a\u5982\u4f55\u521b\u5efa\u76f8\u5e94\u7684\u53c2\u6570\u3002Minimal API \u53ef\u4ee5\u4f7f\u7528\u516d\u4e2a\u4e0d\u540c\u7684\u7ed1\u5b9a\u6e90\u6765\u521b\u5efa handler \u53c2\u6570\uff1a<\/p>\n<ul>\n<li>\n<p>Route values\u2014These values are obtained from URL segments or through default values after matching a route, as you saw in chapter 5.<br \/>\n\u8def\u7531\u503c \u2014 \u8fd9\u4e9b\u503c\u662f\u4ece URL \u5206\u6bb5\u6216\u901a\u8fc7\u5339\u914d\u8def\u7531\u540e\u7684\u9ed8\u8ba4\u503c\u83b7\u53d6\u7684\uff0c\u5982\u7b2c 5 \u7ae0\u6240\u793a\u3002<\/p>\n<\/li>\n<li>\n<p>Query string values\u2014These values are passed at the end of the URL, not used during routing.<br \/>\n\u67e5\u8be2\u5b57\u7b26\u4e32\u503c \u2013 \u8fd9\u4e9b\u503c\u5728 URL \u672b\u5c3e\u4f20\u9012\uff0c\u5728\u8def\u7531\u671f\u95f4\u4e0d\u4f7f\u7528\u3002<\/p>\n<\/li>\n<li>\n<p>Header values\u2014Header values are provided in the HTTP request.<br \/>\n\u6807\u5934\u503c \u2014 HTTP \u8bf7\u6c42\u4e2d\u63d0\u4f9b\u6807\u5934\u503c\u3002<\/p>\n<\/li>\n<li>\n<p>Body JSON\u2014A single parameter may be bound to the JSON body of a request.<br \/>\n\u6b63\u6587 JSON \u2014 \u5355\u4e2a\u53c2\u6570\u53ef\u4ee5\u7ed1\u5b9a\u5230\u8bf7\u6c42\u7684 JSON \u6b63\u6587\u3002<\/p>\n<\/li>\n<li>\n<p>Dependency injected services\u2014Services available through dependency injection can be used as endpoint handler arguments. We look at dependency injection in chapters 8 and 9.<br \/>\n\u4f9d\u8d56\u9879\u6ce8\u5165\u670d\u52a1 - \u901a\u8fc7\u4f9d\u8d56\u9879\u6ce8\u5165\u63d0\u4f9b\u7684\u670d\u52a1\u53ef\u7528\u4f5c\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u3002\u6211\u4eec\u5c06\u5728\u7b2c 8 \u7ae0\u548c\u7b2c 9 \u7ae0\u4e2d\u4ecb\u7ecd\u4f9d\u8d56\u6ce8\u5165\u3002<\/p>\n<\/li>\n<li>\n<p>Custom binding\u2014ASP.NET Core exposes methods for you to customize how a type is bound by providing access to the HttpRequest object.<br \/>\n\u81ea\u5b9a\u4e49\u7ed1\u5b9a \u2014 ASP.NET Core \u516c\u5f00\u4e86\u4e00\u4e9b\u65b9\u6cd5\uff0c\u4f9b\u60a8\u901a\u8fc7\u63d0\u4f9b\u5bf9 HttpRequest \u5bf9\u8c61\u7684\u8bbf\u95ee\u6765\u81ea\u5b9a\u4e49\u7c7b\u578b\u7684\u7ed1\u5b9a\u65b9\u5f0f\u3002<\/p>\n<\/li>\n<\/ul>\n<p><strong>Warning<\/strong> Unlike MVC controllers and Razor Pages, minimal APIs do not automatically bind to the body of requests sent as forms, using the application\/x-www-form-urlencoded mime type. Minimal APIs will bind only to a JSON request body. If you need to work with form data in a minimal API endpoint, you can access it on HttpRequest.Form, but you won\u2019t benefit from automatic binding.<br \/>\n<strong>\u8b66\u544a<\/strong> \u4e0e MVC \u63a7\u5236\u5668\u548c Razor Pages \u4e0d\u540c\uff0c\u6700\u5c0f\u7684 API \u4e0d\u4f1a\u4f7f\u7528 application\/ x-www-form- urlencoded MIME \u7c7b\u578b\u81ea\u52a8\u7ed1\u5b9a\u5230\u4f5c\u4e3a\u8868\u5355\u53d1\u9001\u7684\u8bf7\u6c42\u6b63\u6587\u3002\u6700\u5c0f API \u5c06\u4ec5\u7ed1\u5b9a\u5230 JSON \u8bf7\u6c42\u6b63\u6587\u3002\u5982\u679c\u60a8\u9700\u8981\u5728\u6700\u5c0f\u7684 API \u7aef\u70b9\u4e2d\u5904\u7406\u8868\u5355\u6570\u636e\uff0c\u60a8\u53ef\u4ee5\u5728HttpRequest.Form \u7684 URL \u8bf7\u6c42\uff0c\u4f46\u4f60\u4e0d\u4f1a\u4ece\u81ea\u52a8\u7ed1\u5b9a\u4e2d\u53d7\u76ca\u3002<\/p>\n<p>We\u2019ll look at the exact algorithm ASP.NET Core uses to choose which binding source to use in section 7.8, but we\u2019ll start by looking at how ASP.NET Core binds simple types such as int and double.<\/p>\n<p>\u6211\u4eec\u5c06\u4e86\u89e3 ASP.NET Core \u7528\u4e8e\u9009\u62e9\u5728 7.8 \u8282\u4e2d\u4f7f\u7528\u54ea\u4e2a\u7ed1\u5b9a\u6e90\u7684\u786e\u5207\u7b97\u6cd5\uff0c\u4f46\u9996\u5148\u6211\u4eec\u5c06\u4e86\u89e3 ASP.NET Core \u5982\u4f55\u7ed1\u5b9a\u7b80\u5355\u7c7b\u578b\uff0c\u4f8b\u5982 int \u548c double\u3002<\/p>\n<h2>7.2 Binding simple types to a request<\/h2>\n<h2>7.2 \u5c06\u7b80\u5355\u7c7b\u578b\u7ed1\u5b9a\u5230\u8bf7\u6c42<\/h2>\n<p>When you\u2019re building minimal API handlers, you\u2019ll often want to extract a simple value from the request. If you\u2019re loading a list of products in a category, for example, you\u2019ll likely need the category\u2019s ID, and in the calculator example at the start of section 7.1, you\u2019ll need the number to square.<\/p>\n<p>\u5728\u6784\u5efa\u6700\u5c0f API \u5904\u7406\u7a0b\u5e8f\u65f6\uff0c\u60a8\u901a\u5e38\u9700\u8981\u4ece\u8bf7\u6c42\u4e2d\u63d0\u53d6\u4e00\u4e2a\u7b80\u5355\u7684\u503c\u3002\u4f8b\u5982\uff0c\u5982\u679c\u8981\u52a0\u8f7d\u67d0\u4e2a\u7c7b\u522b\u4e2d\u7684\u4ea7\u54c1\u5217\u8868\uff0c\u5219\u53ef\u80fd\u9700\u8981\u7c7b\u522b\u7684 ID\uff0c\u5728\u7b2c 7.1 \u8282\u5f00\u5934\u7684\u8ba1\u7b97\u5668\u793a\u4f8b\u4e2d\uff0c\u9700\u8981\u5c06\u6570\u5b57\u5e73\u65b9\u3002<\/p>\n<p>When you create an endpoint handler that contains simple types such as int, string, and double, ASP.NET Core automatically tries to bind the value to a route parameter, or a query string value:<\/p>\n<p>\u5f53\u60a8\u521b\u5efa\u5305\u542b\u7b80\u5355\u7c7b\u578b\uff08\u5982 int\u3001string \u548c double\uff09\u7684\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u65f6\uff0cASP.NET Core \u4f1a\u81ea\u52a8\u5c1d\u8bd5\u5c06\u503c\u7ed1\u5b9a\u5230\u8def\u7531\u53c2\u6570\u6216\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\uff1a<\/p>\n<ul>\n<li>\n<p>If the name of the handler parameter matches the name of a route parameter in the route template, ASP.NET Core binds to the associated route value.<br \/>\n\u5982\u679c handler \u53c2\u6570\u7684\u540d\u79f0\u4e0e\u8def\u7531\u6a21\u677f\u4e2d\u7684\u8def\u7531\u53c2\u6570\u540d\u79f0\u5339\u914d\uff0c\u5219 ASP.NET Core \u5c06\u7ed1\u5b9a\u5230\u5173\u8054\u7684\u8def\u7531\u503c\u3002<\/p>\n<\/li>\n<li>\n<p>If the name of the handler parameter doesn\u2019t match any parameters in the route template, ASP.NET Core tries to bind to a query string value.<br \/>\n\u5982\u679c handler \u53c2\u6570\u7684\u540d\u79f0\u4e0e\u8def\u7531\u6a21\u677f\u4e2d\u7684\u4efb\u4f55\u53c2\u6570\u90fd\u4e0d\u5339\u914d\uff0c\u5219 ASP.NET Core \u4f1a\u5c1d\u8bd5\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\u3002<\/p>\n<\/li>\n<\/ul>\n<p>If you make a request to \/products\/123, for example, this will match the following endpoint:<\/p>\n<p>\u4f8b\u5982\uff0c\u5982\u679c\u60a8\u5411 \/products\/123 \u53d1\u51fa\u8bf7\u6c42\uff0c\u8fd9\u5c06\u5339\u914d\u4ee5\u4e0b\u7aef\u70b9\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapGet(&quot;\/products\/{id}&quot;, (int id) =&gt; $&quot;Received {id}&quot;);\n<\/pre>\n<p>ASP.NET Core binds the id handler argument to the {id} route parameter, so the handler function is called with id=123. Conversely, if you make a request to \/products?id=456, this will match the following endpoint instead:<\/p>\n<p>ASP.NET Core \u5c06 id \u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u7ed1\u5b9a\u5230 {id} \u8def\u7531\u53c2\u6570\uff0c\u56e0\u6b64\u4f7f\u7528 id=123 \u8c03\u7528\u5904\u7406\u7a0b\u5e8f\u51fd\u6570\u3002\u76f8\u53cd\uff0c\u5982\u679c\u60a8\u5411 \/products\uff1fid=456 \u53d1\u51fa\u8bf7\u6c42\uff0c\u5219\u8fd9\u5c06\u5339\u914d\u4ee5\u4e0b\u7aef\u70b9\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapGet(&quot;\/products&quot;, (int id) =&gt; $&quot;Received {id}&quot;);\n<\/pre>\n<p>In this case, there\u2019s no id parameter in the route template, so ASP.NET Core binds to the query string instead, and the handler function is called with id=456.<\/p>\n<p>\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u8def\u7531\u6a21\u677f\u4e2d\u6ca1\u6709 id \u53c2\u6570\uff0c\u56e0\u6b64 ASP.NET Core \u4f1a\u6539\u4e3a\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\uff0c\u5e76\u4f7f\u7528 id=456 \u8c03\u7528\u5904\u7406\u7a0b\u5e8f\u51fd\u6570\u3002<\/p>\n<p>In addition to this \u201cautomatic\u201d inference, you can force ASP.NET Core to bind from a specific source by adding attributes to the parameters. [FromRoute] explicitly binds to route parameters, [FromQuery] to the query string, and [FromHeader] to header values, as shown in figure 7.2.<\/p>\n<p>\u9664\u4e86\u8fd9\u79cd \u201c\u81ea\u52a8\u201d \u63a8\u7406\u4e4b\u5916\uff0c\u60a8\u8fd8\u53ef\u4ee5\u901a\u8fc7\u5411\u53c2\u6570\u6dfb\u52a0\u5c5e\u6027\u6765\u5f3a\u5236 ASP.NET Core \u4ece\u7279\u5b9a\u6e90\u8fdb\u884c\u7ed1\u5b9a\u3002[FromRoute] \u663e\u5f0f\u7ed1\u5b9a\u5230\u8def\u7531\u53c2\u6570\uff0c[FromQuery] \u663e\u5f0f\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\uff0c[FromHeader] \u663e\u5f0f\u7ed1\u5b9a\u5230\u6807\u5934\u503c\uff0c\u5982\u56fe 7.2 \u6240\u793a\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0702.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 7.2 Model binding an HTTP get request to an endpoint. The [FromRoute], [FromQuery], and [FromHeader] attributes force the endpoint parameters to bind to specific parts of the request. Only the [FromHeader] attribute is required in this case; the route parameter and query string would be inferred automatically.<br \/>\n\u56fe 7.2 \u5c06 HTTP get \u8bf7\u6c42\u7ed1\u5b9a\u5230\u7ec8\u7aef\u8282\u70b9\u7684\u6a21\u578b\u3002[FromRoute]\u3001[FromQuery] \u548c [FromHeader] \u5c5e\u6027\u5f3a\u5236\u7ec8\u7ed3\u70b9\u53c2\u6570\u7ed1\u5b9a\u5230\u8bf7\u6c42\u7684\u7279\u5b9a\u90e8\u5206\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u53ea\u9700\u8981 [FromHeader] \u5c5e\u6027;Route \u53c2\u6570\u548c Query String \u5c06\u81ea\u52a8\u63a8\u65ad\u3002<\/p>\n<p>The [From<em>] attributes override ASP.NET Core\u2019s default logic and forces the parameters to load from a specific binding source. Listing 7.1 demonstrates three possible [From<\/em>] attributes:<\/p>\n<p>[From<em>] \u5c5e\u6027\u4f1a\u8986\u76d6 ASP.NET Core \u7684\u9ed8\u8ba4\u903b\u8f91\uff0c\u5e76\u5f3a\u5236\u53c2\u6570\u4ece\u7279\u5b9a\u7684binding \u6e90\u3002\u6e05\u5355 7.1 \u6f14\u793a\u4e86\u4e09\u79cd\u53ef\u80fd[From<\/em>]\u5c5e\u6027\uff1a<\/p>\n<ul>\n<li>\n<p>[FromQuery]\u2014As you\u2019ve already seen, this attribute forces a parameter to bind to the query string.<br \/>\n[\u53d1\u4ef6\u4eba\u67e5\u8be2]- \u5982\u60a8\u6240\u89c1\uff0c\u6b64\u5c5e\u6027\u5f3a\u5236\u53c2\u6570\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\u3002<\/p>\n<\/li>\n<li>\n<p>[FromRoute]\u2014This attribute forces the parameter to bind a route parameter value. Note that if a parameter of the required name doesn\u2019t exist in the route template, you\u2019ll get an exception at runtime.<br \/>\n[\u4ece\u8def\u7ebf]- \u6b64\u5c5e\u6027\u5f3a\u5236\u53c2\u6570\u7ed1\u5b9a\u8def\u5f84\u53c2\u6570\u503c\u3002\u8bf7\u6ce8\u610f\uff0c\u5982\u679c\u8def\u7531\u6a21\u677f\u4e2d\u4e0d\u5b58\u5728\u6240\u9700\u540d\u79f0\u7684\u53c2\u6570\uff0c\u60a8\u5c06\u5728\u8fd0\u884c\u65f6\u6536\u5230\u5f02\u5e38\u3002<\/p>\n<\/li>\n<li>\n<p>[FromHeader]\u2014This attribute binds a parameter to a header value in the request.<br \/>\n[\u53d1\u4ef6\u4eba\u6807\u5934]\u2014 \u6b64\u5c5e\u6027\u5c06\u53c2\u6570\u7ed1\u5b9a\u5230\u8bf7\u6c42\u4e2d\u7684\u6807\u5934\u503c\u3002<\/p>\n<\/li>\n<\/ul>\n<p>Listing 7.1 Binding simple values using [From] attributes<br \/>\n\u5217\u8868 7.1 \u4f7f\u7528 [From] \u7ed1\u5b9a\u7b80\u5355\u503c\u5c5e\u6027<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing Microsoft.AspNetCore.Mvc;                                       \/\/ \u2776\n\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\napp.MapGet(&quot;\/products\/{id}\/paged&quot;,\n    (&#x5B;FromRoute] int id,                                              \/\/ \u2777\n    &#x5B;FromQuery] int page,                                             \/\/ \u2778\n    &#x5B;FromHeader(Name = &quot;PageSize&quot;)] int pageSize)                     \/\/ \u2779\n    =&gt; $&quot;Received id {id}, page {page}, pageSize {pageSize}&quot;);\napp.Run();\n<\/pre>\n<p>\u2776 All the [From<em>] attributes are in this namespace.<br \/>\n\u6240\u6709 [From<\/em>] \u5c5e\u6027\u90fd\u4f4d\u4e8e\u6b64\u547d\u540d\u7a7a\u95f4\u4e2d\u3002<\/p>\n<p>\u2777 [FromRoute] forces the argument to bind to the route value.<br \/>\n[FromRoute] \u5f3a\u5236\u53c2\u6570\u7ed1\u5b9a\u5230\u8def\u7531\u503c\u3002<\/p>\n<p>\u2778 [FromQuery] forces the argument to bind to the query string.<br \/>\n[FromQuery] \u5f3a\u5236\u53c2\u6570\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\u3002<\/p>\n<p>\u2779 [FromHeader] binds the argument to the specified header.<br \/>\n[FromHeader] \u5c06\u53c2\u6570\u7ed1\u5b9a\u5230\u6307\u5b9a\u7684\u6807\u5934\u3002<\/p>\n<p>Later, you\u2019ll see other attributes, such as [FromBody] and [FromServices], but the preceding three attributes are the only [From*] attributes that operate on simple types such as int and double. prefer to avoid using [FromQuery] and [FromRoute] wherever possible and rely on the default binding conventions instead, as I find that they clutter the method signatures, and it\u2019s generally obvious whether a simple type is going to bind to the query string or a route value.<\/p>\n<p>\u7a0d\u540e\uff0c\u60a8\u5c06\u770b\u5230\u5176\u4ed6\u5c5e\u6027\uff0c\u4f8b\u5982 [FromBody] \u548c [FromServices]\uff0c\u4f46\u524d\u9762\u7684\u4e09\u4e2a\u5c5e\u6027\u662f\u552f\u4e00\u5bf9\u7b80\u5355\u7c7b\u578b\uff08\u5982 int \u548c double\uff09\u8fdb\u884c\u4f5c\u7684 [From*] \u5c5e\u6027\u3002\u6211\u503e\u5411\u4e8e\u5c3d\u53ef\u80fd\u907f\u514d\u4f7f\u7528 [FromQuery] \u548c [FromRoute]\uff0c\u800c\u662f\u4f9d\u8d56\u9ed8\u8ba4\u7684\u7ed1\u5b9a\u7ea6\u5b9a\uff0c\u56e0\u4e3a\u6211\u53d1\u73b0\u5b83\u4eec\u4f1a\u4f7f\u65b9\u6cd5\u7b7e\u540d\u53d8\u5f97\u6df7\u4e71\uff0c\u5e76\u4e14\u901a\u5e38\u5f88\u660e\u663e\u5730\u5c06\u7b80\u5355\u7c7b\u578b\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\u8fd8\u662f\u8def\u7531\u503c\u3002<\/p>\n<p><strong>Tip<\/strong> ASP.NET Core binds to route parameters and query string values based on convention, but the only way to bind to a header value is with the [FromHeader] attribute.<br \/>\n<strong>\u63d0\u793a<\/strong> ASP.NET Core \u6839\u636e\u7ea6\u5b9a\u7ed1\u5b9a\u5230\u8def\u7531\u53c2\u6570\u548c\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\uff0c\u4f46\u7ed1\u5b9a\u5230\u6807\u5934\u503c\u7684\u552f\u4e00\u65b9\u6cd5\u662f\u4f7f\u7528 [FromHeader] \u5c5e\u6027\u3002<\/p>\n<p>You may be wondering what would happen if you try to bind a type to an incompatible value. What if you try to bind an int to the string value &quot;two&quot;, for example? In that case ASP.NET Core throws a BadHttpRequestException and returns a 400 Bad Request response.<br \/>\n\u60a8\u53ef\u80fd\u60f3\u77e5\u9053\uff0c\u5982\u679c\u5c1d\u8bd5\u5c06\u7c7b\u578b\u7ed1\u5b9a\u5230\u4e0d\u517c\u5bb9\u7684\u503c\uff0c\u4f1a\u53d1\u751f\u4ec0\u4e48\u60c5\u51b5\u3002\u4f8b\u5982\uff0c\u5982\u679c\u60a8\u5c1d\u8bd5\u5c06 int \u7ed1\u5b9a\u5230\u5b57\u7b26\u4e32\u503c \u201ctwo\u201d \u600e\u4e48\u529e\uff1f\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b ASP.NET Core \u4f1a\u5f15\u53d1 BadHttpRequestException \u5e76\u8fd4\u56de 400 Bad Request \u54cd\u5e94\u3002<\/p>\n<p><strong>Note<\/strong> When the minimal API infrastructure fails to bind a handler parameter due to an incompatible format, it throws a BadHttpRequestException and returns a 400 Bad Request response.<br \/>\n<strong>\u6ce8\u610f<\/strong> \u5f53\u6700\u5c0f API \u57fa\u7840\u8bbe\u65bd\u7531\u4e8e\u683c\u5f0f\u4e0d\u517c\u5bb9\u800c\u65e0\u6cd5\u7ed1\u5b9a\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u65f6\uff0c\u5b83\u4f1a\u5f15\u53d1 BadHttpRequestException \u5e76\u8fd4\u56de 400 Bad Request \u54cd\u5e94\u3002<\/p>\n<p>I\u2019ve mentioned several times in this section that you can bind route values, query string values, and headers to simple types, but what is a simple type? A simple type is defined as any type that contains either of the following TryParse methods, where T is the implementing type:<\/p>\n<p>\u6211\u5728\u672c\u8282\u4e2d\u591a\u6b21\u63d0\u5230\uff0c\u60a8\u53ef\u4ee5\u5c06\u8def\u7531\u503c\u3001\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\u548c\u6807\u5934\u7ed1\u5b9a\u5230\u7b80\u5355\u7c7b\u578b\uff0c\u4f46\u4ec0\u4e48\u662f \u7b80\u5355\u7c7b\u578b\uff1f\u7b80\u5355\u7c7b\u578b\u5b9a\u4e49\u4e3a\u5305\u542b\u4ee5\u4e0b\u4efb\u4e00 TryParse \u65b9\u6cd5\u7684\u4efb\u610f\u7c7b\u578b\uff0c\u5176\u4e2d T \u662f\u5b9e\u73b0\u7c7b\u578b\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic static bool TryParse(string value, out T result);\npublic static bool TryParse(\n    string value, IFormatProvider provider, out T result);\n<\/pre>\n<p>Types such as int and bool contain one (or both) these methods. But it\u2019s also worth noting that you can create your own types that implement one of these methods, and they\u2019ll be treated as simple types, capable of binding from route values, query string values, and headers.<\/p>\n<p>int \u548c bool \u7b49\u7c7b\u578b\u5305\u542b\u4e00\u4e2a\uff08\u6216\u4e24\u4e2a\uff09\u8fd9\u4e9b\u65b9\u6cd5\u3002\u4f46\u8fd8\u503c\u5f97\u6ce8\u610f\u7684\u662f\uff0c\u60a8\u53ef\u4ee5\u521b\u5efa\u81ea\u5df1\u7684\u7c7b\u578b\u6765\u5b9e\u73b0\u8fd9\u4e9b\u65b9\u6cd5\u4e4b\u4e00\uff0c\u5b83\u4eec\u5c06\u88ab\u89c6\u4e3a\u7b80\u5355\u7c7b\u578b\uff0c\u80fd\u591f\u4ece\u8def\u7531\u503c\u3001\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\u548c\u6807\u5934\u8fdb\u884c\u7ed1\u5b9a\u3002<\/p>\n<p>Figure 7.3 shows an example of implementing a simple strongly-typed ID[1] that\u2019s treated as a simple type thanks to the TryParse method it exposes. When you send a request to \/product\/p123, ASP.NET Core sees that the ProductId type used in the endpoint handler contains a TryParse method and that the name of the id parameter has a matching route parameter name. It creates the id argument by calling ProductId.TryParse() and passes in the route value, p123.<\/p>\n<p>\u56fe 7.3 \u663e\u793a\u4e86\u5b9e\u73b0\u7b80\u5355\u7684\u5f3a\u7c7b\u578b ID1 \u7684\u793a\u4f8b\uff0c\u7531\u4e8e\u5b83\u516c\u5f00\u4e86 TryParse \u65b9\u6cd5\uff0c\u8be5 ID 1 \u88ab\u89c6\u4e3a\u7b80\u5355\u7c7b\u578b\u3002\u5f53\u60a8\u5411 \/product\/p123 \u53d1\u9001\u8bf7\u6c42\u65f6\uff0cASP.NET Core \u4f1a\u53d1\u73b0\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u4f7f\u7528\u7684 ProductId \u7c7b\u578b\u5305\u542b TryParse \u65b9\u6cd5\uff0c\u5e76\u4e14 id \u53c2\u6570\u7684\u540d\u79f0\u5177\u6709\u5339\u914d\u7684\u8def\u7531\u53c2\u6570\u540d\u79f0\u3002\u5b83\u901a\u8fc7\u8c03\u7528 ProductId.TryParse\uff08\uff09 \u521b\u5efa id \u53c2\u6570\uff0c\u5e76\u4f20\u5165\u8def\u7531\u503c p123\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0703.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 7.3 The routing middleware matches the incoming URL to the endpoint. The endpoint middleware attempts to bind the route parameter id to the endpoint parameter. The endpoint parameter type ProductId implements TryParse. If parsing is successful, the parsed parameter is used to call the endpoint handler. If parsing fails, the endpoint middleware returns a 400 Bad Request response.<br \/>\n\u56fe 7.3 \u8def\u7531\u4e2d\u95f4\u4ef6\u5c06\u4f20\u5165\u7684 URL \u4e0e\u7aef\u70b9\u5339\u914d\u3002\u7ec8\u7aef\u8282\u70b9\u4e2d\u95f4\u4ef6\u5c1d\u8bd5\u5c06\u8def\u7531\u53c2\u6570 ID \u7ed1\u5b9a\u5230 endpoint \u53c2\u6570\u3002\u7ec8\u7aef\u8282\u70b9\u53c2\u6570\u7c7b\u578b ProductId \u5b9e\u73b0 TryParse\u3002\u5982\u679c\u89e3\u6790\u6210\u529f\uff0c\u5219 parsed \u53c2\u6570\u7528\u4e8e\u8c03\u7528\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u3002\u5982\u679c\u89e3\u6790\u5931\u8d25\uff0c\u7ec8\u7aef\u8282\u70b9\u4e2d\u95f4\u4ef6\u5c06\u8fd4\u56de 400 Bad Request \u54cd\u5e94\u3002<\/p>\n<p>Listing 7.2 shows how you could implement the TryParse method for ProductId. This method creates a ProductId from strings that consist of an integer prefixed with 'p' (p123 or p456, for example). If the input string matches the required format, it creates a ProductId instance and returns true. If the format is invalid, it returns false, binding fails, and a 400 Bad Request is returned.<\/p>\n<p>\u6e05\u5355 7.2 \u5c55\u793a\u4e86\u5982\u4f55\u5b9e\u73b0 ProductId \u7684 TryParse \u65b9\u6cd5\u3002\u6b64\u65b9\u6cd5\u4ece\u5b57\u7b26\u4e32s \u521b\u5efa\u4e00\u4e2a ProductId\uff0c\u8be5\u5b57\u7b26\u4e32\u7531\u4e00\u4e2a\u524d\u7f00\u4e3a 'p' \u7684\u6574\u6570\u7ec4\u6210\uff08\u4f8b\u5982 p123 \u6216 p456\uff09\u3002\u5982\u679c\u8f93\u5165\u5b57\u7b26\u4e32\u4e0e\u6240\u9700\u7684\u683c\u5f0f\u5339\u914d\uff0c\u5b83\u5c06\u521b\u5efa\u4e00\u4e2a ProductId \u5b9e\u4f8b\u5e76\u8fd4\u56de true\u3002\u5982\u679c\u683c\u5f0f\u65e0\u6548\uff0c\u5219\u8fd4\u56de false\uff0c\u7ed1\u5b9a\u5931\u8d25\uff0c\u5e76\u8fd4\u56de 400 Bad Request\u3002<\/p>\n<p>Listing 7.2 Implementing TryParse in a custom type to allow parsing from route values<br \/>\n\u6e05\u5355 7.2 \u5728\u81ea\u5b9a\u4e49\u7c7b\u578b\u4e2d\u5b9e\u73b0 TryParse \u4ee5\u5141\u8bb8\u4ece\u8def\u7531\u503c\u89e3\u6790<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\napp.MapGet(&quot;\/product\/{id}&quot;, (ProductId id) =&gt; $&quot;Received {id}&quot;);             \/\/ \u2776\napp.Run();\n\nreadonly record struct ProductId(int Id)                                     \/\/ \u2777\n{\n    public static bool TryParse(string? s, out ProductId result)             \/\/ \u2778\n    {\n    if (s is not null                                                        \/\/ \u2779\n            &amp;&amp; s.StartsWith(&#039;p&#039;)                                             \/\/ \u2779\n            &amp;&amp; int.TryParse(                                                 \/\/ \u277a\n                s.AsSpan().Slice(1),                                         \/\/ \u277b\n                out int id))                                                 \/\/ \u277c\n        {\n        result = new ProductId(id);                                          \/\/ \u277d\n            return true;                                                     \/\/ \u277d\n        }\n    result = default;                                                        \/\/ \u277e\n        return false;                                                        \/\/ \u277e\n    }\n}\n<\/pre>\n<p>\u2776 ProductId automatically binds to route values as it implements TryParse.<br \/>\nProductId \u5728\u5b9e\u73b0 TryParse \u65f6\u4f1a\u81ea\u52a8\u7ed1\u5b9a\u5230\u8def\u7531\u503c\u3002<\/p>\n<p>\u2777 ProductId is a C# 10 record struct.<br \/>\nProductId \u662f C# 10 \u8bb0\u5f55\u7ed3\u6784\u3002<\/p>\n<p>\u2778 It implements TryParse, so it\u2019s treated as a simple type by minimal APIs.<br \/>\n\u5b83\u5b9e\u73b0\u4e86 TryParse\uff0c\u56e0\u6b64\u5b83\u88ab\u6700\u5c0f\u7684 API \u89c6\u4e3a\u7b80\u5355\u7c7b\u578b\u3002<\/p>\n<p>\u2779 Checks that the string is not null and that the first character in the string is \u2018p\u2019 .<br \/>\n\u68c0\u67e5\u5b57\u7b26\u4e32\u662f\u5426\u4e0d\u4e3a null\uff0c\u4ee5\u53ca\u5b57\u7b26\u4e32\u4e2d\u7684\u7b2c\u4e00\u4e2a\u5b57\u7b26\u662f\u5426\u4e3a 'p' \u3002<\/p>\n<p>\u277a and if it is, tries to parse the remaining characters as an integer<br \/>\n\u5982\u679c\u662f\uff0c\u5219\u5c1d\u8bd5\u5c06\u5269\u4f59\u5b57\u7b26\u89e3\u6790\u4e3a\u6574\u6570<\/p>\n<p>\u277b Efficiently skips the first character by treating the string as a ReadOnlySpan<br \/>\n\u901a\u8fc7\u5c06\u5b57\u7b26\u4e32\u89c6\u4e3a ReadOnlySpan \u6765\u6709\u6548\u5730\u8df3\u8fc7\u7b2c\u4e00\u4e2a\u5b57\u7b26<\/p>\n<p>\u277c If the string was parsed successfully, id contains the parsed value.<br \/>\n\u5982\u679c\u5b57\u7b26\u4e32\u89e3\u6790\u6210\u529f\uff0c\u5219 id \u5305\u542b\u89e3\u6790\u7684\u503c\u3002<\/p>\n<p>\u277d Everything parsed successfully, so creates a new ProductId and returns true<\/p>\n<p>\u277e Something went wrong, so returns false and assigns a default value to the<br \/>\n(unused) result<br \/>\n\u6240\u6709\u5185\u5bb9\u90fd\u89e3\u6790\u6210\u529f\uff0c\u56e0\u6b64\u521b\u5efa\u4e00\u4e2a\u65b0\u7684 ProductId \u5e76\u8fd4\u56de true<\/p>\n<blockquote>\n<p>Using modern C# and .NET features<br \/>\n\u4f7f\u7528\u73b0\u4ee3 C# \u548c .NET \u529f\u80fd<br \/>\nListing 7.2 included some C# and .NET features that you may not have seen before, depending on your background:<br \/>\n\u6e05\u5355 7.2 \u5305\u542b\u4e86\u4e00\u4e9b\u60a8\u4ee5\u524d\u53ef\u80fd\u6ca1\u6709\u89c1\u8fc7\u7684 C# \u548c .NET \u529f\u80fd\uff0c\u5177\u4f53\u53d6\u51b3\u4e8e\u60a8\u7684\u80cc\u666f\uff1a<br \/>\n\u00b7   Pattern matching for null values\u2014s is not null. Pattern matching features have been introduced gradually into C# since C# 7. The is not null pattern, introduced in C# 9, has some minor advantages over the common != null expression. You can read all about pattern matching at <a href=\"http:\/\/mng.bz\/gBxl\">http:\/\/mng.bz\/gBxl<\/a>.<br \/>\n\u7a7a\u503c\u7684\u6a21\u5f0f\u5339\u914d - s \u4e0d\u4e3a\u7a7a\u3002\u81ea C# 7 \u4ee5\u6765\uff0c\u6a21\u5f0f\u5339\u914d\u529f\u80fd\u5df2\u9010\u6e10\u5f15\u5165 C# \u4e2d\u3002C# 9 \u4e2d\u5f15\u5165\u7684 is not null \u6a21\u5f0f\u4e0e\u5e38\u89c1\u7684 \uff01= null \u8868\u8fbe\u5f0f\u76f8\u6bd4\uff0c\u5177\u6709\u4e00\u4e9b\u7ec6\u5fae\u7684\u4f18\u52bf\u3002\u60a8\u53ef\u4ee5\u5728 <a href=\"http:\/\/mng.bz\/gBxl\">http:\/\/mng.bz\/gBxl<\/a> \u9605\u8bfb\u6709\u5173\u6a21\u5f0f\u5339\u914d\u7684\u6240\u6709\u4fe1\u606f\u3002<br \/>\n\u00b7   Records and struct records\u2014readonly record struct. Records are syntactical sugar over normal class and struct declarations, which make declaring new types more succinct and provide convenience methods for working with immutable types. Record structs were introduced in C# 10. You can read more at <a href=\"http:\/\/mng.bz\/5wWz\">http:\/\/mng.bz\/5wWz<\/a>.<br \/>\n\u8bb0\u5f55\u548c\u7ed3\u6784\u8bb0\u5f55 - \u53ea\u8bfb\u8bb0\u5f55\u7ed3\u6784\u3002\u8bb0\u5f55\u662f\u666e\u901a\u7c7b\u548c\u7ed3\u6784\u58f0\u660e\u7684\u8bed\u6cd5\u7cd6\uff0c\u8fd9\u4f7f\u5f97\u58f0\u660e\u65b0\u7c7b\u578b\u66f4\u52a0\u7b80\u6d01\uff0c\u5e76\u4e3a\u4f7f\u7528\u4e0d\u53ef\u53d8\u7c7b\u578b\u63d0\u4f9b\u4e86\u4fbf\u6377\u7684\u65b9\u6cd5\u3002\u8bb0\u5f55\u7ed3\u6784\u662f\u5728 C# 10 \u4e2d\u5f15\u5165\u7684\u3002\u60a8\u53ef\u4ee5\u5728 <a href=\"http:\/\/mng.bz\/5wWz\">http:\/\/mng.bz\/5wWz<\/a> \u4e0a\u9605\u8bfb\u66f4\u591a\u5185\u5bb9\u3002<br \/>\n\u00b7   Span<T> for performance\u2014s.AsSpan(). Span<T> and ReadOnlySpan<T> were introduced in .NET Core 2.1 and are particularly useful for reducing allocations when working with string values. You can read more about them at <a href=\"http:\/\/mng.bz\/6DNy\">http:\/\/mng.bz\/6DNy<\/a>.<br \/>\nSpan<T> \u7528\u4e8e\u6027\u80fd - s.AsSpan\uff08\uff09\u3002 Span<T> \u548c ReadOnlySpan<T> \u662f\u5728 .NET Core 2.1 \u4e2d\u5f15\u5165\u7684\uff0c\u5bf9\u4e8e\u5728\u5904\u7406\u5b57\u7b26\u4e32\u503c\u65f6\u51cf\u5c11\u5206\u914d\u7279\u522b\u6709\u7528\u3002\u60a8\u53ef\u4ee5\u5728 <a href=\"http:\/\/mng.bz\/6DNy\">http:\/\/mng.bz\/6DNy<\/a> \u4e0a\u9605\u8bfb\u66f4\u591a\u5173\u4e8e\u5b83\u4eec\u7684\u4fe1\u606f\u3002<br \/>\n\u00b7   ValueTask<T>\u2014It\u2019s not shown in listing 7.2, but many of the APIs in ASP.NET Core use ValueTask instead of the more common Task for APIs that normally complete asynchronously but may complete asynchronously. You can read about why they were introduced and when to use them at <a href=\"http:\/\/mng.bz\/o1GM\">http:\/\/mng.bz\/o1GM<\/a>.<br \/>\nValueTask<T> \u2014 \u5b83\u672a\u663e\u793a\u5728\u6e05\u5355 7.2 \u4e2d\uff0c\u4f46 ASP.NET Core \u4e2d\u7684\u8bb8\u591a API \u90fd\u4f7f\u7528 ValueTask\uff0c\u800c\u4e0d\u662f\u66f4\u5e38\u89c1\u7684 Task\uff0c\u7528\u4e8e\u901a\u5e38\u5f02\u6b65\u5b8c\u6210\u4f46\u53ef\u80fd\u5f02\u6b65\u5b8c\u6210\u7684 API\u3002\u60a8\u53ef\u4ee5\u5728 <a href=\"http:\/\/mng.bz\/o1GM\">http:\/\/mng.bz\/o1GM<\/a> \u4e0a\u9605\u8bfb\u6709\u5173\u5f15\u5165\u5b83\u4eec\u7684\u539f\u56e0\u4ee5\u53ca\u4f55\u65f6\u4f7f\u7528\u5b83\u4eec\u7684\u4fe1\u606f\u3002<br \/>\nDon\u2019t worry if you\u2019re not familiar with these constructs. C# is a fast-moving language, so keeping up can be tricky, but there\u2019s generally no reason you need to use the new features. Nevertheless, it\u2019s useful to be able to recognize them sot hat you can read and understand code that uses them.<br \/>\n\u5982\u679c\u60a8\u4e0d\u719f\u6089\u8fd9\u4e9b\u7ed3\u6784\uff0c\u8bf7\u4e0d\u8981\u62c5\u5fc3\u3002C# \u662f\u4e00\u79cd\u5feb\u901f\u53d1\u5c55\u7684\u8bed\u8a00\uff0c\u56e0\u6b64\u8ddf\u4e0a\u6b65\u4f10\u53ef\u80fd\u5f88\u68d8\u624b\uff0c\u4f46\u901a\u5e38\u6ca1\u6709\u7406\u7531\u9700\u8981\u4f7f\u7528\u65b0\u529f\u80fd\u3002\u5c3d\u7ba1\u5982\u6b64\uff0c\u80fd\u591f\u8bc6\u522b\u5b83\u4eec\u8fd8\u662f\u5f88\u6709\u7528\u7684\uff0c\u8fd9\u6837\u60a8\u5c31\u53ef\u4ee5\u9605\u8bfb\u548c\u7406\u89e3\u4f7f\u7528\u5b83\u4eec\u7684\u4ee3\u7801\u3002<br \/>\nIf you\u2019re keen to embrace new features, you might consider implementing the IParsable interface when you implement TryParse. This interface uses the static abstract interfaces feature, which was introduced in C# 11, and requires implementing both a TryParse and Parse method. You can read more about the IParsable interface in the announcement post at <a href=\"http:\/\/mng.bz\/nW2K\">http:\/\/mng.bz\/nW2K<\/a>.<br \/>\n\u5982\u679c\u60a8\u70ed\u8877\u4e8e\u91c7\u7528\u65b0\u529f\u80fd\uff0c\u5219\u53ef\u4ee5\u8003\u8651\u5728\u5b9e\u73b0 TryParse \u65f6\u5b9e\u73b0 IParsable \u63a5\u53e3\u3002\u6b64\u63a5\u53e3\u4f7f\u7528 C# 11 \u4e2d\u5f15\u5165\u7684\u9759\u6001\u62bd\u8c61\u63a5\u53e3\u529f\u80fd\uff0c\u5e76\u4e14\u9700\u8981\u5b9e\u73b0 TryParse \u548c Parse \u65b9\u6cd5\u3002\u200c\u200c\u60a8\u53ef\u4ee5\u5728 <a href=\"http:\/\/mng.bz\/nW2K\">http:\/\/mng.bz\/nW2K<\/a> \u7684\u516c\u544a\u5e16\u5b50\u4e2d\u9605\u8bfb\u6709\u5173 IParsable \u63a5\u53e3\u7684\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<\/blockquote>\n<p>Now we\u2019ve looked extensively at binding simple types to route values, query strings, and headers. In section 7.3 we\u2019ll learn about binding to the body of a request by deserializing JSON to complex types.<\/p>\n<p>\u73b0\u5728\uff0c\u6211\u4eec\u5df2\u7ecf\u5e7f\u6cdb\u7814\u7a76\u4e86\u5982\u4f55\u5c06\u7b80\u5355\u7c7b\u578b\u7ed1\u5b9a\u5230\u8def\u7531\u503c\u3001\u67e5\u8be2\u5b57\u7b26\u4e32\u548c\u6807\u5934\u3002\u5728\u7b2c 7.3 \u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u901a\u8fc7\u5c06 JSON \u53cd\u5e8f\u5217\u5316\u4e3a\u590d\u6742\u7c7b\u578b\u6765\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6b63\u6587\u3002<\/p>\n<h2>7.3 Binding complex types to the JSON body<\/h2>\n<h2>7.3 \u5c06\u590d\u6742\u7c7b\u578b\u7ed1\u5b9a\u5230 JSON \u6b63\u6587<\/h2>\n<p>Model binding in minimal APIs relies on certain conventions to simplify the code you need to write. One such convention, which you\u2019ve already seen, is about binding to route parameters and query string values. Another important convention is that minimal API endpoints assume that requests will be sent using JSON.<\/p>\n<p>\u6700\u5c0f API \u4e2d\u7684\u6a21\u578b\u7ed1\u5b9a\u4f9d\u8d56\u4e8e\u67d0\u4e9b\u7ea6\u5b9a\u6765\u7b80\u5316\u60a8\u9700\u8981\u7f16\u5199\u7684\u4ee3\u7801\u3002\u5176\u4e2d\u4e00\u4e2a\u7ea6\u5b9a\u60a8\u5df2\u7ecf\u770b\u5230\uff0c\u5b83\u662f\u5173\u4e8e\u7ed1\u5b9a\u5230\u8def\u7531\u53c2\u6570\u548c\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\u7684\u3002\u53e6\u4e00\u4e2a\u91cd\u8981\u7684\u7ea6\u5b9a\u662f\uff0c\u6700\u5c0f API \u7aef\u70b9\u5047\u5b9a\u5c06\u4f7f\u7528 JSON \u53d1\u9001\u8bf7\u6c42\u3002<\/p>\n<p>Minimal APIs can bind the body of a request to a single complex type in your endpoint handler by deserializing the request from JSON. That means that if you have an endpoint such as the one in the following listing, ASP.NET Core will automatically deserialize the request for you from JSON, creating the Product argument.<\/p>\n<p>\u6700\u5c0f API \u53ef\u4ee5\u901a\u8fc7\u4ece JSON \u53cd\u5e8f\u5217\u5316\u8bf7\u6c42\uff0c\u5c06\u8bf7\u6c42\u6b63\u6587\u7ed1\u5b9a\u5230\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u7684\u5355\u4e2a\u590d\u6742\u7c7b\u578b\u3002\u8fd9\u610f\u5473\u7740\uff0c\u5982\u679c\u60a8\u6709\u4e00\u4e2a\u7ec8\u7aef\u8282\u70b9\uff08\u5982\u4e0b\u9762\u7684\u6e05\u5355\u4e2d\u7684\u7ec8\u7aef\u8282\u70b9\uff09\uff0cASP.NET Core \u5c06\u81ea\u52a8\u4ece JSON \u53cd\u5e8f\u5217\u5316\u8bf7\u6c42\uff0c\u4ece\u800c\u521b\u5efa Product \u53c2\u6570\u3002<\/p>\n<p>Listing 7.3 Automatically deserializing a JSON request from the body<br \/>\n\u6e05\u5355 7.3 \u4ece\u6b63\u6587\u4e2d\u81ea\u52a8\u53cd\u5e8f\u5217\u5316 JSON \u8bf7\u6c42<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\napp.MapPost(&quot;\/product&quot;, (Product product) =&gt; $&quot;Received {product}&quot;);  \/\/ \u2776\n\napp.Run();\n\nrecord Product(int Id, string Name, int Stock);                       \/\/ \u2777\n<\/pre>\n<p>\u2776 Product is a complex type, so it\u2019s bound to the JSON body of the request.<br \/>\nProduct \u662f\u4e00\u79cd\u590d\u6742\u7c7b\u578b\uff0c\u56e0\u6b64\u5b83\u7ed1\u5b9a\u5230\u8bf7\u6c42\u7684 JSON \u6b63\u6587\u3002<\/p>\n<p>\u2777 Product doesn\u2019t implement TryParse, so it\u2019s a complex type.<br \/>\nProduct \u6ca1\u6709\u5b9e\u73b0 TryParse\uff0c\u6240\u4ee5\u5b83\u662f\u4e00\u4e2a\u590d\u6742\u7684\u7c7b\u578b\u3002<\/p>\n<p>If you send a POST request to \/product for the app in listing 7.3, you need to provide valid JSON in the request body, such as<\/p>\n<p>\u5982\u679c\u5411\u6e05\u5355 7.3 \u4e2d\u7684\u5e94\u7528\u7a0b\u5e8f\u7684 \/product \u53d1\u9001 POST \u8bf7\u6c42\uff0c\u5219\u9700\u8981\u5728\u8bf7\u6c42\u6b63\u6587\u4e2d\u63d0\u4f9b\u6709\u6548\u7684 JSON\uff0c\u4f8b\u5982<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n{ &quot;id&quot;: 1, &quot;Name&quot;: &quot;Shoes&quot;, &quot;Stock&quot;: 12 }\n<\/pre>\n<p>ASP.NET Core uses the built-in System.Text.Json library to deserialize the JSON into a Product instance and uses it as the product argument in the handler.<\/p>\n<p>ASP.NET Core \u4f7f\u7528\u5185\u7f6e\u7684 System.Text.Json \u5e93\u5c06 JSON \u53cd\u5e8f\u5217\u5316\u4e3a Product \u5b9e\u4f8b\uff0c\u5e76\u5c06\u5176\u7528\u4f5c\u5904\u7406\u7a0b\u5e8f\u4e2d\u7684 product \u53c2\u6570\u3002<\/p>\n<p>Configuring JSON binding with System.Text.Json<br \/>\n\u4f7f\u7528 System.Text.Json \u914d\u7f6e JSON \u7ed1\u5b9a<br \/>\nThe System.Text.Json library, introduced in .NET Core 3.0, provides a high-performance, low-allocation JSON serialization library. It was designed to be something of a successor to the ubiquitous Newtonsoft.Json library, but it trades flexibility for performance.<br \/>\n.NET Core 3.0 \u4e2d\u5f15\u5165\u7684 System.Text.Json \u5e93\u63d0\u4f9b\u9ad8\u6027\u80fd\u3001\u4f4e\u5206\u914d\u7684 JSON \u5e8f\u5217\u5316\u5e93\u3002\u5b83\u65e8\u5728\u6210\u4e3a\u65e0\u5904\u4e0d\u5728\u7684 Newtonsoft.Json \u5e93\u7684\u7ee7\u4efb\u8005\uff0c\u4f46\u5b83\u4ee5\u7075\u6d3b\u6027\u6362\u53d6\u4e86\u6027\u80fd\u3002<br \/>\nMinimal APIs use System.Text.Json for both JSON deserialization (when binding to a request\u2019s body) and serialization (when writing results, as you saw in chapter 6). Unlike for MVC and Razor Pages, you can\u2019t replace the JSON serialization library used by minimal APIs, so there\u2019s no way to use Newtonsoft.Json instead. But you can customize some of the library\u2019s serialization behavior for your minimal APIs.<br \/>\n\u6700\u5c0f API \u4f7f\u7528 System.Text.Json \u8fdb\u884c JSON \u53cd\u5e8f\u5217\u5316\uff08\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6b63\u6587\u65f6\uff09\u548c\u5e8f\u5217\u5316\uff08\u5199\u5165\u7ed3\u679c\u65f6\uff0c\u5982\u7b2c 6 \u7ae0\u6240\u793a\uff09\u3002\u4e0e MVC \u548c Razor Pages \u4e0d\u540c\uff0c\u60a8\u65e0\u6cd5\u66ff\u6362\u6700\u5c0f API \u4f7f\u7528\u7684 JSON \u5e8f\u5217\u5316\u5e93\uff0c\u56e0\u6b64\u65e0\u6cd5\u6539\u7528 Newtonsoft.Json\u3002\u4f46\u662f\uff0c\u60a8\u53ef\u4ee5\u4e3a\u6700\u5c0f\u7684 API \u81ea\u5b9a\u4e49\u5e93\u7684\u67d0\u4e9b\u5e8f\u5217\u5316\u884c\u4e3a\u3002<br \/>\nYou can set System.Text.Json, for example, to relax some of its strictness to allow trailing commas in the JSON and control how property names are serialized with code like the following example:<br \/>\n\u4f8b\u5982\uff0c\u60a8\u53ef\u4ee5\u8bbe\u7f6e System.Text.Json \u4ee5\u653e\u5bbd\u5176\u4e00\u4e9b\u4e25\u683c\u6027\uff0c\u4ee5\u5141\u8bb8 JSON \u4e2d\u4f7f\u7528\u5c3e\u90e8\u9017\u53f7\uff0c\u5e76\u63a7\u5236\u5982\u4f55\u4f7f\u7528\u4ee3\u7801\u5e8f\u5217\u5316\u5c5e\u6027\u540d\u79f0\uff0c\u5982\u4ee5\u4e0b\u793a\u4f8b\u6240\u793a\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nbuilder.Services.ConfigureRouteHandlerJsonOptions(o =&gt; {\n    o.SerializerOptions.AllowTrailingCommas = true;\n    o.SerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;\n    o.SerializerOptions.PropertyNameCaseInsensitive = true;\n});\n<\/pre>\n<p>Typically, the automatic binding for JSON requests is convenient, as most APIs these days are built around JSON requests and responses. The built-in binding uses the most performant approach and eliminates a lot of boilerplate that you\u2019d otherwise need to write yourself. Nevertheless, bear several things in mind when you\u2019re binding to the request body:<\/p>\n<p>\u901a\u5e38\uff0cJSON \u8bf7\u6c42\u7684\u81ea\u52a8\u7ed1\u5b9a\u5f88\u65b9\u4fbf\uff0c\u56e0\u4e3a\u73b0\u5728\u5927\u591a\u6570 API \u90fd\u662f\u56f4\u7ed5 JSON \u8bf7\u6c42\u548c\u54cd\u5e94\u6784\u5efa\u7684\u3002\u5185\u7f6e\u7ed1\u5b9a\u4f7f\u7528\u6027\u80fd\u6700\u9ad8\u7684\u65b9\u6cd5\uff0c\u5e76\u6d88\u9664\u4e86\u8bb8\u591a\u60a8\u9700\u8981\u81ea\u5df1\u7f16\u5199\u7684\u6837\u677f\u6587\u4ef6\u3002\u5c3d\u7ba1\u5982\u6b64\uff0c\u8bf7\u718a\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6b63\u6587\u65f6\uff0c\u8bf7\u8bb0\u4f4f\u4ee5\u4e0b\u51e0\u70b9\uff1a<\/p>\n<ul>\n<li>\n<p>You can bind only a single handler parameter to the JSON body. If more than one complex parameter is eligible to bind to the body, you\u2019ll get an exception at runtime when the app receives its first request.<br \/>\n\u60a8\u53ea\u80fd\u5c06\u5355\u4e2a\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u7ed1\u5b9a\u5230 JSON \u6b63\u6587\u3002\u5982\u679c\u591a\u4e2a\u590d\u6742\u53c2\u6570\u7b26\u5408\u7ed1\u5b9a\u5230\u6b63\u6587\u7684\u6761\u4ef6\uff0c\u5219\u5f53\u5e94\u7528\u7a0b\u5e8f\u6536\u5230\u5176\u7b2c\u4e00\u4e2a\u8bf7\u6c42\u65f6\uff0c\u60a8\u5c06\u5728\u8fd0\u884c\u65f6\u6536\u5230\u5f02\u5e38\u3002<\/p>\n<\/li>\n<li>\n<p>If the request body isn\u2019t JSON, the endpoint handler won\u2019t run, and the EndpointMiddleware will return a 415 Unsupported Media Type response.<br \/>\n\u5982\u679c\u8bf7\u6c42\u6b63\u6587\u4e0d\u662f JSON\uff0c\u5219\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e0d\u4f1a\u8fd0\u884c\uff0c\u5e76\u4e14 EndpointMiddleware \u5c06\u8fd4\u56de 415 Unsupported Media Type \u54cd\u5e94\u3002<\/p>\n<\/li>\n<li>\n<p>If you try to bind to the body for an HTTP verb that usually doesn\u2019t send a body (GET, HEAD, OPTIONS, DELETE, TRACE, and CONNECT), you\u2019ll get an exception at runtime. If you change the endpoint in listing 7.3 to MapGet instead of MapPost, for example, you\u2019ll get an exception on your first request, as shown in figure 7.4.<br \/>\n\u5982\u679c\u60a8\u5c1d\u8bd5\u7ed1\u5b9a\u5230\u901a\u5e38\u4e0d\u53d1\u9001\u6b63\u6587\u7684 HTTP \u52a8\u8bcd\uff08GET\u3001HEAD\u3001OPTIONS\u3001DELETE\u3001TRACE \u548c CONNECT\uff09\u7684\u6b63\u6587\uff0c\u5219\u4f1a\u5728\u8fd0\u884c\u65f6\u6536\u5230\u5f02\u5e38\u3002\u4f8b\u5982\uff0c\u5982\u679c\u5c06\u6e05\u5355 7.3 \u4e2d\u7684\u7aef\u70b9\u66f4\u6539\u4e3a MapGet \u800c\u4e0d\u662f MapPost\uff0c\u5219\u7b2c\u4e00\u4e2a\u8bf7\u6c42\u5c06\u6536\u5230\u5f02\u5e38\uff0c\u5982\u56fe 7.4 \u6240\u793a\u3002<\/p>\n<\/li>\n<li>\n<p>If you\u2019re sure that you want to bind the body of these requests, you can override the preceding behavior by applying the [FromBody] attribute to the handler parameter. I strongly advise against this approach, though: sending a body with GET requests is unusual, could confuse the consumers of your API, and is discouraged in the HTTP specification (<a href=\"https:\/\/www.rfc-editor.org\/rfc\/rfc9110#name-get\">https:\/\/www.rfc-editor.org\/rfc\/rfc9110#name-get<\/a>).<br \/>\n\u5982\u679c\u786e\u5b9a\u8981\u7ed1\u5b9a\u8fd9\u4e9b\u8bf7\u6c42\u7684\u6b63\u6587\uff0c\u53ef\u4ee5\u901a\u8fc7\u5c06 [FromBody] \u5c5e\u6027\u5e94\u7528\u4e8e handler \u53c2\u6570\u6765\u66ff\u4ee3\u4e0a\u8ff0\u884c\u4e3a\u3002\u4e0d\u8fc7\uff0c\u6211\u5f3a\u70c8\u5efa\u8bae\u4e0d\u8981\u4f7f\u7528\u8fd9\u79cd\u65b9\u6cd5\uff1a\u53d1\u9001\u5e26\u6709 GET \u8bf7\u6c42\u7684\u6b63\u6587\u662f\u4e0d\u5bfb\u5e38\u7684\uff0c\u53ef\u80fd\u4f1a\u4f7f API \u7684\u4f7f\u7528\u8005\u611f\u5230\u56f0\u60d1\uff0c\u5e76\u4e14\u5728 HTTP \u89c4\u8303 \uff08<a href=\"https:\/\/www.rfc\">https:\/\/www.rfc<\/a>- editor.org\/rfc\/rfc9110#name-get\uff09 \u4e2d\u4e0d\u5efa\u8bae\u8fd9\u6837\u505a\u3002<\/p>\n<\/li>\n<li>\n<p>It\u2019s uncommon to see, but you can also apply [FromBody] to a simple type parameter to force it to bind to the request body instead of to the route\/query string. As for complex types, the body is deserialized from JSON into your parameter.<br \/>\n\u8fd9\u79cd\u60c5\u51b5\u5e76\u4e0d\u5e38\u89c1\uff0c\u4f46\u4f60\u4e5f\u53ef\u4ee5\u5c06 [FromBody] \u5e94\u7528\u4e8e\u7b80\u5355\u7c7b\u578b\u53c2\u6570\uff0c\u4ee5\u5f3a\u5236\u5b83\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6b63\u6587\uff0c\u800c\u4e0d\u662f\u8def\u7531\u5230\u8def\u7531\/\u67e5\u8be2\u5b57\u7b26\u4e32\u3002\u5bf9\u4e8e\u590d\u6742\u7c7b\u578b\uff0c\u6b63\u6587\u4ece JSON \u53cd\u5e8f\u5217\u5316\u4e3a\u53c2\u6570\u3002<\/p>\n<\/li>\n<\/ul>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0704.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 7.4 If you try to bind the body to a parameter for a GET request, you\u2019ll get an exception when your app receives its first request.<br \/>\n\u56fe 7.4 \u5982\u679c\u60a8\u5c1d\u8bd5\u5c06 body \u7ed1\u5b9a\u5230 GET \u8bf7\u6c42\u7684\u53c2\u6570\uff0c\u5219\u5f53\u5e94\u7528\u7a0b\u5e8f\u6536\u5230\u5176\u7b2c\u4e00\u4e2a\u8bf7\u6c42\u65f6\uff0c\u60a8\u5c06\u6536\u5230\u5f02\u5e38\u3002<\/p>\n<p>We\u2019ve discussed binding of both simple types and complex types. Unfortunately, now it\u2019s time to admit to a gray area: arrays, which can be simple types or complex types.<\/p>\n<p>\u6211\u4eec\u5df2\u7ecf\u8ba8\u8bba\u4e86\u7b80\u5355\u7c7b\u578b\u548c\u590d\u6742\u7c7b\u578b\u7684\u7ed1\u5b9a\u3002\u4e0d\u5e78\u7684\u662f\uff0c\u73b0\u5728\u662f\u65f6\u5019\u627f\u8ba4\u4e00\u4e2a\u7070\u8272\u5730\u5e26\u4e86\uff1a\u6570\u7ec4\uff0c\u5b83\u53ef\u4ee5\u662f\u7b80\u5355\u7c7b\u578b\u6216\u590d\u6742\u7c7b\u578b\u3002<\/p>\n<h2>7.4 Arrays: Simple types or complex types?<\/h2>\n<h2>7.4 \u6570\u7ec4\uff1a\u7b80\u5355\u7c7b\u578b\u8fd8\u662f\u590d\u6742\u7c7b\u578b\uff1f<\/h2>\n<p>It\u2019s a little-known fact that entries in the query string of a URL don\u2019t have to be unique. The following URL is valid, for example, even though it includes a duplicate id parameter:<\/p>\n<p>\u4e00\u4e2a\u9c9c\u4e3a\u4eba\u77e5\u7684\u4e8b\u5b9e\u662f\uff0cURL \u7684\u67e5\u8be2\u5b57\u7b26\u4e32\u4e2d\u7684\u6761\u76ee\u4e0d\u5fc5\u662f\u552f\u4e00\u7684\u3002\u4f8b\u5982\uff0c\u4ee5\u4e0b URL \u6709\u6548\uff0c\u5373\u4f7f\u5b83\u5305\u542b\u91cd\u590d\u7684 id \u53c2\u6570\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/products?id=123&amp;id=456\n<\/pre>\n<p>So how do you access these query string values with minimal APIs? If you create an endpoint like<\/p>\n<p>\u90a3\u4e48\uff0c\u5982\u4f55\u4f7f\u7528\u6700\u5c11\u7684 API \u8bbf\u95ee\u8fd9\u4e9b\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\u5462\uff1f\u5982\u679c\u60a8\u521b\u5efa\u7c7b\u4f3c\u4e8e<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapGet(&quot;\/products&quot;, (int id) =&gt; $&quot;Received {id}&quot;);\n<\/pre>\n<p>a request to \/products?id=123 would bind the id parameter to the query string, as you\u2019d expect. But a request that includes two id values in the query string, such as \/products?id=123&amp;id=456, will cause a runtime error, as shown in figure 7.5. ASP.NET Core returns a 400 Bad Request response without the handler\u2019s or filter pipeline\u2019s running at all.<\/p>\n<p>\u5982\u60a8\u6240\u6599\uff0c\u5bf9 \/products\uff1fid=123 \u7684\u8bf7\u6c42\u4f1a\u5c06 id \u53c2\u6570\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\u3002\u4f46\u662f\uff0c\u5728\u67e5\u8be2\u5b57\u7b26\u4e32\u4e2d\u5305\u542b\u4e24\u4e2a id \u503c\u7684\u8bf7\u6c42\uff08\u4f8b\u5982 \/products\uff1fid=123&amp;id=456\uff09\u5c06\u5bfc\u81f4\u8fd0\u884c\u65f6\u9519\u8bef\uff0c\u5982\u56fe 7.5 \u6240\u793a\u3002ASP.NET Core \u8fd4\u56de 400 Bad Request \u54cd\u5e94\uff0c\u800c\u5904\u7406\u7a0b\u5e8f\u6216\u7b5b\u9009\u5668\u7ba1\u9053\u6839\u672c\u6ca1\u6709\u8fd0\u884c\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0705.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 7.5 Attempting to bind a handler with a signature such as (int id) to a query string that contains ?id=123&amp;id=456 causes an exception at runtime and a 400 Bad Request response.<\/p>\n<p>\u56fe 7.5 \u5c1d\u8bd5\u5c06\u5177\u6709 \uff08int id\uff09 \u7b49\u7b7e\u540d\u7684\u5904\u7406\u7a0b\u5e8f\u7ed1\u5b9a\u5230\u5305\u542b \uff1fid=123&amp;id=456 \u7684\u67e5\u8be2\u5b57\u7b26\u4e32\u4f1a\u5bfc\u81f4\u8fd0\u884c\u65f6\u51fa\u73b0\u5f02\u5e38\u548c 400 Bad Request \u54cd\u5e94\u3002<\/p>\n<p>If you want to handle query strings like this one, so that users can optionally pass multiple possible values for a parameter, you need to use arrays. The following listing shows an example of an endpoint that accepts multiple id values from the query string and binds them to an array.<\/p>\n<p>\u5982\u679c\u8981\u5904\u7406\u50cf\u8fd9\u6837\u7684\u67e5\u8be2\u5b57\u7b26\u4e32\uff0c\u4ee5\u4fbf\u7528\u6237\u53ef\u4ee5\u9009\u62e9\u4e3a\u53c2\u6570\u4f20\u9012\u591a\u4e2a\u53ef\u80fd\u7684\u503c\uff0c\u5219\u9700\u8981\u4f7f\u7528\u6570\u7ec4\u3002\u4ee5\u4e0b\u6e05\u5355\u663e\u793a\u4e86\u4e00\u4e2a\u7ec8\u7aef\u8282\u70b9\u793a\u4f8b\uff0c\u8be5\u7ec8\u7aef\u8282\u70b9\u63a5\u53d7\u6765\u81ea\u67e5\u8be2\u5b57\u7b26\u4e32\u7684\u591a\u4e2a id \u503c\u5e76\u5c06\u5b83\u4eec\u7ed1\u5b9a\u5230\u6570\u7ec4\u3002<\/p>\n<p>Listing 7.4 Binding multiple values for a parameter in a query string to an array<br \/>\n\u6e05\u5355 7.4 \u4e3a \u4e2d\u7684\u53c2\u6570\u7ed1\u5b9a\u591a\u4e2a\u503c\u6570\u7ec4\u7684\u67e5\u8be2\u5b57\u7b26\u4e32<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\napp.MapGet(&quot;\/products\/search&quot;,\n    (int&#x5B;] id) =&gt; $&quot;Received {id.Length} ids&quot;);    \/\/ \u2776\n\napp.Run();\n\n<\/pre>\n<p>\u2776 The array will bind to multiple instances of id in the query string.<br \/>\n\u6570\u7ec4\u5c06\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\u4e2d id \u7684\u591a\u4e2a\u5b9e\u4f8b\u3002<\/p>\n<p>If you\u2019re anything like me, the fact that the int[] handler parameter in listing 7.4 is called id and not ids will really bug you. Unfortunately, you have to use id here so that the parameter binds correctly to a query string like ?id=123&amp;id=456. If you renamed it ids, the query string would need to be ?ids=123&amp;ids=456.<\/p>\n<p>\u5982\u679c\u4f60\u548c\u6211\u4e00\u6837\uff0c\u6e05\u5355 7.4 \u4e2d\u7684 int[] handler \u53c2\u6570\u53eb id \u800c\u4e0d\u662f ids \u8fd9\u4e00\u4e8b\u5b9e\u771f\u7684\u4f1a\u8ba9\u4f60\u611f\u5230\u56f0\u6270\u3002\u9057\u61be\u7684\u662f\uff0c\u60a8\u5fc5\u987b\u5728\u6b64\u5904\u4f7f\u7528 id\uff0c\u4ee5\u4fbf\u53c2\u6570\u6b63\u786e\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\uff0c\u4f8b\u5982 \uff1fid=123&amp;id=456\u3002\u5982\u679c\u60a8\u5c06\u5176\u91cd\u547d\u540d\u4e3a ids\uff0c\u5219\u67e5\u8be2\u5b57\u7b26\u4e32\u9700\u8981\u4e3a \uff1fids=123&amp;ids=456\u3002<\/p>\n<p>Luckily, you have another option. You can control the name of the target that a handler parameter binds to by using the [FromQuery] and [FromRoute] attributes, similar to the way you use [FromHeader]. For this example, you can have the best of both words by renaming the handler parameter ids and adding the [FromQuery] attribute:<\/p>\n<p>\u5e78\u8fd0\u7684\u662f\uff0c\u60a8\u8fd8\u6709\u53e6\u4e00\u79cd\u9009\u62e9\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528 [FromQuery] \u548c [FromRoute] \u5c5e\u6027\u6765\u63a7\u5236\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u7ed1\u5b9a\u5230\u7684\u76ee\u6807\u7684\u540d\u79f0\uff0c\u7c7b\u4f3c\u4e8e\u4f7f\u7528 [FromHeader] \u7684\u65b9\u5f0f\u3002\u5bf9\u4e8e\u6b64\u793a\u4f8b\uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7\u91cd\u547d\u540d\u5904\u7406\u7a0b\u5e8f\u53c2\u6570 ID \u5e76\u6dfb\u52a0 [FromQuery] \u5c5e\u6027\u6765\u83b7\u5f97\u4e24\u5168\u5176\u7f8e\u7684\u6548\u679c\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapGet(&quot;\/products\/search&quot;,\n(&#x5B;FromQuery(Name = &quot;id&quot;)] int&#x5B;] ids) =&gt; $&quot;Received {ids.Length} ids&quot;);\n<\/pre>\n<p>Now you can sleep easy. The handler parameter has a better name, but it still binds to the query string ?id=123&amp;id=456 correctly.<\/p>\n<p>\u73b0\u5728\u60a8\u53ef\u4ee5\u5b89\u5fc3\u5165\u7761\u4e86\u3002handler \u53c2\u6570\u5177\u6709\u66f4\u597d\u7684\u540d\u79f0\uff0c\u4f46\u5b83\u4ecd\u6b63\u786e\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32 \uff1fid=123&amp;id=456\u3002<\/p>\n<p><strong>Tip<\/strong> You can bind array parameters to multiple header values in the same way that you do for as query string values, using the [FromHeader] attribute.<br \/>\n<strong>\u63d0\u793a<\/strong> \u53ef\u4ee5\u4f7f\u7528 [FromHeader] \u5c5e\u6027\uff0c\u4ee5\u4e0e\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\u76f8\u540c\u7684\u65b9\u5f0f\u5c06\u6570\u7ec4\u53c2\u6570\u7ed1\u5b9a\u5230\u591a\u4e2a\u6807\u5934\u503c\u3002<\/p>\n<p>The example in listing 7.4 binds an int[], but you can bind an array of any simple type, including custom types with a TryParse method (listing 7.2), as well as string[] and StringValues.<\/p>\n<p>\u6e05\u5355 7.4 \u4e2d\u7684\u793a\u4f8b\u7ed1\u5b9a\u4e86\u4e00\u4e2a int[]\uff0c\u4f46\u4f60\u53ef\u4ee5\u7ed1\u5b9a\u4efb\u4f55\u7b80\u5355\u7c7b\u578b\u7684\u6570\u7ec4\uff0c\u5305\u62ec\u5e26\u6709 TryParse \u65b9\u6cd5\u7684\u81ea\u5b9a\u4e49\u7c7b\u578b\uff08\u6e05\u5355 7.2\uff09\uff0c\u4ee5\u53ca string[] \u548c StringValues\u3002<\/p>\n<p><strong>Note<\/strong> StringValues is a helper type in the Microsoft.Extensions.Primitives namespace that represents zero, one, or many strings in an efficient way.<br \/>\n<strong>\u6ce8\u610f<\/strong> StringValues \u662f Microsoft.Extensions.Primitives \u547d\u540d\u7a7a\u95f4\u4e2d\u7684\u5e2e\u52a9\u7a0b\u5e8f\u7c7b\u578b\uff0c\u5b83\u4ee5\u9ad8\u6548\u7684\u65b9\u5f0f\u8868\u793a\u96f6\u4e2a\u3001\u4e00\u4e2a\u6216\u591a\u4e2a\u5b57\u7b26\u4e32\u3002<\/p>\n<p>So where is that gray area I mentioned? Well, arrays work as I\u2019ve described only if<\/p>\n<p>\u90a3\u4e48\u6211\u63d0\u5230\u7684\u7070\u8272\u5730\u5e26\u5728\u54ea\u91cc\u5462\uff1f\u597d\u5427\uff0c\u6570\u7ec4\u53ea\u6709\u5728<\/p>\n<ul>\n<li>\n<p>You\u2019re using an HTTP verb that typically doesn\u2019t include a request body, such as GET, HEAD, or DELETE.<br \/>\n\u60a8\u4f7f\u7528\u7684\u662f\u901a\u5e38\u4e0d\u5305\u542b\u8bf7\u6c42\u6b63\u6587\u7684 HTTP \u52a8\u8bcd\uff0c\u4f8b\u5982 GET\u3001HEAD \u6216 DELETE\u3002<\/p>\n<\/li>\n<li>\n<p>The array is an array of simple types (or string[] or StringValues).<br \/>\n\u8be5\u6570\u7ec4\u662f\u7b80\u5355\u7c7b\u578b\uff08\u6216string[] \u6216 StringValues\uff09\u3002<\/p>\n<\/li>\n<\/ul>\n<p>If either of these statements is not true, ASP.NET Core will attempt to bind the array to the JSON body of the request instead. For POST requests (or other verbs that typically have a request body), this process works without problems: the JSON body is deserialized to the parameter array. For GET requests (and other verbs without a body), it causes the same unhandled exception you saw in figure 7.4 when a body binding is detected in one of these verbs.<\/p>\n<p>\u5982\u679c\u8fd9\u4e9b\u8bed\u53e5\u4e2d\u7684\u4efb\u4f55\u4e00\u4e2a\u4e0d \u4e3a true\uff0c\u5219 ASP.NET Core \u5c06\u5c1d\u8bd5\u5c06\u6570\u7ec4\u7ed1\u5b9a\u5230\u8bf7\u6c42\u7684 JSON \u6b63\u6587\u3002\u5bf9\u4e8e POST \u8bf7\u6c42\uff08\u6216\u5176\u4ed6\u901a\u5e38\u5177\u6709\u8bf7\u6c42\u6b63\u6587\u7684\u52a8\u8bcd\uff09\uff0c\u6b64\u8fc7\u7a0b\u53ef\u4ee5\u6b63\u5e38\u5de5\u4f5c\uff1aJSON \u6b63\u6587\u88ab\u53cd\u5e8f\u5217\u5316\u4e3a\u53c2\u6570\u6570\u7ec4\u3002\u5bf9\u4e8e GET \u8bf7\u6c42\uff08\u4ee5\u53ca\u5176\u4ed6\u6ca1\u6709\u6b63\u6587\u7684\u52a8\u8bcd\uff09\uff0c\u5f53\u5728\u5176\u4e2d\u4e00\u4e2a\u52a8\u8bcd\u4e2d\u68c0\u6d4b\u5230\u6b63\u6587\u7ed1\u5b9a\u65f6\uff0c\u5b83\u4f1a\u5bfc\u81f4\u60a8\u5728\u56fe 7.4 \u4e2d\u770b\u5230\u7684\u76f8\u540c\u672a\u5904\u7406\u5f02\u5e38\u3002<\/p>\n<p><strong>Note<\/strong> As before, when binding body parameters, you can work around this situation for GET requests by adding an explicit [FromBody] to the handler parameter, but you shouldn\u2019t!<br \/>\n<strong>\u6ce8\u610f<\/strong> \u548c\u4ee5\u524d\u4e00\u6837\uff0c\u5728\u7ed1\u5b9a body \u53c2\u6570\u65f6\uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7\u5411 handler \u53c2\u6570\u6dfb\u52a0\u663e\u5f0f [FromBody] \u6765\u89e3\u51b3 GET \u8bf7\u6c42\u7684\u8fd9\u79cd\u60c5\u51b5\uff0c\u4f46\u60a8\u4e0d\u5e94\u8be5\u8fd9\u6837\u505a\uff01<\/p>\n<p>We\u2019ve covered binding both simple types and complex types, from the URL and the body, and we\u2019ve even looked at some cases in which a mismatch between what you expect and what you receive causes errors. But what if a value you expect isn\u2019t there? In section 7.5 we look at how you can choose what happens.<\/p>\n<p>\u6211\u4eec\u5df2\u7ecf\u4ecb\u7ecd\u4e86\u4ece URL \u548c\u6b63\u6587\u7ed1\u5b9a\u7b80\u5355\u7c7b\u578b\u548c\u590d\u6742\u7c7b\u578b\uff0c\u6211\u4eec\u751a\u81f3\u7814\u7a76\u4e86\u4e00\u4e9b\u60c5\u51b5\uff0c\u5728\u8fd9\u4e9b\u60c5\u51b5\u4e0b\uff0c\u4f60\u671f\u671b\u7684\u548c\u4f60\u6536\u5230\u7684\u5185\u5bb9\u4e0d\u5339\u914d\u4f1a\u5bfc\u81f4\u9519\u8bef\u3002\u4f46\u662f\uff0c\u5982\u679c\u60a8\u671f\u671b\u7684\u503c\u4e0d\u5b58\u5728\u600e\u4e48\u529e\uff1f\u5728 7.5 \u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u9009\u62e9\u53d1\u751f\u7684\u60c5\u51b5\u3002<\/p>\n<h2>7.5 Making parameters optional with nullables<\/h2>\n<h2>7.5 \u4f7f\u53c2\u6570\u5bf9\u53ef\u4e3a null \u503c<\/h2>\n<p>We\u2019ve described lots of ways to bind parameters to minimal API endpoints. If you\u2019ve been experimenting with the code samples and sending requests, you may have noticed that if the endpoint can\u2019t bind a parameter at runtime, you get an error and a 400 Bad Request response. If you have an endpoint that binds a parameter to the query string, such as<\/p>\n<p>\u6211\u4eec\u5df2\u7ecf\u4ecb\u7ecd\u4e86\u8bb8\u591a\u5c06\u53c2\u6570\u7ed1\u5b9a\u5230\u6700\u5c0f API \u7aef\u70b9\u7684\u65b9\u6cd5\u3002\u5982\u679c\u60a8\u4e00\u76f4\u5728\u8bd5\u9a8c\u4ee3\u7801\u793a\u4f8b\u5e76\u53d1\u9001\u8bf7\u6c42\uff0c\u60a8\u53ef\u80fd\u5df2\u7ecf\u6ce8\u610f\u5230\uff0c\u5982\u679c\u7ec8\u7aef\u8282\u70b9\u5728\u8fd0\u884c\u65f6\u65e0\u6cd5\u7ed1\u5b9a\u53c2\u6570\uff0c\u5219\u4f1a\u6536\u5230\u9519\u8bef\u548c 400 Bad Request \u54cd\u5e94\u3002\u5982\u679c\u60a8\u6709\u4e00\u4e2a\u5c06\u53c2\u6570\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\u7684\u7ec8\u7aef\u8282\u70b9\uff0c\u4f8b\u5982<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapGet(&quot;\/products&quot;, (int id) =&gt; $&quot;Received {id}&quot;);\n<\/pre>\n<p>but you send a request without a query string or with the wrong name in the query string, such as a request to \/products?p=3, the EndpointMiddleware throws an exception, as shown in figure 7.6. The id parameter is required, so if it can\u2019t bind, you\u2019ll get an error message and a 400 Bad Request response, and the endpoint handler won\u2019t run.<\/p>\n<p>\u4f46\u662f\u60a8\u53d1\u9001\u7684\u8bf7\u6c42\u6ca1\u6709\u67e5\u8be2\u5b57\u7b26\u4e32\u6216\u67e5\u8be2\u5b57\u7b26\u4e32\u4e2d\u7684\u540d\u79f0\u9519\u8bef\uff0c\u4f8b\u5982\u5bf9\/products\uff1fp=3 \u4e2d\uff0cEndpointMiddleware \u4f1a\u629b\u51fa\u4e00\u4e2aexception \u7684 intent \u793a\u4f8b\uff0c\u5982\u56fe 7.6 \u6240\u793a\u3002id \u53c2\u6570\u662f\u5fc5\u9700\u7684\uff0c\u56e0\u6b64\u5982\u679c\u5b83\u65e0\u6cd5\u7ed1\u5b9a\uff0c\u60a8\u5c06\u6536\u5230\u4e00\u6761\u9519\u8bef\u6d88\u606f\u548c\u4e00\u4e2a400 Bad Request \u54cd\u5e94\uff0c\u5e76\u4e14\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e0d\u4f1a\u8fd0\u884c\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0706.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 7.6 If a parameter can\u2019t be bound because a value is missing, the EndpointMiddleware throws an exception and returns a 400 Bad Request response. The endpoint handler doesn\u2019t run.<br \/>\n\u56fe 7.6 \u5982\u679c\u67d0\u4e2a\u53c2\u6570\u56e0\u4e3a\u7f3a\u5c11\u503c\u800c\u65e0\u6cd5\u7ed1\u5b9a\uff0c\u5219 EndpointMiddleware \u4f1a\u629b\u51fa\u4e00\u4e2aexception \u5e76\u8fd4\u56de 400 Bad Request \u54cd\u5e94\u3002\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e0d\u8fd0\u884c\u3002<\/p>\n<p>All parameters are required regardless of which binding source they use, whether that\u2019s from a route value, a query string value, a header, or the request body. But what if you want a handler parameter to be optional? If you have an endpoint like this one,<\/p>\n<p>\u65e0\u8bba\u5b83\u4eec\u4f7f\u7528\u54ea\u4e2a\u7ed1\u5b9a\u6e90\uff0c\u65e0\u8bba\u662f\u6765\u81ea\u8def\u7531\u503c\u3001\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\u3001\u6807\u5934\u8fd8\u662f\u8bf7\u6c42\u6b63\u6587\uff0c\u6240\u6709\u53c2\u6570\u90fd\u662f\u5fc5\u9700\u7684\u3002\u4f46\u662f\uff0c\u5982\u679c\u60a8\u5e0c\u671b handler \u53c2\u6570\u662f\u53ef\u9009\u7684\uff0c\u8be5\u600e\u4e48\u529e\uff1f\u5982\u679c\u60a8\u6709\u50cf\u8fd9\u6837\u7684\u7ec8\u7aef\u8282\u70b9\uff0c<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapGet(&quot;\/stock\/{id?}&quot;, (int id) =&gt; $&quot;Received {id}&quot;);\n<\/pre>\n<p>given that the route parameter is marked optional, requests to both \/stock\/123 and \/stock will invoke the handler. But in the latter case, there\u2019ll be no id route value, and you\u2019ll get an error like the one shown in figure 7.6.<\/p>\n<p>\u9274\u4e8e route \u53c2\u6570\u6807\u8bb0\u4e3a\u53ef\u9009\uff0c\u5219\u5bf9 \/stock\/123 \u548c \/stock \u7684\u8bf7\u6c42\u90fd\u5c06\u8c03\u7528\u5904\u7406\u7a0b\u5e8f\u3002\u4f46\u5728\u540e\u4e00\u79cd\u60c5\u51b5\u4e0b\uff0c\u5c06\u6ca1\u6709 id \u8def\u7531\u503c\uff0c\u5e76\u4e14\u60a8\u5c06\u6536\u5230\u5982\u56fe 7.6 \u6240\u793a\u7684\u9519\u8bef\u3002<\/p>\n<p>The way around this problem is to mark the handler parameter as optional by making it nullable. Just as ? signifies optional in route templates, it signifies optional in the handler parameters. You can update the handler to use int? instead of int, as shown in the following listing, and the endpoint will handle both \/stock\/123 and \/stock without errors.<\/p>\n<p>\u89e3\u51b3\u6b64\u95ee\u9898\u7684\u65b9\u6cd5\u662f\u901a\u8fc7\u5c06 handler \u53c2\u6570\u8bbe\u4e3a null\uff0c\u5c06\u5176\u6807\u8bb0\u4e3a\u53ef\u9009\u3002\u5c31\u50cf \uff1f \u8868\u793a optional \u5728\u8def\u7531\u6a21\u677f\u4e2d\uff0c\u5b83\u5728\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u4e2d\u8868\u793a optional\u3002\u60a8\u53ef\u4ee5\u66f4\u65b0\u5904\u7406\u7a0b\u5e8f\u4ee5\u4f7f\u7528 int\uff1f \u800c\u4e0d\u662f int\uff0c\u5982\u4e0b\u9762\u7684\u6e05\u5355\u6240\u793a\uff0c\u5e76\u4e14\u7aef\u70b9\u5c06\u540c\u65f6\u5904\u7406 \/stock\/123 \u548c \/stock\uff0c\u800c\u4e0d\u4f1a\u51fa\u9519\u3002<\/p>\n<p>Listing 7.5 Using optional parameters in endpoint handlers<br \/>\n\u5217\u8868 7.5 \u5728 endpoint \u4e2d\u4f7f\u7528\u53ef\u9009\u53c2\u6570\u5904\u7406\u5668<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\napp.MapGet(&quot;\/stock\/{id?}&quot;, (int? id) =&gt; $&quot;Received {id}&quot;);            \/\/ \u2776\n\napp.MapGet(&quot;\/stock2&quot;, (int? id) =&gt; $&quot;Received {id}&quot;);                 \/\/ \u2777\n\napp.MapPost(&quot;\/stock&quot;, (Product? product) =&gt; $&quot;Received {product}&quot;);   \/\/ \u2778\n\napp.Run();\n\nrecord Product(int Id, string Name, int Stock);\n\n<\/pre>\n<p>\u2776 Uses a nullable simple type to indicate that the value is optional, so id is null when calling \/stock<br \/>\n\u4f7f\u7528\u53ef\u4e3a null \u7684\u7b80\u5355\u7c7b\u578b\u6765\u6307\u793a\u503c\u662f\u53ef\u9009\u7684\uff0c\u56e0\u6b64\u5728\u8c03\u7528 \/stock \u65f6 id \u4e3a null<\/p>\n<p>\u2777 This example binds to the query string. Id will be null for the request \/stock2.<br \/>\n\u6b64\u793a\u4f8b\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\u3002\u8bf7\u6c42 \/stock2 \u7684 Id \u5c06\u4e3a null\u3002<\/p>\n<p>\u2778 A nullable complex type binds to the body if it\u2019s available; otherwise, it\u2019s null.<br \/>\n\u5982\u679c\u53ef\u7528\uff0c\u5219\u53ef\u4e3a null \u7684\u590d\u6742\u7c7b\u578b\u7ed1\u5b9a\u5230\u6b63\u6587;\u5426\u5219\uff0c\u5b83\u662f null\u3002<\/p>\n<p>If no corresponding route value or query string contains the required value and the handler parameter is optional, the EndpointHandler uses null as the argument when invoking the endpoint handler. Similarly, for complex types that bind to the request body, if the request doesn\u2019t contain anything in the body and the parameter is optional, the handler will have a null argument.<\/p>\n<p>\u5982\u679c\u6ca1\u6709\u76f8\u5e94\u7684\u8def\u7531\u503c\u6216\u67e5\u8be2\u5b57\u7b26\u4e32\u5305\u542b\u6240\u9700\u7684\u503c\uff0c\u5e76\u4e14 handler \u53c2\u6570\u662f\u53ef\u9009\u7684\uff0c\u5219 EndpointHandler \u5728\u8c03\u7528\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u65f6\u4f7f\u7528 null \u4f5c\u4e3a\u53c2\u6570\u3002\u540c\u6837\uff0c\u5bf9\u4e8e\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6b63\u6587\u7684\u590d\u6742\u7c7b\u578b\uff0c\u5982\u679c\u8bf7\u6c42\u4e0d\u5305\u542bbody \u4e2d\u7684\u4efb\u4f55\u5185\u5bb9\uff0c\u5e76\u4e14\u53c2\u6570\u662f\u53ef\u9009\u7684\uff0c\u5219\u5904\u7406\u7a0b\u5e8f\u5c06\u5177\u6709 null \u53c2\u6570\u3002<\/p>\n<p><strong>Warning<\/strong> If the request body contains the literal JSON value null and the handler parameter is marked optional, the handler argument will also be null. If the parameter isn\u2019t marked optional, you get the same error as though the request didn\u2019t have a body.<br \/>\n<strong>\u8b66\u544a<\/strong> \u5982\u679c\u8bf7\u6c42\u6b63\u6587\u5305\u542b\u6587\u672c JSON \u503c null\uff0c\u5e76\u4e14 handler \u53c2\u6570\u6807\u8bb0\u4e3a\u53ef\u9009\uff0c\u5219 handler \u53c2\u6570\u4e5f\u5c06\u4e3a null\u3002\u5982\u679c\u53c2\u6570\u672a\u6807\u8bb0\u4e3a optional\uff0c\u5219\u4f1a\u6536\u5230\u4e0e\u8bf7\u6c42\u6ca1\u6709\u6b63\u6587\u76f8\u540c\u7684\u9519\u8bef\u3002<\/p>\n<p>It\u2019s worth noting that you mark complex types binding to the request body as optional by using a nullable reference type (NRT) annotation: ?. NRTs, introduced in C# 8, are an attempt to reduce the scourge of null-reference exceptions in C#, colloquially known as \u201cthe billion-dollar mistake.\u201d See <a href=\"http:\/\/mng.bz\/vneM\">http:\/\/mng.bz\/vneM<\/a>.<\/p>\n<p>\u503c\u5f97\u6ce8\u610f\u7684\u662f\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528\u53ef\u4e3a null \u7684\u5f15\u7528\u7c7b\u578b \uff08NRT\uff09 \u6ce8\u91ca\u5c06\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6b63\u6587\u7684\u590d\u6742\u7c7b\u578b\u6807\u8bb0\u4e3a\u53ef\u9009\uff1a\uff1f\u3002C# 8 \u4e2d\u5f15\u5165\u7684 NRT \u65e8\u5728\u51cf\u5c11 C# \u4e2d null \u5f15\u7528\u5f02\u5e38\u7684\u7978\u5bb3\uff0c\u4fd7\u79f0\u201c\u5341\u4ebf\u7f8e\u5143\u7684\u9519\u8bef\u201d\u3002\u8bf7\u53c2\u9605 <a href=\"http:\/\/mng.bz\/vneM\">http:\/\/mng.bz\/vneM<\/a>\u3002<\/p>\n<p>ASP.NET Core in .NET 7 is built with the assumption that NRTs are enabled for your project (and they\u2019re enabled by default in all the templates), so it\u2019s worth using them wherever you can. If you choose to disable NRTs explicitly, you may find that some of your types are unexpectedly marked optional, which can lead to some hard-to-debug errors.<\/p>\n<p>.NET 7 \u4e2d\u7684 ASP.NET Core \u7684\u6784\u5efa\u5047\u8bbe\u662f\u4e3a\u60a8\u7684\u9879\u76ee\u542f\u7528\u4e86 NRT\uff08\u5e76\u4e14\u5b83\u4eec\u5728\u6240\u6709\u6a21\u677f\u4e2d\u90fd\u9ed8\u8ba4\u542f\u7528\uff09\uff0c\u56e0\u6b64\u503c\u5f97\u5c3d\u53ef\u80fd\u4f7f\u7528\u5b83\u4eec\u3002\u5982\u679c\u9009\u62e9\u663e\u5f0f\u7981\u7528 NRT\uff0c\u5219\u53ef\u80fd\u4f1a\u53d1\u73b0\u67d0\u4e9b\u7c7b\u578b\u610f\u5916\u5730\u6807\u8bb0\u4e3a optional\uff0c\u8fd9\u53ef\u80fd\u4f1a\u5bfc\u81f4\u4e00\u4e9b\u96be\u4ee5\u8c03\u8bd5\u7684\u9519\u8bef\u3002<\/p>\n<p><strong>Tip<\/strong> Keep NRTs enabled for your minimal API endpoints wherever possible. If you can\u2019t use them for your whole project, consider enabling them selectively in Program.cs (or wherever you add your endpoints) by adding #nullable enable to the top of the file.<br \/>\n<strong>\u63d0\u793a<\/strong> \u5c3d\u53ef\u80fd\u4e3a\u6700\u5c0f API \u7ec8\u7ed3\u70b9\u542f\u7528 NRT\u3002\u5982\u679c\u60a8\u65e0\u6cd5\u5728\u6574\u4e2a\u9879\u76ee\u4e2d\u4f7f\u7528\u5b83\u4eec\uff0c\u8bf7\u8003\u8651\u901a\u8fc7\u5728\u6587\u4ef6\u9876\u90e8\u6dfb\u52a0 #nullable enable \u6765\u6709\u9009\u62e9\u5730\u5728 Program.cs \u4e2d\u542f\u7528\u5b83\u4eec\uff08\u6216\u6dfb\u52a0\u7ec8\u7aef\u8282\u70b9\u7684\u4efb\u4f55\u4f4d\u7f6e\uff09\u3002<\/p>\n<p>The good news is that ASP.NET Core includes several analyzers built into the compiler to catch configuration problems like the ones described in this section. If you have an optional route parameter but forget to mark the corresponding handler parameter as optional, for example, integrated development environments (IDEs) such as Visual Studio will show a hint, as shown in figure 7.7, and you\u2019ll get a build warning. You can read more about the built-in analyzers at <a href=\"http:\/\/mng.bz\/4DMV\">http:\/\/mng.bz\/4DMV<\/a>.<br \/>\n\u597d\u6d88\u606f\u662f ASP.NET Core \u5305\u542b\u7f16\u8bd1\u5668\u4e2d\u5185\u7f6e\u7684\u591a\u4e2a\u5206\u6790\u5668\u6765\u6355\u83b7\u914d\u7f6e\u4e0e\u672c\u8282\u4e2d\u63cf\u8ff0\u7684\u95ee\u9898\u7c7b\u4f3c\u3002\u4f8b\u5982\uff0c\u5982\u679c\u4f60\u6709\u4e00\u4e2a\u53ef\u9009\u7684 route \u53c2\u6570\uff0c\u4f46\u5fd8\u8bb0\u5c06\u76f8\u5e94\u7684 handler \u53c2\u6570\u6807\u8bb0\u4e3a\u53ef\u9009\uff0c\u5219\u96c6\u6210\u5f00\u53d1\u73af\u5883 \uff08IDE\uff09\uff08\u5982 Visual Studio\uff09\u5c06\u663e\u793a\u4e00\u4e2a\u63d0\u793a\uff0c\u5982\u56fe 7.7 \u6240\u793a\uff0c\u5e76\u4e14\u4f60\u5c06\u6536\u5230\u751f\u6210\u8b66\u544a\u3002\u60a8\u53ef\u4ee5\u5728 <a href=\"http:\/\/mng.bz\/4DMV\">http:\/\/mng.bz\/4DMV<\/a> \u4e0a\u9605\u8bfb\u6709\u5173\u5185\u7f6e\u5206\u6790\u5668\u7684\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0707.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 7.7 Visual Studio and other IDEs use analyzers to detect potential problems with mismatched optionality.<br \/>\n\u56fe 7.7 Visual Studio \u548c\u5176\u4ed6 IDE \u4f7f\u7528\u5206\u6790\u5668\u6765\u68c0\u6d4b\u53ef\u9009\u6027\u4e0d\u5339\u914d\u7684\u6f5c\u5728\u95ee\u9898\u3002<\/p>\n<p>Making your handler parameters optional is one of the approaches you can take, whether they\u2019re bound to route parameters, headers, or the query string. Alternatively, you can provide a default value for the parameter as part of the method signature. You can\u2019t provide default values for parameters in lambda functions in C#, so the following listing shows how to use a local function instead.<\/p>\n<p>\u5c06\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u8bbe\u4e3a\u53ef\u9009\u662f\u60a8\u53ef\u4ee5\u91c7\u7528\u7684\u65b9\u6cd5\u4e4b\u4e00\uff0c\u65e0\u8bba\u5b83\u4eec\u662f\u7ed1\u5b9a\u5230\u8def\u7531\u53c2\u6570\u3001\u6807\u5934\u8fd8\u662f\u67e5\u8be2\u5b57\u7b26\u4e32\u3002\u6216\u8005\uff0c\u60a8\u53ef\u4ee5\u4e3a\u53c2\u6570\u63d0\u4f9b\u9ed8\u8ba4\u503c\u4f5c\u4e3a\u65b9\u6cd5\u7b7e\u540d\u7684\u4e00\u90e8\u5206\u3002\u5728 C# 11,2 \u4e2d\uff0c\u60a8\u65e0\u6cd5\u4e3a lambda \u51fd\u6570\u4e2d\u7684\u53c2\u6570\u63d0\u4f9b\u9ed8\u8ba4\u503c\uff0c\u56e0\u6b64\u4ee5\u4e0b\u6e05\u5355\u663e\u793a\u4e86\u5982\u4f55\u6539\u7528\u672c\u5730\u51fd\u6570\u3002<\/p>\n<p>Listing 7.6 Using default values for parameters in endpoint handlers<br \/>\n\u6e05\u5355 7.6 \u5bf9 \u4e2d\u7684\u53c2\u6570\u4f7f\u7528\u9ed8\u8ba4\u503c\u7aef\u70b9\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;\/stock&quot;, StockWithDefaultValue);                     \/\/ \u2776\n\napp.Run();\n\nstring StockWithDefaultValue(int id = 0) =&gt; $&quot;Received {id}&quot;;    \/\/ \u2777\n<\/pre>\n<p>\u2776 The local function StockWithDefaultValue is the endpoint handler.<br \/>\n\u672c\u5730\u51fd\u6570 StockWithDefaultValue \u662f\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u3002<\/p>\n<p>\u2777 The id parameter binds to the query string value if it\u2019s available; otherwise, ithas the value 0.<br \/>\nid \u53c2\u6570\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\uff08\u5982\u679c\u53ef\u7528\uff09;\u5426\u5219\uff0c\u5176\u503c\u4e3a 0\u3002<\/p>\n<p>We\u2019ve thoroughly covered the differences between simple types and complex types and how they bind. In section 7.6 we look at some special types that don\u2019t follow these rules.<\/p>\n<p>\u6211\u4eec\u5df2\u7ecf\u5f7b\u5e95\u4ecb\u7ecd\u4e86\u7b80\u5355\u7c7b\u578b\u548c\u590d\u6742\u7c7b\u578b\u4e4b\u95f4\u7684\u533a\u522b\u4ee5\u53ca\u5b83\u4eec\u5982\u4f55\u7ed1\u5b9a\u3002\u5728 7.6 \u8282 \u4e2d\uff0c\u6211\u4eec\u770b\u4e86\u4e00\u4e9b\u4e0d\u9075\u5faa\u8fd9\u4e9b\u89c4\u5219\u7684\u7279\u6b8a\u7c7b\u578b\u3002<\/p>\n<h2>7.6 Binding services and special types<\/h2>\n<h2>7.6 \u7ed1\u5b9a\u670d\u52a1\u548c\u7279\u6b8a\u7c7b\u578b<\/h2>\n<p>In this section you\u2019ll learn how to use some of the special types that you can bind to in your endpoint handlers. By special, I mean types that ASP.NET Core is hardcoded to understand or that aren\u2019t created from the details of the request, by contrast with the binding you\u2019ve seen so far. The section looks at three types of parameters:<\/p>\n<p>\u5728\u672c\u8282\u4e2d\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u4f7f\u7528\u53ef\u5728\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u7ed1\u5b9a\u5230\u7684\u4e00\u4e9b\u7279\u6b8a\u7c7b\u578b\u3002\u6211\u6240\u8bf4\u7684\u7279\u6b8a\u662f\u6307 ASP.NET Core \u7ecf\u8fc7\u786c\u7f16\u7801\u4ee5\u7406\u89e3\u7684\u7c7b\u578b\uff0c\u6216\u8005\u4e0d\u662f\u6839\u636e\u8bf7\u6c42\u7684\u8be6\u7ec6\u4fe1\u606f\u521b\u5efa\u7684\u7c7b\u578b\uff0c\u8fd9\u4e0e\u4f60\u76ee\u524d\u770b\u5230\u7684\u7ed1\u5b9a\u5f62\u6210\u5bf9\u6bd4\u3002\u672c\u8282\u4ecb\u7ecd\u4e09\u79cd\u7c7b\u578b\u7684\u53c2\u6570\uff1a<\/p>\n<ul>\n<li>\n<p>Well-known types\u2014that is, hard-coded types that ASP.NET Core knows about, such as HttpContext and HttpRequest<br \/>\n\u5df2\u77e5\u7c7b\u578b \u2014 \u5373 ASP.NET Core \u77e5\u9053\u7684\u786c\u7f16\u7801\u7c7b\u578b\uff0c\u4f8b\u5982 HttpContext \u548c HttpRequest<\/p>\n<\/li>\n<li>\n<p>IFormCollection and IFormFile for working with form data<br \/>\nIFormFileCollection \u548c IFormFile\uff0c\u7528\u4e8e\u5904\u7406\u6587\u4ef6\u4e0a\u4f20<\/p>\n<\/li>\n<li>\n<p>Application services registered in WebApplicationBuilder.Services<br \/>\n\u6ce8\u518c\u7684\u5e94\u7528\u7a0b\u5e8f\u670d\u52a1WebApplicationBuilder.Services<\/p>\n<\/li>\n<\/ul>\n<p>We start by looking at the well-known types you can bind to.<br \/>\n\u6211\u4eec\u9996\u5148\u67e5\u770b\u53ef\u4ee5\u7ed1\u5b9a\u5230\u7684\u5df2\u77e5\u7c7b\u578b\u3002<\/p>\n<h3>7.6.1 Injecting well-known types<\/h3>\n<h3>7.6.1 \u6ce8\u5165\u5df2\u77e5\u7c7b\u578b<\/h3>\n<p>Throughout this book you\u2019ve seen examples of several well-known types that you can inject into your endpoint handlers, the most notable one being HttpContext. The remaining well-known types provide shortcuts for accessing various properties of the HttpContext object.<\/p>\n<p>\u5728\u672c\u4e66\u4e2d\uff0c\u60a8\u5df2\u7ecf\u770b\u5230\u4e86\u51e0\u79cd\u5df2\u77e5\u7c7b\u578b\u7684\u793a\u4f8b\uff0c\u60a8\u53ef\u4ee5\u5c06\u8fd9\u4e9b\u7c7b\u578b\u6ce8\u5165\u5230\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\uff0c\u5176\u4e2d\u6700\u8457\u540d\u7684\u662f HttpContext\u3002\u5176\u4f59\u5df2\u77e5\u7c7b\u578b\u63d0\u4f9b\u4e86\u7528\u4e8e\u8bbf\u95ee HttpContext \u5bf9\u8c61\u7684\u5404\u79cd\u5c5e\u6027\u7684\u5feb\u6377\u65b9\u5f0f\u3002<\/p>\n<p><strong>Note<\/strong> As described in chapter 3, HttpContext acts as a storage box for everything related to a single a request. It contains access to all the low-level details about the request and the response, plus any application services and features you might need.<br \/>\n<strong>\u6ce8\u610f<\/strong> \u5982\u7b2c 3 \u7ae0\u6240\u8ff0\uff0cHttpContext \u5145\u5f53\u4e0e\u5355\u4e2a a \u8bf7\u6c42\u76f8\u5173\u7684\u6240\u6709\u5185\u5bb9\u7684\u5b58\u50a8\u76d2\u3002\u5b83\u5305\u542b\u5bf9\u6709\u5173\u8bf7\u6c42\u548c\u54cd\u5e94\u7684\u6240\u6709\u4f4e\u7ea7\u8be6\u7ec6\u4fe1\u606f\u7684\u8bbf\u95ee\uff0c\u4ee5\u53ca\u60a8\u53ef\u80fd\u9700\u8981\u7684\u4efb\u4f55\u5e94\u7528\u7a0b\u5e8f\u670d\u52a1\u548c\u529f\u80fd\u3002<\/p>\n<p>You can use a well-known type in your endpoint handler by including a parameter of the appropriate type. To access the HttpContext in your handler, for example, you could use<\/p>\n<p>\u60a8\u53ef\u4ee5\u901a\u8fc7\u5305\u542b\u76f8\u5e94\u7c7b\u578b\u7684\u53c2\u6570\uff0c\u5728\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u4f7f\u7528\u5df2\u77e5\u7c7b\u578b\u3002\u4f8b\u5982\uff0c\u8981\u5728\u5904\u7406\u7a0b\u5e8f\u4e2d\u8bbf\u95ee HttpContext\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapGet(&quot;\/&quot;, (HttpContext context) =&gt; &quot;Hello world!&quot;);\n<\/pre>\n<p>You can use the following well-known types in your minimal API endpoint handlers:<\/p>\n<p>\u60a8\u53ef\u4ee5\u5728\u6700\u5c0f API \u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u4f7f\u7528\u4ee5\u4e0b\u5df2\u77e5\u7c7b\u578b\uff1a<\/p>\n<ul>\n<li>HttpContext\u2014This type contains all the details on both the request and the response. You can access everything you need from here, but often, an easier way to access the common properties is to use one of the other well-known types.<br \/>\nHttpContext \u2014 \u6b64\u7c7b\u578b\u5305\u542b\u6709\u5173\u8bf7\u6c42\u548c\u54cd\u5e94\u7684\u6240\u6709\u8be6\u7ec6\u4fe1\u606f\u3002\u60a8\u53ef\u4ee5\u4ece\u6b64\u5904\u8bbf\u95ee\u6240\u9700\u7684\u4e00\u5207\uff0c\u4f46\u901a\u5e38\uff0c\u8bbf\u95ee\u516c\u5171\u5c5e\u6027\u7684\u4e00\u79cd\u66f4\u7b80\u5355\u7684\u65b9\u6cd5\u662f\u4f7f\u7528\u5176\u4ed6\u5df2\u77e5\u7c7b\u578b\u4e4b\u4e00\u3002<\/li>\n<li>HttpRequest\u2014Equivalent to the property HttpContext.Request, this type contains all the details about the request only.<br \/>\nHttpRequest - \u7b49\u6548\u4e8e\u5c5e\u6027 HttpContext.Request\uff0c\u6b64\u7c7b\u578b\u4ec5\u5305\u542b\u6709\u5173\u8bf7\u6c42\u7684\u6240\u6709\u8be6\u7ec6\u4fe1\u606f\u3002<\/li>\n<li>HttpResponse\u2014Equivalent to the property HttpContext.Response, this type contains all the details about the response only.<br \/>\nHttpResponse \u2014 \u7b49\u6548\u4e8e\u5c5e\u6027 HttpContext.Response\uff0c\u6b64\u7c7b\u578b\u4ec5\u5305\u542b\u6709\u5173\u54cd\u5e94\u7684\u6240\u6709\u8be6\u7ec6\u4fe1\u606f\u3002<\/li>\n<li>CancellationToken\u2014Equivalent to the property HttpContext.RequestAborted, this token is canceled if the client aborts the request. It\u2019s useful if you need to cancel a long-running task, as described in my post at <a href=\"http:\/\/mng.bz\/QP2j\">http:\/\/mng.bz\/QP2j<\/a>.<br \/>\nCancellationToken - \u7b49\u6548\u4e8e\u5c5e\u6027 HttpContext.RequestAborted\uff0c\u5982\u679c\u5ba2\u6237\u7aef\u4e2d\u6b62\u8bf7\u6c42\uff0c\u5219\u6b64\u4ee4\u724c\u5c06\u88ab\u53d6\u6d88\u3002\u5982\u679c\u60a8\u9700\u8981\u53d6\u6d88\u957f\u65f6\u95f4\u8fd0\u884c\u7684\u4efb\u52a1\uff0c\u8fd9\u5f88\u6709\u7528\uff0c\u5982\u6211\u5728 <a href=\"http:\/\/mng.bz\/QP2j\">http:\/\/mng.bz\/QP2j<\/a> \u4e0a\u7684\u5e16\u5b50\u4e2d\u6240\u8ff0\u3002<\/li>\n<li>ClaimsPrincipal\u2014Equivalent to the property HttpContext.User, this type contains authentication information about the user. You\u2019ll learn more about authentication in chapter 23.<br \/>\nClaimsPrincipal \u2014 \u7b49\u6548\u4e8e\u5c5e\u6027 HttpContext.User\uff0c\u6b64\u7c7b\u578b\u5305\u542b\u6709\u5173\u7528\u6237\u7684\u8eab\u4efd\u9a8c\u8bc1\u4fe1\u606f\u3002\u60a8\u5c06\u5728\u7b2c 23 \u7ae0\u4e2d\u4e86\u89e3\u6709\u5173\u8eab\u4efd\u9a8c\u8bc1\u7684\u66f4\u591a\u4fe1\u606f\u3002<\/li>\n<li>Stream\u2014Equivalent to the property HttpRequest.Body, this parameter is a reference to the Stream object of the request. This parameter can be useful for scenarios in which you need to process large amounts of data from a request efficiently, without holding it all in memory at the same time.<br \/>\nStream \u2014 \u7b49\u6548\u4e8e\u5c5e\u6027 HttpRequest.Body\uff0c\u6b64\u53c2\u6570\u662f\u5bf9\u8bf7\u6c42\u7684 Stream \u5bf9\u8c61\u7684\u5f15\u7528\u3002\u6b64\u53c2\u6570\u53ef\u7528\u4e8e\u9700\u8981\u9ad8\u6548\u5904\u7406\u8bf7\u6c42\u4e2d\u7684\u5927\u91cf\u6570\u636e\uff0c\u800c\u65e0\u9700\u540c\u65f6\u5c06\u5176\u5168\u90e8\u4fdd\u5b58\u5728\u5185\u5b58\u4e2d\u7684\u65b9\u6848\u3002<\/li>\n<li>PipeReader\u2014Equivalent to the property HttpContext.BodyReader, PipeReader provides a higher-level API compared with Stream, but it\u2019s useful in similar scenarios. You can read more about PipeReader and the System.IO.Pipelines namespace at <a href=\"http:\/\/mng.bz\/XNY6\">http:\/\/mng.bz\/XNY6<\/a>.<br \/>\nPipeReader \u2014 \u76f8\u5f53\u4e8e\u5c5e\u6027 HttpContext.BodyReader\uff0c\u4e0e Stream \u76f8\u6bd4\uff0cPipeReader \u63d0\u4f9b\u4e86\u66f4\u9ad8\u7ea7\u522b\u7684 API\uff0c\u4f46\u5b83\u5728\u7c7b\u4f3c\u60c5\u51b5\u4e0b\u5f88\u6709\u7528\u3002\u60a8\u53ef\u4ee5\u5728 <a href=\"http:\/\/mng.bz\/XNY6\">http:\/\/mng.bz\/XNY6<\/a> \u4e2d\u9605\u8bfb\u6709\u5173 PipeReader \u548c System.IO.Pipelines \u547d\u540d\u7a7a\u95f4\u7684\u66f4\u591a\u4fe1\u606f\u3002<\/li>\n<\/ul>\n<p>You can access each of the latter well-known types by navigating via an injected HttpContext object if you prefer. But injecting the exact object you need generally makes for code that\u2019s easier to read.<\/p>\n<p>\u5982\u679c\u4f60\u613f\u610f\uff0c\u4f60\u53ef\u4ee5\u901a\u8fc7\u901a\u8fc7\u63d2\u5165\u7684 HttpContext \u5bf9\u8c61\u5bfc\u822a\u6765\u8bbf\u95ee\u540e\u4e00\u79cd\u5df2\u77e5\u7c7b\u578b \u3002\u4f46\u662f\uff0c\u6ce8\u5165\u6240\u9700\u7684\u786e\u5207\u5bf9\u8c61\u901a\u5e38\u4f1a\u4f7f\u4ee3\u7801\u66f4\u6613\u4e8e\u9605\u8bfb\u3002<\/p>\n<h3>7.6.2 Injecting services<\/h3>\n<h3>7.6.2 \u6ce8\u5165\u670d\u52a1<\/h3>\n<p>I\u2019ve mentioned several times in this book that you need to configure various core services to work with ASP.NET Core. Many services are registered automatically, but often, you must add more to use extra features, such as when you called AddHttpLogging() in chapter 3 to add request logging to your pipeline.<\/p>\n<p>\u6211\u5728\u672c\u4e66\u4e2d\u591a\u6b21\u63d0\u5230\uff0c\u60a8\u9700\u8981\u914d\u7f6e\u5404\u79cd\u6838\u5fc3\u670d\u52a1\u624d\u80fd\u4e0e ASP.NET Core \u914d\u5408\u4f7f\u7528\u3002\u8bb8\u591a\u670d\u52a1\u662f\u81ea\u52a8\u6ce8\u518c\u7684\uff0c\u4f46\u901a\u5e38\uff0c\u60a8\u5fc5\u987b\u6dfb\u52a0\u66f4\u591a\u670d\u52a1\u624d\u80fd\u4f7f\u7528\u989d\u5916\u7684\u529f\u80fd\uff0c\u4f8b\u5982\u5f53\u60a8\u5728\u7b2c 3 \u7ae0\u4e2d\u8c03\u7528 AddHttpLogging\uff08\uff09 \u4ee5\u5c06\u8bf7\u6c42\u65e5\u5fd7\u8bb0\u5f55\u6dfb\u52a0\u5230\u60a8\u7684\u7ba1\u9053\u65f6\u3002<\/p>\n<p><strong>Note<\/strong> Adding services to your application involves registering them with a dependency injection (DI) container. You\u2019ll learn all about DI and registering services in chapters 8 and 9.<br \/>\n<strong>\u6ce8\u610f\uff1a<\/strong> \u5411\u5e94\u7528\u7a0b\u5e8f\u6dfb\u52a0\u670d\u52a1\u6d89\u53ca\u5411\u4f9d\u8d56\u5173\u7cfb\u6ce8\u5165 \uff08DI\uff09 \u5bb9\u5668\u6ce8\u518c\u670d\u52a1\u3002\u60a8\u5c06\u5728\u7b2c 8 \u7ae0\u548c\u7b2c 9 \u7ae0\u4e2d\u4e86\u89e3\u6709\u5173 DI \u548c\u6ce8\u518c\u670d\u52a1\u7684\u6240\u6709\u4fe1\u606f\u3002<\/p>\n<p>You can automatically use any registered service in your endpoint handlers, and ASP.NET Core will inject an instance of the service from the DI container. You saw an example in chapter 6 when you used the LinkGenerator service in an endpoint handler. LinkGenerator is one of the core services registered by WebApplicationBuilder, so it\u2019s always available, as shown in the following listing.<\/p>\n<p>\u60a8\u53ef\u4ee5\u5728\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u81ea\u52a8\u4f7f\u7528\u4efb\u4f55\u5df2\u6ce8\u518c\u7684\u670d\u52a1\uff0cASP.NET Core \u5c06\u4ece DI \u5bb9\u5668\u6ce8\u5165\u670d\u52a1\u5b9e\u4f8b\u3002\u60a8\u5728\u7b2c 6 \u7ae0\u4e2d\u770b\u5230\u4e86\u4e00\u4e2a\u793a\u4f8b\uff0c\u5f53\u65f6\u60a8\u5728endpoint \u5904\u7406\u7a0b\u5e8f\u3002LinkGenerator \u662f WebApplicationBuilder \u6ce8\u518c\u7684\u6838\u5fc3\u670d\u52a1\u4e4b\u4e00\uff0c\u56e0\u6b64\u5b83\u59cb\u7ec8\u53ef\u7528\uff0c\u5982\u4e0b\u9762\u7684\u6e05\u5355\u6240\u793a\u3002<\/p>\n<p>Listing 7.7 Using the LinkGenerator service in an endpoint handler<br \/>\n\u6e05\u5355 7.7 \u5728\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapGet(&quot;\/links&quot;, (LinkGenerator links) =&gt;             \/\/ \u2776\n{\n    string link = links.GetPathByName(&quot;products&quot;);\n    return $&quot;View the product at {link}&quot;;\n});\n<\/pre>\n<p>\u2776 The LinkGenerator can be used as a parameter because it\u2019s available in the DI container.<br \/>\nLinkGenerator \u53ef\u4ee5\u7528\u4f5c\u53c2\u6570\uff0c\u56e0\u4e3a\u5b83\u5728 DI \u5bb9\u5668\u4e2d\u53ef\u7528\u3002<\/p>\n<p>Minimal APIs can automatically detect when a service is available in the DI container, but if you want to be explicit, you can also decorate your parameters with the [FromServices] attribute:<\/p>\n<p>\u6700\u5c0f\u7684 API \u53ef\u4ee5\u81ea\u52a8\u68c0\u6d4b DI \u5bb9\u5668\u4e2d\u4f55\u65f6\u6709\u53ef\u7528\u7684\u670d\u52a1\uff0c\u4f46\u5982\u679c\u8981\u663e\u5f0f\uff0c\u8fd8\u53ef\u4ee5\u4f7f\u7528 [FromServices] \u5c5e\u6027\u4fee\u9970\u53c2\u6570\uff1a<\/p>\n<p>app.MapGet(&quot;\/links&quot;, ([FromServices] LinkGenerator links) =&gt;<\/p>\n<p>[FromServices] may be necessary in some rare cases if you\u2019re using a custom DI container that doesn\u2019t support the APIs used by minimal APIs. But generally, I find that I can keep endpoints readable by avoiding the [From*] attributes wherever possible and relying on minimal APIs to do the right thing automatically.<\/p>\n<p>\u5728\u6781\u5c11\u6570\u60c5\u51b5\u4e0b\uff0c\u5982\u679c\u4f60\u4f7f\u7528\u7684\u81ea\u5b9a\u4e49 DI \u5bb9\u5668\u4e0d\u652f\u6301\u6700\u5c0f API \u4f7f\u7528\u7684 API\uff0c\u5219\u53ef\u80fd\u9700\u8981 [FromServices]\u3002\u4f46\u603b\u7684\u6765\u8bf4\uff0c\u6211\u53d1\u73b0\u6211\u53ef\u4ee5\u901a\u8fc7\u5c3d\u53ef\u80fd\u907f\u514d\u4f7f\u7528 [From*] \u5c5e\u6027\u5e76\u4f9d\u9760\u6700\u5c11\u7684 API \u6765\u81ea\u52a8\u6267\u884c\u6b63\u786e\u7684\u4f5c\uff0c\u4ece\u800c\u4fdd\u6301\u7aef\u70b9\u7684\u53ef\u8bfb\u6027\u3002<\/p>\n<h3>7.6.3 Binding file uploads with IFormFile and IFormFileCollection<\/h3>\n<h3>7.6.3 \u4f7f\u7528 IFormFile \u548c IFormFileCollection \u7ed1\u5b9a\u6587\u4ef6\u4e0a\u4f20<\/h3>\n<p>A common feature of many websites is the ability to upload files. This activity could be relatively infrequent, such as a user\u2019s uploading a profile picture to their Stack Overflow profile, or it may be integral to the application, such as uploading photos to Facebook.<\/p>\n<p>\u8bb8\u591a\u7f51\u7ad9\u7684\u4e00\u4e2a\u5171\u540c\u7279\u70b9\u662f\u80fd\u591f\u4e0a\u4f20\u6587\u4ef6\u3002\u6b64\u6d3b\u52a8\u53ef\u80fd\u76f8\u5bf9\u4e0d\u9891\u7e41\uff0c\u4f8b\u5982\u7528\u6237\u5c06\u4e2a\u4eba\u8d44\u6599\u56fe\u7247\u4e0a\u4f20\u5230\u5176 Stack Overflow \u4e2a\u4eba\u8d44\u6599\uff0c\u4e5f\u53ef\u80fd\u662f\u5e94\u7528\u7a0b\u5e8f\u4e0d\u53ef\u6216\u7f3a\u7684\u4e00\u90e8\u5206\uff0c\u4f8b\u5982\u5c06\u7167\u7247\u4e0a\u4f20\u5230 Facebook\u3002<\/p>\n<blockquote>\n<p>Letting users upload files to your application<br \/>\n\u5141\u8bb8\u7528\u6237\u5c06\u6587\u4ef6\u4e0a\u4f20\u5230\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f<br \/>\nUploading files to websites is a common activity, but you should consider carefully whether your application needs that ability. Whenever users can upload files, the situation is fraught with danger.<br \/>\n\u5c06\u6587\u4ef6\u4e0a\u4f20\u5230 Web \u7ad9\u70b9\u662f\u4e00\u9879\u5e38\u89c1\u7684\u6d3b\u52a8\uff0c\u4f46\u60a8\u5e94\u8be5\u4ed4\u7ec6\u8003\u8651\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u662f\u5426\u9700\u8981\u8be5\u529f\u80fd\u3002\u53ea\u8981\u7528\u6237\u53ef\u4ee5\u4e0a\u4f20\u6587\u4ef6\uff0c\u60c5\u51b5\u5c31\u5145\u6ee1\u4e86\u5371\u9669\u3002<br \/>\nYou should be careful to treat the incoming files as potentially malicious. Don\u2019t trust the filename provided, take care of large files being uploaded, and don\u2019t allow the files to be executed on your server.<br \/>\n\u5e94\u5c0f\u5fc3\u5c06\u4f20\u5165\u6587\u4ef6\u89c6\u4e3a\u6f5c\u5728\u6076\u610f\u6587\u4ef6\u3002\u4e0d\u8981\u76f8\u4fe1\u63d0\u4f9b\u7684\u6587\u4ef6\u540d\uff0c\u6ce8\u610f\u4e0a\u4f20\u7684\u5927\u6587\u4ef6\uff0c\u5e76\u4e14\u4e0d\u5141\u8bb8\u5728\u60a8\u7684\u670d\u52a1\u5668\u4e0a\u6267\u884c\u8fd9\u4e9b\u6587\u4ef6\u3002<br \/>\nFiles also raise questions about where the data should be stored: in a database, in the filesystem, or in some other storage? None of these questions has a straightforward answer, and you should think hard about the implications of choosing one over the other. Better, don\u2019t let users upload files if you don\u2019t have to!<br \/>\n\u6587\u4ef6\u8fd8\u5f15\u53d1\u4e86\u5173\u4e8e\u6570\u636e\u5e94\u8be5\u5b58\u50a8\u5728\u54ea\u91cc\u7684\u95ee\u9898\uff1a\u5728\u6570\u636e\u5e93\u4e2d\u3001\u5728\u6587\u4ef6\u7cfb\u7edf\u4e2d\uff0c\u8fd8\u662f\u5728\u5176\u4ed6\u5b58\u50a8\u4e2d\uff1f\u8fd9\u4e9b\u95ee\u9898\u90fd\u6ca1\u6709\u76f4\u63a5\u7684\u7b54\u6848\uff0c\u60a8\u5e94\u8be5\u8ba4\u771f\u8003\u8651\u9009\u62e9\u4e00\u4e2a\u800c\u4e0d\u662f\u53e6\u4e00\u4e2a\u7684\u5f71\u54cd\u3002\u66f4\u597d\u7684\u662f\uff0c\u5982\u679c\u4e0d\u9700\u8981\uff0c\u8bf7\u4e0d\u8981\u8ba9\u7528\u6237\u4e0a\u4f20\u6587\u4ef6\uff01<\/p>\n<\/blockquote>\n<p>ASP.NET Core supports uploading files by exposing the IFormFile interface. You can use this interface in your endpoint handlers, and it will be populated with the details of the file upload:<\/p>\n<p>ASP.NET Core \u652f\u6301\u901a\u8fc7\u516c\u5f00 IFormFile \u63a5\u53e3\u6765\u4e0a\u4f20\u6587\u4ef6\u3002\u60a8\u53ef\u4ee5\u5728\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u4f7f\u7528\u6b64\u63a5\u53e3\uff0c\u5b83\u5c06\u586b\u5145\u6587\u4ef6\u4e0a\u4f20\u7684\u8be6\u7ec6\u4fe1\u606f\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/upload&quot;, (IFormFile file) =&gt; {});<\/code><\/pre>\n<p>You can also use an IFormFileCollection if you need to accept multiple files:<br \/>\n\u5982\u679c\u9700\u8981\u63a5\u53d7\u591a\u4e2a\u6587\u4ef6\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528 IFormFileCollection\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapGet(&quot;\/upload&quot;, (IFormFileCollection files) =&gt;\n{\n    foreach (IFormFile file in files)\n    {\n    }\n});\n<\/pre>\n<p>The IFormFile object exposes several properties and utility methods for reading the contents of the uploaded file, some of which are shown here:<br \/>\nIFormFile \u5bf9\u8c61\u516c\u5f00\u4e86\u51e0\u4e2a\u7528\u4e8e\u8bfb\u53d6\u4e0a\u8f7d\u6587\u4ef6\u5185\u5bb9\u7684\u5c5e\u6027\u548c\u5b9e\u7528\u7a0b\u5e8f\u65b9\u6cd5\uff0c\u5176\u4e2d\u4e00\u4e9b\u65b9\u6cd5\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic interface IFormFile\n{\n    string ContentType { get; }\n    long Length { get; }\n    string FileName { get; }\n    Stream OpenReadStream();\n}\n<\/pre>\n<p>As you can see, this interface exposes a FileName property, which returns the filename that the file was uploaded with. But you know not to trust users, right? You should never use the filename directly in your code; users can use it to attack your website and access files that they shouldn\u2019t. Always generate a new name for the file before you save it anywhere.<\/p>\n<p>\u5982\u60a8\u6240\u89c1\uff0c\u6b64\u63a5\u53e3\u516c\u5f00\u4e86\u4e00\u4e2a FileName \u5c5e\u6027\uff0c\u8be5\u5c5e\u6027\u8fd4\u56de\u4e0a\u4f20\u6587\u4ef6\u65f6\u4f7f\u7528\u7684\u6587\u4ef6\u540d\u3002\u4f46\u60a8\u77e5\u9053\u4e0d\u8981\u76f8\u4fe1\u7528\u6237\uff0c\u5bf9\u5427\uff1f\u5207\u52ff\u5728\u4ee3\u7801\u4e2d\u76f4\u63a5\u4f7f\u7528\u6587\u4ef6\u540d;\u7528\u6237\u53ef\u4ee5\u4f7f\u7528\u5b83\u6765\u653b\u51fb\u60a8\u7684\u7f51\u7ad9\u5e76\u8bbf\u95ee\u4ed6\u4eec\u4e0d\u5e94\u8be5\u8bbf\u95ee\u7684\u6587\u4ef6\u3002\u5728\u5c06\u6587\u4ef6\u4fdd\u5b58\u5230\u4efb\u4f55\u4f4d\u7f6e\u4e4b\u524d\uff0c\u8bf7\u59cb\u7ec8\u4e3a\u6587\u4ef6\u751f\u6210\u65b0\u540d\u79f0\u3002<\/p>\n<p><strong>Warning<\/strong> There are lots of potential threats to consider when accepting file uploads from users. For more information, see <a href=\"http:\/\/mng.bz\/yQ9q\">http:\/\/mng.bz\/yQ9q<\/a>.<br \/>\n<strong>\u8b66\u544a<\/strong> \u5728\u63a5\u53d7\u7528\u6237\u4e0a\u4f20\u7684\u6587\u4ef6\u65f6\uff0c\u9700\u8981\u8003\u8651\u8bb8\u591a\u6f5c\u5728\u5a01\u80c1\u3002\u6709\u5173\u66f4\u591a\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605 <a href=\"http:\/\/mng.bz\/yQ9q\">http:\/\/mng.bz\/yQ9q<\/a>\u3002<\/p>\n<p>The IFormFile approach is fine if users are going to be uploading only small files. When your method accepts an IFormFile instance, the whole content of the file is buffered in memory and on disk before you receive it. Then you can use the OpenReadStream method to read the data out.<br \/>\n\u5982\u679c\u7528\u6237\u53ea\u4e0a\u4f20\u5c0f\u6587\u4ef6\uff0c\u5219 IFormFile \u65b9\u6cd5\u5f88\u597d\u3002\u5f53\u60a8\u7684\u65b9\u6cd5\u63a5\u53d7IFormFile \u5b9e\u4f8b\uff0c\u5219\u6587\u4ef6\u7684\u5168\u90e8\u5185\u5bb9\u5728\u60a8\u6536\u5230\u4e4b\u524d\u90fd\u4f1a\u7f13\u51b2\u5728\u5185\u5b58\u548c\u78c1\u76d8\u4e0a\u3002\u7136\u540e\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528 OpenReadStream \u65b9\u6cd5\u8bfb\u51fa\u6570\u636e\u3002<\/p>\n<p>If users post large files to your website, you may start to run out of space in memory or on disk as ASP.NET Core buffers each of the files. In that case, you may need to stream the files directly to avoid saving all the data at the same time. Unfortunately, unlike the model-binding approach, streaming large files can be complex and error-prone, so it\u2019s outside the scope of this book. For details, see Microsoft\u2019s documentation at <a href=\"http:\/\/mng.bz\/MBgn\">http:\/\/mng.bz\/MBgn<\/a>.<\/p>\n<p>\u5982\u679c\u7528\u6237\u5c06\u5927\u578b\u6587\u4ef6\u53d1\u5e03\u5230\u60a8\u7684\u7f51\u7ad9\uff0c\u60a8\u53ef\u80fd\u4f1a\u5f00\u59cb\u8017\u5c3d\u5185\u5b58\u6216\u78c1\u76d8\u4e2d\u7684\u7a7a\u95f4\uff0c\u56e0\u4e3a ASP.NET Core \u4f1a\u7f13\u51b2\u6bcf\u4e2a\u6587\u4ef6\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u60a8\u53ef\u80fd\u9700\u8981\u76f4\u63a5\u6d41\u5f0f\u4f20\u8f93\u6587\u4ef6\uff0c\u4ee5\u907f\u514d\u540c\u65f6\u4fdd\u5b58\u6240\u6709\u6570\u636e\u3002\u9057\u61be\u7684\u662f\uff0c\u4e0e\u6a21\u578b\u7ed1\u5b9a\u65b9\u6cd5\u4e0d\u540c\uff0c\u6d41\u5f0f\u5904\u7406\u5927\u6587\u4ef6\u53ef\u80fd\u5f88\u590d\u6742\u4e14\u5bb9\u6613\u51fa\u9519\uff0c\u56e0\u6b64\u4e0d\u5728\u672c\u4e66\u7684\u8ba8\u8bba\u8303\u56f4\u4e4b\u5185\u3002\u6709\u5173\u8be6\u7ec6\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605 Microsoft \u7684\u6587\u6863 <a href=\"http:\/\/mng.bz\/MBgn\">http:\/\/mng.bz\/MBgn<\/a>\u3002<\/p>\n<p><strong>Tip<\/strong> Don\u2019t use the IFormFile interface to handle large file uploads, as you may see performance problem. Be aware that you can\u2019t rely on users not to upload large files, so avoid file uploads when you can!<br \/>\n<strong>\u63d0\u793a<\/strong> \u4e0d\u8981\u4f7f\u7528 IFormFile \u63a5\u53e3\u6765\u5904\u7406\u5927\u6587\u4ef6\u4e0a\u4f20\uff0c\u56e0\u4e3a\u60a8\u53ef\u80fd\u4f1a\u770b\u5230\u6027\u80fd\u95ee\u9898\u3002\u8bf7\u6ce8\u610f\uff0c\u60a8\u4e0d\u80fd\u6307\u671b\u7528\u6237\u4e0d\u4e0a\u4f20\u5927\u6587\u4ef6\uff0c\u56e0\u6b64\u8bf7\u5c3d\u53ef\u80fd\u907f\u514d\u4e0a\u4f20\u6587\u4ef6\uff01<\/p>\n<p>For the vast majority of minimal API endpoints, the default configuration of model binding for simple and complex types works perfectly well. But you may find some situations in which you need to take a bit more control.<br \/>\n\u5bf9\u4e8e\u7edd\u5927\u591a\u6570\u6700\u5c0f API \u7aef\u70b9\uff0c\u7b80\u5355\u7c7b\u578b\u548c\u590d\u6742\u7c7b\u578b\u7684\u6a21\u578b\u7ed1\u5b9a\u7684\u9ed8\u8ba4\u914d\u7f6e\u8fd0\u884c\u826f\u597d\u3002\u4f46\u662f\u60a8\u53ef\u80fd\u4f1a\u53d1\u73b0\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\uff0c\u60a8\u9700\u8981\u91c7\u53d6\u66f4\u591a\u7684\u63a7\u5236\u63aa\u65bd\u3002<\/p>\n<h2>7.7 Custom binding with BindAsync<\/h2>\n<h2>7.7 \u4f7f\u7528 BindAsync \u7684\u81ea\u5b9a\u4e49\u7ed1\u5b9a<\/h2>\n<p>The model binding you get out of the box with minimal APIs covers most of the common situations that you\u2019ll run into when building HTTP APIs, but there are always a few edge cases in which you can\u2019t use it.<\/p>\n<p>\u4f7f\u7528\u6700\u5c11\u7684 API \u83b7\u5f97\u7684\u5f00\u7bb1\u5373\u7528\u7684\u6a21\u578b\u7ed1\u5b9a\u6db5\u76d6\u4e86\u60a8\u5c06\u9047\u5230\u7684\u5927\u591a\u6570\u5e38\u89c1\u60c5\u51b5\u3002\u5728\u6784\u5efa HTTP API \u65f6\uff0c\u4f46\u603b\u6709\u4e00\u4e9b\u8fb9\u7f18\u60c5\u51b5\u60a8\u65e0\u6cd5\u4f7f\u7528\u5b83\u3002<\/p>\n<p>You\u2019ve already seen that you can inject HttpContext into your endpoint handlers, so you have direct access to the request details in your handler, but often, you still want to encapsulate the logic for extracting the data you need. You can get the best of both worlds in minimal APIs by implementing BindAsync in your endpoint handler parameter types and taking advantage of completely custom model binding. To add custom binding for a parameter type, you must implement one of the following two static BindAsync methods in your type T:<\/p>\n<p>\u60a8\u5df2\u7ecf\u770b\u5230\uff0c\u60a8\u53ef\u4ee5\u5c06 HttpContext \u6ce8\u5165\u5230\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\uff0c\u56e0\u6b64\u60a8\u53ef\u4ee5\u76f4\u63a5\u8bbf\u95ee\u5904\u7406\u7a0b\u5e8f\u4e2d\u7684\u8bf7\u6c42\u8be6\u7ec6\u4fe1\u606f\uff0c\u4f46\u901a\u5e38\uff0c\u60a8\u4ecd\u7136\u5e0c\u671b\u5c01\u88c5\u7528\u4e8e\u63d0\u53d6\u6240\u9700\u6570\u636e\u7684\u903b\u8f91\u3002\u901a\u8fc7\u5728\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u7c7b\u578b\u4e2d\u5b9e\u73b0 BindAsync \u5e76\u5229\u7528\u5b8c\u5168\u81ea\u5b9a\u4e49\u7684\u6a21\u578b\u7ed1\u5b9a\uff0c\u60a8\u53ef\u4ee5\u5728\u6700\u5c11\u7684 API \u4e2d\u5b9e\u73b0\u4e24\u5168\u5176\u7f8e\u7684\u6548\u679c\u3002\u82e5\u8981\u4e3a\u53c2\u6570\u7c7b\u578b\u6dfb\u52a0\u81ea\u5b9a\u4e49\u7ed1\u5b9a\uff0c\u5fc5\u987b\u5728\u7c7b\u578b T \u4e2d\u5b9e\u73b0\u4ee5\u4e0b\u4e24\u4e2a\u9759\u6001 BindAsync \u65b9\u6cd5\u4e4b\u4e00\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic static ValueTask&lt;T?&gt; BindAsync(HttpContext context);\npublic static ValueTask&lt;T?&gt; BindAsync(\nHttpContext context, ParameterInfo parameter);\n<\/pre>\n<p>Both methods accept an HttpContext, so you can extract anything you need from the request. But the latter case also provides reflection details about the parameter you\u2019re binding. In most cases the simpler signature should be sufficient, but you never know!<\/p>\n<p>\u8fd9\u4e24\u79cd\u65b9\u6cd5\u90fd\u63a5\u53d7 HttpContext\uff0c\u56e0\u6b64\u60a8\u53ef\u4ee5\u4ece\u8bf7\u6c42\u4e2d\u63d0\u53d6\u6240\u9700\u7684\u4efb\u4f55\u5185\u5bb9\u3002\u4f46\u540e\u4e00\u79cd\u60c5\u51b5\u8fd8\u63d0\u4f9b\u4e86\u6709\u5173\u60a8\u6b63\u5728\u7ed1\u5b9a\u7684\u53c2\u6570\u7684\u53cd\u5c04\u8be6\u7ec6\u4fe1\u606f\u3002\u5728\u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u66f4\u7b80\u5355\u7684\u7b7e\u540d\u5e94\u8be5\u5c31\u8db3\u591f\u4e86\uff0c\u4f46\u60a8\u6c38\u8fdc\u4e0d\u77e5\u9053\uff01<\/p>\n<p>Listing 7.8 shows an example of using BindAsync to bind a record to the request body by using a custom format. The implementation shown in the listing assumes that the body contains two double values, with a line break between them, and if so, it successfully parses the SizeDetails object. If there are any problems along the way, it returns null.<\/p>\n<p>\u6e05\u5355 7.8 \u5c55\u793a\u4e86\u4e00\u4e2a\u4f7f\u7528 BindAsync \u901a\u8fc7\u81ea\u5b9a\u4e49\u683c\u5f0f\u5c06\u8bb0\u5f55\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6b63\u6587\u7684\u793a\u4f8b\u3002\u6e05\u5355\u4e2d\u663e\u793a\u7684\u5b9e\u73b0\u5047\u8bbe\u4e3b\u4f53\u5305\u542b\u4e24\u4e2a double \u503c\uff0c\u5b83\u4eec\u4e4b\u95f4\u6709\u4e00\u4e2a\u6362\u884c\u7b26\uff0c\u5982\u679c\u662f\u8fd9\u6837\uff0c\u5b83\u5c06\u6210\u529f\u89e3\u6790 SizeDetails \u5bf9\u8c61\u3002\u5982\u679c\u5728\u6b64\u8fc7\u7a0b\u4e2d\u51fa\u73b0\u4efb\u4f55\u95ee\u9898\uff0c\u5b83\u5c06\u8fd4\u56de null\u3002<\/p>\n<p>Listing 7.8 Using BindAsync for custom model binding<br \/>\n\u6e05\u5355 7.8 \u4f7f\u7528 BindAsync \u8fdb\u884c\u81ea\u5b9a\u4e49\u6a21\u578b\u7ed1\u5b9a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\napp.MapPost(&quot;\/sizes&quot;, (SizeDetails size) =&gt; $&quot;Received {size}&quot;);                    \/\/ \u2776\n\napp.Run();\n\npublic record SizeDetails(double height, double width)                              \/\/ \u2777\n{                                                                                   \/\/ \u2777\n    public static async ValueTask&lt;SizeDetails?&gt; BindAsync(                          \/\/ \u2777\n        HttpContext context)                                                        \/\/ \u2777\n    {\n    using var sr = new StreamReader(context.Request.Body);                          \/\/ \u2778\n\n        string? line1 = await sr.ReadLineAsync(context.RequestAborted);             \/\/ \u2779\n        if (line1 is null) { return null; }                                         \/\/ \u277a\n\n        string? line2 = await sr.ReadLineAsync(context.RequestAborted);             \/\/ \u2779\n        if (line2 is null) { return null; }                                         \/\/ \u277a\n\n        return double.TryParse(line1, out double height)                            \/\/ \u277b\n            &amp;&amp; double.TryParse(line2, out double width)                             \/\/ \u277b\n            ? new SizeDetails(height, width)                                        \/\/ \u277c\n            : null;                                                                 \/\/ \u277d\n    }\n}\n<\/pre>\n<p>\u2776 No extra attributes are needed for the SizeDetails parameter, as it has a BindAsync method.<br \/>\nSizeDetails \u53c2\u6570\u4e0d\u9700\u8981\u989d\u5916\u7684\u5c5e\u6027\uff0c\u56e0\u4e3a\u5b83\u5177\u6709 BindAsync \u65b9\u6cd5\u3002<\/p>\n<p>\u2777 SizeDetails implements the static BindAsync method.<br \/>\nSizeDetails \u5b9e\u73b0\u9759\u6001 BindAsync \u65b9\u6cd5\u3002<\/p>\n<p>\u2778 Creates a StreamReader to read the request body<br \/>\n\u521b\u5efa\u4e00\u4e2a StreamReader \u6765\u8bfb\u53d6\u8bf7\u6c42\u6b63\u6587<\/p>\n<p>\u2779 Reads a line of text from the body<br \/>\n\u4ece\u6b63\u6587\u4e2d\u8bfb\u53d6\u4e00\u884c\u6587\u672c<\/p>\n<p>\u277a If either line is null, indicating no content, stops processing<br \/>\n\u5982\u679c\u4efb\u4e00\u884c\u4e3a null\uff0c\u5219\u8868\u793a\u65e0\u5185\u5bb9\uff0c\u5219\u505c\u6b62\u5904\u7406<\/p>\n<p>\u277b Tries to parse the two lines as doubles<br \/>\n\u5c1d\u8bd5\u5c06\u8fd9\u4e24\u884c\u89e3\u6790\u4e3a\u53cc\u7cbe\u5ea6<\/p>\n<p>\u277c If the parsing is successful, creates the SizeDetails model and returns it . . .<br \/>\n\u5982\u679c\u89e3\u6790\u6210\u529f\uff0c\u5219\u521b\u5efa SizeDetails \u6a21\u578b\u5e76\u5c06\u5176\u8fd4\u56de . . .<\/p>\n<p>\u277d . . . otherwise, returns null<br \/>\n. . . .\u5426\u5219\uff0c\u8fd4\u56de null<\/p>\n<p>In listing 7.8 we return null if parsing fails. The endpoint shown will cause the EndpointMiddleware to throw a BadHttpRequestException and return a 400 error, because the size parameter in the endpoint is required (not marked optional). You could have thrown an exception, but it wouldn\u2019t have been caught by the EndpointMiddleware and would have resulted in a 500 response.<br \/>\n\u5728\u5217\u8868 7.8 \u4e2d\uff0c\u5982\u679c\u89e3\u6790\u5931\u8d25\uff0c\u6211\u4eec\u8fd4\u56de null\u3002\u663e\u793a\u7684\u7aef\u70b9\u5c06\u5bfc\u81f4EndpointMiddleware \u629b\u51fa\u4e00\u4e2aBadHttpRequestException \u5e76\u8fd4\u56de 400 \u9519\u8bef\uff0c\u56e0\u4e3a\u7ec8\u7aef\u8282\u70b9\u4e2d\u7684 size \u53c2\u6570\u662f\u5fc5\u9700\u7684\uff08\u672a\u6807\u8bb0\u4e3a\u53ef\u9009\uff09\u3002\u60a8\u53ef\u4ee5\u5728 BindAsync \u4e2d\u5f15\u53d1\u5f02\u5e38\uff0c\u4f46\u5b83\u4e0d\u4f1a\u88ab EndpointMiddleware \u6355\u83b7\uff0c\u5e76\u4e14\u4f1a\u5bfc\u81f4 500 \u54cd\u5e94\u3002<\/p>\n<h3>7.8 Choosing a binding source<\/h3>\n<h3>7.8 \u9009\u62e9\u7ed1\u5b9a\u6e90<\/h3>\n<p>Phew! We\u2019ve finally covered all the ways you can bind a request to parameters in minimal APIs. In many cases, things should work as you expect. Simple types such as int and string bind to route values and query string values by default, and complex types bind to the request body. But it can get confusing when you add attributes, BindAsync, and TryParse to the mix!<\/p>\n<p>\u5537\uff01\u6211\u4eec\u7ec8\u4e8e\u4ecb\u7ecd\u4e86\u5728\u6700\u5c0f API \u4e2d\u5c06\u8bf7\u6c42\u7ed1\u5b9a\u5230\u53c2\u6570\u7684\u6240\u6709\u65b9\u6cd5\u3002\u5728\u8bb8\u591a\u60c5\u51b5\u4e0b\uff0c\u4e8b\u60c5\u5e94\u8be5\u6309\u7167\u60a8\u7684\u9884\u671f\u8fdb\u884c\u3002\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u7b80\u5355\u7c7b\u578b\uff08\u5982 int \u548c string\uff09\u7ed1\u5b9a\u5230\u8def\u7531\u503c\u548c\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\uff0c\u590d\u6742\u7c7b\u578b\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6b63\u6587\u3002\u4f46\u662f\uff0c\u5f53\u60a8\u5c06\u5c5e\u6027\u3001BindAsync \u548c TryParse \u6dfb\u52a0\u5230\u7ec4\u5408\u4e2d\u65f6\uff0c\u53ef\u80fd\u4f1a\u611f\u5230\u56f0\u60d1\uff01<\/p>\n<p>When the minimal API infrastructure tries to bind a parameter, it checks all the following binding sources in order. The first binding source that matches is the one it uses:<\/p>\n<p>\u5f53\u6700\u5c0f API \u57fa\u7840\u8bbe\u65bd\u5c1d\u8bd5\u7ed1\u5b9a\u53c2\u6570\u65f6\uff0c\u5b83\u4f1a\u6309\u987a\u5e8f\u68c0\u67e5\u4ee5\u4e0b\u6240\u6709\u7ed1\u5b9a\u6e90\u3002\u5339\u914d\u7684\u7b2c\u4e00\u4e2a\u7ed1\u5b9a\u6e90\u662f\u5b83\u4f7f\u7528\u7684\u7ed1\u5b9a\u6e90\uff1a<\/p>\n<ol>\n<li>\n<p>If the parameter defines an explicit binding source using attributes such as [FromRoute], [FromQuery], or [FromBody], the parameter binds to that part of the request.<br \/>\n\u5982\u679c\u53c2\u6570\u4f7f\u7528 [FromRoute]\u3001[FromQuery] \u6216 [FromBody] \u7b49\u5c5e\u6027\u5b9a\u4e49\u663e\u5f0f\u7ed1\u5b9a\u6e90\uff0c\u5219\u53c2\u6570\u5c06\u7ed1\u5b9a\u5230\u8bf7\u6c42\u7684\u8be5\u90e8\u5206\u3002<\/p>\n<\/li>\n<li>\n<p>If the parameter is a well-known type such as HttpContext, HttpRequest, Stream, or IFormFile, the parameter is bound to the corresponding value.<br \/>\n\u5982\u679c\u53c2\u6570\u662f\u5df2\u77e5\u7c7b\u578b\uff0c\u5982 HttpContext\u3001HttpRequest\u3001Stream \u6216 IFormFile\uff0c\u5219\u53c2\u6570\u5c06\u7ed1\u5b9a\u5230\u76f8\u5e94\u7684\u503c\u3002<\/p>\n<\/li>\n<li>\n<p>If the parameter type has a BindAsync() method, use that method for binding.<br \/>\n\u5982\u679c\u53c2\u6570\u7c7b\u578b\u5177\u6709 BindAsync\uff08\uff09\u65b9\u6cd5\uff0c\u8bf7\u4f7f\u7528\u8be5\u65b9\u6cd5\u8fdb\u884c\u7ed1\u5b9a\u3002<\/p>\n<\/li>\n<li>\n<p>If the parameter is a string or has an appropriate TryParse() method (so is a simple type):<br \/>\n\u5982\u679c\u53c2\u6570\u662f\u5b57\u7b26\u4e32\u6216\u5177\u6709\u9002\u5f53\u7684TryParse\uff08\uff09 \u65b9\u6cd5\uff08\u7b80\u5355\u7c7b\u578b\u4e5f\u662f\u5982\u6b64\uff09\uff1a<br \/>\na. If the name of the parameter matches a route parameter name, bind to the route value.<br \/>\n\u5982\u679c\u53c2\u6570\u540d\u79f0\u4e0e\u8def\u7531\u53c2\u6570\u540d\u79f0\u5339\u914d\uff0c\u5219\u7ed1\u5b9a\u5230\u8def\u7531\u503c\u3002<br \/>\nb. Otherwise, bind to the query string.<br \/>\n\u5426\u5219\uff0c\u8bf7\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\u3002<\/p>\n<\/li>\n<li>\n<p>If the parameter is an array of simple types, a string[] or StringValues, the request is a GET or similar HTTP verb that normally doesn\u2019t have a request body, bind to the query string.<br \/>\n\u5982\u679c\u53c2\u6570\u662f\u7b80\u5355\u7c7b\u578b\u3001string[] \u6216 StringValues \u7684\u6570\u7ec4\uff0c\u5e76\u4e14\u8bf7\u6c42\u662f\u901a\u5e38\u6ca1\u6709\u8bf7\u6c42\u6b63\u6587\u7684 GET \u6216\u7c7b\u4f3c\u7684 HTTP \u52a8\u8bcd\uff0c\u8bf7\u7ed1\u5b9a\u5230\u67e5\u8be2\u5b57\u7b26\u4e32\u3002<\/p>\n<\/li>\n<li>\n<p>If the parameter is a known service type from the dependency injection container, bind by injecting the service from the container.<br \/>\n\u5982\u679c\u53c2\u6570\u662f\u4f9d\u8d56\u9879\u6ce8\u5165\u5bb9\u5668\u4e2d\u7684\u5df2\u77e5\u670d\u52a1\u7c7b\u578b\uff0c\u5219\u901a\u8fc7\u4ece\u5bb9\u5668\u6ce8\u5165\u670d\u52a1\u6765\u7ed1\u5b9a\u3002<\/p>\n<\/li>\n<\/ol>\n<p>7.Finally, bind to the body by deserializing from JSON.<br \/>\n\u6700\u540e\uff0c\u901a\u8fc7\u4ece JSON \u53cd\u5e8f\u5217\u5316\u6765\u7ed1\u5b9a\u5230\u6b63\u6587\u3002<\/p>\n<p>The minimal API infrastructure follows this sequence for every parameter in a handler and stops at the first matching binding source.<\/p>\n<p>\u6700\u5c0f API \u57fa\u7840\u7ed3\u6784\u5bf9\u5904\u7406\u7a0b\u5e8f\u4e2d\u7684\u6bcf\u4e2a\u53c2\u6570\u90fd\u9075\u5faa\u6b64\u5e8f\u5217\uff0c\u5e76\u5728\u7b2c\u4e00\u4e2a\u5339\u914d\u7684\u7ed1\u5b9a\u6e90\u5904\u505c\u6b62\u3002<\/p>\n<p><strong>Warning<\/strong> If binding fails for the entry, and the parameter isn\u2019t optional, the request fails with a 400 Bad Request response. The minimal API doesn\u2019t try another binding source after one source fails.<br \/>\n<strong>\u8b66\u544a<\/strong> \u5982\u679c\u6761\u76ee\u7684\u7ed1\u5b9a\u5931\u8d25\uff0c\u5e76\u4e14\u53c2\u6570\u4e0d\u662f\u53ef\u9009\u7684\uff0c\u5219\u8bf7\u6c42\u5c06\u5931\u8d25\uff0c\u5e76\u663e\u793a 400 Bad Request\u54cd\u5e94\u3002\u6700\u5c0f API \u4e0d\u4f1a\u5728\u4e00\u4e2a\u6e90\u5931\u8d25\u540e\u5c1d\u8bd5\u53e6\u4e00\u4e2a\u7ed1\u5b9a\u6e90\u3002<\/p>\n<p>Remembering this sequence of binding sources is one of the hardest things about minimal APIs to get your head around. If you\u2019re struggling to work out why a request isn\u2019t working as you expect, be sure to come back and check this sequence. I once had a parameter that wasn\u2019t binding to a route parameter, despite its having a TryParse method. When I checked the sequence, I realized that it also had a BindAsync method that was taking precedence!<br \/>\n\u8bb0\u4f4f\u8fd9\u4e00\u7cfb\u5217\u7ed1\u5b9a\u6e90\u662f\u6700\u5c0f API \u6700\u96be\u7406\u89e3\u7684\u4e8b\u60c5\u4e4b\u4e00\u3002\u5982\u679c\u4f60\u6b63\u5728\u52aa\u529b\u627e\u51fa\u8bf7\u6c42\u6ca1\u6709\u6309\u9884\u671f\u5de5\u4f5c\u7684\u539f\u56e0\uff0c\u8bf7\u52a1\u5fc5\u56de\u6765\u68c0\u67e5\u6b64\u5e8f\u5217\u3002\u6211\u66fe\u7ecf\u6709\u4e00\u4e2a\u53c2\u6570\uff0c\u5c3d\u7ba1\u5b83\u6709\u4e00\u4e2a TryParse \u65b9\u6cd5\uff0c\u4f46\u5b83\u6ca1\u6709\u7ed1\u5b9a\u5230\u8def\u7531\u53c2\u6570\u3002\u5f53\u6211\u68c0\u67e5\u5e8f\u5217\u65f6\uff0c\u6211\u610f\u8bc6\u5230\u5b83\u8fd8\u5177\u6709BindAsync \u65b9\u6cd5\u4f18\u5148\uff01<\/p>\n<h2>7.9 Simplifying handlers with AsParameters<\/h2>\n<h2>7.9 \u4f7f\u7528 AsParameters \u7b80\u5316\u5904\u7406\u7a0b\u5e8f<\/h2>\n<p>Before we move on, we\u2019ll take a quick look at a .NET 7 feature for minimal APIs that can simplify some endpoint handlers: the [AsParameters] attribute. Consider the following GET endpoint, which binds to a route value, a header value, and some query values:<br \/>\n\u5728\u7ee7\u7eed\u4e4b\u524d\uff0c\u6211\u4eec\u5c06\u5feb\u901f\u4e86\u89e3\u4e00\u4e0b .NET 7 \u529f\u80fd\uff0c\u8fd9\u4e9b\u529f\u80fd\u9002\u7528\u4e8e\u53ef\u7b80\u5316\u67d0\u4e9b\u7ec8\u7ed3\u70b9\u5904\u7406\u7a0b\u5e8f\u7684\u6700\u5c0f API\uff1a[AsParameters] \u5c5e\u6027\u3002\u8bf7\u8003\u8651\u4ee5\u4e0b GET \u7ec8\u7aef\u8282\u70b9\uff0c\u8be5\u7ec8\u7aef\u8282\u70b9\u7ed1\u5b9a\u5230\u8def\u7531\u503c\u3001\u6807\u5934\u503c\u548c\u4e00\u4e9b\u67e5\u8be2\u503c\uff1a<\/p>\n<p>Before we move on, we\u2019ll take a quick look at a .NET 7 feature for minimal APIs that can simplify some endpoint handlers: the [AsParameters] attribute. Consider the following GET endpoint, which binds to a route value, a header value, and some query values:<br \/>\n\u5728\u7ee7\u7eed\u4e4b\u524d\uff0c\u6211\u4eec\u5c06\u5feb\u901f\u4e86\u89e3\u4e00\u4e0b .NET 7 \u529f\u80fd\uff0c\u8fd9\u4e9b\u529f\u80fd\u9002\u7528\u4e8e\u53ef\u7b80\u5316\u67d0\u4e9b\u7ec8\u7ed3\u70b9\u5904\u7406\u7a0b\u5e8f\u7684\u6700\u5c0f API\uff1a[AsParameters] \u5c5e\u6027\u3002\u8bf7\u8003\u8651\u4ee5\u4e0b GET \u7ec8\u7aef\u8282\u70b9\uff0c\u8be5\u7ec8\u7aef\u8282\u70b9\u7ed1\u5b9a\u5230\u8def\u7531\u503c\u3001\u6807\u5934\u503c\u548c\u4e00\u4e9b\u67e5\u8be2\u503c\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\napp.MapGet(&quot;\/category\/{id}&quot;, (int id, int page, &#x5B;FromHeader(Name = &quot;sort&quot;)] bool? sortAsc, &#x5B;FromQuery(Name = &quot;q&quot;)] string search) =&gt; { });\n<\/pre>\n<p>I think you\u2019ll agree that the handler parameters for this method are somewhat hard to read. The parameters define the expected shape of the request, which isn\u2019t ideal. The [AsParameters] attribute lets you wrap all these arguments into a single class or struct, simplifying the method signature and making everything more readable.<\/p>\n<p>\u6211\u60f3\u4f60\u4f1a\u540c\u610f\u8fd9\u4e2a\u65b9\u6cd5\u7684\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u6709\u70b9\u96be\u4ee5\u9605\u8bfb\u3002\u53c2\u6570\u5b9a\u4e49\u8bf7\u6c42\u7684\u9884\u671f\u5f62\u72b6\uff0c\u8fd9\u5e76\u4e0d\u7406\u60f3\u3002[AsParameters] \u5c5e\u6027\u5141\u8bb8\u60a8\u5305\u88c5\u6240\u6709\u8fd9\u4e9b<\/p>\n<p>\u53c2\u6570\u8f6c\u6362\u4e3a\u5355\u4e2a\u7c7b\u6216\u7ed3\u6784\u4f53\uff0c\u4ece\u800c\u7b80\u5316\u65b9\u6cd5\u7b7e\u540d\u5e76\u4f7f\u6240\u6709\u5185\u5bb9\u66f4\u5177\u53ef\u8bfb\u6027\u3002<\/p>\n<p>Listing 7.9 shows an example of converting this endpoint to use [AsParameters] by replacing it with a record struct. You could also use a class, record, or struct, and you can use properties instead of constructor parameters if you prefer. See the documentation for all the permutations available at <a href=\"http:\/\/mng.bz\/a1KB\">http:\/\/mng.bz\/a1KB<\/a>.<\/p>\n<p>\u6e05\u5355 7.9 \u5c55\u793a\u4e86\u4e00\u4e2a\u901a\u8fc7\u5c06\u8fd9\u4e2a\u7aef\u70b9\u66ff\u6362\u4e3a record \u7ed3\u6784\u4f53\u6765\u8f6c\u6362\u5b83\u4ee5\u4f7f\u7528 [AsParameters] \u7684\u793a\u4f8b\u3002\u60a8\u8fd8\u53ef\u4ee5\u4f7f\u7528 class\u3001record \u6216 struct\uff0c\u5982\u679c\u60a8\u613f\u610f\uff0c\u53ef\u4ee5\u4f7f\u7528 properties \u800c\u4e0d\u662f constructor \u53c2\u6570\u3002\u8bf7\u53c2\u9605 <a href=\"http:\/\/mng.bz\/a1KB\">http:\/\/mng.bz\/a1KB<\/a> \u4e0a\u63d0\u4f9b\u7684\u6240\u6709\u6392\u5217\u7684\u6587\u6863\u3002<\/p>\n<p>Listing 7.9 Using [AsParameters] to simplify endpoint handler parameters<br \/>\n\u6e05\u5355 7.9 \u4f7f\u7528 [AsParameters] \u7b80\u5316\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u53c2\u6570<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing Microsoft.AspNetCore.Mvc;\n\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\napp.MapGet(&quot;\/category\/{id}&quot;,\n    (&#x5B;AsParameters] SearchModel model) =&gt; $&quot;Received {model}&quot;);    \/\/ \u2776\n\napp.Run();\n\nrecord struct SearchModel(\n    int id,                                                        \/\/ \u2777\n    int page,                                                      \/\/ \u2777\n    &#x5B;FromHeader(Name = &quot;sort&quot;)] bool? sortAsc,                     \/\/ \u2777\n    &#x5B;FromQuery(Name = &quot;q&quot;)] string search);                        \/\/ \u2777\n<\/pre>\n<p>\u2776 [AsParameters] indicates that the constructor or properties of the type should be bound, not the type itself.<br \/>\n[AsParameters] \u6307\u793a\u5e94\u7ed1\u5b9a\u7c7b\u578b\u7684\u6784\u9020\u51fd\u6570\u6216\u5c5e\u6027\uff0c\u800c\u4e0d\u662f\u7c7b\u578b\u672c\u8eab\u3002<\/p>\n<p>\u2777 Each parameter is bound as though it were written in the endpoint handler<br \/>\n\u6bcf\u4e2a\u53c2\u6570\u90fd\u88ab\u7ed1\u5b9a\uff0c\u5c31\u50cf\u5b83\u662f\u5728\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u5199\u5165\u7684\u4e00\u6837\u3002<\/p>\n<p>The same attributes and rules apply for binding an [AsParameters] type\u2019s constructor parameters and binding endpoint handler parameters, so you can use [From*] attributes, inject services and well-known types, and read from the body. This approach can make your endpoints more readable if you find that they\u2019re getting a bit unwieldy.<\/p>\n<p>\u76f8\u540c\u7684\u5c5e\u6027\u548c\u89c4\u5219\u9002\u7528\u4e8e\u7ed1\u5b9a [AsParameters] \u7c7b\u578b\u7684\u6784\u9020\u51fd\u6570\u53c2\u6570\u548c\u7ed1\u5b9a\u7aef\u70b9\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\uff0c\u56e0\u6b64\u60a8\u53ef\u4ee5\u4f7f\u7528 [From*] \u5c5e\u6027\u3001\u6ce8\u5165\u670d\u52a1\u548c\u5df2\u77e5\u7c7b\u578b\uff0c\u4ee5\u53ca\u4ece\u6b63\u6587\u4e2d\u8bfb\u53d6\u3002\u8fd9\u79cd\u65b9\u6cd5\u53ef\u4ee5\u4f7f\u60a8\u7684\u5982\u679c\u4f60\u53d1\u73b0\u5b83\u4eec\u53d8\u5f97\u6709\u70b9\u7b28\u62d9\uff0c\u5b83\u4eec\u4f1a\u66f4\u5177\u53ef\u8bfb\u6027\u3002<\/p>\n<p><strong>Tip<\/strong> In chapter 16 you\u2019ll learn about model binding in MVC and Razor Pages. You\u2019ll be pleased to know that in those cases, the [AsParameters] approach works out of the box without the need for an extra attribute.<br \/>\n<strong>\u63d0\u793a<\/strong> \u5728\u7b2c 16 \u7ae0\u4e2d\uff0c\u60a8\u5c06\u4e86\u89e3 MVC \u548c Razor Pages \u4e2d\u7684\u6a21\u578b\u7ed1\u5b9a\u3002\u60a8\u4f1a\u5f88\u9ad8\u5174\u5730\u77e5\u9053\uff0c\u5728\u8fd9\u4e9b\u60c5\u51b5\u4e0b\uff0c[AsParameters] \u65b9\u6cd5\u5f00\u7bb1\u5373\u7528\uff0c\u65e0\u9700\u989d\u5916\u7684\u5c5e\u6027\u3002<\/p>\n<p>That brings us to the end of this section on model binding. If all went well, your endpoint handler\u2019s arguments are created, and the handler is ready to execute its logic. It\u2019s time to handle the request, right? Nothing to worry about.<\/p>\n<p>\u8fd9\u8ba9\u6211\u4eec\u7ed3\u675f\u4e86\u672c\u8282\u5173\u4e8e\u6a21\u578b\u7ed1\u5b9a\u7684\u5185\u5bb9\u3002\u5982\u679c\u4e00\u5207\u987a\u5229\uff0c\u5219 endpoint \u5904\u7406\u7a0b\u5e8f\u7684\u53c2\u6570\u5c06\u521b\u5efa\uff0c\u5e76\u4e14\u5904\u7406\u7a0b\u5e8f\u5df2\u51c6\u5907\u597d\u6267\u884c\u5176 logic\u3002\u662f\u65f6\u5019\u5904\u7406\u8fd9\u4e2a\u8bf7\u6c42\u4e86\uff0c\u5bf9\u5427\uff1f\u6ca1\u4ec0\u4e48\u597d\u62c5\u5fc3\u7684\u3002<\/p>\n<p>Not so fast! How do you know that the data you received was valid? How do you know that you haven\u2019t been sent malicious data attempting a SQL injection attack or a phone number full of letters? The binder is relatively blindly assigning values sent in a request, which you\u2019re happily going to plug into your own methods. What stops nefarious little Jimmy from sending malicious values to your application? Except for basic safeguards, nothing is stopping him, which is why it\u2019s important that you always validate the input coming in. ASP.NET Core provides a way to do this in a declarative manner out of the box, which is the focus of section 7.10.<\/p>\n<p>\u6ca1\u90a3\u4e48\u5feb\uff01\u60a8\u5982\u4f55\u77e5\u9053\u60a8\u6536\u5230\u7684\u6570\u636e\u6709\u6548\uff1f\u60a8\u5982\u4f55\u77e5\u9053\u60a8\u6ca1\u6709\u6536\u5230\u5c1d\u8bd5 SQL \u6ce8\u5165\u653b\u51fb\u7684\u6076\u610f\u6570\u636e\u6216\u5145\u6ee1\u5b57\u6bcd\u7684\u7535\u8bdd\u53f7\u7801\uff1fBinders \u76f8\u5bf9\u76f2\u76ee\u5730\u5206\u914d\u8bf7\u6c42\u4e2d\u53d1\u9001\u7684\u503c\uff0c\u60a8\u5f88\u4e50\u610f\u5c06\u5176\u63d2\u5165\u5230\u81ea\u5df1\u7684\u65b9\u6cd5\u4e2d\u3002\u5982\u4f55\u963b\u6b62\u90aa\u6076\u7684\u5c0f Jimmy \u5411\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u53d1\u9001\u6076\u610f\u503c\uff1f\u9664\u4e86\u57fa\u672c\u7684\u4fdd\u62a4\u63aa\u65bd\u5916\uff0c\u6ca1\u6709\u4ec0\u4e48\u80fd\u963b\u6b62\u4ed6\uff0c\u8fd9\u5c31\u662f\u4e3a\u4ec0\u4e48\u4f60\u603b\u662f\u9a8c\u8bc1\u8f93\u5165\u5f88\u91cd\u8981\u7684\u539f\u56e0\u3002ASP.NET Core \u63d0\u4f9b\u4e86\u4e00\u79cd\u5f00\u7bb1\u5373\u7528\u7684\u58f0\u660e\u5f0f\u65b9\u5f0f\u6267\u884c\u6b64\u4f5c\u7684\u65b9\u6cd5\uff0c\u8fd9\u662f\u7b2c 7.10 \u8282\u7684\u91cd\u70b9\u3002<\/p>\n<h2>7.10 Handling user input with model validation<\/h2>\n<h2>7.10 \u4f7f\u7528\u6a21\u578b\u9a8c\u8bc1\u5904\u7406\u7528\u6237\u8f93\u5165<\/h2>\n<p>In this section, I discuss the following topics:<\/p>\n<p>\u5728\u672c\u8282\u4e2d\uff0c\u6211\u5c06\u8ba8\u8bba\u4ee5\u4e0b\u4e3b\u9898\uff1a<\/p>\n<ul>\n<li>\n<p>What validation is and why you need it<br \/>\n\u4ec0\u4e48\u662f\u9a8c\u8bc1\u4ee5\u53ca\u4e3a\u4ec0\u4e48\u9700\u8981\u9a8c\u8bc1<\/p>\n<\/li>\n<li>\n<p>How to use DataAnnotations attributes to describe the data you expect<br \/>\n\u5982\u4f55\u4f7f\u7528 DataAnnotations \u5c5e\u6027\u63cf\u8ff0\u6240\u9700\u7684\u6570\u636e<\/p>\n<\/li>\n<li>\n<p>How to validate your endpoint handler parameters<br \/>\n\u5982\u4f55\u9a8c\u8bc1\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u53c2\u6570<\/p>\n<\/li>\n<\/ul>\n<p>Validation in general is a big topic, one that you\u2019ll need to consider in every app you build. Minimal APIs don\u2019t include validation by default, instead opting to provide nonprescriptive hooks via the filters you learned about in chapter 5. This design gives you multiple options for adding validation to your app; be sure that you do add some!<\/p>\n<p>\u4e00\u822c\u6765\u8bf4\uff0c\u9a8c\u8bc1\u662f\u4e00\u4e2a\u5f88\u5927\u7684\u8bdd\u9898\uff0c\u60a8\u5728\u6784\u5efa\u7684\u6bcf\u4e2a\u5e94\u7528\u7a0b\u5e8f\u4e2d\u90fd\u9700\u8981\u8003\u8651\u8fd9\u4e2a\u8bdd\u9898\u3002\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cMinimal API \u4e0d\u5305\u542b\u9a8c\u8bc1\uff0c\u800c\u662f\u9009\u62e9\u901a\u8fc7\u60a8\u5728\u7b2c 5 \u7ae0\u4e2d\u5b66\u5230\u7684\u8fc7\u6ee4\u5668\u63d0\u4f9b\u975e\u89c4\u8303\u6027\u94a9\u5b50\u3002\u6b64\u8bbe\u8ba1\u4e3a\u60a8\u63d0\u4f9b\u4e86\u591a\u4e2a\u9009\u9879\uff0c\u7528\u4e8e\u5411\u5e94\u7528\u7a0b\u5e8f\u6dfb\u52a0\u9a8c\u8bc1;\u786e\u4fdd\u4f60\u786e\u5b9e\u6dfb\u52a0\u4e86\u4e00\u4e9b\uff01<\/p>\n<h3>7.10.1 The need for validation<\/h3>\n<h3>7.10.1 \u9a8c\u8bc1\u7684\u5fc5\u8981\u6027<\/h3>\n<p>Data can come from many sources in your web application. You could load data from files, read it from a database, or accept values that are sent in a request. Although you may be inclined to trust that the data already on your server is valid (though this assumption is sometimes dangerous!), you definitely shouldn\u2019t trust the data sent as part of a request.<\/p>\n<p>\u6570\u636e\u53ef\u4ee5\u6765\u81ea Web \u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u8bb8\u591a\u6765\u6e90\u3002\u60a8\u53ef\u4ee5\u4ece\u6587\u4ef6\u4e2d\u52a0\u8f7d\u6570\u636e\u3001\u4ece\u6570\u636e\u5e93\u4e2d\u8bfb\u53d6\u6570\u636e\u6216\u63a5\u53d7\u8bf7\u6c42\u4e2d\u53d1\u9001\u7684\u503c\u3002\u5c3d\u7ba1\u60a8\u53ef\u80fd\u503e\u5411\u4e8e\u76f8\u4fe1\u670d\u52a1\u5668\u4e0a\u5df2\u6709\u7684\u6570\u636e\u662f\u6709\u6548\u7684\uff08\u5c3d\u7ba1\u8fd9\u79cd\u5047\u8bbe\u6709\u65f6\u5f88\u5371\u9669\uff01\uff09\uff0c\u4f46\u60a8\u7edd\u5bf9\u4e0d\u5e94\u8be5\u76f8\u4fe1\u4f5c\u4e3a\u8bf7\u6c42\u7684\u4e00\u90e8\u5206\u53d1\u9001\u7684\u6570\u636e\u3002<\/p>\n<p><strong>Tip<\/strong> You can read more about the goals of validation, implementation approaches, and potential attacks at <a href=\"http:\/\/mng.bz\/gBxE\">http:\/\/mng.bz\/gBxE<\/a>.<br \/>\n<strong>\u63d0\u793a<\/strong> \u60a8\u53ef\u4ee5\u5728 <a href=\"http:\/\/mng.bz\/gBxE\">http:\/\/mng.bz\/gBxE<\/a> \u4e0a\u9605\u8bfb\u6709\u5173\u9a8c\u8bc1\u76ee\u6807\u3001\u5b9e\u65bd\u65b9\u6cd5\u548c\u6f5c\u5728\u653b\u51fb\u7684\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<p>You should validate your endpoint handler parameters before you use them to do anything that touches your domain, anything that touches your infrastructure, or anything that could leak information to an attacker. Note that this warning is intentionally vague, as there\u2019s no defined point in minimal APIs where validation should occur. I advise that you do it as soon as possible in the minimal API filter pipeline.<\/p>\n<p>\u5728\u4f7f\u7528\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u6267\u884c\u4efb\u4f55\u6d89\u53ca\u60a8\u7684\u57df\u3001\u6d89\u53ca\u60a8\u7684\u57fa\u7840\u8bbe\u65bd\u6216\u53ef\u80fd\u5c06\u4fe1\u606f\u6cc4\u9732\u7ed9\u653b\u51fb\u8005\u7684\u4efb\u4f55\u4f5c\u4e4b\u524d\uff0c\u60a8\u5e94\u8be5\u5148\u9a8c\u8bc1\u5b83\u4eec\u3002\u8bf7\u6ce8\u610f\uff0c\u6b64\u8b66\u544a\u6545\u610f\u542b\u7cca\u4e0d\u6e05\uff0c\u56e0\u4e3a minimal \u4e2d\u6ca1\u6709\u5b9a\u4e49\u70b9\u5e94\u8fdb\u884c\u9a8c\u8bc1\u7684 API\u3002\u6211\u5efa\u8bae\u60a8\u5c3d\u5feb\u5728\u6700\u5c0f API \u8fc7\u6ee4\u5668\u7ba1\u9053\u4e2d\u6267\u884c\u6b64\u4f5c\u3002<\/p>\n<p>Always validate data provided by users before you use it in your methods. You have no idea what the browser may have sent you. The classic example of little Bobby Tables (<a href=\"https:\/\/xkcd.com\/327\">https:\/\/xkcd.com\/327<\/a>) highlights the need to always validate data sent by a user.<\/p>\n<p>\u5728\u65b9\u6cd5\u4e2d\u4f7f\u7528\u7528\u6237\u63d0\u4f9b\u7684\u6570\u636e\u4e4b\u524d\uff0c\u8bf7\u59cb\u7ec8\u5bf9\u5176\u8fdb\u884c\u9a8c\u8bc1\u3002\u60a8\u4e0d\u77e5\u9053\u6d4f\u89c8\u5668\u53ef\u80fd\u5411\u60a8\u53d1\u9001\u4e86\u4ec0\u4e48\u3002\u5c0f Bobby Tables \uff08<a href=\"https:\/\/xkcd.com\/327\">https:\/\/xkcd.com\/327<\/a>\uff09 \u7684\u7ecf\u5178\u793a\u4f8b\u5f3a\u8c03\u4e86\u59cb\u7ec8\u9a8c\u8bc1\u7528\u6237\u53d1\u9001\u7684\u6570\u636e\u7684\u5fc5\u8981\u6027\u3002<\/p>\n<p>Validation isn\u2019t used only to check for security threats, though. It\u2019s also needed to check for nonmalicious errors:<\/p>\n<p>\u4e0d\u8fc7\uff0c\u9a8c\u8bc1\u4e0d\u4ec5\u4ec5\u7528\u4e8e\u68c0\u67e5\u5b89\u5168\u5a01\u80c1\u3002\u8fd8\u9700\u8981\u68c0\u67e5\u975e\u6076\u610f\u9519\u8bef\uff1a<\/p>\n<ul>\n<li>\n<p>Data should be formatted correctly. Email fields have a valid email format, for example.<br \/>\n\u6570\u636e\u7684\u683c\u5f0f\u5e94\u6b63\u786e\u3002\u4f8b\u5982\uff0c\u7535\u5b50\u90ae\u4ef6\u5b57\u6bb5\u5177\u6709\u6709\u6548\u7684\u7535\u5b50\u90ae\u4ef6\u683c\u5f0f\u3002<\/p>\n<\/li>\n<li>\n<p>Numbers may need to be in a particular range. You can\u2019t buy -1 copies of this book!<br \/>\n\u6570\u5b57\u53ef\u80fd\u9700\u8981\u5728\u7279\u5b9a\u8303\u56f4\u5185\u3002\u8fd9\u672c\u4e66\u4f60\u4e70\u4e0d\u5230 -1 \u672c\uff01<\/p>\n<\/li>\n<li>\n<p>Some values may be required, but others are optional. Name may be required for a profile, but phone number is optional.<br \/>\n\u67d0\u4e9b\u503c\u53ef\u80fd\u662f\u5fc5\u9700\u7684\uff0c\u4f46\u5176\u4ed6\u503c\u662f\u53ef\u9009\u7684\u3002\u914d\u7f6e\u6587\u4ef6\u53ef\u80fd\u9700\u8981\u540d\u79f0\uff0c\u4f46\u7535\u8bdd\u53f7\u7801\u662f\u53ef\u9009\u7684\u3002<\/p>\n<\/li>\n<li>\n<p>Values must conform to your business requirements. You can\u2019t convert a currency to itself; it needs to be converted to a different currency.<br \/>\n\u503c\u5fc5\u987b\u7b26\u5408\u60a8\u7684\u4e1a\u52a1\u8981\u6c42\u3002\u4e0d\u80fd\u5c06\u8d27\u5e01\u8f6c\u6362\u4e3a\u81ea\u8eab;\u5b83\u9700\u8981\u8f6c\u6362\u4e3a\u5176\u4ed6\u8d27\u5e01\u3002<\/p>\n<\/li>\n<\/ul>\n<p>As mentioned earlier, the minimal API framework doesn\u2019t include anything specific to help you with these requirements, but you can use filters to implement validation, as you\u2019ll see in section 7.10.3. .NET 7 also includes a set of attributes that you can use to simplify your validation code significantly.<\/p>\n<p>\u5982\u524d\u6240\u8ff0\uff0c\u6700\u5c0f API \u6846\u67b6\u4e0d\u5305\u542b\u4efb\u4f55\u5e2e\u52a9\u60a8\u6ee1\u8db3\u8fd9\u4e9b\u8981\u6c42\u7684\u7279\u5b9a\u5185\u5bb9\uff0c\u4f46\u60a8\u53ef\u4ee5\u4f7f\u7528\u8fc7\u6ee4\u5668\u6765\u5b9e\u73b0\u9a8c\u8bc1\uff0c\u5982\u7b2c 7.10.3 \u8282\u6240\u793a\u3002.NET 7 \u8fd8\u5305\u62ec\u4e00\u7ec4\u5c5e\u6027\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528\u8fd9\u4e9b\u5c5e\u6027\u6765\u663e\u8457\u7b80\u5316\u9a8c\u8bc1\u4ee3\u7801\u3002<\/p>\n<h3>7.10.2 Using DataAnnotations attributes for validation<\/h3>\n<h3>7.10.2 \u4f7f\u7528 DataAnnotations \u5c5e\u6027\u8fdb\u884c\u9a8c\u8bc1<\/h3>\n<p>Validation attributes\u2014more precisely, DataAnnotations attributes\u2014allow you to specify the rules that your parameters should conform to. They provide metadata about a parameter type by describing the sort of data the binding model should contain, as opposed to the data itself.<\/p>\n<p>\u9a8c\u8bc1\u5c5e\u6027\uff08\u66f4\u51c6\u786e\u5730\u8bf4\uff0cDataAnnotations \u5c5e\u6027\uff09\u5141\u8bb8\u60a8\u6307\u5b9a\u53c2\u6570\u5e94\u9075\u5faa\u7684\u89c4\u5219\u3002\u5b83\u4eec\u901a\u8fc7\u63cf\u8ff0\u7ed1\u5b9a\u6a21\u578b\u5e94\u5305\u542b\u7684\u6570\u636e\u7c7b\u578b\uff08\u800c\u4e0d\u662f\u6570\u636e\u672c\u8eab\uff09\u6765\u63d0\u4f9b\u6709\u5173\u53c2\u6570\u7c7b\u578b\u7684\u5143\u6570\u636e\u3002<\/p>\n<p>You can apply DataAnnotations attributes directly to your parameter types to indicate the type of data that\u2019s acceptable. This approach allows you to check that required fields have been provided, that numbers are in the correct range, and that email fields are valid email addresses, for example.<\/p>\n<p>\u60a8\u53ef\u4ee5\u5c06 DataAnnotations \u5c5e\u6027\u76f4\u63a5\u5e94\u7528\u4e8e\u53c2\u6570\u7c7b\u578b\uff0c\u4ee5\u6307\u793a\u53ef\u63a5\u53d7\u7684\u6570\u636e\u7c7b\u578b\u3002\u4f8b\u5982\uff0c\u6b64\u65b9\u6cd5\u5141\u8bb8\u60a8\u68c0\u67e5\u662f\u5426\u63d0\u4f9b\u4e86\u5fc5\u586b\u5b57\u6bb5\u3001\u6570\u5b57\u662f\u5426\u5728\u6b63\u786e\u7684\u8303\u56f4\u5185\uff0c\u4ee5\u53ca\u7535\u5b50\u90ae\u4ef6\u5b57\u6bb5\u662f\u5426\u4e3a\u6709\u6548\u7684\u7535\u5b50\u90ae\u4ef6\u5730\u5740\u3002<\/p>\n<p>Consider the checkout page for a currency-converter application. You need to collect details about the user\u2014their name, email, and (optionally) phone number\u2014so you create an API to capture these details. The following listing shows the outline of that API, which takes a UserModel parameter. The UserModel type is decorated with validation attributes that represent the validation rules for the model.<\/p>\n<p>\u8bf7\u8003\u8651\u8d27\u5e01\u8f6c\u6362\u5668\u5e94\u7528\u7a0b\u5e8f\u7684\u7ed3\u5e10\u9875\u3002\u60a8\u9700\u8981\u6536\u96c6\u6709\u5173\u7528\u6237\u7684\u8be6\u7ec6\u4fe1\u606f \u2014 \u4ed6\u4eec\u7684\u59d3\u540d\u3001\u7535\u5b50\u90ae\u4ef6\u548c\uff08\u53ef\u9009\uff09\u7535\u8bdd\u53f7\u7801 \u2014 \u56e0\u6b64\u60a8\u521b\u5efa\u4e00\u4e2a API \u6765\u6355\u83b7\u8fd9\u4e9b\u8be6\u7ec6\u4fe1\u606f\u3002\u4e0b\u9762\u7684\u6e05\u5355\u663e\u793a\u4e86\u8be5 API \u7684\u8f6e\u5ed3\uff0c\u5b83\u91c7\u7528 UserModel \u53c2\u6570\u3002UserModel \u7c7b\u578b\u4f7f\u7528\u8868\u793a\u6a21\u578b\u7684\u9a8c\u8bc1\u89c4\u5219\u7684\u9a8c\u8bc1\u5c5e\u6027\u8fdb\u884c\u4fee\u9970\u3002<\/p>\n<p>Listing 7.10 Adding DataAnnotations to a type to provide metadata<br \/>\n\u6e05\u5355 7.10 \u5c06 DataAnnotations \u6dfb\u52a0\u5230\u7c7b\u578b\u4e2d\u4ee5\u63d0\u4f9b\u5143\u6570\u636e<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing System.ComponentModel.DataAnnotations;                        \/\/ \u2776\n\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\napp.MapPost(&quot;\/users&quot;, (UserModel user) =&gt; user.ToString());         \/\/ \u2777\n\napp.Run();\n\npublic record UserModel\n{\n    &#x5B;Required]                                                      \/\/ \u2778\n    &#x5B;StringLength(100)]                                             \/\/ \u2779\n    &#x5B;Display(Name = &quot;Your name&quot;)]                                   \/\/ \u277a\n    public string FirstName { get; set; }\n\n    &#x5B;Required]\n    &#x5B;StringLength(100)]\n    &#x5B;Display(Name = &quot;Last name&quot;)]\n    public string LastName { get; set; }\n\n    &#x5B;Required]\n    &#x5B;EmailAddress]                                                  \/\/ \u277b\n    public string Email { get; set; }\n\n    &#x5B;Phone]                                                         \/\/ \u277c\n    &#x5B;Display(Name = &quot;Phone number&quot;)]\n    public string PhoneNumber { get; set; }\n}\n<\/pre>\n<p>\u2776 Adds this using statement to use the validation attributes<br \/>\n\u6dfb\u52a0\u6b64 using \u8bed\u53e5\u4ee5\u4f7f\u7528\u9a8c\u8bc1\u5c5e\u6027<\/p>\n<p>\u2777 The API takes a UserModel parameter and binds it to the request body.<br \/>\nAPI \u63a5\u53d7 UserModel \u53c2\u6570\u5e76\u5c06\u5176\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6b63\u6587\u3002<\/p>\n<p>\u2778 Values marked Required must be provided.<br \/>\n\u5fc5\u987b\u63d0\u4f9b\u6807\u8bb0\u4e3a Required \u7684\u503c\u3002<\/p>\n<p>\u2779 The StringLengthAttribute sets the maximum length for the property.<br \/>\nStringLengthAttribute \u8bbe\u7f6e\u5c5e\u6027\u7684\u6700\u5927\u957f\u5ea6\u3002<\/p>\n<p>\u277a Customizes the name used to describe the property<br \/>\n\u81ea\u5b9a\u4e49\u7528\u4e8e\u63cf\u8ff0\u5c5e\u6027\u7684\u540d\u79f0<\/p>\n<p>\u277b Validates that the value of Email may be a valid email address<br \/>\n\u9a8c\u8bc1 Email \u7684\u503c\u662f\u5426\u4e3a\u6709\u6548\u7684\u7535\u5b50\u90ae\u4ef6\u5730\u5740<\/p>\n<p>\u277c Validates that the value of PhoneNumber has a valid telephone number format<br \/>\n\u9a8c\u8bc1 PhoneNumber \u7684\u503c\u662f\u5426\u5177\u6709\u6709\u6548\u7684\u7535\u8bdd\u53f7\u7801\u683c\u5f0f<\/p>\n<p>Suddenly, your parameter type, which was sparse on details, contains a wealth of information. You\u2019ve specified that the FirstName property should always be provided; that it should have a maximum length of 100 characters; and that when it\u2019s referred to (in error messages, for example), it should be called &quot;Your name&quot; instead of &quot;FirstName&quot;.<\/p>\n<p>\u7a81\u7136\u4e4b\u95f4\uff0c\u60a8\u7684\u53c2\u6570\u7c7b\u578b\uff08\u7ec6\u8282\u7a00\u758f\uff09\u5305\u542b\u4e86\u5927\u91cf\u4fe1\u606f\u3002\u60a8\u5df2\u6307\u5b9a\u5e94\u59cb\u7ec8\u63d0\u4f9b FirstName \u5c5e\u6027;\u6700\u5927\u957f\u5ea6\u5e94\u4e3a 100 \u4e2a\u5b57\u7b26;\u5f53\u5b83\u88ab\u5f15\u7528\u65f6\uff08\u4f8b\u5982\uff0c\u5728\u9519\u8bef\u6d88\u606f\u4e2d\uff09\uff0c\u5b83\u5e94\u8be5\u88ab\u79f0\u4e3a \u201cYour name\u201d \u800c\u4e0d\u662f \u201cFirstName\u201d\u3002<\/p>\n<p>The great thing about these attributes is that they clearly declare the expected state of an instance of the type. By looking at these attributes, you know what the properties will contain, or at least should contain. Then you can then write code after model binding to confirm that the bound parameter is valid, as you\u2019ll see in section 7.10.3.<\/p>\n<p>\u8fd9\u4e9b\u5c5e\u6027\u7684\u4f1f\u5927\u4e4b\u5904\u5728\u4e8e\uff0c\u5b83\u4eec\u6e05\u695a\u5730\u58f0\u660e\u4e86\u8be5\u7c7b\u578b\u5b9e\u4f8b\u7684\u9884\u671f\u72b6\u6001\u3002\u901a\u8fc7\u67e5\u770b\u8fd9\u4e9b\u5c5e\u6027\uff0c\u60a8\u77e5\u9053\u5c5e\u6027\u5c06\u5305\u542b\u4ec0\u4e48\uff0c\u6216\u8005\u81f3\u5c11\u5e94\u8be5\u5305\u542b\u4ec0\u4e48\u3002\u7136\u540e\uff0c\u60a8\u53ef\u4ee5\u5728\u6a21\u578b\u7ed1\u5b9a\u540e\u7f16\u5199\u4ee3\u7801\u4ee5\u786e\u8ba4 bound \u53c2\u6570\u6709\u6548\uff0c\u5982 7.10.3 \u8282\u6240\u793a\u3002<\/p>\n<p>You\u2019ve got a plethora of attributes to choose among when you apply DataAnnotations to your types. I\u2019ve listed some of the common ones here, but you can find more in the System.ComponentModel.DataAnnotations namespace. For a more complete list, I recommend using IntelliSense in your IDE or checking the documentation at <a href=\"http:\/\/mng.bz\/e1Mv\">http:\/\/mng.bz\/e1Mv<\/a>.<\/p>\n<p>\u5f53\u60a8\u5c06 DataAnnotations \u5e94\u7528\u4e8e\u60a8\u7684\u7c7b\u578b\u65f6\uff0c\u60a8\u6709\u5927\u91cf\u7684\u5c5e\u6027\u53ef\u4f9b\u9009\u62e9\u3002\u6211\u5728\u8fd9\u91cc\u5217\u51fa\u4e86\u4e00\u4e9b\u5e38\u89c1\u7684\u65b9\u6cd5\uff0c\u4f46\u60a8\u53ef\u4ee5\u5728 System.ComponentModel.DataAnnotations \u547d\u540d\u7a7a\u95f4\u4e2d\u627e\u5230\u66f4\u591a\u65b9\u6cd5\u3002\u6709\u5173\u66f4\u5b8c\u6574\u7684\u5217\u8868\uff0c\u6211\u5efa\u8bae\u5728 IDE \u4e2d\u4f7f\u7528 IntelliSense \u6216\u67e5\u770b <a href=\"http:\/\/mng.bz\/e1Mv\">http:\/\/mng.bz\/e1Mv<\/a> \u4e2d\u7684\u6587\u6863\u3002<\/p>\n<ul>\n<li>\n<p>[CreditCard]\u2014Validates that a property has a valid credit card format<br \/>\n[\u4fe1\u7528\u5361]- \u9a8c\u8bc1\u5c5e\u6027\u662f\u5426\u5177\u6709\u6709\u6548\u7684\u4fe1\u7528\u5361\u683c\u5f0f<\/p>\n<\/li>\n<li>\n<p>[EmailAddress]\u2014Validates that a property has a valid email address format<br \/>\n[\u7535\u5b50\u90ae\u4ef6\u5730\u5740]- \u9a8c\u8bc1\u5c5e\u6027\u662f\u5426\u5177\u6709\u6709\u6548\u7684\u7535\u5b50\u90ae\u4ef6\u5730\u5740\u683c\u5f0f<\/p>\n<\/li>\n<li>\n<p>[StringLength(max)]\u2014Validates that a string has at most max number of characters<br \/>\n[\u5b57\u7b26\u4e32\u957f\u5ea6\uff08\u6700\u5927\uff09]- \u9a8c\u8bc1\u5b57\u7b26\u4e32\u662f\u5426\u6700\u591a\u5177\u6709\u6700\u5927\u5b57\u7b26\u6570<\/p>\n<\/li>\n<li>\n<p>[MinLength(min)]\u2014Validates that a collection has at least the min number of items<br \/>\n[\u6700\u5c0f\u957f\u5ea6\uff08min\uff09]- \u9a8c\u8bc1\u96c6\u5408\u662f\u5426\u81f3\u5c11\u5177\u6709\u6700\u5c0f\u9879\u76ee\u6570<\/p>\n<\/li>\n<li>\n<p>[Phone]\u2014Validates that a property has a valid phone number format<br \/>\n[\u7535\u8bdd]- \u9a8c\u8bc1\u5c5e\u6027\u662f\u5426\u5177\u6709\u6709\u6548\u7684\u7535\u8bdd\u53f7\u7801\u683c\u5f0f<\/p>\n<\/li>\n<li>\n<p>[Range(min, max)]\u2014Validates that a property has a value between min and max<br \/>\n[\u8303\u56f4\uff08\u6700\u5c0f\u503c\u3001\u6700\u5927\u503c\uff09]- \u9a8c\u8bc1\u5c5e\u6027\u7684\u503c\u662f\u5426\u4ecb\u4e8e min \u548c max \u4e4b\u95f4<\/p>\n<\/li>\n<li>\n<p>[RegularExpression(regex)]\u2014Validates that a property conforms to the regex regular expression pattern<br \/>\n[\u6b63\u5219\u8868\u8fbe\u5f0f\uff08\u6b63\u5219\u8868\u8fbe\u5f0f\uff09]- \u9a8c\u8bc1\u5c5e\u6027\u662f\u5426\u7b26\u5408 regex \u6b63\u5219\u8868\u8fbe\u5f0f\u6a21\u5f0f<\/p>\n<\/li>\n<li>\n<p>[Url]\u2014Validates that a property has a valid URL format<br \/>\n[\u7f51\u5740]- \u9a8c\u8bc1\u5c5e\u6027\u662f\u5426\u5177\u6709\u6709\u6548\u7684 URL \u683c\u5f0f<\/p>\n<\/li>\n<li>\n<p>[Required]\u2014Indicates that the property must not be null<br \/>\n[\u5fc5\u586b]- \u6307\u793a\u5c5e\u6027\u4e0d\u80fd\u4e3a null<\/p>\n<\/li>\n<li>\n<p>[Compare]\u2014Allows you to confirm that two properties have the same value (such as Email and ConfirmEmail)<br \/>\n[\u6bd4\u8f83]- \u5141\u8bb8\u60a8\u786e\u8ba4\u4e24\u4e2a\u5c5e\u6027\u5177\u6709\u76f8\u540c\u7684\u503c\uff08\u4f8b\u5982 Email \u548c ConfirmEmail)<\/p>\n<\/li>\n<\/ul>\n<p>Warning The [EmailAddress] and [Phone] attributes validate only that the format of the value is potentially correct. They don\u2019t validate that the email address or phone number exists. For an example of how to do more rigorous phone number validation, see this post on the Twilio blog: <a href=\"http:\/\/mng.bz\/xmZe\">http:\/\/mng.bz\/xmZe<\/a>.<br \/>\n\u8b66\u544a [EmailAddress] \u548c [Phone] \u5c5e\u6027\u4ec5\u9a8c\u8bc1\u503c\u7684\u683c\u5f0f\u662f\u5426\u53ef\u80fd\u6b63\u786e\u3002\u5b83\u4eec\u4e0d\u4f1a\u9a8c\u8bc1\u7535\u5b50\u90ae\u4ef6\u5730\u5740\u6216\u7535\u8bdd\u53f7\u7801\u662f\u5426\u5b58\u5728\u3002\u6709\u5173\u5982\u4f55\u6267\u884c\u66f4\u4e25\u683c\u7684\u7535\u8bdd\u53f7\u7801\u9a8c\u8bc1\u7684\u793a\u4f8b\uff0c\u8bf7\u53c2\u9605 Twilio \u535a\u5ba2\u4e0a\u7684\u8fd9\u7bc7\u6587\u7ae0\uff1a<a href=\"http:\/\/mng.bz\/xmZe\">http:\/\/mng.bz\/xmZe<\/a>\u3002<\/p>\n<p>The DataAnnotations attributes aren\u2019t new; they\u2019ve been part of the .NET Framework since version 3.5, and their use in ASP.NET Core is almost the same as in the previous version of ASP.NET. They\u2019re also used for purposes other than validation. Entity Framework Core (among others) uses DataAnnotations to define the types of columns and rules to use when creating database tables from C# classes. You can read more about Entity Framework Core in chapter 12 and in Entity Framework Core in Action, 2nd ed., by Jon P. Smith (Manning, 2021).<\/p>\n<p>DataAnnotations \u5c5e\u6027\u5e76\u4e0d\u65b0\u9c9c;\u5b83\u4eec\u81ea 3.5 \u7248\u4ee5\u6765\u4e00\u76f4\u662f .NET Framework \u7684\u4e00\u90e8\u5206\uff0c\u5b83\u4eec\u5728 ASP.NET Core \u4e2d\u7684\u4f7f\u7528\u4e0e\u5728\u4ee5\u524d\u7248\u672c\u7684 ASP.NET \u4e2d\u7684\u4f7f\u7528\u51e0\u4e4e\u76f8\u540c\u3002\u5b83\u4eec\u8fd8\u7528\u4e8e\u9a8c\u8bc1\u4ee5\u5916\u7684\u76ee\u7684\u3002Entity Framework Core\uff08\u4ee5\u53ca\u5176\u4ed6\uff09\u4f7f\u7528 DataAnnotations \u6765\u5b9a\u4e49\u4ece C# \u7c7b\u521b\u5efa\u6570\u636e\u5e93\u8868\u65f6\u8981\u4f7f\u7528\u7684\u5217\u548c\u89c4\u5219\u7684\u7c7b\u578b\u3002\u60a8\u53ef\u4ee5\u5728\u7b2c 12 \u7ae0\u548c Jon P. Smith \u7684 Entity Framework Core in Action\uff0c 2nd ed.\uff08Manning\uff0c2021 \u5e74\uff09\u4e2d\u9605\u8bfb\u6709\u5173 Entity Framework Core \u7684\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<p>If the DataAnnotation attributes provided out of the box don\u2019t cover everything you need, it\u2019s possible to write custom attributes by deriving from the base ValidationAttribute. You\u2019ll see how to create a custom validation attribute in chapter 32.<\/p>\n<p>\u5982\u679c\u73b0\u6210\u63d0\u4f9b\u7684 DataAnnotation \u5c5e\u6027\u4e0d\u80fd\u6db5\u76d6\u60a8\u9700\u8981\u7684\u6240\u6709\u5185\u5bb9\uff0c\u5219\u53ef\u4ee5\u901a\u8fc7\u4ece\u57fa ValidationAttribute \u6d3e\u751f\u6765\u7f16\u5199\u81ea\u5b9a\u4e49\u5c5e\u6027\u3002\u60a8\u5c06\u5728\u7b2c 32 \u7ae0\u4e2d\u4e86\u89e3\u5982\u4f55\u521b\u5efa\u81ea\u5b9a\u4e49\u9a8c\u8bc1\u5c5e\u6027\u3002<\/p>\n<p>One common limitation with DataAnnotation attributes is that it\u2019s hard to validate properties that depend on the values of other properties. Maybe the UserModel type from listing 7.10 requires you to provide either an email address or a phone number but not both, which is hard to achieve with attributes. In this type of situation, you can implement IValidatableObject in your models instead of, or in addition to, using attributes. In listing 7.11, a validation rule is added to UserModel whether the email or phone number is provided. If it isn\u2019t, Validate() returns a ValidationResult describing the problem.<\/p>\n<p>DataAnnotation \u5c5e\u6027\u7684\u4e00\u4e2a\u5e38\u89c1\u9650\u5236\u662f\u5f88\u96be\u9a8c\u8bc1\u4f9d\u8d56\u4e8e\u5176\u4ed6\u5c5e\u6027\u7684\u503c\u7684\u5c5e\u6027\u3002\u4e5f\u8bb8\u6e05\u5355 7.10 \u4e2d\u7684 UserModel \u7c7b\u578b\u8981\u6c42\u60a8\u63d0\u4f9b\u7535\u5b50\u90ae\u4ef6\u5730\u5740\u6216\u7535\u8bdd\u53f7\u7801\uff0c\u4f46\u4e0d\u80fd\u540c\u65f6\u63d0\u4f9b\u4e24\u8005\uff0c\u8fd9\u5f88\u96be\u901a\u8fc7\u5c5e\u6027\u5b9e\u73b0\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u60a8\u53ef\u4ee5\u5728\u6a21\u578b\u4e2d\u5b9e\u73b0 IValidatableObject \uff0c\u800c\u4e0d\u662f\u4f7f\u7528\u5c5e\u6027\uff0c\u6216\u8005\u540c\u65f6\u4f7f\u7528\u5c5e\u6027\u3002\u5728\u5217\u8868 7.11 \u4e2d\uff0c\u65e0\u8bba\u63d0\u4f9b\u4e86\u7535\u5b50\u90ae\u4ef6\u8fd8\u662f\u7535\u8bdd\u53f7\u7801\uff0c\u90fd\u4f1a\u5411 UserModel \u6dfb\u52a0\u9a8c\u8bc1\u89c4\u5219\u3002\u5982\u679c\u4e0d\u662f\uff0c\u5219 Validate\uff08\uff09 \u8fd4\u56de\u63cf\u8ff0\u95ee\u9898\u7684 ValidationResult\u3002<\/p>\n<p>Listing 7.11 Implementing IValidatableObject<br \/>\n\u6e05\u5355 7.11 \u5b9e\u73b0 IValidatableObject<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing System.ComponentModel.DataAnnotations;\npublic record CreateUserModel : IValidatableObject               \/\/ \u2776\n{\n    &#x5B;EmailAddress]                                               \/\/ \u2777\n    public string Email { get; set; }\n\n    &#x5B;Phone]                                                      \/\/ \u2777\n    public string PhoneNumber { get; set; }\n\n    public IEnumerable&lt;ValidationResult&gt; Validate(               \/\/ \u2778\n        ValidationContext validationContext)                     \/\/ \u2778\n    {\n        if(string.IsNullOrEmpty(Email)                           \/\/ \u2779\n        &amp;&amp; string.IsNullOrEmpty(PhoneNumber))                    \/\/ \u2779\n        {\n            yield return new ValidationResult(                   \/\/ \u277a\n            &quot;You must provide an Email or a PhoneNumber&quot;,        \/\/ \u277a\n            New&#x5B;] { nameof(Email), nameof(PhoneNumber) });       \/\/ \u277a\n        }\n    }\n}\n<\/pre>\n<p>\u2776 Implements the IValidatableObject interface<br \/>\n\u5b9e\u73b0 IValidatableObject \u63a5\u53e3<\/p>\n<p>\u2777 The DataAnnotation attributes continue to validate basic format requirements.<br \/>\nDataAnnotation \u5c5e\u6027\u7ee7\u7eed\u9a8c\u8bc1\u57fa\u672c\u683c\u5f0f\u8981\u6c42\u3002<\/p>\n<p>\u2778 Validate is the only function to implement in IValidatableObject.<br \/>\nValidate \u662f\u5728 IValidatableObject \u4e2d\u5b9e\u73b0\u7684\u552f\u4e00\u51fd\u6570\u3002<\/p>\n<p>\u2779 Checks whether the object is valid . . .<br \/>\n\u68c0\u67e5\u5bf9\u8c61\u662f\u5426\u6709\u6548 . . .<\/p>\n<p>\u277a . . . and if not, returns a result describing the error<br \/>\n. . . .\u5982\u679c\u4e0d\u662f\uff0c\u5219\u8fd4\u56de\u63cf\u8ff0\u9519\u8bef\u7684\u7ed3\u679c<\/p>\n<p>IValidatableObject helps cover some of the cases that attributes alone can\u2019t handle, but it\u2019s not always the best option. The Validate function doesn\u2019t give easy access to your app\u2019s services, and the function executes only if all the DataAnnotation attribute conditions are met.<\/p>\n<p>IValidatableObject \u6709\u52a9\u4e8e\u6db5\u76d6\u67d0\u4e9b\u4ec5\u9760\u5c5e\u6027\u65e0\u6cd5\u5904\u7406\u7684\u60c5\u51b5\uff0c\u4f46\u5b83\u5e76\u4e0d\u603b\u662f\u6700\u4f73\u9009\u62e9\u3002Validate \u51fd\u6570\u65e0\u6cd5\u8f7b\u677e\u8bbf\u95ee\u5e94\u7528\u7a0b\u5e8f\u7684\u670d\u52a1\uff0c\u5e76\u4e14\u4ec5\u5f53\u6ee1\u8db3\u6240\u6709 DataAnnotation \u5c5e\u6027\u6761\u4ef6\u65f6\uff0c\u8be5\u51fd\u6570\u624d\u4f1a\u6267\u884c\u3002<\/p>\n<p><strong>Tip<\/strong> DataAnnotations are good for input validation of properties in isolation but not so good for validating complex business rules. You\u2019ll most likely need to perform this validation outside the DataAnnotations framework.<br \/>\n<strong>\u63d0\u793a<\/strong> DataAnnotations \u9002\u7528\u4e8e\u9694\u79bb\u5c5e\u6027\u7684\u8f93\u5165\u9a8c\u8bc1\uff0c\u4f46\u4e0d\u592a\u9002\u5408\u9a8c\u8bc1\u590d\u6742\u7684\u4e1a\u52a1\u89c4\u5219\u3002\u60a8\u5f88\u53ef\u80fd\u9700\u8981\u5728 DataAnnotations \u6846\u67b6\u4e4b\u5916\u6267\u884c\u6b64\u9a8c\u8bc1\u3002<\/p>\n<p>Alternatively, if you\u2019re not a fan of the DataAnnotation attribute-based-plus-IValidatableObject approach, you could use the popular FluentValidation library (<a href=\"https:\/\/github.com\/JeremySkinner\/FluentValidation\">https:\/\/github.com\/JeremySkinner\/FluentValidation<\/a>) in your minimal APIs instead. Minimal APIs are completely flexible, so you can use whichever approach you prefer.<\/p>\n<p>\u6216\u8005\uff0c\u5982\u679c\u60a8\u4e0d\u559c\u6b22 DataAnnotation \u57fa\u4e8e\u5c5e\u6027\u7684\u52a0 IValidatableObject \u65b9\u6cd5\uff0c\u5219\u53ef\u4ee5\u5728\u6700\u5c0f API \u4e2d\u4f7f\u7528\u6d41\u884c\u7684 FluentValidation \u5e93 \uff08<a href=\"https:\/\/github.com\/JeremySkinner\/FluentValidation\uff09\u3002\u6700\u5c0f\">https:\/\/github.com\/JeremySkinner\/FluentValidation\uff09\u3002\u6700\u5c0f<\/a> API \u662f\u5b8c\u5168\u7075\u6d3b\u7684\uff0c\u56e0\u6b64\u60a8\u53ef\u4ee5\u4f7f\u7528\u81ea\u5df1\u559c\u6b22\u7684\u4efb\u4f55\u65b9\u6cd5\u3002<\/p>\n<p>DataAnnotations attributes provide the basic metadata for validation, but no part of listing 7.10 or listing 7.11 uses the validation attributes you added. You still need to add code to read the parameter type\u2019s metadata, check whether the data is valid, and return an error response if it\u2019s invalid. ASP.NET Core doesn\u2019t include a dedicated validation API for that task in minimal APIs, but you can easily add it with a small NuGet package.<\/p>\n<p>DataAnnotations \u5c5e\u6027\u63d0\u4f9b\u4e86\u7528\u4e8e\u9a8c\u8bc1\u7684\u57fa\u672c\u5143\u6570\u636e\uff0c\u4f46\u6e05\u5355 7.10 \u6216\u6e05\u5355 7.11 \u7684\u4efb\u4f55\u90e8\u5206\u90fd\u6ca1\u6709\u4f7f\u7528\u60a8\u6dfb\u52a0\u7684\u9a8c\u8bc1\u5c5e\u6027\u3002\u60a8\u4ecd\u7136\u9700\u8981\u6dfb\u52a0code \u8bfb\u53d6\u53c2\u6570\u7c7b\u578b\u7684\u5143\u6570\u636e\uff0c\u68c0\u67e5\u6570\u636e\u662f\u5426\u6709\u6548\uff0c\u5982\u679c\u65e0\u6548\uff0c\u5219\u8fd4\u56de\u9519\u8bef\u54cd\u5e94\u3002ASP.NET Core \u5728\u6700\u5c11\u7684 API \u4e2d\u4e0d\u5305\u542b\u7528\u4e8e\u8be5\u4efb\u52a1\u7684\u4e13\u7528\u9a8c\u8bc1 API\uff0c\u4f46\u4f60\u53ef\u4ee5\u4f7f\u7528\u5c0f\u578b NuGet \u5305\u8f7b\u677e\u6dfb\u52a0\u5b83\u3002<\/p>\n<h3>7.10.3 Adding a validation filter to your minimal APIs<\/h3>\n<h3>7.10.3 \u5c06\u9a8c\u8bc1\u7b5b\u9009\u5668\u6dfb\u52a0\u5230\u6700\u5c0f API<\/h3>\n<p>Microsoft decided not to include any dedicated validation APIs in minimal APIs. By contrast, validation is a built-in core feature of Razor Pages and MVC. Microsoft\u2019s reasoning was that the company wanted to provide flexibility and choice for users to add validation in the way that works best for them, but didn\u2019t want to affect performance for those who didn\u2019t want to use their implementation.<\/p>\n<p>Microsoft \u51b3\u5b9a\u4e0d\u5728\u6700\u5c0f API \u4e2d\u5305\u542b\u4efb\u4f55\u4e13\u7528\u7684\u9a8c\u8bc1 API\u3002\u76f8\u6bd4\u4e4b\u4e0b\uff0c\u9a8c\u8bc1\u662f Razor Pages \u548c MVC \u7684\u5185\u7f6e\u6838\u5fc3\u529f\u80fd\u3002Microsoft \u7684\u7406\u7531\u662f\uff0c\u8be5\u516c\u53f8\u5e0c\u671b\u4e3a\u7528\u6237\u63d0\u4f9b\u7075\u6d3b\u6027\u548c\u9009\u62e9\uff0c\u4ee5\u4fbf\u4ee5\u6700\u9002\u5408\u4ed6\u4eec\u7684\u65b9\u5f0f\u6dfb\u52a0\u9a8c\u8bc1\uff0c\u4f46\u53c8\u4e0d\u60f3\u5f71\u54cd\u90a3\u4e9b\u4e0d\u60f3\u4f7f\u7528\u5176\u5b9e\u73b0\u7684\u4eba\u7684\u6027\u80fd\u3002<\/p>\n<p>Consequently, validation in minimal APIs typically relies on the filter pipeline. As a classic cross-cutting concern, validation is a good fit for a filter. The only downside is that typically, you need to write your own filter rather than use an existing API. The positive side is that validation gives you complete flexibility, including the ability to use an alternative validation library (such as FluentValidation) if you prefer.<\/p>\n<p>\u56e0\u6b64\uff0c\u6700\u5c0f API \u4e2d\u7684\u9a8c\u8bc1\u901a\u5e38\u4f9d\u8d56\u4e8e\u7b5b\u9009\u5668\u7ba1\u9053\u3002\u4f5c\u4e3a\u4e00\u4e2a\u5178\u578b\u7684\u6a2a\u5207\u5173\u6ce8\u70b9\uff0c\u9a8c\u8bc1\u975e\u5e38\u9002\u5408 filter\u3002\u552f\u4e00\u7684\u7f3a\u70b9\u662f\uff0c\u901a\u5e38\u60a8\u9700\u8981\u7f16\u5199\u81ea\u5df1\u7684\u8fc7\u6ee4\u5668\uff0c\u800c\u4e0d\u662f\u4f7f\u7528\u73b0\u6709\u7684 API\u3002\u79ef\u6781\u7684\u4e00\u9762\u662f\uff0c\u9a8c\u8bc1\u4e3a\u60a8\u63d0\u4f9b\u4e86\u5b8c\u5168\u7684\u7075\u6d3b\u6027\uff0c\u5305\u62ec\u5982\u679c\u60a8\u613f\u610f\uff0c\u53ef\u4ee5\u4f7f\u7528\u66ff\u4ee3\u9a8c\u8bc1\u5e93\uff08\u4f8b\u5982 FluentValidation\uff09\u3002<\/p>\n<p>Luckily, Damian Edwards, a project manager architect on the ASP.NET Core team at Microsoft, has a NuGet package called MinimalApis.Extensions that provides the filter for you. Using a simple validation system that hooks into the DataAnnotations on your models, this NuGet package provides an extension method called WithParameterValidation() that you can add to your endpoints. To add the package, search for MinimalApis.Extensions from the NuGet Package Manager in your IDE (be sure to include prerelease versions), or run the following, using the .NET command-line interface:<\/p>\n<p>\u5e78\u8fd0\u7684\u662f\uff0cMicrosoft ASP.NET Core \u56e2\u961f\u7684\u9879\u76ee\u7ecf\u7406\u67b6\u6784\u5e08 Damian Edwards \u6709\u4e00\u4e2a\u540d\u4e3a MinimalApis.Extensions \u7684 NuGet \u5305\uff0c\u53ef\u4ee5\u4e3a\u60a8\u63d0\u4f9b\u7b5b\u9009\u5668\u3002\u4f7f\u7528\u6302\u63a5\u5230\u6a21\u578b\u4e0a\u7684 DataAnnotations \u7684\u7b80\u5355\u9a8c\u8bc1\u7cfb\u7edf\uff0c\u6b64 NuGet \u5305\u63d0\u4f9b\u4e86\u4e00\u4e2a\u540d\u4e3aWithParameterValidation\uff08\uff09 \u4e2d\uff0c\u60a8\u53ef\u4ee5\u5c06\u5176\u6dfb\u52a0\u5230\u7ec8\u7aef\u8282\u70b9\u4e2d\u3002\u82e5\u8981\u6dfb\u52a0\u5305\uff0c\u8bf7\u4ece IDE \u4e2d\u7684 NuGet \u5305\u7ba1\u7406\u5668\u4e2d\u641c\u7d22 MinimalApis.Extensions\uff08\u8bf7\u52a1\u5fc5\u5305\u542b\u9884\u53d1\u884c\u7248\u672c\uff09\uff0c\u6216\u4f7f\u7528 .NET \u547d\u4ee4\u884c\u754c\u9762\u8fd0\u884c\u4ee5\u4e0b\u547d\u4ee4\uff1a<\/p>\n<pre><code>dotnet add package MinimalApis.Extensions<\/code><\/pre>\n<p>After you\u2019ve added the package, you can add validation to any of your endpoints by adding a filter using WithParameterValidation(), as shown in listing 7.12. After the UserModel is bound to the JSON body of the request, the validation filter executes as part of the filter pipeline. If the user parameter is valid, execution passes to the endpoint handler. If the parameter is invalid, a 400 Bad Request Problem Details response is returned containing a description of the errors, as shown in figure 7.8.<\/p>\n<p>\u6dfb\u52a0\u5305\u540e\uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7\u4f7f\u7528 WithParameterValidation\uff08\uff09 \u6dfb\u52a0\u8fc7\u6ee4\u5668\u6765\u5411\u4efb\u4f55\u7aef\u70b9\u6dfb\u52a0\u9a8c\u8bc1\uff0c\u5982\u6e05\u5355 7.12 \u6240\u793a\u3002\u5c06 UserModel \u7ed1\u5b9a\u5230\u8bf7\u6c42\u7684 JSON \u6b63\u6587\u540e\uff0c\u9a8c\u8bc1\u7b5b\u9009\u6761\u4ef6\u5c06\u4f5c\u4e3a\u7b5b\u9009\u6761\u4ef6\u7ba1\u9053\u7684\u4e00\u90e8\u5206\u6267\u884c\u3002\u5982\u679c user \u53c2\u6570\u6709\u6548\uff0c\u5219\u6267\u884c\u5c06\u4f20\u9012\u7ed9\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u3002\u5982\u679c\u8be5\u53c2\u6570\u65e0\u6548\uff0c\u5219\u8fd4\u56de 400 Bad Request Problem Details \u54cd\u5e94\uff0c\u5176\u4e2d\u5305\u542b\u9519\u8bef\u63cf\u8ff0\uff0c\u5982\u56fe 7.8 \u6240\u793a\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/0708.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 7.8 If the data sent in the request body is not valid, the validation filter automatically returns a 400 Bad Request response, containing the validation errors, and the endpoint handler doesn\u2019t execute.<br \/>\n\u56fe 7.8 \u5982\u679c\u8bf7\u6c42\u6b63\u6587\u4e2d\u53d1\u9001\u7684\u6570\u636e\u65e0\u6548\uff0c\u5219\u9a8c\u8bc1\u7b5b\u9009\u6761\u4ef6\u4f1a\u81ea\u52a8\u8fd4\u56de 400 Bad Request \u54cd\u5e94\uff0c\u5176\u4e2d\u5305\u542b\u9a8c\u8bc1\u9519\u8bef\uff0c\u5e76\u4e14\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e0d\u4f1a\u6267\u884c\u3002<\/p>\n<p>Listing 7.12 Adding validation to minimal APIs using MinimalApis. Extensions<br \/>\n\u6e05\u5355 7.12 \u4f7f\u7528 MinimalApis.Extensions \u5411\u6700\u5c0f API \u6dfb\u52a0\u9a8c\u8bc1<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing System.ComponentModel.DataAnnotations;\n\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\napp.MapPost(&quot;\/users&quot;, (UserModel user) =&gt; user.ToString())\n    .WithParameterValidation();                               \/\/ \u2776\napp.Run();\n\npublic record UserModel                                       \/\/ \u2777\n{\n    &#x5B;Required]\n    &#x5B;StringLength(100)]\n    &#x5B;Display(Name = &quot;Your name&quot;)]\n    public string Name { get; set; }\n\n    &#x5B;Required]\n    &#x5B;EmailAddress]\n    public string Email { get; set; }\n}\n<\/pre>\n<p>\u2776 Adds the validation filter to the endpoint<br \/>\n\u5c06\u9a8c\u8bc1\u8fc7\u6ee4\u5668\u6dfb\u52a0\u5230\u7aef\u70b9<\/p>\n<p>\u2777 The UserModel defines its validation requirements using DataAnnotations<br \/>\nattributes.<br \/>\nUserModel \u4f7f\u7528 DataAnnotations \u5c5e\u6027\u5b9a\u4e49\u5176\u9a8c\u8bc1\u8981\u6c42\u3002<\/p>\n<p>Listing 7.12 shows how you can validate a complex type, but in some cases, you may want to validate simple types. You may want to validate that the id value in the following handler should be between 1 and 100:<\/p>\n<p>\u6e05\u5355 7.12 \u5c55\u793a\u4e86\u5982\u4f55\u9a8c\u8bc1\u590d\u6742\u7c7b\u578b\uff0c\u4f46\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\uff0c\u4f60\u53ef\u80fd\u60f3\u8981\u9a8c\u8bc1\u7b80\u5355\u7c7b\u578b\u3002\u60a8\u53ef\u80fd\u9700\u8981\u9a8c\u8bc1\u4ee5\u4e0b\u5904\u7406\u7a0b\u5e8f\u4e2d\u7684 id \u503c\u662f\u5426\u5e94\u4ecb\u4e8e 1 \u548c 100 \u4e4b\u95f4\uff1a<\/p>\n<pre><code>app.MapGet(&quot;\/user\/{id}&quot;, (int id) =&gt; $&quot;Received {id}&quot;)\n    .WithParameterValidation();\n<\/code><\/pre>\n<p>Unfortunately, that\u2019s not easy to do with DataAnnotations attributes. The validation filter will check the int type, see that it\u2019s not a type that has any DataAnnotations on its properties, and won\u2019t validate it.<\/p>\n<p>\u9057\u61be\u7684\u662f\uff0c\u4f7f\u7528 DataAnnotations \u5c5e\u6027\u5e76\u4e0d\u5bb9\u6613\u505a\u5230\u8fd9\u4e00\u70b9\u3002\u9a8c\u8bc1\u7b5b\u9009\u5668\u5c06\u68c0\u67e5 int \u7c7b\u578b\uff0c\u67e5\u770b\u5b83\u4e0d\u662f\u5728\u5176\u5c5e\u6027\u4e0a\u5177\u6709\u4efb\u4f55 DataAnnotations \u7684\u7c7b\u578b\uff0c\u5e76\u4e14\u4e0d\u4f1a\u5bf9\u5176\u8fdb\u884c\u9a8c\u8bc1\u3002<\/p>\n<p><strong>Warning<\/strong> Adding attributes to the handler, as in ([Range(1, 100)] int id), doesn\u2019t work. The attributes here are added to the parameter, not to properties of the int type, so the validator won\u2019t find them.<br \/>\n<strong>\u8b66\u544a<\/strong> \u5411\u5904\u7406\u7a0b\u5e8f\u6dfb\u52a0\u5c5e\u6027\uff08\u5982 \uff08[Range\uff081\uff0c 100\uff09] int id\uff09 \uff09\u4e0d\u8d77\u4f5c\u7528\u3002\u8fd9\u91cc\u7684 attributes \u662f\u6dfb\u52a0\u5230 parameter \u7684\uff0c\u800c\u4e0d\u662f int \u7c7b\u578b\u7684 properties \u4e2d\uff0c\u6240\u4ee5 validator \u4e0d\u4f1a\u627e\u5230\u5b83\u4eec\u3002<\/p>\n<p>There are several ways around this problem, but the simplest is to use the [AsParameters] attribute you saw in section 7.9 and apply annotations to the model. The following listing shows how.<\/p>\n<p>\u6709\u51e0\u79cd\u65b9\u6cd5\u53ef\u4ee5\u89e3\u51b3\u6b64\u95ee\u9898\uff0c\u4f46\u6700\u7b80\u5355\u7684\u65b9\u6cd5\u662f\u4f7f\u7528\u60a8\u5728 \u5c0f\u8282 \u4e2d\u770b\u5230\u7684 [AsParameters] \u5c5e\u60277.9 \u5e76\u5c06\u6ce8\u91ca\u5e94\u7528\u4e8e\u6a21\u578b\u3002\u4e0b\u9762\u7684\u6e05\u5355\u663e\u793a\u4e86\u5982\u4f55\u4f5c\u3002<\/p>\n<p>Listing 7.13 Adding validation to minimal APIs using MinimalApis.Extensions<br \/>\n\u6e05\u5355 7.13 \u4f7f\u7528MinimalApis.\u6269\u5c55<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing System.ComponentModel.DataAnnotations;\n\nWebApplicationBuilder builder = WebApplication.CreateBuilder(args);\nWebApplication app = builder.Build();\n\napp.MapPost(&quot;\/user\/{id}&quot;,\n    (&#x5B;AsParameters] GetUserModel model) =&gt; $&quot;Received {model.Id}&quot;)       \/\/ \u2776\n    .WithParameterValidation();                                          \/\/ \u2777\n\napp.Run();\n\nstruct GetUserModel\n{\n    &#x5B;Range(1, 10)]                                                       \/\/ \u2778\n    Public int Id { get; set; }                                          \/\/ \u2778\n}\n<\/pre>\n<p>\u2776 Uses [AsParameters] to create a type than can be validated<br \/>\n\u4f7f\u7528 [AsParameters] \u521b\u5efa\u53ef\u9a8c\u8bc1\u7684\u7c7b\u578b<\/p>\n<p>\u2777 Adds the validation filter to the endpoint<br \/>\n\u5c06\u9a8c\u8bc1\u7b5b\u9009\u5668\u6dfb\u52a0\u5230\u7aef\u70b9<\/p>\n<p>\u2778 Adds validation attributes to your simple types<br \/>\n\u5c06\u9a8c\u8bc1\u5c5e\u6027\u6dfb\u52a0\u5230\u7b80\u5355\u7c7b\u578b<\/p>\n<p>That concludes this look at model binding in minimal APIs. You saw how the ASP.NET Core framework uses model binding to simplify the process of extracting values from a request and turning them into normal .NET objects you can work with quickly. The many ways to bind may be making your head spin, but normally, you can stick to the basics and fall back to the more complex types as and when you need them.<\/p>\n<p>\u5bf9\u6700\u5c0f API \u4e2d\u7684\u6a21\u578b\u7ed1\u5b9a\u7684\u4ecb\u7ecd\u5230\u6b64\u7ed3\u675f\u3002\u60a8\u4e86\u89e3\u4e86 ASP.NET Core \u6846\u67b6\u5982\u4f55\u4f7f\u7528\u6a21\u578b\u7ed1\u5b9a\u6765\u7b80\u5316\u4ece\u8bf7\u6c42\u4e2d\u63d0\u53d6\u503c\u5e76\u5c06\u5176\u8f6c\u6362\u4e3a\u53ef\u5feb\u901f\u4f7f\u7528\u7684\u666e\u901a .NET \u5bf9\u8c61\u7684\u8fc7\u7a0b\u3002\u8bb8\u591a\u7ed1\u5b9a\u65b9\u5f0f\u53ef\u80fd\u4f1a\u8ba9\u60a8\u5934\u6655\u76ee\u7729\uff0c\u4f46\u901a\u5e38\u60c5\u51b5\u4e0b\uff0c\u60a8\u53ef\u4ee5\u575a\u6301\u4f7f\u7528\u57fa\u672c\u65b9\u6cd5\uff0c\u5e76\u5728\u9700\u8981\u65f6\u56de\u9000\u5230\u66f4\u590d\u6742\u7684\u7c7b\u578b\u3002<\/p>\n<p>Although the discussion is short, the most important aspect of this chapter is its focus on validation\u2014a common concern for all web applications. Whether you choose to use DataAnnotations or a different validation approach, you must make sure to validate any data you receive in all your endpoints.<\/p>\n<p>\u867d\u7136\u8ba8\u8bba\u5f88\u7b80\u77ed\uff0c\u4f46\u672c\u7ae0\u6700\u91cd\u8981\u7684\u65b9\u9762\u662f\u5b83\u5bf9\u9a8c\u8bc1\u7684\u5173\u6ce8\u2014\u2014\u4e00\u4e2a\u5171\u540c\u7684\u5173\u6ce8\u70b9\u9002\u7528\u4e8e\u6240\u6709 Web \u5e94\u7528\u7a0b\u5e8f\u3002\u65e0\u8bba\u60a8\u9009\u62e9\u4f7f\u7528 DataAnnotations \u8fd8\u662f\u5176\u4ed6\u9a8c\u8bc1\u65b9\u6cd5\uff0c\u90fd\u5fc5\u987b\u786e\u4fdd\u9a8c\u8bc1\u60a8\u5728\u6240\u6709\u7ec8\u7aef\u8282\u70b9\u4e2d\u6536\u5230\u7684\u4efb\u4f55\u6570\u636e\u3002<\/p>\n<p>In chapter 8 we leave minimal APIs behind to look at dependency injection in ASP.NET Core and see how it helps create loosely coupled applications. You\u2019ll learn how to register the ASP.NET Core framework services with a container, add your own services, and manage service lifetimes.<\/p>\n<p>\u5728\u7b2c 8 \u7ae0\u4e2d\uff0c\u6211\u4eec\u629b\u5f03\u4e86\u6700\u5c11\u7684 API\uff0c\u770b\u770b ASP.NET Core \u4e2d\u7684\u4f9d\u8d56\u6ce8\u5165\uff0c\u770b\u770b\u5b83\u5982\u4f55\u5e2e\u52a9\u521b\u5efa\u677e\u6563\u8026\u5408\u7684\u5e94\u7528\u7a0b\u5e8f\u3002\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u5411\u5bb9\u5668\u6ce8\u518c ASP.NET Core \u6846\u67b6\u670d\u52a1\u3001\u6dfb\u52a0\u60a8\u81ea\u5df1\u7684\u670d\u52a1\u4ee5\u53ca\u7ba1\u7406\u670d\u52a1\u751f\u547d\u5468\u671f\u3002<\/p>\n<h2>7.11 Summary<\/h2>\n<h2>7.11 \u603b\u7ed3<\/h2>\n<p>Model binding is the process of creating the arguments for endpoint handlers from the details of an HTTP request. Model binding takes care of extracting and parsing the strings in the request so that you don\u2019t have to.<br \/>\n\u6a21\u578b\u7ed1\u5b9a\u662f\u6839\u636e HTTP \u8bf7\u6c42\u7684\u8be6\u7ec6\u4fe1\u606f\u4e3a\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u521b\u5efa\u53c2\u6570\u7684\u8fc7\u7a0b\u3002\u6a21\u578b\u7ed1\u5b9a\u8d1f\u8d23\u63d0\u53d6\u548c\u5206\u6790\u8bf7\u6c42\u4e2d\u7684\u5b57\u7b26\u4e32\uff0c\u56e0\u6b64\u60a8\u4e0d\u5fc5\u8fd9\u6837\u505a\u3002<\/p>\n<p>Simple values such as int, string, and double can bind to route values, query string values, and headers. These values are common and easy to extract from the request without any manual parsing.<br \/>\n\u7b80\u5355\u503c\uff08\u5982 int\u3001string \u548c double\uff09\u53ef\u4ee5\u7ed1\u5b9a\u5230\u8def\u7531\u503c\u3001\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\u548c\u6807\u5934\u3002\u8fd9\u4e9b\u503c\u5f88\u5e38\u89c1\uff0c\u5e76\u4e14\u5f88\u5bb9\u6613\u4ece\u8bf7\u6c42\u4e2d\u63d0\u53d6\uff0c\u800c\u65e0\u9700\u4efb\u4f55\u624b\u52a8\u89e3\u6790\u3002<\/p>\n<p>If a simple value fails to bind because the value in the request is incompatible with the handler parameter, a BadHttpRequestException is thrown, and a 400 Bad Request response is returned.<br \/>\n\u5982\u679c\u7531\u4e8e\u8bf7\u6c42\u4e2d\u7684\u503c\u4e0e handler \u53c2\u6570\u4e0d\u517c\u5bb9\u800c\u5bfc\u81f4 simple \u503c\u7ed1\u5b9a\u5931\u8d25\uff0c\u5219\u4f1a\u5f15\u53d1 BadHttpRequestException\uff0c\u5e76\u8fd4\u56de 400 Bad Request \u54cd\u5e94\u3002<\/p>\n<p>You can turn a custom type into a simple type by adding a TryParse method with the signature bool TryParse(string value, out T result). If you return false from this method, minimal APIs will return a 400 Bad Request response.<br \/>\n\u60a8\u53ef\u4ee5\u901a\u8fc7\u6dfb\u52a0\u5177\u6709\u7b7e\u540d bool TryParse\uff08string value\uff0c out T result\uff09 \u7684 TryParse \u65b9\u6cd5\uff0c\u5c06\u81ea\u5b9a\u4e49\u7c7b\u578b\u8f6c\u6362\u4e3a\u7b80\u5355\u7c7b\u578b\u3002\u5982\u679c\u4ece\u6b64\u65b9\u6cd5\u8fd4\u56de false\uff0c\u5219\u6700\u5c11\u7684 API \u5c06\u8fd4\u56de 400 Bad Request \u54cd\u5e94\u3002<\/p>\n<p>Complex types bind to the request body by default by deserializing from JSON. Minimal APIs can bind only to JSON bodies; you can\u2019t use model binding to access form values.<br \/>\n\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u590d\u6742\u7c7b\u578b\u901a\u8fc7\u4ece JSON \u53cd\u5e8f\u5217\u5316\u6765\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6b63\u6587\u3002\u6700\u5c0f API \u53ea\u80fd\u7ed1\u5b9a\u5230 JSON \u6b63\u6587;\u60a8\u4e0d\u80fd\u4f7f\u7528\u6a21\u578b\u7ed1\u5b9a\u6765\u8bbf\u95ee\u8868\u5355\u503c\u3002<\/p>\n<p>By default, you can\u2019t bind the body of GET requests, which goes against the expectations for GET requests. Doing so will cause an exception at runtime.<br \/>\n\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u60a8\u65e0\u6cd5\u7ed1\u5b9a GET \u8bf7\u6c42\u7684\u6b63\u6587\uff0c\u56e0\u4e3a\u8fd9\u4e0e GET \u8bf7\u6c42\u7684\u9884\u671f\u80cc\u9053\u800c\u9a70\u3002\u8fd9\u6837\u505a\u4f1a\u5bfc\u81f4\u8fd0\u884c\u65f6\u51fa\u73b0\u5f02\u5e38\u3002<\/p>\n<p>Arrays of simple types bind by default to query string values for GET requests and to the request body for POST requests. This difference can cause confusion, so always consider whether an array is the best option.<br \/>\n\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u7b80\u5355\u7c7b\u578b\u7684\u6570\u7ec4\u7ed1\u5b9a\u5230 GET \u8bf7\u6c42\u7684\u67e5\u8be2\u5b57\u7b26\u4e32\u503c\u548c POST \u8bf7\u6c42\u7684\u8bf7\u6c42\u6b63\u6587\u3002\u8fd9\u79cd\u5dee\u5f02\u53ef\u80fd\u4f1a\u5f15\u8d77\u6df7\u6dc6\uff0c\u56e0\u6b64\u8bf7\u59cb\u7ec8\u8003\u8651\u6570\u7ec4\u662f\u5426\u662f\u6700\u4f73\u9009\u62e9\u3002<\/p>\n<p>All the parameters of a handler must bind correctly. If a parameter tries to bind to a missing value, you\u2019ll get a BadHttpRequestException and a 400 Bad Request response.<br \/>\n\u5904\u7406\u7a0b\u5e8f\u7684\u6240\u6709\u53c2\u6570\u90fd\u5fc5\u987b\u6b63\u786e\u7ed1\u5b9a\u3002\u5982\u679c\u53c2\u6570\u5c1d\u8bd5\u7ed1\u5b9a\u5230\u7f3a\u5931\u503c\uff0c\u60a8\u5c06\u6536\u5230 BadHttpRequestException \u548c 400 Bad Request \u54cd\u5e94\u3002<\/p>\n<p>You can use well-known types such as HttpContext and any services from the dependency injection container in your endpoint handlers. Minimal APIs check whether each complex type in your handler is registered as a service in the DI container; if not, they treat it as a complex type to bind to the request body instead.<br \/>\n\u60a8\u53ef\u4ee5\u5728\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u4f7f\u7528\u5df2\u77e5\u7c7b\u578b\uff08\u5982 HttpContext\uff09\u548c\u4f9d\u8d56\u9879\u6ce8\u5165\u5bb9\u5668\u4e2d\u7684\u4efb\u4f55\u670d\u52a1\u3002\u6700\u5c0f API \u68c0\u67e5\u5904\u7406\u7a0b\u5e8f\u4e2d\u7684\u6bcf\u4e2a\u590d\u6742\u7c7b\u578b\u662f\u5426\u5728 DI \u5bb9\u5668\u4e2d\u6ce8\u518c\u4e3a\u670d\u52a1;\u5426\u5219\uff0c\u5b83\u4eec\u4f1a\u5c06\u5176\u89c6\u4e3a\u590d\u6742\u7c7b\u578b\uff0c\u4ee5\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6b63\u6587\u3002<\/p>\n<p>You can read files sent in the request by using the IFormFile and IFormFileCollection interfaces in your endpoint handlers. Take care accepting file uploads with these interfaces, as they can open your application to attacks from users.<br \/>\n\u60a8\u53ef\u4ee5\u4f7f\u7528\u7ec8\u7aef\u8282\u70b9\u5904\u7406\u7a0b\u5e8f\u4e2d\u7684 IFormFile \u548c IFormFileCollection \u63a5\u53e3\u8bfb\u53d6\u8bf7\u6c42\u4e2d\u53d1\u9001\u7684\u6587\u4ef6\u3002\u8bf7\u5c0f\u5fc3\u63a5\u53d7\u4f7f\u7528\u8fd9\u4e9b\u63a5\u53e3\u4e0a\u4f20\u7684\u6587\u4ef6\uff0c\u56e0\u4e3a\u5b83\u4eec\u53ef\u80fd\u4f1a\u4f7f\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u53d7\u5230\u7528\u6237\u7684\u653b\u51fb\u3002<\/p>\n<p>You can completely customize how a type binds by using custom binding. Create a static function with the signature \u00b7public static ValueTask&lt;T?&gt; BindAsync(HttpContext context)\u00b7, and return the bound property. This approach can be useful for handling complex scenarios, such as arbitrary JSON uploads.<br \/>\n\u60a8\u53ef\u4ee5\u4f7f\u7528\u81ea\u5b9a\u4e49\u7ed1\u5b9a\u5b8c\u5168\u81ea\u5b9a\u4e49\u7c7b\u578b\u7684\u7ed1\u5b9a\u65b9\u5f0f\u3002\u521b\u5efa\u7b7e\u540d\u4e3a \u00b7public static ValueTask&lt;T\uff1f&gt; BindAsync\uff08HttpContext context\uff09\u00b7 \u7684\u9759\u6001\u51fd\u6570\uff0c\u5e76\u8fd4\u56de\u7ed1\u5b9a\u5c5e\u6027\u3002\u6b64\u65b9\u6cd5\u53ef\u7528\u4e8e\u5904\u7406\u590d\u6742\u573a\u666f\uff0c\u4f8b\u5982\u4efb\u610f JSON \u4e0a\u4f20\u3002<\/p>\n<p>You can override the default binding source for a parameter by applying [From<em>] attributes to your handler parameters, such as [FromHeader], [FromQuery], [FromBody], and [FromServices]. These parameters take precedence over convention-based assumptions.<br \/>\n\u60a8\u53ef\u4ee5\u901a\u8fc7\u5c06 [From<\/em>] \u5c5e\u6027\u5e94\u7528\u4e8e\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\uff08\u5982 [FromHeader]\u3001[FromQuery]\u3001[FromBody] \u548c [FromServices]\uff09\u6765\u8986\u76d6\u53c2\u6570\u7684\u9ed8\u8ba4\u7ed1\u5b9a\u6e90\u3002\u8fd9\u4e9b\u53c2\u6570\u4f18\u5148\u4e8e\u57fa\u4e8e\u7ea6\u5b9a\u7684\u5047\u8bbe\u3002<\/p>\n<p>You can encapsulate an endpoint handler\u2019s parameters by creating a type containing all the parameters as properties or a constructor argument and decorate the parameter with the [AsParameters] attribute. This approach can help you simplify your endpoint\u2019s method signature.<br \/>\n\u53ef\u4ee5\u901a\u8fc7\u521b\u5efa\u5305\u542b\u6240\u6709\u53c2\u6570\u4f5c\u4e3a\u5c5e\u6027\u6216\u6784\u9020\u51fd\u6570\u53c2\u6570\u7684\u7c7b\u578b\u6765\u5c01\u88c5\u7ec8\u7ed3\u70b9\u5904\u7406\u7a0b\u5e8f\u7684\u53c2\u6570\uff0c\u5e76\u4f7f\u7528 [AsParameters] \u5c5e\u6027\u4fee\u9970\u53c2\u6570\u3002\u6b64\u65b9\u6cd5\u53ef\u4ee5\u5e2e\u52a9\u60a8\u7b80\u5316\u7ec8\u7aef\u8282\u70b9\u7684\u65b9\u6cd5\u7b7e\u540d\u3002<\/p>\n<p>Validation is necessary to check for security threats. Check that data is formatted correctly, confirm that it conforms to expected values and verify that it meets your business rules.<br \/>\n\u9a8c\u8bc1\u5bf9\u4e8e\u68c0\u67e5\u5b89\u5168\u5a01\u80c1\u662f\u5fc5\u8981\u7684\u3002\u68c0\u67e5\u6570\u636e\u7684\u683c\u5f0f\u662f\u5426\u6b63\u786e\uff0c\u786e\u8ba4\u5b83\u7b26\u5408\u9884\u671f\u503c\uff0c\u5e76\u9a8c\u8bc1\u5b83\u662f\u5426\u7b26\u5408\u60a8\u7684\u4e1a\u52a1\u89c4\u5219\u3002<\/p>\n<p>Minimal APIs don\u2019t have built-in validation APIs, so you typically apply validation via a minimal API filter. This approach provides flexibility ,as you can implement validation in the way that suits you best, though it typically means that you need to use a third-party package.<br \/>\n\u6700\u5c0f API \u6ca1\u6709\u5185\u7f6e\u7684\u9a8c\u8bc1 API\uff0c\u56e0\u6b64\u60a8\u901a\u5e38\u901a\u8fc7\u6700\u5c0f API \u8fc7\u6ee4\u5668\u5e94\u7528\u9a8c\u8bc1\u3002\u6b64\u65b9\u6cd5\u63d0\u4f9b\u4e86\u7075\u6d3b\u6027\uff0c\u56e0\u4e3a\u60a8\u53ef\u4ee5\u4ee5\u6700\u9002\u5408\u60a8\u7684\u65b9\u5f0f\u5b9e\u65bd\u9a8c\u8bc1\uff0c\u4f46\u8fd9\u901a\u5e38\u610f\u5473\u7740\u60a8\u9700\u8981\u4f7f\u7528\u7b2c\u4e09\u65b9\u5305\u3002<\/p>\n<p>The MinimalApis.Extensions NuGet package provides a validation filter that uses DataAnnotations attributes to declaratively define the expected values. You can add the filter with the extension method WithParameterValidation().<br \/>\nMinimalApis.Extensions NuGet \u5305\u63d0\u4f9b\u4e86\u4e00\u4e2a\u9a8c\u8bc1\u7b5b\u9009\u5668\uff0c\u8be5\u7b5b\u9009\u5668\u4f7f\u7528 DataAnnotations \u5c5e\u6027\u4ee5\u58f0\u660e\u65b9\u5f0f\u5b9a\u4e49\u9884\u671f\u503c\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u6269\u5c55\u65b9\u6cd5 WithParameterValidation\uff08\uff09 \u6dfb\u52a0\u8fc7\u6ee4\u5668\u3002<\/p>\n<p>To add custom validation of simple types with MinimalApis.Extensions, you must create a containing type and use the [AsParameters] attribute.<br \/>\n\u82e5\u8981\u4f7f\u7528 MinimalApis.Extensions \u6dfb\u52a0\u7b80\u5355\u7c7b\u578b\u7684\u81ea\u5b9a\u4e49\u9a8c\u8bc1\uff0c\u5fc5\u987b\u521b\u5efa\u5305\u542b\u7c7b\u578b\u5e76\u4f7f\u7528 [AsParameters] \u5c5e\u6027\u3002<\/p>\n<hr \/>\n<ol>\n<li>\n<p>I have a series discussing strongly-typed IDs and their benefits on my blog at <a href=\"http:\/\/mng.bz\/a1Kz\">http:\/\/mng.bz\/a1Kz<\/a>.<br \/>\n\u6211\u5728 <a href=\"http:\/\/mng.bz\/a1Kz\">http:\/\/mng.bz\/a1Kz<\/a> \u7684\u535a\u5ba2\u4e0a\u6709\u4e00\u4e2a\u7cfb\u5217\u8ba8\u8bba\u5f3a\u7c7b\u578b ID \u53ca\u5176\u597d\u5904\u3002<\/p>\n<\/li>\n<li>\n<p>C# 12, which will be released with .NET 8, should include support for default values in lambda expressions. For more details, see <a href=\"http:\/\/mng.bz\/AoRg\">http:\/\/mng.bz\/AoRg<\/a>.<br \/>\nC# 12 \u5c06\u4e0e .NET 8 \u4e00\u8d77\u53d1\u5e03\uff0c\u5b83\u5e94\u8be5\u5305\u62ec\u5bf9 lambda \u8868\u8fbe\u5f0f\u4e2d\u9ed8\u8ba4\u503c\u7684\u652f\u6301\u3002\u6709\u5173\u66f4\u591a\u8be6\u7ec6\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605 <a href=\"http:\/\/mng.bz\/AoRg\">http:\/\/mng.bz\/AoRg<\/a>\u3002<\/p>\n<\/li>\n<\/ol>\n","protected":false},"excerpt":{"rendered":"<p>7 Model binding and validation in minimal APIs 7 \u6700\u5c0f API \u4e2d\u7684\u6a21\u578b\u7ed1\u5b9a\u548c\u9a8c\u8bc1 This chapter covers \u672c\u7ae0\u6db5\u76d6 Using request values to create binding models \u4f7f\u7528\u8bf7\u6c42\u503c\u521b\u5efa\u7ed1\u5b9a\u6a21\u578b Customizing the model-binding process \u81ea\u5b9a\u4e49\u6a21\u578b\u7ed1\u5b9a\u8fc7\u7a0b Validating user input using DataAnnotations attributes \u4f7f\u7528 DataAnnotations \u5c5e\u6027\u9a8c\u8bc1\u7528\u6237\u8f93\u5165 In chapter 6 I showed you how to define a route with parameters\u2014perhaps for the unique ID [&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-585","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\/585","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=585"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/585\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=585"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=585"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=585"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}