JSON Patch Validation .Net Core
Asked Answered
C

4

9

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

Complementary answered 27/4, 2018 at 15:9 Comment(0)
P
6

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);
Prosit answered 27/1, 2020 at 19:27 Comment(1)
I like how this method silently ignores invalid properties in the request while still using the valid ones. To me, that's what an API should do so long as the documentation makes it clear what you can and can't do in the PATCH.Mascagni
H
3

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.

Homestead answered 27/4, 2018 at 15:39 Comment(1)
Wont this code just always return a list of names of just one 'Id' not really prevent the application of a change to the property. This might assist in a logic block that inspects the patchDoc. I suppose you could use the names in the notAllowedProperties and see if the name pops up in a path of an operation then remove that operation. Ill post my code when completeComplementary
C
3

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.

Constanceconstancia answered 24/2, 2022 at 11:12 Comment(0)
W
0

I did simple things and it worked for me.

class file:
    public class Student
    {
        [Key]
        public int Id { get; set; }
        [Required]
        public string StudentName { get; set; }
        public int age { get; set; }
        public string Address { get; set; } = String.Empty;
    }
Action method code : 

    [Route("Patch")]
    [HttpPatch] //PATCH
    public ActionResult Patch(int id, [FromBody] JsonPatchDocument<Student> patchDoc)
    {
    try
                {
                connString = new SqlConnection(this.Configuration.GetConnectionString("DefaultConnection"));
                cmd = new SqlCommand("update students set Address='" + patchDoc.Operations[0].value + "' where Id=" + id + "", connString);
                connString.Open();
                int x = cmd.ExecuteNonQuery();
                if (x > 0)
                {
                    return Ok(new { Message = "Record Updated" });
                }
                return BadRequest(new { Message = "Record Not found!" });
            }
            catch (Exception ef)
            {
                return BadRequest(ef.Message);
            }
            finally { connString.Close(); }
        }
    }

and json call URL: 
http://localhost:5128/api/Students/Patch?id=1002
raw body
    [
    {
    "path": "/Address",
    "op": "replace",
    "from": "ABCD",
    "value": "Africa"
  }
]
Wilbourn answered 20/8, 2023 at 11:28 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.