Chapter12:WORKING WITH PATCH REQUESTS

12 WORKING WITH PATCH REQUESTS

In the previous chapter, we worked with the PUT request to fully update our resource. But if we want to update our resource only partially, we should use PATCH.
在上一章中,我们使用 PUT 请求来完全更新我们的资源。但是,如果我们只想部分更新我们的资源,我们应该使用 PATCH。

The partial update isn’t the only difference between PATCH and PUT. The request body is different as well. For the Company PATCH request, for example, we should use [FromBody]JsonPatchDocument and not [FromBody]Company as we did with the PUT requests.
部分更新并不是PATCH和PUT之间的唯一区别。请求正文也不同。例如,对于公司补丁请求,我们应该使用 [FromBody]JsonPatchDocument而不是像处理 PUT 请求那样使用 [FromBody]Company。

Additionally, for the PUT request’s media type, we have used application/json — but for the PATCH request’s media type, we should use application/json-patch+json. Even though the first one would be accepted in ASP.NET Core for the PATCH request, the recommendation by REST standards is to use the second one.
此外,对于 PUT 请求的媒体类型,我们使用了 application/json — 但对于 PATCH 请求的媒体类型,我们应该使用 application/json-patch+json。 尽管第一个 ASP.NET Core会接受用于PATCH请求,但REST标准的建议是使用第二个。

Let’s see what the PATCH request body looks like:
让我们看看 PATCH 请求正文是什么样子的:


[ 
    {
         "op": "replace",
         "path": "/name", 
        "value": "new name" 
    }, 
    { 
        "op": "remove", 
        "path": "/name" 
    } 
]

The square brackets represent an array of operations. Every operation is placed between curly brackets. So, in this specific example, we have two operations: Replace and Remove represented by the op property. The path property represents the object’s property that we want to modify and the value property represents a new value.
方括号表示操作数组。每个操作都放在大括号之间。因此,在这个特定示例中,我们有两个操作:替换和删除由 op 属性表示。path 属性表示我们要修改的对象属性,value 属性表示新值。

In this specific example, for the first operation, we replace the value of the name property to a new name. In the second example, we remove the name property, thus setting its value to default.
在此特定示例中,对于第一个操作,我们将 name 属性的值替换为新名称。在第二个示例中,我们删除 name 属性,从而将其值设置为默认值。

There are six different operations for a PATCH request:
对于 PATCH 请求,有六种不同的操作:

OPERATION REQUEST BODY EXPLANATION
Add {
“op”: “add”,
“path”: “/name”,
“value”: “new value”
}
Assigns a new value to a required property.
为必需属性分配新值。
Remove {
“op”: “remove”,
“path”: “/name”
}
Sets a default value to a required property.
将默认值设置为必需属性
Replace {
“op”: “replace”,
“path”: “/name”,
“value”: “new value”
}
Replaces a value of a required property to a new value.
将必需属性的值替换为新值。
Copy {
“op”: “copy”,
“from”: “/name”,
“path”: “/title”
}
Copies the value from a property in the “from” part to the property in the “path” part.
将值从“from”部分中的属性复制到“path”部分中的属性。
Move {
“op”: “move”,
“from”: “/name”,
“path”: “/title”
}
Moves the value from a property in the “from” part to a property in the “path” part.
将值从“from”部分中的属性移动到“path”部分中的属性。
Test {
“op”: “test”,
“path”: “/name”,
“value”: “new value”
}
Tests if a property has a specified value.
测试属性是否具有指定的值。

After all this theory, we are ready to dive into the coding part.
在所有这些理论之后,我们准备深入研究编码部分。

12.1 Applying PATCH to the Employee Entity

Before we start with the controller modification, we have to install two required libraries:
在开始修改控制器之前,我们必须安装两个必需的库:

  • The Microsoft.AspNetCore.JsonPatch library to support the usage of JsonPatchDocument in our controller and
    Microsoft.AspNetCore.JsonPatch 库,支持在我们的控制器中使用 JsonPatchDocument 和

  • The Microsoft.AspNetCore.Mvc.NewtonsoftJson library to support request body conversion to a PatchDocument once we send our request.
    Microsoft.AspNetCore.Mvc.NewtonsoftJson 库,支持在我们发送我们的请求正文后转换为 PatchDocument请求。

As you can see, we are still using the NewtonsoftJson library to support the PatchDocument conversion in .NET 5. The official statement from Microsoft is that they are not going to replace it with System.Text.Json: “The main reason is that this will require a huge investment from us, with not a very high value-add for majority of our customers.”.
如您所见,我们仍在使用 NewtonsoftJson 库来支持 .NET 5 中的 PatchDocument 转换。Microsoft的官方声明是,他们不会用System.Text.Json取代它:“主要原因是这将需要我们的巨额投资,包括对于我们的大多数客户来说,这不是一个很高的附加值。

Once the installation is completed, we have to add the NewtonsoftJson configuration to IServiceCollection:
安装完成后,我们必须添加 NewtonsoftJson配置到 IServiceCollection:

services.AddControllers(config =>
{
    config.RespectBrowserAcceptHeader = true;
    config.ReturnHttpNotAcceptable = true;
}).AddNewtonsoftJson()
.AddXmlDataContractSerializerFormatters()
.AddCustomCSVFormatter();

Add it before the Xml and CSV formatters. Now we can continue.
将其添加到 XML 和 CSV 格式化程序之前。现在我们可以继续了。

We will require a mapping from the Employee type to the EmployeeForUpdateDto type. Therefore, we have to create a mapping rule for that.
我们需要从“员工”类型映射到“员工更新Dto”类型的映射。因此,我们必须为此创建一个映射规则。

If we take a look at the MappingProfile class, we will see that we have a mapping from the EmployeeForUpdateDto to the Employee type:
如果我们看一下 MappingProfile 类,我们将看到我们有一个从 EmployeeForUpdateDto 到 Employee 类型的映射:

CreateMap<EmployeeForUpdateDto, Employee>();

But we need it another way. To do so, we are not going to create an additional rule; we can just use the ReverseMap method to help us in the process:
但我们需要另一种方式。为此,我们不会创建其他规则;我们可以使用 ReverseMap 方法来帮助我们完成这个过程:

CreateMap<EmployeeForUpdateDto, Employee>().ReverseMap();

The ReverseMap method is also going to configure this rule to execute reverse mapping if we ask for it.
如果我们要求,ReverseMap 方法还将配置此规则以执行反向映射。

Now, we can modify our controller:
现在,我们可以修改我们的控制器:

//EmployeesController.cs

[HttpPatch("{id}")]
public IActionResult PartiallyUpdateEmployeeForCompany(Guid companyId, Guid id, [FromBody] JsonPatchDocument<EmployeeForUpdateDto> patchDoc)
{
    if (patchDoc == null)
    {
        _logger.LogError("patchDoc object sent from client is null.");
        return BadRequest("patchDoc object is null");
    }
    var company = _repository.Company.GetCompany(companyId, trackChanges: false);
    if (company == null)
    {
        _logger.LogInfo($"Company with id: {companyId} doesn't exist in the database.");
        return NotFound();
    }
    var employeeEntity = _repository.Employee.GetEmployee(companyId, id, trackChanges: true);
    if (employeeEntity == null)
    {
        _logger.LogInfo($"Employee with id: {id} doesn't exist in the database.");
        return NotFound();
    }
    var employeeToPatch = _mapper.Map<EmployeeForUpdateDto>(employeeEntity);
    patchDoc.ApplyTo(employeeToPatch);
    _mapper.Map(employeeToPatch, employeeEntity);
    _repository.Save();
    return NoContent();
}

You can see that our action signature is different from the PUT actions. We are accepting the JsonPatchDocument from the request body. After that, we have a familiar code where we check the patchDoc for null value and if the company and employee exist in the database. Then, we map from the Employee type to the EmployeeForUpdateDto type; it is important for us to do that because the patchDoc variable can apply only to the EmployeeForUpdateDto type. After apply is executed, we map again to the Employee type (from employeeToPatch to employeeEntity) and save changes in the database.
您可以看到我们的操作签名与 PUT 操作不同。我们正在接受来自请求正文的 JsonPatchDocument。之后,我们有一个熟悉的代码,我们在其中检查 patchDoc 的空值以及数据库中是否存在公司和员工。然后,我们从员工类型映射到员工更新Dto类型;这样做对我们来说很重要,因为 patchDoc 变量只能应用于 EmployeeForUpdateDto 类型。执行应用后,我们映射再次到员工类型(从员工到补丁到employeeEntity),并将更改保存在数据库中。

Now, we can send a couple of requests to test this code:
现在,我们可以发送几个请求来测试此代码:

Let’s first send the replace operation:
让我们首先发送替换操作:

[
    {
        "op":"replace",
        "path":"/age",
        "value":"28"
    }
]

https://localhost:5001/api/companies/C9D4C053-49B6-410C-BC78-2D54A9991870/employees/80ABBCA8-664D– 4B20-B5DE-024705497D4A

Alt text

It works; we get the 204 No Content message. Let’s check the same employee:
它有效;我们收到 204 无内容消息。让我们检查同一个员工:

https://localhost:5001/api/companies/C9D4C053-49B6-410C-BC78-2D54A9991870/employees/80ABBCA8-664D– 4B20-B5DE-024705497D4A

Alt text

And we see the Age property has been changed.
我们看到 Age 属性已更改。

Let’s send a remove operation in a request:
让我们在请求中发送删除操作:

[
    {
        "op":"remove",
        "path":"/age"
    }
]

https://localhost:5001/api/companies/C9D4C053-49B6-410C-BC78-2D54A9991870/employees/80ABBCA8-664D– 4B20-B5DE-024705497D4A

Alt text

This works as well. Now, if we check our employee, its age is going to be set to 0 (the default value for the int type):
这也行得通。现在,如果我们检查我们的员工,它的年龄将设置为 0(int 类型的默认值):
Alt text

Finally, let’s return a value of 28 for the Age property:
最后,让我们为 Age 属性返回值 28:

[
    {
        "op":"add",
        "path":"/age",
        "value":"28"
    }
]

https://localhost:5001/api/companies/C9D4C053-49B6-410C-BC78-2D54A9991870/employees/80ABBCA8-664D– 4B20-B5DE-024705497D4A

Alt text

Let’s check the employee now:
现在让我们检查一下员工:

Alt text

Excellent.
非常好。

Everything is working well.
一切正常。

发表评论