Immutable Properties With JSON Patch in AspNet Core
There is now a NuGet package for this: Tingle.AspNetCore.JsonPatch.NewtonsoftJson
There are times you want to 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 [HttpPatch]
, model binding support for JsonPatchDocument
and 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: VehiclePatchModel
, VehicleCreateModel
and Vehicle
. Employing inheritance, VehicleCreateModel
would inherit from VehiclePatchModel
and 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 VehiclePatchModel
.
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 segments can be tricky and can cause errors.
Nested properties 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 JsonPatchDocument<T>
.
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.
The ListAsync
and GetAsync
methods are straightforward. 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 inheritance, 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.
Samples
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.
Conclusion
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.