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 theOrder
; - 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 ;)
abstract
modifier fromOrder
? – SpancelOrder
as argument, aren't they? The ModelBinder will try and make sense of anOrder
object based on what properties it can match. – SpancelmodelType.Assembly.GetType(concreteTypeValue.AttemptedValue)
instead, this will keep the abstration between the binder and your business model. – Donation