多态模型绑定

Eri*_*sch 56 polymorphism asp.net-mvc model-binding asp.net-mvc-3

之前在早期版本的MVC中已经提出过这个问题.还有一篇关于解决问题的方法的博客文章.我想知道MVC3是否引入了任何可能有用的东西,或者是否还有其他选择.

简而言之.这是情况.我有一个抽象的基本模型和2个具体的子类.我有一个强类型视图,用于渲染模型EditorForModel().然后我有自定义模板来呈现每个具体类型.

问题来自发布时间.如果我使post post方法取基类作为参数,那么MVC不能创建它的抽象版本(我不想要它,我希望它创建实际的具体类型).如果我创建多个仅通过参数签名变化的后期操作方法,那么MVC会抱怨它不明确.

据我所知,我对如何解决这个问题有一些选择.我出于各种原因不喜欢它们,但我会在这里列出它们:

  1. 如Darin在我链接的第一篇文章中所建议的那样创建自定义模型绑定器.
  2. 创建一个鉴别器属性作为我链接到的第二个帖子建议.
  3. 根据类型发布到不同的操作方法
  4. ???

我不喜欢1,因为它基本上是隐藏的配置.其他一些开发代码的开发人员可能不知道它并浪费了大量时间来弄清楚为什么在更改内容时会出现问题.

我不喜欢2,因为它似乎有点hacky.但是,我倾向于这种方法.

我不喜欢3,因为这意味着违反DRY.

还有其他建议吗?

编辑:

我决定采用达林的方法,但稍作改动.我把它添加到我的抽象模型中:

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

然后隐藏自动生成我的DisplayForModel().你唯一需要记住的是,如果你不使用DisplayForModel(),你必须自己添加它.

Dar*_*rov 59

因为我显然选择了选项1(:-)),所以让我尝试再详细说明它,以便它不易破碎,并避免将具体实例硬编码到模型绑定器中.我们的想法是将具体类型传递给隐藏字段,并使用反射来实例化具体类型.

假设您有以下视图模型:

public abstract class BaseViewModel
{
    public int Id { get; set; }
}

public class FooViewModel : BaseViewModel
{
    public string Foo { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

以下控制器:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

相应的Index观点:

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

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

@model FooViewModel
@Html.EditorFor(x => x.Id)
@Html.EditorFor(x => x.Foo)
Run Code Online (Sandbox Code Playgroud)

现在我们可以使用以下自定义模型绑定器:

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;
    }
}
Run Code Online (Sandbox Code Playgroud)

实际类型是从ModelType隐藏字段的值推断出来的.它不是硬编码的,这意味着您可以在以后添加其他子类型而无需触摸此模型绑定器.

这种相同的技术可以很容易地应用于基本视图模型的集合.

  • 我添加了一个检查,以确保传入的类型继承自预期的基类.如果没有这个,攻击者可能会导致您实例化任意类,这可能是危险的. (3认同)

Eri*_*sch 14

我刚刚想到了解决这个问题的有效方法.而不是像这样使用参数bsed模型绑定:

[HttpPost]
public ActionResult Index(MyModel model) {...}
Run Code Online (Sandbox Code Playgroud)

我可以使用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);
}
Run Code Online (Sandbox Code Playgroud)

无论如何,这实际上工作得更好,因为如果我正在进行任何处理,那么我将不得不将模型转换为它实际上的任何东西,或者is用来找出使用AutoMapper调用的正确Map.

我想我们这些还没有被使用MVC从第一天起忘记谁UpdateModelTryUpdateModel,但它仍然有它的用途.


min*_*.dk 7

我花了一个美好的一天来回答一个密切相关的问题 - 尽管我不确定这是一个完全相同的问题,但我在这里发布它以防其他人正在寻找解决同一问题的方法.

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

class View
{
    public AbstractBaseItemView ItemView { get; set; }
}
Run Code Online (Sandbox Code Playgroud)

我有许多AbstractBaseItemView的子类型,其中许多子类定义了它们自己的专有属性.

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

解决这个问题并不是很好:

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);
    }

    // ...
}
Run Code Online (Sandbox Code Playgroud)

虽然这种变化感觉哈克,是非常"系统性",似乎工作-并且没有,就我自己看着办,造成相当大的安全风险,因为它并没有扎入CreateModel(),因此它不会让你发布任何内容并欺骗模型绑定器来创建任何对象.

它也适用于声明的属性类型是抽象类型,例如抽象类或接口.

在一个相关的说明中,我发现我在这里看到的其他实现覆盖CreateModel()可能在您发布全新对象时才会起作用 - 并且会遇到我遇到的同样问题,当声明属性时-type是抽象类型.因此,您很可能无法在现有模型对象上编辑具体类型的特定属性,而只能创建新的对象.

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

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