簡體   English   中英

在 ASP.NET Core MVC 中在運行時動態綁定模型

[英]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 中實現相同的目標?

[1] ASP.NET 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 個部分:

  1. 創建模型綁定器,實現IModelBinder
  2. 創建一個實現IModelBinderProvider的類型,它將用於創建我們的IModelBinder實例
  3. 注冊我們的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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM