[英]Dynamically binding models at runtime in ASP.NET Core MVC
我正在使用 ASP.NET Core MVC 3.1 開發 Web 應用程序。 我正在實施一個系統,人們可以在其中請求事物,然后進入通用的“請求”流程。 因為我不想創建幾十個 80% 相似的控制器和視圖,所以我正在考慮動態綁定模型並使用部分視圖來處理不同的事情。
為此,我想覆蓋模型綁定器行為,以便它可以在運行時從正確的類型進行綁定。 我找到了一些關於如何使用“經典”ASP.NET MVC 執行此操作的指南 [1],但這在 ASP.NET Core MVC 中似乎不起作用,因為這已全部重新設計。
我發現ComplexModelTypeBinder
可能是我需要的,但是繼承和覆蓋它並沒有讓我更進一步,因為 BindingContext 上的很多屬性現在都是只讀的。
如何在 ASP.NET Core MVC 中實現相同的目標?
我可以給你一個出發點。
本着與您鏈接的文章相同的精神,讓我們定義一些與寵物相關的類型:
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>
最后,一個簡單的控制器動作,它將一個IPet
實例作為參數:
public IActionResult BindPet(IPet pet)
{
return RedirectToAction("Index");
}
現在,創建一個像這樣的多態綁定器有 3 個部分:
IModelBinder
IModelBinderProvider
的類型,它將用於創建我們的IModelBinder
實例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
};
}
}
接下來,讓我們實現IModelBinderProvider
:
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);
}
}
如您所見,這比活頁夾本身簡單得多,只不過是一個光榮的工廠。 它查詢每個具體類型的元數據,並創建一個可以處理每種類型的綁定器,並將它們傳遞給我們的綁定器。
最后,在Startup
,我們需要注冊IModelBinderProvider
以供使用:
services.AddControllersWithViews(options =>
{
options.ModelBinderProviders.Insert(0, new PetModelBinderProvider());
});
0
表示模型綁定器的優先級。 這確保我們的活頁夾將首先被檢查。 如果我們不這樣做,另一個綁定器會嘗試綁定類型,但失敗了。
現在已經完成了,啟動調試器,在我們創建的 action 方法中放置一個斷點,然后嘗試提交表單。 檢查IPet
的實例,您應該會看到為Fish
設置的HasFins
屬性。 將PetType
元素編輯為Dog
,重復上述操作,您應該會看到HasTail
被設置。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.