views:

49

answers:

3

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?

+1  A: 

You could consider using a base class instead of the interface.

Alexander Prokofyev
+1  A: 

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 creat it. As you've notice 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.

marcind
I went for this approach in the end purely due to time constraints, but you also explained why the object isn't already an instance when it's passed into the controller, which is useful to know.
Mark B
+1  A: 

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.

Hal
I should add one additional comment - I wouldn't actually pass the abstract class into the controller, but rather the concrete class. You may have a validation service or some other method that can handle the abstract class or interface but for clarity sake, you should really be expecting what you're getting in your controller.
Hal