There are times you want ot make updates to a resource on the server without replacing the whole of it. JSON Patch works a great deal in this, lighter and very powerful. To learn more on JSON Patch check out these links below:
- Official site at http://jsonpatch.com
- Introductory Article by Kevin Sookocheff
- Explainer by Waqat Mansor
ASP.NET Core supports JSON Patch natively using the
HttpPatchAttribute usually decorate on a controller action with
[HttpPath], model binding support for
JsonPatchDocument<T>, and model validation support when applying changes via extension method with the signature
document.ApplyTo(document, ModelState). More on this can be found in the official documentation.
On this post I will not focus on how JSON Patch works, why it is better or how it is supported in ASP.NET Core (for that, see above links). Instead, I will focus on how you can ensure that certain properties are not edited in a patch operation or ensure that only certain properties can be edited.
TLDR; you can jump straight to the code.
Breakdown for parts?
There are many parts in this sample. So let’s dig in. The sample uses Entity Framework Core which I assume anyone working with ASPNET Core understands.
Modeling with JSON Patch (Vehicle.cs)
When working with JSON Patch it is a good idea to have different models for every level of information and then employ inheritance between them. For example, let us consider that the server store information about vehicles in a garage. The unique identifier (which could be the VIN, the registration number, or a randomly generated string) of the vehicle should not be changed after creation but the creator is allowed to specify it during creation. We would model 3 classes:
Vehicle. Employing inheritance,
VehicleCreateModel would inherit from
Vehicle may optionally inherit from
VehicleCreateModel to avoid duplicate properties, ease documentation and maintenance.
These models will also be used to separate the accepted properties at different stages. In the example, the properties that are accepted for the JSON Patch operation are those in the
Immutable properties on JsonPatchDocument<T>
Applying JSON patch operations on a target object can fail without throwing an exception or returning an error. In ASP.NET Core, this is mitigated using an extension method that takes in the ModelState so that it can write the errors to it. This samples, makes use of this function internally by checking the properties before executing it. The logic is shown below:
Checking allowed properties can be difficult. Instead of using a list of properties that are immutable, we check the properties that can be changed. In JSON Patch, paths are specified starting with a forward slash
/ so we have to make sure that we remove it. Also for arrays, lists and collection, the last segment in the path specifies the index or any. For example:
/0 to address index zero,
/30 to address index 30, or
/- to not target a particular index such as when adding a new item to a list. Removing this segements can be tricky and can cause errors.
Nested properties are have a similar problem with indexes above. Our sample, assumes that all nested properties a patch model are not immutable.
Finally, to make the solution easier to use, the implementation is done using an extension method on
Working with MVC Controller
Now that we have the models setup and the extension method, we now can need to bring the two together. This is where an MVC controller is used.
GetAsync methods are straightfoward. The
CreateAsync method works with the
VehicleCreateModel to avoid setting properties that are only system generated. This is a fairly common pattern.
The fun part is in the
UpdateAsync method which takes an important
JsonPatchDocument<VehiclePatchModel> . This type sets the type which contains the properties that we can update. Since we employed inhertiance, we can apply
JsonPatchDocument<VehiclePatchModel> to an object of type
Vehicle . Easy …
Finally, when the patch document is applied on the object, we must check if the ModelState still remains valid. If there were errors in apply the JSON Patch operations, they would be written as errors in the ModelState dictionary. Returning
Problem(); will pick errors from the ModelState, convert to
ValidationProblemDetails and then write to the response.
To try out the sample, make a HTTP Patch request with an try to change the
ChasisNo property. See SampleRequest.json for the body.
The first operation attempts to change an immutable property causing validation to fail and receiving a 400 (Bad Request) response. See SampleResponse.json for the body.
Removing the first operation from the request would result in completion of the request. This samples assumes that you have data in the database context before attempting a patch. If not, you can replace the use of Entity Framework with a static list of items for test purposes.
Working with JSON Patch can be awesome and painful at the same time. Hopefully, I have helped you make it easier with some sort of rules on what can be edited.
There are still some missing pieces on here:
- If there are many projects such as a solution with micro-services, repeating this can be tedious so it may be better to create a library that moves the repeated code out of the application project.
- If there are many types that are to be edited, one may consider moving the code out of the controller to a Validation attribute, model binding extensions or just somewhere else.
- The sample here may not cover all scenarios for nested properties.