asp.net core web api 2中的抽象类模型绑定

Ami*_*esh 6 c# model-binding asp.net-core-2.0

我一直在尝试弄清楚如何将自定义模型绑定与 .net Core 2 Web api 结合使用,但未能使其正常工作。

我已经阅读了一些文章,如下 http://www.palmmedia.de/Blog/2018/5/13/aspnet-core-model-binding-of-abstract-classes Asp net core rc2。抽象类模型绑定

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

下面的示例实现

控制器

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

楷模

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; } }
}
Run Code Online (Sandbox Code Playgroud)

提供者

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

活页夹

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
    }
}
Run Code Online (Sandbox Code Playgroud)

Ami*_*esh 6

我终于设法解决了这个问题。您不需要提供者。只需以下活页夹即可工作

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

        var json = ExtractRequestJson(bindingContext.ActionContext);
        var jObject = Newtonsoft.Json.Linq.JObject.Parse(json);
        var whatTypeInt = (int)jObject.SelectToken("WhatType");

        if (whatTypeInt == 1)
        {
            var obj = DeserializeObject<A>(json);
            bindingContext.Result = ModelBindingResult.Success(obj);
        }
        else if (whatTypeInt == 2)
        {
            var obj = DeserializeObject<B>(json);
            bindingContext.Result = ModelBindingResult.Success(obj);
        }
        else
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return Task.CompletedTask;
        }

        return Task.CompletedTask;
    }

    private static string ExtractRequestJson(ActionContext actionContext)
    {
        var content = actionContext.HttpContext.Request.Body;
        return new StreamReader(content).ReadToEnd();
    }

    private static T DeserializeObject<T>(string json)
    {
        return Newtonsoft.Json.JsonConvert.DeserializeObject<T>(json, new Newtonsoft.Json.JsonSerializerSettings
        {
            TypeNameHandling = Newtonsoft.Json.TypeNameHandling.Auto
        });
    }
}
Run Code Online (Sandbox Code Playgroud)