Reputation: 879
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
Upvotes: 9
Views: 6693
Reputation: 51
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"
}
]
Upvotes: 0
Reputation: 118
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.
Upvotes: 3
Reputation: 505
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);
Upvotes: 6
Reputation: 2559
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
.
Upvotes: 3