c# - ASP.NET Core:HttpPost:多个对象,相同的接口。如何在自定义绑定中使用多态性
问题描述
我有多个具有不同结构的对象,但每次我保存这些对象时,我都想执行常见的控制操作,因为这些对象实际上是类似过程的“内容对象”。
想象在 UI 中用户可以执行任务,他必须根据任务输入不同的数据,但我们停留在同一个“流程”中。
我想避免多个 HttPut Route,所以我想要这样的东西:
[HttpPut("{processId}/content")]
public ActionResult SaveContent(IContent content)
{
//Do something with the processID like security controll
//Now i can do the different save
}
我想要一个我的特定对象的实例,我可以使用属性“ContentType”确定类型
我想要一个 custombinder 来做这样的事情:
if(content.GetContent==ContentA)
{
return ContentA with all properties binded from body
}
else if(content.GetContent==ContentB)
{
return ContentB with all properties binded from body
}
我不想手动映射属性,我想像在 [FromBody] 参数中放入“ContentA”或“ContentB”一样工作。
我只是想避免这种情况:
[HttpPut("{processId}/content-a")]
public ActionResult SaveContentA(ContentA contentA)
{
//Do something with the processID like security control
//Now i can save contentA
}
[HttpPut("{processId}/content-b")]
public ActionResult SaveContentB(ContentB contentb)
{
//Do something with the processID like security control
//Now i can save contentB
}
我可以有 20 多个不同的内容,这是很多不同的路线(当然,我有不同的服务,根据内容 A 或 B 执行不同的操作,但我想避免尽可能多的路线)。
我看着自定义绑定的一面,但我没有设法做我想做的事。
谢谢你。
解决方案
我不确定我是否正确理解了您的要求。从您共享的内容来看,这IContent
是某种基本接口,其中包含所有派生类型(应该是众所周知的)的一组通用成员(属性,...)。所以它实际上是一个多态模型绑定的场景,可以通过使用客户来实现,IModelBinder
如这里的基本示例所示https://docs.microsoft.com/en-us/aspnet/core/mvc/advanced/custom-model- binding?view=aspnetcore-5.0#polymorphic-model-binding
我对该示例进行了一些调整,以使其更清晰,具有更多独立的责任和更好的命名。以下代码根本没有经过测试。因为主要逻辑与上面给定链接中的示例基本相同:
//the marker interface to indicate the model should be polymorphically bound.
//you can replace this with your IContent
public interface IPolymorphModel { }
//a separate resolver to help resolve actual model type from ModelBindingContext
//(kind of separating responsibility
public interface IPolymorphTypeResolver
{
Type ResolveType(ModelBindingContext modelBindingContext);
}
//a separate types provider to get all derived types from a base type/interface
public interface IPolymorphTypeProvider
{
IEnumerable<Type> GetDerivedTypesFrom<T>();
}
//the custom model binder for polymorphism
public class PolymorphModelBinder : IModelBinder
{
readonly IDictionary<Type, (ModelMetadata ModelMetadata, IModelBinder Binder)> _bindersByType;
readonly IPolymorphTypeResolver _polymorphTypeResolver;
public PolymorphModelBinder(IDictionary<Type, (ModelMetadata,IModelBinder)> bindersByType,
IPolymorphTypeResolver polymorphTypeResolver)
{
_bindersByType = bindersByType;
_polymorphTypeResolver = polymorphTypeResolver;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
var modelType = _polymorphTypeResolver.ResolveType(bindingContext);
if(modelType == null ||
!_bindersByType.TryGetValue(modelType, out var binder))
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
//create new binding context with the concrete/actual model type
var actualBindingContext = DefaultModelBindingContext.CreateBindingContext(bindingContext.ActionContext,
bindingContext.ValueProvider,
binder.ModelMetadata,
null,
bindingContext.ModelName);
await binder.Binder.BindModelAsync(actualBindingContext);
//set back the result to the original bindingContext
bindingContext.Result = actualBindingContext.Result;
if (actualBindingContext.Result.IsModelSet)
{
bindingContext.ValidationState[bindingContext.Result] = new ValidationStateEntry {
Metadata = binder.ModelMetadata
};
}
}
}
//the custom model binder provider for polymorphism
public class PolymorphModelBinderProvider<T> : IModelBinderProvider
{
readonly IPolymorphTypeResolver _polymorphTypeResolver;
readonly IPolymorphTypeProvider _polymorphTypeProvider;
public PolymorphModelBinderProvider(IPolymorphTypeResolver polymorphTypeResolver,
IPolymorphTypeProvider polymorphTypeProvider)
{
_polymorphTypeResolver = polymorphTypeResolver;
_polymorphTypeProvider = polymorphTypeProvider;
}
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (!typeof(T).IsAssignableFrom(context.Metadata.ModelType)) return null;
//prepare the list of the well-known derived types
var derivedTypes = _polymorphTypeProvider.GetDerivedTypesFrom<T>();
var binders = derivedTypes.ToDictionary(e => e,
e =>
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(e);
return (modelMetadata, context.CreateBinder(modelMetadata));
}
);
return new PolymorphModelBinder(binders, _polymorphTypeResolver);
}
}
这是 a 的默认实现,IPolymorphTypeResolver
它基本上依赖于IValueProvider
from the ModelBindingContext
(就像 Microsoft 示例中使用的一样):
public class DefaultPolymorphTypeResolver : IPolymorphTypeResolver
{
public Type ResolveType(ModelBindingContext modelBindingContext)
{
var contentTypeValueKey = ModelNames.CreatePropertyModelName(modelBindingContext.ModelName, "ContentType");
var contentType = modelBindingContext.ValueProvider.GetValue(contentTypeValueKey).FirstValue;
switch (contentType)
{
case "whatever ...":
return ...;
//...
default:
return null;
}
}
}
如果存储contentType
in 标头或其他一些来源,只需按照自己的方式实现即可,这里针对存储在请求标头中的内容类型:
public class DefaultPolymorphTypeResolver : IPolymorphTypeResolver {
public Type ResolveType(ModelBindingContext modelBindingContext) {
var contentType = modelBindingContext.ActionContext.HttpContext.Request.ContentType;
switch (contentType)
{
//...
default:
return null;
}
}
}
这是的默认实现IPolymorphTypeProvider
:
public class PolymorphContentTypeProvider : IPolymorphTypeProvider
{
public IEnumerable<Type> GetDerivedTypesFrom<T>()
{
return new List<Type> { /* your logic to populate the list ... */ };
}
}
现在我们将其配置为注册所需的所有内容,包括and和PolymorphModelBinderProvider
您的特定实现:IPolymorphTypeResolver
IPolymorphTypeProvider
//in ConfigureServices
services.AddSingleton<IPolymorphTypeResolver, DefaultPolymorphTypeResolver>();
services.AddSingleton<IPolymorphTypeProvider, PolymorphContentTypeProvider>();
services.AddOptions<MvcOptions>()
.Configure((MvcOptions o,
IPolymorphTypeResolver typeResolver,
IPolymorphTypeProvider typesProvider) =>
{
o.ModelBinderProviders.Insert(0, new PolymorphModelBinderProvider<IPolymorphModel>(typeResolver, typesProvider));
});
推荐阅读
- javascript - 在 nuxtjs serverMiddleware 中,setInterval 处理随着每次热重载而增加
- jq - jq:错误:语法错误,意外的 $end(Unix shell 引用问题?)在
,第 29 行:) - ffmpeg - 尝试使用 FFMPEG 将 webm 视频缩放到其分辨率的 50%,但不断出现错误(顺便说一句,视频具有透明度)
- c# - C# 动态 DataGridViewCell 点击事件
- python - 列表内的Python元组
- java - 解析为 SimpeDateFormat 时用户输入日期给出错误
- python - 如何使用熊猫读取最后一列宽度可变(参差不齐)的固定宽度文件?
- java - 如何更改 java 8 中的 user.dir?
- javascript - 如何将存储的散列密码与纯文本进行比较
- rust - 如何修复特征 `parity_scale_codec::codec::WrapperTypeEncode` 未针对`Hash`实现