You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET API:如何复用模型并动态设置Payload必填属性

解决方案

问题1:复用同一模型动态设置Required属性

方法1:使用FluentValidation(推荐)

FluentValidation支持为同一个实体类配置多套验证规则,完美适配Add/Update的不同场景,无需修改实体类的特性,也能避免大量代码重复。

步骤:

  1. 安装NuGet包:FluentValidation.AspNetCore
  2. 创建验证器,分别定义Add/Update场景的规则:
public class ToDOValidator : AbstractValidator<ToDO>
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    public ToDOValidator(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
        // 通用规则:Update时Id必须大于0
        RuleFor(x => x.Id).GreaterThan(0).When(IsUpdateOperation);
    }

    // 配置Add场景的必填规则
    public void ConfigureForAdd()
    {
        RuleFor(x => x.Name).NotEmpty().WithMessage("名称不能为空");
        RuleFor(x => x.Description).NotEmpty().WithMessage("描述不能为空");
    }

    // 配置Update场景的可选规则:仅当属性有值时验证非空
    public void ConfigureForUpdate()
    {
        RuleFor(x => x.Name).NotEmpty().When(x => !string.IsNullOrEmpty(x.Name));
        RuleFor(x => x.Description).NotEmpty().When(x => !string.IsNullOrEmpty(x.Description));
    }

    // 判断当前是否为Update操作
    private bool IsUpdateOperation()
    {
        var actionName = _httpContextAccessor.HttpContext?.Request.RouteValues["action"]?.ToString();
        return actionName == "Update";
    }
}
  1. 在Program.cs中注册服务,并在控制器中调用对应场景的验证:
// Program.cs
builder.Services.AddHttpContextAccessor();
builder.Services.AddFluentValidationAutoValidation();
builder.Services.AddFluentValidationClientsideAdapters();
builder.Services.AddScoped<ToDOValidator>();

// 控制器代码
[HttpPost]
public IActionResult Add([FromBody] ToDO todo, [FromServices] ToDOValidator validator)
{
    validator.ConfigureForAdd();
    var result = validator.Validate(todo);
    if (!result.IsValid)
    {
        return BadRequest(result.Errors);
    }
    // 执行Add业务逻辑
    return Ok();
}

[HttpPost]
public IActionResult Update([FromBody] ToDO todo, [FromServices] ToDOValidator validator)
{
    validator.ConfigureForUpdate();
    var result = validator.Validate(todo);
    if (!result.IsValid)
    {
        return BadRequest(result.Errors);
    }
    // 执行Update业务逻辑
    return Ok();
}

方法2:实现IValidatableObject接口

在实体类中直接实现IValidatableObject接口,根据当前请求的动作名称动态调整验证规则:

public class ToDO : IValidatableObject
{
    public int Id { get; set; }
    
    public string Name { get; set; }

    public string Description { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var httpContext = validationContext.GetService<IHttpContextAccessor>().HttpContext;
        var actionName = httpContext?.Request.RouteValues["action"]?.ToString();

        if (actionName == "Add")
        {
            if (string.IsNullOrEmpty(Name))
                yield return new ValidationResult("名称不能为空", new[] { nameof(Name) });
            if (string.IsNullOrEmpty(Description))
                yield return new ValidationResult("描述不能为空", new[] { nameof(Description) });
        }
        else if (actionName == "Update")
        {
            if (!string.IsNullOrEmpty(Name) && string.IsNullOrWhiteSpace(Name))
                yield return new ValidationResult("名称不能为空白", new[] { nameof(Name) });
            if (!string.IsNullOrEmpty(Description) && string.IsNullOrWhiteSpace(Description))
                yield return new ValidationResult("描述不能为空白", new[] { nameof(Description) });
        }
    }
}

注意:需要在Program.cs中注册IHttpContextAccessor:

builder.Services.AddHttpContextAccessor();

问题2:Update时仅发送修改的属性是否更优?如何实现?

是否更优?

是的,部分更新的方式有以下明显优势:

  • 减少网络传输的数据量,尤其当实体包含20+属性时效果显著;
  • 符合RESTful规范中PATCH方法的语义(用于部分资源更新);
  • 避免覆盖未修改的属性,降低并发更新时的数据冲突风险。

实现方法:使用JsonPatchDocument(推荐)

ASP.NET Core原生支持JsonPatchDocument,可以接收部分更新请求并自动应用到实体上。

步骤:

  1. 安装NuGet包:Microsoft.AspNetCore.JsonPatch
  2. 修改Update接口为HttpPatch方法,使用JsonPatchDocument<ToDO>作为参数:
[HttpPatch("{id}")]
public IActionResult Update(int id, [FromBody] JsonPatchDocument<ToDO> patchDoc)
{
    // 从数据库获取原始实体
    var existingTodo = _todoRepository.GetById(id);
    if (existingTodo == null)
    {
        return NotFound();
    }

    // 将补丁应用到原始实体
    patchDoc.ApplyTo(existingTodo, ModelState);
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }

    // 结合问题1的验证逻辑,验证更新后的实体
    var validator = new ToDOValidator(_httpContextAccessor);
    validator.ConfigureForUpdate();
    var validationResult = validator.Validate(existingTodo);
    if (!validationResult.IsValid)
    {
        return BadRequest(validationResult.Errors);
    }

    // 保存更新到数据库
    _todoRepository.Update(existingTodo);
    return Ok(existingTodo);
}
  1. 请求示例(JSON格式):
[
    { "op": "replace", "path": "/name", "value": "更新后的待办名称" },
    { "op": "replace", "path": "/description", "value": "更新后的待办描述" }
]

其中op支持replace(替换属性值)、add(添加属性)、remove(移除属性)等操作,path为属性的JSON路径,value为新值。

自定义部分更新(备选)

如果不想使用JsonPatch,可以自定义UpdateDTO,结合AutoMapper减少代码重复:

// 仅包含允许更新的属性
public class ToDOUpdateDTO
{
    public string Name { get; set; }
    public string Description { get; set; }
}

// 控制器代码
[HttpPatch("{id}")]
public IActionResult Update(int id, [FromBody] ToDOUpdateDTO updateDto)
{
    var existingTodo = _todoRepository.GetById(id);
    if (existingTodo == null) return NotFound();

    // 配置AutoMapper仅映射非空属性
    _mapper.Map(updateDto, existingTodo);

    // 验证和保存逻辑...
    return Ok(existingTodo);
}

AutoMapper配置示例:

CreateMap<ToDOUpdateDTO, ToDO>()
    .ForMember(dest => dest.Name, opt => opt.Condition(src => !string.IsNullOrEmpty(src.Name)))
    .ForMember(dest => dest.Description, opt => opt.Condition(src => !string.IsNullOrEmpty(src.Description)));

内容的提问来源于stack exchange,提问作者Piyush

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 21:35:33