.NET Core中如何用数据注解阻止JSON Patch更新特定属性?
在.NET Core中通过数据注解阻止JSON Patch更新特定属性的可行方案
当然有完美的解决方案!我们可以通过自定义数据注解+Action过滤器的组合来实现这个需求,既符合你用数据注解标记禁止更新属性的要求,又能优雅地拦截并过滤非法的Patch操作。下面一步步给你实现:
1. 定义自定义禁止Patch的注解
首先我们创建一个专属的属性注解,用来标记哪些属性不允许通过JSON Patch更新:
using System; [AttributeUsage(AttributeTargets.Property, AllowMultiple = false)] public class DoNotAllowPatchUpdateAttribute : Attribute { // 注解不需要额外逻辑,仅作为标记用 }
2. 标记实体类中的禁止更新属性
把这个注解应用到你的Entity类对应的属性上,比如示例中的Id:
public class Entity { [DoNotAllowPatchUpdate] public string Id { get; set; } public string Name { get; set; } public string Status { get; set; } public string Action { get; set; } }
3. 创建自定义Action过滤器拦截非法Patch操作
接下来我们写一个Action过滤器,它会在Patch请求到达业务逻辑前,自动检查并移除那些针对被标记属性的操作:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.JsonPatch; using System.Reflection; using System.Text.Json.Serialization; public class ValidatePatchAttribute : ActionFilterAttribute { public override void OnActionExecuting(ActionExecutingContext context) { // 从Action参数中获取JSON Patch文档 if (context.ActionArguments.TryGetValue("patchDoc", out var patchDocObj) && patchDocObj is JsonPatchDocument<Entity> patchDoc) { // 获取所有被标记为禁止Patch的属性对应的JSON路径 var forbiddenPaths = typeof(Entity) .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(prop => prop.GetCustomAttribute<DoNotAllowPatchUpdateAttribute>() != null) .Select(prop => { // 兼容JsonPropertyName注解的自定义命名(比如驼峰命名) var jsonNameAttr = prop.GetCustomAttribute<JsonPropertyNameAttribute>(); var propertyName = jsonNameAttr?.Name ?? prop.Name; return $"/{propertyName.ToLowerInvariant()}"; }) .ToList(); // 筛选出需要移除的非法操作 var invalidOperations = patchDoc.Operations .Where(op => forbiddenPaths.Contains(op.Path.ToLowerInvariant())) .ToList(); // 移除非法操作 foreach (var op in invalidOperations) { patchDoc.Operations.Remove(op); } } base.OnActionExecuting(context); } }
4. 在Controller中应用过滤器
把这个过滤器加到你的Patch接口上,就能自动生效了:
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.JsonPatch; [ApiController] [Route("api/entities")] public class EntitiesController : ControllerBase { [HttpPatch("{id}")] [ValidatePatch] // 应用过滤器 public IActionResult PatchEntity(string id, [FromBody] JsonPatchDocument<Entity> patchDoc) { // 模拟从数据库获取实体 var targetEntity = new Entity { Id = id, Name = "OriginalName", Status = "Active", Action = "None" }; // 应用Patch(此时非法操作已经被移除) patchDoc.ApplyTo(targetEntity); // 这里添加保存到数据库的逻辑... return Ok(targetEntity); } }
测试验证
用你提供的示例代码测试:
var patchDoc = new JsonPatchDocument<Entity>(); patchDoc.Replace(o => o.Name, "Foo"); // 这个操作会正常生效 patchDoc.Replace(o => o.Id, "213"); // 这个操作会被过滤器自动移除 // 调用接口后,实体的Name会变成"Foo",Id保持原来的值不变
额外优化(通用化过滤器)
如果你的项目中有多个实体需要这个功能,可以把过滤器改成泛型版本,适配任意实体类型:
public class ValidatePatchAttribute<TEntity> : ActionFilterAttribute where TEntity : class { public override void OnActionExecuting(ActionExecutingContext context) { if (context.ActionArguments.TryGetValue("patchDoc", out var patchDocObj) && patchDocObj is JsonPatchDocument<TEntity> patchDoc) { var forbiddenPaths = typeof(TEntity) .GetProperties(BindingFlags.Public | BindingFlags.Instance) .Where(prop => prop.GetCustomAttribute<DoNotAllowPatchUpdateAttribute>() != null) .Select(prop => { var jsonNameAttr = prop.GetCustomAttribute<JsonPropertyNameAttribute>(); var propertyName = jsonNameAttr?.Name ?? prop.Name; return $"/{propertyName.ToLowerInvariant()}"; }) .ToList(); var invalidOperations = patchDoc.Operations .Where(op => forbiddenPaths.Contains(op.Path.ToLowerInvariant())) .ToList(); foreach (var op in invalidOperations) { patchDoc.Operations.Remove(op); } } base.OnActionExecuting(context); } }
使用时只需要指定实体类型:
[HttpPatch("{id}")] [ValidatePatch<Entity>] public IActionResult PatchEntity(string id, [FromBody] JsonPatchDocument<Entity> patchDoc) { // ... 业务逻辑 }
内容的提问来源于stack exchange,提问作者Hizzy
相关产品推荐
相关产品推荐

