首页 > 解决方案 > .Net Core 2.2 Web API 中的模型绑定 JSON

问题描述

我只是浪费了很多时间试图让一个习惯ComplexTypeModelBinder起作用。无论我做什么,它都没有奏效。事实证明,这仅在数据作为表单数据 POST 时才有效;当您发布 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; }
}

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

[HttpPost]
public MyComplexModel Post(MyComplexModel model)
{
    return model;
}

我的(不工作)自定义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);
        }
    }
}

(实际MyModelBinder检查FormatNumber属性并相应地处理属性,但为了简洁起见,我将其省略,因为它并不重要)。

我的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))
        );
    }
}

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

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

同样,当数据作为表单数据发布时,这可以正常工作,但在作为 JSON 发布时则不行。实现这一点的正确方法是什么?我在某处读到我不应该朝这个ModelBinding方向看,而是朝“JSON 转换器”方向看,但我还没有找到任何实际工作的东西(还)。


编辑:我创建了一个 git 存储库来在这里演示我的问题。要查看我的问题,请在方法中返回模型的位置设置断点。启动项目;将显示一个带有两个按钮的简单网页。左边的将表单数据作为表单数据发布,您将看到返回的模型带有反转的电话号码(作为示例)。单击右键,数据将作为 JSON 模型发布。请注意,返回的模型具有 0 id 和两个属性的值。TestControllerPostnull

标签: c#asp.net-web-apiasp.net-coremodel-bindingasp.net-core-webapi

解决方案


推荐阅读