首页
学习
活动
专区
工具
TVP
发布
社区首页 >问答首页 >ASP.NET MVC3:具有继承/多态性的DefaultModelBinder

ASP.NET MVC3:具有继承/多态性的DefaultModelBinder
EN

Stack Overflow用户
提问于 2011-03-28 21:59:51
回答 5查看 14.8K关注 0票数 17

首先,对于这个重要的帖子(我试着先做一些研究)和关于同一问题的混合技术(ASP.NET MVC3,Ninject和MvcContrib),我深表歉意。

我正在用ASP.NET MVC3开发一个项目来处理一些客户订单。

简而言之,我有一些继承自抽象类Order的对象,当向我的控制器发出POST请求时,我需要解析它们。如何解析正确的类型?我是否需要重写DefaultModelBinder类,或者有其他方法可以做到这一点?有人能给我提供一些代码或其他链接来告诉我如何做到这一点吗?任何帮助都是最好的!如果这篇文章令人困惑,我可以做任何修改来让它变得清晰!

因此,对于我需要处理的订单,我有以下继承树:

代码语言:javascript
复制
public abstract partial class Order {

    public Int32 OrderTypeId {get; set; }

    /* rest of the implementation ommited */
}

public class OrderBottling : Order { /* implementation ommited */ }

public class OrderFinishing : Order { /* implementation ommited */ }

这些类都是由实体框架生成的,所以我不会修改它们,因为我需要更新模型(我知道我可以扩展它们)。此外,还会有更多的订单,但都是从Order派生的。

为了创建订单,我有一个通用视图(Create.aspx),这个视图为每个继承的订单(在本例中是OrderBottlingOrderFinishing)调用一个强类型的局部视图。我在OrderController类上为GET请求定义了一个Create()方法,为POST请求定义了其他方法。第二个示例如下所示:

代码语言:javascript
复制
public class OrderController : Controller
{
    /* rest of the implementation ommited */

    [HttpPost]
    public ActionResult Create(Order order) { /* implementation ommited */ }
}

现在问题来了:当我收到POST请求和表单中的数据时,MVC的默认绑定器尝试实例化一个Order对象,这是可以的,因为该方法的类型是。但是因为Order是抽象的,所以它不能被实例化,而这正是应该做的。

问题:我如何才能发现视图发送的是哪种具体的Order类型?

我已经在Stack Overflow上搜索了这里,并用谷歌搜索了很多关于这个问题的信息(我已经在这个问题上工作了大约3天!)我找到了一些方法来解决一些类似的问题,但我找不到任何与我真正的问题相似的东西。解决此问题的两个选项:

  • 覆盖ASP.NET MVC DefaultModelBinder,并使用直接注入来发现哪种类型是每个订单的
  • 方法(不美观,维护起来会有问题)。

我没有尝试第二种选择,因为我认为这不是解决问题的正确方法。对于第一个选项,我尝试了使用Ninject来解析订单的类型并实例化它。我的Ninject模块如下:

代码语言:javascript
复制
private class OrdersService : NinjectModule
{
    public override void Load()
    {
        Bind<Order>().To<OrderBottling>();
        Bind<Order>().To<OrderFinishing>();
    }
}

我曾尝试通过Ninject的Get<>()方法获取其中一个类型,但它告诉我有不止一种方法可以解析该类型。所以,我理解这个模块没有很好地实现。我也尝试过这样实现这两种类型:Bind<Order>().To<OrderBottling>().WithPropertyInject("OrderTypeId", 2);,但它有相同的问题……实现此模块的正确方式是什么?

我也尝试过使用MvcContrib模型绑定器。我已经这样做了:

代码语言:javascript
复制
[DerivedTypeBinderAware(typeof(OrderBottling))]
[DerivedTypeBinderAware(typeof(OrderFinishing))]
public abstract partial class Order { }

Global.asax.cs上,我这样做了:

代码语言:javascript
复制
protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterRoutes(RouteTable.Routes);

    ModelBinders.Binders.Add(typeof(Order), new DerivedTypeModelBinder());
}

但这抛出了一个异常: System.MissingMethodException:无法创建抽象类。因此,我假设绑定器不是或不能解析为正确的类型。

很多很多感谢提前!

编辑:首先,感谢马丁和杰森的回答,很抱歉让你久等了!我尝试了这两种方法,都有效!我将Martin的答案标记为正确,因为它更灵活,并且满足了我的项目的一些需求。具体地说,每个请求的ID都存储在一个数据库中,如果我只在一个地方(数据库或类)更改ID,那么将它们放在类中可能会破坏软件。在这一点上,Martin的方法非常灵活。

@Martin:在我的代码中,我更改了代码行

代码语言:javascript
复制
var concreteType = Assembly.GetExecutingAssembly().GetType(concreteTypeValue.AttemptedValue);

代码语言:javascript
复制
var concreteType = Assembly.GetAssembly(typeof(Order)).GetType(concreteTypeValue.AttemptedValue);

因为我的类在另一个项目中(因此,在不同的程序集中)。我之所以分享这一点,是因为它似乎比只获取无法解析外部程序集上的类型的正在执行的程序集更灵活。在我的例子中,所有的order类都在同一个程序集中。这不是更好的,也不是一个神奇的公式,但我认为分享这一点很有趣;)

EN

回答 5

Stack Overflow用户

回答已采纳

发布于 2011-03-28 22:34:22

我以前也试过做一些类似的事情,我得出的结论是,没有什么内置的东西可以处理这个问题。

我的选择是创建我自己的模型绑定器(虽然继承自默认的,所以代码不是太多)。它查找名为xxxConcreteType的类型的回发值,其中xxx是它绑定到的另一个类型。这意味着必须使用您试图绑定的类型的值回发一个字段;在本例中,OrderConcreteType的值为OrderBottling或OrderFinishing。

另一种选择是使用UpdateModel或TryUpdateModel并省略方法中的参数。您需要在调用此方法之前确定要更新的模型类型(通过参数或其他方式),并预先实例化该类,然后可以使用任何一种方法来弹出它

编辑:

这是代码..

代码语言:javascript
复制
public class AbstractBindAttribute : CustomModelBinderAttribute
{
    public string ConcreteTypeParameter { get; set; }

    public override IModelBinder GetBinder()
    {
        return new AbstractModelBinder(ConcreteTypeParameter);
    }

    private class AbstractModelBinder : DefaultModelBinder
    {
        private readonly string concreteTypeParameterName;

        public AbstractModelBinder(string concreteTypeParameterName)
        {
            this.concreteTypeParameterName = concreteTypeParameterName;
        }

        protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type modelType)
        {
            var concreteTypeValue = bindingContext.ValueProvider.GetValue(concreteTypeParameterName);

            if (concreteTypeValue == null)
                throw new Exception("Concrete type value not specified for abstract class binding");

            var concreteType = Assembly.GetExecutingAssembly().GetType(concreteTypeValue.AttemptedValue);

            if (concreteType == null)
                throw new Exception("Cannot create abstract model");

            if (!concreteType.IsSubclassOf(modelType))
                throw new Exception("Incorrect model type specified");

            var concreteInstance = Activator.CreateInstance(concreteType);

            bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => concreteInstance, concreteType);

            return concreteInstance;
        }
    }
}

更改您的操作方法,如下所示:

代码语言:javascript
复制
public ActionResult Create([AbstractBind(ConcreteTypeParameter = "orderType")] Order order) { /* implementation ommited */ }

您需要在视图中放置以下内容:

代码语言:javascript
复制
@Html.Hidden("orderType, "Namespace.xxx.OrderBottling")
票数 18
EN

Stack Overflow用户

发布于 2011-03-29 09:11:45

您可以创建一个当您的操作接受特定类型时操作的客户ModelBinder,它可以创建您想要返回的任何类型的对象。CreateModel()方法接受一个ControllerContext和ModelBindingContext,它们允许您访问通过路由、url、查询字符串和post传递的参数,您可以使用这些参数用值填充您的对象。默认的模型绑定器实现转换同名属性的值,以将它们放入对象的字段中。

我在这里所做的只是检查其中一个值以确定要创建的类型,然后调用DefaultModelBinder.CreateModel()方法将要创建的类型切换为适当的类型。

代码语言:javascript
复制
public class OrderModelBinder : DefaultModelBinder
{
    protected override object CreateModel(
        ControllerContext controllerContext,
        ModelBindingContext bindingContext,
        Type modelType)
    {
        // get the parameter OrderTypeId
        ValueProviderResult result;
        result = bindingContext.ValueProvider.GetValue("OrderTypeId");
        if (result == null)
            return null; // OrderTypeId must be specified

        // I'm assuming 1 for Bottling, 2 for Finishing
        if (result.AttemptedValue.Equals("1"))
            return base.CreateModel(controllerContext,
                    bindingContext,
                    typeof(OrderBottling));
        else if (result.AttemptedValue.Equals("2"))
            return base.CreateModel(controllerContext,
                    bindingContext,
                    typeof(OrderFinishing));
        return null; // unknown OrderTypeId
    }
}

将其设置为在您的操作上具有Order参数时使用,方法是将以下内容添加到Global.asax.cs中的Application_Start():

代码语言:javascript
复制
ModelBinders.Binders.Add(typeof(Order), new OrderModelBinder());
票数 7
EN

Stack Overflow用户

发布于 2011-09-22 23:48:04

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

在您的Global.asax.cs Application_Start()中:

代码语言:javascript
复制
ModelBinders.Binders.DefaultBinder = new CustomModelBinder();

CustomModelBinder:

代码语言:javascript
复制
public class CustomModelBinder : DefaultModelBinder 
{
    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, Type 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)
                throw new Exception("Invalid ModelTypeName");

            var concreteInstance = Activator.CreateInstance(type);

            bindingContext.ModelMetadata = ModelMetadataProviders.Current.GetMetadataForType(() => concreteInstance, type);

            return concreteInstance;

        }

        return base.CreateModel(controllerContext, bindingContext, modelType);
    }
}
票数 5
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/5460081

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档