为了账号安全,请及时绑定邮箱和手机立即绑定

多态模型绑定

/ 猿问

多态模型绑定

慕工程0101907 2019-10-05 03:03:06

多态模型绑定

这个问题问过在早期的MVC版本中。也有这篇博客文章一个解决问题的方法。我想知道MVC 3是否引入了可能有帮助的东西,或者是否还有其他选择。

总之。情况就是这样。我有一个抽象的基模型和两个具体的子类。我有一个强类型视图,它将模型呈现为EditorForModel()..然后,我有自定义模板来呈现每个具体类型。

这个问题是在事后发生的。如果我使POST操作方法以基类为参数,那么MVC就不能创建它的抽象版本(无论如何,我都不希望它创建实际的具体类型)。如果我创建了多个POST操作方法,这些方法仅随参数签名而变化,那么MVC会抱怨它不明确。

据我所知,关于如何解决这个问题,我有几个选择。由于种种原因,我不喜欢它们中的任何一个,但我会在这里列出它们:

  1. 创建自定义模型绑定,正如Darin在我链接的第一篇文章中所建议的那样。
  2. 创建一个区分器属性,作为我链接到的第二个帖子。
  3. 基于类型的发布到不同的动作方法
  4. ???

我不喜欢1,因为它基本上是隐藏的配置。其他一些从事代码开发的开发人员可能不知道这一点,并且浪费了大量的时间试图找出为什么在更改时事情会中断。

我不喜欢2,因为它看起来有点烦人。但是,我倾向于这种方法。

我不喜欢3,因为那意味着违反了干法。

还有其他建议吗?

编辑:

我决定采用达林的方法,但做了一点小小的改变。我将其添加到抽象模型中:

[HiddenInput(DisplayValue = false)]public string ConcreteModelType { get { return this.GetType().ToString(); }}

然后在我的DisplayForModel()..你唯一要记住的是如果你不使用DisplayForModel()你得自己加进去。



查看完整描述

3 回答

?
烧仙草VB

由于我显然选择了备选案文1(:-),让我再详细说明一下,这样就更少了易碎避免将具体实例硬编码到模型绑定中。其思想是将具体类型传递到隐藏字段中,并使用反射来实例化具体类型。

假设您有以下视图模型:

public abstract class BaseViewModel{
    public int Id { get; set; }}public class FooViewModel : BaseViewModel{
    public string Foo { get; set; }}

下列控制器:

public class HomeController : Controller{
    public ActionResult Index()
    {
        var model = new FooViewModel { Id = 1, Foo = "foo" };
        return View(model);
    }

    [HttpPost]
    public ActionResult Index(BaseViewModel model)
    {
        return View(model);
    }}

对应Index意见:

@model BaseViewModel@using (Html.BeginForm()){
    @Html.Hidden("ModelType", Model.GetType())    
    @Html.EditorForModel()
    <input type="submit" value="OK" />}

~/Views/Home/EditorTemplates/FooViewModel.cshtml编辑器模板:

@model FooViewModel@Html.EditorFor(x => x.Id)@Html.EditorFor(x => x.Foo)

现在我们可以使用以下定制的模型绑定:

public class BaseViewModelBinder : DefaultModelBinder{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        var typeValue = bindingContext.ValueProvider.GetValue("ModelType");
        var type = Type.GetType(
            (string)typeValue.ConvertTo(typeof(string)),
            true
        );
        if (!typeof(BaseViewModel).IsAssignableFrom(type))
        {
            throw new InvalidOperationException("Bad Type");
        }
        var model = Activator.CreateInstance(type);
        bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => model, type);
        return model;
    }}

的值推断实际类型。ModelType隐藏的领域。它不是硬编码的,这意味着您以后可以添加其他子类型,而不必碰这个模型绑定器。

同样的技术易于应用到基本视图模型的集合。




查看完整回答
反对 回复 2019-10-13
?
慕码人2483693

我刚刚想出了解决这个问题的办法。而不是像这样使用Parameterbsed模型绑定:

[HttpPost]public ActionResult Index(MyModel model) {...}

我可以使用TryUpdateModel()来确定在代码中绑定到哪种模型。例如,我做了这样的事情:

[HttpPost]public ActionResult Index() {...}{
    MyModel model;
    if (ViewData.SomeData == Something) {
        model = new MyDerivedModel();
    } else {
        model = new MyOtherDerivedModel();
    }

    TryUpdateModel(model);

    if (Model.IsValid) {...}

    return View(model);}

这实际上要好得多,因为如果我正在进行任何处理,那么我将不得不将模型转换为任何实际情况,或者使用is找出要使用AutoMapper调用的正确地图。

我想我们中那些从第一天起就没有使用MVC的人忘记了UpdateModelTryUpdateModel,但它仍然有它的用途。



查看完整回答
反对 回复 2019-10-13
?
摇曳的蔷薇

我花了一天时间想出了一个与此密切相关的问题的答案-虽然我不确定这是同一个问题,但我在这里张贴它,以防其他人正在寻找解决同样问题的方法。

在我的例子中,对于许多不同的视图模型类型,我有一个抽象的基类型。所以在主视图模型中,我有一个抽象基类型的属性:

class View{
    public AbstractBaseItemView ItemView { get; set; }}

我有许多抽象BaseItemView的子类型,其中许多都定义了自己的独占属性。

我的问题是,模型绑定器没有查看附加到View.ItemView的对象类型,而是只查看声明的属性类型,即抽象BaseItemView,并决定绑定在抽象类型中定义的属性,忽略特定于正在使用的抽象BaseItemView的具体类型的属性。

解决这个问题的方法不太好:

using System.ComponentModel;using System.ComponentModel.DataAnnotations;// ...public class ModelBinder : DefaultModelBinder{
    // ...

    override protected ICustomTypeDescriptor GetTypeDescriptor(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        if (bindingContext.ModelType.IsAbstract && bindingContext.Model != null)
        {
            var concreteType = bindingContext.Model.GetType();

            if (Nullable.GetUnderlyingType(concreteType) == null)
            {
                return new AssociatedMetadataTypeTypeDescriptionProvider(concreteType).GetTypeDescriptor(concreteType);
            }
        }

        return base.GetTypeDescriptor(controllerContext, bindingContext);
    }

    // ...}

虽然这一变化让人感到烦躁,而且非常“系统性”,但它似乎起作用了-据我所知,它并不构成相当大的安全风险,因为它确实有风险。绑定到CreateModel()中,因此允许你发布任何东西并欺骗模型绑定者来创建任何对象。

它也只在声明的属性类型为摘要类型,例如抽象类或接口。

在一个相关的注意事项上,我意识到我在这里看到的覆盖CreateModel()的其他实现可能会当您发布全新的对象时工作-当声明的属性类型是抽象类型时,会遇到与我遇到的问题相同的问题。所以你很可能无法编辑混凝土类型的特性现有建模对象,但只创建新对象。

因此,换句话说,您可能需要将此工作集成到您的绑定器中,以便能够正确地编辑在绑定之前添加到视图模型中的对象.就我个人而言,我觉得这是一种更安全的方法,因为我控制添加了什么具体类型-因此控制器/操作可以间接地通过使用空实例填充属性来指定可能绑定的具体类型。

我希望这对其他人有帮助.。



查看完整回答
反对 回复 2019-10-13

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信