2010-08-25 16 views
6

En mi aplicación ASP.NET MVC, tengo una interfaz que actúa como plantilla para varios modelos de vista diferentes:Pasando una interfaz a un método de acción ASP.NET MVC controlador

public interface IMyViewModel 
{ 
    Client Client1 { get; set; } 
    Client Client2 { get; set; } 

    Validator Validate(); 
} 

Por lo tanto, mi opinión modelos se definen así:

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 
    } 
} 

Entonces Actualmente tengo una acción de controlador separado para hacer la validación para cada tipo diferente, usando enlace de modelos:

[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); 
} 

Esto funciona bien, pero si tuviera 30 tipos de modelos de vista diferentes, entonces tendría que haber 30 acciones de controlador separadas, todas con código idéntico además de la firma del método, lo que parece una mala práctica.

Mi pregunta es, ¿cómo puedo consolidar estas acciones de validación para poder pasar cualquier tipo de modelo de vista y llamar a su método Validate(), sin importar de qué tipo se trata?

Al principio trató de usar la interfaz como el parámetro de acción:

public ActionResult MyViewModelValidator(IMyViewModel model)... 

Pero esto no funcionó: recibo una excepción Cannot create an instance of an interface. Pensé que una instancia del modelo pasaría a la acción del controlador, pero aparentemente este no es el caso.

Estoy seguro de que me falta algo simple. O quizás acabo de abordar todo esto mal. ¿Puede alguien ayudarme?

Respuesta

10

La razón por la que no puede usar la interfaz es debido a la serialización. Cuando llega una petición que sólo contiene los pares clave de cadena/valor que representan el objeto:

"Client1.Name" = "John" 
"Client2.Name" = "Susan" 

Cuando el método de acción se invoca el tiempo de ejecución MVC trata de crear valores para rellenar los parámetros del método (a través de un proceso llamado modelo de unión) Utiliza el tipo del parámetro para inferir cómo crearlo. Como habrás notado, el parámetro no puede ser una interfaz o cualquier otro tipo abstracto porque el tiempo de ejecución no puede crear una instancia de él. Necesita un tipo concreto.

Si desea eliminar código repetidos se podría escribir un ayudante:

[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); 
} 

Sin embargo, todavía se necesita un método de acción diferente para cada tipo de modelo. Quizás haya otras maneras en que podría refactorizar su código. Parece que la única diferencia en sus clases modelo es el comportamiento de validación. Puede encontrar una forma diferente de codificar el tipo de validación en su clase de modelo.

+0

Al final opté por este enfoque simplemente por limitaciones de tiempo, pero también me explicaste por qué el objeto ya no es una instancia cuando se pasa al controlador, lo que es útil saber. –

1

Podría considerar el uso de una clase base en lugar de la interfaz.

+0

esto da como resultado un mensaje de error similar. Tampoco puede crear una instancia de un tipo abstracto. –

+1

Puede usar una clase base no abstracta. –

1

Creo que crearía una clase base abstracta que implementara IMyViewModel. Haría Validate un método abstracto y requeriría reemplazar en mi vista concreta los modelos heredados de MyAbstractViewModel. Dentro de su controlador, puede trabajar con la interfaz IMyViewModel si lo desea, pero el enlace y la serialización realmente necesitan una clase concreta para enlazarse. Mi $ .02.

+1

Debo añadir un comentario adicional: en realidad, no pasaría la clase abstracta al controlador, sino la clase concreta. Es posible que tenga un servicio de validación u otro método que pueda manejar la clase abstracta o la interfaz, pero para mayor claridad, realmente debe esperar lo que está obteniendo en su controlador. – Hal

4

Puede consultar esto: http://msdn.microsoft.com/en-us/magazine/hh781022.aspx.

Esto se debe a que DefaultModelBinder no tiene forma de saber qué tipo concreto de IMyViewModel debe crear. Para la solución, puede crear un archivador de modelo personalizado e indicar cómo crear y vincular una instancia de interfaz.

+0

+1 En mi humilde opinión este es el camino a seguir, supera todas las demás respuestas (algunas de las cuales obviamente no han sido probadas en el mundo real). –

Cuestiones relacionadas