迭代lambda表达式的属性

8

我正在尝试泛化一个复杂的控件,该控件在我的网站上经常使用,但是使用不同的字段。控件中的功能始终相同,只是底层字段发生了变化。

为了实现显示不同字段的方法,我正在尝试创建一个HTMLHelper扩展,它接受Expression<Func<TModel,TProperty>>作为参数,其中包含用于在控件中显示所需类的属性。例如:

视图:

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

我遇到的问题是关于扩展的 - 我要如何迭代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.???)
    }
}

我觉得我已经盯着这个问题看了太久,而且我感觉还有一种更简单的方法可以实现它。

非常感谢任何帮助。如果需要进一步的细节或者我漏掉了重要的信息,请告诉我。


重新阅读问题和我的回复后,我意识到我的第一个示例无法处理访问模型的复杂属性值。我已更新我的代码示例以解决这种情况。 - mclark1129
4个回答

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)

etc.


我曾考虑过这种方法,但正如你所说有点不太美观,我会把它作为备用方案。:) 谢谢你的回答,Jon。 - Rory McCrossan

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扩展方法,所以可能需要稍微变化一下语法。


1

也许一个类似于构建器的API可以简化事情:

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

请注意,这样可以让您添加可选参数(如果需要的话)。
代码看起来会像这样。
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 文件夹
将 Razor 帮助文件 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);    
}

根据每个模型类的需要插入所有特殊属性。如果变得太复杂,请查看动态和可扩展对象。这取决于您的模型/视图模型有多复杂。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接