In our two previous articles, we have covered a lot of ground regarding the CRUD operations with HttpClient. If you have read them both, you know how to use HttpClient to fetch data from API, and also to send POST, PUT and DELETE requests using HttpClient. When we talk about the PUT request, we know we use it for the full update of our resources. But we can improve that with partial updates using the HTTP PATCH requests. So, in this article, we are going to show you how to use HttpClient to send HTTP PATCH requests to achieve partial updates of our resources, thus improving the application’s performance.
You can also visit our HttpClient Tutorial page, to see all the articles from this tutorial.
So, let’s get going.
More About an HTTP PATCH Request
As we already mentioned, we use the PUT request for full updates and the PATCH request for partial updates. But this is not the only difference between these two HTTP requests. First of all, the request body is different. If we inspect the PUT action on the Web API’s side, we can see that the body of the request is a simple object:
[FromBody] CompanyForUpdateDto company
But if we do the same inspection for the PATCH request:
[FromBody] JsonPatchDocument<EmployeeForUpdateDto> patchDoc
We can see that if we want to support a request body for the PATCH requests, we have to use the JsonPatchDocument
class. This class helps us describe different sets of operations that we can execute with the PATCH request.
Also, for the PUT request, we use the application/json
as a media type. But for the PATCH request, the preferred media type is application/json-patch+json
. We can use the application/json
media type for the HTTP PATCH request, but as we mentioned the preferred media type is application/json-patch+json
, and we are going to use that one in our example.
HTTP PATCH Operations
The PATCH request can execute one or multiple operations as a part of the JSON array. That said, let’s see the request body of the PATCH request:
[ { "op": "replace", "path": "/name", "value": "new name" }, { "op": "remove", "path": "/name" } ]
So, as we can see, the request body is basically an array of JSON objects that specify different operations. That said, we can confirm two operations: Replace
and Remove
, specified by the op
property. The path
part signifies the path to the object’s property that we want to modify – the Name property in this case. Finally, the value
part represents a new value that we use to replace an old one for the Name property.
We have seen two operations but there are six operations in total. So, let’s explore all of them:
We have borrowed this picture from our Ultimate ASP.NET Core Web API book because it explains all the operations in a clear manner.
Now, after the theory part, we can start with some coding.
Using HttpClient’s PatchAsync Method to Send the HTTP Patch Request
Before we start with the client project modification, we can quickly take a look at the route for the PATCH action on the API’s side:
[Route("api/companies/{companyId}/employees")] [ApiController] public class EmployeesController : ControllerBase
So, we can see that we have implemented the PATCH action inside the EmployeesController
. Since a single employee can’t exist without a single company, the route to this controller is: api/companies/{companyId}/employees
. But, since we update only a single employee, we need an id for that employee:
[HttpPatch("{id}")] public IActionResult PartiallyUpdateEmployeeForCompany(Guid companyId, Guid id, [FromBody] JsonPatchDocument<EmployeeForUpdateDto> patchDoc)
This means that the route for this action is: api/companies/{companyId}/employees/{id}
.
We have removed the rest of the actions from this controller for the sake of simplicity, but to read in great detail about the restful API implementations of parent and child resources, refer to our mentioned book.
Also, just for reference, let’s show the API’s implementation of the action:
[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(); }
We are accepting the JsonPatchDocument
from the request body. Next, we check the patchDoc
object for a 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
object can apply only to the EmployeeForUpdateDto
type. After calling the ApplyTo
method, we map again to the Employee
type (from employeeToPatch
to employeeEntity
) and save changes in the database.
Client-Side Implementation
Now, let’s open the client project and add a new service in the Services
folder:
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(); } }
So, this is the initial configuration of the HttpClient class with the base address and the timeout set. This is the same as we have in our previous example. Once we start learning about the HttpClientFactory, we are going to show you how to store the configuration in a single place without repeating it for each service.
After this, we can implement the logic for sending the HTTP PATCH request by using the shortcut PatchAsync
method:
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(); }
Here, we create a new PATCH document with a help of the JsonPatchDocument
class. To be able to use this class, we have to install the Microsoft.AspNetCore.JsonPatch
library. Next, we create two operations (Replace and Remove) with the two helper methods from the JsonPatchDocument
class. Then, we create a URI to the action, serialize the object, and create a new string content by providing our serialized object, encoding type, and media type. The important thing to notice here is that we don’t use JsonSerializer.Serialize()
method from the System.Text.Json
library but we use JsonConvert.SerializeObject()
method from the Newtonsoft.Json
library. We have to do this, otherwise, we get 400 bad request
from our API since the patch document isn’t serialized well with System.Text.Json
.
Finally, we send the request using the PatchAsync
method and ensure that the response has a successful status code.
Now, let’s modify the Execute
method:
public async Task Execute() { await PatchEmployee(); }
And, let’s register this service in the Program
class:
private static void ConfigureServices(IServiceCollection services) { //services.AddScoped<IHttpClientServiceImplementation, HttpClientCrudService>(); services.AddScoped<IHttpClientServiceImplementation, HttpClientPatchService>(); }
Excellent.
Let’s place the breakpoint in the PatchEmployee
method and start both applications:
And we can see the 204 status code, with the No Content message.
Now, if we check the database:
We can see the Name column is modified and the Age column is set to its default value of 0.
Excellent.
Let’s see how we can use the HttpRequestMessage to achieve the same thing.
Using HttRequestMessage to Send the PATCH Request
As we did with all the previous HTTP requests, we are going to use the HttpRequestMessage
class to send the PATCH request to the server. We already talked about the benefits of this approach in our previous articles from this tutorial.
So, let’s add another method in the HttpClientPatchService
class:
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(); }
We create our JsonPatchDocument
object again but this time, we revert the name of the employee and also add the age of 28. Then, we prepare URI and serialize the object. Once we do that, we create a new HttpRequestMessage
providing the HTTP method we want to use and the URI. As we did with all our HttpRequestMessage
examples, we add an accept header, content, and the content type for our request. Finally, we send the request using the SendAsync
method and ensure a successful status code in the response.
To be able to execute this method, we have to call it in the Execute
method:
public async Task Execute() { //await PatchEmployee(); await PatchEmployeeWithHttpRequestMessage(); }
That’s all.
We can place a breakpoint in our method and run both applications:
And we can see the 204 response.
We can confirm that by looking at the record in the Employees table:
Excellent. Both columns have been updated.
Conclusion
Great. With this article – including the previous ones, we have covered all the CRUD requests including the HTTP PATCH request. Now we know how to use HttpClient to send all these types of requests using both shortcut methods and the HttpRequestMessage class.
In the next article, we are going to talk about streams and how they help us improve performance and memory usage.
So, until then.
All the best.
I have followed the steps in your book. I get the null for pathcDoc in the request. Is that because of content type which is application/json? How should I change that in my requests?
Have you tried checking our source code and running it. Y won’t get null for sure if you use our prepared requests. Then you can compare your code with ours. I am really not sure why you have null there.
I haven’t found a HttpPatch example yet that addresses architectures where there is a clean separation of concerns between controllers and services. In such an architecture, the controller should not have knowledge of database entities, repositories, or contexts and services should not have knowledge of HTTP transports, methods, or even that the consumer is a controller. In this case, the controller consumes a service that is called to perform the database update. To perform a patch, it seems like the expectation is that the service needs to have controller “stuff” or the controller needs service “stuff”. Specifically, the service would need to have knowledge of a patch document and that the caller was a controller -or- the service would need a low-level get and put for the data model to return to the controller, which shouldn’t have knowledge of database entities. How could this be structured so that there is still a clean separation of concerns?
Hello Kevin. I completely agree with you. But when we are talking about topics and try to transfer that knowledge to our readers, if we introduce an additional layer in the story, we can confuse the readers pretty easily and risk that they miss the main point of the article, which is how to work with HTTPPatch requests. That said, in our Web API book, we cover exactly this, using the Onion architecture with all the required layers. Your controller should be aware of the JsonPatchDocument, but the main business logic is moved to the service layer, which communicates with the repository layer to get and patch the entity. The controller is only applying the Patch action, so pretty neat and clean.
Looking at your book, that is not true. Regardless of whether the asynchronous or non-asynchronous method is used, the GetEmployeeForPatch methods return an Employee database entity to the consumer. If the consumer is a controller, you are requiring that the controller has knowledge of the database entity being updated by the database. The controller may “only” be applying the Patch action, but it still requires breaking the clean separation of concerns. To maintain a clean separation of concerns, the only knowledge that should be shared between a service and a controller is a view model (also known as a data transfer object or DTO).
I am sorry, but this sentence: “the GetEmployeeForPatch methods return an Employee database entity to the consumer” is not quite correct. This method returns a tuple and our controller has no idea about the Entity itself, it is all propagated through the abstraction of the Service.Contracts project. Our presentation layer is even a separate project, which adds this abstraction restriction. So, all the Action does is accept the PatchDocument from a client, call a service method, apply the patch changes, and call another service method. Action inside a controller is working with the results and not the exact model types from the Entities project.
Do you have an example of your model?
Because i am trying the following:
I am getting the following errors:
For some strange reason it was posted, out of now where this is my complete question:
Do you have an example of your model?
Because i am trying the following:
I am getting the following errors:
If found the solution, does highlight a feature of the JsonPatchDocument.
The line with:
Should be:
Hello. You have the source code at the start of the article so feel free to look at it.
So helpful, thank you so much!
You are most welcome.