在.Net Core 2.2 Web API中绑定JSON的模型

Rob*_*III 6 c# model-binding asp.net-web-api asp.net-core asp.net-core-webapi

我只是浪费了很多时间来尝试定制ComplexTypeModelBinder工作.无论我做了什么,它都没有奏效.事实证明,这仅在数据作为表单数据发布时有效; 当你发布一个JSON对象(在我的情况下从Swagger"试用"表单)时,ComplexTypeModelBinder永远不会调用该SetProperty方法.

我有很多模型,有些比其他更复杂,我用自定义属性注释了一些属性.每当该属性被绑定时,我希望它" 标准化 "(对它应用一些"格式化"),以便在模型得到验证时,验证器可以看到"标准化"数据而不是用户输入的数据.

我真的,真的,想要保持当前的模型绑定行为,因为它目前工作正常但有一个例外,即注释属性由我实现的一些代码处理.所有其他属性和行为应保持原样.这就是我希望继承的原因ComplexTypeModelBinder,但是,事实证明,如果数据以JSON的形式发布,则这不起作用.

我的(示例)模型看起来像:

public class MyComplexModel
{
    public int Id { get; set; }
    public string Name { get; set; }

    [FormatNumber(NumberFormat.E164)]
    public string PhoneNumber { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我的控制器方法如下所示:

[HttpPost]
public MyComplexModel Post(MyComplexModel model)
{
    return model;
}
Run Code Online (Sandbox Code Playgroud)

我的(不工作)自定义ComplexTypeModelBinder看起来像:

public class MyModelBinder : ComplexTypeModelBinder
{
    private readonly INumberFormatter _numberformatter;

    private static readonly ConcurrentDictionary<Type, Dictionary<string, FormatNumberAttribute>> _formatproperties = new ConcurrentDictionary<Type, Dictionary<string, FormatNumberAttribute>>();

    public MyModelBinder(IDictionary<ModelMetadata, IModelBinder> propertyBinders, INumberFormatter numberFormatter, ILoggerFactory loggerFactory)
        : base(propertyBinders, loggerFactory)
    {
        _numberformatter = numberFormatter;
    }

    protected override object CreateModel(ModelBindingContext bindingContext)
    {
        // Index and cache all properties having the FormatNumber Attribute
        _formatproperties.GetOrAdd(bindingContext.ModelType, (t) =>
        {
            return t.GetProperties().Where(prop => Attribute.IsDefined(prop, typeof(FormatNumberAttribute))).ToDictionary(pi => pi.Name, pi => pi.GetCustomAttribute<FormatNumberAttribute>(), StringComparer.OrdinalIgnoreCase);
        });
        return base.CreateModel(bindingContext);
    }

    protected override Task BindProperty(ModelBindingContext bindingContext)
    {
        return base.BindProperty(bindingContext);
    }

    protected override void SetProperty(ModelBindingContext bindingContext, string modelName, ModelMetadata propertyMetadata, ModelBindingResult result)
    {
        if (_formatproperties.TryGetValue(bindingContext.ModelType, out var props) && props.TryGetValue(modelName, out var att))
        {
            // Do our formatting here
            var formatted = _numberformatter.FormatNumber(result.Model as string, att.NumberFormat);
            base.SetProperty(bindingContext, modelName, propertyMetadata, ModelBindingResult.Success(formatted));
        } else
        {
            // Do nothing
            base.SetProperty(bindingContext, modelName, propertyMetadata, result);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

(MyModelBinderFormatNumber属性的实际检查并相应地处理属性,但为了简洁起见,我将其留下来,因为它并不重要).

我的ModelBinderProvider:

public class MyModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context == null)
            throw new ArgumentNullException(nameof(context));

        var modelType = context.Metadata.ModelType;
        if (!typeof(MyComplexModel).IsAssignableFrom(modelType))
            return null;

        if (!context.Metadata.IsComplexType || context.Metadata.IsCollectionType)
            return null;

        var propertyBinders = context.Metadata.Properties
            .ToDictionary(modelProperty => modelProperty, context.CreateBinder);

        return new MyModelBinder(
            propertyBinders,
            (INumberFormatter)context.Services.GetService(typeof(INumberFormatter)),
            (ILoggerFactory)context.Services.GetService(typeof(ILoggerFactory))
        );
    }
}
Run Code Online (Sandbox Code Playgroud)

当然,我在StartUp课堂上添加了提供者:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc(config =>
    {
        config.ModelBinderProviders.Insert(0, new MyModelBinderProvider());
    }).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
}
Run Code Online (Sandbox Code Playgroud)

同样,当数据作为表单数据发布而不是作为JSON发布时,这可以正常工作.实现这个的正确方法是什么?我已经读过某个地方,我不应该ModelBinding朝着方向看,而是在"JSON转换器"方向,但我还没有找到任何实际工作的东西(还).


编辑:我已经创建了一个Git仓库来证明我的问题在这里.要看到我的问题,设置一个断点在这里TestController这里的模型在返回的Post方法.启动项目; 一个简单的网页将显示两个按钮.左边的一个会将表单数据发布为表单数据,您将看到返回的模型带有反向的语音(例如).单击右侧按钮,数据将作为JSON模型发布.请注意,返回的模型具有0个id和null两个属性的值.