Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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(MyViewModel1 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?

like image 785
Mark Bell Avatar asked Aug 25 '10 09:08

Mark Bell


2 Answers

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.

like image 164
marcind Avatar answered Oct 01 '22 03:10

marcind


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.

like image 32
alabra Avatar answered Oct 01 '22 04:10

alabra