6

Sto scrivendo un'app MVC2 utilizzando DataAnnotations. Ho un modello seguente:MVC2 - Come ottenere il modello principale (contenitore) all'interno del modello

public class FooModel 
{ 
    [ScaffoldColumn("false")] 
    public long FooId { get; set; } 

    [UIHint("BarTemplate")] 
    public DateTime? Bar { get; set;} 
} 

Desidero creare un modello di visualizzazione personalizzato per Bar. Ho creato seguente configurazione:

<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl<DateTime?>" %> 

<div class="display-label"> 
    <span><%: Html.LabelForModel() %></span> 
</div> 
<div class="display-field"> 
    <span><%: Html.DisplayForModel()%></span> 
    <%: Html.ActionLink("Some link", "Action", new { id = ??FooId?? }) %> 
</div> 

Ora, il mio problema è che all'interno modello per Bar voglio accedere un'altra proprietà da mio modello. Non voglio creare un modello separato per FooModel perché dovrò codificare tutte le altre proprietà di FooModel.

Dopo una breve indagine con un debugger vedo che:

  1. this.ViewData.ModelMetadata.ContainerType è FooModel (come previsto)
  2. this.ViewData.TemplateInfo ha una proprietà non pubblico VisitedObjects (di tipo System.Collections.Generic.HashSet<object>) che contiene due elementi: FooModel e DateTime?.

Come posso accedere al mio FooModel? Non voglio abituarmi a usare Reflection.

Aggiornamento:

Ho accettato risposta mootinator come sembra a me come la migliore soluzione che permette tipo di sicurezza. Ho anche svalutato la risposta di Tx3, poiché la risposta del mootinator si basa su di essa. Ciononostante, penso che ci dovrebbe essere un supporto migliore per MVC in questo tipo di scenari, che credo siano abbastanza comuni nel mondo reale, ma che mancano alle app di esempio.

+0

@Jakub: il modello di Bar.cshtml è di tipo 'DateTime?', Non c'è 'm.Bar', penso. – xport

+0

@ Bin Riciclo - Saluti, modifica la domanda. –

+0

@Jakub: Non capisco perché sia ​​necessario accedere a 'FooModel' all'interno di' DateTime? '. Non ha senso. :-) – xport

risposta

0

Scusate se questo suggerimento sembra stupido, non l'ho provato, ma non potreste fare ciò che Tx3 suggeriva senza dover creare un gruppo di nuove classi definendo una classe generica per fare riferimento a qualsiasi tipo di genitore che volete?

public class FooModel 
    { 
     [ScaffoldColumn("false")] 
     public long FooId { get; set; } 

     [UIHint("BarTemplate")] 
     public ParentedDateTime<FooModel> Bar { get; set;} 

     public FooModel() 
     { 
      Bar = new ParentedDateTime<FooModel>(this); 
     } 
    } 


    public class ParentedDateTime<T> 
    { 
     public T Parent {get; set;} 
     public DateTime? Babar {get; set; } 

     public ParentedDateTime(T parent) 
     { 
      Parent = parent; 
     } 

} 

Si potrebbe espandere tale incapsulare qualsiasi vecchio tipo con un <Parent, Child> digitato generica, anche.

Che avrebbe anche darvi il vantaggio che il modello fortemente tipizzato sarebbe per

Inherits="System.Web.Mvc.ViewUserControl<ParentedDateTime<FooType>> quindi non avrebbe dovuto nome esplicitamente il modello da utilizzare ovunque. Questo è più come le cose dovrebbero funzionare.

+0

Si potrebbe fare un ulteriore passo avanti e creare ParentedField . Avresti bisogno di 1 lezione, ma il tuo modello sarebbe ancora "brutto" dato che le proprietà di FooModel saranno di tipo ParentedField Ecc. Odora ancora per me. Il mio punto è che ModelMetadata ha già il mio oggetto genitore - Non voglio "ripulire" il mio modello (immagine come apparirebbe se serializzato su JSON, cosa che faccio anch'io). –

+0

@Jakub Probabilmente manterrei un ViewModel separato per JSON. Posso capire perché vorresti evitare la duplicazione, se possibile. –

+0

La sintassi che esiste specificamente per realizzare ciò che vuoi ancora implica l'utilizzo di ViewData: http://www.dalsoft.co.uk/blog/index.php/2010/07/29/asp-net-mvc-2-template- aiutanti-permettono-you-to-specificare--view-dati supplementari / –

3

Forse potresti creare una nuova classe, diciamo UserDateTime e conterrà DateTime nullable e il resto delle informazioni che ti servono. Quindi utilizzerai un modello di visualizzazione personalizzato per UserDateTime e avrai accesso alle informazioni richieste.

Mi rendo conto che potresti cercare un altro tipo di soluzione.

+0

Ciò significherebbe cambiare il mio UserDetailsModel, che preferirei non fare. Dovrei avere 5 proprietà DateTime dovrei continuare a creare classi diverse e copiare i dati in giro. Il mio punto è: ho già i dati di cui ho bisogno nel mio modello. Grazie comunque! –

+0

Buoni punti. Sono anche interessato a sapere qual è la soluzione. – Tx3

1

Non è possibile utilizzare l'oggetto del dizionario ViewData nel controller e quindi acquisirlo in ViewUserControl? Non sarebbe fortemente scritto ma ... potresti scrivere un helper per non fare nulla se è vuoto, e linkare per dire la pagina di cronologia degli accessi di esempio se avesse un valore.

+0

Non voglio duplicare i dati. L'ho già nel mio modello e non voglio iniziare a mettere la metà del mio modello in ViewData. –

+0

Invece di provare a combinare, inserire il modello nel ViewData e quindi estrarlo nel controllo. – Webjedi

+0

ViewData non è sicuro da testo. Apprezzo molto la sicurezza del tipo. –

2

Penso che potrebbe essere meglio estrarre questa funzionalità da una chiamata HtmlHelper dalla vista padre.

Qualcosa come RenderSpecialDateTime<TModel>(this HtmlHelper html, Expression<Func<TModel,DateTime?>> getPropertyExpression) probabilmente farebbe il lavoro.

Altrimenti, dovrete fare qualcosa di simile a quanto suggerito da Tx3. Ho svalutato la sua risposta, ma ho postato questo come alternativa.

+0

Scusa, non capisco. 1) Estrai * quale * funzionalità? 2) Come dovrei chiamare Html.RenderSpecialDateTime() nella Vista Genitore quando tutto ciò che sto facendo nella Vista Genitore è Html.DisplayForModel()? –

1

Sembrerebbe che da qualche parte tra MVC 5.0 e 5.2.2 una proprietà "Contenitore" sia stata aggiunta alla classe ModelMetadata.

Tuttavia, poiché tutti i metodi in un provider responsabile della creazione di metadati (GetMetadataForProperty, Crea ecc.) Non dispongono di un contenitore nella firma, la proprietà Container viene assegnata solo in determinati casi (GetMetadataForProperties e GetMetadataFromProvider in base al codice riflesso) e nel mio caso era solitamente nullo.

Quindi quello che ho finito per fare è l'override del GetMetadataForProperty in un nuovo fornitore di metadati e impostando lì:

public override ModelMetadata GetMetadataForProperty(Func<object> modelAccessor, Type containerType, string propertyName) 
{ 
    var propMetaData = base.GetMetadataForProperty(modelAccessor, containerType, propertyName); 
    Object container = modelAccessor.Target.GetType().GetField("container").GetValue(modelAccessor.Target); 
    propMetaData.Container = container; 
    return propMetaData; 
} 

So che questo è riflesso, ma è piuttosto succinta. Sembrerebbe che MS stia correggendo questo oversite, quindi forse sarà possibile sostituire il codice di riflessione in futuro.