【问题标题】:ASP.NET MVC 2 - Binding To Abstract ModelASP.NET MVC 2 - 绑定到抽象模型
【发布时间】:2011-04-30 01:25:15
【问题描述】:

如果我有以下强类型视图:

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<XXX.DomainModel.Core.Locations.Location>" %>

Location 是一个抽象类。

我有以下控制器,它通过 POST 接受强类型模型:

[HttpPost]
public ActionResult Index(Location model)

我收到一个运行时错误,指出 “无法创建抽象类

这当然是有道理的。但是 - 我不确定这里最好的解决方案是什么。

我有很多具体类型(大约 8 个),这是一个只能编辑抽象类属性的视图。

尝试做的是为所有不同的具体类型创建重载,并以通用方法执行我的逻辑。

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

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

等等等等

然后:

[NonAction]
private void UpdateLocationModel (Location model)
{
   // ..snip - update model
}

但这也不起作用,MVC 抱怨操作方法不明确(也有道理)。

我们做什么?我们可以不绑定到抽象模型吗?

【问题讨论】:

  • 好问题。有兴趣看看答案!
  • 我很好奇您是否找到了更好的方法来处理这个问题?
  • @Mystere Man - 不。我不必再这样做了。如果我这样做了,我会按照公认答案的建议去做。

标签: c# .net asp.net-mvc-2 abstract-class model-binding


【解决方案1】:

为这个抽象类编写一个自定义模型绑定器怎么样:

public class CustomBinder : DefaultModelBinder
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
    {
        // TODO: based on some request parameter choose the proper child type
        // to instantiate here
        return new Child();
    }
}

只有当您有一个基于某些用户操作动态插入输入元素的表单时,这才有意义。在这种情况下,您需要传递一些附加参数来指示您需要哪个具体类。否则我会坚持使用具体的视图模型作为动作参数。

【讨论】:

  • 这是一个非常简单的视图,编辑抽象模型上的属性字段。所以我不想在多个强类型视图中“复制”这个 HTML。我将查看自定义模型绑定器 - 因为我在渲染视图时确实知道子类型。我明天去办公室试试 - 干杯。
  • 好的,我已经阅读了模型活页夹,我同意 - 这在我的场景中没有意义。我将坚持使用具体的视图模型。不过这会起作用,所以我会接受你的回答。谢谢。
【解决方案2】:

您还可以构建适用于所有抽象模型的通用 ModelBinder。我的解决方案要求您在视图中添加一个名为“ModelTypeName”的隐藏字段,并将值设置为您想要的具体类型的名称。 但是,应该可以通过将类型属性与视图中的字段匹配来使这个东西更智能并选择一个具体的类型。

Application_Start() 中的 Global.asax.cs 文件中:

ModelBinders.Binders.DefaultBinder = new CustomModelBinder();

CustomModelBinder:

public class CustomModelBinder2 : DefaultModelBinder 
{
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var modelType = bindingContext.ModelType;
        if (modelType.IsAbstract)
        {
            var modelTypeValue = controllerContext.Controller.ValueProvider.GetValue("ModelTypeName");
            if (modelTypeValue == null)
                throw new Exception("View does not contain ModelTypeName");

            var modelTypeName = modelTypeValue.AttemptedValue;

            var type = modelType.Assembly.GetTypes().SingleOrDefault(x => x.IsSubclassOf(modelType) && x.Name == modelTypeName);

            if (type != null)
            {
                var instance= bindingContext.Model ?? base.CreateModel(controllerContext, bindingContext, type);
                bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => instance, type);
            }
        }
        return base.BindModel(controllerContext, bindingContext);
    }
}

【讨论】:

  • 这是一个有用的答案,但对于任何应用它的人来说,我怀疑如果客户端发回带有有害构造函数的意外但合法的类型名称,这里可能存在安全风险。对吗?
  • @uosɐſ:仅在 MVC 试图映射到控制器动作签名中使用的预期抽象类的子类(参见 .IsSubclassOf(...))或控制器动作签名对象之一的情况下子属性。然后它必须有一个“有害”的构造函数。我正在考虑使用此代码。你能举一个这样的安全风险的例子吗?
  • 嗯,当我问这个问题时,我想我没有注意到这个限制。我现在什么都想不出来。
  • @Joel 如果您打算使用此代码,请参阅我的编辑。我一直在生产中使用这个版本,效果很好。
【解决方案3】:

只是把它扔在那里 - 我对其他人可能会回答什么非常感兴趣,但这是我在遇到类似情况的情况下最终做的事情;

基本上,我没有在 Action 方法中使用模型类作为参数,而是传入FormCollection 并测试几个已知的鉴别器以确定要创建/编辑的类型,然后从那里使用TryUpdateModel

似乎有更好的方法,但我从来没有考虑过更多。

【讨论】:

  • 是的,那是我的下一步! :) 但是,我不想强​​类型的善良。 :)
  • 味道鲜美,营养丰富;这份完整早餐的一部分!
猜你喜欢
  • 2018-06-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-04-27
相关资源
最近更新 更多