2012-09-10 67 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);  
} 

所有插件的特殊性能,你每个模型类需要。如果太复杂,请查看动态和扩展对象。这取决于你的模型/视图模型有多复杂。