首先,对这篇大文章(我尝试先做一些研究)以及针对同一问题的技术组合(ASP.NET MVC 3、Ninject 和 MvcContrib)表示歉意。
我正在使用 ASP.NET MVC 3 开发一个项目来处理一些客户订单。
简而言之:我有一些继承自抽象类Order
的对象,当向我的控制器发出POST请求时,我需要解析它们。如何解析正确的类型?我是否需要重写 DefaultModelBinder
类或者还有其他方法可以做到这一点?有人可以为我提供一些关于如何执行此操作的代码或其他链接吗?任何帮助都会很棒!
如果帖子令人困惑,我可以做任何更改以使其清晰!
因此,我需要处理的订单有以下继承树:
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
) 来创建订单,该视图为每个继承订单调用强类型部分视图(在本例中为 OrderBotttle
> 和订单完成
)。我在 OrderController
类上为 GET 请求定义了一个 Create()
方法,为 POST 请求定义了其他方法。第二个如下所示:
public class OrderController : Controller
{
/* rest of the implementation ommited */
[HttpPost]
public ActionResult Create(Order order) { /* implementation ommited */ }
}
现在的问题是:当我收到带有表单数据的 POST 请求时,MVC 的默认绑定器尝试实例化一个 Order
对象,这是可以的,因为该方法的类型就是这样。但由于 Order
是抽象的,因此无法实例化,而这正是应该做的。
问题:如何发现视图发送的具体Order
类型?
我已经在 Stack Overflow 上搜索过,并在 google 上搜索了很多相关内容(我现在正在研究这个问题大约 3 天!),并找到了一些解决一些类似问题的方法,但我找不到像我真实的问题一样的东西问题。解决此问题的两种选择:
- 重写 ASP.NET MVC DefaultModelBinder 并使用直接注入来发现 Order 的类型;
- 为每个订单创建一个方法(不美观并且维护起来会有问题)。
我没有尝试第二个选项,因为我认为这不是解决问题的正确方法。对于第一个选项,我尝试使用 Ninject 解析订单类型并实例化它。我的 Ninject 模块如下所示:
private class OrdersService : NinjectModule
{
public override void Load()
{
Bind<Order>().To<OrderBottling>();
Bind<Order>().To<OrderFinishing>();
}
}
我尝试通过 Ninject 的 Get<>() 方法获取其中一种类型,但它告诉我,解决该问题的方法不止一种。类型。所以,我知道该模块没有得到很好的实现。我也尝试对两种类型实现这样的: Bind().To().WithPropertyInject("OrderTypeId", 2);
,但它有同样的问题...实现该模块的正确方法是什么?
我还尝试过使用 MvcContrib Model Binder。我已经这样做了:
[DerivedTypeBinderAware(typeof(OrderBottling))]
[DerivedTypeBinderAware(typeof(OrderFinishing))]
public abstract partial class Order { }
在 Global.asax.cs
上我已经这样做了:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
ModelBinders.Binders.Add(typeof(Order), new DerivedTypeModelBinder());
}
但这会引发异常:System.MissingMethodException:无法创建抽象类。因此,我认为活页夹没有或无法解析为正确的类型。
非常非常感谢!
编辑:首先,感谢 Martin 和 Jason 的回答,对于延迟,我们深表歉意!我尝试了两种方法并且都有效!我将 Martin 的答案标记为正确,因为它更灵活并且满足了我项目的一些需求。具体来说,每个请求的 ID 都存储在数据库中,如果我仅在一个位置(数据库或类上)更改 ID,则将它们放在类上可能会破坏软件。马丁的方法在这一点上非常灵活。
@Martin:在我的代码中,我将行更改
var concreteType = Assembly.GetExecutingAssembly().GetType(concreteTypeValue.AttemptedValue);
为,
var concreteType = Assembly.GetAssembly(typeof(Order)).GetType(concreteTypeValue.AttemptedValue);
因为我的类位于另一个项目上(因此,在不同的程序集中)。我分享这个是因为它似乎比仅获取无法解析外部程序集类型的执行程序集更灵活。就我而言,所有订单类都位于同一个程序集中。这不是更好,也不是一个神奇的公式,但我认为分享这个很有趣;)
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).
I'm developing a project with ASP.NET MVC 3 to handle some client orders.
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 */ }
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
.
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 OrderController
class. The second is like the following:
public class OrderController : Controller
{
/* rest of the implementation ommited */
[HttpPost]
public ActionResult Create(Order order) { /* implementation ommited */ }
}
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.
The question: how can I discover which concrete Order
type is sent by the view?
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:
- 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).
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>();
}
}
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?
I've also tried use MvcContrib Model Binder. I've done this:
[DerivedTypeBinderAware(typeof(OrderBottling))]
[DerivedTypeBinderAware(typeof(OrderFinishing))]
public abstract partial class Order { }
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());
}
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.
Many many thanks in advance!
Edit: 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: on my code I changed the line
var concreteType = Assembly.GetExecutingAssembly().GetType(concreteTypeValue.AttemptedValue);
to
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 ;)
发布评论
评论(5)
我之前尝试过做类似的事情,但我得出的结论是,没有内置的东西可以处理这个问题。
我选择的选项是创建自己的模型绑定器(尽管继承自默认模型绑定器,因此代码不多)。它查找名为 xxxConcreteType 的类型名称的回发值,其中 xxx 是它绑定到的另一种类型。这意味着必须将一个字段与您尝试绑定的类型的值一起发回;在本例中,OrderConcreteType 的值为 OrderBotttle 或 OrderFinishing。
您的另一种选择是使用 UpdateModel 或 TryUpdateModel 并省略方法中的参数。您需要在调用此函数之前确定要更新哪种模型(通过参数或其他方式)并预先实例化该类,然后您可以使用任一方法来填充它
编辑:
这是代码..
更改您的操作方法如下所示:
您需要将以下内容放入您的视图中:
I've tried to do something similar before and I came to the conclusion that there's nothing built in which will handle this.
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.
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
Edit:
Here is the code..
Change your action method to look like this:
You would need to put the following in your view:
您可以创建一个自定义 ModelBinder,该 ModelBinder 在您的操作接受某种类型时运行,并且它可以创建您想要返回的任何类型的对象。 CreateModel() 方法采用 ControllerContext 和 ModelBindingContext,使您可以访问通过路由、url 查询字符串和 post 传递的参数,您可以使用它们来填充值的对象。默认模型绑定器实现会转换同名属性的值,以将它们放入对象的字段中。
我在这里所做的只是检查其中一个值以确定要创建的类型,然后调用 DefaultModelBinder.CreateModel() 方法将要创建的类型切换为适当的类型。
通过将其添加到 Global.asax.cs 中的 Application_Start() 中,将其设置为在操作上有 Order 参数时使用:
You can create a custome ModelBinder that operates when your action accepts a certain type, and it can create an object of whatever type you want to return. The CreateModel() method takes a ControllerContext and ModelBindingContext that give you access to the parameters passed by route, url querystring and post that you can use to populate your object with values. The default model binder implementation converts values for properties of the same name to put them in the fields of the object.
What I do here is simply check one of the values to determine what type to create, then call the DefaultModelBinder.CreateModel() method switching the type it is to create to the appropriate type.
Set it to be used when you have an Order parameter on your actions by adding this to Application_Start() in Global.asax.cs:
您还可以构建适用于所有抽象模型的通用 ModelBinder。我的解决方案要求您向视图中添加一个名为“ModelTypeName”的隐藏字段,并将值设置为所需的具体类型的名称。但是,应该可以使这个东西变得更智能,并通过将类型属性与视图中的字段匹配来选择具体类型。
在 Global.asax.cs Application_Start():
CustomModelBinder 中:
You can also build a generic ModelBinder that works for all of your abstract models. My solution requires you to add a hidden field to your view called 'ModelTypeName' with the value set to the name of the concrete type that you want. However, it should be possible to make this thing smarter and pick a concrete type by matching type properties to fields in the view.
In your Global.asax.cs Application_Start():
CustomModelBinder:
我针对该问题的解决方案支持复杂模型,其中可以包含其他抽象类、多重继承、集合或泛型类。
正如您所看到的,您必须添加字段(名称为类型),其中包含应创建从抽象类继承的具体类的信息。例如类:类抽象内容、类 TextContent,内容的类型应设置为“TextContent”。
请记住在 global.asax 中切换默认模型绑定器:
有关更多信息和示例项目,请检查以下 链接。
My solution for that problem support complex models that can contain other abstract class, multiple inheritance, collections or generic classes.
As you see you have to add field (of name Type) that contains information what concrete class inheriting from abstract class should be created. For example classes: class abstract Content, class TextContent, the Content should have Type set to "TextContent".
Remember to switch default model binder in global.asax:
For more information and sample project check following link.
更改行:
改为:
这是一个简单的实现,用于检查每个程序集的类型。我确信有更聪明的方法可以做到这一点,但这已经足够好了。
Change the line:
To this:
This is a naive implementation that checks every assembly for the type. I'm sure there's smarter ways to do it, but this works well enough.