2012-09-10 9 views
8

Sto tentando di generare un controllo complesso che viene utilizzato nel mio sito Web abbastanza spesso ma con campi diversi. La funzionalità nel controllo è sempre la stessa, sono solo i campi sottostanti che cambiano.Iterazione delle proprietà di un'espressione lambda

Per ottenere il metodo di visualizzazione di campi diversi, sto tentando di creare un'estensione HTMLHelper che accetta un parametro Expression<Func<TModel,TProperty>>, che conterrà le proprietà di una classe richiesta per la visualizzazione nel controllo. Per esempio:

La vista:

@model Project.Core.Page 
@Html.MyHelper(p => new { p.Author.Name, p.Author.Location, p.Author.Age }); 

E 'l'estensione sto avendo problemi con - come posso scorrere i params forniti nel lambda per fornire ciascuno con un TextBoxFor(), o anche creare manualmente un input elemento e popolare con il value e name del parametro lambda?

L'extention in pseudo:

public static MvcHtmlString MyHelper<TModel,TProperty>(
    this HtmlHelper<TModel> helper, 
    Expression<Func<TModel,TProperty>> expression) { 
    foreach (var parameter in expression.???) { 
     // helper.TextBoxFor(???) 
     // TagBuilder("input").Attributes("name", expression.???) 
    } 
} 

Mi sento come se fossi stato a guardare questo per troppo tempo, e sto anche sentendo c'è un modo più semplice che sto con vista di raggiungere questo.

Qualsiasi aiuto è molto apprezzato. Se hai bisogno di ulteriori dettagli, o ho perso qualcosa di importante, fammi sapere.

+0

Dopo aver riletto la domanda e la mia risposta, mi sono reso conto che il mio primo esempio non poteva gestire l'accesso ai valori di proprietà complesse sul modello. Ho aggiornato il mio esempio di codice per tenere conto di questa situazione. – mclark1129

risposta

2

Se si assumere i seguenti:

  1. Il risultato dell'espressione di ingresso è una proiezione (restituisce un nuovo oggetto, anonima o altro)
  2. Gli elementi della proiezione sono tutti MemberExpressions e non contengono una chiamata a un metodo sul modello o sui relativi figli

Poi si può ottenere quello che vuoi utilizzando il seguente approccio:

Edit:

Dopo aver realizzato che il mio primo esempio non poteva gestire oggetti con proprietà complesse, ho aggiornato il codice per utilizzare un aiutante metodo per accedere ai valori delle proprietà. Questo metodo attraversa la catena di proprietà utilizzando la ricorsione per restituire i valori appropriati.

public static MvcHtmlString MyHelper<TModel,object>(
    this HtmlHelper<TModel> helper, 
    Expression<Func<TModel,object>> expression) { 

     var newExpression = expression.Body as NewExpression; 
     TModel model = helper.ViewData.Model; 

     foreach (MemberExpression a in newExpression.Arguments) { 

      var propertyName = a.Member.Name; 
      var propertyValue = GetPropertyValue<TModel>(model, a); 

      // Do whatever you need to with the property name and value; 

     } 

    } 

    private static object GetPropertyValue<T>(T instance, MemberExpression me) { 

     object target; 

     if (me.Expression.NodeType == ExpressionType.Parameter) { 
      // If the current MemberExpression is at the root object, set that as the target.    
      target = instance; 
     } 
     else {     
      target = GetPropertyValue<T>(instance, me.Expression as MemberExpression); 
     } 

     // Return the value from current MemberExpression against the current target 
     return target.GetType().GetProperty(me.Member.Name).GetValue(target, null); 

    } 

Nota: non ho implementare questo direttamente come metodo di estensione MVC nella mia IDE, quindi una leggera variazione della sintassi può essere richiesto.

2

L'espressione che avremo creato sarà relativamente complicata: sarà necessario recuperare tutte le proprietà e quindi chiamare il costruttore di tipo anonimo. "Disassemblare" che potrebbe diventare doloroso ... anche se se vuoi ancora provare, ti suggerisco di lasciare solo un'implementazione del metodo vuota e guardare nel debugger per vedere come appare l'espressione.

Se saresti felice di accontentarsi di una forma leggermente più brutta di chiamare il codice, sarebbe molto più semplice da implementare questo:

@Html.MyHelper(p => p.Author.Name, p => p.Author.Location, p => p.Author.Age); 

Si potrebbe fare che prendere un params Expression<TModel, object> o si può dichiarare sovraccarichi multipli con diversi numeri di parametri, ad es

// Overload for one property 
MyHelper<TModel, TProperty1>(this ..., Expression<Func<TModel, TProperty1>> func1) 

// Overload for two properties 
MyHelper<TModel, TProperty1, TProperty2>(this ..., 
    Expression<Func<TModel, TProperty1>> func1, 
    Expression<Func<TModel, TProperty2>> func2) 

ecc

+0

Avevo preso in considerazione questo approccio, ma come dici tu è un po 'sgradevole, lo terrò giù come un "piano B". :) Grazie per aver risposto, Jon. –

1

Forse un API costruttore di stile potrebbe semplificare le cose:

@(Html.MyHelper(p) 
    .Add(p => p.Author.Age) 
    .Add(p => p.Author.LastName, "Last Name") 
    .Build()) 

Si noti che questo consente di aggiungere params opzionali nel caso in cui ne avete bisogno.

Il codice dovrebbe essere simile a questo

public static class Test 
{ 
    public static Helper<TModel> MyHelper<TModel>(this HtmlHelper helper, TModel model) 
    { 
     return new Helper<TModel>(helper, model); 
    } 
} 

public class Helper<TModel> 
{ 
    private readonly HtmlHelper helper; 
    private readonly TModel model; 

    public Helper(HtmlHelper helper, TModel model) 
    { 
     this.helper = helper; 
     this.model = model; 
    } 

    public Helper<TModel> Add<TProperty>(Expression<Func<TModel, TProperty>> expression) 
    { 
     // TODO 
     return this; 
    } 

    public MvcHtmlString Build() 
    { 
     return new MvcHtmlString("TODO"); 
    } 
} 
1

Considerate questo:

Crea cartella App_Code

Inserire il file rasoio aiutante Templates.cshtml.

sembra che qui di seguito:

@helper Print(string myvalue,string special="") 
{ 
    <pre> <input id="id" type="text" value ="@myvalue" data-val="@special"/> </pre> 
} 

In questo modo non dovete scrivere codice HTML in file C#. È molto utile

Authors.cshtml si presenta come di seguito:

@model IEnumerable<MvcApplication1.Models.Author> 
@{ 
    ViewBag.Title = "Authors"; 
} 

<h2>Authors</h2> 

@foreach(var auth in Model) 
{ 
    @Templates.Print(auth.Name);  
} 

books.cshtml si presenta come di seguito:

@model IEnumerable<MvcApplication1.Models.Book> 
@{ 
    ViewBag.Title = "Books"; 
} 

<h2>Books</h2> 

@foreach(var book in Model) 
{ 
    @Templates.Print(book.Title,book.ISBN);  
} 

Plugin tutte le proprietà speciali di cui hai bisogno per classe modello. Se diventa troppo complicato, cerca in oggetto dinamico e expando. Dipende da quanto sono complessi i tuoi modelli/modelli visivi.

Problemi correlati