Iterate over lambda expression properties

I am trying to generalize a complex control that is used on my site quite often, but with different fields. The functionality in the control is always the same, it is only the base fields that change.

To achieve a way to display different fields, I am trying to create an HTMLHelper extension that accepts Expression<Func<TModel,TProperty>> as a parameter that will contain the class properties needed to be displayed in the control. For example:

View:

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

This is an extension I am having problems with - how can I TextBoxFor() over the provided parameters in lambda to provide each TextBoxFor() or even manually create an input element and fill it with the value and name parameter of the lambda?

Extension in psuedo:

 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.???) } } 

I feel like looking at it for too long, and I also feel that there is an easier way, I donโ€™t notice it.

Any help is appreciated. If you need more information, or I missed something important, let me know.

+8
c # lambda expression-trees
source share
4 answers

If you accept the following:

  • The result of the input expression is the projection (returns a new object, anonymously or otherwise)
  • Projection elements are all MemberExpressions and do not contain a method call on the model or its children

Then you can achieve what you want using the following approach:

Edit:

Realizing that my first example cannot handle objects with complex properties, I updated the code to use a helper method to access property values. This method moves through the property chain using recursion to return the corresponding values.

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

Note. I did not directly implement this as an MVC extension method in my IDE, so a little syntax change may be required.

+2
source share

The expression that you created will be relatively complex - it will need to get all the properties, and then call the constructor of an anonymous type. A โ€œdisassemblyโ€ that can become painful ... although if you still want to try, I would suggest just leaving the method implementation empty and looking in the debugger to see what the expression looks like.

If you gladly agree to a slightly ugly form of code invocation, it would be much easier to implement this:

 @Html.MyHelper(p => p.Author.Name, p => p.Author.Location, p => p.Author.Age); 

You can do this with params Expression<TModel, object> , or you can declare multiple overloads with a different number of parameters, for example.

 // 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) 

and etc.

+2
source share

Perhaps a builder-style API can simplify:

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

Please note that this allows you to add optional parameters if necessary.

The code looks something like this:

 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
source share

Consider this:

Create App_Code Folder

Place the helper razor file Templates.cshtml.

It looks like this:

  @helper Print(string myvalue,string special="") { <pre> <input id="id" type="text" value ="@myvalue" data-val="@special"/> </pre> } 

This way you do not need to write HTML files in C #. It is very comfortable.

Authors.cshtml is as follows:

 @model IEnumerable<MvcApplication1.Models.Author> @{ ViewBag.Title = "Authors"; } <h2>Authors</h2> @foreach(var auth in Model) { @Templates.Print(auth.Name); } 

books.cshtml is as follows:

 @model IEnumerable<MvcApplication1.Models.Book> @{ ViewBag.Title = "Books"; } <h2>Books</h2> @foreach(var book in Model) { @Templates.Print(book.Title,book.ISBN); } 

Connect all the special properties that you need for each class of the model. If it gets too complicated, take a look at the dynamic and expando object. It depends on how complex your view models / models are.

+1
source share

All Articles