我一直在尝试找出如何将自定义模型绑定与.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
}
}
答案 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
,因此没有合适的前缀。
为此,我们需要:
Type
-s。此处有有关创建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参数。
意识到您正在尝试根据在现有实例上动态计算的属性来确定类型(它们不是静态的)。通过了解当前的实现,虽然我知道这是可能的(创建模型的空实例,检查WhatType
属性,将实例扔掉),但这是非常糟糕的做法,因为没有任何东西可以保证实例属性是静态常数。
一个干净的解决方案将是一个属性,其中包含该类的WhatType编号。然后,我们可以考虑该属性并构建将int
映射到Type
的映射。如果这个问题不在讨论范围之内,但是如果您不熟悉,可以查阅任何自定义属性教程,并且可以很快地将其组合在一起。