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

C# WebApi如何为DTO内自定义类型属性实现模型绑定验证

问题原因

在Password类上标注[ModelBinder]不生效的核心原因:当接口标记[ApiController]且接收application/json类型请求体时,ASP.NET Core 默认使用JSON格式化器(InputFormatter)直接反序列化整个UserRegisterDto对象,该过程不会为单个嵌套属性触发独立模型绑定流程,类上标注的模型绑定器特性不会被执行。

可行实现方案

方案1:自定义JSON转换器(适配JSON请求场景,改动最小)

JSON请求走InputFormatter反序列化流程,直接为Password类型编写自定义JsonConverter,即可在反序列化Password属性时自动触发校验逻辑,无需为整个DTO添加绑定器,也无需修改Controller代码。

  • 编写Password对应的JsonConverter,复用已有的校验逻辑:
using System.Text.Json;
using System.Text.Json.Serialization;

namespace UserService.Models
{
    public class PasswordJsonConverter : JsonConverter<Password>
    {
        public override Password? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
        {
            if (reader.TokenType != JsonTokenType.String)
            {
                throw new JsonException("密码字段必须为字符串类型");
            }
            string? passwordStr = reader.GetString();
            if (Password.TryParse(passwordStr.AsSpan(), out Password? result))
            {
                return result;
            }
            throw new JsonException("密码强度不符合要求:需至少8位,包含大小写字母、数字和特殊字符");
        }

        public override void Write(Utf8JsonWriter writer, Password value, JsonSerializerOptions options)
        {
            writer.WriteStringValue(value.Value);
        }
    }
}
  • 为Password类添加转换器特性,原有类内部代码无需修改:
namespace UserService.Models
{
    [JsonConverter(typeof(PasswordJsonConverter))]
    public class Password
    {
        private const string PasswordRegex = "(?=^.{8,}$)(?=.*\\d)(?=.*[!@#$%&?*\\\\\"§$\\/()=~]+)(?![.\\n])(?=.*[A-Z])(?=.*[a-z]).*$";
        public string Value { get; set; }
        public override string ToString() => Value;
        public static implicit operator string(Password e) => e.Value;
        public static bool TryParse(ReadOnlySpan<char> s, out Password? result)
        {
            result = null;
            if (string.IsNullOrWhiteSpace(s.ToString()))
                return false;
            if (!Regex.IsMatch(s.ToString(), PasswordRegex))
                return false;
            result = new Password()
            {
                Value = s.ToString(),
            };
            return true;
        }
    }
}

方案生效后,前端传入JSON请求体时,反序列化到Password属性会自动执行正则校验,校验失败直接返回400错误,无需修改Controller和DTO其他代码。

方案2:兼容非JSON场景的模型绑定器实现

如果需要支持form-data、x-www-form-urlencoded提交,或直接将Password作为路由、查询参数传值,可补充实现模型绑定器,与上述JsonConverter互不冲突:

  • 实现PasswordEntityBinder:
using Microsoft.AspNetCore.Mvc.ModelBinding;

namespace UserService.Models
{
    public class PasswordEntityBinder : IModelBinder
    {
        public Task BindModelAsync(ModelBindingContext bindingContext)
        {
            if (bindingContext == null)
                throw new ArgumentNullException(nameof(bindingContext));

            ValueProviderResult valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
            if (valueProviderResult == ValueProviderResult.None)
                return Task.CompletedTask;

            string? value = valueProviderResult.FirstValue;
            if (Password.TryParse(value.AsSpan(), out Password? result))
            {
                bindingContext.Result = ModelBindingResult.Success(result);
            }
            else
            {
                bindingContext.ModelState.TryAddModelError(bindingContext.ModelName, "密码强度不符合要求");
                bindingContext.Result = ModelBindingResult.Failed();
            }
            return Task.CompletedTask;
        }
    }
}
  • 将绑定器特性添加到Password类上,与[JsonConverter]共存:
[JsonConverter(typeof(PasswordJsonConverter))]
[ModelBinder(typeof(PasswordEntityBinder))]
public class Password
{
    // 内部代码保持不变
}
注意事项
  • 不要为整个DTO添加模型绑定器处理JSON请求,该方式会绕过默认JSON反序列化逻辑,增加额外维护成本
  • 已实现的TryParse方法覆盖了核心校验逻辑,JSON转换器、模型绑定器均可直接复用,无需重复编写校验代码
  • 校验失败抛出的JsonException、添加的ModelState错误,都会被[ApiController]的自动400响应机制捕获,无需在Controller中手动处理错误

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 12:27:25