ASP.NET Core Web API 2中的抽象类模型绑定

时间:2019-01-07 17:47:10

标签: c# asp.net-core-2.0 model-binding

我一直在尝试找出如何将自定义模型绑定与.net Core 2 Web API结合使用,但无法使其正常工作。

我看过以下几篇文章 http://www.palmmedia.de/Blog/2018/5/13/aspnet-core-model-binding-of-abstract-classes Asp net core rc2. Abstract class model binding

就我而言, bindingContext.ModelName 始终为空。谁能解释为什么会这样?

下面的示例实现

控制器

        public IActionResult SomeAction([ModelBinder(BinderType = typeof(BlahTypeModelBinder))][FromBody]TheBaseClass theBase)
    {
        return Ok();
    }

模型

public abstract class TheBaseClass
{
    public abstract int WhatType { get; }
}

public class A : TheBaseClass
{
    public override int WhatType { get { return 1; }  }
}

public class B : TheBaseClass
{
    public override int WhatType { get { return 2; } }
}

提供商

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

        if (context.Metadata.ModelType == typeof(TheBaseClass))
        {
            var assembly = typeof(TheBaseClass).Assembly;
            var abstractSearchClasses = assembly.GetExportedTypes()
                .Where(t => t.BaseType.Equals(typeof(TheBaseClass)))
                .Where(t => !t.IsAbstract)
                .ToList();

            var modelBuilderByType = new Dictionary<Type, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder>();

            foreach (var type in abstractSearchClasses)
            {
                var propertyBinders = new Dictionary<ModelMetadata, IModelBinder>();
                var metadata = context.MetadataProvider.GetMetadataForType(type);

                foreach (var property in metadata.Properties)
                {
                    propertyBinders.Add(property, context.CreateBinder(property));
                }

                modelBuilderByType.Add(type, new Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder(propertyBinders));
            }

            return new BlahTypeModelBinder(modelBuilderByType, context.MetadataProvider);
        }

        return null;
    }
}

活页夹

public class BlahTypeModelBinder : IModelBinder
{
    private readonly IModelMetadataProvider _metadataProvider;
    private readonly IDictionary<Type, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder> _binders;

    public BlahTypeModelBinder(IDictionary<Type, Microsoft.AspNetCore.Mvc.ModelBinding.Binders.ComplexTypeModelBinder> binders, IModelMetadataProvider metadataProvider)
    {
        _metadataProvider = metadataProvider;
        _binders = binders;
    }

    public Task BindModelAsync(ModelBindingContext bindingContext)
    {
        if (bindingContext == null)
            throw new ArgumentNullException(nameof(bindingContext));

        var modelTypeValue = bindingContext.ValueProvider.GetValue(ModelNames.CreatePropertyModelName(bindingContext.ModelName, "WhatType"));
        if (modelTypeValue != null && modelTypeValue.FirstValue != null)
        {
            Type modelType = Type.GetType(modelTypeValue.FirstValue);
            if (this._binders.TryGetValue(modelType, out var modelBinder))
            {
                ModelBindingContext innerModelBindingContext = DefaultModelBindingContext.CreateBindingContext(
                    bindingContext.ActionContext,
                    bindingContext.ValueProvider,
                    this._metadataProvider.GetMetadataForType(modelType),
                    null,
                    bindingContext.ModelName);

                /*modelBinder*/
                this._binders.First().Value.BindModelAsync(innerModelBindingContext);

                bindingContext.Result = innerModelBindingContext.Result;
                return Task.CompletedTask;
            }
        }

       //More code
    }
}

2 个答案:

答案 0 :(得分:2)

我终于设法解决了这个问题。您不需要提供程序。只是以下活页夹有效

["# Bob's markdown header 1\n\nsomething here.\n\n## this is markdown header 2\n\nyeah.\n\n", "# kitty's header 1\n\nmeow.\n\n"]

答案 1 :(得分:0)

您链接的示例使用外部查询字符串参数来确定类型。

如果您这样调用操作SomeAction?WhatType=YourNamespaceName.A绑定将按预期工作。

bindingContext.ModelName为空就可以了,可以在模型绑定之后进行设置。如果需要,可以在设置bindingContext.Result之后进行设置。参数WhatType来自QueryStringValueProvider,因此没有合适的前缀。

如何仅基于JSON完成抽象模型绑定

为此,我们需要:

  1. 一个值提供程序,用于读取JSON并为我们提供一些“ WhatType”值,以代替QueryStringValueProvider。
  2. 一些反射将提取的数字映射到Type-s。

1。 ValueProvider

此处有有关创建ValueProviders的详细文章:

从此处开始的是一些代码,该代码成功地从正文json中提取了WhatType整数:

    public class BlahValueProvider : IValueProvider
{
    private readonly string _requestBody;

    public BlahValueProvider(string requestBody)
    {
        _requestBody = requestBody;
    }

    private const string PROPERTY_NAME = "WhatType";

    public bool ContainsPrefix(string prefix)
    {
        return prefix == PROPERTY_NAME;
    }

    public ValueProviderResult GetValue(string key)
    {
        if (key != PROPERTY_NAME)
            return ValueProviderResult.None;

        // parse json

        try
        {
            var json = JObject.Parse(_requestBody);
            return new ValueProviderResult(json.Value<int>("WhatType").ToString());
        }
        catch (Exception e)
        {
            // TODO: error handling
            throw;
        }
    }
}

public class BlahValueProviderFactory : IValueProviderFactory
{
    public Task CreateValueProviderAsync(ValueProviderFactoryContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        var request = context.ActionContext.HttpContext.Request;
        if (request.ContentType == "application/json")
        {
            return AddValueProviderAsync(context);
        }

        return Task.CompletedTask;
    }

    private Task AddValueProviderAsync(ValueProviderFactoryContext context)
    {
        using (StreamReader sr = new StreamReader(context.ActionContext.HttpContext.Request.Body))
        {
            string bodyString = sr.ReadToEnd();
            context.ValueProviders.Add(new BlahValueProvider(bodyString));
        }

        return Task.CompletedTask;
    }
}

当然您必须在注册模型资料夹的同时在Startup.cs中注册该工厂。而且这绝对会错过将提取的数字转换为实际Type的信息(为此,请参见下面的第2点),但是如果您在一行上以if (modelTypeValue != null开头的断点,则即使没有单独的GET参数。

2。反射

意识到您正在尝试根据在现有实例上动态计算的属性来确定类型(它们不是静态的)。通过了解当前的实现,虽然我知道这是可能的(创建模型的空实例,检查WhatType属性,将实例扔掉),但这是非常糟糕的做法,因为没有任何东西可以保证实例属性是静态常数。

一个干净的解决方案将是一个属性,其中包含该类的WhatType编号。然后,我们可以考虑该属性并构建将int映射到Type的映射。如果这个问题不在讨论范围之内,但是如果您不熟悉,可以查阅任何自定义属性教程,并且可以很快地将其组合在一起。