在前面的兩篇文章中,我們討論了很多關於使用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類。
原文連結:https://code-maze.com/using-httpclient-to-send-http-patch-requests-in-asp-net-core/