2011-01-17 17 views
8

Insomma vorrei essere in grado di passare un ViewModel generica nelle mie opinioniMVC ViewModel generico

Ecco il codice semplificato del succo di quello che sto cercando di realizzare

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

Se io creare mio punto di vista come segue tutte le opere come previsto

<%@ 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> 

Comunque io non voglio dipendere direttamente FakePerson nella vista nel caso in cui voglio passare qualche altra implementazione IPerson, così ho provato a cambiare la direttiva pagina

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

Ma, naturalmente, che non funziona, così, dopo un'intera giornata di pasticciare in giro, ho più capelli grigi e nessun indizio che cosa fare dopo.

Qualcuno può aiutare per favore.

[UPDATE]

Qualche consiglio ha sugested che dovrei usare un'interfaccia covariante; definire un'interfaccia non generica e utilizzarla nella vista. Unfortuanetly ho provato questo, ma c'è una implicazione adizionale. Vorrei la funzione HtmlHelper per poter accedere a qualsiasi attributi dati di annotazione che possono essere definite nella classe IPerson derivata

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

Così, mentre utilizzando un'interfaccia covariante questo modo funziona, in parte, per accedere al tipo derivato tramite il ViewModel; poiché la vista è stata digitata nell'interfaccia, sembra che gli attributi non siano accessibili.

C'è forse un modo, nella vista, per ottenere l'accesso a questi attributi, magari con la riflessione. Oppure potrebbe esserci un altro modo per digitare la vista sul generico.

+0

Come ci si può aspettare l'immagine per l'accesso derivato dati, ma non vuoi farlo dipendere dai dati derivati? Mettiamola in un altro modo, se accedi ai dati che sono univoci per FakePerson, allora la tua vista dipende da FakePerson, e come tale dovresti rendere il tuo modello un FakePerson. Se passi in qualche altro oggetto, allora avrà degli errori all'accesso a FakePerson. –

+0

Non è possibile utilizzare le annotazioni di dati con le interfacce in ogni caso, poiché gli attributi non sono ereditati (consultare http://stackoverflow.com/questions/540749/can-ac-sharp-class-inherit-attributes-from-its-interface) –

risposta

0

Il codice è quasi corretto; è sufficiente passare un HomeViewModel<IPerson> (non FakePerson) nel controller.

Si potrebbe anche utilizzare un'interfaccia covariante per il modello nella vista, ma probabilmente è eccessivo.

+0

Grazie per la risposta rapida. Intendi il codice – ricardo

+0

classe pubblica HomeController: Controller { public ViewResult Index() { return View (new HomeViewModel ()); } } – ricardo

+0

public ViewResult Index() {return View (nuovo HomeViewModello ()) ;. Questo dà un errore di runtime dal momento che IPerson non è un tipo concreto e il punto è essere in grado di passare una classe derivata di IPerson alla vista – ricardo

0

Fai in modo che la classe ViewModel implementa un'interfaccia non generica (o generica covariante), quindi modifica la vista per utilizzare tale interfaccia anziché la classe concreta.

+0

Grazie. Ho preso in considerazione questo e ho appena provato di nuovo per buona misura, ma questo approccio non mi dà tutte le funzionalità che speravo.Si prega di consultare l'aggiornamento sulla mia domanda. – ricardo

2

Sono riuscito a far funzionare covariante, ovvero legare la vista a una classe di base astratta. In effetti, quello che ho è un legame a un elenco <MyBaseClass>. Quindi creo una vista specifica fortemente digitata in ciascuna sottoclasse. Questo si prende cura della rilegatura.

Ma il rebinding fallirà perché DefaultModelBinder conosce solo la classe base astratta e otterrete un'eccezione come "Impossibile creare una classe astratta". La soluzione è quella di avere una proprietà sulla vostra classe di base in questo modo:

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

Bind che ad un ingresso nascosto nella vista. Quindi sostituisci il tuo ModelBinder predefinito con uno personalizzato in Global.asax:

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

E nel raccoglitore modello personalizzato si intercetta il legame. Se è per una delle tue note tipi astratti, si analizza la proprietà bindingType e sostituire il tipo di modello in modo da ottenere un'istanza della sottoclasse:

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

Ecco, la mia classe astratta è BaseParameter e sto sostituendo lo StringValue proprietà con un valore diverso dalla sottoclasse (non mostrato).

Si noti che sebbene sia possibile eseguire nuovamente il binding al tipo corretto, i valori del modulo associati solo alla sottoclasse non verranno automaticamente trasferiti in quanto il modelbinder visualizza solo le proprietà nella classe base. Nel mio caso, ho dovuto solo sostituire un valore in GetValue e ottenerlo invece dalla sottoclasse, quindi è stato facile. Se è necessario associare molte proprietà di sottoclasse, è necessario eseguire un po 'più di lavoro e recuperare il modulo (ValueProvider [0]) e compilare personalmente l'istanza.

Si noti che è possibile aggiungere un nuovo raccoglitore modello per un tipo specifico in modo da evitare il controllo di tipo generico.

0

ho creato un'interfaccia

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

Creare classe ed ereditato da questa interfaccia

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

    } 

istanza creata della classe controller

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

View creato come questo senso

<%@ 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> 

Ora posso passare qualsiasi oggetto di qualsiasi interfaccia simile

spero che vi aiuterà :)