{"id":603,"date":"2025-04-05T11:41:38","date_gmt":"2025-04-05T03:41:38","guid":{"rendered":"https:\/\/www.hyy.net\/?p=603"},"modified":"2025-04-05T11:41:38","modified_gmt":"2025-04-05T03:41:38","slug":"asp-net-core-in-action-16-binding-and-validating-requests-with-razor-pages","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=603","title":{"rendered":"ASP.NET Core in Action 16 Binding and validating requests with Razor Pages"},"content":{"rendered":"<p>16 Binding and validating requests with Razor Pages<br \/>\n16 \u4f7f\u7528 Razor Pages \u7ed1\u5b9a\u548c\u9a8c\u8bc1\u8bf7\u6c42<\/p>\n<p>This chapter covers<br \/>\n\u672c\u7ae0\u6db5\u76d6<\/p>\n<p>\u2022  Using request values to create binding models<br \/>\n\u4f7f\u7528\u8bf7\u6c42\u503c\u521b\u5efa\u7ed1\u5b9a\u6a21\u578b<br \/>\n\u2022  Customizing the model-binding process<br \/>\n\u81ea\u5b9a\u4e49\u6a21\u578b\u7ed1\u5b9a\u8fc7\u7a0b<br \/>\n\u2022  Validating user input using DataAnnotations attributes<br \/>\n\u4f7f\u7528 DataAnnotations \u5c5e\u6027\u9a8c\u8bc1\u7528\u6237\u8f93\u5165<\/p>\n<p>In chapter 7 we looked at the process of model binding and validation in minimal APIs. In this chapter we look at the Razor Pages equivalent: extracting values from a request using model binding and validating user input.<br \/>\n\u5728\u7b2c 7 \u7ae0\u4e2d\uff0c\u6211\u4eec\u4e86\u89e3\u4e86\u6700\u5c0f API \u4e2d\u7684\u6a21\u578b\u7ed1\u5b9a\u548c\u9a8c\u8bc1\u8fc7\u7a0b\u3002\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u7b49\u6548\u7684 Razor Pages\uff1a\u4f7f\u7528\u6a21\u578b\u7ed1\u5b9a\u4ece\u8bf7\u6c42\u4e2d\u63d0\u53d6\u503c\u5e76\u9a8c\u8bc1\u7528\u6237\u8f93\u5165\u3002<\/p>\n<p>In the first half of this chapter, we look at using binding models to retrieve those parameters from the request so that you can use them in your Razor Pages by creating C# objects. These objects are passed to your Razor Page handlers as method parameters or are set as properties on your Razor Page PageModel.<br \/>\n\u5728\u672c\u7ae0\u7684\u524d\u534a\u90e8\u5206\uff0c\u6211\u4eec\u5c06\u4ecb\u7ecd\u5982\u4f55\u4f7f\u7528\u7ed1\u5b9a\u6a21\u578b\u4ece\u8bf7\u6c42\u4e2d\u68c0\u7d22\u8fd9\u4e9b\u53c2\u6570\uff0c\u4ee5\u4fbf\u4f60\u53ef\u4ee5\u901a\u8fc7\u521b\u5efa C# \u5bf9\u8c61\u5728 Razor Pages \u4e2d\u4f7f\u7528\u5b83\u4eec\u3002\u8fd9\u4e9b\u5bf9\u8c61\u4f5c\u4e3a\u65b9\u6cd5\u53c2\u6570\u4f20\u9012\u7ed9 Razor Page \u5904\u7406\u7a0b\u5e8f\uff0c\u6216\u8bbe\u7f6e\u4e3a Razor Page PageModel \u4e0a\u7684\u5c5e\u6027\u3002<\/p>\n<p>Once your code is executing in a page handler method, you can\u2019t simply use the binding model without any further thought. Any time you\u2019re using data provided by a user, you need to validate it! The second half of the chapter focuses on how to validate your binding models with Razor Pages.<br \/>\n\u4e00\u65e6\u4f60\u7684\u4ee3\u7801\u5728\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u4e2d\u6267\u884c\uff0c\u4f60\u5c31\u4e0d\u80fd\u7b80\u5355\u5730\u4f7f\u7528\u7ed1\u5b9a\u6a21\u578b\u800c\u4e0d\u505a\u4efb\u4f55\u8fdb\u4e00\u6b65\u7684\u8003\u8651\u3002\u4efb\u4f55\u65f6\u5019\u60a8\u4f7f\u7528\u7528\u6237\u63d0\u4f9b\u7684\u6570\u636e\u65f6\uff0c\u90fd\u9700\u8981\u5bf9\u5176\u8fdb\u884c\u9a8c\u8bc1\uff01\u672c\u7ae0\u7684\u540e\u534a\u90e8\u5206\u91cd\u70b9\u4ecb\u7ecd\u5982\u4f55\u4f7f\u7528 Razor Pages \u9a8c\u8bc1\u7ed1\u5b9a\u6a21\u578b\u3002<\/p>\n<p>We covered model binding and validation for minimal APIs in chapter 7, and conceptually, binding and validation are the same for Razor Pages. However, the details and mechanics of both binding and validation are quite different for Razor Pages.<br \/>\n\u6211\u4eec\u5728\u7b2c 7 \u7ae0\u4e2d\u4ecb\u7ecd\u4e86\u6700\u5c0f API \u7684\u6a21\u578b\u7ed1\u5b9a\u548c\u9a8c\u8bc1\uff0c\u4ece\u6982\u5ff5\u4e0a\u8bb2\uff0cRazor Pages \u7684\u7ed1\u5b9a\u548c\u9a8c\u8bc1\u662f\u76f8\u540c\u7684\u3002\u4f46\u662f\uff0cRazor Pages \u7684\u7ed1\u5b9a\u548c\u9a8c\u8bc1\u7684\u8be6\u7ec6\u4fe1\u606f\u548c\u673a\u5236\u5b8c\u5168\u4e0d\u540c\u3002<\/p>\n<p>The binding models populated by the Razor Pages infrastructure are passed to page handlers when they execute. Once the page handler has run, you\u2019re all set up to use the output models in ASP.NET Core\u2019s implementation of Model-View-Controller (MVC): the view models and API models. These are used to generate a response to the user\u2019s request. We\u2019ll cover them in chapters 19 and 20.<br \/>\nRazor Pages \u57fa\u7840\u7ed3\u6784\u586b\u5145\u7684\u7ed1\u5b9a\u6a21\u578b\u5728\u6267\u884c\u65f6\u4f20\u9012\u7ed9\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u3002\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u8fd0\u884c\u540e\uff0c\u60a8\u5c31\u53ef\u4ee5\u4f7f\u7528 ASP.NET Core \u7684\u6a21\u578b-\u89c6\u56fe-\u63a7\u5236\u5668 \uff08MVC\uff09 \u5b9e\u73b0\u4e2d\u7684\u8f93\u51fa\u6a21\u578b\uff1a\u89c6\u56fe\u6a21\u578b\u548c API \u6a21\u578b\u3002\u8fd9\u4e9b\u7528\u4e8e\u751f\u6210\u5bf9\u7528\u6237\u8bf7\u6c42\u7684\u54cd\u5e94\u3002\u6211\u4eec\u5c06\u5728\u7b2c 19 \u7ae0\u548c\u7b2c 20 \u7ae0\u4e2d\u4ecb\u7ecd\u5b83\u4eec\u3002<\/p>\n<p>Before we go any further, let\u2019s recap the MVC design pattern and how binding models fit into ASP.NET Core.<br \/>\n\u5728\u8fdb\u4e00\u6b65\u8ba8\u8bba\u4e4b\u524d\uff0c\u8ba9\u6211\u4eec\u56de\u987e\u4e00\u4e0b MVC \u8bbe\u8ba1\u6a21\u5f0f\u4ee5\u53ca\u7ed1\u5b9a\u6a21\u578b\u5982\u4f55\u9002\u5e94 ASP.NET Core\u3002<\/p>\n<h2>16.1 Understanding the models in Razor Pages and MVC<\/h2>\n<p>16.1 \u4e86\u89e3 Razor Pages \u548c MVC \u4e2d\u7684\u6a21\u578b<\/p>\n<p>In this section I describe how binding models fit into the MVC design pattern we covered in chapter 13. I describe the difference between binding models and the other \u201cmodel\u201d concepts in the MVC pattern and how they\u2019re each used in ASP.NET Core.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u5c06\u4ecb\u7ecd\u7ed1\u5b9a\u6a21\u578b\u5982\u4f55\u9002\u5e94\u6211\u4eec\u5728\u7b2c 13 \u7ae0\u4e2d\u4ecb\u7ecd\u7684 MVC \u8bbe\u8ba1\u6a21\u5f0f\u3002\u6211\u5c06\u4ecb\u7ecd\u7ed1\u5b9a\u6a21\u578b\u4e0e MVC \u6a21\u5f0f\u4e2d\u7684\u5176\u4ed6\u201c\u6a21\u578b\u201d\u6982\u5ff5\u4e4b\u95f4\u7684\u533a\u522b\uff0c\u4ee5\u53ca\u5b83\u4eec\u5728 ASP.NET Core \u4e2d\u7684\u4f7f\u7528\u65b9\u5f0f\u3002<\/p>\n<p>MVC is all about the separation of concerns. The premise is that isolating each aspect of your application to focus on a single responsibility reduces the interdependencies in your system. This separation makes it easier to make changes without affecting other parts of your application.<br \/>\nMVC \u5c31\u662f\u5173\u6ce8\u70b9\u5206\u79bb\u3002\u524d\u63d0\u662f\uff0c\u5c06\u5e94\u7528\u7a0b\u5e8f\u7684\u6bcf\u4e2a\u65b9\u9762\u9694\u79bb\u8d77\u6765\uff0c\u4e13\u6ce8\u4e8e\u5355\u4e00\u804c\u8d23\uff0c\u53ef\u4ee5\u51cf\u5c11\u7cfb\u7edf\u4e2d\u7684\u76f8\u4e92\u4f9d\u8d56\u5173\u7cfb\u3002\u8fd9\u79cd\u5206\u79bb\u53ef\u4ee5\u66f4\u8f7b\u677e\u5730\u8fdb\u884c\u66f4\u6539\uff0c\u800c\u4e0d\u4f1a\u5f71\u54cd\u5e94\u7528\u7a0b\u5e8f\u7684\u5176\u4ed6\u90e8\u5206\u3002<\/p>\n<p>The classic MVC design pattern has three independent components:<br \/>\n\u7ecf\u5178 MVC \u8bbe\u8ba1\u6a21\u5f0f\u5177\u6709\u4e09\u4e2a\u72ec\u7acb\u7684\u7ec4\u4ef6\uff1a<\/p>\n<p>\u2022  Model\u2014The data to display and the methods for updating this data<br \/>\n\u6a21\u578b - \u8981\u663e\u793a\u7684\u6570\u636e\u548c\u66f4\u65b0\u6b64\u6570\u636e\u7684\u65b9\u6cd5<br \/>\n\u2022  View\u2014Displays a representation of data that makes up the model<br \/>\n\u89c6\u56fe - \u663e\u793a\u6784\u6210\u6a21\u578b\u7684\u6570\u636e\u7684\u8868\u793a\u5f62\u5f0f<br \/>\n\u2022  Controller\u2014Calls methods on the model and selects a view<br \/>\n\u63a7\u5236\u5668 - \u8c03\u7528\u6a21\u578b\u7684\u65b9\u6cd5\u5e76\u9009\u62e9\u89c6\u56fe<\/p>\n<p>In this representation, there\u2019s only one model, the application model, which represents all the business logic for the application as well as how to update and modify its internal state. ASP.NET Core has multiple models, which takes the single-responsibility principle (SRP) one step further than some views of MVC.<br \/>\n\u5728\u8fd9\u79cd\u8868\u793a\u5f62\u5f0f\u4e2d\uff0c\u53ea\u6709\u4e00\u4e2a\u6a21\u578b\uff0c\u5373\u5e94\u7528\u7a0b\u5e8f\u6a21\u578b\uff0c\u5b83\u8868\u793a\u5e94\u7528\u7a0b\u5e8f\u7684\u6240\u6709\u4e1a\u52a1\u903b\u8f91\u4ee5\u53ca\u5982\u4f55\u66f4\u65b0\u548c\u4fee\u6539\u5176\u5185\u90e8\u72b6\u6001\u3002ASP.NET Core \u5177\u6709\u591a\u4e2a\u6a21\u578b\uff0c\u8fd9\u4f7f\u5f97\u5355\u4e00\u8d23\u4efb\u539f\u5219 \uff08SRP\uff09 \u6bd4 MVC \u7684\u67d0\u4e9b\u89c6\u56fe\u66f4\u8fdb\u4e00\u6b65\u3002<\/p>\n<p>In chapter 13 we looked at an example of a to-do list application that can show all the to-do items for a given category and username. With this application, you make a request to a URL that\u2019s routed using todo\/listcategory\/{category}\/{username}. This returns a response showing all the relevant to-do items, as shown in figure 16.1.<br \/>\n\u5728\u7b2c 13 \u7ae0\u4e2d\uff0c\u6211\u4eec\u770b\u4e86\u4e00\u4e2a\u5f85\u529e\u4e8b\u9879\u5217\u8868\u5e94\u7528\u7a0b\u5e8f\u7684\u793a\u4f8b\uff0c\u5b83\u53ef\u4ee5\u663e\u793a\u7ed9\u5b9a\u7c7b\u522b\u548c\u7528\u6237\u540d\u7684\u6240\u6709\u5f85\u529e\u4e8b\u9879\u3002\u4f7f\u7528\u6b64\u5e94\u7528\u7a0b\u5e8f\uff0c\u60a8\u53ef\u4ee5\u5411\u4f7f\u7528 todo\/listcategory\/{category}\/{username} \u8def\u7531\u7684 URL \u53d1\u51fa\u8bf7\u6c42\u3002\u8fd9\u5c06\u8fd4\u56de\u4e00\u4e2a\u54cd\u5e94\uff0c\u5176\u4e2d\u663e\u793a\u4e86\u6240\u6709\u76f8\u5173\u7684\u5f85\u529e\u4e8b\u9879\uff0c\u5982\u56fe 16.1 \u6240\u793a\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/1601.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 16.1 A basic to-do list application that displays to-do list items. A user can filter the list of items by changing the category and username parameters in the URL.<br \/>\n\u56fe 16.1 \u663e\u793a\u5f85\u529e\u4e8b\u9879\u5217\u8868\u9879\u7684\u57fa\u672c\u5f85\u529e\u4e8b\u9879\u5217\u8868\u5e94\u7528\u7a0b\u5e8f\u3002\u7528\u6237\u53ef\u4ee5\u901a\u8fc7\u66f4\u6539 URL \u4e2d\u7684 category \u548c username \u53c2\u6570\u6765\u7b5b\u9009\u9879\u76ee\u5217\u8868\u3002<\/p>\n<p>The application uses the same MVC constructs you\u2019ve already seen, such as routing to a Razor Page handler, as well as various models. Figure 16.2 shows how a request to this application maps to the MVC design pattern and how it generates the final response, including additional details around the model binding and validation of the request.<br \/>\n\u8be5\u5e94\u7528\u7a0b\u5e8f\u4f7f\u7528\u60a8\u5df2\u7ecf\u770b\u5230\u7684\u76f8\u540c MVC \u6784\u9020\uff0c\u4f8b\u5982\u8def\u7531\u5230 Razor Page \u5904\u7406\u7a0b\u5e8f\u4ee5\u53ca\u5404\u79cd\u6a21\u578b\u3002\u56fe 16.2 \u663e\u793a\u4e86\u5bf9\u6b64\u5e94\u7528\u7a0b\u5e8f\u7684\u8bf7\u6c42\u5982\u4f55\u6620\u5c04\u5230 MVC \u8bbe\u8ba1\u6a21\u5f0f\uff0c\u4ee5\u53ca\u5b83\u5982\u4f55\u751f\u6210\u6700\u7ec8\u54cd\u5e94\uff0c\u5305\u62ec\u6709\u5173\u8bf7\u6c42\u7684\u6a21\u578b\u7ed1\u5b9a\u548c\u9a8c\u8bc1\u7684\u5176\u4ed6\u8be6\u7ec6\u4fe1\u606f\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/1602.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 16.2 The MVC pattern in ASP.NET Core handling a request to view a subset of items in a to-do list Razor Pages application<br \/>\n\u56fe 16.2 ASP.NET Core \u4e2d\u7684 MVC \u6a21\u5f0f\u5904\u7406\u67e5\u770b\u5f85\u529e\u4e8b\u9879\u5217\u8868 Razor Pages \u5e94\u7528\u7a0b\u5e8f\u4e2d\u9879\u5b50\u96c6\u7684\u8bf7\u6c42<\/p>\n<p>ASP.NET Core Razor Pages uses several models, most of which are plain old CLR objects (POCOs), and the application model, which is more of a concept around a collection of services. Each of the models in ASP.NET Core is responsible for handling a different aspect of the overall request:<br \/>\nASP.NET Core Razor Pages \u4f7f\u7528\u591a\u4e2a\u6a21\u578b\uff0c\u5176\u4e2d\u5927\u591a\u6570\u662f\u666e\u901a\u7684\u65e7 CLR \u5bf9\u8c61 \uff08POCO\uff09\uff0c\u4ee5\u53ca\u5e94\u7528\u7a0b\u5e8f\u6a21\u578b\uff0c\u540e\u8005\u66f4\u50cf\u662f\u56f4\u7ed5\u670d\u52a1\u96c6\u5408\u7684\u6982\u5ff5\u3002ASP.NET Core \u4e2d\u7684\u6bcf\u4e2a\u6a21\u578b\u90fd\u8d1f\u8d23\u5904\u7406\u6574\u4e2a\u8bf7\u6c42\u7684\u4e0d\u540c\u65b9\u9762\uff1a<\/p>\n<p>\u2022  Binding model\u2014The binding model is all the information that\u2019s provided by the user when making a request, as well as additional contextual data. This includes things like route parameters parsed from the URL, the query string, and form or JavaScript Object Notation (JSON) data in the request body. The binding model itself is one or more POCO objects that you define. Binding models in Razor Pages are typically defined by creating a public property on the page\u2019s PageModel and decorating it with the [BindProperty] attribute. They can also be passed to a page handler as parameters.<br \/>\nFor this example, the binding model would include the name of the category, open, and the username, Andrew. The Razor Pages infrastructure inspects the binding model before the page handler executes to check whether the provided values are valid, though the page handler executes even if they\u2019re not, as you\u2019ll see when we discuss validation in section 16.3.<br \/>\n\u7ed1\u5b9a\u6a21\u578b - \u7ed1\u5b9a\u6a21\u578b\u662f\u7528\u6237\u5728\u53d1\u51fa\u8bf7\u6c42\u65f6\u63d0\u4f9b\u7684\u6240\u6709\u4fe1\u606f\uff0c\u4ee5\u53ca\u5176\u4ed6\u4e0a\u4e0b\u6587\u6570\u636e\u3002\u8fd9\u5305\u62ec\u4ece URL \u89e3\u6790\u7684\u8def\u7531\u53c2\u6570\u3001\u67e5\u8be2\u5b57\u7b26\u4e32\u4ee5\u53ca\u8bf7\u6c42\u6b63\u6587\u4e2d\u7684\u8868\u5355\u6216 JavaScript \u5bf9\u8c61\u8868\u793a\u6cd5 \uff08JSON\uff09 \u6570\u636e\u7b49\u5185\u5bb9\u3002\u7ed1\u5b9a\u6a21\u578b\u672c\u8eab\u662f\u60a8\u5b9a\u4e49\u7684\u4e00\u4e2a\u6216\u591a\u4e2a POCO \u5bf9\u8c61\u3002Razor Pages \u4e2d\u7684\u7ed1\u5b9a\u6a21\u578b\u901a\u5e38\u662f\u901a\u8fc7\u5728\u9875\u9762\u7684 PageModel \u4e0a\u521b\u5efa\u516c\u5171\u5c5e\u6027\u5e76\u4f7f\u7528 [BindProperty] \u5c5e\u6027\u5bf9\u5176\u8fdb\u884c\u4fee\u9970\u6765\u5b9a\u4e49\u7684\u3002\u5b83\u4eec\u4e5f\u53ef\u4ee5\u4f5c\u4e3a\u53c2\u6570\u4f20\u9012\u7ed9\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u3002<br \/>\n\u5728\u6b64\u793a\u4f8b\u4e2d\uff0c\u7ed1\u5b9a\u6a21\u578b\u5c06\u5305\u62ec category \u7684\u540d\u79f0 open \u548c\u7528\u6237\u540d Andrew\u3002Razor Pages \u57fa\u7840\u7ed3\u6784\u5728\u6267\u884c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u4e4b\u524d\u68c0\u67e5\u7ed1\u5b9a\u6a21\u578b\uff0c\u4ee5\u68c0\u67e5\u63d0\u4f9b\u7684\u503c\u662f\u5426\u6709\u6548\uff0c\u4f46\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u5373\u4f7f\u65e0\u6548\u4e5f\u4f1a\u6267\u884c\uff0c\u6b63\u5982\u6211\u4eec\u5728\u7b2c 16.3 \u8282\u4e2d\u8ba8\u8bba\u9a8c\u8bc1\u65f6\u6240\u770b\u5230\u7684\u90a3\u6837<\/p>\n<p>\u2022  Application model\u2014The application model isn\u2019t really an ASP.NET Core model at all. It\u2019s typically a whole group of different services and classes and is more of a concept\u2014anything needed to perform some sort of business action in your application. It may include the domain model (which represents the thing your app is trying to describe) and database models (which represent the data stored in a database), as well as any other, additional services.<br \/>\nIn the to-do list application, the application model would contain the complete list of to-do items, probably stored in a database, and would know how to find only those to-do items in the open category assigned to Andrew.<br \/>\n\u5e94\u7528\u7a0b\u5e8f\u6a21\u578b - \u5e94\u7528\u7a0b\u5e8f\u6a21\u578b\u6839\u672c\u4e0d\u662f\u771f\u6b63\u7684 ASP.NET Core \u6a21\u578b\u3002\u5b83\u901a\u5e38\u662f\u4e00\u6574\u5957\u4e0d\u540c\u7684\u670d\u52a1\u548c\u7c7b\uff0c\u66f4\u50cf\u662f\u4e00\u4e2a\u6982\u5ff5 \u2014 \u5728\u5e94\u7528\u7a0b\u5e8f\u4e2d\u6267\u884c\u67d0\u79cd\u4e1a\u52a1\u4f5c\u6240\u9700\u7684\u4efb\u4f55\u5185\u5bb9\u3002\u5b83\u53ef\u80fd\u5305\u62ec\u57df\u6a21\u578b\uff08\u8868\u793a\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u5c1d\u8bd5\u63cf\u8ff0\u7684\u4e8b\u7269\uff09\u548c\u6570\u636e\u5e93\u6a21\u578b\uff08\u8868\u793a\u5b58\u50a8\u5728\u6570\u636e\u5e93\u4e2d\u7684\u6570\u636e\uff09\uff0c\u4ee5\u53ca\u4efb\u4f55\u5176\u4ed6\u9644\u52a0\u670d\u52a1\u3002<br \/>\n\u5728\u5f85\u529e\u4e8b\u9879\u5217\u8868\u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u5e94\u7528\u7a0b\u5e8f\u6a21\u578b\u5c06\u5305\u542b\u5f85\u529e\u4e8b\u9879\u7684\u5b8c\u6574\u5217\u8868\uff0c\u8fd9\u4e9b\u9879\u76ee\u53ef\u80fd\u5b58\u50a8\u5728\u6570\u636e\u5e93\u4e2d\uff0c\u5e76\u4e14\u77e5\u9053\u5982\u4f55\u5728\u5206\u914d\u7ed9 Andrew \u7684\u6253\u5f00\u7c7b\u522b\u4e2d\u4ec5\u67e5\u627e\u90a3\u4e9b\u5f85\u529e\u4e8b\u9879\u3002<\/p>\n<p>\u2022  Page model\u2014The PageModel of a Razor Page serves two main functions: it acts as the controller for the application by exposing page handler methods, and it acts as the view model for a Razor view. All the data required for the view to generate a response is exposed on the PageModel, such as the list of to-dos in the open category assigned to Andrew.<br \/>\n\u9875\u9762\u6a21\u578b - Razor \u9875\u9762\u7684 PageModel \u6709\u4e24\u4e2a\u4e3b\u8981\u529f\u80fd\uff1a\u5b83\u901a\u8fc7\u516c\u5f00\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u5145\u5f53\u5e94\u7528\u7a0b\u5e8f\u7684\u63a7\u5236\u5668\uff0c\u4ee5\u53ca\u5145\u5f53 Razor \u89c6\u56fe\u7684\u89c6\u56fe\u6a21\u578b\u3002\u89c6\u56fe\u751f\u6210\u54cd\u5e94\u6240\u9700\u7684\u6240\u6709\u6570\u636e\u90fd\u5728 PageModel \u4e0a\u516c\u5f00\uff0c\u4f8b\u5982\u5206\u914d\u7ed9 Andrew \u7684\u6253\u5f00\u7c7b\u522b\u4e2d\u7684\u5f85\u529e\u4e8b\u9879\u5217\u8868\u3002<\/p>\n<p>The PageModel base class that you derive your Razor Pages from contains various helper properties and methods. One of these, the ModelState property, contains the result of the model validation as a series of key-value pairs. You\u2019ll learn more about validation and the ModelState property in section 16.3.<br \/>\n\u4ece\u4e2d\u6d3e\u751f Razor Pages \u7684 PageModel \u57fa\u7c7b\u5305\u542b\u5404\u79cd\u5e2e\u52a9\u7a0b\u5e8f\u5c5e\u6027\u548c\u65b9\u6cd5\u3002\u5176\u4e2d\u4e00\u4e2a\u5c5e\u6027 ModelState \u5305\u542b\u6a21\u578b\u9a8c\u8bc1\u7684\u7ed3\u679c\uff0c\u4f5c\u4e3a\u4e00\u7cfb\u5217\u952e\u503c\u5bf9\u3002\u60a8\u5c06\u5728\u7b2c 16.3 \u8282\u4e2d\u4e86\u89e3\u6709\u5173\u9a8c\u8bc1\u548c ModelState \u5c5e\u6027\u7684\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<p>These models make up the bulk of any Razor Pages application, handling the input, business logic, and output of each page handler. Imagine you have an e-commerce application that allows users to search for clothes by sending requests to the \/search\/{query} URL, where {query} holds their search term:<br \/>\n\u8fd9\u4e9b\u6a21\u578b\u6784\u6210\u4e86\u4efb\u4f55 Razor Pages \u5e94\u7528\u7a0b\u5e8f\u7684\u5927\u90e8\u5206\uff0c\u5904\u7406\u6bcf\u4e2a\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u7684\u8f93\u5165\u3001\u4e1a\u52a1\u903b\u8f91\u548c\u8f93\u51fa\u3002\u5047\u8bbe\u60a8\u6709\u4e00\u4e2a\u7535\u5b50\u5546\u52a1\u5e94\u7528\u7a0b\u5e8f\uff0c\u5b83\u5141\u8bb8\u7528\u6237\u901a\u8fc7\u5411 \/search\/{query} URL \u53d1\u9001\u8bf7\u6c42\u6765\u641c\u7d22\u8863\u670d\uff0c\u5176\u4e2d {query} \u4fdd\u5b58\u4ed6\u4eec\u7684\u641c\u7d22\u8bcd\uff1a<\/p>\n<p>\u2022  Binding model\u2014This would take the {query} route parameter from the URL and any values posted in the request body (maybe a sort order, or the number of items to show), and bind them to a C# class, which typically acts as a throwaway data transport class. This would be set as a property on the PageModel when the page handler is invoked.<br \/>\n\u7ed1\u5b9a\u6a21\u578b - \u8fd9\u5c06\u4ece URL \u548c\u8bf7\u6c42\u6b63\u6587\u4e2d\u53d1\u5e03\u7684\u4efb\u4f55\u503c \uff08\u53ef\u80fd\u662f\u6392\u5e8f\u987a\u5e8f\u6216\u8981\u663e\u793a\u7684\u9879\u76ee\u6570\uff09 \u4e2d\u83b7\u53d6 {query} \u8def\u7531\u53c2\u6570\uff0c\u5e76\u5c06\u5b83\u4eec\u7ed1\u5b9a\u5230 C# \u7c7b\uff0c\u8be5\u7c7b\u901a\u5e38\u5145\u5f53\u4e00\u6b21\u6027\u6570\u636e\u4f20\u8f93\u7c7b\u3002\u8c03\u7528\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65f6\uff0c\u8fd9\u5c06\u8bbe\u7f6e\u4e3a PageModel \u4e0a\u7684\u5c5e\u6027\u3002<\/p>\n<p>\u2022  Application model\u2014This is the services and classes that perform the logic. When invoked by the page handler, this model would load all the clothes that match the query, applying the necessary sorting and filters, and return the results to the controller.<br \/>\n\u5e94\u7528\u7a0b\u5e8f\u6a21\u578b - \u8fd9\u662f\u6267\u884c\u903b\u8f91\u7684\u670d\u52a1\u548c\u7c7b\u3002\u5f53\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u8c03\u7528\u65f6\uff0c\u6b64\u6a21\u578b\u5c06\u52a0\u8f7d\u4e0e\u67e5\u8be2\u5339\u914d\u7684\u6240\u6709\u8863\u670d\uff0c\u5e94\u7528\u5fc5\u8981\u7684\u6392\u5e8f\u548c\u8fc7\u6ee4\u5668\uff0c\u5e76\u5c06\u7ed3\u679c\u8fd4\u56de\u7ed9\u63a7\u5236\u5668\u3002<\/p>\n<p>\u2022  Page model\u2014The values provided by the application model would be set as properties on the Razor Page\u2019s PageModel, along with other metadata, such as the total number of items available or whether the user can currently check out. The Razor view would use this data to render the Razor view to HTML.<br \/>\n\u9875\u9762\u6a21\u578b - \u5e94\u7528\u7a0b\u5e8f\u6a21\u578b\u63d0\u4f9b\u7684\u503c\u5c06\u4e0e\u5176\u4ed6\u5143\u6570\u636e \uff08\u4f8b\u5982\u53ef\u7528\u9879\u7684\u603b\u6570\u6216\u7528\u6237\u5f53\u524d\u662f\u5426\u53ef\u4ee5\u7b7e\u51fa\uff09 \u4e00\u8d77\u8bbe\u7f6e\u4e3a Razor \u9875\u9762\u7684 PageModel \u4e0a\u7684\u5c5e\u6027\u3002Razor \u89c6\u56fe\u5c06\u4f7f\u7528\u6b64\u6570\u636e\u5c06 Razor \u89c6\u56fe\u5448\u73b0\u4e3a HTML\u3002<\/p>\n<p>The important point about all these models is that their responsibilities are well defined and distinct. Keeping them separate and avoiding reuse helps ensure that your application stays agile and easy to update.<br \/>\n\u6240\u6709\u8fd9\u4e9b\u6a21\u578b\u7684\u91cd\u8981\u4e00\u70b9\u662f\uff0c\u5b83\u4eec\u7684\u8d23\u4efb\u662f\u660e\u786e\u548c\u4e0d\u540c\u7684\u3002\u5c06\u5b83\u4eec\u5206\u5f00\u5e76\u907f\u514d\u91cd\u590d\u4f7f\u7528\u6709\u52a9\u4e8e\u786e\u4fdd\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u4fdd\u6301\u654f\u6377\u4e14\u6613\u4e8e\u66f4\u65b0\u3002<\/p>\n<p>The obvious exception to this separation is the PageModel, as it is where the binding models and page handlers are defined, and it also holds the data required for rendering the view. Some people may consider the apparent lack of separation to be sacrilege, but it\u2019s not generally a problem. The lines of demarcation are pretty apparent. So long as you don\u2019t try to, for example, invoke a page handler from inside a Razor view, you shouldn\u2019t run into any problems!<br \/>\n\u8fd9\u79cd\u5206\u79bb\u7684\u660e\u663e\u4f8b\u5916\u662f PageModel\uff0c\u56e0\u4e3a\u5b83\u662f\u5b9a\u4e49\u7ed1\u5b9a\u6a21\u578b\u548c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u7684\u5730\u65b9\uff0c\u5e76\u4e14\u5b83\u8fd8\u4fdd\u5b58\u5448\u73b0\u89c6\u56fe\u6240\u9700\u7684\u6570\u636e\u3002\u6709\u4e9b\u4eba\u53ef\u80fd\u8ba4\u4e3a\u660e\u663e\u7f3a\u4e4f\u5206\u79bb\u662f\u4eb5\u6e0e\u795e\u660e\uff0c\u4f46\u8fd9\u901a\u5e38\u4e0d\u662f\u95ee\u9898\u3002\u5206\u754c\u7ebf\u975e\u5e38\u660e\u663e\u3002\u4f8b\u5982\uff0c\u53ea\u8981\u60a8\u4e0d\u5c1d\u8bd5\u4ece Razor \u89c6\u56fe\u5185\u90e8\u8c03\u7528\u9875\u9762\u5904\u7406\u7a0b\u5e8f\uff0c\u5c31\u4e0d\u4f1a\u9047\u5230\u4efb\u4f55\u95ee\u9898\uff01<\/p>\n<p>Now that you\u2019ve been properly introduced to the various models in ASP.NET Core, it\u2019s time to focus on how to use them. This chapter looks at the binding models that are built from incoming requests\u2014how are they created, and where do the values come from?<br \/>\n\u73b0\u5728\uff0c\u60a8\u5df2\u7ecf\u6b63\u786e\u4ecb\u7ecd\u4e86 ASP.NET Core \u4e2d\u7684\u5404\u79cd\u6a21\u578b\uff0c\u662f\u65f6\u5019\u4e13\u6ce8\u4e8e\u5982\u4f55\u4f7f\u7528\u5b83\u4eec\u4e86\u3002\u672c\u7ae0\u4ecb\u7ecd\u4ece\u4f20\u5165\u8bf7\u6c42\u6784\u5efa\u7684\u7ed1\u5b9a\u6a21\u578b \u2014 \u5b83\u4eec\u662f\u5982\u4f55\u521b\u5efa\u7684\uff0c\u8fd9\u4e9b\u503c\u6765\u81ea\u4f55\u5904\uff1f<\/p>\n<h2>16.2 From request to model: Making the request useful<\/h2>\n<p>16.2 \u4ece\u8bf7\u6c42\u5230\u6a21\u578b\uff1a\u4f7f\u8bf7\u6c42\u6709\u7528<\/p>\n<p>In this section you will learn<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u60a8\u5c06\u5b66\u4e60<br \/>\n\u2022  How ASP.NET Core creates binding models from a request<br \/>\nASP.NET Core \u5982\u4f55\u4ece\u8bf7\u6c42\u521b\u5efa\u7ed1\u5b9a\u6a21\u578b<br \/>\n\u2022  How to bind simple types, like int and string, as well as complex classes<br \/>\n\u5982\u4f55\u7ed1\u5b9a\u7b80\u5355\u7c7b\u578b\uff08\u5982 int \u548c string\uff09\u4ee5\u53ca\u590d\u6742\u7c7b<br \/>\n\u2022  How to choose which parts of a request are used in the binding model<br \/>\n\u5982\u4f55\u9009\u62e9\u5728\u7ed1\u5b9a\u6a21\u578b\u4e2d\u4f7f\u7528\u8bf7\u6c42\u7684\u54ea\u4e9b\u90e8\u5206<\/p>\n<p>By now, you should be familiar with how ASP.NET Core handles a request by executing a page handler on a Razor Page. Page handlers are normal C# methods, so the ASP.NET Core framework needs to be able to call them in the usual way. The process of extracting values from the request and creating C# objects from them is called model binding.<br \/>\n\u5230\u76ee\u524d\u4e3a\u6b62\uff0c\u4f60\u5e94\u8be5\u719f\u6089 ASP.NET Core \u5982\u4f55\u901a\u8fc7\u5728 Razor \u9875\u9762\u4e0a\u6267\u884c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u6765\u5904\u7406\u8bf7\u6c42\u3002\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u662f\u666e\u901a\u7684 C# \u65b9\u6cd5\uff0c\u56e0\u6b64 ASP.NET Core \u6846\u67b6\u9700\u8981\u80fd\u591f\u4ee5\u5e38\u89c4\u65b9\u5f0f\u8c03\u7528\u5b83\u4eec\u3002\u4ece\u8bf7\u6c42\u4e2d\u63d0\u53d6\u503c\u5e76\u4ece\u4e2d\u521b\u5efa C# \u5bf9\u8c61\u7684\u8fc7\u7a0b\u79f0\u4e3a\u6a21\u578b\u7ed1\u5b9a\u3002<\/p>\n<p>Any publicly settable properties on your Razor Page\u2019s PageModel (in the .cshtml.cs file for your Razor Page), that are decorated with the [BindProperty] attribute are created from the incoming request using model binding, as shown in listing 16.1. Similarly, if your page handler method has any parameters, these are also created using model binding.<br \/>\nRazor \u9875\u9762\u7684 PageModel\uff08\u5728 Razor \u9875\u9762\u7684 .cshtml.cs \u6587\u4ef6\u4e2d\uff09\u4e0a\u7528 [BindProperty] \u5c5e\u6027\u4fee\u9970\u7684\u4efb\u4f55\u53ef\u516c\u5f00\u8bbe\u7f6e\u7684\u5c5e\u6027\u90fd\u662f\u4f7f\u7528\u6a21\u578b\u7ed1\u5b9a\u4ece\u4f20\u5165\u8bf7\u6c42\u521b\u5efa\u7684\uff0c\u5982\u6e05\u5355 16.1 \u6240\u793a\u3002\u540c\u6837\uff0c\u5982\u679c\u60a8\u7684\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u5177\u6709\u4efb\u4f55\u53c2\u6570\uff0c\u5219\u8fd9\u4e9b\u53c2\u6570\u4e5f\u662f\u4f7f\u7528\u6a21\u578b\u7ed1\u5b9a\u521b\u5efa\u7684\u3002<\/p>\n<p><b>Warning<\/b> Properties decorated with [BindProperty] must have a public setter; otherwise, binding will silently fail.<br \/>\n\u8b66\u544a:\u4f7f\u7528 [BindProperty] \u4fee\u9970\u7684\u5c5e\u6027\u5fc5\u987b\u5177\u6709\u516c\u5171 setter;\u5426\u5219\uff0c\u7ed1\u5b9a\u5c06\u5931\u8d25\u3002<\/p>\n<p>Listing 16.1 Model binding requests to properties in a Razor Page<br \/>\n\u5217\u8868 16.1 \u5c06\u8bf7\u6c42\u7ed1\u5b9a\u5230 Razor \u9875\u9762\u4e2d\u7684\u5c5e\u6027<\/p>\n<pre><code>public class IndexModel: PageModel\n{\n[BindProperty] \u2776\npublic string Category { get; set; } \u2776\n[BindProperty(SupportsGet = true)] \u2777\npublic string Username { get; set; } \u2777\npublic void OnGet()\n{\n}\npublic void OnPost(ProductModel model) \u2778\n{\n}\n}<\/code><\/pre>\n<p>\u2776 Properties decorated with [BindProperty] take part in model binding.<br \/>\n\u7528 [BindProperty] \u4fee\u9970\u7684\u5c5e\u6027\u53c2\u4e0e\u6a21\u578b\u7ed1\u5b9a\u3002<br \/>\n\u2777 Properties are not model-bound for GET requests unless you use SupportsGet.<br \/>\n\u9664\u975e\u4f7f\u7528 SupportsGet\uff0c\u5426\u5219 GET \u8bf7\u6c42\u7684\u5c5e\u6027\u4e0d\u53d7\u6a21\u578b\u7ed1\u5b9a\u3002<br \/>\n\u2778 Parameters to page handlers are also model-bound when that handler is selected.<br \/>\n\u9009\u62e9\u8be5\u5904\u7406\u7a0b\u5e8f\u65f6\uff0c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u7684\u53c2\u6570\u4e5f\u662f\u6a21\u578b\u7ed1\u5b9a\u7684\u3002<\/p>\n<p>As described in chapter 15 and shown in the preceding listing, PageModel properties are not model-bound for GET requests, even if you add the [BindProperty] attribute. For security reasons, only requests using verbs like POST and PUT are bound. If you do want to bind GET requests, you can set the SupportsGet property on the [BindProperty] attribute to opt in to model binding.<br \/>\n\u5982\u7b2c 15 \u7ae0\u4e2d\u6240\u8ff0\uff0c\u5982\u524d\u9762\u7684\u6e05\u5355\u6240\u793a\uff0cPageModel \u5c5e\u6027\u4e0d\u662f GET \u8bf7\u6c42\u7684\u6a21\u578b\u7ed1\u5b9a\u7684\uff0c\u5373\u4f7f\u4f60\u6dfb\u52a0\u4e86 [BindProperty] \u5c5e\u6027\u4e5f\u662f\u5982\u6b64\u3002\u51fa\u4e8e\u5b89\u5168\u539f\u56e0\uff0c\u4ec5\u7ed1\u5b9a\u4f7f\u7528 POST \u548c PUT \u7b49\u52a8\u8bcd\u7684\u8bf7\u6c42\u3002\u5982\u679c\u60a8\u786e\u5b9e\u60f3\u8981\u7ed1\u5b9a GET \u8bf7\u6c42\uff0c\u5219\u53ef\u4ee5\u5728 [BindProperty] \u5c5e\u6027\u4e0a\u8bbe\u7f6e SupportsGet \u5c5e\u6027\uff0c\u4ee5\u9009\u62e9\u52a0\u5165\u6a21\u578b\u7ed1\u5b9a\u3002<\/p>\n<blockquote>\n<p>Which part is the binding model?<br \/>\n\u54ea\u4e2a\u90e8\u5206\u662f\u7ed1\u5b9a\u6a21\u578b\uff1f<br \/>\nListing 16.1 shows a Razor Page that uses multiple binding models: the Category property, the Username property, and the ProductModel property (in the OnPost handler) are all model-bound.<br \/>\n\u6e05\u5355 16.1 \u663e\u793a\u4e86\u4e00\u4e2a\u4f7f\u7528\u591a\u4e2a\u7ed1\u5b9a\u6a21\u578b\u7684 Razor Page\uff1aCategory \u5c5e\u6027\u3001Username \u5c5e\u6027\u548c ProductModel \u5c5e\u6027\uff08\u5728 OnPost \u5904\u7406\u7a0b\u5e8f\u4e2d\uff09\u90fd\u662f\u6a21\u578b\u7ed1\u5b9a\u7684\u3002<br \/>\nUsing multiple models in this way is fine, but I prefer to use an approach that keeps all the model binding in a single, nested class, which I often call InputModel. With this approach, the Razor Page in listing 16.1 could be written as follows:<br \/>\n\u4ee5\u8fd9\u79cd\u65b9\u5f0f\u4f7f\u7528\u591a\u4e2a\u6a21\u578b\u5f88\u597d\uff0c\u4f46\u6211\u66f4\u559c\u6b22\u4f7f\u7528\u4e00\u79cd\u65b9\u6cd5\uff0c\u5c06\u6240\u6709\u6a21\u578b\u7ed1\u5b9a\u4fdd\u5b58\u5728\u4e00\u4e2a\u5d4c\u5957\u7c7b\u4e2d\uff0c\u6211\u901a\u5e38\u5c06\u5176\u79f0\u4e3a InputModel\u3002\u4f7f\u7528\u8fd9\u79cd\u65b9\u6cd5\uff0c\u6e05\u5355 16.1 \u4e2d\u7684 Razor Page \u53ef\u4ee5\u7f16\u5199\u5982\u4e0b\uff1a<\/p>\n<\/blockquote>\n<pre><code>public class IndexModel: PageModel\n{\n    [BindProperty]\n    public InputModel Input { get; set; }\n    public void OnGet()\n    {\n    }\n\n    public class InputModel\n    {\n        public string Category { get; set; }\n        public string Username { get; set; }\n        public ProductModel Model { get; set; }\n    }\n}<\/code><\/pre>\n<blockquote>\n<p>This approach has some organizational benefits that you\u2019ll learn more about in section 16.4.<br \/>\n\u8fd9\u79cd\u65b9\u6cd5\u5177\u6709\u4e00\u4e9b\u7ec4\u7ec7\u4f18\u52bf\uff0c\u60a8\u5c06\u5728 Section 16.4 \u4e2d\u4e86\u89e3\u66f4\u591a\u4fe1\u606f\u3002<\/p>\n<\/blockquote>\n<p>ASP.NET Core automatically populates your binding models for you using properties of the request, such as the request URL, any headers sent in the HTTP request, any data explicitly POSTed in the request body, and so on.<br \/>\nASP.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 \u7684\u4efb\u4f55\u6570\u636e\u7b49\uff09\u81ea\u52a8\u586b\u5145\u60a8\u7684\u7ed1\u5b9a\u6a21\u578b\u3002<\/p>\n<p><b>NOTE<\/b> In this chapter I describe how to bind your models to an incoming request, but I don\u2019t show how Razor Pages uses your binding models to help generate that request using HTML forms. In chapter 17 you\u2019ll learn about Razor syntax, which renders HTML, and in chapter 18 you\u2019ll learn about Razor Tag Helpers, which generate form fields based on your binding model.<br \/>\n\u6ce8\u610f:\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u5c06\u4ecb\u7ecd\u5982\u4f55\u5c06\u6a21\u578b\u7ed1\u5b9a\u5230\u4f20\u5165\u8bf7\u6c42\uff0c\u4f46\u4e0d\u4f1a\u5c55\u793a Razor Pages \u5982\u4f55\u4f7f\u7528\u7ed1\u5b9a\u6a21\u578b\u6765\u5e2e\u52a9\u4f7f\u7528 HTML \u8868\u5355\u751f\u6210\u8be5\u8bf7\u6c42\u3002\u5728\u7b2c 17 \u7ae0\u4e2d\uff0c\u60a8\u5c06\u4e86\u89e3\u5448\u73b0 HTML \u7684 Razor \u8bed\u6cd5\uff0c\u5728\u7b2c 18 \u7ae0\u4e2d\uff0c\u60a8\u5c06\u4e86\u89e3\u57fa\u4e8e\u7ed1\u5b9a\u6a21\u578b\u751f\u6210\u8868\u5355\u5b57\u6bb5\u7684 Razor \u6807\u8bb0\u5e2e\u52a9\u7a0b\u5e8f\u3002<\/p>\n<p>By default, ASP.NET Core uses three different binding sources when creating your binding models in Razor Pages. It looks through each of these in order and takes the first value it finds (if any) that matches the name of the binding model:<br \/>\n\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cASP.NET Core \u5728 Razor Pages \u4e2d\u521b\u5efa\u7ed1\u5b9a\u6a21\u578b\u65f6\u4f7f\u7528\u4e09\u79cd\u4e0d\u540c\u7684\u7ed1\u5b9a\u6e90\u3002\u5b83\u6309\u987a\u5e8f\u67e5\u770b\u6bcf\u4e2a\u503c\uff0c\u5e76\u83b7\u53d6\u627e\u5230\u7684\u4e0e\u7ed1\u5b9a\u6a21\u578b\u540d\u79f0\u5339\u914d\u7684\u7b2c\u4e00\u4e2a\u503c\uff08\u5982\u679c\u6709\uff09\uff1a<\/p>\n<p>\u2022  Form values\u2014Sent in the body of an HTTP request when a form is sent to the server using a POST<br \/>\n\u8868\u5355\u503c - \u4f7f\u7528 POST\u5c06\u8868\u5355\u53d1\u9001\u5230\u670d\u52a1\u5668\u65f6\uff0c\u5728 HTTP \u8bf7\u6c42\u7684\u6b63\u6587\u4e2d\u53d1\u9001<br \/>\n\u2022  Route values\u2014Obtained from URL segments or through default values after matching a route, as you saw in chapter 14<br \/>\n\u8def\u7531\u503c - \u4ece URL \u6bb5\u83b7\u53d6\uff0c\u6216\u5728\u5339\u914d\u8def\u7531\u540e\u901a\u8fc7\u9ed8\u8ba4\u503c\u83b7\u53d6\uff0c\u5982\u7b2c 14 \u7ae0\u6240\u793a<br \/>\n\u2022  Query string values\u2014Passed at the end of the URL, not used during routing<br \/>\n\u67e5\u8be2\u5b57\u7b26\u4e32\u503c - \u5728 URL \u672b\u5c3e\u4f20\u9012\uff0c\u5728\u8def\u7531\u671f\u95f4\u4e0d\u4f7f\u7528<\/p>\n<p><b>Warning<\/b> Even though conceptually similar, the Razor Page binding process works quite differently from the approach used by minimal APIs.<br \/>\n\u8b66\u544a:\u5c3d\u7ba1\u5728\u6982\u5ff5\u4e0a\u76f8\u4f3c\uff0c\u4f46 Razor \u9875\u9762\u7ed1\u5b9a\u8fc7\u7a0b\u7684\u5de5\u4f5c\u65b9\u5f0f\u4e0e\u6700\u5c0f API \u4f7f\u7528\u7684\u65b9\u6cd5\u5b8c\u5168\u4e0d\u540c\u3002<\/p>\n<p>The model binding process for Razor Pages is shown in figure 16.3. The model binder checks each binding source to see whether it contains a value that could be set on the model. Alternatively, the model can choose the specific source the value should come from, as you\u2019ll see in section 16.2.3. Once each property is bound, the model is validated and is set as a property on the PageModel or passed as a parameter to the page handler. You\u2019ll learn about the validation process in the second half of this chapter.<br \/>\nRazor Pages \u7684\u6a21\u578b\u7ed1\u5b9a\u8fc7\u7a0b\u5982\u56fe 16.3 \u6240\u793a\u3002\u6a21\u578b\u7ed1\u5b9a\u5668\u68c0\u67e5\u6bcf\u4e2a\u7ed1\u5b9a\u6e90\uff0c\u4ee5\u67e5\u770b\u5b83\u662f\u5426\u5305\u542b\u53ef\u5728\u6a21\u578b\u4e0a\u8bbe\u7f6e\u7684\u503c\u3002\u6216\u8005\uff0c\u6a21\u578b\u53ef\u4ee5\u9009\u62e9\u503c\u5e94\u6765\u81ea\u7684\u7279\u5b9a\u6765\u6e90\uff0c\u5982\u7b2c 16.2.3 \u8282\u6240\u793a\u3002\u7ed1\u5b9a\u6bcf\u4e2a\u5c5e\u6027\u540e\uff0c\u5c06\u9a8c\u8bc1\u6a21\u578b\u5e76\u5c06\u5176\u8bbe\u7f6e\u4e3a PageModel \u4e0a\u7684\u5c5e\u6027\u6216\u4f5c\u4e3a\u53c2\u6570\u4f20\u9012\u7ed9\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u3002\u60a8\u5c06\u5728\u672c\u7ae0\u7684\u540e\u534a\u90e8\u5206\u4e86\u89e3\u9a8c\u8bc1\u8fc7\u7a0b\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/1603.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 16.3 Model binding involves mapping values from binding sources, which correspond to different parts of a request.<br \/>\n\u56fe 16.3 \u6a21\u578b\u7ed1\u5b9a\u6d89\u53ca\u6620\u5c04\u6765\u81ea\u7ed1\u5b9a\u6e90\u7684\u503c\uff0c\u8fd9\u4e9b\u503c\u5bf9\u5e94\u4e8e\u8bf7\u6c42\u7684\u4e0d\u540c\u90e8\u5206\u3002<\/p>\n<p><b>NOTE<\/b> In Razor Pages, different properties of a complex model can be model-bound to different sources. This differs from minimal APIs, where the whole object would be bound from a single source, and \u201cpartial\u201d binding is not possible. Razor Pages also bind to form bodies by default, while minimal APIs cannot. These differences are partly for historical reasons and partly because minimal APIs opts for performance over convenience in this respect.<br \/>\n\u6ce8\u610f:\u5728 Razor Pages \u4e2d\uff0c\u590d\u6742\u6a21\u578b\u7684\u4e0d\u540c\u5c5e\u6027\u53ef\u4ee5\u901a\u8fc7\u6a21\u578b\u7ed1\u5b9a\u5230\u4e0d\u540c\u7684\u6e90\u3002\u8fd9\u4e0e\u6700\u5c0f API \u4e0d\u540c\uff0c\u5728\u6700\u5c0f API \u4e2d\uff0c\u6574\u4e2a\u5bf9\u8c61\u5c06\u4ece\u5355\u4e2a\u6e90\u7ed1\u5b9a\uff0c\u5e76\u4e14\u4e0d\u53ef\u80fd\u8fdb\u884c\u201c\u90e8\u5206\u201d\u7ed1\u5b9a\u3002\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cRazor Pages \u8fd8\u4f1a\u7ed1\u5b9a\u5230\u8868\u5355\u6b63\u6587\uff0c\u800c\u6700\u5c0f\u7684 API \u5219\u4e0d\u80fd\u3002\u8fd9\u4e9b\u5dee\u5f02\u90e8\u5206\u662f\u7531\u4e8e\u5386\u53f2\u539f\u56e0\uff0c\u90e8\u5206\u662f\u56e0\u4e3a\u5728\u8fd9\u65b9\u9762\uff0c\u6700\u5c0f\u7684 API \u9009\u62e9\u4e86\u6027\u80fd\u800c\u4e0d\u662f\u4fbf\u5229\u6027\u3002<\/p>\n<blockquote>\n<p>PageModel properties or page handler parameters?<br \/>\nPageModel \u5c5e\u6027\u8fd8\u662f\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\uff1f<br \/>\nThere are three ways to use model binding in Razor Pages:<br \/>\n\u6709\u4e09\u79cd\u65b9\u6cd5\u53ef\u4ee5\u5728 Razor Pages \u4e2d\u4f7f\u7528\u6a21\u578b\u7ed1\u5b9a\uff1a<\/p>\n<\/blockquote>\n<p>\u2022   Decorate properties on your PageModel with the [BindProperty] attribute.<br \/>\n\u4f7f\u7528 [BindProperty] \u5c5e\u6027\u4fee\u9970 PageModel \u4e0a\u7684\u5c5e\u6027\u3002<br \/>\n\u2022   Add parameters to your page handler method.<br \/>\n\u5c06\u53c2\u6570\u6dfb\u52a0\u5230\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u3002<br \/>\n\u2022   Decorate the whole PageModel with [BindProperties].<br \/>\n\u4f7f\u7528 [BindProperties] \u88c5\u9970\u6574\u4e2a PageModel\u3002<\/p>\n<blockquote>\n<p>Which of these approaches should you choose?<br \/>\n\u60a8\u5e94\u8be5\u9009\u62e9\u54ea\u79cd\u65b9\u6cd5\uff1f<\/p>\n<p>This answer to this question is largely a matter of taste. Setting properties on the PageModel and marking them with [BindProperty] is the approach you\u2019ll see most often in examples. If you use this approach, you\u2019ll be able to access the binding model when the view is rendered, as you\u2019ll see in chapters 17 and 18.<br \/>\n\u8fd9\u4e2a\u95ee\u9898\u7684\u7b54\u6848\u5728\u5f88\u5927\u7a0b\u5ea6\u4e0a\u662f\u4e00\u4e2a\u54c1\u5473\u95ee\u9898\u3002\u5728 PageModel \u4e0a\u8bbe\u7f6e\u5c5e\u6027\u5e76\u4f7f\u7528 [BindProperty] \u6807\u8bb0\u5b83\u4eec\u662f\u793a\u4f8b\u4e2d\u6700\u5e38\u89c1\u7684\u65b9\u6cd5\u3002\u5982\u679c\u4f7f\u7528\u8fd9\u79cd\u65b9\u6cd5\uff0c\u60a8\u5c06\u80fd\u591f\u5728\u5448\u73b0\u89c6\u56fe\u65f6\u8bbf\u95ee\u7ed1\u5b9a\u6a21\u578b\uff0c\u5982\u7b2c 17 \u7ae0\u548c\u7b2c 18 \u7ae0\u6240\u793a\u3002<\/p>\n<p>The second approach, adding parameters to page handler methods, provides more separation between the different MVC stages, because you won\u2019t be able to access the parameters outside the page handler. On the downside, if you do need to display those values in the Razor view, you\u2019ll have to copy the parameters across manually to properties that can be accessed in the view.<br \/>\n\u7b2c\u4e8c\u79cd\u65b9\u6cd5\uff08\u5411\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u6dfb\u52a0\u53c2\u6570\uff09\u5728\u4e0d\u540c\u7684 MVC \u9636\u6bb5\u4e4b\u95f4\u63d0\u4f9b\u4e86\u66f4\u591a\u7684\u5206\u79bb\uff0c\u56e0\u4e3a\u60a8\u5c06\u65e0\u6cd5\u5728\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u4e4b\u5916\u8bbf\u95ee\u53c2\u6570\u3002\u7f3a\u70b9\u662f\uff0c\u5982\u679c\u60a8\u786e\u5b9e\u9700\u8981\u5728 Razor \u89c6\u56fe\u4e2d\u663e\u793a\u8fd9\u4e9b\u503c\uff0c\u5219\u5fc5\u987b\u624b\u52a8\u5c06\u53c2\u6570\u590d\u5236\u5230\u53ef\u5728\u89c6\u56fe\u4e2d\u8bbf\u95ee\u7684\u5c5e\u6027\u3002<\/p>\n<p>I avoid the final approach, decorating the PageModel itself with [BindProperties]. With this approach, every property on your PageModel takes part in model binding. I don\u2019t like the indirection this gives and the risk of accidentally binding properties I didn\u2019t want to be model-bound.<br \/>\n\u6211\u907f\u514d\u4f7f\u7528\u6700\u540e\u4e00\u79cd\u65b9\u6cd5\uff0c\u5373\u4f7f\u7528 [BindProperties] \u4fee\u9970 PageModel \u672c\u8eab\u3002\u4f7f\u7528\u8fd9\u79cd\u65b9\u6cd5\uff0cPageModel \u4e0a\u7684\u6bcf\u4e2a\u5c5e\u6027\u90fd\u53c2\u4e0e\u6a21\u578b\u7ed1\u5b9a\u3002\u6211\u4e0d\u559c\u6b22\u8fd9\u63d0\u4f9b\u7684\u95f4\u63a5\u6027\uff0c\u4ee5\u53ca\u610f\u5916\u7ed1\u5b9a\u6211\u4e0d\u60f3\u88ab\u6a21\u578b\u7ed1\u5b9a\u7684\u5c5e\u6027\u7684\u98ce\u9669\u3002<\/p>\n<p>The approach I choose tends to depend on the specific Razor Page I\u2019m building. If I\u2019m creating a form, I will favor the [BindProperty] approach, as I typically need access to the request values inside the Razor view. For simple pages, where the binding model is a product ID, for example, I tend to favor the page handler parameter approach for its simplicity, especially if the handler is for a GET request. I give some more specific advice on my approach in section 16.4.<br \/>\n\u6211\u9009\u62e9\u7684\u65b9\u6cd5\u5f80\u5f80\u53d6\u51b3\u4e8e\u6211\u6b63\u5728\u6784\u5efa\u7684\u7279\u5b9a Razor \u9875\u9762\u3002\u5982\u679c\u6211\u8981\u521b\u5efa\u8868\u5355\uff0c\u6211\u5c06\u503e\u5411\u4e8e\u4f7f\u7528 [BindProperty] \u65b9\u6cd5\uff0c\u56e0\u4e3a\u6211\u901a\u5e38\u9700\u8981\u8bbf\u95ee Razor \u89c6\u56fe\u4e2d\u7684\u8bf7\u6c42\u503c\u3002\u4f8b\u5982\uff0c\u5bf9\u4e8e\u7ed1\u5b9a\u6a21\u578b\u662f\u4ea7\u54c1 ID \u7684\u7b80\u5355\u9875\u9762\uff0c\u6211\u503e\u5411\u4e8e\u4f7f\u7528\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u65b9\u6cd5\uff0c\u56e0\u4e3a\u5b83\u7b80\u5355\uff0c\u5c24\u5176\u662f\u5728\u5904\u7406\u7a0b\u5e8f\u7528\u4e8e GET \u8bf7\u6c42\u65f6\u3002\u6211\u5728 16.4 \u8282\u4e2d\u5bf9\u6211\u7684\u65b9\u6cd5\u7ed9\u51fa\u4e86\u4e00\u4e9b\u66f4\u5177\u4f53\u7684\u5efa\u8bae\u3002<\/p>\n<\/blockquote>\n<p>Figure 16.4 shows an example of a request creating the ProductModel method argument using model binding for the example shown at the start of this section:<br \/>\n\u56fe 16.4 \u663e\u793a\u4e86\u4f7f\u7528\u6a21\u578b\u7ed1\u5b9a\u521b\u5efa ProductModel \u65b9\u6cd5\u53c2\u6570\u7684\u8bf7\u6c42\u793a\u4f8b\uff0c\u8be5\u793a\u4f8b\u4f4d\u4e8e\u672c\u8282\u5f00\u5934\u6240\u793a\uff1a<\/p>\n<pre><code>public void OnPost(ProductModel product)<\/code><\/pre>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/1604.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 16.4 Using model binding to create an instance of a model that\u2019s used to execute a Razor Page<br \/>\n\u56fe 16.4 \u4f7f\u7528\u6a21\u578b\u7ed1\u5b9a\u521b\u5efa\u7528\u4e8e\u6267\u884c Razor \u9875\u9762\u7684\u6a21\u578b\u5b9e\u4f8b<\/p>\n<p>The Id property has been bound from a URL route parameter, but the Name and SellPrice properties have been bound from the request body. The big advantage of using model binding is that you don\u2019t have to write the code to parse requests and map the data yourself. This sort of code is typically repetitive and error-prone, so using the built-in conventional approach lets you focus on the important aspects of your application: the business requirements.<br \/>\nId \u5c5e\u6027\u5df2\u4ece URL \u8def\u7531\u53c2\u6570\u7ed1\u5b9a\uff0c\u4f46 Name \u548c SellPrice \u5c5e\u6027\u5df2\u4ece\u8bf7\u6c42\u6b63\u6587\u7ed1\u5b9a\u3002\u4f7f\u7528\u6a21\u578b\u7ed1\u5b9a\u7684\u4e00\u5927\u4f18\u70b9\u662f\uff0c\u60a8\u4e0d\u5fc5\u81ea\u5df1\u7f16\u5199\u4ee3\u7801\u6765\u89e3\u6790\u8bf7\u6c42\u548c\u6620\u5c04\u6570\u636e\u3002\u6b64\u7c7b\u4ee3\u7801\u901a\u5e38\u662f\u91cd\u590d\u7684\u4e14\u5bb9\u6613\u51fa\u9519\uff0c\u56e0\u6b64\u4f7f\u7528\u5185\u7f6e\u7684\u5e38\u89c4\u65b9\u6cd5\u53ef\u4ee5\u8ba9\u60a8\u4e13\u6ce8\u4e8e\u5e94\u7528\u7a0b\u5e8f\u7684\u91cd\u8981\u65b9\u9762\uff1a\u4e1a\u52a1\u9700\u6c42\u3002<\/p>\n<p><b>Tip<\/b> Model binding is great for reducing repetitive code. Take advantage of it whenever possible, and you\u2019ll rarely find yourself having to access the Request object directly.<br \/>\n\u63d0\u793a:\u6a21\u578b\u7ed1\u5b9a\u975e\u5e38\u9002\u5408\u51cf\u5c11\u91cd\u590d\u4ee3\u7801\u3002\u5c3d\u53ef\u80fd\u5229\u7528\u5b83\uff0c\u60a8\u5f88\u5c11\u4f1a\u53d1\u73b0\u81ea\u5df1\u5fc5\u987b\u76f4\u63a5\u8bbf\u95ee Request \u5bf9\u8c61\u3002<\/p>\n<p>If you need to, the capabilities are there to let you completely customize the way model binding works, but it\u2019s relatively rare that you\u2019ll find yourself needing to dig too deep into this. For the majority of cases, it works as is, as you\u2019ll see in the remainder of this section.<br \/>\n\u5982\u679c\u9700\u8981\uff0c\u8fd9\u4e9b\u529f\u80fd\u53ef\u4ee5\u8ba9\u60a8\u5b8c\u5168\u81ea\u5b9a\u4e49\u6a21\u578b\u7ed1\u5b9a\u7684\u5de5\u4f5c\u65b9\u5f0f\uff0c\u4f46\u76f8\u5bf9\u8f83\u5c11\u7684\u60c5\u51b5\u662f\uff0c\u60a8\u4f1a\u53d1\u73b0\u81ea\u5df1\u9700\u8981\u5bf9\u6b64\u8fdb\u884c\u6df1\u5165\u7684\u7814\u7a76\u3002\u5728\u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u5b83\u6309\u539f\u6837\u5de5\u4f5c\uff0c\u5982\u672c\u8282\u7684\u5176\u4f59\u90e8\u5206\u6240\u793a\u3002<\/p>\n<h3>16.2.1 Binding simple types<\/h3>\n<p>16.2.1 \u7ed1\u5b9a\u7b80\u5355\u7c7b\u578b<\/p>\n<p>We\u2019ll start our journey into model binding by considering a simple Razor Page handler. The next listing shows a simple Razor Page that takes one number as a method parameter and squares it by multiplying the number by itself.<br \/>\n\u6211\u4eec\u5c06\u901a\u8fc7\u8003\u8651\u4e00\u4e2a\u7b80\u5355\u7684 Razor Page \u5904\u7406\u7a0b\u5e8f\u6765\u5f00\u59cb\u6a21\u578b\u7ed1\u5b9a\u4e4b\u65c5\u3002\u4e0b\u4e00\u4e2a\u6e05\u5355\u663e\u793a\u4e86\u4e00\u4e2a\u7b80\u5355\u7684 Razor Page\uff0c\u5b83\u91c7\u7528\u4e00\u4e2a\u6570\u5b57\u4f5c\u4e3a\u65b9\u6cd5\u53c2\u6570\uff0c\u5e76\u901a\u8fc7\u5c06\u6570\u5b57\u672c\u8eab\u4e58\u4ee5\u6765\u5e73\u65b9\u3002<\/p>\n<p>Listing 16.2 A Razor Page accepting a simple parameter<br \/>\n\u5217\u8868 16.2 \u63a5\u53d7\u7b80\u5355\u53c2\u6570\u7684 Razor \u9875\u9762<\/p>\n<pre><code>public class CalculateSquareModel : PageModel\n{\npublic void OnGet(int number) \u2776\n{\nSquare = number * number; \u2777\n}\npublic int Square { get; set; } \u2778\n}<\/code><\/pre>\n<p>\u2776 The method parameter is the binding model.<br \/>\nmethod \u53c2\u6570\u662f\u7ed1\u5b9a\u6a21\u578b\u3002<br \/>\n\u2777 A more complex example would do this work in an external service, in the application model.<br \/>\n\u4e00\u4e2a\u66f4\u590d\u6742\u7684\u793a\u4f8b\u662f\u5728\u5e94\u7528\u7a0b\u5e8f\u6a21\u578b\u4e2d\u7684\u5916\u90e8\u670d\u52a1\u4e2d\u5b8c\u6210\u8fd9\u9879\u5de5\u4f5c\u3002<br \/>\n\u2778 The result is exposed as a property and is used by the view to generate a response.<br \/>\n\u7ed3\u679c\u4f5c\u4e3a\u5c5e\u6027\u516c\u5f00\uff0c\u5e76\u7531\u89c6\u56fe\u7528\u4e8e\u751f\u6210\u54cd\u5e94\u3002<\/p>\n<p>In chapters 6 and 14, you learned about routing and how it selects a Razor Page to execute. You can update the route template for the Razor Page to be &quot;CalculateSquare\/{number}&quot; by adding a {number} segment to the Razor Page\u2019s @page directive in the .cshtml file:<br \/>\n\u5728\u7b2c 6 \u7ae0\u548c\u7b2c 14 \u7ae0\u4e2d\uff0c\u60a8\u4e86\u89e3\u4e86\u8def\u7531\u4ee5\u53ca\u5b83\u5982\u4f55\u9009\u62e9\u8981\u6267\u884c\u7684 Razor \u9875\u9762\u3002\u53ef\u4ee5\u901a\u8fc7\u5728 .cshtml \u6587\u4ef6\u4e2d\u5c06 {number} \u6bb5\u6dfb\u52a0\u5230 Razor \u9875\u9762\u7684 @page \u6307\u4ee4\uff0c\u5c06 Razor \u9875\u9762\u7684\u8def\u7531\u6a21\u677f\u66f4\u65b0\u4e3a\u201cCalculateSquare\/{number}\u201d\uff1a<\/p>\n<pre><code>@page &quot;{number}&quot;<\/code><\/pre>\n<p>When a client requests the URL \/CalculateSquare\/5, the Razor Page framework uses routing to parse it for route parameters. This produces the route value pair<br \/>\n\u5f53\u5ba2\u6237\u7aef\u8bf7\u6c42 URL \/CalculateSquare\/5 \u65f6\uff0cRazor Page \u6846\u67b6\u4f7f\u7528\u8def\u7531\u6765\u5206\u6790\u8def\u7531\u53c2\u6570\u3002\u8fd9\u5c06\u751f\u6210\u8def\u7531\u503c\u5bf9<\/p>\n<pre><code>number=5<\/code><\/pre>\n<p>The Razor Page\u2019s OnGet page handler contains a single parameter\u2014an integer called number\u2014which is your binding model. When ASP.NET Core executes this page handler method, it will spot the expected parameter, flick through the route values associated with the request, and find the number=5 pair. Then it can bind the number parameter to this route value and execute the method. The page handler method itself doesn\u2019t care where this value came from; it goes along its merry way, calculating the square of the value and setting it on the Square property.<br \/>\nRazor Page \u7684 OnGet \u9875\u9762\u5904\u7406\u7a0b\u5e8f\u5305\u542b\u4e00\u4e2a\u53c2\u6570\uff08\u79f0\u4e3a number \u7684\u6574\u6570\uff09\uff0c\u8be5\u53c2\u6570\u662f\u7ed1\u5b9a\u6a21\u578b\u3002\u5f53 ASP.NET Core \u6267\u884c\u6b64\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u65f6\uff0c\u5b83\u5c06\u53d1\u73b0\u9884\u671f\u7684\u53c2\u6570\uff0c\u6d4f\u89c8\u4e0e\u8bf7\u6c42\u5173\u8054\u7684\u8def\u7531\u503c\uff0c\u5e76\u627e\u5230 number=5 \u5bf9\u3002\u7136\u540e\u5b83\u53ef\u4ee5\u5c06 number \u53c2\u6570\u7ed1\u5b9a\u5230\u8fd9\u4e2a\u8def\u7531\u503c\u4e0a\uff0c\u5e76\u6267\u884c\u8be5\u65b9\u6cd5\u3002\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u672c\u8eab\u5e76\u4e0d\u5173\u5fc3\u6b64\u503c\u7684\u6765\u6e90;\u5b83\u6cbf\u7740\u5feb\u4e50\u7684\u65b9\u5f0f\u524d\u8fdb\uff0c\u8ba1\u7b97\u503c\u7684\u5e73\u65b9\u5e76\u5c06\u5176\u8bbe\u7f6e\u4e3a Square \u5c5e\u6027\u3002<\/p>\n<p>The key thing to appreciate is that you didn\u2019t have to write any extra code to try to extract the number from the URL when the method executed. All you needed to do was create a method parameter (or public property) with the right name and let model binding do its magic.<br \/>\n\u9700\u8981\u6ce8\u610f\u7684\u5173\u952e\u662f\uff0c\u5728\u65b9\u6cd5\u6267\u884c\u65f6\uff0c\u60a8\u4e0d\u5fc5\u7f16\u5199\u4efb\u4f55\u989d\u5916\u7684\u4ee3\u7801\u6765\u5c1d\u8bd5\u4ece URL \u4e2d\u63d0\u53d6\u6570\u5b57\u3002\u60a8\u9700\u8981\u505a\u7684\u5c31\u662f\u521b\u5efa\u4e00\u4e2a\u5177\u6709\u6b63\u786e\u540d\u79f0\u7684\u65b9\u6cd5\u53c2\u6570\uff08\u6216\u516c\u5171\u5c5e\u6027\uff09\uff0c\u7136\u540e\u8ba9\u6a21\u578b\u7ed1\u5b9a\u53d1\u6325\u5b83\u7684\u9b54\u529b\u3002<\/p>\n<p>Route values aren\u2019t the only values the Razor Pages model binder can use to create your binding models. As you saw previously, the framework will look through three default binding sources to find a match for your binding models:<br \/>\n\u8def\u7531\u503c\u5e76\u4e0d\u662f Razor Pages \u6a21\u578b\u7ed1\u5b9a\u5668\u53ef\u7528\u4e8e\u521b\u5efa\u7ed1\u5b9a\u6a21\u578b\u7684\u552f\u4e00\u503c\u3002\u5982\u524d\u6240\u8ff0\uff0c\u6846\u67b6\u5c06\u904d\u5386\u4e09\u4e2a\u9ed8\u8ba4\u7ed1\u5b9a\u6e90\uff0c\u4ee5\u67e5\u627e\u7ed1\u5b9a\u6a21\u578b\u7684\u5339\u914d\u9879\uff1a<\/p>\n<p>\u2022  Form values<br \/>\n\u2022  Route values<br \/>\n\u2022  Query string values<\/p>\n<p>Each of these binding sources store values as name-value pairs. If none of the binding sources contains the required value, the binding model is set to a new, default instance of the type instead. The exact value the binding model will have in this case depends on the type of the variable:<br \/>\n\u8fd9\u4e9b\u7ed1\u5b9a\u6e90\u4e2d\u7684\u6bcf\u4e00\u4e2a\u90fd\u5c06\u503c\u5b58\u50a8\u4e3a\u540d\u79f0-\u503c\u5bf9\u3002\u5982\u679c\u6ca1\u6709\u4efb\u4f55\u7ed1\u5b9a\u6e90\u5305\u542b\u6240\u9700\u7684\u503c\uff0c\u5219\u7ed1\u5b9a\u6a21\u578b\u5c06\u6539\u4e3a\u8be5\u7c7b\u578b\u7684\u65b0\u9ed8\u8ba4\u5b9e\u4f8b\u3002\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u7ed1\u5b9a\u6a21\u578b\u5c06\u5177\u6709\u7684\u786e\u5207\u503c\u53d6\u51b3\u4e8e\u53d8\u91cf\u7684\u7c7b\u578b\uff1a<\/p>\n<p>\u2022  For value types, the value will be default(T). For an int parameter this would be 0, and for a bool it would be false.<br \/>\n\u5bf9\u4e8e\u503c\u7c7b\u578b\uff0c\u8be5\u503c\u5c06\u4e3a default\uff08T\uff09\u3002\u5bf9\u4e8e int \u53c2\u6570\uff0c\u6b64\u503c\u4e3a 0\uff0c\u5bf9\u4e8e bool \u53c2\u6570\uff0c\u6b64\u503c\u4e3a false\u3002<br \/>\n\u2022  For reference types, the type is created using the default (parameterless) constructor. For custom types like ProductModel, that will create a new object. For nullable types like int? or bool?, the value will be null.<br \/>\n\u5bf9\u4e8e\u5f15\u7528\u7c7b\u578b\uff0c\u7c7b\u578b\u662f\u4f7f\u7528\u9ed8\u8ba4 \uff08\u65e0\u53c2\u6570\uff09 \u6784\u9020\u51fd\u6570\u521b\u5efa\u7684\u3002\u5bf9\u4e8e\u50cf ProductModel \u8fd9\u6837\u7684\u81ea\u5b9a\u4e49\u7c7b\u578b\uff0c\u8fd9\u5c06\u521b\u5efa\u4e00\u4e2a\u65b0\u5bf9\u8c61\u3002\u5bf9\u4e8e\u50cf int \u8fd9\u6837\u7684\u53ef\u7a7a\u7c7b\u578b\uff1f\u6216 bool\uff1f\uff0c\u5219\u503c\u4e3a null\u3002<br \/>\n\u2022  For string types, the value will be null.<br \/>\n\u5bf9\u4e8e\u5b57\u7b26\u4e32\u7c7b\u578b\uff0c\u8be5\u503c\u5c06\u4e3a null\u3002<\/p>\n<p><b>Warning<\/b> It\u2019s important to consider the behavior of your page handler when model binding fails to bind your method parameters. If none of the binding sources contains the value, the value passed to the method could be null or could unexpectedly have a default value (for value types).<br \/>\n\u8b66\u544a:\u5f53\u6a21\u578b\u7ed1\u5b9a\u65e0\u6cd5\u7ed1\u5b9a\u65b9\u6cd5\u53c2\u6570\u65f6\uff0c\u8bf7\u52a1\u5fc5\u8003\u8651\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u7684\u884c\u4e3a\u3002\u5982\u679c\u6ca1\u6709\u4efb\u4f55\u7ed1\u5b9a\u6e90\u5305\u542b\u8be5\u503c\uff0c\u5219\u4f20\u9012\u7ed9\u8be5\u65b9\u6cd5\u7684\u503c\u53ef\u80fd\u4e3a null\uff0c\u6216\u8005\u53ef\u80fd\u610f\u5916\u5730\u5177\u6709\u9ed8\u8ba4\u503c\uff08\u5bf9\u4e8e\u503c\u7c7b\u578b\uff09\u3002<\/p>\n<p>Listing 16.2 showed how to bind a single method parameter. Let\u2019s take the next logical step and look at how you\u2019d bind multiple method parameters.<br \/>\n\u6e05\u5355 16.2 \u5c55\u793a\u4e86\u5982\u4f55\u7ed1\u5b9a\u5355\u4e2a\u65b9\u6cd5\u53c2\u6570\u3002\u8ba9\u6211\u4eec\u8fdb\u884c\u4e0b\u4e00\u4e2a\u903b\u8f91\u6b65\u9aa4\uff0c\u770b\u770b\u5982\u4f55\u7ed1\u5b9a\u591a\u4e2a\u65b9\u6cd5\u53c2\u6570\u3002<\/p>\n<p>Let\u2019s say you\u2019re building a currency converter application. As the first step you need to create a method in which the user provides a value in one currency, and you must convert it to another. You first create a Razor Page called Convert.cshtml and then customize the route template for the page using the @page directive to use an absolute path containing two route values:<br \/>\n\u5047\u8bbe\u60a8\u6b63\u5728\u6784\u5efa\u4e00\u4e2a\u8d27\u5e01\u8f6c\u6362\u5668\u5e94\u7528\u7a0b\u5e8f\u3002\u7b2c\u4e00\u6b65\uff0c\u60a8\u9700\u8981\u521b\u5efa\u4e00\u4e2a\u65b9\u6cd5\uff0c\u5728\u8be5\u65b9\u6cd5\u4e2d\uff0c\u7528\u6237\u4ee5\u4e00\u79cd\u8d27\u5e01\u63d0\u4f9b\u503c\uff0c\u5e76\u4e14\u5fc5\u987b\u5c06\u5176\u8f6c\u6362\u4e3a\u53e6\u4e00\u79cd\u8d27\u5e01\u3002\u9996\u5148\u521b\u5efa\u4e00\u4e2a\u540d\u4e3a Convert.cshtml \u7684 Razor \u9875\u9762\uff0c\u7136\u540e\u4f7f\u7528 @page \u6307\u4ee4\u81ea\u5b9a\u4e49\u9875\u9762\u7684\u8def\u7531\u6a21\u677f\uff0c\u4ee5\u4f7f\u7528\u5305\u542b\u4e24\u4e2a\u8def\u7531\u503c\u7684\u7edd\u5bf9\u8def\u5f84\uff1a<\/p>\n<pre><code>@page &quot;\/{currencyIn}\/{currencyOut}&quot;<\/code><\/pre>\n<p>Then you create a page handler that accepts the three values you need, as shown in the following listing.<br \/>\n\u7136\u540e\uff0c\u521b\u5efa\u4e00\u4e2a\u63a5\u53d7\u60a8\u9700\u8981\u7684 3 \u4e2a\u503c\u7684\u9875\u9762\u5904\u7406\u7a0b\u5e8f\uff0c\u5982\u4e0b\u9762\u7684\u6e05\u5355\u6240\u793a\u3002<\/p>\n<p>Listing 16.3 A Razor Page handler accepting multiple binding parameters<br \/>\n\u5217\u8868 16.3 \u63a5\u53d7\u591a\u4e2a\u7ed1\u5b9a\u53c2\u6570\u7684 Razor Page \u5904\u7406\u7a0b\u5e8f<\/p>\n<pre><code>public class ConvertModel : PageModel\n{\n    public void OnGet(\n        string currencyIn,\n        string currencyOut,\n        int qty\n)\n    {\n        \/* method implementation *\/\n    }\n}<\/code><\/pre>\n<p>As you can see, there are three different parameters to bind. The question is, where will the values come from and what will they be set to? The answer is, it depends! Table 16.1 shows a whole variety of possibilities. All these examples use the same route template and page handler, but depending on the data sent, different values will be bound. The actual values might differ from what you expect, as the available binding sources offer conflicting values!<br \/>\n\u5982\u60a8\u6240\u89c1\uff0c\u6709\u4e09\u4e2a\u4e0d\u540c\u7684\u53c2\u6570\u9700\u8981\u7ed1\u5b9a\u3002\u95ee\u9898\u662f\uff0c\u8fd9\u4e9b\u503c\u4ece\u4f55\u800c\u6765\uff0c\u5b83\u4eec\u5c06\u8bbe\u7f6e\u4e3a\u4ec0\u4e48\uff1f\u7b54\u6848\u662f\uff0c\u8fd9\u8981\u770b\u60c5\u51b5\uff01\u8868 16.1 \u663e\u793a\u4e86\u5404\u79cd\u53ef\u80fd\u6027\u3002\u6240\u6709\u8fd9\u4e9b\u793a\u4f8b\u90fd\u4f7f\u7528\u76f8\u540c\u7684\u8def\u7531\u6a21\u677f\u548c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\uff0c\u4f46\u6839\u636e\u53d1\u9001\u7684\u6570\u636e\uff0c\u5c06\u7ed1\u5b9a\u4e0d\u540c\u7684\u503c\u3002\u5b9e\u9645\u503c\u53ef\u80fd\u4e0e\u60a8\u7684\u9884\u671f\u4e0d\u540c\uff0c\u56e0\u4e3a\u53ef\u7528\u7684\u7ed1\u5b9a\u6e90\u63d0\u4f9b\u7684\u503c\u76f8\u4e92\u51b2\u7a81\uff01<\/p>\n<p>Table 16.1 Binding request data to page handler parameters from multiple binding sources<br \/>\n\u8868 16.1 \u5c06\u8bf7\u6c42\u6570\u636e\u7ed1\u5b9a\u5230\u6765\u81ea\u591a\u4e2a\u7ed1\u5b9a\u6e90\u7684\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u53c2\u6570<\/p>\n<table>\n<thead>\n<tr>\n<th>URL (route values)<\/th>\n<th>HTTP body data (form values)<\/th>\n<th>Parameter values bound<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>\/GBP\/USD<\/td>\n<td>-<\/td>\n<td>currencyIn=GBP <\/br> currencyOut=USD qty=0<\/td>\n<\/tr>\n<tr>\n<td>\/GBP\/USD?currencyIn=CAD<\/td>\n<td>QTY=50<\/td>\n<td>currencyIn=GBP <\/br> currencyOut=USD qty=50<\/td>\n<\/tr>\n<tr>\n<td>\/GBP\/USD?qty=100<\/td>\n<td>qty=50<\/td>\n<td>currencyIn=GBP <\/br> currencyOut=USD qty=50<\/td>\n<\/tr>\n<tr>\n<td>\/GBP\/USD?qty=100<\/td>\n<td>currencyIn=CAD&amp; <\/br> currencyOut=EUR&amp;qty=50<\/td>\n<td>currencyIn=CAD <\/br> currencyOut=EUR qty=50<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>For each example, be sure you understand why the bound values have the values that they do. In the first example, the qty value isn\u2019t found in the form data, in the route values, or in the query string, so it has the default value of 0. In each of the other examples, the request contains one or more duplicated values; in these cases, it\u2019s important to bear in mind the order in which the model binder consults the binding sources. By default, form values will take precedence over other binding sources, including route values!<br \/>\n\u5bf9\u4e8e\u6bcf\u4e2a\u793a\u4f8b\uff0c\u8bf7\u786e\u4fdd\u60a8\u4e86\u89e3\u4e3a\u4ec0\u4e48\u7ed1\u5b9a\u503c\u5177\u6709\u5b83\u4eec\u6240\u5177\u6709\u7684\u503c\u3002\u5728\u7b2c\u4e00\u4e2a\u793a\u4f8b\u4e2d\uff0c\u5728\u8868\u5355\u6570\u636e\u3001\u8def\u7531\u503c\u6216\u67e5\u8be2\u5b57\u7b26\u4e32\u4e2d\u627e\u4e0d\u5230 qty \u503c\uff0c\u56e0\u6b64\u5b83\u7684\u9ed8\u8ba4\u503c\u4e3a 0\u3002\u5728\u6240\u6709\u5176\u4ed6\u793a\u4f8b\u4e2d\uff0c\u8bf7\u6c42\u90fd\u5305\u542b\u4e00\u4e2a\u6216\u591a\u4e2a\u91cd\u590d\u503c;\u5728\u8fd9\u4e9b\u60c5\u51b5\u4e0b\uff0c\u8bf7\u52a1\u5fc5\u8bb0\u4f4f Model Binder \u67e5\u8be2 Binding \u6e90\u7684\u987a\u5e8f\u3002\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u8868\u5355\u503c\u5c06\u4f18\u5148\u4e8e\u5176\u4ed6\u7ed1\u5b9a\u6e90\uff0c\u5305\u62ec\u8def\u7531\u503c\uff01<\/p>\n<p><b>NOTE<\/b> The default model binder isn\u2019t case-sensitive, so a binding value of QTY=50 will happily bind to the qty parameter.<br \/>\n\u6ce8\u610f:\u9ed8\u8ba4\u6a21\u578b\u7ed1\u5b9a\u5668\u4e0d\u533a\u5206\u5927\u5c0f\u5199\uff0c\u56e0\u6b64 QTY=50 \u7684\u7ed1\u5b9a\u503c\u5c06\u5f88\u9ad8\u5174\u5730\u7ed1\u5b9a\u5230 qty \u53c2\u6570\u3002<\/p>\n<p>Although this may seem a little overwhelming, it\u2019s relatively unusual to be binding from all these different sources at once. It\u2019s more common to have your values all come from the request body as form values, maybe with an ID from URL route values. This scenario serves as more of a cautionary tale about the knots you can twist yourself into if you\u2019re not sure how things work under the hood.<br \/>\n\u867d\u7136\u8fd9\u770b\u8d77\u6765\u6709\u70b9\u8ba9\u4eba\u4e0d\u77e5\u6240\u63aa\uff0c\u4f46\u540c\u65f6\u4ece\u6240\u6709\u8fd9\u4e9b\u4e0d\u540c\u7684\u6765\u6e90\u7ed1\u5b9a\u662f\u76f8\u5bf9\u4e0d\u5bfb\u5e38\u7684\u3002\u66f4\u5e38\u89c1\u7684\u505a\u6cd5\u662f\uff0c\u4f60\u7684\u503c\u90fd\u6765\u81ea\u8bf7\u6c42\u6b63\u6587\u4f5c\u4e3a\u8868\u5355\u503c\uff0c\u53ef\u80fd\u5e26\u6709\u6765\u81ea URL \u8def\u7531\u503c\u7684 ID\u3002\u8fd9\u4e2a\u573a\u666f\u66f4\u50cf\u662f\u4e00\u4e2a\u8b66\u793a\u6545\u4e8b\uff0c\u5982\u679c\u4f60\u4e0d\u786e\u5b9a\u5f15\u64ce\u76d6\u4e0b\u7684\u4e8b\u60c5\u662f\u5982\u4f55\u8fd0\u4f5c\u7684\uff0c\u4f60\u53ef\u4ee5\u628a\u81ea\u5df1\u626d\u6210\u4e00\u56e2\u3002<\/p>\n<p>In these examples, you happily bound the qty integer property to incoming values, but as I mentioned earlier, the values stored in binding sources are all strings. What types can you convert a string to?<br \/>\n\u5728\u8fd9\u4e9b\u793a\u4f8b\u4e2d\uff0c\u60a8\u6109\u5feb\u5730\u5c06 qty integer \u5c5e\u6027\u7ed1\u5b9a\u5230\u4f20\u5165\u503c\uff0c\u4f46\u6b63\u5982\u6211\u524d\u9762\u63d0\u5230\u7684\uff0c\u5b58\u50a8\u5728\u7ed1\u5b9a\u6e90\u4e2d\u7684\u503c\u90fd\u662f\u5b57\u7b26\u4e32\u3002\u4f60\u53ef\u4ee5\u5c06\u5b57\u7b26\u4e32\u8f6c\u6362\u4e3a\u54ea\u4e9b\u7c7b\u578b\uff1f<\/p>\n<p>The model binder will convert pretty much any primitive .NET type such as int, float, decimal (and string obviously), any custom type that has a TryParse method (like minimal APIs, as you saw in chapter 7) plus anything that has a TypeConverter.<br \/>\n\u6a21\u578b\u7ed1\u5b9a\u5668\u5c06\u8f6c\u6362\u51e0\u4e4e\u4efb\u4f55\u57fa\u5143 .NET \u7c7b\u578b\uff0c\u5982 int\u3001float\u3001decimal\uff08\u663e\u7136\u8fd8\u6709 string\uff09\u3001\u4efb\u4f55\u5177\u6709 TryParse \u65b9\u6cd5\u7684\u81ea\u5b9a\u4e49\u7c7b\u578b\uff08\u5982\u7b2c 7 \u7ae0\u4e2d\u6240\u770b\u5230\u7684\u6700\u5c0f API\uff09\u4ee5\u53ca\u4efb\u4f55\u5177\u6709 TypeConverter \u7684\u7c7b\u578b\u3002<\/p>\n<p><b>NOTE<\/b> TypeConverters can be found in the System.ComponentModel.TypeConverter package. You can read more about them in Microsoft\u2019s \u201cType conversion in .NET\u201d documentation: <a href=\"http:\/\/mng.bz\/A0GK\">http:\/\/mng.bz\/A0GK<\/a>.<br \/>\n\u6ce8\u610f:TypeConverters \u53ef\u4ee5\u5728 System.ComponentModel.TypeConverter \u5305\u4e2d\u627e\u5230\u3002\u60a8\u53ef\u4ee5\u5728 Microsoft \u7684\u201c.NET \u4e2d\u7684\u7c7b\u578b\u8f6c\u6362\u201d\u6587\u6863\u4e2d\u9605\u8bfb\u6709\u5173\u5b83\u4eec\u7684\u66f4\u591a\u4fe1\u606f\uff1a<a href=\"http:\/\/mng.bz\/A0GK\">http:\/\/mng.bz\/A0GK<\/a>\u3002<\/p>\n<p>There are a few other special cases that can be converted from a string, such as Type, but thinking of it as built-in types only will get you a long way there!<br \/>\n\u8fd8\u6709\u4e00\u4e9b\u5176\u4ed6\u7279\u6b8a\u60c5\u51b5\u53ef\u4ee5\u4ece\u5b57\u7b26\u4e32\u8f6c\u6362\uff0c\u4f8b\u5982 Type\uff0c\u4f46\u4ec5\u5c06\u5176\u89c6\u4e3a\u5185\u7f6e\u7c7b\u578b\u5c06\u4f7f\u60a8\u5927\u6709\u5e2e\u52a9\uff01<\/p>\n<h3>16.2.2 Binding complex types<\/h3>\n<p>16.2.2 \u7ed1\u5b9a\u590d\u6742\u7c7b\u578b<\/p>\n<p>If it seems like only being able to bind simple built-in types is a bit limiting, you\u2019re right! Luckily, that\u2019s not the case for the model binder. Although it can only convert strings directly to those simple types, it\u2019s also able to bind complex types by traversing any properties your binding models expose, binding each of those properties to strings instead.<br \/>\n\u5982\u679c\u770b\u8d77\u6765\u53ea\u80fd\u7ed1\u5b9a\u7b80\u5355\u7684\u5185\u7f6e\u7c7b\u578b\u6709\u70b9\u9650\u5236\uff0c\u90a3\u4f60\u662f\u5bf9\u7684\uff01\u5e78\u8fd0\u7684\u662f\uff0c\u6a21\u578b Binder \u5e76\u975e\u5982\u6b64\u3002\u867d\u7136\u5b83\u53ea\u80fd\u5c06\u5b57\u7b26\u4e32\u76f4\u63a5\u8f6c\u6362\u4e3a\u8fd9\u4e9b\u7b80\u5355\u7c7b\u578b\uff0c\u4f46\u5b83\u4e5f\u80fd\u591f\u901a\u8fc7\u904d\u5386\u7ed1\u5b9a\u6a21\u578b\u516c\u5f00\u7684\u4efb\u4f55\u5c5e\u6027\u6765\u7ed1\u5b9a\u590d\u6742\u7c7b\u578b\uff0c\u800c\u662f\u5c06\u6bcf\u4e2a\u5c5e\u6027\u7ed1\u5b9a\u5230\u5b57\u7b26\u4e32\u3002<\/p>\n<p>If this doesn\u2019t make you happy straight off the bat, let\u2019s look at how you\u2019d have to build your page handlers if simple types were your only option. Imagine a user of your currency converter application has reached a checkout page and is going to exchange some currency. Great! All you need now is to collect their name, email address, and phone number. Unfortunately, your page handler method would have to look something like this:<br \/>\n\u5982\u679c\u8fd9\u4e0d\u80fd\u8ba9\u4f60\u7acb\u5373\u6ee1\u610f\uff0c\u8ba9\u6211\u4eec\u770b\u770b\u5982\u679c\u7b80\u5355\u7c7b\u578b\u662f\u4f60\u552f\u4e00\u7684\u9009\u62e9\uff0c\u4f60\u5c06\u5982\u4f55\u6784\u5efa\u4f60\u7684\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u3002\u60f3\u8c61\u4e00\u4e0b\uff0c\u60a8\u7684\u8d27\u5e01\u8f6c\u6362\u5668\u5e94\u7528\u7a0b\u5e8f\u7684\u7528\u6237\u5df2\u5230\u8fbe\u7ed3\u5e10\u9875\u9762\u5e76\u51c6\u5907\u5151\u6362\u4e00\u4e9b\u8d27\u5e01\u3002\u4f1f\u5927\uff01\u60a8\u73b0\u5728\u9700\u8981\u505a\u7684\u5c31\u662f\u6536\u96c6\u4ed6\u4eec\u7684\u59d3\u540d\u3001\u7535\u5b50\u90ae\u4ef6\u5730\u5740\u548c\u7535\u8bdd\u53f7\u7801\u3002\u4e0d\u5e78\u7684\u662f\uff0c\u60a8\u7684\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u5fc5\u987b\u5982\u4e0b\u6240\u793a\uff1a<\/p>\n<pre><code>public IActionResult OnPost(string firstName, string lastName, string phoneNumber, string email)<\/code><\/pre>\n<p>Yuck! Four parameters might not seem that bad right now, but what happens when the requirements change and you need to collect other details? The method signature will keep growing. The model binder will bind the values quite happily, but it\u2019s not exactly clean code. Using the [BindProperty] approach doesn\u2019t really help either; you still have to clutter your PageModel with lots of properties and attributes!<br \/>\n\u5478\uff01\u56db\u4e2a\u53c2\u6570\u73b0\u5728\u770b\u8d77\u6765\u53ef\u80fd\u8fd8\u4e0d\u9519\uff0c\u4f46\u662f\u5f53\u9700\u6c42\u53d1\u751f\u53d8\u5316\u5e76\u4e14\u60a8\u9700\u8981\u6536\u96c6\u5176\u4ed6\u8be6\u7ec6\u4fe1\u606f\u65f6\u4f1a\u53d1\u751f\u4ec0\u4e48\uff1f\u65b9\u6cd5\u7b7e\u540d\u5c06\u4e0d\u65ad\u589e\u957f\u3002\u6a21\u578b Binder \u4f1a\u975e\u5e38\u6109\u5feb\u5730\u7ed1\u5b9a\u8fd9\u4e9b\u503c\uff0c\u4f46\u5b83\u5e76\u4e0d\u662f\u5b8c\u5168\u5e72\u51c0\u7684\u4ee3\u7801\u3002\u4f7f\u7528 [BindProperty] \u65b9\u6cd5\u4e5f\u6ca1\u6709\u771f\u6b63\u7684\u5e2e\u52a9;\u4f60\u4ecd\u7136\u9700\u8981\u7528\u5927\u91cf\u7684\u5c5e\u6027\u548c\u7279\u6027\u6765\u6574\u7406\u4f60\u7684 PageModel\uff01<\/p>\n<blockquote>\n<p>Simplifying method parameters by binding to complex objects<br \/>\n\u901a\u8fc7\u7ed1\u5b9a\u5230\u590d\u6742\u5bf9\u8c61\u6765\u7b80\u5316\u65b9\u6cd5\u53c2\u6570<\/p>\n<\/blockquote>\n<p>A common pattern for any C# code when you have many method parameters is to extract a class that encapsulates the data the method requires. If extra parameters need to be added, you can add a new property to this class. This class becomes your binding model, and it might look something like the following listing.<br \/>\n\u5f53\u6709\u8bb8\u591a\u65b9\u6cd5\u53c2\u6570\u65f6\uff0c\u4efb\u4f55 C# \u4ee3\u7801\u7684\u5e38\u89c1\u6a21\u5f0f\u662f\u63d0\u53d6\u4e00\u4e2a\u5c01\u88c5\u65b9\u6cd5\u6240\u9700\u6570\u636e\u7684\u7c7b\u3002\u5982\u679c\u9700\u8981\u6dfb\u52a0\u989d\u5916\u7684\u53c2\u6570\uff0c\u60a8\u53ef\u4ee5\u5411\u6b64\u7c7b\u6dfb\u52a0\u65b0\u5c5e\u6027\u3002\u8fd9\u4e2a\u7c7b\u5c06\u6210\u4e3a\u60a8\u7684\u7ed1\u5b9a\u6a21\u578b\uff0c\u5b83\u53ef\u80fd\u7c7b\u4f3c\u4e8e\u4e0b\u9762\u7684\u6e05\u5355\u3002<\/p>\n<p>Listing 16.4 A binding model for capturing a user\u2019s details<br \/>\n\u6e05\u5355 16.4 \u7528\u4e8e\u6355\u83b7\u7528\u6237\u8be6\u7ec6\u4fe1\u606f\u7684\u7ed1\u5b9a\u6a21\u578b<\/p>\n<pre><code>public class UserBindingModel\n{\n    public string FirstName { get; set; }\n    public string LastName { get; set; }\n    public string Email { get; set; }\n    public string PhoneNumber { get; set; }\n}<\/code><\/pre>\n<p><b>NOTE<\/b> In this book I primarily use class instead of record for my binding models, but you can use record if you prefer. I find the terseness that the record positional syntax provides is lost if you want to add attributes to properties, such as to add validation attributes, as you\u2019ll see in section 16.3. You can see the required syntax for positional property attributes in the documentation at <a href=\"http:\/\/mng.bz\/Kex0\">http:\/\/mng.bz\/Kex0<\/a>.<br \/>\n\u6ce8\u610f:\u5728\u672c\u4e66\u4e2d\uff0c\u6211\u4e3b\u8981\u4f7f\u7528 class \u800c\u4e0d\u662f record \u4f5c\u4e3a\u6211\u7684\u7ed1\u5b9a\u6a21\u578b\uff0c\u4f46\u5982\u679c\u60a8\u613f\u610f\uff0c\u4e5f\u53ef\u4ee5\u4f7f\u7528 record\u3002\u6211\u53d1\u73b0\uff0c\u5982\u679c\u60a8\u60f3\u5411\u5c5e\u6027\u6dfb\u52a0\u5c5e\u6027\uff08\u4f8b\u5982\u6dfb\u52a0\u9a8c\u8bc1\u5c5e\u6027\uff09\uff0c\u5219\u8bb0\u5f55\u4f4d\u7f6e\u8bed\u6cd5\u63d0\u4f9b\u7684\u7b80\u6d01\u6027\u4f1a\u4e22\u5931\uff0c\u5982\u7b2c 16.3 \u8282\u6240\u793a\u3002\u60a8\u53ef\u4ee5\u5728 <a href=\"http:\/\/mng.bz\/Kex0\">http:\/\/mng.bz\/Kex0<\/a> \u6587\u6863\u4e2d\u67e5\u770b\u4f4d\u7f6e\u5c5e\u6027\u5c5e\u6027\u6240\u9700\u7684\u8bed\u6cd5\u3002<\/p>\n<p>With this model, you can update your page handler\u2019s method signature to<br \/>\n\u4f7f\u7528\u6b64\u6a21\u578b\uff0c\u60a8\u53ef\u4ee5\u5c06\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u7684\u65b9\u6cd5\u7b7e\u540d\u66f4\u65b0\u4e3a<\/p>\n<pre><code>public IActionResult OnPost(UserBindingModel user)<\/code><\/pre>\n<p>Alternatively, using the [BindProperty] approach, create a property on the PageModel:<br \/>\n\u6216\u8005\uff0c\u4f7f\u7528 [BindProperty] \u65b9\u6cd5\uff0c\u5728 PageModel \u4e0a\u521b\u5efa\u4e00\u4e2a\u5c5e\u6027\uff1a<\/p>\n<pre><code>[BindProperty]\npublic UserBindingModel User { get; set; }<\/code><\/pre>\n<p>Now you can simplify the page handler signature even further:<br \/>\n\u73b0\u5728\uff0c\u60a8\u53ef\u4ee5\u8fdb\u4e00\u6b65\u7b80\u5316\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u7b7e\u540d\uff1a<\/p>\n<pre><code>public IActionResult OnPost()<\/code><\/pre>\n<p>Functionally, the model binder treats this new complex type a little differently. Rather than look for parameters with a value that matches the parameter name (user, or User for the property), the model binder creates a new instance of the model using new UserBindingModel().<br \/>\n\u4ece\u529f\u80fd\u4e0a\u8bb2\uff0c\u6a21\u578b Binder \u5bf9\u8fd9\u79cd\u65b0\u7684\u590d\u6742\u7c7b\u578b\u7684\u5904\u7406\u65b9\u5f0f\u7565\u6709\u4e0d\u540c\u3002\u6a21\u578b\u7ed1\u5b9a\u5668\u4e0d\u4f1a\u67e5\u627e\u503c\u4e0e\u53c2\u6570\u540d\u79f0\uff08\u6216\u5c5e\u6027\u7684 User\uff09\u5339\u914d\u7684\u53c2\u6570\uff0c\u800c\u662f\u4f7f\u7528 new UserBindingModel\uff08\uff09 \u521b\u5efa\u6a21\u578b\u7684\u65b0\u5b9e\u4f8b\u3002<\/p>\n<p><b>NOTE<\/b> You don\u2019t have to use custom classes for your methods; it depends on your requirements. If your page handler needs only a single integer, it makes more sense to bind to the simple parameter.<br \/>\n\u6ce8\u610f:\u60a8\u4e0d\u5fc5\u4e3a\u60a8\u7684\u65b9\u6cd5\u4f7f\u7528\u81ea\u5b9a\u4e49\u7c7b;\u8fd9\u53d6\u51b3\u4e8e\u60a8\u7684\u8981\u6c42\u3002\u5982\u679c\u60a8\u7684\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u53ea\u9700\u8981\u4e00\u4e2a\u6574\u6570\uff0c\u5219\u7ed1\u5b9a\u5230 simple \u53c2\u6570\u66f4\u6709\u610f\u4e49\u3002<\/p>\n<p>Next, the model binder loops through all the properties your binding model has, such as FirstName and LastName in listing 16.4. For each of these properties, it consults the collection of binding sources and attempts to find a name-value pair that matches. If it finds one, it sets the value on the property and moves on to the next.<br \/>\n\u63a5\u4e0b\u6765\uff0c\u6a21\u578b Binders \u904d\u5386\u7ed1\u5b9a\u6a21\u578b\u5177\u6709\u7684\u6240\u6709\u5c5e\u6027\uff0c\u4f8b\u5982\u6e05\u5355 16.4 \u4e2d\u7684 FirstName \u548c LastName\u3002\u5bf9\u4e8e\u5176\u4e2d\u6bcf\u4e2a\u5c5e\u6027\uff0c\u5b83\u90fd\u4f1a\u67e5\u8be2\u7ed1\u5b9a\u6e90\u7684\u96c6\u5408\uff0c\u5e76\u5c1d\u8bd5\u67e5\u627e\u5339\u914d\u7684\u540d\u79f0\/\u503c\u5bf9\u3002\u5982\u679c\u627e\u5230\u4e00\u4e2a\uff0c\u5b83\u5c06\u8bbe\u7f6e\u8be5\u5c5e\u6027\u7684\u503c\uff0c\u7136\u540e\u7ee7\u7eed\u6267\u884c\u4e0b\u4e00\u4e2a\u5c5e\u6027\u3002<\/p>\n<p><b>Tip<\/b> Although the name of the model isn\u2019t necessary in this example, the model binder will also look for properties prefixed with the name of the property, such as user.FirstName and user.LastName for a property called User. You can use this approach when you have multiple complex parameters to a page handler or multiple complex [BindProperty] properties. In general, for simplicity, you should avoid this situation if possible. As for all model binding, the casing of the prefix does not matter.<br \/>\n\u63d0\u793a:\u5c3d\u7ba1\u5728\u6b64\u793a\u4f8b\u4e2d\u4e0d\u9700\u8981\u6a21\u578b\u540d\u79f0\uff0c\u4f46\u6a21\u578b\u7ed1\u5b9a\u5668\u8fd8\u5c06\u67e5\u627e\u4ee5\u5c5e\u6027\u540d\u79f0\u4e3a\u524d\u7f00\u7684\u5c5e\u6027\uff0c\u4f8b\u5982 user\u3002FirstName \u548c user\u3002\u540d\u4e3a User \u7684\u5c5e\u6027\u7684 LastName\u3002\u5f53\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u5177\u6709\u591a\u4e2a\u590d\u6742\u53c2\u6570\u6216\u591a\u4e2a\u590d\u6742 [BindProperty] \u5c5e\u6027\u65f6\uff0c\u53ef\u4ee5\u4f7f\u7528\u6b64\u65b9\u6cd5\u3002\u901a\u5e38\uff0c\u4e3a\u7b80\u5355\u8d77\u89c1\uff0c\u5e94\u5c3d\u53ef\u80fd\u907f\u514d\u8fd9\u79cd\u60c5\u51b5\u3002\u5bf9\u4e8e\u6240\u6709\u6a21\u578b\u7ed1\u5b9a\uff0c\u524d\u7f00\u7684\u5927\u5c0f\u5199\u65e0\u5173\u7d27\u8981\u3002<\/p>\n<p>Once all the properties that can be bound on the binding model are set, the model is passed to the page handler (or the [BindProperty] property is set), and the handler is executed as usual. The behavior from this point on is identical to when you have lots of individual parameters\u2014you\u2019ll end up with the same values set on your binding model\u2014but the code is cleaner and easier to work with.<br \/>\n\u8bbe\u7f6e\u4e86\u53ef\u5728\u7ed1\u5b9a\u6a21\u578b\u4e0a\u7ed1\u5b9a\u7684\u6240\u6709\u5c5e\u6027\u540e\uff0c\u6a21\u578b\u5c06\u4f20\u9012\u7ed9\u9875\u9762\u5904\u7406\u7a0b\u5e8f \uff08\u6216\u8bbe\u7f6e\u4e86 [BindProperty] \u5c5e\u6027\uff09 \uff0c\u5e76\u7167\u5e38\u6267\u884c\u5904\u7406\u7a0b\u5e8f\u3002\u4ece\u6b64\u65f6\u5f00\u59cb\uff0c\u884c\u4e3a\u4e0e\u5177\u6709\u5927\u91cf\u5355\u4e2a\u53c2\u6570\u65f6\u7684\u884c\u4e3a\u76f8\u540c \u2014 \u60a8\u6700\u7ec8\u5c06\u5728\u7ed1\u5b9a\u6a21\u578b\u4e0a\u8bbe\u7f6e\u76f8\u540c\u7684\u503c \u2014 \u4f46\u4ee3\u7801\u66f4\u7b80\u6d01\uff0c\u66f4\u6613\u4e8e\u4f7f\u7528\u3002<\/p>\n<p><b>Tip<\/b> For a class to be model-bound, it must have a default public constructor. You can bind only properties that are public and settable.<br \/>\n\u63d0\u793a:\u5bf9\u4e8e\u8981\u8fdb\u884c\u6a21\u578b\u7ed1\u5b9a\u7684\u7c7b\uff0c\u5b83\u5fc5\u987b\u5177\u6709\u9ed8\u8ba4\u7684 public \u6784\u9020\u51fd\u6570\u3002\u60a8\u53ea\u80fd\u7ed1\u5b9a public \u548c settable \u5c5e\u6027\u3002<\/p>\n<p>With this technique you can bind complex hierarchical models whose properties are themselves complex models. As long as each property exposes a type that can be model-bound, the binder can traverse it with ease.<br \/>\n\u4f7f\u7528\u8fd9\u79cd\u6280\u672f\uff0c\u60a8\u53ef\u4ee5\u7ed1\u5b9a\u590d\u6742\u7684\u5206\u5c42\u6a21\u578b\uff0c\u8fd9\u4e9b\u6a21\u578b\u7684\u5c5e\u6027\u672c\u8eab\u5c31\u662f\u590d\u6742\u6a21\u578b\u3002\u53ea\u8981\u6bcf\u4e2a\u5c5e\u6027\u90fd\u516c\u5f00\u4e00\u4e2a\u53ef\u4ee5\u8fdb\u884c\u6a21\u578b\u7ed1\u5b9a\u7684\u7c7b\u578b\uff0cBinders \u5c31\u53ef\u4ee5\u8f7b\u677e\u904d\u5386\u5b83\u3002<\/p>\n<blockquote>\n<p>Binding collections and dictionaries<br \/>\n\u7ed1\u5b9a\u96c6\u5408\u548c\u8bcd\u5178<br \/>\nAs well as binding to ordinary custom classes and primitives, you can bind to collections, lists, and dictionaries. Imagine you had a page in which a user selected all the currencies they were interested in; you\u2019d display the rates for all those selected, as shown in figure 16.5.<br \/>\n\u9664\u4e86\u7ed1\u5b9a\u5230\u666e\u901a\u81ea\u5b9a\u4e49\u7c7b\u548c\u57fa\u5143\u5916\uff0c\u8fd8\u53ef\u4ee5\u7ed1\u5b9a\u5230\u96c6\u5408\u3001\u5217\u8868\u548c\u8bcd\u5178\u3002\u60f3\u8c61\u4e00\u4e0b\uff0c\u60a8\u6709\u4e00\u4e2a\u9875\u9762\uff0c\u7528\u6237\u5728\u5176\u4e2d\u9009\u62e9\u4e86\u4ed6\u4eec\u611f\u5174\u8da3\u7684\u6240\u6709\u8d27\u5e01;\u5c06\u663e\u793a\u6240\u6709\u9009\u5b9a\u9879\u76ee\u7684 Rate\uff0c\u5982\u56fe 16.5 \u6240\u793a\u3002<\/p>\n<\/blockquote>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/1605.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 16.5 The select list in the currency converter application sends a list of selected currencies to the application. Model binding binds the selected currencies and customizes the view for the user to show the equivalent cost in the selected currencies.<br \/>\n\u56fe 16.5 \u8d27\u5e01\u8f6c\u6362\u5668\u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u9009\u62e9\u5217\u8868\u5c06\u6240\u9009\u8d27\u5e01\u7684\u5217\u8868\u53d1\u9001\u5230\u5e94\u7528\u7a0b\u5e8f\u3002\u6a21\u578b\u7ed1\u5b9a\u7ed1\u5b9a\u6240\u9009\u8d27\u5e01\u5e76\u81ea\u5b9a\u4e49\u89c6\u56fe\uff0c\u4ee5\u4fbf\u7528\u6237\u663e\u793a\u6240\u9009\u8d27\u5e01\u7684\u7b49\u6548\u6210\u672c\u3002<\/p>\n<p>To achieve this, you could create a page handler that accepts a List<string> type, such as<br \/>\n\u4e3a\u6b64\uff0c\u60a8\u53ef\u4ee5\u521b\u5efa\u4e00\u4e2a\u63a5\u53d7 List \u7c7b\u578b\u7684\u9875\u9762\u5904\u7406\u7a0b\u5e8f\uff0c\u4f8b\u5982<\/p>\n<pre><code>public void OnPost(List&lt;string&gt; currencies);<\/code><\/pre>\n<p>You could then POST data to this method by providing values in several different formats:<br \/>\n\u7136\u540e\uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7\u63d0\u4f9b\u51e0\u79cd\u4e0d\u540c\u683c\u5f0f\u7684\u503c\u6765\u5c06\u6570\u636e POST \u5230\u6b64\u65b9\u6cd5\uff1a<\/p>\n<p>\u2022  currencies[index]\u2014Where currencies is the name of the parameter to bind and index is the index of the item to bind, such as currencies[0]= GBP&amp;currencies[1]=USD.<br \/>\ncurrencies[index] - \u5176\u4e2d currencies \u662f\u8981\u7ed1\u5b9a\u7684\u53c2\u6570\u7684\u540d\u79f0\uff0cindex \u662f\u8981\u7ed1\u5b9a\u7684\u9879\u76ee\u7684\u7d22\u5f15\uff0c\u4f8b\u5982 currencies[0]= GBP&amp;currencies[1]=USD\u3002<\/p>\n<p>\u2022  [index]\u2014If you\u2019re binding to a single list (as in this example), you can omit the name of the parameter, such as [0]=GBP&amp;[1]=USD.<br \/>\n[index] - \u5982\u679c\u8981\u7ed1\u5b9a\u5230\u5355\u4e2a\u5217\u8868 \uff08\u5982\u672c\u4f8b\u6240\u793a\uff09\uff0c\u5219\u53ef\u4ee5\u7701\u7565\u53c2\u6570\u7684\u540d\u79f0\uff0c\u4f8b\u5982 [0]=GBP&amp;[1]=USD\u3002<\/p>\n<p>\u2022  currencies\u2014Alternatively, you can omit the index and send currencies as the key for every value, such as currencies=GBP&amp;currencies=USD.<br \/>\ncurrencies - \u6216\u8005\uff0c\u60a8\u53ef\u4ee5\u7701\u7565\u7d22\u5f15\u5e76\u5c06 currencies \u4f5c\u4e3a\u6bcf\u4e2a\u503c\u7684\u952e\u53d1\u9001\uff0c\u4f8b\u5982 currencies=GBP&amp;currencies=USD\u3002<\/p>\n<p>The key values can come from route values and query values, but it\u2019s far more common to POST them in a form. Dictionaries can use similar binding, where the dictionary key replaces the index both when the parameter is named and when it\u2019s omitted.<br \/>\n\u952e\u503c\u53ef\u4ee5\u6765\u81ea\u8def\u7531\u503c\u548c\u67e5\u8be2\u503c\uff0c\u4f46\u5728\u8868\u5355\u4e2d POST \u5b83\u4eec\u66f4\u4e3a\u5e38\u89c1\u3002\u5b57\u5178\u53ef\u4ee5\u4f7f\u7528\u7c7b\u4f3c\u7684\u7ed1\u5b9a\uff0c\u5176\u4e2d\u5b57\u5178\u952e\u5728\u547d\u540d\u53c2\u6570\u548c\u7701\u7565\u53c2\u6570\u65f6\u66ff\u6362\u7d22\u5f15\u3002<\/p>\n<p><b>Tip<\/b> In the previous example I showed a collection using the built-in string type, but you can also bind collections of complex type, such as a <code>List&lt;UserBindingModel&gt;<\/code>.<br \/>\n\u63d0\u793a:\u5728\u524d\u9762\u7684\u793a\u4f8b\u4e2d\uff0c\u6211\u5c55\u793a\u4e86\u4e00\u4e2a\u4f7f\u7528\u5185\u7f6e\u5b57\u7b26\u4e32\u7c7b\u578b\u7684\u96c6\u5408\uff0c\u4f46\u60a8\u4e5f\u53ef\u4ee5\u7ed1\u5b9a\u590d\u6742\u7c7b\u578b\u7684\u96c6\u5408\uff0c\u4f8b\u5982 <code>List&lt;UserBindingModel&gt;<\/code>\u3002<\/p>\n<p>If this all seems a bit confusing, don\u2019t feel too alarmed. If you\u2019re building a traditional web application and using Razor views to generate HTML, the framework will take care of generating the correct names for you. As you\u2019ll see in chapter 18, the Razor view ensures that any form data you POST is generated in the correct format.<br \/>\n\u5982\u679c\u8fd9\u4e00\u5207\u770b\u8d77\u6765\u6709\u70b9\u4ee4\u4eba\u56f0\u60d1\uff0c\u8bf7\u4e0d\u8981\u592a\u60ca\u614c\u3002\u5982\u679c\u60a8\u6b63\u5728\u6784\u5efa\u4f20\u7edf\u7684 Web \u5e94\u7528\u7a0b\u5e8f\u5e76\u4f7f\u7528 Razor \u89c6\u56fe\u751f\u6210 HTML\uff0c\u6846\u67b6\u5c06\u8d1f\u8d23\u4e3a\u60a8\u751f\u6210\u6b63\u786e\u7684\u540d\u79f0\u3002\u6b63\u5982\u60a8\u5c06\u5728\u7b2c 18 \u7ae0\u4e2d\u770b\u5230\u7684\u90a3\u6837\uff0cRazor \u89c6\u56fe\u53ef\u786e\u4fdd\u60a8 POST \u7684\u4efb\u4f55\u8868\u5355\u6570\u636e\u90fd\u4ee5\u6b63\u786e\u7684\u683c\u5f0f\u751f\u6210\u3002<\/p>\n<blockquote>\n<p>Binding file uploads with IFormFile<br \/>\n\u5c06\u6587\u4ef6\u4e0a\u4f20\u4e0e IFormFile\u7ed1\u5b9a<br \/>\nRazor Pages supports users uploading files by exposing the IFormFile and IFormFileCollection interfaces. You can use these interfaces as your binding model, either as a method parameter to your page handler or using the [BindProperty] approach, and they will be populated with the details of the file upload:<br \/>\nRazor Pages \u652f\u6301\u7528\u6237\u901a\u8fc7\u516c\u5f00 IFormFile \u548c IFormFileCollection \u63a5\u53e3\u6765\u4e0a\u4f20\u6587\u4ef6\u3002\u60a8\u53ef\u4ee5\u5c06\u8fd9\u4e9b\u63a5\u53e3\u7528\u4f5c\u7ed1\u5b9a\u6a21\u578b\uff0c\u4f5c\u4e3a\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u7684\u65b9\u6cd5\u53c2\u6570\u6216\u4f7f\u7528 [BindProperty] \u65b9\u6cd5\uff0c\u5b83\u4eec\u5c06\u586b\u5145\u6587\u4ef6\u4e0a\u4f20\u7684\u8be6\u7ec6\u4fe1\u606f\uff1a<\/p>\n<pre><code>public void OnPost(IFormFile file);<\/code><\/pre>\n<\/blockquote>\n<p>If you need to accept multiple files, you can use IFormFileCollection, <code>IEnumerable&lt;IFormFile&gt;<\/code>, or <code>List&lt;IFormFile&gt;<\/code>:<br \/>\n\u5982\u679c\u9700\u8981\u63a5\u53d7\u591a\u4e2a\u6587\u4ef6\uff0c\u53ef\u4ee5\u4f7f\u7528 IFormFileCollection\u3001<code>IEnumerable&lt;IFormFile&gt;<\/code> \u6216<code>List&lt;IFormFile&gt;<\/code>\uff1a<\/p>\n<pre><code>public void OnPost(IEnumerable&lt;IFormFile&gt; file);<\/code><\/pre>\n<p>You already learned how to use IFormFile in chapter 7 when you looked at minimal API binding. The process is the same for Razor Pages. I\u2019ll reiterate one point here: if you don\u2019t need users to upload files, great! There are so many potential threats to consider when handling files\u2014from malicious attacks, to accidental denial-of-service vulnerabilities\u2014that I avoid them whenever possible.<br \/>\n\u5728\u7b2c 7 \u7ae0\u4e2d\uff0c\u60a8\u5df2\u7ecf\u5b66\u4e60\u4e86\u5982\u4f55\u4f7f\u7528 IFormFile\uff0c\u5f53\u65f6\u60a8\u4e86\u89e3\u4e86\u6700\u5c0f API \u7ed1\u5b9a\u3002Razor Pages \u7684\u8fc7\u7a0b\u76f8\u540c\u3002\u6211\u5728\u8fd9\u91cc\u91cd\u7533\u4e00\u70b9\uff1a\u5982\u679c\u60a8\u4e0d\u9700\u8981\u7528\u6237\u4e0a\u4f20\u6587\u4ef6\uff0c\u90a3\u5c31\u592a\u597d\u4e86\uff01\u5728\u5904\u7406\u6587\u4ef6\u65f6\uff0c\u9700\u8981\u8003\u8651\u8bb8\u591a\u6f5c\u5728\u5a01\u80c1 \u2014 \u4ece\u6076\u610f\u653b\u51fb\u5230\u610f\u5916\u7684\u62d2\u7edd\u670d\u52a1\u6f0f\u6d1e \u2014 \u56e0\u6b64\u6211\u5c3d\u53ef\u80fd\u907f\u514d\u5b83\u4eec\u3002<\/p>\n<p>For the vast majority of Razor Pages, the default configuration of model binding for simple and complex types works perfectly well, but you may find some situations where you need to take a bit more control. Luckily, that\u2019s perfectly possible, and you can completely override the process if necessary by replacing the ModelBinders used in the guts of the framework.<br \/>\n\u5bf9\u4e8e\u7edd\u5927\u591a\u6570 Razor Pages\uff0c\u7b80\u5355\u7c7b\u578b\u548c\u590d\u6742\u7c7b\u578b\u7684\u6a21\u578b\u7ed1\u5b9a\u7684\u9ed8\u8ba4\u914d\u7f6e\u8fd0\u884c\u826f\u597d\uff0c\u4f46\u4f60\u53ef\u80fd\u4f1a\u53d1\u73b0\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\u9700\u8981\u8fdb\u884c\u66f4\u591a\u63a7\u5236\u3002\u5e78\u8fd0\u7684\u662f\uff0c\u8fd9\u662f\u5b8c\u5168\u53ef\u80fd\u7684\uff0c\u5982\u6709\u5fc5\u8981\uff0c\u60a8\u53ef\u4ee5\u901a\u8fc7\u66ff\u6362\u6846\u67b6\u5185\u90e8\u4e2d\u4f7f\u7528\u7684 ModelBinders \u6765\u5b8c\u5168\u8986\u76d6\u8be5\u8fc7\u7a0b\u3002<\/p>\n<p>However, it\u2019s rare to need that level of customization. I\u2019ve found it\u2019s more common to want to specify which binding source to use for a page\u2019s binding instead.<br \/>\n\u4f46\u662f\uff0c\u5f88\u5c11\u9700\u8981\u8fd9\u79cd\u7ea7\u522b\u7684\u81ea\u5b9a\u4e49\u3002\u6211\u53d1\u73b0\uff0c\u66f4\u5e38\u89c1\u7684\u505a\u6cd5\u662f\u6307\u5b9a\u8981\u7528\u4e8e\u9875\u9762\u7ed1\u5b9a\u7684\u7ed1\u5b9a\u6e90\u3002<\/p>\n<h3>16.2.3 Choosing a binding source<\/h3>\n<p>16.2.3 \u9009\u62e9\u7ed1\u5b9a\u6e90<\/p>\n<p>As you\u2019ve already seen, by default the ASP.NET Core model binder attempts to bind your binding models from three binding sources: form data, route data, and the query string.<br \/>\n\u5982\u60a8\u6240\u89c1\uff0c\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cASP.NET Core \u6a21\u578b Binder \u4f1a\u5c1d\u8bd5\u4ece\u4e09\u4e2a\u7ed1\u5b9a\u6e90\u7ed1\u5b9a\u60a8\u7684\u7ed1\u5b9a\u6a21\u578b\uff1a\u8868\u5355\u6570\u636e\u3001\u8def\u7531\u6570\u636e\u548c\u67e5\u8be2\u5b57\u7b26\u4e32\u3002<\/p>\n<p>Occasionally, you may find it necessary to specifically declare which binding source to bind to. In other cases, these three sources won\u2019t be sufficient at all. The most common scenarios are when you want to bind a method parameter to a request header value or when the body of a request contains JSON-formatted data that you want to bind to a parameter. In these cases, you can decorate your binding models with attributes that say where to bind from, as shown in the following listing.<br \/>\n\u6709\u65f6\uff0c\u60a8\u53ef\u80fd\u4f1a\u53d1\u73b0\u6709\u5fc5\u8981\u4e13\u95e8\u58f0\u660e\u8981\u7ed1\u5b9a\u5230\u7684\u7ed1\u5b9a\u6e90\u3002\u5728\u5176\u4ed6\u60c5\u51b5\u4e0b\uff0c\u8fd9\u4e09\u4e2a\u6765\u6e90\u6839\u672c\u4e0d\u591f\u3002\u6700\u5e38\u89c1\u7684\u60c5\u51b5\u662f\uff0c\u5f53\u60a8\u8981\u5c06\u65b9\u6cd5\u53c2\u6570\u7ed1\u5b9a\u5230\u8bf7\u6c42\u6807\u5934\u503c\u65f6\uff0c\u6216\u8005\u5f53\u8bf7\u6c42\u6b63\u6587\u5305\u542b\u8981\u7ed1\u5b9a\u5230\u53c2\u6570\u7684 JSON \u683c\u5f0f\u6570\u636e\u65f6\u3002\u5728\u8fd9\u4e9b\u60c5\u51b5\u4e0b\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528\u8bf4\u660e\u7ed1\u5b9a\u4f4d\u7f6e\u7684\u5c5e\u6027\u6765\u88c5\u9970\u60a8\u7684\u7ed1\u5b9a\u6a21\u578b\uff0c\u5982\u4e0b\u9762\u7684\u6e05\u5355\u6240\u793a\u3002<\/p>\n<p>Listing 16.5 Choosing a binding source for model binding<br \/>\n\u6e05\u5355 16.5 \u4e3a\u6a21\u578b\u7ed1\u5b9a\u9009\u62e9\u7ed1\u5b9a\u6e90<\/p>\n<pre><code>public class PhotosModel: PageModel\n{\n    public void OnPost(\n        [FromHeader] string userId,     \u2776\n        [FromBody] List&lt;Photo&gt; photos)      \u2777\n    {\n        \/* method implementation *\/\n    }\n}<\/code><\/pre>\n<p>\u2776 The userId is bound from an HTTP header in the request.<br \/>\nuserId \u4ece\u8bf7\u6c42\u4e2d\u7684 HTTP \u6807\u5934\u7ed1\u5b9a\u3002<br \/>\n\u2777 The list of photo objects is bound to the body of the request, typically in JSON format.<br \/>\n\u7167\u7247\u5bf9\u8c61\u5217\u8868\u7ed1\u5b9a\u5230\u8bf7\u6c42\u7684\u6b63\u6587\uff0c\u901a\u5e38\u91c7\u7528 JSON \u683c\u5f0f\u3002<\/p>\n<p>In this example, a page handler updates a collection of photos with a user ID. There are method parameters for the ID of the user to be tagged in the photos, userId, and a list of Photo objects to tag, photos.<br \/>\n\u5728\u6b64\u793a\u4f8b\u4e2d\uff0c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u4f7f\u7528\u7528\u6237 ID \u66f4\u65b0\u7167\u7247\u96c6\u5408\u3002\u6709\u65b9\u6cd5\u53c2\u6570\uff0c\u7528\u4e8e\u5728 photos \u4e2d\u6807\u8bb0\u7684\u7528\u6237 ID\u3001userId \u548c\u8981\u6807\u8bb0\u7684 Photo \u5bf9\u8c61\u5217\u8868 photos\u3002<\/p>\n<p>Rather than binding these method parameters using the standard binding sources, I\u2019ve added attributes to each parameter, indicating the binding source to use. The [FromHeader] attribute has been applied to the userId parameter. This tells the model binder to bind the value to an HTTP request header value called userId.<br \/>\n\u6211\u6ca1\u6709\u4f7f\u7528\u6807\u51c6\u7ed1\u5b9a\u6e90\u6765\u7ed1\u5b9a\u8fd9\u4e9b\u65b9\u6cd5\u53c2\u6570\uff0c\u800c\u662f\u5411\u6bcf\u4e2a\u53c2\u6570\u6dfb\u52a0\u4e86\u5c5e\u6027\uff0c\u4ee5\u6307\u793a\u8981\u4f7f\u7528\u7684\u7ed1\u5b9a\u6e90\u3002[FromHeader] \u5c5e\u6027\u5df2\u5e94\u7528\u4e8e userId \u53c2\u6570\u3002\u8fd9\u4f1a\u544a\u77e5\u6a21\u578b\u7ed1\u5b9a\u5668\u5c06\u503c\u7ed1\u5b9a\u5230\u540d\u4e3a userId \u7684 HTTP \u8bf7\u6c42\u6807\u5934\u503c\u3002<\/p>\n<p>We\u2019re also binding a list of photos to the body of the HTTP request by using the [FromBody] attribute. This tells the binder to read JSON from the body of the request and bind it to the <code>List&lt;Photo&gt;<\/code> method parameter.<br \/>\n\u6211\u4eec\u8fd8\u4f7f\u7528 [FromBody] \u5c5e\u6027\u5c06\u7167\u7247\u5217\u8868\u7ed1\u5b9a\u5230 HTTP \u8bf7\u6c42\u7684\u6b63\u6587\u3002\u8fd9\u4f1a\u544a\u77e5 Binder \u4ece\u8bf7\u6c42\u6b63\u6587\u4e2d\u8bfb\u53d6 JSON\uff0c\u5e76\u5c06\u5176\u7ed1\u5b9a\u5230 <code>List&lt;Photo&gt;<\/code> \u65b9\u6cd5\u53c2\u6570\u3002<\/p>\n<p><b>Warning<\/b> Developers coming from .NET Framework and the legacy version of ASP.NET should take note that the [FromBody] attribute is explicitly required when binding to JSON requests in Razor Pages. This differs from the legacy ASP.NET behavior, in which no attribute was required.<br \/>\n\u8b66\u544a:\u6765\u81ea .NET Framework \u548c\u65e7\u7248 ASP.NET \u7684\u5f00\u53d1\u4eba\u5458\u5e94\u6ce8\u610f\uff0c\u5728 Razor Pages \u4e2d\u7ed1\u5b9a\u5230 JSON \u8bf7\u6c42\u65f6\uff0c\u663e\u5f0f\u9700\u8981 [FromBody] \u5c5e\u6027\u3002\u8fd9\u4e0e legacy ASP.NET \u884c\u4e3a\u4e0d\u540c\uff0c\u540e\u8005\u4e0d\u9700\u8981\u4efb\u4f55\u5c5e\u6027\u3002<\/p>\n<p>You aren\u2019t limited to binding JSON data from the request body. You can use other formats too, depending on which InputFormatters you configure the framework to use. By default, only a JSON input formatter is configured. You\u2019ll see how to add an XML formatter in chapter 20, when I discuss web APIs.<br \/>\n\u60a8\u4e0d\u4ec5\u9650\u4e8e\u4ece\u8bf7\u6c42\u6b63\u6587\u7ed1\u5b9a JSON \u6570\u636e\u3002\u60a8\u4e5f\u53ef\u4ee5\u4f7f\u7528\u5176\u4ed6\u683c\u5f0f\uff0c\u5177\u4f53\u53d6\u51b3\u4e8e\u60a8\u914d\u7f6e\u6846\u67b6\u8981\u4f7f\u7528\u7684 InputFormatters\u3002\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u4ec5\u914d\u7f6e JSON \u8f93\u5165\u683c\u5f0f\u5316\u7a0b\u5e8f\u3002\u5728\u7b2c 20 \u7ae0\u4e2d\uff0c\u6211\u5c06\u4ecb\u7ecd\u5982\u4f55\u6dfb\u52a0 XML \u683c\u5f0f\u5316\u7a0b\u5e8f\uff0c\u5c4a\u65f6\u6211\u5c06\u8ba8\u8bba Web API\u3002<\/p>\n<p><b>Tip<\/b> Automatic binding of multiple formats from the request body is one of the features specific to Razor Pages and MVC controllers, which is missing from minimal APIs.<br \/>\n\u63d0\u793a:\u4ece\u8bf7\u6c42\u6b63\u6587\u81ea\u52a8\u7ed1\u5b9a\u591a\u79cd\u683c\u5f0f\u662f\u7279\u5b9a\u4e8e Razor Pages \u548c MVC \u63a7\u5236\u5668\u7684\u529f\u80fd\u4e4b\u4e00\uff0c\u800c\u6700\u5c0f API \u4e2d\u7f3a\u5c11\u6b64\u529f\u80fd\u3002<\/p>\n<p>You can use a few different attributes to override the defaults and to specify a binding source for each binding model (or each property on the binding model). These are the same attributes you used in chapter 7 with minimal APIs:<br \/>\n\u53ef\u4ee5\u4f7f\u7528\u51e0\u4e2a\u4e0d\u540c\u7684\u5c5e\u6027\u6765\u8986\u76d6\u9ed8\u8ba4\u503c\uff0c\u5e76\u4e3a\u6bcf\u4e2a\u7ed1\u5b9a\u6a21\u578b\uff08\u6216\u7ed1\u5b9a\u6a21\u578b\u4e0a\u7684\u6bcf\u4e2a\u5c5e\u6027\uff09\u6307\u5b9a\u7ed1\u5b9a\u6e90\u3002\u8fd9\u4e9b\u662f\u60a8\u5728\u7b2c 7 \u7ae0\u4e2d\u4f7f\u7528\u7684\u76f8\u540c\u5c5e\u6027\uff0c\u5177\u6709\u6700\u5c11\u7684 API\uff1a<\/p>\n<p>\u2022  [FromHeader]\u2014Bind to a header value.<br \/>\n\u2022  [FromQuery]\u2014Bind to a query string value.<br \/>\n\u2022  [FromRoute]\u2014Bind to route parameters.<br \/>\n\u2022  [FromForm]\u2014Bind to form data posted in the body of the request. This attribute is not available in minimal APIs.<br \/>\n\u2022  [FromBody]\u2014Bind to the request\u2019s body content.<\/p>\n<p>You can apply each of these to any number of handler method parameters or properties, as you saw in listing 16.5, with the exception of the [FromBody] attribute. Only one value may be decorated with the [FromBody] attribute. Also, as form data is sent in the body of a request, the [FromBody] and [FromForm] attributes are effectively mutually exclusive.<br \/>\n\u60a8\u53ef\u4ee5\u5c06\u8fd9\u4e9b\u53c2\u6570\u4e2d\u7684\u6bcf\u4e00\u4e2a\u5e94\u7528\u4e8e\u4efb\u610f\u6570\u91cf\u7684\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u53c2\u6570\u6216\u5c5e\u6027\uff0c\u5982\u6e05\u5355 16.5 \u4e2d\u6240\u793a\uff0c\u4f46 [FromBody] \u5c5e\u6027\u9664\u5916\u3002\u53ea\u80fd\u7528 [FromBody] \u5c5e\u6027\u4fee\u9970\u4e00\u4e2a\u503c\u3002\u6b64\u5916\uff0c\u7531\u4e8e\u8868\u5355\u6570\u636e\u662f\u5728\u8bf7\u6c42\u6b63\u6587\u4e2d\u53d1\u9001\u7684\uff0c\u56e0\u6b64 [FromBody] \u548c [FromForm] \u5c5e\u6027\u5b9e\u9645\u4e0a\u662f\u4e92\u65a5\u7684\u3002<\/p>\n<p><b>Tip<\/b> Only one parameter may use the [FromBody] attribute. This attribute consumes the incoming request as HTTP request bodies can be safely read only once.<br \/>\n\u63d0\u793a:\u53ea\u6709\u4e00\u4e2a\u53c2\u6570\u53ef\u4ee5\u4f7f\u7528 [FromBody] \u5c5e\u6027\u3002\u6b64\u5c5e\u6027\u4f7f\u7528\u4f20\u5165\u8bf7\u6c42\uff0c\u56e0\u4e3a HTTP \u8bf7\u6c42\u6b63\u6587\u53ea\u80fd\u5b89\u5168\u5730\u8bfb\u53d6\u4e00\u6b21\u3002<\/p>\n<p>As well as these attributes for specifying binding sources, there are a few attributes for customizing the binding process even further:<br \/>\n\u9664\u4e86\u8fd9\u4e9b\u7528\u4e8e\u6307\u5b9a\u7ed1\u5b9a\u6e90\u7684\u5c5e\u6027\u5916\uff0c\u8fd8\u6709\u4e00\u4e9b\u5c5e\u6027\u53ef\u7528\u4e8e\u8fdb\u4e00\u6b65\u81ea\u5b9a\u4e49\u7ed1\u5b9a\u8fc7\u7a0b\uff1a<\/p>\n<p>\u2022  [BindNever]\u2014The model binder will skip this parameter completely. You can use this attribute to prevent mass assignment, as discussed in these two posts on my blog: <a href=\"http:\/\/mng.bz\/QvfG\">http:\/\/mng.bz\/QvfG<\/a> and <a href=\"http:\/\/mng.bz\/Vd90\">http:\/\/mng.bz\/Vd90<\/a>.<br \/>\n[BindNever] - \u6a21\u578b\u7ed1\u5b9a\u5668\u5c06\u5b8c\u5168\u8df3\u8fc7\u6b64\u53c2\u6570\u3002\u60a8\u53ef\u4ee5\u4f7f\u7528\u6b64\u5c5e\u6027\u6765\u9632\u6b62\u6279\u91cf\u5206\u914d\uff0c\u5982\u6211\u535a\u5ba2\u4e0a\u7684\u4ee5\u4e0b\u4e24\u7bc7\u6587\u7ae0\u6240\u8ff0\uff1a<a href=\"http:\/\/mng.bz\/QvfG\">http:\/\/mng.bz\/QvfG<\/a> \u548c <a href=\"http:\/\/mng.bz\/Vd90\">http:\/\/mng.bz\/Vd90<\/a>\u3002<br \/>\n\u2022  [BindRequired]\u2014If the parameter was not provided or was empty, the binder will add a validation error.<br \/>\n[BindRequired] - \u5982\u679c\u53c2\u6570\u672a\u63d0\u4f9b\u6216\u4e3a\u7a7a\uff0c\u5219 Binder \u5c06\u6dfb\u52a0\u9a8c\u8bc1\u9519\u8bef\u3002<br \/>\n\u2022  [FromServices]\u2014This is used to indicate the parameter should be provided using dependency injection (DI). This attribute isn\u2019t required in most cases, as .NET 7 is smart enough to know that a parameter is a service registered in DI, but you can be explicit if you prefer.<br \/>\n[FromServices] - \u8fd9\u7528\u4e8e\u6307\u793a\u5e94\u4f7f\u7528\u4f9d\u8d56\u5173\u7cfb\u6ce8\u5165 \uff08DI\uff09 \u63d0\u4f9b\u53c2\u6570\u3002\u5728\u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u6b64\u5c5e\u6027\u4e0d\u662f\u5fc5\u9700\u7684\uff0c\u56e0\u4e3a .NET 7 \u8db3\u591f\u667a\u80fd\uff0c\u53ef\u4ee5\u77e5\u9053\u53c2\u6570\u662f\u5728 DI \u4e2d\u6ce8\u518c\u7684\u670d\u52a1\uff0c\u4f46\u5982\u679c\u4f60\u613f\u610f\uff0c\u53ef\u4ee5\u660e\u786e\u8868\u793a\u3002<\/p>\n<p>In addition, you have the [ModelBinder] attribute, which puts you into \u201cGod mode\u201d with respect to model binding. With this attribute, you can specify the exact binding source, override the name of the parameter to bind to, and specify the type of binding to perform. It\u2019ll be rare that you need this one, but when you do, at least it\u2019s there!<br \/>\n\u6b64\u5916\uff0c\u60a8\u8fd8\u6709 [ModelBinder] \u5c5e\u6027\uff0c\u8be5\u5c5e\u6027\u5c06\u60a8\u7f6e\u4e8e\u6a21\u578b\u7ed1\u5b9a\u7684\u201c\u4e0a\u5e1d\u6a21\u5f0f\u201d\u3002\u4f7f\u7528\u6b64\u5c5e\u6027\uff0c\u60a8\u53ef\u4ee5\u6307\u5b9a\u786e\u5207\u7684\u7ed1\u5b9a\u6e90\uff0c\u8986\u76d6\u8981\u7ed1\u5b9a\u5230\u7684\u53c2\u6570\u7684\u540d\u79f0\uff0c\u5e76\u6307\u5b9a\u8981\u6267\u884c\u7684\u7ed1\u5b9a\u7c7b\u578b\u3002\u4f60\u5f88\u5c11\u9700\u8981\u8fd9\u4e2a\uff0c\u4f46\u5f53\u4f60\u9700\u8981\u65f6\uff0c\u81f3\u5c11\u5b83\u5c31\u5728\u90a3\u91cc\uff01<\/p>\n<p>By combining all these attributes, you should find you\u2019re able to configure the model binder to bind to pretty much any request data your page handler wants to use. In general, though, you\u2019ll probably find you rarely need to use them; the defaults should work well for you in most cases.<br \/>\n\u901a\u8fc7\u7ec4\u5408\u6240\u6709\u8fd9\u4e9b\u5c5e\u6027\uff0c\u60a8\u5e94\u8be5\u80fd\u591f\u914d\u7f6e\u6a21\u578b Binders \u4ee5\u7ed1\u5b9a\u5230\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u60f3\u8981\u4f7f\u7528\u7684\u51e0\u4e4e\u6240\u6709\u8bf7\u6c42\u6570\u636e\u3002\u4e0d\u8fc7\uff0c\u4e00\u822c\u6765\u8bf4\uff0c\u60a8\u53ef\u80fd\u4f1a\u53d1\u73b0\u60a8\u5f88\u5c11\u9700\u8981\u4f7f\u7528\u5b83\u4eec;\u5728\u5927\u591a\u6570\u60c5\u51b5\u4e0b\uff0c\u9ed8\u8ba4\u503c\u5e94\u8be5\u5bf9\u60a8\u6765\u8bf4\u6548\u679c\u5f88\u597d\u3002<\/p>\n<p>That brings us to the end of this section on model binding. At the end of the model binding process, your page handler should have access to a populated binding model, and it\u2019s ready to execute its logic. But before you use that user input for anything, you must always validate your data, which is the focus of the second half of this chapter. Razor Pages automatically does validation for you out-of-the-box, but you have to actually check the results.<br \/>\n\u8fd9\u8ba9\u6211\u4eec\u7ed3\u675f\u4e86\u672c\u8282\u5173\u4e8e\u6a21\u578b\u7ed1\u5b9a\u7684\u5185\u5bb9\u3002\u5728\u6a21\u578b\u7ed1\u5b9a\u8fc7\u7a0b\u7ed3\u675f\u65f6\uff0c\u60a8\u7684\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u5e94\u8be5\u53ef\u4ee5\u8bbf\u95ee\u586b\u5145\u7684\u7ed1\u5b9a\u6a21\u578b\uff0c\u5e76\u4e14\u5b83\u5df2\u51c6\u5907\u597d\u6267\u884c\u5176\u903b\u8f91\u3002\u4f46\u662f\uff0c\u5728\u5c06\u8be5\u7528\u6237\u8f93\u5165\u7528\u4e8e\u4efb\u4f55\u4f5c\u4e4b\u524d\uff0c\u5fc5\u987b\u59cb\u7ec8\u9a8c\u8bc1\u6570\u636e\uff0c\u8fd9\u662f\u672c\u7ae0\u540e\u534a\u90e8\u5206\u7684\u91cd\u70b9\u3002Razor Pages \u4f1a\u81ea\u52a8\u4e3a\u4f60\u6267\u884c\u5f00\u7bb1\u5373\u7528\u7684\u9a8c\u8bc1\uff0c\u4f46\u4f60\u5fc5\u987b\u5b9e\u9645\u68c0\u67e5\u7ed3\u679c\u3002<\/p>\n<h2>16.3 Validating binding models<\/h2>\n<p>16.3 \u9a8c\u8bc1\u7ed1\u5b9a\u6a21\u578b<\/p>\n<p>In this section I discuss how validation works in Razor Pages. You already learned how important it is to validate user input in chapter 7, as well as how you can use DataAnnotation attributes to declaratively describe your validation requirements of a model. In this section you\u2019ll learn how to reuse this knowledge to validate your Razor Page binding models. The good news is that validation is built into the Razor Pages framework.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u5c06\u8ba8\u8bba\u9a8c\u8bc1\u5728 Razor Pages \u4e2d\u7684\u5de5\u4f5c\u539f\u7406\u3002\u60a8\u5df2\u7ecf\u5728\u7b2c 7 \u7ae0\u4e2d\u4e86\u89e3\u4e86\u9a8c\u8bc1\u7528\u6237\u8f93\u5165\u7684\u91cd\u8981\u6027\uff0c\u4ee5\u53ca\u5982\u4f55\u4f7f\u7528 DataAnnotation \u5c5e\u6027\u4ee5\u58f0\u660e\u65b9\u5f0f\u63cf\u8ff0\u6a21\u578b\u7684\u9a8c\u8bc1\u8981\u6c42\u3002\u5728\u672c\u90e8\u5206\u4e2d\uff0c\u4f60\u5c06\u4e86\u89e3\u5982\u4f55\u91cd\u590d\u4f7f\u7528\u6b64\u77e5\u8bc6\u6765\u9a8c\u8bc1 Razor \u9875\u9762\u7ed1\u5b9a\u6a21\u578b\u3002\u597d\u6d88\u606f\u662f\uff0c\u9a8c\u8bc1\u5185\u7f6e\u4e8e Razor Pages \u6846\u67b6\u4e2d\u3002<\/p>\n<h3>16.3.1 Validation in Razor Pages<\/h3>\n<p>16.3.1 Razor Pages \u4e2d\u7684\u9a8c\u8bc1<\/p>\n<p>In chapter 7 you learned that validation is an essential part of any web application. Nevertheless, minimal APIs don\u2019t have any direct support for validation in the framework; you have to layer it on top using filters and additional packages.<br \/>\n\u5728\u7b2c 7 \u7ae0\u4e2d\uff0c\u60a8\u4e86\u89e3\u5230\u9a8c\u8bc1\u662f\u4efb\u4f55 Web \u5e94\u7528\u7a0b\u5e8f\u7684\u91cd\u8981\u7ec4\u6210\u90e8\u5206\u3002\u5c3d\u7ba1\u5982\u6b64\uff0c\u6700\u5c0f\u7684 API \u5728\u6846\u67b6\u4e2d\u6ca1\u6709\u4efb\u4f55\u5bf9\u9a8c\u8bc1\u7684\u76f4\u63a5\u652f\u6301;\u60a8\u5fc5\u987b\u4f7f\u7528\u8fc7\u6ee4\u5668\u548c\u9644\u52a0\u5305\u5c06\u5176\u5206\u5c42\u3002<\/p>\n<p>In Razor Pages, validation is built in. Validation occurs automatically after model binding but before the page handler executes, as you saw in figure 16.2. Figure 16.6 shows a more compact view of where model validation fits in this process, demonstrating how a request to a checkout page that requests a user\u2019s personal details is bound and validated.<br \/>\n\u5728 Razor Pages \u4e2d\uff0c\u9a8c\u8bc1\u662f\u5185\u7f6e\u7684\u3002\u9a8c\u8bc1\u5728\u6a21\u578b\u7ed1\u5b9a\u4e4b\u540e\u4f46\u5728\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u6267\u884c\u4e4b\u524d\u81ea\u52a8\u8fdb\u884c\uff0c\u5982\u56fe 16.2 \u6240\u793a\u3002\u56fe 16.6 \u663e\u793a\u4e86\u6a21\u578b\u9a8c\u8bc1\u5728\u6b64\u8fc7\u7a0b\u4e2d\u7684\u9002\u7528\u4f4d\u7f6e\u7684\u66f4\u7d27\u51d1\u89c6\u56fe\uff0c\u6f14\u793a\u4e86\u5982\u4f55\u7ed1\u5b9a\u548c\u9a8c\u8bc1\u5bf9\u8bf7\u6c42\u7528\u6237\u4e2a\u4eba\u8be6\u7ec6\u4fe1\u606f\u7684\u7ed3\u5e10\u9875\u9762\u7684\u8bf7\u6c42\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/1606.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 16.6 Validation occurs after model binding but before the page handler executes. The page handler executes whether or not validation is successful.<br \/>\n\u56fe 16.6 \u9a8c\u8bc1\u53d1\u751f\u5728\u6a21\u578b\u7ed1\u5b9a\u4e4b\u540e\uff0c\u4f46\u5728\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u6267\u884c\u4e4b\u524d\u3002\u65e0\u8bba\u9a8c\u8bc1\u662f\u5426\u6210\u529f\uff0c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u90fd\u4f1a\u6267\u884c\u3002<\/p>\n<p>As discussed in chapter 7, validation isn\u2019t only about protecting against security threats, it\u2019s also about ensuring that<br \/>\n\u5982\u7b2c 7 \u7ae0\u6240\u8ff0\uff0c\u9a8c\u8bc1\u4e0d\u4ec5\u8981\u9632\u6b62\u5b89\u5168\u5a01\u80c1\uff0c\u8fd8\u8981\u786e\u4fdd<br \/>\n\u2022  Data is formatted correctly. (Email fields have a valid email format.)<br \/>\n\u6570\u636e\u683c\u5f0f\u6b63\u786e\u3002\uff08\u7535\u5b50\u90ae\u4ef6\u5b57\u6bb5\u5177\u6709\u6709\u6548\u7684\u7535\u5b50\u90ae\u4ef6\u683c\u5f0f\u3002\uff09<br \/>\n\u2022  Numbers are in a particular range. (You can\u2019t buy -1 copies of a product.)<br \/>\n\u6570\u5b57\u5728\u7279\u5b9a\u8303\u56f4\u5185\u3002\uff08\u60a8\u4e0d\u80fd\u8d2d\u4e70 -1 \u4efd\u4ea7\u54c1\u3002\uff09<br \/>\n\u2022  Required values are provided while others are optional. (Name may be required, but phone number is optional.)<br \/>\n\u63d0\u4f9b\u5fc5\u9700\u503c\uff0c\u800c\u5176\u4ed6\u503c\u4e3a\u53ef\u9009\u503c\u3002\uff08\u59d3\u540d\u53ef\u80fd\u662f\u5fc5\u9700\u7684\uff0c\u4f46\u7535\u8bdd\u53f7\u7801\u662f\u53ef\u9009\u7684\u3002\uff09<br \/>\n\u2022  Values 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\u7b26\u5408\u60a8\u7684\u4e1a\u52a1\u9700\u6c42\u3002\uff08\u60a8\u65e0\u6cd5\u5c06\u8d27\u5e01\u8f6c\u6362\u4e3a\u81ea\u8eab\uff0c\u5b83\u9700\u8981\u8f6c\u6362\u4e3a\u5176\u4ed6\u8d27\u5e01\u3002\uff09<\/p>\n<p>It might seem like some of these can be dealt with easily enough in the browser. For example, if a user is selecting a currency to convert to, don\u2019t let them pick the same currency; and we\u2019ve all seen the \u201cplease enter a valid email address\u201d messages.<br \/>\n\u5176\u4e2d\u4e00\u4e9b\u4f3c\u4e4e\u53ef\u4ee5\u5728\u6d4f\u89c8\u5668\u4e2d\u8f7b\u677e\u5904\u7406\u3002\u4f8b\u5982\uff0c\u5982\u679c\u7528\u6237\u9009\u62e9\u8981\u8f6c\u6362\u4e3a\u7684\u8d27\u5e01\uff0c\u8bf7\u4e0d\u8981\u8ba9\u4ed6\u4eec\u9009\u62e9\u76f8\u540c\u7684\u8d27\u5e01;\u6211\u4eec\u90fd\u89c1\u8fc7 \u201cPlease enter a valid email address\u201d \u6d88\u606f\u3002<\/p>\n<p>Unfortunately, although this client-side validation is useful for users, as it gives them instant feedback, you can never rely on it, as it will always be possible to bypass these browser protections. It\u2019s always necessary to validate the data as it arrives at your web application using server-side validation.<br \/>\n\u4e0d\u5e78\u7684\u662f\uff0c\u5c3d\u7ba1\u8fd9\u79cd\u5ba2\u6237\u7aef\u9a8c\u8bc1\u5bf9\u7528\u6237\u5f88\u6709\u7528\uff0c\u56e0\u4e3a\u5b83\u4e3a\u4ed6\u4eec\u63d0\u4f9b\u4e86\u5373\u65f6\u53cd\u9988\uff0c\u4f46\u60a8\u6c38\u8fdc\u4e0d\u80fd\u4f9d\u8d56\u5b83\uff0c\u56e0\u4e3a\u603b\u662f\u53ef\u4ee5\u7ed5\u8fc7\u8fd9\u4e9b\u6d4f\u89c8\u5668\u4fdd\u62a4\u3002\u5f53\u6570\u636e\u5230\u8fbe Web \u5e94\u7528\u7a0b\u5e8f\u65f6\uff0c\u59cb\u7ec8\u6709\u5fc5\u8981\u4f7f\u7528\u670d\u52a1\u5668\u7aef\u9a8c\u8bc1\u6765\u9a8c\u8bc1\u6570\u636e\u3002<\/p>\n<p><b>Warning<\/b> Always validate user input on the server side of your application.<br \/>\n\u8b66\u544a\uff1a\u59cb\u7ec8\u5728\u5e94\u7528\u7a0b\u5e8f\u7684\u670d\u52a1\u5668\u7aef\u9a8c\u8bc1\u7528\u6237\u8f93\u5165\u3002<\/p>\n<p>If that feels a little redundant, like you\u2019ll be duplicating logic and code between your client and server applications, I\u2019m afraid you\u2019re right. It\u2019s one of the unfortunate aspects of web development; the duplication is a necessary evil. Fortunately, ASP.NET Core provides several features to try to reduce this burden.<br \/>\n\u5982\u679c\u8fd9\u611f\u89c9\u6709\u70b9\u591a\u4f59\uff0c\u6bd4\u5982\u60a8\u5c06\u5728\u5ba2\u6237\u7aef\u548c\u670d\u52a1\u5668\u5e94\u7528\u7a0b\u5e8f\u4e4b\u95f4\u590d\u5236\u903b\u8f91\u548c\u4ee3\u7801\uff0c\u90a3\u4e48\u6050\u6015\u60a8\u662f\u5bf9\u7684\u3002\u8fd9\u662f Web \u5f00\u53d1\u4e0d\u5e78\u7684\u65b9\u9762\u4e4b\u4e00;\u91cd\u590d\u662f\u4e00\u79cd\u5fc5\u8981\u7684\u90aa\u6076\u3002\u5e78\u8fd0\u7684\u662f\uff0cASP.NET Core \u63d0\u4f9b\u4e86\u591a\u9879\u529f\u80fd\u6765\u5c1d\u8bd5\u51cf\u8f7b\u8fd9\u79cd\u8d1f\u62c5\u3002<\/p>\n<p><b>Tip<\/b> Blazor, the new C# single-page application (SPA) framework, promises to solve some of these problems. For details, see <a href=\"http:\/\/mng.bz\/9D51\">http:\/\/mng.bz\/9D51<\/a> and Blazor in Action, by Chris Sainty (Manning, 2021).<br \/>\n\u63d0\u793a\uff1a\u65b0\u7684 C# \u5355\u9875\u5e94\u7528\u7a0b\u5e8f \uff08SPA\uff09 \u6846\u67b6 Blazor \u6709\u671b\u89e3\u51b3\u5176\u4e2d\u7684\u4e00\u4e9b\u95ee\u9898\u3002\u6709\u5173\u8be6\u7ec6\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605 Chris Sainty \u7684 <a href=\"http:\/\/mng.bz\/9D51\">http:\/\/mng.bz\/9D51<\/a> \u548c Blazor \u7684\u5b9e\u9645\u5e94\u7528\uff08Manning\uff0c2021 \u5e74\uff09\u3002<\/p>\n<p>If you had to write this validation code fresh for every app, it would be tedious and likely error-prone. Luckily, you can use DataAnnotations attributes to declaratively describe the validation requirements for your binding models. The following listing, first shown in chapter 7, shows how you can decorate a binding model with various validation attributes. This expands on the example you saw earlier in listing 16.4.<br \/>\n\u5982\u679c\u60a8\u5fc5\u987b\u4e3a\u6bcf\u4e2a\u5e94\u7528\u7a0b\u5e8f\u91cd\u65b0\u7f16\u5199\u6b64\u9a8c\u8bc1\u4ee3\u7801\uff0c\u8fd9\u5c06\u662f\u4e4f\u5473\u7684\uff0c\u5e76\u4e14\u53ef\u80fd\u5bb9\u6613\u51fa\u9519\u3002\u5e78\u8fd0\u7684\u662f\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528 DataAnnotations \u5c5e\u6027\u4ee5\u58f0\u660e\u65b9\u5f0f\u63cf\u8ff0\u7ed1\u5b9a\u6a21\u578b\u7684\u9a8c\u8bc1\u8981\u6c42\u3002\u4e0b\u9762\u7684\u6e05\u5355\u9996\u5148\u5728\u7b2c 7 \u7ae0\u4e2d\u663e\u793a\uff0c\u5b83\u663e\u793a\u4e86\u5982\u4f55\u4f7f\u7528\u5404\u79cd\u9a8c\u8bc1\u5c5e\u6027\u6765\u88c5\u9970\u7ed1\u5b9a\u6a21\u578b\u3002\u8fd9\u6269\u5c55\u4e86\u60a8\u4e4b\u524d\u5728 Listing 16.4 \u4e2d\u770b\u5230\u7684\u793a\u4f8b\u3002<\/p>\n<p>Listing 16.6 Adding DataAnnotations to a binding model to provide metadata<br \/>\n\u6e05\u5355 16.6 \u5411\u7ed1\u5b9a\u6a21\u578b\u6dfb\u52a0 DataAnnotations \u4ee5\u63d0\u4f9b\u5143\u6570\u636e<\/p>\n<pre><code>public class UserBindingModel\n{\n[Required] \u2776\n[StringLength(100)] \u2777\n[Display(Name = &quot;Your name&quot;)] \u2778\npublic string FirstName { get; set; }\n[Required]\n[StringLength(100)]\n[Display(Name = &quot;Last name&quot;)]\npublic string LastName { get; set; }\n[Required]\n[EmailAddress] \u2779\npublic string Email { get; set; }\n[Phone] \u277a\n[Display(Name = &quot;Phone number&quot;)]\npublic string PhoneNumber { get; set; }\n}<\/code><\/pre>\n<p>\u2776 Values marked Required must be provided.<br \/>\n\u5fc5\u987b\u63d0\u4f9b\u6807\u8bb0\u4e3a Required \u7684\u503c\u3002<br \/>\n\u2777 The StringLengthAttribute sets the maximum length for the property.<br \/>\nStringLengthAttribute \u8bbe\u7f6e\u5c5e\u6027\u7684\u6700\u5927\u957f\u5ea6\u3002<br \/>\n\u2778 Customizes the name used to describe the property<br \/>\n\u81ea\u5b9a\u4e49\u7528\u4e8e\u63cf\u8ff0\u5c5e\u6027\u7684\u540d\u79f0<br \/>\n\u2779 Validates that the value of Email is a valid email address<br \/>\n\u9a8c\u8bc1 Email \u7684\u503c\u662f\u5426\u4e3a\u6709\u6548\u7684\u7535\u5b50\u90ae\u4ef6\u5730\u5740<br \/>\n\u277a Validates that the value of PhoneNumber has a valid telephone format<br \/>\n\u9a8c\u8bc1 PhoneNumber \u7684\u503c\u662f\u5426\u5177\u6709\u6709\u6548\u7684\u7535\u8bdd\u683c\u5f0f<\/p>\n<p>For validation requirements that don\u2019t lend themselves to attributes, such as when the validity of one property depends on the value of another, you can implement IValidatableObject, as described in chapter 7. Alternatively, you can use a different validation framework, such as FluentValidation, as you\u2019ll see in chapter 32.<br \/>\n\u5bf9\u4e8e\u4e0d\u9002\u5408\u5c5e\u6027\u7684\u9a8c\u8bc1\u8981\u6c42\uff0c\u4f8b\u5982\u5f53\u4e00\u4e2a\u5c5e\u6027\u7684\u6709\u6548\u6027\u53d6\u51b3\u4e8e\u53e6\u4e00\u4e2a\u5c5e\u6027\u7684\u503c\u65f6\uff0c\u60a8\u53ef\u4ee5\u5b9e\u73b0 IValidatableObject\uff0c\u5982\u7b2c 7 \u7ae0\u6240\u8ff0\u3002\u6216\u8005\uff0c\u4f60\u53ef\u4ee5\u4f7f\u7528\u4e0d\u540c\u7684\u9a8c\u8bc1\u6846\u67b6\uff0c\u6bd4\u5982 FluentValidation\uff0c\u4f60\u5c06\u5728\u7b2c 32 \u7ae0\u4e2d\u770b\u5230\u3002<\/p>\n<p>Whichever validation approach you use, it\u2019s important to remember that these techniques don\u2019t protect your application by themselves. The Razor Pages framework automatically executes the validation code after model binding, but it doesn\u2019t do anything different if validation fails! In the next section we\u2019ll look at how to check the validation result on the server and handle the case where validation has failed.<br \/>\n\u65e0\u8bba\u60a8\u4f7f\u7528\u54ea\u79cd\u9a8c\u8bc1\u65b9\u6cd5\uff0c\u8bf7\u52a1\u5fc5\u8bb0\u4f4f\uff0c\u8fd9\u4e9b\u6280\u672f\u672c\u8eab\u5e76\u4e0d\u80fd\u4fdd\u62a4\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u3002Razor Pages \u6846\u67b6\u4f1a\u5728\u6a21\u578b\u7ed1\u5b9a\u540e\u81ea\u52a8\u6267\u884c\u9a8c\u8bc1\u4ee3\u7801\uff0c\u4f46\u5982\u679c\u9a8c\u8bc1\u5931\u8d25\uff0c\u5b83\u4e0d\u4f1a\u6267\u884c\u4efb\u4f55\u4e0d\u540c\u7684\u4f5c\uff01\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u4eec\u5c06\u4e86\u89e3\u5982\u4f55\u5728\u670d\u52a1\u5668\u4e0a\u68c0\u67e5\u9a8c\u8bc1\u7ed3\u679c\u5e76\u5904\u7406\u9a8c\u8bc1\u5931\u8d25\u7684\u60c5\u51b5\u3002<\/p>\n<h3>16.3.2 Validating on the server for safety<\/h3>\n<p>16.3.2 \u5728\u670d\u52a1\u5668\u4e0a\u9a8c\u8bc1\u5b89\u5168\u6027<\/p>\n<p>Validation of the binding model occurs before the page handler executes, but note that the handler always executes, whether the validation failed or succeeded. It\u2019s the responsibility of the page handler to check the result of the validation.<br \/>\n\u7ed1\u5b9a\u6a21\u578b\u7684\u9a8c\u8bc1\u53d1\u751f\u5728\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u6267\u884c\u4e4b\u524d\uff0c\u4f46\u8bf7\u6ce8\u610f\uff0c\u65e0\u8bba\u9a8c\u8bc1\u5931\u8d25\u8fd8\u662f\u6210\u529f\uff0c\u5904\u7406\u7a0b\u5e8f\u59cb\u7ec8\u6267\u884c\u3002\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u8d1f\u8d23\u68c0\u67e5\u9a8c\u8bc1\u7ed3\u679c\u3002<\/p>\n<p><b>NOTE<\/b> Validation happens automatically, but handling validation failures is the responsibility of the page handler.<br \/>\n\u6ce8\u610f\uff1a\u9a8c\u8bc1\u4f1a\u81ea\u52a8\u8fdb\u884c\uff0c\u4f46\u5904\u7406\u9a8c\u8bc1\u5931\u8d25\u662f\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u7684\u8d23\u4efb\u3002<\/p>\n<p>The Razor Pages framework stores the output of the validation attempt in a property on the PageModel called ModelState. This property is a ModelStateDictionary object, which contains a list of all the validation errors that occurred after model binding, as well as some utility properties for working with it.<br \/>\nRazor Pages \u6846\u67b6\u5c06\u9a8c\u8bc1\u5c1d\u8bd5\u7684\u8f93\u51fa\u5b58\u50a8\u5728 PageModel \u4e0a\u540d\u4e3a ModelState \u7684\u5c5e\u6027\u4e2d\u3002\u6b64\u5c5e\u6027\u662f\u4e00\u4e2a ModelStateDictionary \u5bf9\u8c61\uff0c\u5b83\u5305\u542b\u6a21\u578b\u7ed1\u5b9a\u540e\u53d1\u751f\u7684\u6240\u6709\u9a8c\u8bc1\u9519\u8bef\u7684\u5217\u8868\uff0c\u4ee5\u53ca\u7528\u4e8e\u5904\u7406\u5b83\u7684\u4e00\u4e9b\u5b9e\u7528\u7a0b\u5e8f\u5c5e\u6027\u3002<\/p>\n<p>As an example, listing 16.7 shows the OnPost page handler for the Checkout.cshtml Razor Page. The Input property is marked for binding and uses the UserBindingModel type shown previously in listing 16.6. This page handler doesn\u2019t do anything with the data currently, but the pattern of checking ModelState early in the method is the key takeaway here.<br \/>\n\u4f8b\u5982\uff0c\u5217\u8868 16.7 \u663e\u793a\u4e86 Checkout.cshtml Razor \u9875\u9762\u7684 OnPost \u9875\u9762\u5904\u7406\u7a0b\u5e8f\u3002Input \u5c5e\u6027\u6807\u8bb0\u4e3a\u7ed1\u5b9a\uff0c\u5e76\u4f7f\u7528\u524d\u9762\u5217\u8868 16.6 \u4e2d\u6240\u793a\u7684 UserBindingModel \u7c7b\u578b\u3002\u6b64\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u76ee\u524d\u4e0d\u5bf9\u6570\u636e\u6267\u884c\u4efb\u4f55\u4f5c\uff0c\u4f46\u5728\u65b9\u6cd5\u7684\u65e9\u671f\u68c0\u67e5 ModelState \u7684\u6a21\u5f0f\u662f\u8fd9\u91cc\u7684\u5173\u952e\u8981\u70b9\u3002<\/p>\n<p>Listing 16.7 Checking model state to view the validation result<br \/>\n\u793a\u4f8b 16.7 \u68c0\u67e5\u6a21\u578b\u72b6\u6001\u4ee5\u67e5\u770b\u9a8c\u8bc1\u7ed3\u679c<\/p>\n<pre><code>public class CheckoutModel : PageModel \u2776\n{\n[BindProperty] \u2777\npublic UserBindingModel Input { get; set; } \u2777\npublic IActionResult OnPost() \u2778\n{\nif (!ModelState.IsValid) \u2779\n{\nreturn Page(); \u277a\n}\n\/* Save to the database, update user, return success *\/ \u277b\nreturn RedirectToPage(&quot;Success&quot;);\n}\n}<\/code><\/pre>\n<p>\u2776 The ModelState property is available on the PageModel base class.<br \/>\nModelState \u5c5e\u6027\u5728 PageModel \u57fa\u7c7b\u4e2d\u53ef\u7528\u3002<br \/>\n\u2777 The Input property contains the model-bound data.<br \/>\nInput \u5c5e\u6027\u5305\u542b\u6a21\u578b\u7ed1\u5b9a\u6570\u636e\u3002<br \/>\n\u2778 The binding model is validated before the page handler is executed.<br \/>\n\u5728\u6267\u884c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u4e4b\u524d\u9a8c\u8bc1\u7ed1\u5b9a\u6a21\u578b\u3002<br \/>\n\u2779 If there were validation errors, IsValid will be false.<br \/>\n\u5982\u679c\u5b58\u5728\u9a8c\u8bc1\u9519\u8bef\uff0cIsValid \u5c06\u4e3a false\u3002<br \/>\n\u277a Validation failed, so redisplay the form with errors and finish the method early.<br \/>\n\u9a8c\u8bc1\u5931\u8d25\uff0c\u56e0\u6b64\u8bf7\u91cd\u65b0\u663e\u793a\u6709\u9519\u8bef\u7684\u8868\u5355\u5e76\u63d0\u524d\u5b8c\u6210\u8be5\u65b9\u6cd5\u3002<br \/>\n\u277b Validation passed, so it\u2019s safe to use the data provided in the model.<br \/>\n\u9a8c\u8bc1\u901a\u8fc7\uff0c\u56e0\u6b64\u53ef\u4ee5\u5b89\u5168\u5730\u4f7f\u7528\u6a21\u578b\u4e2d\u63d0\u4f9b\u7684\u6570\u636e\u3002<\/p>\n<p>If the ModelState property indicates that an error occurred, the method immediately calls the Page() helper method. This returns a PageResult that ultimately generates HTML to return to the user, as you saw in chapter 15. The view uses the (invalid) values provided in the Input property to repopulate the form when it\u2019s displayed, as shown in figure 16.7. Also, helpful messages for the user are added automatically, using the validation errors in the ModelState property.<br \/>\n\u5982\u679c ModelState \u5c5e\u6027\u6307\u793a\u53d1\u751f\u4e86\u9519\u8bef\uff0c\u8be5\u65b9\u6cd5\u4f1a\u7acb\u5373\u8c03\u7528 Page\uff08\uff09 \u5e2e\u52a9\u7a0b\u5e8f\u65b9\u6cd5\u3002\u8fd9\u5c06\u8fd4\u56de\u4e00\u4e2a PageResult\uff0c\u8be5\u7ed3\u679c\u6700\u7ec8\u751f\u6210 HTML \u5e76\u8fd4\u56de\u7ed9\u7528\u6237\uff0c\u5982\u7b2c 15 \u7ae0\u6240\u793a\u3002\u89c6\u56fe\u4f7f\u7528 Input \u5c5e\u6027\u4e2d\u63d0\u4f9b\u7684\uff08\u65e0\u6548\uff09\u503c\u5728\u663e\u793a\u8868\u5355\u65f6\u91cd\u65b0\u586b\u5145\u8868\u5355\uff0c\u5982\u56fe 16.7 \u6240\u793a\u3002\u6b64\u5916\uff0c\u5c06\u4f7f\u7528 ModelState \u5c5e\u6027\u4e2d\u7684\u9a8c\u8bc1\u9519\u8bef\u81ea\u52a8\u6dfb\u52a0\u5bf9\u7528\u6237\u6709\u7528\u7684\u6d88\u606f\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/1607.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 16.7 When validation fails, you can redisplay the form to display ModelState validation errors to the user. Note that the Your Name field has no associated validation errors, unlike the other fields.<br \/>\n\u56fe 16.7 \u9a8c\u8bc1\u5931\u8d25\u65f6\uff0c\u60a8\u53ef\u4ee5\u91cd\u65b0\u663e\u793a\u8868\u5355\u4ee5\u5411\u7528\u6237\u663e\u793a ModelState \u9a8c\u8bc1\u9519\u8bef\u3002\u8bf7\u6ce8\u610f\uff0c\u4e0e\u5176\u4ed6\u5b57\u6bb5\u4e0d\u540c\uff0cYour Name \u5b57\u6bb5\u6ca1\u6709\u5173\u8054\u7684\u9a8c\u8bc1\u9519\u8bef\u3002<\/p>\n<p><b>NOTE<\/b> The error messages displayed on the form are the default values for each validation attribute. You can customize the message by setting the ErrorMessage property on any of the validation attributes. For example, you could customize a [Required] attribute using [Required(ErrorMessage=&quot;Required&quot;)].<br \/>\n\u6ce8\u610f\u8868\u5355\u4e0a\u663e\u793a\u7684\u9519\u8bef\u6d88\u606f\u662f\u6bcf\u4e2a validation \u5c5e\u6027\u7684\u9ed8\u8ba4\u503c\u3002\u60a8\u53ef\u4ee5\u901a\u8fc7\u5728\u4efb\u4f55\u9a8c\u8bc1\u5c5e\u6027\u4e0a\u8bbe\u7f6e ErrorMessage \u5c5e\u6027\u6765\u81ea\u5b9a\u4e49\u6d88\u606f\u3002\u4f8b\u5982\uff0c\u60a8\u53ef\u4ee5\u4f7f\u7528 [Required\uff08ErrorMessage=\u201cRequired\u201d\uff09] \u81ea\u5b9a\u4e49 [Required] \u5c5e\u6027\u3002<\/p>\n<p>If the request is successful, the page handler returns a RedirectToPageResult (using the RedirectToPage() helper method) that redirects the user to the Success.cshtml Razor Page. This pattern of returning a redirect response after a successful POST is called the POST-REDIRECT-GET pattern.<br \/>\n\u5982\u679c\u8bf7\u6c42\u6210\u529f\uff0c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u5c06\u8fd4\u56de\u4e00\u4e2a RedirectToPageResult\uff08\u4f7f\u7528 RedirectToPage\uff08\uff09 \u5e2e\u52a9\u7a0b\u5e8f\u65b9\u6cd5\uff09\uff0c\u5c06\u7528\u6237\u91cd\u5b9a\u5411\u5230 Success.cshtml Razor \u9875\u9762\u3002\u8fd9\u79cd\u5728\u6210\u529f POST \u540e\u8fd4\u56de\u91cd\u5b9a\u5411\u54cd\u5e94\u7684\u6a21\u5f0f\u79f0\u4e3a POST-REDIRECT-GET \u6a21\u5f0f\u3002<\/p>\n<blockquote>\n<p>POST-REDIRECT-GET<br \/>\nThe POST-REDIRECT-GET design pattern is a web development pattern that prevents users from accidentally submitting the same form multiple times. Users typically submit a form using the standard browser POST mechanism, sending data to the server. This is the normal way by which you might take a payment, for example.<br \/>\nPOST-REDIRECT-GET \u8bbe\u8ba1\u6a21\u5f0f\u662f\u4e00\u79cd Web \u5f00\u53d1\u6a21\u5f0f\uff0c\u53ef\u9632\u6b62\u7528\u6237\u610f\u5916\u5730\u591a\u6b21\u63d0\u4ea4\u76f8\u540c\u7684\u8868\u5355\u3002\u7528\u6237\u901a\u5e38\u4f7f\u7528\u6807\u51c6\u6d4f\u89c8\u5668 POST \u673a\u5236\u63d0\u4ea4\u8868\u5355\uff0c\u5e76\u5c06\u6570\u636e\u53d1\u9001\u5230\u670d\u52a1\u5668\u3002\u4f8b\u5982\uff0c\u8fd9\u662f\u60a8\u53ef\u80fd\u63a5\u53d7\u4ed8\u6b3e\u7684\u6b63\u5e38\u65b9\u5f0f\u3002<\/p>\n<p>If a server takes the naive approach and responds with a 200 OK response and some HTML to display, the user will still be on the same URL. If the user refreshes their browser, they will be making an additional POST to the server, potentially making another payment! Browsers have some mechanisms to prevent this, such as in the following figure, but the user experience isn\u2019t desirable.<br \/>\n\u5982\u679c\u670d\u52a1\u5668\u91c7\u7528\u7b80\u5355\u7684\u65b9\u6cd5\uff0c\u5e76\u4ee5 200 OK \u54cd\u5e94\u548c\u4e00\u4e9b\u8981\u663e\u793a\u7684 HTML \u8fdb\u884c\u54cd\u5e94\uff0c\u5219\u7528\u6237\u4ecd\u5c06\u4f4d\u4e8e\u540c\u4e00 URL \u4e0a\u3002\u5982\u679c\u7528\u6237\u5237\u65b0\u6d4f\u89c8\u5668\uff0c\u4ed6\u4eec\u5c06\u5411\u670d\u52a1\u5668\u8fdb\u884c\u989d\u5916\u7684 POST\uff0c\u53ef\u80fd\u4f1a\u518d\u6b21\u4ed8\u6b3e\uff01\u6d4f\u89c8\u5668\u6709\u4e00\u4e9b\u673a\u5236\u53ef\u4ee5\u9632\u6b62\u8fd9\u79cd\u60c5\u51b5\uff0c\u5982\u4e0b\u56fe\u6240\u793a\uff0c\u4f46\u7528\u6237\u4f53\u9a8c\u5e76\u4e0d\u7406\u60f3\u3002<\/p>\n<\/blockquote>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/1608.png\" alt=\"alt text\" \/><\/p>\n<blockquote>\n<p>Refreshing a browser window after a POST causes a warning message to be shown to the user<br \/>\n\u5728 POST \u540e\u5237\u65b0\u6d4f\u89c8\u5668\u7a97\u53e3\u4f1a\u5bfc\u81f4\u5411\u7528\u6237\u663e\u793a\u8b66\u544a\u6d88\u606f<\/p>\n<p>The POST-REDIRECT-GET pattern says that in response to a successful POST, you should return a REDIRECT response to a new URL, which will be followed by the browser making a GET to the new URL. If the user refreshes their browser now, they\u2019ll be refreshing the final GET call to the new URL. No additional POST is made, so no additional payments or side effects should occur.<br \/>\nPOST-REDIRECT-GET \u6a21\u5f0f\u8868\u793a\uff0c\u4e3a\u4e86\u54cd\u5e94\u6210\u529f\u7684 POST\uff0c\u60a8\u5e94\u8be5\u8fd4\u56de\u5bf9\u65b0 URL \u7684 REDIRECT \u54cd\u5e94\uff0c\u7136\u540e\u6d4f\u89c8\u5668\u5c06\u5bf9\u65b0 URL \u8fdb\u884c GET\u3002\u5982\u679c\u7528\u6237\u73b0\u5728\u5237\u65b0\u6d4f\u89c8\u5668\uff0c\u4ed6\u4eec\u5c06\u5237\u65b0\u5bf9\u65b0 URL \u7684\u6700\u7ec8 GET \u8c03\u7528\u3002\u4e0d\u4f1a\u8fdb\u884c\u989d\u5916\u7684 POST\uff0c\u56e0\u6b64\u4e0d\u4f1a\u53d1\u751f\u989d\u5916\u7684\u4ed8\u6b3e\u6216\u526f\u4f5c\u7528\u3002<\/p>\n<p>This pattern is easy to achieve in ASP.NET Core applications using the pattern shown in listing 16.7. By returning a RedirectToPageResult after a successful POST, your application will be safe if the user refreshes the page in their browser.<br \/>\n\u5728 ASP.NET Core \u5e94\u7528\u7a0b\u5e8f\u4e2d\uff0c\u4f7f\u7528\u6e05\u5355 16.7 \u4e2d\u6240\u793a\u7684\u6a21\u5f0f\u5f88\u5bb9\u6613\u5b9e\u73b0\u8fd9\u79cd\u6a21\u5f0f\u3002\u901a\u8fc7\u5728\u6210\u529f POST \u540e\u8fd4\u56de RedirectToPageResult\uff0c\u5982\u679c\u7528\u6237\u5728\u6d4f\u89c8\u5668\u4e2d\u5237\u65b0\u9875\u9762\uff0c\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u5c06\u662f\u5b89\u5168\u7684\u3002<\/p>\n<\/blockquote>\n<p>You might be wondering why ASP.NET Core doesn\u2019t handle invalid requests for you automatically; if validation has failed, and you have the result, why does the page handler get executed at all? Isn\u2019t there a risk that you might forget to check the validation result?<br \/>\n\u60a8\u53ef\u80fd\u60f3\u77e5\u9053\u4e3a\u4ec0\u4e48 ASP.NET Core \u4e0d\u81ea\u52a8\u4e3a\u60a8\u5904\u7406\u65e0\u6548\u8bf7\u6c42;\u5982\u679c\u9a8c\u8bc1\u5931\u8d25\uff0c\u5e76\u4e14\u60a8\u6709\u7ed3\u679c\uff0c\u4e3a\u4ec0\u4e48\u8fd8\u8981\u6267\u884c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u5462\uff1f\u662f\u5426\u6709\u5fd8\u8bb0\u68c0\u67e5\u9a8c\u8bc1\u7ed3\u679c\u7684\u98ce\u9669\uff1f<\/p>\n<p>This is true, and in some cases the best thing to do is to make the generation of the validation check and response automatic. In fact, this is exactly the approach we will use for web APIs using MVC controllers with the [ApiController] attribute when we cover them in chapter 20.<br \/>\n\u8fd9\u662f\u771f\u7684\uff0c\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\uff0c\u6700\u597d\u7684\u529e\u6cd5\u662f\u81ea\u52a8\u751f\u6210\u9a8c\u8bc1\u68c0\u67e5\u548c\u54cd\u5e94\u3002\u4e8b\u5b9e\u4e0a\uff0c\u8fd9\u6b63\u662f\u6211\u4eec\u5728\u7b2c 20 \u7ae0\u4e2d\u4ecb\u7ecd\u65f6\u5c06\u7528\u4e8e\u4f7f\u7528\u5e26\u6709 [ApiController] \u5c5e\u6027\u7684 MVC \u63a7\u5236\u5668\u7684 Web API \u7684\u65b9\u6cd5\u3002<\/p>\n<p>For Razor Pages apps, however, you typically still want to generate an HTML response, even when validation failed. This allows the user to see the problem and potentially correct it. This is much harder to make automatic.<br \/>\n\u4f46\u662f\uff0c\u5bf9\u4e8e Razor Pages \u5e94\u7528\u7a0b\u5e8f\uff0c\u5373\u4f7f\u9a8c\u8bc1\u5931\u8d25\uff0c\u60a8\u901a\u5e38\u4ecd\u5e0c\u671b\u751f\u6210 HTML \u54cd\u5e94\u3002\u8fd9\u6837\uff0c\u7528\u6237\u5c31\u53ef\u4ee5\u770b\u5230\u95ee\u9898\uff0c\u5e76\u53ef\u80fd\u7ea0\u6b63\u95ee\u9898\u3002\u8fd9\u8981\u81ea\u52a8\u5316\u8981\u56f0\u96be\u5f97\u591a\u3002<\/p>\n<p>For example, you might find you need to load additional data before you can redisplay the Razor Page, such as loading a list of available currencies. That becomes simpler and more explicit with the ModelState.IsValid pattern. Trying to do that automatically would likely end up with you fighting against edge cases and workarounds.<br \/>\n\u4f8b\u5982\uff0c\u4f60\u53ef\u80fd\u4f1a\u53d1\u73b0\u9700\u8981\u5148\u52a0\u8f7d\u5176\u4ed6\u6570\u636e\uff0c\u7136\u540e\u624d\u80fd\u91cd\u65b0\u663e\u793a Razor \u9875\u9762\uff0c\u4f8b\u5982\u52a0\u8f7d\u53ef\u7528\u8d27\u5e01\u7684\u5217\u8868\u3002\u4f7f\u7528 ModelState.IsValid \u6a21\u5f0f\uff0c\u8fd9\u5c06\u53d8\u5f97\u66f4\u7b80\u5355\u3001\u66f4\u660e\u786e\u3002\u5c1d\u8bd5\u81ea\u52a8\u6267\u884c\u6b64\u4f5c\u53ef\u80fd\u6700\u7ec8\u4f1a\u8ba9\u60a8\u4e0e\u8fb9\u7f18\u60c5\u51b5\u548c\u89e3\u51b3\u65b9\u6cd5\u4f5c\u6597\u4e89\u3002<\/p>\n<p>Also, by including the IsValid check explicitly in your page handlers, it\u2019s easier to control what happens when additional validation checks fail. For example, if the user tries to update a product, the DataAnnotation validation won\u2019t know whether a product with the requested ID exists, only whether the ID has the correct format. By moving the validation to the handler method, you can treat data and business rule validation failures in the same way.<br \/>\n\u6b64\u5916\uff0c\u901a\u8fc7\u5728\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u4e2d\u663e\u5f0f\u5305\u542b IsValid \u68c0\u67e5\uff0c\u53ef\u4ee5\u66f4\u8f7b\u677e\u5730\u63a7\u5236\u5176\u4ed6\u9a8c\u8bc1\u68c0\u67e5\u5931\u8d25\u65f6\u53d1\u751f\u7684\u60c5\u51b5\u3002\u4f8b\u5982\uff0c\u5982\u679c\u7528\u6237\u5c1d\u8bd5\u66f4\u65b0\u4ea7\u54c1\uff0c\u5219 DataAnnotation \u9a8c\u8bc1\u5c06\u4e0d\u77e5\u9053\u5177\u6709\u8bf7\u6c42\u7684 ID \u7684\u4ea7\u54c1\u662f\u5426\u5b58\u5728\uff0c\u800c\u53ea\u77e5\u9053 ID \u662f\u5426\u5177\u6709\u6b63\u786e\u7684\u683c\u5f0f\u3002\u901a\u8fc7\u5c06\u9a8c\u8bc1\u79fb\u81f3\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\uff0c\u60a8\u53ef\u4ee5\u4ee5\u76f8\u540c\u7684\u65b9\u5f0f\u5904\u7406\u6570\u636e\u548c\u4e1a\u52a1\u89c4\u5219\u9a8c\u8bc1\u5931\u8d25\u3002<\/p>\n<p><b>Tip<\/b> You can also add extra validation errors to the collection, such as business rule validation errors that come from a different system. You can add errors to ModelState by calling AddModelError(), which will be displayed to users on the form alongside the DataAnnotation attribute errors.<br \/>\n\u63d0\u793a\uff1a\u60a8\u8fd8\u53ef\u4ee5\u5411\u96c6\u5408\u4e2d\u6dfb\u52a0\u989d\u5916\u7684\u9a8c\u8bc1\u9519\u8bef\uff0c\u4f8b\u5982\u6765\u81ea\u4e0d\u540c\u7cfb\u7edf\u7684\u4e1a\u52a1\u89c4\u5219\u9a8c\u8bc1\u9519\u8bef\u3002\u60a8\u53ef\u4ee5\u901a\u8fc7\u8c03\u7528 AddModelError\uff08\uff09 \u5411 ModelState \u6dfb\u52a0\u9519\u8bef\uff0c\u8be5\u9519\u8bef\u5c06\u4e0e DataAnnotation \u5c5e\u6027\u9519\u8bef\u4e00\u8d77\u663e\u793a\u5728\u8868\u5355\u4e0a\u7684\u7528\u6237\u3002<\/p>\n<p>I hope I\u2019ve hammered home how important it is to validate user input in ASP.NET Core, but just in case: VALIDATE! There, we\u2019re good. Having said that, performing validation only on the server can leave users with a slightly poor experience. How many times have you filled out a form online, submitted it, gone to get a snack, and come back to find out you mistyped something and have to redo it? Wouldn\u2019t it be nicer to have that feedback immediately?<br \/>\n\u6211\u5e0c\u671b\u6211\u5df2\u7ecf\u6e05\u695a\u5730\u8ba4\u8bc6\u5230\u5728 ASP.NET Core \u4e2d\u9a8c\u8bc1\u7528\u6237\u8f93\u5165\u7684\u91cd\u8981\u6027\uff0c\u4f46\u4ee5\u9632\u4e07\u4e00\uff1a\u9a8c\u8bc1\uff01\u597d\u4e86\uff0c\u6211\u4eec\u5f88\u597d\u3002\u8bdd\u867d\u5982\u6b64\uff0c\u4ec5\u5728\u670d\u52a1\u5668\u4e0a\u6267\u884c\u9a8c\u8bc1\u53ef\u80fd\u4f1a\u7ed9\u7528\u6237\u5e26\u6765\u7565\u5fae\u7cdf\u7cd5\u7684\u4f53\u9a8c\u3002\u4f60\u6709\u591a\u5c11\u6b21\u5728\u7f51\u4e0a\u586b\u5199\u4e86\u4e00\u4efd\u8868\u683c\uff0c\u63d0\u4ea4\u4e86\u5b83\uff0c\u53bb\u4e70\u4e86\u70b9\u96f6\u98df\uff0c\u7136\u540e\u56de\u6765\u53d1\u73b0\u4f60\u6253\u9519\u4e86\u4e1c\u897f\uff0c\u4e0d\u5f97\u4e0d\u91cd\u505a\u4e00\u904d\uff1f\u7acb\u5373\u83b7\u5f97\u8fd9\u4e9b\u53cd\u9988\u4e0d\u662f\u66f4\u597d\u5417\uff1f<\/p>\n<h3>16.3.3 Validating on the client for user experience<\/h3>\n<p>16.3.3 \u5728\u5ba2\u6237\u7aef\u4e0a\u9a8c\u8bc1\u7528\u6237\u4f53\u9a8c<\/p>\n<p>You can add client-side validation to your application in a few different ways. HTML5 has several built-in validation behaviors that many browsers use. If you display an email address field on a page and use the \u201cemail\u201d HTML input type, the browser automatically stops you from submitting an invalid format, as shown in figure 16.8. Your application doesn\u2019t control this validation; it\u2019s built into modern HTML5 browsers.<br \/>\n\u60a8\u53ef\u4ee5\u901a\u8fc7\u51e0\u79cd\u4e0d\u540c\u7684\u65b9\u5f0f\u5c06\u5ba2\u6237\u7aef\u9a8c\u8bc1\u6dfb\u52a0\u5230\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u4e2d\u3002HTML5 \u5177\u6709\u8bb8\u591a\u6d4f\u89c8\u5668\u4f7f\u7528\u7684\u51e0\u4e2a\u5185\u7f6e\u9a8c\u8bc1\u884c\u4e3a\u3002\u5982\u679c\u60a8\u5728\u9875\u9762\u4e0a\u663e\u793a\u7535\u5b50\u90ae\u4ef6\u5730\u5740\u5b57\u6bb5\u5e76\u4f7f\u7528 \u201cemail\u201d HTML \u8f93\u5165\u7c7b\u578b\uff0c\u6d4f\u89c8\u5668\u4f1a\u81ea\u52a8\u963b\u6b62\u60a8\u63d0\u4ea4\u65e0\u6548\u683c\u5f0f\uff0c\u5982\u56fe 16.8 \u6240\u793a\u3002\u60a8\u7684\u5e94\u7528\u7a0b\u5e8f\u4e0d\u63a7\u5236\u6b64\u9a8c\u8bc1;\u5b83\u5185\u7f6e\u4e8e\u73b0\u4ee3 HTML5 \u6d4f\u89c8\u5668\u4e2d\u3002<\/p>\n<p><b>NOTE<\/b> HTML5 constraint validation support varies by browser. For details on the available constraints, see the Mozilla documentation (<a href=\"http:\/\/mng.bz\/daX3\">http:\/\/mng.bz\/daX3<\/a>) and <a href=\"https:\/\/caniuse.com\/#feat=constraint-validation\">https:\/\/caniuse.com\/#feat=constraint-validation<\/a>.<br \/>\n\u6ce8\u610f\uff1aHTML5 \u7ea6\u675f\u9a8c\u8bc1\u652f\u6301\u56e0\u6d4f\u89c8\u5668\u800c\u5f02\u3002\u6709\u5173\u53ef\u7528\u7ea6\u675f\u7684\u8be6\u7ec6\u4fe1\u606f\uff0c\u8bf7\u53c2\u9605 Mozilla \u6587\u6863 \uff08<a href=\"http:\/\/mng.bz\/daX3\">http:\/\/mng.bz\/daX3<\/a>\uff09 \u548c <a href=\"https:\/\/caniuse.com\/#feat=constraint-validation\">https:\/\/caniuse.com\/#feat=constraint-validation<\/a>\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/1609.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 16.8 By default, modern browsers automatically validate fields of the email type before a form is submitted.<br \/>\n\u56fe 16.8 \u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u73b0\u4ee3\u6d4f\u89c8\u5668\u4f1a\u5728\u63d0\u4ea4\u8868\u5355\u4e4b\u524d\u81ea\u52a8\u9a8c\u8bc1\u7535\u5b50\u90ae\u4ef6\u7c7b\u578b\u7684\u5b57\u6bb5\u3002<\/p>\n<p>The alternative approach to HTML validation is to perform client-side validation by running JavaScript on the page and checking the values the user entered before submitting the form. This is the most common approach used in Razor Pages.<br \/>\nHTML \u9a8c\u8bc1\u7684\u53e6\u4e00\u79cd\u65b9\u6cd5\u662f\u901a\u8fc7\u5728\u9875\u9762\u4e0a\u8fd0\u884c JavaScript \u5e76\u5728\u63d0\u4ea4\u8868\u5355\u4e4b\u524d\u68c0\u67e5\u7528\u6237\u8f93\u5165\u7684\u503c\u6765\u6267\u884c\u5ba2\u6237\u7aef\u9a8c\u8bc1\u3002\u8fd9\u662f Razor Pages \u4e2d\u6700\u5e38\u7528\u7684\u65b9\u6cd5\u3002<\/p>\n<p>I\u2019ll go into detail on how to generate the client-side validation helpers in chapter 18, where you\u2019ll see the DataAnnotation attributes come to the fore once again. By decorating a view model with these attributes, you provide the necessary metadata to the Razor engine for it to generate the appropriate validation HTML.<br \/>\n\u5728\u7b2c 18 \u7ae0\u4e2d\uff0c\u6211\u5c06\u8be6\u7ec6\u4ecb\u7ecd\u5982\u4f55\u751f\u6210\u5ba2\u6237\u7aef\u9a8c\u8bc1\u5e2e\u52a9\u7a0b\u5e8f\uff0c\u5c4a\u65f6\u60a8\u5c06\u770b\u5230 DataAnnotation \u5c5e\u6027\u518d\u6b21\u51fa\u73b0\u3002\u901a\u8fc7\u4f7f\u7528\u8fd9\u4e9b\u5c5e\u6027\u4fee\u9970\u89c6\u56fe\u6a21\u578b\uff0c\u60a8\u53ef\u4ee5\u5411 Razor \u5f15\u64ce\u63d0\u4f9b\u5fc5\u8981\u7684\u5143\u6570\u636e\uff0c\u4ee5\u4fbf\u5b83\u751f\u6210\u9002\u5f53\u7684\u9a8c\u8bc1 HTML\u3002<\/p>\n<p>With this approach, the user sees any errors with their form immediately, even before the request is sent to the server, as shown in figure 16.9. This gives a much shorter feedback cycle, providing a better user experience.<br \/>\n\u4f7f\u7528\u8fd9\u79cd\u65b9\u6cd5\uff0c\u7528\u6237\u4f1a\u7acb\u5373\u770b\u5230\u5176\u8868\u5355\u4e2d\u7684\u4efb\u4f55\u9519\u8bef\uff0c\u751a\u81f3\u5728\u8bf7\u6c42\u53d1\u9001\u5230\u670d\u52a1\u5668\u4e4b\u524d\uff0c\u5982\u56fe 16.9 \u6240\u793a\u3002\u8fd9\u63d0\u4f9b\u4e86\u66f4\u77ed\u7684\u53cd\u9988\u5468\u671f\uff0c\u4ece\u800c\u63d0\u4f9b\u66f4\u597d\u7684\u7528\u6237\u4f53\u9a8c\u3002<\/p>\n<p><img decoding=\"async\" src=\"\/images\/aspnetcoreinaction\/1610.png\" alt=\"alt text\" \/><\/p>\n<p>Figure 16.9 With client-side validation, clicking Submit triggers validation to be shown in the browser before the request is sent to the server. As shown in the right pane, no request is sent.<br \/>\n\u56fe 16.9 \u4f7f\u7528\u5ba2\u6237\u7aef\u9a8c\u8bc1\u65f6\uff0c\u5355\u51fb\u201c\u63d0\u4ea4\u201d\u4f1a\u89e6\u53d1\u9a8c\u8bc1\uff0c\u5728\u5c06\u8bf7\u6c42\u53d1\u9001\u5230\u670d\u52a1\u5668\u4e4b\u524d\uff0c\u5c06\u5728\u6d4f\u89c8\u5668\u4e2d\u663e\u793a\u3002\u5982\u53f3\u7a97\u683c\u4e2d\u6240\u793a\uff0c\u672a\u53d1\u9001\u4efb\u4f55\u8bf7\u6c42\u3002<\/p>\n<p>If you\u2019re building an SPA, the onus is on the client-side framework to validate the data on the client side before posting it to the API. The API must still validate the data when it arrives at the server, but the client-side framework is responsible for providing the smooth user experience.<br \/>\n\u5982\u679c\u60a8\u6b63\u5728\u6784\u5efa SPA\uff0c\u5219\u5ba2\u6237\u7aef\u6846\u67b6\u6709\u8d23\u4efb\u5728\u5c06\u6570\u636e\u53d1\u5e03\u5230 API \u4e4b\u524d\u5728\u5ba2\u6237\u7aef\u9a8c\u8bc1\u6570\u636e\u3002\u5f53\u6570\u636e\u5230\u8fbe\u670d\u52a1\u5668\u65f6\uff0cAPI \u4ecd\u5fc5\u987b\u9a8c\u8bc1\u6570\u636e\uff0c\u4f46\u5ba2\u6237\u7aef\u6846\u67b6\u8d1f\u8d23\u63d0\u4f9b\u6d41\u7545\u7684\u7528\u6237\u4f53\u9a8c\u3002<\/p>\n<p>When you use Razor Pages to generate your HTML, you get much of this validation code for free. Razor Pages automatically configures client-side validation for most of the built-in attributes without requiring additional work, as you\u2019ll see in chapter 18. Unfortunately, if you\u2019ve used custom ValidationAttributes, these will run only on the server by default; you need to do some additional wiring up of the attribute to make it work on the client side too. Despite this, custom validation attributes can be useful for handling common validation scenarios in your application, as you\u2019ll see in chapter 31.<br \/>\n\u4f7f\u7528 Razor Pages \u751f\u6210 HTML \u65f6\uff0c\u53ef\u4ee5\u514d\u8d39\u83b7\u5f97\u5927\u90e8\u5206\u9a8c\u8bc1\u4ee3\u7801\u3002Razor Pages \u4f1a\u81ea\u52a8\u4e3a\u5927\u591a\u6570\u5185\u7f6e\u5c5e\u6027\u914d\u7f6e\u5ba2\u6237\u7aef\u9a8c\u8bc1\uff0c\u800c\u65e0\u9700\u6267\u884c\u5176\u4ed6\u5de5\u4f5c\uff0c\u5982\u7b2c 18 \u7ae0\u6240\u793a\u3002\u9057\u61be\u7684\u662f\uff0c\u5982\u679c\u4f60\u4f7f\u7528\u4e86\u81ea\u5b9a\u4e49 ValidationAttributes\uff0c\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0c\u8fd9\u4e9b\u5c5e\u6027\u5c06\u4ec5\u5728\u670d\u52a1\u5668\u4e0a\u8fd0\u884c;\u60a8\u9700\u8981\u5bf9 attribute \u8fdb\u884c\u4e00\u4e9b\u989d\u5916\u7684\u8fde\u63a5\uff0c\u4f7f\u5176\u4e5f\u53ef\u4ee5\u5728 Client \u7aef\u5de5\u4f5c\u3002\u5c3d\u7ba1\u5982\u6b64\uff0c\u81ea\u5b9a\u4e49\u9a8c\u8bc1\u5c5e\u6027\u5bf9\u4e8e\u5904\u7406\u5e94\u7528\u7a0b\u5e8f\u4e2d\u7684\u5e38\u89c1\u9a8c\u8bc1\u573a\u666f\u975e\u5e38\u6709\u7528\uff0c\u5982\u7b2c 31 \u7ae0\u6240\u793a\u3002<\/p>\n<p>The model binding framework in ASP.NET Core gives you a lot of options on how to organize your Razor Pages: page handler parameters or PageModel properties; one binding model or multiple; options for where to define your binding model classes. In the next section I give some advice on how I like to organize my Razor Pages.<br \/>\nASP.NET Core \u4e2d\u7684\u6a21\u578b\u7ed1\u5b9a\u6846\u67b6\u63d0\u4f9b\u4e86\u8bb8\u591a\u6709\u5173\u5982\u4f55\u7ec4\u7ec7 Razor \u9875\u9762\u7684\u9009\u9879\uff1a\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u53c2\u6570\u6216 PageModel \u5c5e\u6027;\u4e00\u4e2a\u6216\u591a\u4e2a\u88c5\u8ba2\u6a21\u578b;\u7528\u4e8e\u5b9a\u4e49\u7ed1\u5b9a\u6a21\u578b\u7c7b\u7684\u4f4d\u7f6e\u7684\u9009\u9879\u3002\u5728\u4e0b\u4e00\u8282\u4e2d\uff0c\u6211\u5c06\u5c31\u5982\u4f55\u7ec4\u7ec7\u6211\u7684 Razor \u9875\u9762\u63d0\u4f9b\u4e00\u4e9b\u5efa\u8bae\u3002<\/p>\n<h2>16.4 Organizing your binding models in Razor Pages<\/h2>\n<p>16.4 \u5728 Razor Pages \u4e2d\u7ec4\u7ec7\u7ed1\u5b9a\u6a21\u578b<\/p>\n<p>In this section I give some general advice on how I like to configure the binding models in my Razor Pages. If you follow the patterns in this section, your Razor Pages will follow a consistent layout, making it easier for others to understand how each Razor Page in your app works.<br \/>\n\u5728\u672c\u8282\u4e2d\uff0c\u6211\u5c06\u5c31\u5982\u4f55\u5728 Razor Pages \u4e2d\u914d\u7f6e\u7ed1\u5b9a\u6a21\u578b\u63d0\u4f9b\u4e00\u4e9b\u4e00\u822c\u6027\u5efa\u8bae\u3002\u5982\u679c\u9075\u5faa\u672c\u90e8\u5206\u4e2d\u7684\u6a21\u5f0f\uff0c\u5219 Razor \u9875\u9762\u5c06\u9075\u5faa\u4e00\u81f4\u7684\u5e03\u5c40\uff0c\u4f7f\u5176\u4ed6\u4eba\u66f4\u5bb9\u6613\u4e86\u89e3\u5e94\u7528\u4e2d\u7684\u6bcf\u4e2a Razor \u9875\u9762\u7684\u5de5\u4f5c\u539f\u7406\u3002<\/p>\n<p><b>NOTE<\/b> This advice is just personal preference, so feel free to adapt it if there are aspects you don\u2019t agree with. The important thing is to understand why I make each suggestion, and to take that on board. Where appropriate, I deviate from these guidelines too!<br \/>\n\u6ce8\u610f\uff1a\u6b64\u5efa\u8bae\u53ea\u662f\u4e2a\u4eba\u559c\u597d\uff0c\u56e0\u6b64\u5982\u679c\u60a8\u6709\u4e0d\u540c\u610f\u7684\u65b9\u9762\uff0c\u8bf7\u968f\u65f6\u5bf9\u5176\u8fdb\u884c\u8c03\u6574\u3002\u91cd\u8981\u7684\u662f\u7406\u89e3\u6211\u4e3a\u4ec0\u4e48\u63d0\u51fa\u6bcf\u4e2a\u5efa\u8bae\uff0c\u5e76\u91c7\u7eb3\u5b83\u3002\u5728\u9002\u5f53\u7684\u60c5\u51b5\u4e0b\uff0c\u6211\u4e5f\u4f1a\u504f\u79bb\u8fd9\u4e9b\u51c6\u5219\uff01<\/p>\n<p>Model binding in ASP.NET Core has a lot of equivalent approaches to take, so there is no \u201ccorrect\u201d way to do it. Listing 16.8 shows an example of how I would design a simple Razor Page. This Razor Page displays a form for a product with a given ID and allows you to edit the details using a POST request. It\u2019s a much longer sample than we\u2019ve looked at so far, but I highlight the important points.<br \/>\nASP.NET Core \u4e2d\u7684\u6a21\u578b\u7ed1\u5b9a\u6709\u5f88\u591a\u7b49\u6548\u7684\u65b9\u6cd5\u53ef\u4f9b\u91c7\u7528\uff0c\u56e0\u6b64\u6ca1\u6709\u201c\u6b63\u786e\u201d\u7684\u65b9\u6cd5\u3002\u6e05\u5355 16.8 \u663e\u793a\u4e86\u5982\u4f55\u8bbe\u8ba1\u4e00\u4e2a\u7b80\u5355\u7684 Razor Page \u7684\u793a\u4f8b\u3002\u6b64 Razor \u9875\u9762\u663e\u793a\u5177\u6709\u7ed9\u5b9a ID \u7684\u4ea7\u54c1\u7684\u8868\u5355\uff0c\u5e76\u5141\u8bb8\u60a8\u4f7f\u7528 POST \u8bf7\u6c42\u7f16\u8f91\u8be6\u7ec6\u4fe1\u606f\u3002\u8fd9\u4e2a\u6837\u672c\u6bd4\u6211\u4eec\u76ee\u524d\u770b\u5230\u7684\u8981\u957f\u5f97\u591a\uff0c\u4f46\u6211\u5f3a\u8c03\u4e86\u8981\u70b9\u3002<\/p>\n<p>Listing 16.8 Designing an edit product Razor Page<br \/>\n\u6e05\u5355 16.8 \u8bbe\u8ba1\u7f16\u8f91\u4ea7\u54c1 Razor \u9875\u9762<\/p>\n<pre><code>public class EditProductModel : PageModel\n{\nprivate readonly ProductService _productService; \u2776\npublic EditProductModel(ProductService productService) \u2776\n{ \u2776\n_productService = productService; \u2776\n} \u2776\n[BindProperty] \u2777\npublic InputModel Input { get; set; } \u2777\npublic IActionResult OnGet(int id) \u2778\n{\nvar product = _productService.GetProduct(id); \u2779\nInput = new InputModel \u277a\n{ \u277a\nName = product.ProductName, \u277a\nPrice = product.SellPrice, \u277a\n}; \u277a\nreturn Page(); \u277a\n}\npublic IActionResult OnPost(int id) \u277b\n{\nif (!ModelState.IsValid) \u277c\n{ \u277c\nreturn Page(); \u277c\n} \u277c\n_productService.UpdateProduct(id, Input.Name, Input.Price); \u277d\nreturn RedirectToPage(&quot;Index&quot;); \u277e\n}\npublic class InputModel \u277f\n{ \u277f\n[Required] \u277f\npublic string Name { get; set; } \u277f\n[Range(0, int.MaxValue)] \u277f\npublic decimal Price { get; set; } \u277f\n} \u277f\n}\n<\/code><\/pre>\n<p>\u2776 The ProductService is injected using DI and provides access to the application model.<br \/>\nProductService \u4f7f\u7528 DI \u6ce8\u5165\uff0c\u5e76\u63d0\u4f9b\u5bf9\u5e94\u7528\u7a0b\u5e8f\u6a21\u578b\u7684\u8bbf\u95ee\u3002<br \/>\n\u2777 A single property is marked with BindProperty.<br \/>\n\u5355\u4e2a\u5c5e\u6027\u4f7f\u7528 BindProperty \u8fdb\u884c\u6807\u8bb0\u3002<br \/>\n\u2778 The id parameter is model-bound from the route template for both OnGet and OnPost handlers.<br \/>\nid \u53c2\u6570\u662f OnGet \u548c OnPost \u5904\u7406\u7a0b\u5e8f\u7684\u8def\u7531\u6a21\u677f\u7684\u6a21\u578b\u7ed1\u5b9a\u7684\u3002<br \/>\n\u2779 Loads the product details from the application model<br \/>\n\u4ece\u5e94\u7528\u7a0b\u5e8f\u6a21\u578b\u52a0\u8f7d\u4ea7\u54c1\u8be6\u7ec6\u4fe1\u606f<br \/>\n\u277a Builds an instance of the InputModel for editing in the form from the existing product\u2019s details<br \/>\n\u6784\u5efa InputModel \u7684\u5b9e\u4f8b\uff0c\u4ee5\u4fbf\u6839\u636e\u73b0\u6709\u4ea7\u54c1\u7684\u8be6\u7ec6\u4fe1\u606f\u5728\u8868\u5355\u4e2d\u8fdb\u884c\u7f16\u8f91<br \/>\n\u277b The id parameter is model-bound from the route template for both OnGet and OnPost handlers.<br \/>\nid \u53c2\u6570\u4e0e OnGet \u548c OnPost \u5904\u7406\u7a0b\u5e8f\u7684\u8def\u7531\u6a21\u677f\u8fdb\u884c\u6a21\u578b\u7ed1\u5b9a\u3002<br \/>\n\u277c If the request was not valid, redisplays the form without saving<br \/>\n\u5982\u679c\u8bf7\u6c42\u65e0\u6548\uff0c\u5219\u91cd\u65b0\u663e\u793a\u8868\u5355\u800c\u4e0d\u4fdd\u5b58<br \/>\n\u277d Updates the product in the application model using the ProductService<br \/>\n\u4f7f\u7528 ProductService\u66f4\u65b0\u5e94\u7528\u7a0b\u5e8f\u6a21\u578b\u4e2d\u7684\u4ea7\u54c1<br \/>\n\u277e Redirects to a new page using the POST-REDIRECT-GET pattern<br \/>\n\u4f7f\u7528 POST-REDIRECT-GET \u6a21\u5f0f\u91cd\u5b9a\u5411\u5230\u65b0\u9875\u9762<br \/>\n\u277f Defines the InputModel as a nested class in the Razor Page<br \/>\n\u5c06 InputModel \u5b9a\u4e49\u4e3a Razor \u9875\u9762\u4e2d\u7684\u5d4c\u5957\u7c7b<\/p>\n<p>This page shows the PageModel for a typical \u201cedit form.\u201d These are common in many line-of-business applications, among others, and it\u2019s a scenario that Razor Pages works well for. You\u2019ll see how to create the HTML side of forms in chapter 18.<br \/>\n\u6b64\u9875\u9762\u663e\u793a\u4e86\u5178\u578b\u7684 \u201c\u7f16\u8f91\u8868\u5355\u201d \u7684 PageModel\u3002\u8fd9\u4e9b\u5728\u8bb8\u591a\u4e1a\u52a1\u7ebf\u5e94\u7528\u7a0b\u5e8f\u4e2d\u5f88\u5e38\u89c1\uff0c\u8fd9\u662f Razor Pages \u975e\u5e38\u9002\u5408\u7684\u65b9\u6848\u3002\u60a8\u5c06\u5728\u7b2c 18 \u7ae0\u4e2d\u4e86\u89e3\u5982\u4f55\u521b\u5efa\u8868\u5355\u7684 HTML \u7aef\u3002<\/p>\n<p><b>NOTE<\/b> The purpose of this example is to highlight the model-binding approach. The code is overly simplistic from a logic point of view. For example, it doesn\u2019t check that the product with the provided ID exists or include any error handling.<br \/>\n\u6ce8\u610f\uff1a\u6b64\u793a\u4f8b\u7684\u76ee\u7684\u662f\u5f3a\u8c03\u6a21\u578b\u7ed1\u5b9a\u65b9\u6cd5\u3002\u4ece\u903b\u8f91\u7684\u89d2\u5ea6\u6765\u770b\uff0c\u4ee3\u7801\u8fc7\u4e8e\u7b80\u5355\u3002\u4f8b\u5982\uff0c\u5b83\u4e0d\u4f1a\u68c0\u67e5\u5177\u6709\u6240\u63d0\u4f9b ID \u7684\u4ea7\u54c1\u662f\u5426\u5b58\u5728\uff0c\u4e5f\u4e0d\u5305\u542b\u4efb\u4f55\u9519\u8bef\u5904\u7406\u3002<\/p>\n<p>This form shows several patterns related to model binding that I try to adhere to when building Razor Pages:<br \/>\n\u6b64\u8868\u5355\u663e\u793a\u4e86\u6211\u5728\u6784\u5efa Razor Pages \u65f6\u5c1d\u8bd5\u9075\u5faa\u7684\u51e0\u79cd\u4e0e\u6a21\u578b\u7ed1\u5b9a\u76f8\u5173\u7684\u6a21\u5f0f\uff1a<\/p>\n<p>\u2022  Bind only a single property with [BindProperty]. I favor having a single property decorated with [BindProperty] for model binding in general. When more than one value needs to be bound, I create a separate class, InputModel, to hold the values, and I decorate that single property with [BindProperty]. Decorating a single property like this makes it harder to forget to add the attribute, and it means all your Razor Pages use the same pattern.<br \/>\n\u4ec5\u5c06\u5355\u4e2a\u5c5e\u6027\u4e0e [BindProperty] \u7ed1\u5b9a\u3002\u901a\u5e38\uff0c\u6211\u8d5e\u6210\u4f7f\u7528 [BindProperty] \u4fee\u9970\u5355\u4e2a\u5c5e\u6027\u4ee5\u8fdb\u884c\u6a21\u578b\u7ed1\u5b9a\u3002\u5f53\u9700\u8981\u7ed1\u5b9a\u591a\u4e2a\u503c\u65f6\uff0c\u6211\u521b\u5efa\u4e00\u4e2a\u5355\u72ec\u7684\u7c7b InputModel \u6765\u4fdd\u5b58\u8fd9\u4e9b\u503c\uff0c\u5e76\u4f7f\u7528 [BindProperty] \u4fee\u9970\u8be5\u5355\u4e2a\u5c5e\u6027\u3002\u50cf\u8fd9\u6837\u4fee\u9970\u5355\u4e2a\u5c5e\u6027\u4f1a\u8ba9\u4eba\u66f4\u96be\u5fd8\u8bb0\u6dfb\u52a0\u5c5e\u6027\uff0c\u8fd9\u610f\u5473\u7740\u4f60\u7684\u6240\u6709 Razor \u9875\u9762\u90fd\u4f7f\u7528\u76f8\u540c\u7684\u6a21\u5f0f\u3002<\/p>\n<p>\u2022  Define your binding model as a nested class. I define the InputModel as a nested class inside my Razor Page. The binding model is normally highly specific to that single page, so doing this keeps everything you\u2019re working on together. Additionally, I normally use that exact class name, InputModel, for all my pages. Again, this adds consistency to your Razor Pages.<br \/>\n\u5c06\u7ed1\u5b9a\u6a21\u578b\u5b9a\u4e49\u4e3a\u5d4c\u5957\u7c7b\u3002\u6211\u5c06 InputModel \u5b9a\u4e49\u4e3a Razor Page \u4e2d\u7684\u5d4c\u5957\u7c7b\u3002\u7ed1\u5b9a\u6a21\u578b\u901a\u5e38\u9ad8\u5ea6\u7279\u5b9a\u4e8e\u8be5\u5355\u4e2a\u9875\u9762\uff0c\u56e0\u6b64\u8fd9\u6837\u505a\u4f1a\u5c06\u60a8\u6b63\u5728\u5904\u7406\u7684\u6240\u6709\u5185\u5bb9\u653e\u5728\u4e00\u8d77\u3002\u6b64\u5916\uff0c\u6211\u901a\u5e38\u5bf9\u6211\u7684\u6240\u6709\u9875\u9762\u4f7f\u7528\u8be5\u786e\u5207\u7684\u7c7b\u540d InputModel\u3002\u540c\u6837\uff0c\u8fd9\u589e\u52a0\u4e86 Razor \u9875\u9762\u7684\u4e00\u81f4\u6027\u3002<\/p>\n<p>\u2022  Don\u2019t use [BindProperties]. In addition to the [BindProperty] attribute, there is a [BindProperties] attribute (note the different spelling) that can be applied to the Razor Page PageModel directly. This will cause all properties in your model to be model-bound, which can leave you open to overposting attacks if you\u2019re not careful. I suggest you don\u2019t use the [BindProperties] attribute and stick to binding a single property with [BindProperty] instead.<br \/>\n\u4e0d\u8981\u4f7f\u7528 [BindProperties]\u3002\u9664\u4e86 [BindProperty] \u5c5e\u6027\u4e4b\u5916\uff0c\u8fd8\u6709\u4e00\u4e2a [BindProperties] \u5c5e\u6027\uff08\u8bf7\u6ce8\u610f\u4e0d\u540c\u7684\u62fc\u5199\uff09\uff0c\u8be5\u5c5e\u6027\u53ef\u4ee5\u76f4\u63a5\u5e94\u7528\u4e8e Razor Page PageModel\u3002\u8fd9\u5c06\u5bfc\u81f4\u6a21\u578b\u4e2d\u7684\u6240\u6709\u5c5e\u6027\u90fd\u53d7\u6a21\u578b\u9650\u5236\uff0c\u5982\u679c\u60a8\u4e0d\u5c0f\u5fc3\uff0c\u53ef\u80fd\u4f1a\u4f7f\u60a8\u9762\u4e34\u8fc7\u5ea6\u53d1\u5e03\u653b\u51fb\u3002\u6211\u5efa\u8bae\u60a8\u4e0d\u8981\u4f7f\u7528 [BindProperties] \u5c5e\u6027\uff0c\u800c\u662f\u575a\u6301\u4f7f\u7528 [BindProperty] \u7ed1\u5b9a\u5355\u4e2a\u5c5e\u6027\u3002<\/p>\n<p>\u2022  Accept route parameters in the page handler. For simple route parameters, such as the id passed into the OnGet and OnPost handlers in listing 16.8, I add parameters to the page handler method itself. This avoids the clunky SupportsGet=true syntax for GET requests.<br \/>\n\u5728\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u4e2d\u63a5\u53d7\u8def\u7531\u53c2\u6570\u3002\u5bf9\u4e8e\u7b80\u5355\u7684\u8def\u7531\u53c2\u6570\uff0c\u4f8b\u5982\u5728\u6e05\u5355 16.8 \u4e2d\u4f20\u9012\u7ed9 OnGet \u548c OnPost \u5904\u7406\u7a0b\u5e8f\u7684 id\uff0c\u6211\u5c06\u53c2\u6570\u6dfb\u52a0\u5230\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u65b9\u6cd5\u672c\u8eab\u3002\u8fd9\u907f\u514d\u4e86 GET \u8bf7\u6c42\u7684\u7b28\u62d9 SupportsGet=true \u8bed\u6cd5\u3002<\/p>\n<p>\u2022  Always validate before using data. I said it before, so I\u2019ll say it again: validate user input!<br \/>\n\u4f7f\u7528\u6570\u636e\u4e4b\u524d\u59cb\u7ec8\u8fdb\u884c\u9a8c\u8bc1\u3002\u6211\u4e4b\u524d\u8bf4\u8fc7\uff0c\u6240\u4ee5\u6211\u518d\u8bf4\u4e00\u904d\uff1a\u9a8c\u8bc1\u7528\u6237\u8f93\u5165\uff01<\/p>\n<p>That concludes this look at model binding in Razor Pages. 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 most important aspect of this chapter is the focus on validation. This is a common concern for all web applications, and the use of DataAnnotations can make it easy to add validation to your models.<br \/>\nRazor Pages \u4e2d\u7684\u6a21\u578b\u7ed1\u5b9a\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\u672c\u7ae0\u6700\u91cd\u8981\u7684\u65b9\u9762\u662f\u5173\u6ce8\u9a8c\u8bc1\u3002\u8fd9\u662f\u6240\u6709 Web \u5e94\u7528\u7a0b\u5e8f\u7684\u5171\u540c\u5173\u6ce8\u70b9\uff0c\u4f7f\u7528 DataAnnotations \u53ef\u4ee5\u8f7b\u677e\u5730\u5411\u6a21\u578b\u6dfb\u52a0\u9a8c\u8bc1\u3002<\/p>\n<p>In the next chapter we\u2019ll continue our journey through Razor Pages by looking at how to create views. In particular, you\u2019ll learn how to generate HTML in response to a request using the Razor templating engine.<br \/>\n\u5728\u4e0b\u4e00\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u901a\u8fc7\u4e86\u89e3\u5982\u4f55\u521b\u5efa\u89c6\u56fe\u6765\u7ee7\u7eed\u6d4f\u89c8 Razor \u9875\u9762\u3002\u5177\u4f53\u800c\u8a00\uff0c\u60a8\u5c06\u5b66\u4e60\u5982\u4f55\u4f7f\u7528 Razor \u6a21\u677f\u5f15\u64ce\u751f\u6210 HTML \u4ee5\u54cd\u5e94\u8bf7\u6c42\u3002<\/p>\n<h2>16.5 Summary<\/h2>\n<p>16.5 \u603b\u7ed3<\/p>\n<p>Razor Pages uses three distinct models, each responsible for a different aspect of a request. The binding model encapsulates data sent as part of a request. The application model represents the state of the application. The PageModel is the backing class for the Razor Page, and it exposes the data used by the Razor view to generate a response.<br \/>\nRazor Pages \u4f7f\u7528\u4e09\u79cd\u4e0d\u540c\u7684\u6a21\u578b\uff0c\u6bcf\u79cd\u6a21\u578b\u8d1f\u8d23\u8bf7\u6c42\u7684\u4e0d\u540c\u65b9\u9762\u3002\u7ed1\u5b9a\u6a21\u578b\u5c01\u88c5\u4f5c\u4e3a\u8bf7\u6c42\u7684\u4e00\u90e8\u5206\u53d1\u9001\u7684\u6570\u636e\u3002\u5e94\u7528\u7a0b\u5e8f\u6a21\u578b\u8868\u793a\u5e94\u7528\u7a0b\u5e8f\u7684\u72b6\u6001\u3002PageModel \u662f Razor Page \u7684\u652f\u6301\u7c7b\uff0c\u5b83\u516c\u5f00 Razor \u89c6\u56fe\u7528\u4e8e\u751f\u6210\u54cd\u5e94\u7684\u6570\u636e\u3002<\/p>\n<p>Model binding extracts values from a request and uses them to create .NET objects the page handler can use when they execute. Any properties on the PageModel marked with the [BindProperty] attribute and method parameters of the page handlers will take part in model binding.<br \/>\n\u6a21\u578b\u7ed1\u5b9a\u4ece\u8bf7\u6c42\u4e2d\u63d0\u53d6\u503c\uff0c\u5e76\u4f7f\u7528\u5b83\u4eec\u521b\u5efa\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u5728\u6267\u884c\u65f6\u53ef\u4ee5\u4f7f\u7528\u7684 .NET \u5bf9\u8c61\u3002PageModel \u4e0a\u6807\u6709 [BindProperty] \u5c5e\u6027\u548c\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u7684\u65b9\u6cd5\u53c2\u6570\u7684\u4efb\u4f55\u5c5e\u6027\u90fd\u5c06\u53c2\u4e0e\u6a21\u578b\u7ed1\u5b9a\u3002<\/p>\n<p>By default, there are three binding sources for Razor Pages: POSTed form values, route values, and the query string. The binder will interrogate these sources in order when trying to bind your binding models.<br \/>\n\u9ed8\u8ba4\u60c5\u51b5\u4e0b\uff0cRazor Pages \u6709\u4e09\u4e2a\u7ed1\u5b9a\u6e90\uff1aPOST \u8868\u5355\u503c\u3001\u8def\u7531\u503c\u548c\u67e5\u8be2\u5b57\u7b26\u4e32\u3002Binder \u5c06\u5728\u5c1d\u8bd5\u7ed1\u5b9a Binding Models \u65f6\u6309\u987a\u5e8f\u8be2\u95ee\u8fd9\u4e9b\u6e90\u3002<\/p>\n<p>When binding values to models, the names of the parameters and properties aren\u2019t case-sensitive.<br \/>\n\u5c06\u503c\u7ed1\u5b9a\u5230\u6a21\u578b\u65f6\uff0c\u53c2\u6570\u548c\u5c5e\u6027\u7684\u540d\u79f0\u4e0d\u533a\u5206\u5927\u5c0f\u5199\u3002<\/p>\n<p>You can bind to simple types or to the properties of complex types. Simple types must be convertible from strings to be bound automatically, such as numbers, dates, Boolean values, and custom types with a TryParse method.<br \/>\n\u53ef\u4ee5\u7ed1\u5b9a\u5230\u7b80\u5355\u7c7b\u578b\u6216\u590d\u6742\u7c7b\u578b\u7684\u5c5e\u6027\u3002\u7b80\u5355\u7c7b\u578b\u5fc5\u987b\u53ef\u4ece\u5b57\u7b26\u4e32\u8f6c\u6362\u800c\u6765\uff0c\u4ee5\u4fbf\u4f7f\u7528 TryParse \u65b9\u6cd5\u81ea\u52a8\u7ed1\u5b9a\uff0c\u4f8b\u5982\u6570\u5b57\u3001\u65e5\u671f\u3001\u5e03\u5c14\u503c\u548c\u81ea\u5b9a\u4e49\u7c7b\u578b\u3002<\/p>\n<p>To bind complex types, the types must have a default constructor and public, settable properties. The Razor Pages model binder binds each property of a complex type using values from the binding sources.<br \/>\n\u82e5\u8981\u7ed1\u5b9a\u590d\u6742\u7c7b\u578b\uff0c\u7c7b\u578b\u5fc5\u987b\u5177\u6709\u9ed8\u8ba4\u6784\u9020\u51fd\u6570\u548c\u516c\u5171\u7684\u53ef\u8bbe\u7f6e\u5c5e\u6027\u3002Razor Pages \u6a21\u578b\u7ed1\u5b9a\u5668\u4f7f\u7528\u7ed1\u5b9a\u6e90\u4e2d\u7684\u503c\u7ed1\u5b9a\u590d\u6742\u7c7b\u578b\u7684\u6bcf\u4e2a\u5c5e\u6027\u3002<\/p>\n<p>You can bind collections and dictionaries using the [index]=value and [key] =value syntax, respectively.<br \/>\n\u60a8\u53ef\u4ee5\u5206\u522b\u4f7f\u7528 [index]=value \u548c [key] =value \u8bed\u6cd5\u7ed1\u5b9a\u96c6\u5408\u548c\u5b57\u5178\u3002<\/p>\n<p>You can customize the binding source for a binding model using [From<em>] attributes applied to the method, such as [FromHeader] and [FromBody]. These can be used to bind to nondefault binding sources, such as headers or JSON body content. The [FromBody] attribute is always required when binding to a JSON body.<br \/>\n\u60a8\u53ef\u4ee5\u4f7f\u7528\u5e94\u7528\u4e8e\u65b9\u6cd5\u7684 [From<\/em>] \u5c5e\u6027\uff08\u5982 [FromHeader] \u548c [FromBody]\uff09\u81ea\u5b9a\u4e49\u7ed1\u5b9a\u6a21\u578b\u7684\u7ed1\u5b9a\u6e90\u3002\u8fd9\u4e9b\u53ef\u7528\u4e8e\u7ed1\u5b9a\u5230\u975e\u9ed8\u8ba4\u7ed1\u5b9a\u6e90\uff0c\u4f8b\u5982\u6807\u5934\u6216 JSON \u6b63\u6587\u5185\u5bb9\u3002\u7ed1\u5b9a\u5230 JSON \u6b63\u6587\u65f6\uff0c\u59cb\u7ec8\u9700\u8981 [FromBody] \u5c5e\u6027\u3002<\/p>\n<p>Validation is necessary to check for security threats. Check that data is formatted correctly and confirm that it conforms to expected values and 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\u5e76\u786e\u8ba4\u5b83\u7b26\u5408\u9884\u671f\u503c\u4ee5\u53ca\u662f\u5426\u7b26\u5408\u60a8\u7684\u4e1a\u52a1\u89c4\u5219\u3002<\/p>\n<p>Validation in Razor Pages occurs automatically after model binding, but you must manually check the result of the validation and act accordingly in your page handler by interrogating the ModelState.IsValid property.<br \/>\nRazor Pages \u4e2d\u7684\u9a8c\u8bc1\u5728\u6a21\u578b\u7ed1\u5b9a\u540e\u81ea\u52a8\u8fdb\u884c\uff0c\u4f46\u60a8\u5fc5\u987b\u624b\u52a8\u68c0\u67e5\u9a8c\u8bc1\u7ed3\u679c\uff0c\u5e76\u901a\u8fc7\u8be2\u95ee ModelState.IsValid \u5c5e\u6027\u5728\u9875\u9762\u5904\u7406\u7a0b\u5e8f\u4e2d\u91c7\u53d6\u76f8\u5e94\u63aa\u65bd\u3002<\/p>\n<p>Client-side validation provides a better user experience than server-side validation alone, but you should always use server-side validation. Client-side validation typically uses JavaScript and attributes applied to your HTML elements to validate form values.<br \/>\n\u4e0e\u5355\u72ec\u7684\u670d\u52a1\u5668\u7aef\u9a8c\u8bc1\u76f8\u6bd4\uff0c\u5ba2\u6237\u7aef\u9a8c\u8bc1\u63d0\u4f9b\u4e86\u66f4\u597d\u7684\u7528\u6237\u4f53\u9a8c\uff0c\u4f46\u60a8\u5e94\u8be5\u59cb\u7ec8\u4f7f\u7528\u670d\u52a1\u5668\u7aef\u9a8c\u8bc1\u3002\u5ba2\u6237\u7aef\u9a8c\u8bc1\u901a\u5e38\u4f7f\u7528\u5e94\u7528\u4e8e HTML \u5143\u7d20\u7684 JavaScript \u548c\u5c5e\u6027\u6765\u9a8c\u8bc1\u8868\u5355\u503c\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>16 Binding and validating requests with Razor Pages 16 \u4f7f\u7528 Razor Pages \u7ed1\u5b9a\u548c\u9a8c\u8bc1\u8bf7\u6c42 This chapter covers \u672c\u7ae0\u6db5\u76d6 \u2022 Using request values to create binding models \u4f7f\u7528\u8bf7\u6c42\u503c\u521b\u5efa\u7ed1\u5b9a\u6a21\u578b \u2022 Customizing the model-binding process \u81ea\u5b9a\u4e49\u6a21\u578b\u7ed1\u5b9a\u8fc7\u7a0b \u2022 Validating user input using DataAnnotations attributes \u4f7f\u7528 DataAnnotations \u5c5e\u6027\u9a8c\u8bc1\u7528\u6237\u8f93\u5165 In chapter 7 we looked at the process of model binding and validation [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[3],"tags":[],"class_list":["post-603","post","type-post","status-publish","format-standard","hentry","category-csharp"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/603","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=603"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/603\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=603"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=603"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=603"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}