Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JSON Patch Validation .Net Core

Has anyone found a good way to use data annotations to prevent specifc properties from being updated in a json patch doc.

Model:

 public class Entity
 {
    [DoNotAllowPatchUpdate]
    public string Id     { get; set; }

    public string Name   { get; set; }

    public string Status { get; set; }

    public string Action { get; set; }
 }

Logic:

var patchDoc = new JsonPatchDocument<Entity>();
patchDoc.Replace(o => o.Name, "Foo");

//Prevent this from being applied
patchDoc.Replace(o => o.Id, "213");

patchDoc.ApplyTo(Entity);

The logic code is just an example of what the patch doc could look like coming from the client just generating in C# for quick testing purposes

like image 545
Hizzy Avatar asked Apr 27 '18 15:04

Hizzy


3 Answers

I wrote an extension method for JsonPatchDocument; here's an abbreviated version:

public static void Sanitize<T>(this Microsoft.AspNetCore.JsonPatch.JsonPatchDocument<T> document) where T : class
{
    for (int i = document.Operations.Count - 1; i >= 0; i--)
    {
        string pathPropertyName = document.Operations[i].path.Split("/", StringSplitOptions.RemoveEmptyEntries).FirstOrDefault();

        if (typeof(T).GetProperties().Where(p => p.IsDefined(typeof(DoNotPatchAttribute), true) && string.Equals(p.Name, pathPropertyName, StringComparison.CurrentCultureIgnoreCase)).Any())
        {
            // remove
            document.Operations.RemoveAt(i); 

            //todo: log removal
        }
    }
}

Add a minimal attribute:

[AttributeUsage(AttributeTargets.Property)]
public class DoNotPatchAttribute : Attribute

Apply the attribute to your class properties:

public class SomeEntity
{
    [DoNotPatch]
    public int SomeNonModifiableProperty { get; set; }
    public string SomeModifiableProperty { get; set; }
}

Then you can call it before applying the transformation:

patchData.Sanitize<SomeEntity>();

SomeEntity entity = new SomeEntity();

patchData.ApplyTo(entity);
like image 133
RobertMGlynn Avatar answered Oct 06 '22 04:10

RobertMGlynn


You could create your own Attribute. Something like :

DoNotAllowPatchUpdate:Attribute{}

public class Entity
 {
    [DoNotAllowPatchUpdate]
    public string Id     { get; set; }

    public string Name   { get; set; }

    public string Status { get; set; }

    public string Action { get; set; }
 }

And then check for it like:

    var notAllowedProperties = typeof(Entity).GetProperties()
      .Where(x => Attribute.IsDefined(x, typeof(DoNotAllowPatchUpdate)))
      .Select(x => x.Name).ToList();

now before you update them you can check notAllowedProperties.

like image 22
3xGuy Avatar answered Oct 06 '22 03:10

3xGuy


Although the question specifically asked about using annotations to restrict updates via JsonPatchDocuments, I thought adding another approach may be helpful to some.

I usually create an update-specific model which only has the fields that I want to allow to be updated. Then it's impossible to update the Id, for example:

public class UpdateEntityModel
{    
    public string Name { get; set; }

    public string Status { get; set; }

    public string Action { get; set; }
 }
 

My controller/function receives a parameter of type JsonPatchDocument<UpdateEntityModel>. I fetch the required entity from the database, map its properties to my update model, apply the patch to the update model and validate the result. Then map this back to the entity to persist the changes in the database.

/* Fetch entity from db */


var updateEntityModel = MapEntityToUpdateModel(entity);
    
jsonPatchDocument.ApplyTo(updateEntityModel);

ValidateModel(updateEntityModel); // Using FluentValidation validator
     
MapUpdateModelBackToEntity(entity, updateEntityModel);


/* Persist entity in db */

I use FluentValidation, AbstractValidator<UpdateEntityModel>, to specifically validate the update models.

like image 41
System.Linq Avatar answered Oct 06 '22 03:10

System.Linq