Web requests
.NET Core C# HTTP REST Top Web API

Handling HTTP PATCH Requests in a .NET Core Web API

Welcome to today’s post.

In today’s post I will discuss how to use HTTP PATCH requests within a Web API using .NET Core. As part of the discussion, I will show how to update JSON document resources that are passed into a Web API HTTP PATCH method using ASP.NET Core.

You may already be familiar with how updates to backend resources occur with HTTP PUT requests. With a HTTP PUT request, we know that when we pass in identifier into the request, the PUT applies an update to the resource (which can be a database record) that corresponds to an entry or record with the identifier.

Before I go into the details of how we setup and configure Web API services to serve HTTP PATCH requests, I will explain what HTTP PATCH is, and how it differs from an HTTP PUT request. I will also give an example to illustrate how HTTP patching works with a JSON document resource.  

Differences between HTTP PATCH and HTTP PUT

The difference between HTTP PATCH and HTTP PUT is that with PUT the changes replace the entire resource and PATCH only applies to changes within the resource.

To make sense of this an example would be an array of updates to our Books with an example patch document we submit to our API is shown:

[
{
  "op": "replace",
  "path": "/books/1/Edition",
  "value": "2"
},
{
  "op": "replace",
  "path": "/books/2/Edition",
  "value": "4"
},
{
  "op": "add",
  "path": "/books/-",
  "value": 
  {
    "Title": "American Dirt",
    "Author": "Jeanine Cummins",
    "YearPublished": "2020",
    "Genre": "Fiction",
    "Edition": "1",
    "ISBN": "12345678",
    "Location": "Lane Cove"
  }
},  
{
  "op": "add",
  "path": "/books/-",
  "value": 
  {
    "Title": "Thief River Falls",
    "Author": "Brian Freeman",
    "YearPublished": "2020",
    "Genre": "Fiction",
    "Edition": "1",
    "ISBN": "12345678",
    "Location": "Lane Cove"
  }
}
]

The above document includes two updates and two additions to the Books resource.

The operation to add an additional record to the document is the following key-value pair:

"op": "add",

The operation to update an existing record within the document is the following key-value pair:

"op": "replace",

The path to the node or nodes within the document that needs inserting or updating is specified with the following key-value pair with the value as the XPath to the target node:

"path": "[XPath to node]",

And the value having a specific single value for updates or a JSON value for additions:


“value”: “[single value or JSON object]”

Configuring an ASP.NET Core Web API to Support HTTP PATCH Requests

In order to use HTTP PATCH within a .NET Core Web API application you will need package Microsoft.AspNetCore.App (which is part of the .NET Core package when you create a .NET Core project using the VS templates). You will then have the JsonPatch classes ready to use, else you will need to use the following namespace included within your source code:

using Microsoft.AspNetCore.JsonPatch;

To use HTTP PATCH requests in your API will require using the JSON Patch format, which is an array of operations we apply to the resource.

To declare an API method to accept an HTTP PATCH requires the following:

  • Declare the [HttpPatch] attribute.
  • A JsonPatchDocument<T> parameter.
  • Call ApplyTo on the patch document to apply changes.

Implementation of an HTTP Patching Web API Method for our Resource

To implement an API that supports the above patching document we define the following DTO class for our Book records that will structure the patched resource documents.

Our Book class DTO is shown:

public class BookViewModel
{
  public int ID { get; set; }
  [Required]
  public string Title { get; set; }
  [Required]
  public string Author { get; set; }
  public int YearPublished { get; set; }
  public string Genre { get; set; }
  public string Edition { get; set; }
  public string ISBN { get; set; }
  [Required]
  public string Location { get; set; }
  public DateTime DateCreated { get; set; }
  public DateTime DateUpdated { get; set; }
}

We construct a class containing a list of books that will be our patching model:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using BookLoan.Models;

namespace BookLoan.Models
{
    public class PatchBookViewModel
    {
        public List<BookViewModel> books { get; set; }
    }
}

The controller API patch method is as shown:

[HttpPatch("api/[controller]/BookPatch")]
public async Task<IActionResult> BookPatch([FromBody]
  JsonPatchDocument<PatchBookViewModel> patchDoc)
{
  if (patchDoc != null)
  {
    var originalBooks = await _bookService.GetBooks();
    var originalBooksCopy = new List<BookViewModel>(originalBooks);
    var patchOriginalBooksCopy = new PatchBookViewModel()
    {
      books = originalBooksCopy
    };

    patchDoc.ApplyTo(patchOriginalBooksCopy, ModelState);

    if (!ModelState.IsValid)
    {
      return BadRequest(ModelState);
    }

    return new ObjectResult(patchOriginalBooksCopy);
  }
  else
  {
    return BadRequest(ModelState);
  }
}

What we are doing with the patching method is:

  • Get the list of original books.
  • Make a copy of the list of original books.
  • Create an instance of a copy of the list of original books.
  • Apply the patch document to the instance of a copy of the list of original books.

Testing the HTTP Patching Web API Method

To test the above patch document and associated code, we first build and run the Web API using IISExpress and open POSTMAN.

In POSTMAN setup a HTTP PATCH request as follows:

  • Set the HTTP request type to PATCH.
  • Set the body as a raw type and Text format.
  • Paste the patch document JSON text into the body.

Set a breakpoint within the API patch method and submit the request from POSTMAN.

After the breakpoint hits, inspect the patchDoc:

The JsonPatchDocument consists of one record for each operation that is defined within the JSON patch document. The record consists of the following keys:

  • The operations value, op is one of the main operations that can be defined within the patch document. Four most common operations are shown below:
OperationNotes
addAdd a property or array element or set value for existing properties.
removeRemove a property or array element.
replaceSame as remove followed by add at same location.
moveSame as remove from source followed by add to destination using value from source.
  • A path, path that is the route within the JSON patch to the target key.
  • A value, value which we apply to the operation.

The first replace patch operation is shown below:

Note: If the path is specified with a numeric index, then that will specify the array index of the record within the record array, not the ID of the underlying record.

In this case, the path:

/books/1/Edition

Will retrieve the Edition field of the second book in the books array.

We verify that the first operation has applied the update correctly by inspecting the original of the second record of the array:

And compare it with the patched record:

The record has updated as the Edition field has changed to the target value:

{
  "op": "replace",
  "path": "/books/1/Edition",
  "value": "2"
},

With the add operation, we can verify the additional records have been added.

The total number of records before patching is 8:

And the total after applying the two add operations is 10:

Expanding the 9th record in the array shows the record properties:

As we can see, the record properties shown below in the patch add operation have been appended to the array successfully.

{
  "op": "add",
  "path": "/books/-",
  "value": 
  {
    "Title": "American Dirt",
    "Author": "Jeanine Cummins",
    "YearPublished": "2020",
    "Genre": "Fiction",
    "Edition": "1",
    "ISBN": "12345678",
    "Location": "Lane Cove"
  }
},  

Running to continuation we then check the response in POSTMAN.

The replace operations have been applied to the records as shown:

The add operations have been applied to the records as shown:

The response of the patched JSON document result is as shown:

{
    "books": [
        {
            "id": 1,
            "title": "The Lord of the Rings",
            "author": "J. R. R. Tolkien",
            "yearPublished": 1954,
            "genre": "fantasy",
            "edition": "0",
            "isbn": "",
            "location": "sydney",
            "dateCreated": "2019-11-05T00:00:00",
            "dateUpdated": "2019-11-05T00:00:00"
        },
        {
            "id": 2,
            "title": "The Alchemist (O Alquimista)",
            "author": "Paulo Coelho",
            "yearPublished": 1988,
            "genre": "fantasy",
            "edition": "2",
            "isbn": "",
            "location": "sydney",
            "dateCreated": "2019-11-05T00:00:00",
            "dateUpdated": "2020-04-19T01:19:36.5288767"
        },
        {
            "id": 3,
            "title": "The Little Prince (Le Petit Prince)",
            "author": "Antoine de Saint-Exupéry",
            "yearPublished": 1943,
            "genre": "fantasy",
            "edition": "4",
            "isbn": "",
            "location": "sydney",
            "dateCreated": "2019-11-05T00:00:00",
            "dateUpdated": "2019-11-12T01:08:06.564811"
        },

	..

        {
            "id": 8,
            "title": "Dream of the Red Chamber (红楼梦)",
            "author": "Cao Xueqin",
            "yearPublished": 1791,
            "genre": "family sage",
            "edition": "0",
            "isbn": "",
            "location": "sydney",
            "dateCreated": "2019-11-05T00:00:00",
            "dateUpdated": "2019-11-05T00:00:00"
        },
        {
            "id": 0,
            "title": "American Dirt",
            "author": "Jeanine Cummins",
            "yearPublished": 2020,
            "genre": "Fiction",
            "edition": "1",
            "isbn": "12345678",
            "location": "Lane Cove",
            "dateCreated": "0001-01-01T00:00:00",
            "dateUpdated": "0001-01-01T00:00:00"
        },
        {
            "id": 0,
            "title": "Thief River Falls",
            "author": "Brian Freeman",
            "yearPublished": 2020,
            "genre": "Fiction",
            "edition": "1",
            "isbn": "12345678",
            "location": "Lane Cove",
            "dateCreated": "0001-01-01T00:00:00",
            "dateUpdated": "0001-01-01T00:00:00"
        }
    ]
}

That’s all for today’s post.

In a future post I will show how to expand on the above to apply HTTP PATCH updates to data using EF Core.

I hope you found this post useful and informative.

Social media & sharing icons powered by UltimatelySocial