Лямбда-выражение присваивает исходный объект - PullRequest
4 голосов
/ 11 января 2012

Контекст: Поскольку мы разрабатываем на 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();
    }

Чтобы это работало, мы должны установить значение параметра «Персона» из выражения, хранящегося в столбце, для текущего элемента.

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

Как мы должны это сделать? Должны ли мы (если это возможно) изменить выражение, чтобы установить значение? Должны ли мы воссоздать новое выражение, используя старое как основное выражение?

Я искал 3 дня и не могу найти ответы.

Спасибо за вашу помощь.

ОБНОВЛЕНИЕ:

Проблема в том (как сказал @Groo & @Darin Dimitrov), что Помощник имеет тип HtmlHelper>, а не HtmlHelper. Любая идея, как я мог бы получить HtmlHelper из HtmlHelper>?

ОБНОВЛЕНИЕ:

Класс персонажа выглядит следующим образом:

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

Ответы [ 3 ]

4 голосов
/ 12 января 2012

Вот как вы могли бы продолжить.Начните с написания пользовательской реализации контейнера данных представления, которая может быть простой:

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

    public ViewDataDictionary ViewData { get; set; }
}

, а затем просто создайте HtmlHelper<T>, что вам нужно:

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

UPDATE:

В предыдущем примере не обрабатываются типы значений, поскольку выражение в столбце имеет тип Expression<Func<T, object>>, а когда вы указываете на свойство типа значения, значение будет упаковано и 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>");
2 голосов
/ 11 января 2012

Есть несколько вещей, которые вы должны изменить.

  1. Первое, что меня удивило, это то, что ваша таблица имеет список столбцов и строк.Вы должны изменить дизайн на что-то вроде: Таблица имеет список строк, а каждая строка имеет список столбцов (или наоборот).

    Но это замечание менее актуально.Я предполагаю, что «Столбец» является чем-то вроде «Определение столбца» и не содержит данных, но в этом случае я не вижу смысла иметь ICollection<Row<T>> вместо просто ICollection<T>.

  2. Далее, вы, вероятно, захотите сохранить делегат, такой как Func<T, object>, вместо Expression<Func<T, object>>.

  3. Property должен по крайней мере иметь частные сеттеры (или, что еще лучше,только для чтения резервных полей).Это не то, что вы хотели бы изменить в других частях вашего кода.

  4. Именование очень запутанно ИМХО.Я бы выбрал лучшие имена свойств.Если я вас правильно понял, Value и Property на самом деле должны называться HeaderName и GetValue соответственно.

Сказав все это, я бы изменил 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 голос
/ 11 января 2012

Вам нужно скомпилировать выражение, используя expression.Compile() (у вас есть выражение свойства в вашем столбце. Свойство).Это даст вам делегата.Вы можете передать объект туда и получить значение.Вам также нужно будет передать этого человека, или T, вспомогательному методу.

...