2011-01-17 36 views
8

En resumen me gustaría ser capaz de pasar un modelo de vista genérico en mis vistasMVC modelo de vista genérico

Aquí hay un código simplificado de la esencia de lo que estoy tratando de lograr

public interface IPerson 
{ 
    string FirstName {get;} 
    string LastName {get;} 
} 

public class FakePerson : IPerson 
{ 
    public FakePerson() 
    { 
     FirstName = "Foo"; 
     LastName = "Bar"; 
    } 

    public string FirstName {get; private set;} 
    public string LastName {get; private set;} 
} 

public class HomeViewModel<T> 
    where T : IPerson, new() 
{ 
    public string SomeOtherProperty{ get; set;} 
    public T Person { get; private set; } 

    public HomeViewModel() 
    { 
     Person = new T(); 
    } 
} 

public class HomeController : Controller { 
    public ViewResult Index() { 
     return View(new HomeViewModel<FakePerson>()); 
    } 
} 

Si yo crear mi punto de vista de la siguiente manera todos funciona como se espera

<%@ Page Language="C#" 
    MasterPageFile="~/Views/Shared/Site.Master" 
    Inherits="System.Web.Mvc.ViewPage<HomeViewModel<FakePerson>>" %> 
<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> 
    <%: Html.DisplayFor(m=>m.Person.FirstName) %> 
    <%: Html.DisplayFor(m=>m.Person.LastName) %> 
</asp:Content> 

sin embargo, yo no quiero depender directamente de FakePerson en la vista en el caso de que quiero pasar alguna otra aplicación IPerson, por lo que tratado de cambiar la directiva de página a

<%@ Page Language="C#" 
    MasterPageFile="~/Views/Shared/Site.Master" 
    Inherits="System.Web.Mvc.ViewPage<HomeViewModel<IPerson>>" %> 

Pero claro que no funciona, por lo que, después de todo un día de curioseaba, tengo más canas y ni idea de qué hacer a continuación.

¿Alguien puede ayudar por favor?

[ACTUALIZACIÓN]

Algunos aconsejan ha sugested que debo utilizar una interfaz covariante; definir una interfaz no genérica y usarla en la Vista. Desafortunadamente lo he intentado pero hay una implicación adicional. Me gustaría que la función HtmlHelper para poder acceder a los atributos de anotación de datos que se pueden definir en la clase IPerson derivada

public class FakePerson : IPerson 
{ 
    public FakePerson() 
    { 
     FirstName = "Foo"; 
     LastName = "Bar"; 
    } 

    [DisplayName("First Name")] 
    public string FirstName {get; private set;} 

    [DisplayName("Last Name")] 
    public string LastName {get; private set;} 
} 

Así, mientras que el uso de una interfaz covariante esta manera funciona, en parte, para acceder al tipo derivado a través ViewModel; dado que la vista se escribe a la interfaz, parece que los atributos no son accesibles.

¿Existe alguna forma, en la vista, de acceder a estos atributos, tal vez con reflexión? ¿O podría haber otra forma de escribir la Vista en el genérico?

+0

¿Cómo se puede esperar que su visión para el acceso deriva datos, pero no desea que dependa de los datos derivados? Puesto de otra forma, si accede a datos que son exclusivos de FakePerson, su vista depende de FakePerson, y como tal debería hacer que su modelo sea FakePerson. Si transfiere algún otro objeto, tendrá errores al acceder a FakePerson. –

+0

No se pueden usar anotaciones de datos con interfaces de todos modos, ya que los atributos no se heredan (ver http://stackoverflow.com/questions/540749/can-ac-sharp-class-inherit-attributes-from-its-interface) –

Respuesta

0

Su código es casi correcto; solo necesita pasar un HomeViewModel<IPerson> (no FakePerson) en el controlador.

También podría usar una interfaz covariante para el Modelo en la vista, pero eso probablemente sea excesivo.

+0

Gracias por la rápida respuesta. ¿Quiere decir el código – ricardo

+0

clase pública HomeController: Controller { public ViewResult Index() { return View (new HomeViewModel ()); } } – ricardo

+0

public ViewResult Index() {return View (new HomeViewModel ()) ;. Esto da un error de tiempo de ejecución ya que IPerson no es un tipo concreto y el punto es ser capaz de pasar una clase derivada de IPerson a la vista – ricardo

0

Haga que su clase ViewModel implemente una interfaz no genérica (o covariante genérica), luego modifique la vista para tomar esa interfaz en lugar de la clase concreta.

+0

Gracias. Lo he considerado y lo intenté nuevamente, pero esta aplicación no me da todas las características que esperaba.Por favor, mira la actualización de mi pregunta. – ricardo

2

He logrado que la covariante funcione, es decir, vincular la Vista a una clase base abstracta. De hecho, lo que tengo es un enlace a una lista <MyBaseClass>. Luego creo una vista específica fuertemente tipada para cada subclase. Eso se encarga de la unión.

Pero volver a enlazar fallará porque DefaultModelBinder solo sabe acerca de la clase base abstracta y obtendrá una excepción como, 'No se puede crear clase abstracta'. La solución es tener una propiedad en su clase base como esta:

public virtual string BindingType 
    { 
     get 
     { 
      return this.GetType().AssemblyQualifiedName; 
     } 
    } 

Enlazar eso con una entrada oculta en su vista. Luego, reemplace su ModelBinder predeterminado con uno personalizado en Global.asax:

// Replace default model binder with one that can deal with BaseParameter, etc. 
    ModelBinders.Binders.DefaultBinder = new CustomModelBinder(); 

Y en su carpeta de modelo personalizado que interceptar los unen. Si se trata de uno de los tipos abstractos conocidos, que analizar la propiedad bindingtype y reemplazar el tipo de modelo para que pueda obtener una instancia de la subclase:

public class CustomModelBinder : DefaultModelBinder 
{ 
    private static readonly ILog logger = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); 

    protected override object CreateModel(ControllerContext controllerContext, ModelBindingContext bindingContext, System.Type modelType) 
    { 
     if (modelType.IsInterface || modelType.IsAbstract) 
     { 
      // This is our convention for specifying the actual type of a base type or interface. 
      string key = string.Format("{0}.{1}", bindingContext.ModelName, Constants.UIKeys.BindingTypeProperty);    
      var boundValue = bindingContext.ValueProvider.GetValue(key); 

      if (boundValue != null && boundValue.RawValue != null) 
      { 
       string newTypeName = ((string[])boundValue.RawValue)[0].ToString(); 
       logger.DebugFormat("Found type override {0} for Abstract/Interface type {1}.", modelType.Name, newTypeName); 

       try 
       { 
        modelType = System.Type.GetType(newTypeName); 
       } 
       catch (Exception ex) 
       { 
        logger.ErrorFormat("Error trying to create new binding type {0} to replace original type {1}. Error: {2}", newTypeName, modelType.Name, ex.ToString()); 
        throw; 
       } 
      } 
     } 

     return base.CreateModel(controllerContext, bindingContext, modelType); 
    } 

    protected override object GetPropertyValue(ControllerContext controllerContext, ModelBindingContext bindingContext, System.ComponentModel.PropertyDescriptor propertyDescriptor, IModelBinder propertyBinder) 
    { 
     if (propertyDescriptor.ComponentType == typeof(BaseParameter)) 
     { 
      string match = ".StringValue"; 
      if (bindingContext.ModelName.EndsWith(match)) 
      { 
       logger.DebugFormat("Try override for BaseParameter StringValue - looking for real type's Value instead."); 
       string pattern = match.Replace(".", @"\."); 
       string key = Regex.Replace(bindingContext.ModelName, pattern, ".Value"); 
       var boundValue = bindingContext.ValueProvider.GetValue(key); 
       if (boundValue != null && boundValue.RawValue != null) 
       { 
       // Do some work here to replace the base value with a subclass value... 
        return value; 
       } 
      } 
     } 

     return base.GetPropertyValue(controllerContext, bindingContext, propertyDescriptor, propertyBinder); 
    } 
} 

Aquí, mi clase abstracta es BaseParameter y sustituyo a la StringValue propiedad con un valor diferente de la subclase (no se muestra).

Tenga en cuenta que aunque se puede volver a enlazar con el tipo correcto, los valores del formulario asociado sólo con la subclase no serán roundtripped automáticamente porque la única modelbinder ver las propiedades de la clase base. En mi caso, solo tuve que reemplazar un valor en GetValue y obtenerlo de la subclase, así que fue fácil. Si necesita vincular muchas propiedades de subclase, tendrá que trabajar un poco más y obtener el formulario (ValueProvider [0]) y rellenar la instancia usted mismo.

Tenga en cuenta que puede agregar un nuevo encuadernador de modelo para un tipo específico para que pueda evitar la verificación de tipo genérico.

0

He creado una interfaz

public interface ITestModel 
    { 
     string FirstName { get; set; } 
     string LastName { get; set; } 
     string Address { get; set; } 
     int Age { get; set; } 
    } 

Crear clase y heredada de esta interfaz

class TestModel : ITestModel 
    { 
     public string FirstName { get; set; } 
     public string LastName { get; set; } 
     public string Address { get; set; } 
     public int Age { get; set; } 

    } 

instancia creada de la clase en el controlador

public ActionResult TestMethod() 
     { 
      ITestModel testModel; 
      testModel = new TestModel(); 
      testModel.FirstName = "joginder"; 
      testModel.Address = "Lovely Home"; 
      testModel.LastName = "singh"; 
      testModel.Age = 36; 
      return View("testMethod", testModel); 
     } 

Ver creado de esta manera como

<%@ Page Title="" Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage<TestProject.ITestModel>" %> 

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server"> 
    TestMethod 
</asp:Content> 

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server"> 

    <h2>TestMethod</h2> 

    <fieldset> 
     <legend>Fields</legend> 

     <div class="display-label">FirstName</div> 
     <div class="display-field"><%: Model.FirstName %></div> 

     <div class="display-label">LastName</div> 
     <div class="display-field"><%: Model.LastName %></div> 

     <div class="display-label">Address</div> 
     <div class="display-field"><%: Model.Address %></div> 

     <div class="display-label">Age</div> 
     <div class="display-field"><%: Model.Age %></div> 

    </fieldset> 
    <p> 
     <%: Html.ActionLink("Edit", "Edit", new { /* id=Model.PrimaryKey */ }) %> | 
     <%: Html.ActionLink("Back to List", "Index") %> 
    </p> 

</asp:Content> 

Ahora me puede pasar cualquier objeto de cualquier interfaz similar

espero que ayudará :)

Cuestiones relacionadas