自定义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不会生效。可以通过自定义模型绑定器实现全局字符串修剪:
- 创建字符串修剪模型绑定器:
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; } }
- 创建绑定器提供器:
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; } }
- 在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实现单个属性的字符串修剪:
- 创建自定义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); } }
- 在DTO属性上标记:
[TypeConverter(typeof(TrimStringConverter))] public string ShortName { get; set; }
内容的提问来源于stack exchange,提问作者Pawan Nogariya
相关产品推荐
相关产品推荐

