ASP.NET MVC 3:具有继承/多态性的 DefaultModelBinder [英] ASP.NET MVC 3: DefaultModelBinder with inheritance/polymorphism

查看:16
本文介绍了ASP.NET MVC 3:具有继承/多态性的 DefaultModelBinder的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

首先,对于这篇重要的帖子(我已经尝试先做一些研究)以及针对同一问题的技术混合(ASP.NET MVC 3、Ninject 和 MvcContrib),我深表歉意.

First, sorry for the big post (I've tried to do some research first) and for the mix of technologies on the same question (ASP.NET MVC 3, Ninject and MvcContrib).

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

I'm developing a project with ASP.NET MVC 3 to handle some client orders.

简而言之:我有一些从抽象类 Order 继承的对象,我需要在向我的控制器发出 POST 请求时解析它们.如何解析正确的类型?我需要覆盖 DefaultModelBinder 类还是有其他方法可以做到这一点?有人可以向我提供有关如何执行此操作的一些代码或其他链接吗?任何帮助都会很棒!如果帖子令人困惑,我可以做任何更改以使其清楚!

In short: I have some objects inherited from and abstract class Order and I need to parse them when a POST request is made to my controller. How can I resolve the correct type? Do I need to override the DefaultModelBinder class or there are some other way to do this? Can somebody provide me some code or other links on how to do this? Any help would be great! If the post is confusing I can do any change to make it clear!

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

So, I have the following inheritance tree for the orders I need to handle:

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 */ }

这些类都是由Entity Framework生成的,所以我不会修改它们,因为我需要更新模型(我知道我可以扩展它们).还有,订单会更多,但都是从Order派生出来的.

This classes are all generated by Entity Framework, so I won't modify them because I will need to update the model (I know I can extend them). Also, there will be more orders, but all derived from Order.

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

I have a generic view (Create.aspx) in order to create a order and this view calls a strongly-typed partial view for each of the inherited orders (in this case OrderBottling and OrderFinishing). I defined a Create() method for a GET request and other for a POST request on OrderControllerclass. The second is like the following:

public class OrderController : Controller
{
    /* rest of the implementation ommited */

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

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

Now the problem: when I receive the POST request with the data from the form, MVC's default binder tries to instantiate an Order object, which is OK since the type of the method is that. But because Order is abstract, it cannot be instantiated, which is what is supposed to do.

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

The question: how can I discover which concrete Order type is sent by the view?

我已经在 Stack Overflow 上在这里搜索并搜索了很多关于这个问题(我现在正在解决这个问题大约 3 天!)并找到了一些方法来解决一些类似的问题,但我找不到任何东西就像我真正的问题.解决这个问题的两个选项:

I've already searched here on Stack Overflow and googled a lot about this (I'm working on this problem for about 3 days now!) and found some ways to solve some similar problems, but I couldn't find anything like my real problem. Two options for solving this:

  • 覆盖 ASP.NET MVC DefaultModelBinder 并使用直接注入来发现哪种类型是 Order;
  • 为每个订单创建一个方法(不美观,维护起来会有问题).
  • override ASP.NET MVC DefaultModelBinder and use Direct Injection to discover which type is the Order;
  • create a method for each order (not beautiful and would be problematic to maintain).

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

I haven't tried the second option because I don't think it's the right way to solve the problem. For the first option I've tried Ninject to resolve the type of the order and instantiate it. My Ninject module is like the following:

private class OrdersService : NinjectModule
{
    public override void Load()
    {
        Bind<Order>().To<OrderBottling>();
        Bind<Order>().To<OrderFinishing>();
    }
}

我已经尝试通过 Ninject 的 Get<>() 方法获取其中一种类型,但它告诉我,这不仅仅是一种解析类型的方法.所以,我知道该模块没有很好地实现.我也尝试为这两种类型实现这样的:Bind().To().WithPropertyInject("OrderTypeId", 2);,但它有同样的问题...实现这个模块的正确方法是什么?

I've have tried to get one of the types throught Ninject's Get<>() method, but it tells me that the are more then one way to resolve the type. So, I understand the module is not well implemented. I've also tried to implement like this for both types: Bind<Order>().To<OrderBottling>().WithPropertyInject("OrderTypeId", 2);, but it has the same problem... What would be the right way to implement this module?

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

I've also tried use MvcContrib Model Binder. I've done this:

[DerivedTypeBinderAware(typeof(OrderBottling))]
[DerivedTypeBinderAware(typeof(OrderFinishing))]
public abstract partial class Order { }

Global.asax.cs 上我已经做到了:

and on Global.asax.cs I've done this:

protected void Application_Start()
{
    AreaRegistration.RegisterAllAreas();

    RegisterRoutes(RouteTable.Routes);

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

但这会引发异常:System.MissingMethodException:无法创建抽象类.因此,我认为活页夹不是或无法解析为正确的类型.

But this throws an exception: System.MissingMethodException: Cannot create an abstract class. So, I presume the binder isn't or can't resolve to the correct type.

非常感谢!

首先,感谢 Martin 和 Jason 的回答,并对延迟表示抱歉!我尝试了两种方法并且都有效!我将 Martin 的答案标记为正确,因为它更灵活并且满足我项目的一些需求.具体来说,每个请求的 ID 都存储在数据库中,如果我只在一个地方(数据库或类)更改 ID,将它们放在类上会破坏软件.马丁的方法在这一点上非常灵活.

first of all, thank you Martin and Jason for your answers and sorry for the delay! I tried both approaches and both worked! I marked Martin's answer as correct because it is more flexible and meets some of the needs for my project. Specifically, the IDs for each request are stored on a database and putting them on the class can break the software if I change the ID only in one place (database or on the class). Martin's approach is very flexible in that point.

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

@Martin: on my code I changed the line

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

var concreteType = Assembly.GetAssembly(typeof(Order)).GetType(concreteTypeValue.AttemptedValue);

因为我的课程在另一个项目中(因此,在不同的程序集中).我分享这个是因为它似乎比只获取无法解析外部程序集类型的执行程序集更灵活.在我的情况下,所有订单类都在同一个程序集中.这不是更好也不是魔法公式,但我认为分享这个很有趣;)

because my classes where on another project (and so, on a different assembly). I'm sharing this because it's seems a like more flexible than getting only the executing assembly that cannot resolve types on external assemblies. In my case all the order classes are on the same assembly. It's not better nor a magic formula, but I think is interesting to share this ;)

推荐答案

我之前尝试过做类似的事情,我得出的结论是没有内置的任何东西可以处理这个问题.

I've tried to do something similar before and I came to the conclusion that there's nothing built in which will handle this.

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

The option I went with was to create my own model binder (though inherited from the default so its not too much code). It looked for a post back value with the name of the type called xxxConcreteType where xxx was another type it was binding to. This means that a field must be posted back with the value of the type you're trying to bind; in this case OrderConcreteType with a value of either OrderBottling or OrderFinishing.

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

Your other alternative is to use UpdateModel or TryUpdateModel and ommit the parameter from your method. You will need to determine which kind of model you're updating before calling this (either by a parameter or otherwise) and instantiate the class beforehand, then you can use either method to popuplate it

这是代码..

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

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

Change your action method to look like this:

public ActionResult Create([AbstractBind(ConcreteTypeParameter = "orderType")] Order order) { /* implementation ommited */ }

您需要将以下内容放入您的视图中:

You would need to put the following in your view:

@Html.Hidden("orderType, "Namespace.xxx.OrderBottling")

这篇关于ASP.NET MVC 3:具有继承/多态性的 DefaultModelBinder的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆