在前面的两篇文章中,我们讨论了很多关于使用HttpClient进行CRUD操作的基础知识。如果你已经读过它们,你就知道如何使用HttpClient从API中获取数据,并使用HttpClient发送POST、PUT和DELETE请求。当我们使用PUT请求时,用它来更新我们的资源。但我们可以通过使用HTTP PATCH请求进行部分更新来改进这一点。因此,在本文中,我们将展示如何使用HttpClient发送HTTP PATCH请求来实现资源的部分更新,从而提高应用程序的性能。

要下载源码,可以访问https://github.com/CodeMazeBlog/httpclient-aspnetcore/tree/patch-with-httpclient获取项目。

更多关于HTTP PATCH请求

正如我们已经提到的,我们使用PUT请求进行完整更新,使用PATCH请求进行部分更新。但这并不是这两个HTTP请求之间的唯一区别。首先,请求主体是不同的。如果我们检查Web API中的PUT,我们可以看到请求体是一个简单的对象:

[FromBody] CompanyForUpdateDto company

但如果我们对PATCH请求做同样的检查:

[FromBody] JsonPatchDocument<EmployeeForUpdateDto> patchDoc

可以看到,如果我们想要支持PATCH请求的请求体,我们必须使用JsonPatchDocument类。这个类帮助我们描述可以使用PATCH请求执行的不同操作集。

同样,对于PUT请求,我们使用application/json作为媒体类型。但是对于PATCH请求,首选的媒体类型是application/json-patch+json。我们可以为HTTP PATCH请求使用application/json媒体类型,但正如我们提到的,首选的媒体类型是application/json-patch+json,我们将在示例中使用它。

HTTP PATCH操作

PATCH请求可以作为JSON数组的一部分执行一个或多个操作。让我们看看PATCH请求的请求体:

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

正如我们所看到的,请求主体基本上是一个指定不同操作的JSON对象数组。也就是说,我们可以确认两个操作:由op属性指定的Replace和Remove。路径部分表示到我们想要修改的对象属性的路径。最后,value部分表示一个新值,我们用它来替换Name属性的旧值。

使用HttpClient的PatchAsync方法发送HTTP PATCH请求

在我们开始修改客户端项目之前,可以快速地看一下API的PATCH操作的路径:

[Route("api/companies/{companyId}/employees")]
[ApiController]
public class EmployeesController : ControllerBase

可以看到我们已经在EmployeesController中实现了PATCH操作。由于单个员工不能在没有单个公司的情况下存在,所以到这个控制器的路由是:

api/companies/{companyId}/employees

但是,我们只更新了一个雇员,所以我们需要该雇员的id:

[HttpPatch("{id}")]
public IActionResult PartiallyUpdateEmployeeForCompany(Guid companyId, Guid id, [FromBody] JsonPatchDocument<EmployeeForUpdateDto> patchDoc)

这意味着该操作的路由是:

api/companies/{companyId}/employees/{id}

为了简单起见,我们已经从这个控制器中删除了其余的操作,另外,为了便于参考,让我们展示一下API实现:

[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();
}

从请求体接受JsonPatchDocument对象。接下来,我们检查patchDoc对象是否存在空值,以及公司和员工是否存在于数据库中。然后,我们把Employee类型映射到EmployeeForUpdateDto类型。这对我们来说很重要,因为patchDoc对象只能应用于EmployeeForUpdateDto类型。在调用ApplyTo方法之后,我们将再次映射到员工类型,并将更改保存到数据库中。

客户端实现

现在,让我们打开客户端项目,并在Services文件夹中添加一个新服务:

public class HttpClientPatchService : IHttpClientServiceImplementation
{
private static readonly HttpClient _httpClient = new HttpClient(); public HttpClientPatchService()
{
_httpClient.BaseAddress = new Uri("https://localhost:5001/api/");
_httpClient.Timeout = new TimeSpan(0, 0, 30);
_httpClient.DefaultRequestHeaders.Clear();
} public async Task Execute()
{
throw new NotImplementedException();
}
}

因此,这是HttpClient类的初始配置。一旦开始学习HttpClientFactory,我们将展示如何能够一次性进行配置,而不会为每个服务重复它。

在此之后,我们可以使用PatchAsync方法实现发送HTTP PATCH请求的逻辑:

private async Task PatchEmployee()
{
var patchDoc = new JsonPatchDocument<EmployeeForUpdateDto>();
patchDoc.Replace(e => e.Name, "Sam Raiden Updated");
patchDoc.Remove(e => e.Age); var uri = Path.Combine("companies", "C9D4C053-49B6-410C-BC78-2D54A9991870", "employees", "80ABBCA8-664D-4B20-B5DE-024705497D4A");
var serializedDoc = JsonConvert.SerializeObject(patchDoc);
var requestContent = new StringContent(serializedDoc, Encoding.UTF8, "application/json-patch+json"); var response = await _httpClient.PatchAsync(uri, requestContent);
response.EnsureSuccessStatusCode();
}

在这里,我们在JsonPatchDocument类的帮助下创建了一个新的Patch类。为了能够使用这个类,我们必须安装Microsoft.AspNetCore.JsonPatch。接下来,我们使用JsonPatchDocument类中的两个helper方法创建两个操作。然后,创建URI、序列化对象,并创建新的字符串内容。这里需要注意的重要一点是,我们没有使用System.Text中的JsonSerializer.Serialize()方法。而是使用Newtonsoft.Json的JsonConvert.SerializeObject()方法。我们必须这么做,因为PATCH文件不能很好地用System.Text.Json序列化,我们的API会收到400 bad request。

最后,我们使用PatchAsync方法发送请求,并确保响应有一个成功的状态码。

现在,让我们修改Execute方法:

public async Task Execute(){   
  await PatchEmployee();

将服务注册到Program类:

private static void ConfigureServices(IServiceCollection services)
{
//services.AddScoped<IHttpClientServiceImplementation, HttpClientCrudService>();
services.AddScoped<IHttpClientServiceImplementation, HttpClientPatchService>();
}

在PatchEmployee放置断点,并启动项目。

检查数据库:

我们可以看到Name列被修改,Age列被设置为默认值0。让我们看看如何使用HttpRequestMessage来实现同样的功能。

使用HttpRequestMessage发送PATCH请求

与前面处理所有HTTP请求时一样,我们将使用HttpRequestMessage类向服务器送PATCH请求。在本教程的前几篇文章中,我们已经讨论了这种方法的好处。

所以,让我们在HttpClientPatchService类中添加另一个方法:

private async Task PatchEmployeeWithHttpRequestMessage()
{
var patchDoc = new JsonPatchDocument<EmployeeForUpdateDto>();
patchDoc.Replace(e => e.Name, "Sam Raiden");
patchDoc.Add(e => e.Age, 28); var uri = Path.Combine("companies", "C9D4C053-49B6-410C-BC78-2D54A9991870", "employees", "80ABBCA8-664D-4B20-B5DE-024705497D4A");
var serializedDoc = JsonConvert.SerializeObject(patchDoc); var request = new HttpRequestMessage(HttpMethod.Patch, uri);
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
request.Content = new StringContent(serializedDoc, Encoding.UTF8);
request.Content.Headers.ContentType = new MediaTypeHeaderValue("application/json-patch+json"); var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
}

我们再次创建了JsonPatchDocument对象,但这一次,我们replace了员工的姓名,并add了年纪。准备URI并序列化对象。完成之后,我们创建一个新的HttpRequestMessage,提供我们想要使用的HTTP方法和URI。就像我们在所有的HttpRequestMessage示例中所做的那样,我们为请求添加了一个accept header、content和content type。最后,我们使用SendAsync方法发送请求,并确保响应中的状态码成功。

为了能够执行这个方法,我们必须在execute方法中调用它:

public async Task Execute()
{
//await PatchEmployee();
await PatchEmployeeWithHttpRequestMessage();

在方法中放置断点,并启动项目:

检查数据库:

结论

在本文中——包括前面的文章,我们讨论了所有的CRUD请求,包括HTTP PATCH请求。现在我们知道了如何使用HttpClient发送所有这些类型的请求,也使用了HttpRequestMessage类。

在ASP.NET Core中用HttpClient(三)——发送HTTP PATCH请求的更多相关文章

  1. C# ASP.NET Core使用HttpClient的同步和异步请求

    引用 Newtonsoft.Json // Post请求 public string PostResponse(string url,string postData,out string status ...

  2. 在ASP.NET Core中用HttpClient(二)——发送POST, PUT和DELETE请求

    在上一篇文章中,我们已经学习了如何在ASP.NET Core中使用HttpClient从Web API获取数据.此外,我们还学习了如何使用GetAsync方法和HttpRequestMessage类发 ...

  3. 在ASP.NET Core中用HttpClient(一)——获取数据和内容

    在本文中,我们将学习如何在ASP.NET Core中集成和使用HttpClient.在学习不同HttpClient功能的同时使用Web API的资源.如何从Web API获取数据,以及如何直接使用Ht ...

  4. 在ASP.NET Core中用HttpClient(六)——ASP.NET Core中使用HttpClientFactory

    ​到目前为止,我们一直直接使用HttpClient.在每个服务中,我们都创建了一个HttpClient实例和所有必需的配置.这会导致了重复代码.在这篇文章中,我们将学习如何通过使用HttpClient ...

  5. 在ASP.NET Core中用HttpClient(四)——提高性能和优化内存

    到目前为止,我们一直在使用字符串创建请求体,并读取响应的内容.但是我们可以通过使用流提高性能和优化内存.因此,在本文中,我们将学习如何在请求和响应中使用HttpClient流. 什么是流 流是以文件. ...

  6. 在ASP.NET Core中用HttpClient(五)——通过CancellationToken取消HTTP请求

    ​用户向服务器发送HTTP请求应用程序页面是一种非常可能的情况.当我们的应用程序处理请求时,用户可以从该页面离开.在这种情况下,我们希望取消HTTP请求,因为响应对该用户不再重要.当然,这只是实际应用 ...

  7. ASP.NET Core使用HttpClient的同步和异步请求

    using System; using System.Collections.Generic; using System.Collections.Specialized; using System.I ...

  8. ASP.NET Core教程【三】实体字段属性、链接标签、并发数据异常、文件上传及读取

    前文索引:ASP.NET Core教程[二]从保存数据看Razor Page的特有属性与服务端验证ASP.NET Core教程[一]关于Razor Page的知识 实体字段属性 再来看看我们的实体类 ...

  9. asp.net core 系列 9 三种运行环境和IIS发布

    一.在asp.net core中使用多个环境 ASP.NET Core 配置是基于运行时环境, 使用环境变量.ASP.NET Core 在应用启动时读取环境变量ASPNETCORE_ENVIRONME ...

随机推荐

  1. codeforces 870C

    C. Maximum splitting time limit per test 2 seconds memory limit per test 256 megabytes input standar ...

  2. 018-019 NET5_内置容器支持依赖注入+IServiceCollection的生命周期

    概念: DI依赖注入: IServiceCollection仅支持构造函数注入 什么是依赖注入? 如果对象A依赖对象B,对象B依赖对象C,就可以先构造对象C,然后传递给对象B,再把对象B传递给A.得到 ...

  3. vue 单文件组件最佳实践

    vue 单文件组件最佳实践 生命周期 template <template> <section> <h1>vue single file components te ...

  4. Vue Login Form Component

    Vue Login Form Component Account Login <template> <div> <slot></slot> <el ...

  5. js 生成Excel

    https://www.npmjs.com/package/xlsx 安装依赖 npm install xlsx Example import * as XLSX from "xlsx&qu ...

  6. Mosquitto

    mosquitto可连接远程服务器及本地服务器. mosquitto可在一个节点内建立一个连接用于收发,也可在一个节点内建立多个连接用于收发.建立一个连接用于收发时,会有初始部分帧的延迟.(可能由于内 ...

  7. 《Effective Java》总结

    导语 <Effective Java>是和<Thinking in java>齐名的java进阶书籍.作者参与了JDK标准库的编写工作,对于此书的学习,让我收获很多.好记性不如 ...

  8. hadoop环境搭建:完全分布式

    目录 1.硬件配置 2.软件版本 3.准备工作 3.1.建立虚拟机,网络设置为桥接模式 3.2.更改主机名 3.3.绑定主机名和IP,建立各主机间的联系 3.4.关闭防火墙 3.5.配置宿主机host ...

  9. uni-app小白入门自学笔记(一)

    码文不易啊,转载请带上本文链接呀,感谢感谢 https://www.cnblogs.com/echoyya/p/14427845.html 目录 码文不易啊,转载请带上本文链接呀,感谢感谢 https ...

  10. CSS绘制三角形和箭头

    <html> <head> <meta charset="utf-8"> <title>CSS绘制三角形和箭头</title& ...