使用 Binding ASP.Net Core 5 规范化字符串输入

Normalize string inputs with Binding ASP.Net Core 5

我需要对字符串数据进行规范化(将一些字符相互替换,例如:将“ь”替换为“ي”或trim)。为此,我创建了如下模型活页夹:

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;
    }
}

此活页夹适用于 QueryRoute 绑定,但如果我使用 FromBody 属性则失败。它失败了,因为 BindModelAsync 永远不会被调用。我发现了针对此问题提出的另一个问题 here,遗憾的是没有答案。

我试图扩展 ComplexObjectModelBinder 但它是一个 sealed class (并且也不提供任何构造函数)。所以我尝试扩展 ComplexTypeModelBinder,它被注释为已过时。

我已经从 source code 复制了 ComplexTypeModelBinderProvider 的逻辑,令我惊讶的是,我的 StringModelBinderBindModelAsync 现在可以收到呼叫。但仍然失败,因为 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。 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());
            });