{"id":249,"date":"2023-10-15T16:11:07","date_gmt":"2023-10-15T08:11:07","guid":{"rendered":"https:\/\/miie.net\/?p=249"},"modified":"2023-10-15T16:11:07","modified_gmt":"2023-10-15T08:11:07","slug":"ultimate-asp-net-core-web-api-chapter12-validation","status":"publish","type":"post","link":"https:\/\/diji.net\/?p=249","title":{"rendered":"Chapter13:VALIDATION"},"content":{"rendered":"<h1>13 VALIDATION<\/h1>\n<p>While writing API actions, we have a set of rules that we need to check. If we take a look at the Company class, we can see different data annotation attributes above our properties:<br \/>\n\u5728\u7f16\u5199 API \u64cd\u4f5c\u65f6\uff0c\u6211\u4eec\u9700\u8981\u68c0\u67e5\u4e00\u7ec4\u89c4\u5219\u3002\u5982\u679c\u6211\u4eec\u770b\u4e00\u4e0b Company \u7c7b\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u5c5e\u6027\u4e0a\u65b9\u770b\u5230\u4e0d\u540c\u7684\u6570\u636e\u6ce8\u91ca\u5c5e\u6027\uff1a<\/p>\n<\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_1301.jpg\" alt=\"Alt text\" \/><\/p>\n<p>Those attributes serve the purpose to validate our model object while creating or updating resources in the database. But we are not making use of them yet.<br \/>\n\u8fd9\u4e9b\u5c5e\u6027\u7528\u4e8e\u5728\u6570\u636e\u5e93\u4e2d\u521b\u5efa\u6216\u66f4\u65b0\u8d44\u6e90\u65f6\u9a8c\u8bc1\u6a21\u578b\u5bf9\u8c61\u3002\u4f46\u6211\u4eec\u8fd8\u6ca1\u6709\u5229\u7528\u5b83\u4eec\u3002<\/p>\n<p>In this chapter, we are going to show you how to validate our model objects and how to return an appropriate response to the client if the model is not valid. So, we need to validate the input and not the output of our controller actions. This means that we are going to apply this validation to the POST, PUT, and PATCH requests, but not for the GET request.<br \/>\n\u5728\u672c\u7ae0\u4e2d\uff0c\u6211\u4eec\u5c06\u5411\u60a8\u5c55\u793a\u5982\u4f55\u9a8c\u8bc1\u6211\u4eec\u7684\u6a21\u578b\u5bf9\u8c61\uff0c\u4ee5\u53ca\u5982\u4f55\u5728\u6a21\u578b\u65e0\u6548\u65f6\u5411\u5ba2\u6237\u7aef\u8fd4\u56de\u9002\u5f53\u7684\u54cd\u5e94\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u9700\u8981\u9a8c\u8bc1\u63a7\u5236\u5668\u64cd\u4f5c\u7684\u8f93\u5165\uff0c\u800c\u4e0d\u662f\u8f93\u51fa\u3002\u8fd9\u610f\u5473\u7740\u6211\u4eec\u5c06\u628a\u6b64\u9a8c\u8bc1\u5e94\u7528\u4e8e POST\u3001PUT \u548c PATCH \u8bf7\u6c42\uff0c\u4f46\u4e0d\u5e94\u7528\u4e8e GET \u8bf7\u6c42\u3002<\/p>\n<p>To validate against validation rules applied by Data Annotation attributes, we are going to use the concept of ModelState. It is a dictionary containing the state of the model and model binding validation.<br \/>\n\u4e3a\u4e86\u6839\u636e\u6570\u636e\u6ce8\u91ca\u5c5e\u6027\u5e94\u7528\u7684\u9a8c\u8bc1\u89c4\u5219\u8fdb\u884c\u9a8c\u8bc1\uff0c\u6211\u4eec\u5c06\u4f7f\u7528 ModelState \u7684\u6982\u5ff5\u3002\u5b83\u662f\u4e00\u4e2a\u5b57\u5178\uff0c\u5305\u542b\u6a21\u578b\u7684\u72b6\u6001\u548c\u6a21\u578b\u7ed1\u5b9a\u9a8c\u8bc1\u3002<\/p>\n<p>Once we send our request, the rules defined by Data Annotation attributes are checked. If one of the rules doesn\u2019t check out, the appropriate error message will be returned. We are going to use the ModelState.IsValid expression to check for those validation rules.<br \/>\n\u53d1\u9001\u8bf7\u6c42\u540e\uff0c\u5c06\u68c0\u67e5\u6570\u636e\u6ce8\u91ca\u5c5e\u6027\u5b9a\u4e49\u7684\u89c4\u5219\u3002\u5982\u679c\u5176\u4e2d\u4e00\u4e2a\u89c4\u5219\u672a\u7b7e\u51fa\uff0c\u5219\u5c06\u8fd4\u56de\u76f8\u5e94\u7684\u9519\u8bef\u6d88\u606f\u3002\u6211\u4eec\u5c06\u4f7f\u7528ModelState.Is\u7528\u4e8e\u68c0\u67e5\u8fd9\u4e9b\u9a8c\u8bc1\u89c4\u5219\u7684\u6709\u6548\u8868\u8fbe\u5f0f\u3002<\/p>\n<p>Finally, the response status code, when validation fails, should be 422 Unprocessable Entity. That means that the server understood the content type of the request and the syntax of the request entity is correct, but it was unable to process validation rules applied on the entity inside the request body.<br \/>\n\u6700\u540e\uff0c\u9a8c\u8bc1\u5931\u8d25\u65f6\u7684\u54cd\u5e94\u72b6\u6001\u4ee3\u7801\u5e94\u4e3a 422 \u65e0\u6cd5\u5904\u7406\u7684\u5b9e\u4f53\u3002\u8fd9\u610f\u5473\u7740\u670d\u52a1\u5668\u7406\u89e3\u8bf7\u6c42\u7684\u5185\u5bb9\u7c7b\u578b\u548c\u8bf7\u6c42\u5b9e\u4f53\u7684\u8bed\u6cd5\u662f\u6b63\u786e\u7684\uff0c\u4f46\u5b83\u65e0\u6cd5\u5904\u7406\u5e94\u7528\u4e8e\u8bf7\u6c42\u6b63\u6587\u5185\u5b9e\u4f53\u7684\u9a8c\u8bc1\u89c4\u5219\u3002<\/p>\n<p>So, with all this in mind, we are ready to implement model validation in our code.<br \/>\n\u56e0\u6b64\uff0c\u8003\u8651\u5230\u6240\u6709\u8fd9\u4e9b\uff0c\u6211\u4eec\u5df2\u51c6\u5907\u597d\u5728\u4ee3\u7801\u4e2d\u5b9e\u73b0\u6a21\u578b\u9a8c\u8bc1\u3002<\/p>\n<h2>13.1 Validation while Creating Resource<\/h2>\n<p>Let\u2019s send another request for the CreateEmployee action, but this time with the invalid request body:<br \/>\n\u8ba9\u6211\u4eec\u4e3a CreateEmployee \u64cd\u4f5c\u53d1\u9001\u53e6\u4e00\u4e2a\u8bf7\u6c42\uff0c\u4f46\u8fd9\u6b21\u4f7f\u7528\u65e0\u6548\u7684\u8bf7\u6c42\u6b63\u6587\uff1a<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n{\n    &quot;name&quot;:null,\n    &quot;age&quot;:29,\n    &quot;position&quot;:null\n}\n<\/pre>\n<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\">https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees<\/a><\/a><\/a><\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_1302.jpg\" alt=\"Alt text\" \/><\/p>\n<p>And we get the 500 Internal Server Error, which is a generic message when something unhandled happens in our code. But this is not good. This means that the server made an error, which is not the case. In this case, we, as a consumer, sent a wrong model to the API \u2014 thus the error message should be different.<br \/>\n\u6211\u4eec\u5f97\u5230 500 \u5185\u90e8\u670d\u52a1\u5668\u9519\u8bef(VisualStudio\u63d0\u793a\u5f02\u5e38\uff0c\u7136\u540ePostman\u6536\u5230500\u9519\u8bef)\uff0c\u8fd9\u662f\u4ee3\u7801\u4e2d\u53d1\u751f\u672a\u5904\u7406\u67d0\u4e9b\u4e8b\u60c5\u65f6\u7684\u901a\u7528\u6d88\u606f\u3002\u4f46\u8fd9\u5e76\u4e0d\u597d\u3002\u8fd9\u610f\u5473\u7740\u670d\u52a1\u5668\u72af\u4e86\u4e00\u4e2a\u9519\u8bef\uff0c\u4f46\u4e8b\u5b9e\u5e76\u975e\u5982\u6b64\u3002\u5728\u5728\u8fd9\u79cd\u60c5\u51b5\u4e0b\uff0c\u4f5c\u4e3a\u6d88\u8d39\u8005\uff0c\u6211\u4eec\u5411 API \u53d1\u9001\u4e86\u4e00\u4e2a\u9519\u8bef\u7684\u6a21\u578b\u2014\u2014\u56e0\u6b64\u9519\u8bef\u6d88\u606f\u5e94\u8be5\u6709\u6240\u4e0d\u540c\u3002<\/p>\n<p>In order to fix this, let\u2019s modify our EmployeeForCreationDto class because that\u2019s what we deserialize the request body to:<br \/>\n\u4e3a\u4e86\u89e3\u51b3\u8fd9\u4e2a\u95ee\u9898\uff0c\u8ba9\u6211\u4eec\u4fee\u6539\u6211\u4eec\u7684 EmployeeForCreationDto \u7c7b\uff0c\u56e0\u4e3a\u8fd9\u5c31\u662f\u6211\u4eec\u5c06\u8bf7\u6c42\u6b63\u6587\u53cd\u5e8f\u5217\u5316\u4e3a\u7684\u5185\u5bb9\uff1a<\/p>\n<p><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic class EmployeeForCreationDto\n{\n    &#x5B;Required(ErrorMessage = &quot;Employee name is a required field.&quot;)]\n    &#x5B;MaxLength(30, ErrorMessage = &quot;Maximum length for the Name is 30 characters.&quot;)]\n    public string Name { get; set; }\n\n    &#x5B;Required(ErrorMessage = &quot;Age is a required field.&quot;)]\n    public int Age { get; set; }\n\n    &#x5B;Required(ErrorMessage = &quot;Position is a required field.&quot;)]\n    &#x5B;MaxLength(20, ErrorMessage = &quot;Maximum length for the Position is 20 characters.&quot;)]\n    public string Position { get; set; }\n}\n<\/pre>\n<\/p>\n<p>Once we have the rules applied, we can send the same request again:<br \/>\n\u5e94\u7528\u89c4\u5219\u540e\uff0c\u6211\u4eec\u53ef\u4ee5\u518d\u6b21\u53d1\u9001\u76f8\u540c\u7684\u8bf7\u6c42\uff1a<br \/>\n<a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\">https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees<\/a><\/a><\/a><\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_1303.jpg\" alt=\"Alt text\" \/><\/p>\n<p>You can see that our validation rules have been applied and verified as well. ASP.NET Core validates the model object as soon as the request gets to the action.<br \/>\n\u60a8\u53ef\u4ee5\u770b\u5230\u6211\u4eec\u7684\u9a8c\u8bc1\u89c4\u5219\u4e5f\u5df2\u5e94\u7528\u548c\u9a8c\u8bc1\u3002ASP.NET Core \u4f1a\u5728\u8bf7\u6c42\u5230\u8fbe\u64cd\u4f5c\u540e\u7acb\u5373\u9a8c\u8bc1\u6a21\u578b\u5bf9\u8c61\u3002<\/p>\n<p>But the status code for this response is 400 Bad Request. That is acceptable, but as we said, there is a status code that better fits this kind of situation. It is a 422 Unprocessable Entity.<br \/>\n\u4f46\u6b64\u54cd\u5e94\u7684\u72b6\u6001\u4ee3\u7801\u4e3a 400 \u9519\u8bef\u8bf7\u6c42\u3002\u8fd9\u662f\u53ef\u4ee5\u63a5\u53d7\u7684\uff0c\u4f46\u6b63\u5982\u6211\u4eec\u6240\u8bf4\uff0c\u6709\u4e00\u4e2a\u72b6\u6001\u4ee3\u7801\u66f4\u9002\u5408\u8fd9\u79cd\u60c5\u51b5\u3002\u5b83\u662f\u4e00\u4e2a 422 \u65e0\u6cd5\u5904\u7406\u7684\u5b9e\u4f53\u3002<\/p>\n<p>To return 422 instead of 400, the first thing we have to do is to suppress the BadRequest error when the ModelState is invalid. We are going to do that by adding this code into the Startup class in the ConfigureServices method:<br \/>\n\u8981\u8fd4\u56de 422 \u800c\u4e0d\u662f 400\uff0c\u6211\u4eec\u8981\u505a\u7684\u7b2c\u4e00\u4ef6\u4e8b\u662f\u5728 ModelState \u65e0\u6548\u65f6\u6291\u5236 BadRequest \u9519\u8bef\u3002\u6211\u4eec\u5c06\u901a\u8fc7\u5c06\u4ee5\u4e0b\u4ee3\u7801\u6dfb\u52a0\u5230 ConfigureServices \u65b9\u6cd5\u4e2d\u7684 Startup \u7c7b\u4e2d\u6765\u505a\u5230\u8fd9\u4e00\u70b9\uff1a<\/p>\n<p><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/ Startup.cs\n\nservices.Configure&lt;ApiBehaviorOptions&gt;(options =&gt;\n{\n    options.SuppressModelStateInvalidFilter = true;\n});\n<\/pre>\n<\/p>\n<p>With this, we are suppressing a default model state validation that is implemented due to the existence of the [ApiController] attribute in all API controllers. Before our request reaches the action, it is validated with the [ApiController] attribute. So this means that we can solve the same problem differently, by commenting out or removing the [ApiController] attribute only, without additional code for suppressing validation. It's all up to you.<br \/>\n\u8fd9\u6837\uff0c\u6211\u4eec\u5c06\u7981\u6b62\u663e\u793a\u7531\u4e8e\u6240\u6709 API \u63a7\u5236\u5668\u4e2d\u5b58\u5728 [ApiController] \u5c5e\u6027\u800c\u5b9e\u73b0\u7684\u9ed8\u8ba4\u6a21\u578b\u72b6\u6001\u9a8c\u8bc1\u3002\u5728\u6211\u4eec\u7684\u8bf7\u6c42\u5230\u8fbe\u64cd\u4f5c\u4e4b\u524d\uff0c\u5c06\u4f7f\u7528 [ApiController] \u5c5e\u6027\u5bf9\u5176\u8fdb\u884c\u9a8c\u8bc1\u3002\u56e0\u6b64\uff0c\u8fd9\u610f\u5473\u7740\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u4ec5\u6ce8\u91ca\u6389\u6216\u5220\u9664 [ApiController] \u5c5e\u6027\u6765\u4ee5\u4e0d\u540c\u7684\u65b9\u5f0f\u89e3\u51b3\u76f8\u540c\u7684\u95ee\u9898\uff0c\u800c\u65e0\u9700\u989d\u5916\u7684\u4ee3\u7801\u6765\u6291\u5236\u9a8c\u8bc1\u3002\u8fd9\u4e00\u5207\u90fd\u53d6\u51b3\u4e8e\u4f60\u3002<\/p>\n<p>Then, we have to modify our action:<br \/>\n\u7136\u540e\uff0c\u6211\u4eec\u5fc5\u987b\u4fee\u6539\u6211\u4eec\u7684\u63a7\u5236\u5668\uff1a<\/p>\n<p><pre class=\"brush: csharp; highlight: [10,11,12,13,14]; title: ; notranslate\" title=\"\">\n\n&#x5B;HttpPost]\npublic IActionResult CreateEmployeeForCompany(Guid companyId, &#x5B;FromBody] EmployeeForCreationDto employee)\n{\n    if (employee == null)\n    {\n        _logger.LogError(&quot;EmployeeForCreationDto object sent from client is null.&quot;);\n        return BadRequest(&quot;EmployeeForCreationDto object is null&quot;);\n    }\n    if (!ModelState.IsValid)\n    {\n        _logger.LogError(&quot;Invalid model state for the EmployeeForCreationDto object&quot;);\n        return UnprocessableEntity(ModelState);\n    }\n\n    var company = _repository.Company.GetCompany(companyId, trackChanges: false);\n    if (company == null)\n    {\n        _logger.LogInfo($&quot;Company with id: {companyId} doesn&#039;t exist in the database.&quot;);\n        return NotFound();\n    }\n    var employeeEntity = _mapper.Map&lt;Employee&gt;(employee);\n\n    _repository.Employee.CreateEmployeeForCompany(companyId, employeeEntity);\n    _repository.Save();\n    var employeeToReturn = _mapper.Map&lt;EmployeeDto&gt;(employeeEntity);\n\n    return CreatedAtRoute(&quot;GetEmployeeForCompany&quot;, new { companyId, id = employeeToReturn.Id }, employeeToReturn);\n}\n``\n<\/pre>\n<\/p>\n<p>And that is all.<br \/>\n\u4ec5\u6b64\u800c\u5df2\u3002<\/p>\n<p>Let\u2019s send our request one more time:<br \/>\n\u8ba9\u6211\u4eec\u518d\u53d1\u9001\u4e00\u6b21\u8bf7\u6c42\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\">https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees<\/a><\/a><\/a><\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_1304.jpg\" alt=\"Alt text\" \/><\/p>\n<p>Let\u2019s send an additional request to test the max length rule:<br \/>\n\u8ba9\u6211\u4eec\u53d1\u9001\u4e00\u4e2a\u989d\u5916\u7684\u8bf7\u6c42\u6765\u6d4b\u8bd5\u6700\u5927\u957f\u5ea6\u89c4\u5219\uff1a<\/p>\n<p><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n{\n    &quot;name&quot;:&quot;Michael Patel&quot;,\n    &quot;age&quot;:29,\n    &quot;position&quot;:&quot;Some position with invalid length&quot;\n}\n<\/pre>\n<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\">https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees<\/a><\/a><\/a><\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_1305.jpg\" alt=\"Alt text\" \/><\/p>\n<p>Excellent. It is working as expected.<br \/>\n\u975e\u5e38\u597d\u3002\u5b83\u6b63\u5728\u6309\u9884\u671f\u5de5\u4f5c\u3002<\/p>\n<p>The same actions can be applied for the CreateCompany action and CompanyForCreationDto class \u2014 and if you check the source code for this chapter, you will find it implemented.<br \/>\n\u76f8\u540c\u7684\u64cd\u4f5c\u53ef\u4ee5\u5e94\u7528\u4e8e CreateCompany \u64cd\u4f5c\u548c CompanyForCreationDto \u7c7b \u2014 \u5982\u679c\u60a8\u67e5\u770b\u672c\u7ae0\u7684\u6e90\u4ee3\u7801\uff0c\u60a8\u4f1a\u53d1\u73b0\u5b83\u5df2\u5b9e\u73b0\u3002<\/p>\n<h3>13.1.1 Validating Int Type<\/h3>\n<p>Let\u2019s create one more request with the request body without the age property:<br \/>\n\u8ba9\u6211\u4eec\u518d\u521b\u5efa\u4e00\u4e2a\u8bf7\u6c42\uff0c\u8bf7\u6c42\u6b63\u6587\u6ca1\u6709\u5e74\u9f84\u5c5e\u6027\uff1a<\/p>\n<pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n{\n    &quot;name&quot;:null,\n    &quot;position&quot;:&quot;Some position with invalid length&quot;\n}\n<\/pre>\n<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\">https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees<\/a><\/a><\/a><\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_1306.jpg\" alt=\"Alt text\" \/><\/p>\n<p>We can clearly see that the age property hasn\u2019t been sent, but in the response body, we don\u2019t see the error message for the age property next to other error messages. That is because the age is of type int and if we don\u2019t send that property, it would be set to a default value, which is 0.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u6e05\u695a\u5730\u770b\u5230 age \u5c5e\u6027\u5c1a\u672a\u53d1\u9001\uff0c\u4f46\u5728\u54cd\u5e94\u6b63\u6587\u4e2d\uff0c\u6211\u4eec\u5728\u5176\u4ed6\u9519\u8bef\u6d88\u606f\u65c1\u8fb9\u770b\u4e0d\u5230 age \u5c5e\u6027\u7684\u9519\u8bef\u6d88\u606f\u3002\u8fd9\u662f\u56e0\u4e3a\u5e74\u9f84\u662f int \u7c7b\u578b\uff0c\u5982\u679c\u6211\u4eec\u4e0d\u53d1\u9001\u8be5\u5c5e\u6027\uff0c\u5b83\u5c06\u88ab\u8bbe\u7f6e\u4e3a\u9ed8\u8ba4\u503c\uff0c\u5373 0\u3002<\/p>\n<p>So, on the server-side, validation for the Age property will pass, because it is not null.<br \/>\n\u56e0\u6b64\uff0c\u5728\u670d\u52a1\u5668\u7aef\uff0cAge \u5c5e\u6027\u7684\u9a8c\u8bc1\u5c06\u901a\u8fc7\uff0c\u56e0\u4e3a\u5b83\u4e0d\u4e3a null\u3002<\/p>\n<p>To prevent this type of behavior, we have to modify the data annotation attribute on top of the Age property in the EmployeeForCreationDto class:<br \/>\n\u4e3a\u4e86\u9632\u6b62\u8fd9\u79cd\u7c7b\u578b\u7684\u884c\u4e3a\uff0c\u6211\u4eec\u5fc5\u987b\u4fee\u6539 EmployeeForCreationDto \u7c7b\u4e2d Age \u5c5e\u6027\u4e4b\u4e0a\u7684\u6570\u636e\u6ce8\u91ca\u5c5e\u6027\uff1a<\/p>\n<p><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n&#x5B;Range(18, int.MaxValue, ErrorMessage = &quot;Age is required and it can&#039;t be lower than 18&quot;)] \npublic int Age { get; set; }\n<\/pre>\n<\/p>\n<p>Now, let\u2019s try to send the same request one more time:<br \/>\n\u73b0\u5728\uff0c\u8ba9\u6211\u4eec\u5c1d\u8bd5\u518d\u6b21\u53d1\u9001\u76f8\u540c\u7684\u8bf7\u6c42\uff1a<br \/>\n<a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\">https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees<\/a><\/a><\/a><\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_1307.jpg\" alt=\"Alt text\" \/><\/p>\n<p>Now, we have the Age error message in our response.<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u7684\u54cd\u5e94\u4e2d\u6709 Age \u9519\u8bef\u6d88\u606f\u3002<\/p>\n<p>If we want, we can add the custom error messages in our action:<br \/>\n\u5982\u679c\u9700\u8981\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u64cd\u4f5c\u4e2d\u6dfb\u52a0\u81ea\u5b9a\u4e49\u9519\u8bef\u6d88\u606f\uff1a<\/p>\n<p><strong>ModelState.AddModelError(string key, string errorMessage)<\/strong><\/p>\n<p>With this expression, the additional error message will be included with all the other messages.<br \/>\n\u4f7f\u7528\u6b64\u8868\u8fbe\u5f0f\uff0c\u5176\u4ed6\u9519\u8bef\u6d88\u606f\u5c06\u5305\u542b\u5728\u6240\u6709\u5176\u4ed6\u6d88\u606f\u4e2d\u3002<\/p>\n<h2>13.2 Validation for PUT Requests<\/h2>\n<p>The validation for PUT requests shouldn\u2019t be different from POST requests (except in some cases), but there are still things we have to do to at least optimize our code.<br \/>\nPUT\u8bf7\u6c42\u7684\u9a8c\u8bc1\u4e0d\u5e94\u8be5\u4e0ePOST\u8bf7\u6c42\u4e0d\u540c\uff08\u5728\u67d0\u4e9b\u60c5\u51b5\u4e0b\u9664\u5916\uff09\uff0c\u4f46\u6211\u4eec\u4ecd\u7136\u9700\u8981\u505a\u4e00\u4e9b\u4e8b\u60c5\u6765\u81f3\u5c11\u4f18\u5316\u6211\u4eec\u7684\u4ee3\u7801\u3002<\/p>\n<p>But let\u2019s go step by step.<br \/>\n\u4f46\u662f\uff0c\u8ba9\u6211\u4eec\u4e00\u6b65\u4e00\u6b65\u6765<\/p>\n<p>First, let\u2019s add Data Annotation Attributes to the EmployeeForUpdateDto class:<br \/>\n\u9996\u5148\uff0c\u8ba9\u6211\u4eec\u5c06\u6570\u636e\u6ce8\u91ca\u5c5e\u6027\u6dfb\u52a0\u5230 EmployeeForUpdateDto class\uff1a<\/p>\n<p><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\n\/\/EmployeeForUpdateDto.cs\n\nusing System.ComponentModel.DataAnnotations;\n\nnamespace Entities.DataTransferObjects\n{\n    public class EmployeeForUpdateDto\n    {\n        &#x5B;Required(ErrorMessage = &quot;Employee name is a required field.&quot;)]\n        &#x5B;MaxLength(30, ErrorMessage = &quot;Maximum length for the Name is 30 characters.&quot;)]\n        public string Name { get; set; }\n\n        &#x5B;Range(18, int.MaxValue, ErrorMessage = &quot;Age is required and it can&#039;t be lower than 18&quot;)]\n        public int Age { get; set; }\n\n        &#x5B;Required(ErrorMessage = &quot;Position is a required field.&quot;)]\n        &#x5B;MaxLength(20, ErrorMessage = &quot;Maximum length for the Position is 20 characters.&quot;)]\n        public string Position { get; set; }\n    }\n}\n\n<\/pre>\n<\/p>\n<p>Once we have done this, we realize we have a small problem. If we compare this class with the DTO class for creation, we are going to see that they are the same. Of course, we don\u2019t want to repeat ourselves, thus we are going to add some modifications.<br \/>\n\u4e00\u65e6\u6211\u4eec\u8fd9\u6837\u505a\u4e86\uff0c\u6211\u4eec\u5c31\u4f1a\u610f\u8bc6\u5230\u6211\u4eec\u6709\u4e00\u4e2a\u5c0f\u95ee\u9898\u3002\u5982\u679c\u6211\u4eec\u5c06\u6b64\u7c7b\u4e0e\u7528\u4e8e\u521b\u5efa\u7684 DTO \u7c7b\u8fdb\u884c\u6bd4\u8f83\uff0c\u6211\u4eec\u5c06\u770b\u5230\u5b83\u4eec\u662f\u76f8\u540c\u7684\u3002\u5f53\u7136\uff0c\u6211\u4eec\u4e0d\u60f3\u91cd\u590d\u81ea\u5df1\uff0c\u56e0\u6b64\u6211\u4eec\u5c06\u6dfb\u52a0\u4e00\u4e9b\u4fee\u6539\u3002<\/p>\n<p>Let\u2019s create a new class in the DataTransferObjects folder:<br \/>\n\u8ba9\u6211\u4eec\u5728 DataTransferObjects \u6587\u4ef6\u5939\u4e2d\u521b\u5efa\u4e00\u4e2a\u65b0\u7c7b\uff1a<\/p>\n<pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nusing System;\nusing System.ComponentModel.DataAnnotations;\n\nnamespace Entities.DataTransferObjects\n{\n    public abstract class EmployeeForManipulationDto\n    {\n        &#x5B;Required(ErrorMessage = &quot;Employee name is a required field.&quot;)]\n        &#x5B;MaxLength(30, ErrorMessage = &quot;Maximum length for the Name is 30 characters.&quot;)]\n        public string Name { get; set; }\n\n        &#x5B;Range(18, int.MaxValue, ErrorMessage = &quot;Age is required and it can&#039;t be lower than 18&quot;)]\n        public int Age { get; set; }\n\n        &#x5B;Required(ErrorMessage = &quot;Position is a required field.&quot;)]\n        &#x5B;MaxLength(20, ErrorMessage = &quot;Maximum length for the Position is 20 characters.&quot;)]\n        public string Position { get; set; }\n    }\n}\n\n<\/pre>\n<\/p>\n<p>We create this class as an abstract class because we want our creation and update DTO classes to inherit from it:<br \/>\n\u6211\u4eec\u5c06\u8fd9\u4e2a\u7c7b\u521b\u5efa\u4e3a\u62bd\u8c61\u7c7b\uff0c\u56e0\u4e3a\u6211\u4eec\u5e0c\u671b\u6211\u4eec\u7684\u521b\u5efa\u548c\u66f4\u65b0 DTO \u7c7b\u7ee7\u627f\u81ea\u5b83\uff1a<\/p>\n<p><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npublic class EmployeeForUpdateDto : EmployeeForManipulationDto { } \n\npublic class EmployeeForCreationDto : EmployeeForManipulationDto { }\n<\/pre>\n<\/p>\n<p>Now, we can modify the UpdateEmployeeForCompany action by adding the model validation right after the null check:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u901a\u8fc7\u5728\u7a7a\u68c0\u67e5\u540e\u7acb\u5373\u6dfb\u52a0\u6a21\u578b\u9a8c\u8bc1\u6765\u4fee\u6539 UpdateEmployeeForCompany \u63a7\u5236\u5668\uff1a<\/p>\n<p><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nif(employee == null)\n{ _logger.LogError(&quot;EmployeeForUpdateDto object sent from client is null.&quot;); \nreturn BadRequest(&quot;EmployeeForUpdateDto object is null&quot;); \n} \nif (!ModelState.IsValid) \n{ \n_logger.LogError(&quot;Invalid model state for the EmployeeForUpdateDto object&quot;); \nreturn UnprocessableEntity(ModelState); \n}\n<\/pre>\n<\/p>\n<p>The same process can be applied to the Company DTO classes and create action. You can find it implemented in the source code for this chapter.<br \/>\n\u53ef\u4ee5\u5c06\u76f8\u540c\u7684\u8fc7\u7a0b\u5e94\u7528\u4e8e\u516c\u53f8 DTO \u7c7b\u5e76\u521b\u5efa\u64cd\u4f5c\u3002\u60a8\u53ef\u4ee5\u5728\u672c\u7ae0\u7684\u6e90\u4ee3\u7801\u4e2d\u627e\u5230\u5b83\u7684\u5b9e\u73b0\u3002<\/p>\n<p>Let\u2019s test this:<br \/>\n\u8ba9\u6211\u4eec\u6d4b\u8bd5\u4e00\u4e0b\uff1a<\/p>\n<p><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n{\n    &quot;name&quot;:null,\n    &quot;age&quot;:29,\n    &quot;position&quot;:null\n}\n<\/pre>\n<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\/80ABBCA8-664D-4B20-B5DE-024705497D4A\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\/80ABBCA8-664D-4B20-B5DE-024705497D4A\"><a href=\"https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\/80ABBCA8-664D-4B20-B5DE-024705497D4A\">https:\/\/localhost:5001\/api\/companies\/53a1237a-3ed3-4462-b9f0-5a7bb1056a33\/employees\/80ABBCA8-664D-4B20-B5DE-024705497D4A<\/a><\/a><\/a><\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_1308.jpg\" alt=\"Alt text\" \/><\/p>\n<p>Great.<br \/>\n\u4f1f\u5927\u3002<\/p>\n<p>Everything works well.<br \/>\n\u4e00\u5207\u6b63\u5e38\u3002<\/p>\n<h2>13.3 Validation for PATCH Requests<\/h2>\n<p>The validation for PATCH requests is a bit different from the previous ones. We are using the ModelState concept again, but this time we have to place it in the ApplyTo method first:<br \/>\nPATCH \u8bf7\u6c42\u7684\u9a8c\u8bc1\u4e0e\u4ee5\u524d\u7684\u9a8c\u8bc1\u7565\u6709\u4e0d\u540c\u3002\u6211\u4eec\u518d\u6b21\u4f7f\u7528 ModelState \u6982\u5ff5\uff0c\u4f46\u8fd9\u6b21\u6211\u4eec\u5fc5\u987b\u9996\u5148\u5c06\u5176\u653e\u5728 ApplyTo \u65b9\u6cd5\u4e2d\uff1a<\/p>\n<p><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\npatchDoc.ApplyTo(employeeToPatch, ModelState);\n<\/pre>\n<\/p>\n<p>Right below, we can add our familiar validation logic:<br \/>\n\u5728\u4e0b\u9762\uff0c\u6211\u4eec\u53ef\u4ee5\u6dfb\u52a0\u6211\u4eec\u719f\u6089\u7684\u9a8c\u8bc1\u903b\u8f91\uff1a<\/p>\n<p><pre class=\"brush: csharp; highlight: [22,23,24,25,26,27]; title: ; notranslate\" title=\"\">\n\n&#x5B;HttpPatch(&quot;{id}&quot;)]\npublic IActionResult PartiallyUpdateEmployeeForCompany(Guid companyId, Guid id, &#x5B;FromBody] JsonPatchDocument&lt;EmployeeForUpdateDto&gt; patchDoc)\n{\n    if (patchDoc == null)\n    {\n        _logger.LogError(&quot;patchDoc object sent from client is null.&quot;);\n        return BadRequest(&quot;patchDoc object is null&quot;);\n    }\n    var company = _repository.Company.GetCompany(companyId, trackChanges: false);\n    if (company == null)\n    {\n        _logger.LogInfo($&quot;Company with id: {companyId} doesn&#039;t exist in the database.&quot;);\n        return NotFound();\n    }\n    var employeeEntity = _repository.Employee.GetEmployee(companyId, id, trackChanges: true);\n    if (employeeEntity == null)\n    {\n        _logger.LogInfo($&quot;Employee with id: {id} doesn&#039;t exist in the database.&quot;);\n        return NotFound();\n    }\n    var employeeToPatch = _mapper.Map&lt;EmployeeForUpdateDto&gt;(employeeEntity);\n    patchDoc.ApplyTo(employeeToPatch,ModelState);\n    if(!ModelState.IsValid)\n    {\n        _logger.LogError(&quot;Invalid model state for the patch document&quot;); \n        return UnprocessableEntity(ModelState);\n    }\n    _mapper.Map(employeeToPatch, employeeEntity);\n    _repository.Save();\n    return NoContent();\n}\n<\/pre>\n<\/p>\n<p>Let\u2019s test this now:<br \/>\n\u73b0\u5728\u8ba9\u6211\u4eec\u6d4b\u8bd5\u4e00\u4e0b\uff1a<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees\/80ABBCA8-664D-4B20-B5DE-024705497D4A\"><a href=\"https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees\/80ABBCA8-664D-4B20-B5DE-024705497D4A\"><a href=\"https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees\/80ABBCA8-664D-4B20-B5DE-024705497D4A\">https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees\/80ABBCA8-664D-4B20-B5DE-024705497D4A<\/a><\/a><\/a><\/p>\n<p><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n&#x5B;\n    {\n        &quot;op&quot;:&quot;remove&quot;,\n        &quot;path&quot;:&quot;\/ageeeeeee&quot;\n    }\n]\n<\/pre>\n<\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_1309.jpg\" alt=\"Alt text\" \/><\/p>\n<p>You can see that it works as it is supposed to.<br \/>\n\u60a8\u53ef\u4ee5\u770b\u5230\u5b83\u6309\u9884\u671f\u5de5\u4f5c\u3002<\/p>\n<p>But, we have a small problem now. What if we try to send a remove operation, but for the valid path:<br \/>\n\u4f46\u662f\uff0c\u6211\u4eec\u73b0\u5728\u6709\u4e00\u4e2a\u5c0f\u95ee\u9898\u3002\u5982\u679c\u6211\u4eec\u5c1d\u8bd5\u53d1\u9001\u5220\u9664\u64cd\u4f5c\uff0c\u4f46\u5bf9\u4e8e\u6709\u6548\u8def\u5f84\uff0c\u8be5\u600e\u4e48\u529e\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_1310.gif\" alt=\"Alt text\" \/><\/p>\n<p>We can see it passes, but this is not good. If you can remember, we said that the remove operation will set the value for the included property to its default value, which is 0. But in the EmployeeForUpdateDto class, we have a Range attribute which doesn\u2019t allow that value to be below 18. So, where is the problem?<br \/>\n\u6211\u4eec\u53ef\u4ee5\u770b\u5230\u5b83\u8fc7\u53bb\u4e86\uff0c\u4f46\u8fd9\u5e76\u4e0d\u597d\u3002\u5982\u679c\u60a8\u8fd8\u8bb0\u5f97\uff0c\u6211\u4eec\u8bf4\u8fc7\u5220\u9664\u64cd\u4f5c\u4f1a\u5c06\u5305\u542b\u5c5e\u6027\u7684\u503c\u8bbe\u7f6e\u4e3a\u5176\u9ed8\u8ba4\u503c\uff0c\u5373 0\u3002\u4f46\u662f\u5728 EmployeeForUpdateDto \u7c7b\u4e2d\uff0c\u6211\u4eec\u6709\u4e00\u4e2a Range \u5c5e\u6027\uff0c\u5b83\u4e0d\u5141\u8bb8\u8be5\u503c\u4f4e\u4e8e 18\u3002\u90a3\u4e48\uff0c\u95ee\u9898\u51fa\u5728\u54ea\u91cc\u5462\uff1f<\/p>\n<p>Let\u2019s illustrate this for you:<br \/>\n\u8ba9\u6211\u4eec\u4e3a\u60a8\u8bf4\u660e\u8fd9\u4e00\u70b9\uff1a<\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_1311.jpg\" alt=\"Alt text\" \/><\/p>\n<p>As you can see, we are validating the patchDoc which is completely valid at this moment, but we save employeeEntity to the database. So, we need some additional validation to prevent an invalid employeeEntity from being saved to the database:<br \/>\n\u5982\u60a8\u6240\u89c1\uff0c\u6211\u4eec\u6b63\u5728\u9a8c\u8bc1\u76ee\u524d\u5b8c\u5168\u6709\u6548\u7684 patchDoc\uff0c\u4f46\u6211\u4eec\u5c06 employeeEntity \u4fdd\u5b58\u5230\u6570\u636e\u5e93\u4e2d\u3002\u56e0\u6b64\uff0c\u6211\u4eec\u9700\u8981\u4e00\u4e9b\u989d\u5916\u7684\u9a8c\u8bc1\u6765\u9632\u6b62\u65e0\u6548\u7684 employeeEntity \u4fdd\u5b58\u5230\u6570\u636e\u5e93\u4e2d\uff1a<\/p>\n<p><pre class=\"brush: csharp; title: ; notranslate\" title=\"\">\nvar employeeToPatch = _mapper.Map&lt;EmployeeForUpdateDto&gt;(employeeEntity); \npatchDoc.ApplyTo(employeeToPatch, ModelState);\nTryValidateModel(employeeToPatch); \nif(!ModelState.IsValid) \n{\n_logger.LogError(&quot;Invalid model state for the patch document&quot;); \nreturn UnprocessableEntity(ModelState);\n}\n<\/pre>\n<\/p>\n<p>We can use the TryValidateModel method to validate the already patched employeeToPatch instance. This will trigger validation and every error will make ModelState invalid.<br \/>\n\u6211\u4eec\u53ef\u4ee5\u4f7f\u7528 TryValidateModel \u65b9\u6cd5\u6765\u9a8c\u8bc1\u5df2\u7ecf\u4fee\u8865\u7684 employeeToPatch \u5b9e\u4f8b\u3002\u8fd9\u5c06\u89e6\u53d1\u9a8c\u8bc1\uff0c\u6bcf\u4e2a\u9519\u8bef\u90fd\u4f1a\u4f7f\u6a21\u578b\u72b6\u6001\u65e0\u6548\u3002<\/p>\n<p>After that, we execute a familiar validation check.<br \/>\n\u4e4b\u540e\uff0c\u6211\u4eec\u6267\u884c\u719f\u6089\u7684\u9a8c\u8bc1\u68c0\u67e5\u3002<\/p>\n<p>Now, we can test this again:<br \/>\n\u73b0\u5728\uff0c\u6211\u4eec\u53ef\u4ee5\u518d\u6b21\u6d4b\u8bd5\uff1a<\/p>\n<p><pre class=\"brush: jscript; title: ; notranslate\" title=\"\">\n&#x5B;\n    {\n        &quot;op&quot;:&quot;remove&quot;,\n        &quot;path&quot;:&quot;\/age&quot;\n    }\n]\n<\/pre>\n<\/p>\n<p><a href=\"https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees\/80ABBCA8-664D-4B20-B5DE-024705497D4A\"><a href=\"https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees\/80ABBCA8-664D-4B20-B5DE-024705497D4A\"><a href=\"https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees\/80ABBCA8-664D-4B20-B5DE-024705497D4A\">https:\/\/localhost:5001\/api\/companies\/C9D4C053-49B6-410C-BC78-2D54A9991870\/employees\/80ABBCA8-664D-4B20-B5DE-024705497D4A<\/a><\/a><\/a><\/p>\n<p><img decoding=\"async\" src=\"\/img\/20220824-ultimate-asp-net-core-web-api\/Image_1312.jpg\" alt=\"Alt text\" \/><\/p>\n<p>And we get 422, which is the expected status code.<br \/>\n\u6211\u4eec\u5f97\u5230 422\uff0c\u8fd9\u662f\u9884\u671f\u7684\u72b6\u6001\u4ee3\u7801\u3002<\/p>\n","protected":false},"excerpt":{"rendered":"<p>13 VALIDATION While writing API actions, we have a set of rules that we need to check. If we take a look at the Company class, we can see different data annotation attributes above our properties: \u5728\u7f16\u5199 API \u64cd\u4f5c\u65f6\uff0c\u6211\u4eec\u9700\u8981\u68c0\u67e5\u4e00\u7ec4\u89c4\u5219\u3002\u5982\u679c\u6211\u4eec\u770b\u4e00\u4e0b Company \u7c7b\uff0c\u6211\u4eec\u53ef\u4ee5\u5728\u5c5e\u6027\u4e0a\u65b9\u770b\u5230\u4e0d\u540c\u7684\u6570\u636e\u6ce8\u91ca\u5c5e\u6027\uff1a Those attributes serve the purpose to validate our model object while creating or updating [&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-249","post","type-post","status-publish","format-standard","hentry","category-csharp"],"_links":{"self":[{"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/249","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=249"}],"version-history":[{"count":0,"href":"https:\/\/diji.net\/index.php?rest_route=\/wp\/v2\/posts\/249\/revisions"}],"wp:attachment":[{"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=249"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=249"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/diji.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=249"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}