Lambda Expression assigns source object

Context: Since we are developing in C # MVC3, we wanted to have some classes designed to handle tables on a web page. (Pagination / search / etc.).

So, we finally found out that it is best to have the following classes:

A table object that will hold all other objects and knows the current search on pages / current data, etc .... (odd information)

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

A column object that knows which property should be displayed, and the value of the header

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

Other classes are not very interesting, so I will not publish them here.

In the controller, we use the following classes:

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

Now we are writing an assistant that will correctly display the table. It works well for the header, but we run into a problem with expressions when we want to use the @ Html.DisplayFor () helper.

This is what we currently have for content:

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

For this to work, we must set the value of the Person parameter from the expression stored in the column to the current element.

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

How should we do this? Should we (if possible) change the expression to set the value? Should we recreate the new expression using the old as the main expression?

I searched for 3 days and can not find the answers.

Thank you for your help.

UPDATE:

The problem is that the Helper is of type HtmlHelper>, not HtmlHelper (as @Groo and @Darin Dimitrov said). Any idea how I can get HtmlHelper from HtmlHelper>?

UPDATE:

The Person class is as follows:

 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; } } 
+4
source share
3 answers

Here you could continue. Start by writing a custom implementation of the presentation data container, which can be as simple as:

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

and then just create the HtmlHelper<T> instance that you need:

 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:

The previous example does not handle value types because the expression in the column is of type Expression<Func<T, object>> , and when you point to the value type property, the value will be inserted into the square, and ASP.NET MVC does not allow such expressions to use with template helpers. To fix this problem, one of the possibilities is to check if the value has been inserted into the field and extract the actual type:

 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>"); 
+4
source

There are a few things you need to change.

  • The first thing that surprised me was that your table has a list of columns and Rows . You should change the design to something like: the table has a list of rows, and each row has a list of columns (or vice versa).

    But this remark is less relevant. I think Column is something like Column Definition and does not contain data, but in this case I see no reason to have ICollection<Row<T>> instead of just ICollection<T> .

  • Next, you will most likely want to save the delegate, for example Func<T, object> , instead of Expression<Func<T, object>> .

  • Property should at least have private setters (or, even better, read-only read fields). This is not something you would like to change other parts of your code.

  • Naming is very confusing IMHO. I would choose the best property names. If I understood correctly, Value and Property should be called HeaderName and GetValue respectively.

Having said all this, I would change Column to something like this:

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

And then just use this in your loop:

 sb.AppendLine(column.GetValueFrom(item).ToString()); 
+2
source

You need to compile the expression using expression.Compile() (you have a property expression in your .Property column). This will give you a delegate. You can pass the object there and get the value. You will also need to transfer the person or T to an auxiliary method.

+1
source

Source: https://habr.com/ru/post/1390445/


All Articles