2012-09-10 86 views
8

我試圖泛型化一個複雜的控件,該控件在我的網站中經常使用,但使用不同的字段。控件中的功能總是相同的,它只是變化的底層字段。迭代lambda表達式的屬性

要實現顯示不同字段的方法,我試圖創建一個接受Expression<Func<TModel,TProperty>>作爲參數的HTMLHelper擴展,該擴展將包含控件中顯示所需的類的屬性。例如:

的觀點:

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

這是我有問題的擴展 - 我該如何遍歷提供PARAMS在lambda提供每一個TextBoxFor(),甚至手動創建input元素並使用lambda參數的valuename填充它?

在僞進一步擴展:

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.???) 
    } 
} 

我覺得我一直在盯着這個時間太長了,而且我也覺得有我俯瞰實現這一目標的一個更簡單的方法。

任何幫助,非常感謝。如果您需要更多詳細信息,或者我錯過了一些重要的信息,請告訴我。

+0

重新閱讀問題和我的迴應後,我意識到我的第一個示例無法處理訪問模型上覆雜屬性的值。我已更新我的代碼示例以解決這種情況。 – mclark1129

回答

2

如果假定:

  1. 輸入表達式的結果是一個凸起(返回一個新對象, 匿名或以其他方式)
  2. 元素的投影都是MemberExpressions,並且不包含對模型或其子模型上的方法的調用

然後你就可以實現你想要的使用以下方法:

編輯:

意識到我的第一個例子不能處理複雜屬性的對象後,我更新使用輔助代碼方法來訪問屬性值。此方法使用遞歸遍歷屬性鏈以返回適當的值。

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

    } 

注:我沒有直接實現這個作爲我的IDE中的MVC擴展方法,所以可能需要對語法的輕微變化。

2

您創建的表達式會相對複雜 - 它需要獲取所有屬性,然後調用匿名類型構造函數。 「反彙編」可能會帶來痛苦......但如果你仍然想嘗試,我建議只留下一個空的方法實現,並在調試器中查看錶達式的樣子。

如果你很樂意滿足於調用代碼的稍微難看的形式,這將是容易實現這一點:

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

你可以做的是採取params Expression<TModel, object>或者你可以聲明具有不同數量參數的多重過載,例如

// 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) 

+0

我曾經考慮過這種方法,但正如你所說這有點難看,我會把它作爲'B計劃'來保存。 :)謝謝你的回答,喬恩。 –

1

也許建設者風格的API可能會簡化事情:

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

注意,這使您可以添加可選的PARAMS的情況下,你需要他們。

的代碼會是這個樣子

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

考慮一下:

創建App_Code文件夾

把剃刀幫助文件Templates.cshtml。

它看起來象下面這樣:

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

這樣,你沒有寫在C#中的文件的HTML。這非常方便。

Authors.cshtml看起來象下面這樣:

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

<h2>Authors</h2> 

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

books.cshtml看起來象下面這樣:

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

<h2>Books</h2> 

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

所有插件的特殊性能,你每個模型類需要。如果太複雜,請查看動態和擴展對象。這取決於你的模型/視圖模型有多複雜。