【问题标题】:Lambda Expression assign source objectLambda 表达式分配源对象
【发布时间】:2012-01-11 14:48:06
【问题描述】:

上下文: 由于我们是在 C# MVC3 中开发的,因此我们希望设计一些类来处理网页上的表格。 (分页/搜索/等...)。

所以我们最终发现最好有以下类:

将包含所有其他对象并知道当前页面/当前搜索等的表对象...(其他信息)

public class Table<T> where T : IPrivateObject
{
    ...

    public ICollection<Column<T>> Columns { get; set; }
    public ICollection<Row<T>> Rows { get; set; }
    public ICollection<RowMenu<T>> Menus { get; set; }

    public ICollection<T> Items { get; set; }

    public Table(
        ICollection<T> inputItems,
        ICollection<Column<T>> columns, 
        ICollection<RowMenuItem<T>> rowMenuItems,
        ...)
        {
            ...
            this.Columns = columns;
        }

知道应该显示哪个属性和标题值的列对象

public class Column<T> where T : IPrivateObject
{
    public string Value { get; set; }
    public Expression<Func<T, object>> Property { get; set; }

    public Column(Expression<Func<T, object>> property, string value)
    {
        this.Property = property;
        this.Value = value;
    }
}

其他课程不是很有趣,所以我不会在这里发布。

在控制器中,我们像这样使用这些类:

public ActionResult Index(string search = null, string sort = null, int order = 1, int take = 10, int page = 1)
    {
        ICollection<Person> people = prismaManager.PersonManager.Search(search);
        ICollection<Column<Person>> columns= new List<Column<Person>>();
        columns.Add(new Column<Person>(Person => Person, "Person"));
        columns.Add(new Column<Person>(Person => Person.LastMembershipApproval, "Last Membership approval"));
        Table<Person> table = people.ToTable(columns);
    }

我们现在正在编写一个可以正确显示表格的助手。 它适用于标题,但是当我们想要使用 @Html.DisplayFor() 帮助器时,我们会遇到表达式问题。

这是我们目前拥有的内容:

private static string TableRows<T>(HtmlHelper<Table<T>> helper, Table<T> table) where T : IPrivateObject
    {
        StringBuilder sb = new StringBuilder();
        foreach (var item in table.Items)
        {
            sb.AppendLine("<tr>");
            foreach (var column in table.Columns)
            {
                sb.AppendLine("<td>");
                sb.AppendLine(helper.DisplayFor(obj => ??? ).ToString()); // How should I use the Expression that is stored in the column but for the current element ?
                sb.AppendLine("</td>");
            }
            sb.AppendLine("</tr>");
        }
        return sb.ToString();
    }

为此,我们应该将存储在列中的表达式中的“Person”参数的值设置为当前项。

new Column<Person>(Person => Person, "Person"));

我们应该怎么做呢? 我们应该(如果可能的话)修改表达式来设置值吗? 我们是否应该使用旧表达式作为基本表达式重新创建一个新表达式?

我已经搜索了 3 天,但找不到任何答案。

感谢您的帮助。

更新:

问题是(正如@Groo 和@Darin Dimitrov 所说)Helper 的类型是 HtmlHelper> 而不是 HtmlHelper。 知道如何从 HtmlHelper> 中获取 HtmlHelper 吗?

更新:

Person类如下:

public class Person : IPrivateObject
{
    public int Id { get; set; }
    public int? AddrId { get; set; }

    [DisplayName("First Name")]
    [StringLength(100)]
    [Required]
    public string FirstName { get; set; }

    [DisplayName("Last Name")]
    [StringLength(100)]
    [Required]
    public string LastName { get; set; }

    [DisplayName("Initials")]
    [StringLength(6)]
    public string Initials { get; set; }

    [DisplayName("Last membership approval")]
    public Nullable<DateTime> LastMembershipApproval { get; set; }

    [DisplayName("Full name")]
    public string FullName
    {
        get
        {
            return FirstName + " " + LastName;
        }
    }
    public override string ToString()
    {
        return FullName;
    }
}

【问题讨论】:

    标签: c# asp.net-mvc-3 lambda expression


    【解决方案1】:

    以下是您可以继续操作的方法。首先编写一个自定义视图数据容器实现,它可能很简单:

    public class ViewDataContainer : IViewDataContainer
    {
        public ViewDataContainer(ViewDataDictionary viewData)
        {
            ViewData = viewData;
        }
    
        public ViewDataDictionary ViewData { get; set; }
    }
    

    然后只需实例化 HtmlHelper&lt;T&gt; 这就是您所需要的:

    private static string TableRows<T>(HtmlHelper<Table<T>> helper, Table<T> table) where T : IPrivateObject
    {
        var sb = new StringBuilder();
        sb.AppendLine("<table>");
        foreach (var item in table.Items)
        {
            sb.AppendLine("<tr>");
            foreach (var column in table.Columns)
            {
                var viewData = new ViewDataDictionary<T>(item);
                var viewContext = new ViewContext(
                    helper.ViewContext.Controller.ControllerContext,
                    helper.ViewContext.View,
                    new ViewDataDictionary<T>(item),
                    helper.ViewContext.Controller.TempData,
                    helper.ViewContext.Writer
                );
                var viewDataContainer = new ViewDataContainer(viewData);
                var itemHelper = new HtmlHelper<T>(viewContext, viewDataContainer);
    
                sb.AppendLine("<td>");
                sb.AppendLine(itemHelper.DisplayFor(column.Property));
                sb.AppendLine("</td>");
            }
            sb.AppendLine("</tr>");
        }
        sb.AppendLine("</table>");
        return sb.ToString();    
    }
    

    更新:

    前面的示例不处理值类型,因为列中的表达式是 Expression&lt;Func&lt;T, object&gt;&gt; 类型,并且当您指向值类型属性时,值将被装箱并且 ASP.NET MVC 不允许这样的表达式与模板助手一起使用。为了解决这个问题,一种可能性是测试值是否被装箱并提取实际类型:

    sb.AppendLine("<td>");
    var unary = column.Property.Body as UnaryExpression;
    if (unary != null && unary.NodeType == ExpressionType.Convert)
    {
        var lambda = Expression.Lambda(unary.Operand, column.Property.Parameters[0]);
        sb.AppendLine(itemHelper.Display(ExpressionHelper.GetExpressionText(lambda)).ToHtmlString());
    }
    else
    {
        sb.AppendLine(itemHelper.DisplayFor(column.Property).ToHtmlString());
    }
    sb.AppendLine("</td>");
    

    【讨论】:

    • 感谢您的帮助。它适用于 Person => Person 表达式。但它不适用于 Person 上的属性...我仍然遇到异常:“模板只能用于字段访问、属性访问、单维数组索引或单参数自定义索引器表达式。”何时轮到要评估的表达式“Person => Person.LastMembershipApproval”...知道为什么吗?
    • @Whoami,很奇怪,当我测试它时它起作用了。如果您向我发送说明问题的示例项目,我可能会为您提供帮助。
    • 我将添加 Person 类,以便您查看。 更新类已添加。
    • ,我会这样做的。将在几分钟内发送。
    • @Whoami,您可以让 Column 类采用 2 个通用参数:Column&lt;T, TValue&gt; 但正如我所说,这可能会影响您设计的其他部分,例如 Table 类。另一种可能性是通过尝试将表达式的主体转换为 UnaryExpression 然后测试其节点类型是否等于ExpressionType.Convert 来测试 column.Property 是否被装箱。在这种情况下,您可以简单地使用此一元表达式的操作数来获取实际的 lambda 并构建一个您将传递给 Display 助手的新表达式...
    【解决方案2】:

    有几件事你应该改变。

    1. 首先让我感到惊讶的是,您的表格既有列列表,也有。您应该将设计更改为:一个 Table 有一个 Rows 列表,每个 Row 有一个 Columns 列表(反之亦然)。

      但这句话的意义不大。我猜“列”类似于“列定义”并且不包含任何数据,但在这种情况下,我认为拥有 ICollection&lt;Row&lt;T&gt;&gt; 而不仅仅是 ICollection&lt;T&gt; 毫无意义。

    2. 接下来,您可能想要存储一个委托,例如 Func&lt;T, object&gt;,而不是 Expression&lt;Func&lt;T, object&gt;&gt;

    3. Property 至少应该有私有设置器(或者,更好的是,只读支持字段)。这不是您希望代码的其他部分更改的内容。

    4. 命名非常混乱恕我直言。我会选择更好的属性名称。如果我没听错的话,ValueProperty 实际上应该分别称为 HeaderNameGetValue

    说了这么多,我会把Column改成这样的:

    public class Column<T> where T : IPrivateObject
    {
        private readonly string _name;
        private readonly Func<T, object> _valueGetter;
    
        /// <summary>
        /// Gets the column name.
        /// </summary>
        public string Name
        {
            get { return _name; }
        }
                
        /// <summary>
        /// Gets the value of this column from the
        /// specified object.
        /// </summary>
        /// <param name="obj">The object.</param>
        /// <returns></returns>
        public object GetValueFrom(T obj)
        {
            return _valueGetter(obj);
        }
    
        public Column(string columnName, Func<T, object> valueGetter)
        {
            _name = columnName;
            _valueGetter = valueGetter;
        }
    }
    

    然后在你的循环中简单地使用它:

    sb.AppendLine(column.GetValueFrom(item).ToString()); 
    

    【讨论】:

    • 感谢您的快速回复!但是对于 1. 我不同意,因为表是由列驱动的。仅当行有特殊内容时,该行才会出现在此处(例如,带有 personId % 2 == 0 的行应显示为红色或其他内容)。其余的,我同意并感谢您的回复。谢谢
    • @Whoami:我已经更新了答案。我相信最后一行就是您要问的。
    • 是的,这就是我要问的。我会尝试这种方式并检查它是否可以与 DisplayFor 帮助器一起使用。
    • @Groo,因为它不会编译。 DisplayFor 期望 Expression&lt;Func&lt;Table&lt;T&gt;, TValue&gt;&gt;column.PropertyExpression&lt;Func&lt;T, object&gt;&gt;
    • @Whoami: 嗯...好吧,我一定是管理员没怎么用过HtmlHelper,所以我有点困惑。这就是问题所在:如果您使用HtmlHelper&lt;Table&lt;T&gt;&gt;,这意味着您的模型是Table&lt;T&gt; 类型,这反过来意味着您的表达式必须是Expression&lt;Table&lt;T&gt;, Tvalue&gt; 类型。换句话说,它需要一个表达式树,它将从您的Table&lt;T&gt; 中获取所需的属性,而不是Column&lt;T&gt;。当我整理出来时,我会重新考虑并更新。
    【解决方案3】:

    您需要使用 expression.Compile() 编译表达式(您的 column.Property 中有属性表达式)。这会给你一个代表。您可以在那里传递对象并获取值。您还需要将人或 T 传递给辅助方法。

    【讨论】:

    • 感谢您的回复,但这不起作用。它不能与 DisplayFor 模板一起使用,因为我得到以下异常:模板只能用于字段访问、属性访问、单维数组索引或单参数自定义索引器表达式。
    • 知道如何转换/实例化一个新的 T 类型的 HtmlHelper 以便我可以将它用于 DisplayFor 吗?
    • 哦!所以你想在绑定中使用它?您需要 lambda 表达式中的属性名称吗?
    • 所以,你不能这样做: sb.AppendLine(helper.DisplayFor(column.Property).ToString()); ?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-10-05
    • 1970-01-01
    • 1970-01-01
    • 2014-09-20
    • 1970-01-01
    • 2018-12-07
    相关资源
    最近更新 更多