问题描述
我需要对字符串数据进行规范化(将一些字符相互替换,例如:'ی' 与 'ي' 或修剪它)。为此,我创建了以下模型绑定器,如下所示:
public class StringModelBinder : IModelBinder
{
public Task BindModelAsync(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
return Task.CompletedTask;
var value = Normalize(valueProviderResult.FirstValue);
bindingContext.Result = ModelBindingResult.Success(value);
return Task.CompletedTask;
}
}
此活页夹适用于 Query
和 Route
绑定,但如果我使用 FromBody
属性则失败。它失败是因为 BindModelAsync
永远不会被调用。我发现针对此问题 here 提出的另一个问题,遗憾的是没有答案。
我尝试扩展 ComplexObjectModelBinder
但它是一个 sealed
类(并且也不提供任何构造函数)。所以我尝试扩展 ComplexTypeModelBinder
,它被注释为已过时。
我已经从 source code 复制了 ComplexTypeModelBinderProvider
的逻辑,令我惊讶的是,我的 BindModelAsync
的 StringModelBinder
现在收到了调用。但仍然失败,因为 bindingContext.ValueProvider
只包含一个路由提供者,结果仍然为空。
我现阶段的活页夹提供者:
public class MyModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.Metadata.IsComplexType && !context.Metadata.IsCollectionType)
{
var propertyBinders = new Dictionary<ModelMetadata,IModelBinder>();
for (var i = 0; i < context.Metadata.Properties.Count; i++)
{
var property = context.Metadata.Properties[i];
propertyBinders.Add(property,context.CreateBinder(property));
}
var loggerFactory = context.Services.GetRequiredService<ILoggerFactory>();
return new ComplexTypeModelBinder(
propertyBinders,loggerFactory,allowValidatingTopLevelNodes: true);
}
if (context.Metadata.ModelType == typeof(string))
{
return new StringModelBinder();
}
return null;
}
}
我还尝试从正文创建一个提供程序并将我的 StringModelBinder
更改为:
public class StringModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var valueProviderResult = bindingContext.ValueProvider.GetValue(bindingContext.ModelName);
if (valueProviderResult == ValueProviderResult.None)
{
var context = new ValueProviderFactoryContext(bindingContext.ActionContext);
await new FormValueProviderFactory().CreateValueProviderAsync(context);
valueProviderResult = context.ValueProviders
.Select(x => x.GetValue(bindingContext.ModelName))
.FirstOrDefault(x => x != ValueProviderResult.None);
if (valueProviderResult == ValueProviderResult.None) return;
}
var value = valueProviderResult.FirstValue.Replace("A","B");
bindingContext.Result = ModelBindingResult.Success(value);
}
}
现在的问题是,在 .Net 5 中进行这种标准化的最佳方法是什么?
可能关心的人:这个问题可能看起来重复,但我找不到与 .Net 5 相关的任何内容,如果有问题回答 ComplexTypeModelBinder
问题,它将不适合 .Net 5因为它已经过时了。
解决方法
FromBody
不同于 FromQuery
和其他 HTTP 动词。
在复杂模型绑定 (FromBody) 中,您可以在 bindingContext.HttpContext.Request.Body
中获取它们。
public class StringModelBinder : IModelBinder
{
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
using (var reader = new StreamReader(bindingContext.HttpContext.Request.Body))
{
var body = reader.ReadToEndAsync();
var mydata = body.Result;
//...
bindingContext.Result = ModelBindingResult.Success(mydata);
}
//...
}
}
动作
[HttpPost]
public IActionResult test1([ModelBinder(binderType: typeof(StringModelBinder))]string model)
{
return Ok(model);
}
然后,将字符串传递给操作。
在StringModelBinder
获取。
您可以实现自定义字符串转换器:
public class CustomStringConverter : System.Text.Json.Serialization.JsonConverter<string>
{
public override string Read(ref Utf8JsonReader reader,Type typeToConvert,JsonSerializerOptions options)
{
var value = reader.GetString();
// Do your stuffs with value
return value;
}
public override void Write(Utf8JsonWriter writer,string value,JsonSerializerOptions options)
{
writer.WriteStringValue(value);
}
}
然后注册:
services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new CustomStringConverter());
});