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

自定义JsonConverter对[FromForm]表单数据失效的解决方法问询

自定义JsonConverter在[FromForm]接口中失效的解决方案

问题描述

我给Web API添加了自定义TrimStringJsonConverter,用于反序列化时自动修剪所有字符串,注册代码如下:

options.JsonSerializerOptions.Converters.Add(new TrimStringJsonConverter());

该转换器在使用[FromBody]读取JSON请求体的接口中正常工作,但在使用[FromForm]接收multipart/form-data类型表单数据的接口里完全失效。

有效接口示例:

[HttpPost("fund/add-update")]
public async Task<ActionResult<ResponseDto<string>>> AddUpdateFund([FromBody] AddUpdateFundDto fund)
{
}

失效接口示例:

[HttpPost("fund-house/add-update")]
[Consumes("multipart/form-data")]
[Produces("application/json")]
public async Task<ActionResult<ResponseDto<string>>> AddUpdateFundHouse([FromForm] AddUpdateFundHouseDto model)
{
}

我推测这是设计特性(表单序列化逻辑与JSON序列化不同),但仍希望找到让该转换器对表单数据生效的办法。

补充:前端Angular构造FormData并调用接口的代码:

let formData = new FormData();
formData.append('id', this.addFundHouseFormControl.id.value);
formData.append('shortName', this.addFundHouseFormControl.shortName.value);
formData.append('fullName', this.addFundHouseFormControl.fullName.value);
formData.append('logoImage', this.selectedLogoImage);

this.adminService.addUpdateFundHouse(formData);

AdminService代码:

addUpdateFundHouse(dto: FormData) {
    return this.http.post<IResponse<boolean>>(`${environment.apiUrl}/admin/fund-house/add-update`, dto)
        .pipe(map((response) => {
            return response;
        }));
}

解决方案

方法1:全局自定义模型绑定器

[FromForm]依赖ASP.NET Core的模型绑定系统,而非JSON序列化,因此JsonConverter不会生效。可以通过自定义模型绑定器实现全局字符串修剪:

  1. 创建字符串修剪模型绑定器:
public class TrimStringModelBinder : IModelBinder
{
    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

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

        bindingContext.ModelState.SetModelValue(bindingContext.ModelName, valueProviderResult);

        var value = valueProviderResult.FirstValue;
        if (!string.IsNullOrWhiteSpace(value))
            bindingContext.Result = ModelBindingResult.Success(value.Trim());

        return Task.CompletedTask;
    }
}
  1. 创建绑定器提供器:
public class TrimStringModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        if (context.Metadata.ModelType == typeof(string))
            return new TrimStringModelBinder();

        return null;
    }
}
  1. 在Program.cs中注册:
builder.Services.AddControllers(options =>
{
    // 插入到最前面,确保优先使用自定义绑定器
    options.ModelBinderProviders.Insert(0, new TrimStringModelBinderProvider());
});

方法2:DTO属性Setter手动修剪

如果不需要全局生效,可以在目标DTO的字符串属性Setter中直接处理:

public class AddUpdateFundHouseDto
{
    private string _shortName;
    public string ShortName
    {
        get => _shortName;
        set => _shortName = value?.Trim();
    }

    // 其他字符串属性同理处理
}

方法3:自定义TypeConverter标记属性

通过TypeConverter实现单个属性的字符串修剪:

  1. 创建自定义TypeConverter:
public class TrimStringConverter : TypeConverter
{
    public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    {
        return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType);
    }

    public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
    {
        if (value is string str)
            return str.Trim();
        return base.ConvertFrom(context, culture, value);
    }
}
  1. 在DTO属性上标记:
[TypeConverter(typeof(TrimStringConverter))]
public string ShortName { get; set; }

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 05:03:23