Passing an interface to an ASP.NET MVC Controller Action method
In my ASP.NET MVC app, I have an interface which acts as the template for several different view models:
public interface IMyViewModel
{
Client Client1 { get; set; }
Client Client2 { get; set; }
Validator Validate();
}
So, my view models are defined like this:
public interface MyViewModel1 : IMyViewModel
{
Client Client1 { get; set; }
Client Client2 { get; set; }
// Properties specific to MyViewModel1 here
public Validator Validate()
{
// Do ViewModel-specific validation here
}
}
public interface MyViewModel2 : IMyViewModel
{
Client Client1 { get; set; }
Client Client2 { get; set; }
// Properties specific to MyViewModel2 here
public Validator Validate()
{
// Do ViewModel-specific validation here
}
}
Then I currently have a separate controller action to do the validation for each different type, using model binding:
[HttpPost]
public ActionResult MyViewModel1Validator(MyViewMod开发者_StackOverflow社区el1 model)
{
var validator = model.Validate();
var output = from Error e in validator.Errors
select new { Field = e.FieldName, Message = e.Message };
return Json(output);
}
[HttpPost]
public ActionResult MyViewModel2Validator(MyViewModel2 model)
{
var validator = model.Validate();
var output = from Error e in validator.Errors
select new { Field = e.FieldName, Message = e.Message };
return Json(output);
}
This works fine—but if I had 30 different view model types then there would have to be 30 separate controller actions, all with identical code apart from the method signature, which seems like bad practice.
My question is, how can I consolidate these validation actions so that I can pass any kind of view model in and call it's Validate() method, without caring about which type it is?
At first I tried using the interface itself as the action parameter:
public ActionResult MyViewModelValidator(IMyViewModel model)...
But this didn't work: I get a Cannot create an instance of an interface
exception. I thought an instance of the model would be passed into the controller action, but apparently this is not the case.
I'm sure I'm missing something simple. Or perhaps I've just approached this all wrong. Can anyone help me out?
The reason why you cannot use the interface is because of serialization. When a request comes in it only contains string key/value pairs that represent the object:
"Client1.Name" = "John"
"Client2.Name" = "Susan"
When the action method gets invoked the MVC runtime tries to create values to populate the method's parameters (via a process called model binding). It uses the type of the parameter to infer how to create it. As you've noticed, the parameter cannot be an interface or any other abstract type because the runtime cannot create an instance of it. It needs a concrete type.
If you want to remove repeated code you could write a helper:
[HttpPost]
public ActionResult MyViewModel1Validator(MyViewModel1 model)
{
return ValidateHelper(model);
}
[HttpPost]
public ActionResult MyViewModel2Validator(MyViewModel2 model)
{
return ValidateHelper(model);
}
private ActionResult ValidateHelper(IMyViewModel model) {
var validator = model.Validate();
var output = from Error e in validator.Errors
select new { Field = e.FieldName, Message = e.Message };
return Json(output);
}
However, you will still need a different action method for each model type. Perhaps there are other ways you could refactor your code. It seems the only difference in your model classes is the validataion behavior. You could find a different way to encode the validation type in your model class.
You could check this: http://msdn.microsoft.com/en-us/magazine/hh781022.aspx.
This is caused because DefaultModelBinder has no way of knowing what concrete type of IMyViewModel should create. For solution that, you create custom model binder and indicate how to create and bind an instance of interface.
I think I would create an abstract base class that implemented IMyViewModel. I would make Validate an abstract method and require overriding in my concrete view models that inherited from MyAbstractViewModel. Inside your controller, you can work with the IMyViewModel interface if you want, but binding and serialization really needs a concrete class to bind. My $.02.
You could consider using a base class instead of the interface.
精彩评论