nick gowdy
nick gowdy

Reputation: 6531

C# HTTP PATCH using HTTPClient

I've written a test using Test Server in dot net core 3.1 and I'm trying to do a PATCH request to an endpoint. However as I'm new to using PATCH, I'm a bit stuck with how to send the correct object that the endpoint is expecting.

[Fact]
public async Task Patch()
{
    var operations = new List<Operation>
    {
        new Operation("replace", "entryId", "'attendance ui", 5)
    };

    var jsonPatchDocument = new JsonPatchDocument(operations, new DefaultContractResolver());

        
    // Act
    var content = new StringContent(JsonConvert.SerializeObject(jsonPatchDocument), Encoding.UTF8, "application/json");
    var httpResponse = await HttpClient.PatchAsync($"v1/Entry/1", content);
    var actual = await httpResponse.Content.ReadAsStringAsync();
        
}

[HttpPatch("{entryId}")]
public async Task<ActionResult> Patch(int entryId, [FromBody] JsonPatchDocument<EntryModel> patchDocument)
{
    if (patchDocument == null)
       {
           return BadRequest();
       }

       var existingEntry = _mapper.Map<EntryModel>(await _entryService.Get(entryId));

       patchDocument.ApplyTo(existingEntry);

       var entry = _mapper.Map<Entry>(existingEntry);
       var updatedEntry = _mapper.Map<Entry>(await _entryService.Update(entryId, entry));

       return Ok(await updatedEntry.ModelToPayload());
}

From the example I'm creating a JsonPatchDocument with a list of operations, serializing it to JSON and then doing PatchAsync with HTTP Client with the URL for the endpoint.

So my question is what is the shape of the object that I should be Patching and I'm doing this correctly in general?

I tried sending the EntryModel as shown in the picture below, however patchDocument.Operations has an empty list.

enter image description here

Thanks, Nick

Upvotes: 5

Views: 14179

Answers (3)

Atul Dc
Atul Dc

Reputation: 11

The JsonPatchDocument will not work. To make it work you have to add a media type formater.

  1. Install Microsoft.AspNetCore.Mvc.NewtonsoftJson

  2. In startup.cs , after AddControllers() add ->

    .AddNewtonsoftJson(x => x.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); })

  3. If you need to have JSON as the default media type formatter. Keep it before any other media type of formatter.

Upvotes: 0

nick gowdy
nick gowdy

Reputation: 6531

I ended up solving my problem by doing several things:

  • JsonPatchDocument doesn't seem to work without the dependency services.AddControllers().AddNewtonsoftJson(); in Startup.cs. This is from the Nuget package `Microsoft.AspNetCore.Mvc.Newtonsoft.json.
  • There is an easier way to create the array than the answer from @Neil. Which is this: var patchDoc = new JsonPatchDocument<EntryModel>().Replace(o => o.EntryTypeId, 5);
  • You need this specific media type: var content = new StringContent(JsonConvert.SerializeObject(patchDoc), Encoding.UTF8, "application/json-patch+json");

Here is the complete code:

/// <summary>
/// Verify PUT /Entrys is working and returns updated records
/// </summary>
[Fact]
public async Task Patch()
{
    var patchDoc = new JsonPatchDocument<EntryModel>()
            .Replace(o => o.EntryTypeId, 5);

    var content = new StringContent(JsonConvert.SerializeObject(patchDoc), Encoding.UTF8, "application/json-patch+json");
    var httpResponse = await HttpClient.PatchAsync($"v1/Entry/1", content);
    var actual = await httpResponse.Content.ReadAsStringAsync();

    // Assert
    Assert.Equal(HttpStatusCode.OK, httpResponse.StatusCode);
    Assert.True(httpResponse.IsSuccessStatusCode);
}

/// <summary>
/// Endpoint to do partial update
/// </summary>
/// <returns></returns>
[HttpPatch("{entryId}")]
public async Task<ActionResult> Patch(int entryId, [FromBody] JsonPatchDocument<EntryModel> patchDocument)
{
    if (patchDocument == null)
       {
           return BadRequest();
       }

       var existingEntry = _mapper.Map<EntryModel>(await _entryService.Get(entryId));

        // Apply changes 
        patchDocument.ApplyTo(existingEntry);

        var entry = _mapper.Map<Entry>(existingEntry);
        var updatedEntry = _mapper.Map<Entry>(await _entryService.Update(entryId, entry));

        return Ok();
}

Upvotes: 4

Neil
Neil

Reputation: 11919

Take a look at this page: https://learn.microsoft.com/en-us/aspnet/core/web-api/jsonpatch?view=aspnetcore-3.1

But the content is something like:

[   {
    "op": "add",
    "path": "/customerName",
    "value": "Barry"   },   {
    "op": "add",
    "path": "/orders/-",
    "value": {
      "orderName": "Order2",
      "orderType": null
    }   } ]

Upvotes: 0

Related Questions