C# ASP.NET核心MVC中运行时动态绑定模型
我正在使用ASP.NET核心MVC 3.1开发一个web应用程序。我正在实现一个系统,人们可以在其中请求东西,然后进入一个通用的“请求”流。因为我不想创建几十个80%相似的控制器和视图,所以我考虑动态绑定模型,并使用部分视图处理不同的内容 为此,我希望重写模型绑定器行为,以便它可以在运行时从正确的类型绑定。我找到了一些关于如何使用“经典”ASP.NET MVC实现这一点的指导[1],但这在ASP.NET核心MVC中似乎不起作用,因为这些都经过了重新设计 我发现,C# ASP.NET核心MVC中运行时动态绑定模型,c#,asp.net,asp.net-mvc,asp.net-core,C#,Asp.net,Asp.net Mvc,Asp.net Core,我正在使用ASP.NET核心MVC 3.1开发一个web应用程序。我正在实现一个系统,人们可以在其中请求东西,然后进入一个通用的“请求”流。因为我不想创建几十个80%相似的控制器和视图,所以我考虑动态绑定模型,并使用部分视图处理不同的内容 为此,我希望重写模型绑定器行为,以便它可以在运行时从正确的类型绑定。我找到了一些关于如何使用“经典”ASP.NET MVC实现这一点的指导[1],但这在ASP.NET核心MVC中似乎不起作用,因为这些都经过了重新设计 我发现,ComplexModelTypeB
ComplexModelTypeBinder
可能是我所需要的,但是继承和重写它并不能让我走得更远,因为BindingContext上的许多属性现在都是只读的
如何在ASP.NET核心MVC中实现相同的目标
[1] 我可以给你一个出发点 本着与您链接的文章相同的精神,让我们定义一些与宠物相关的类型:
public interface IPet
{
string Name { get; }
}
public class Cat : IPet
{
public string Name => "Cat";
public bool HasTail { get; set; }
}
public class Dog : IPet
{
public string Name => "Dog";
public bool HasTail { get; set; }
}
public class Fish : IPet
{
public string Name => "Fish";
public bool HasFins { get; set; }
}
在一个视图中,定义我们可以使用的以下形式:
<form asp-action="BindPet" method="post">
<input type="hidden" name="PetType" value="Fish" />
<input type="hidden" name="pet.HasTail" value="true" />
<input type="hidden" name="pet.HasFins" value="true" />
<input type="submit" />
</form>
现在,有3个部分可以创建这样的多态绑定器:
IModelBinder
IModelBinderProvider
的类型,该类型将用于创建我们的IModelBinder
IModelBinderProvider
类型以便可以使用启动
中,我们需要注册IModelBinderProvider
以供使用:
services.AddControllersWithViews(options =>
{
options.ModelBinderProviders.Insert(0, new PetModelBinderProvider());
});
0
表示模型绑定器的优先级。这确保我们的活页夹将首先被检查。如果我们不这样做,另一个绑定器将尝试绑定该类型,但失败
完成后,启动调试器,在我们创建的操作方法中放置一个断点,然后尝试提交表单。检查IPet
的实例,您应该会看到正在为Fish
设置HasFins
属性。将PetType
元素编辑为Dog
,重复上述操作,您将看到设置了HasTail
IModelBinderProvider是我无法理解的东西,但现在由于您的出色示例,我已经能够创建我需要的东西。谢谢你的帮助!
public class PetModelBinder : IModelBinder
{
private readonly IDictionary<Type, (ModelMetadata, IModelBinder)> _binders;
public PetModelBinder(IDictionary<Type, (ModelMetadata, IModelBinder)> binders)
{
_binders = binders;
}
public async Task BindModelAsync(ModelBindingContext bindingContext)
{
// Read our expected type from a form,
// and convert to its .NET type.
var petType = bindingContext.ActionContext.HttpContext.Request.Form["PetType"];
var actualType = TypeFrom(petType);
// No point continuing if a valid type isn't found.
if (actualType == null)
{
bindingContext.Result = ModelBindingResult.Failed();
return;
}
// This will become clearer once we see how _binders
// is populated in our IModelBinderProvider.
var (modelMetadata, modelBinder) = _binders[actualType];
// Create a new binding context, as we have provided
// type information the framework didn't know was available.
var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
bindingContext.ActionContext,
bindingContext.ValueProvider,
modelMetadata,
bindingInfo: null,
bindingContext.ModelName);
// This tries to bind the actual model using our
// context, setting its Result property to the bound model.
await modelBinder.BindModelAsync(newBindingContext);
bindingContext.Result = newBindingContext.Result;
// Sets up model validation.
if (newBindingContext.Result.IsModelSet)
{
bindingContext.ValidationState[newBindingContext.Result] = new ValidationStateEntry
{
Metadata = modelMetadata,
};
}
}
private static Type? TypeFrom(string name)
{
return name switch
{
"Cat" => typeof(Cat),
"Dog" => typeof(Dog),
"Fish" => typeof(Fish),
_ => null
};
}
}
public class PetModelBinderProvider : IModelBinderProvider
{
public IModelBinder GetBinder(ModelBinderProviderContext context)
{
if (context.Metadata.ModelType != typeof(IPet))
{
return null;
}
var pets = new[] { typeof(Cat), typeof(Dog), typeof(Fish) };
var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
foreach (var type in pets)
{
var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
}
return new PetModelBinder(binders);
}
}
services.AddControllersWithViews(options =>
{
options.ModelBinderProviders.Insert(0, new PetModelBinderProvider());
});