简体   繁体   English

Razor Pages - 如何使用带有抽象类的 EditorFor 模板

[英]Razor Pages - How to Use EditorFor Templates with Abstract Classes

I've tried a million and one different things and poured through and endless number of SO solutions, but I've yet to find a working solution.我已经尝试了一百万零一种不同的东西,并倾注了无数的 SO 解决方案,但我还没有找到一个可行的解决方案。 I have a page model that will contain a list of different classes all deriving from abstract class .我有一个页面模型,其中将包含所有从abstract class派生的不同类的列表。 I have different editor templates for each class type.对于每种类类型,我都有不同的编辑器模板。 So, when the page loads and my items get loaded in, the correct templates show up and everything is as expected.因此,当页面加载并且我的项目被加载时,正确的模板会显示出来,一切都如预期的那样。 However, things go wrong when I try to post my form back.但是,当我尝试将表单发回时,出现了问题。 I'm never getting the proper values and I end up getting an error about trying to submit a form expecting an abstract class/interface.我从来没有得到正确的值,我最终得到一个关于尝试提交一个需要抽象类/接口的表单的错误。

To take a step back and attempt to get the thought working in its simplest form, we have the following scenario:为了退后一步并尝试以最简单的形式让思想发挥作用,我们有以下场景:

public class Device
{
    public string Type => GetType().FullName;
}

public class Laptop : Device
{
    public string CPUIndex { get; set; }
}

public class SmartPhone : Device
{
    public string ScreenSize { get; set; }
}

TestPage.cshtml测试页.cshtml

<form method="post">
    @Html.EditorFor(m => m.Item, Model.Item.GetType().Name)

    <input type="submit" value="Submit"/>
</form>

Laptop Editor Template笔记本电脑编辑器模板

@model Laptop

<p>Laptop</p>
<input asp-for="CPUIndex"/>

TestPage.cs (Page model) TestPage.cs(页面模型)

public Device Item { get; set; }

public void OnGet()
{
    Item = new Laptop { CPUIndex = "Random Value" };
}

public void OnPost(Device item)
{

}

This will throw an exception when I post the form because I'm trying to access an abstract class.这将在我发布表单时引发异常,因为我正在尝试访问一个抽象类。 I have found here under the Polymorphic Binding where I need to create a custom binding.我在多态绑定下找到我需要创建自定义绑定的地方。

public class DeviceModelBinder : IModelBinder
{
    private Dictionary<Type, (ModelMetadata, IModelBinder)> binders;

    public DeviceModelBinder(Dictionary<Type, (ModelMetadata, IModelBinder)> binders)
    {
        this.binders = binders;
    }

    public async Task BindModelAsync(ModelBindingContext bindingContext)
    {
        var modelKindName = ModelNames.CreatePropertyModelName(bindingContext.ModelName, nameof(Device.Type));
        var modelTypeValue = bindingContext.ValueProvider.GetValue(modelKindName).FirstValue;

        IModelBinder modelBinder;
        ModelMetadata modelMetadata;
        if (modelTypeValue == "Laptop")
        {
            (modelMetadata, modelBinder) = binders[typeof(Laptop)];
        }
        else if (modelTypeValue == "SmartPhone")
        {
            (modelMetadata, modelBinder) = binders[typeof(SmartPhone)];
        }
        else
        {
            bindingContext.Result = ModelBindingResult.Failed();
            return;
        }

        var newBindingContext = DefaultModelBindingContext.CreateBindingContext(
            bindingContext.ActionContext,
            bindingContext.ValueProvider,
            modelMetadata,
            bindingInfo: null,
            bindingContext.ModelName);

        await modelBinder.BindModelAsync(newBindingContext);
        bindingContext.Result = newBindingContext.Result;

        if (newBindingContext.Result.IsModelSet)
        {
            // Setting the ValidationState ensures properties on derived types are correctly 
            bindingContext.ValidationState[newBindingContext.Result] = new ValidationStateEntry
            {
                Metadata = modelMetadata,
            };
        }
    }
}

public class DeviceModelBinderProvider : IModelBinderProvider
{
    public IModelBinder GetBinder(ModelBinderProviderContext context)
    {
        if (context.Metadata.ModelType != typeof(Device))
        {
            return null;
        }

        var subclasses = new[] { typeof(Laptop), typeof(SmartPhone), };

        var binders = new Dictionary<Type, (ModelMetadata, IModelBinder)>();
        foreach (var type in subclasses)
        {
            var modelMetadata = context.MetadataProvider.GetMetadataForType(type);
            binders[type] = (modelMetadata, context.CreateBinder(modelMetadata));
        }

        return new DeviceModelBinder(binders);
    }
}

Then, I register it in Startup.cs然后,我在 Startup.cs 中注册它

 services.AddMvc(options =>
            {
                options.ModelBinderProviders.Insert(0, new DeviceModelBinderProvider());
            })

Even with all of this, when I submit my form the value of item in OnPost is null.即使这一切,当我提交表单的值item在onPOST等为null。 What am I missing here?我在这里缺少什么?

I found it.我找到了。 The missing piece was that I wasn't putting an input field in the HTML to hold the Type property.缺失的部分是我没有在 HTML 中放置一个input字段来保存Type属性。

I changed my TestPage.cshtml from this我从此改变了我的TestPage.cshtml

<form method="post">
    @Html.EditorFor(m => m.Item, Model.Item.GetType().Name)

    <input type="submit" value="Submit"/>
</form>

To this对此

<form method="post">
    @Html.HiddenFor(m => m.Item.Type)
    @Html.EditorFor(m => m.Item, Model.Item.GetType().Name)

    <input type="submit" value="Submit"/>
</form>

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM