Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Iterating over properties of a lambda expression

I am attempting to genericise a complex control which is used in my website quite often but with different fields. The functionality in the control is always the same, it's just the underlying fields which change.

To achieve the method of showing different fields I am attempting to create a HTMLHelper extension which accepts a Expression<Func<TModel,TProperty>> as a parameter, which would contain the properties of a class required for display in the control. For example:

The view:

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

It's the extension I'm having problems with - how can I iterate over the provided params in the lambda to provide each with a TextBoxFor(), or even manually create a input element and populate it with the value and name of the lambda parameter?

The extention 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 I've been staring at this for far too long, and I'm also feeling there's a more simple way I'm overlooking of achieving this.

Any help is greatly appreciated. If you need further details, or I've missed something important, let me know.

like image 323
Rory McCrossan Avatar asked Sep 10 '12 19:09

Rory McCrossan


1 Answers

If you assume the following:

  1. The result of the input expression is a projection (returns a new object, anonymous or otherwise)
  2. The elements of the projection are all MemberExpressions, and do not contain a call to a method on the model or on its children

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

Edit:

After realizing that my first example could not handle objects with complex properties, I updated the code to use a helper method to access property values. This method traverses over the property chain using recursion to return the appropriate 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 implement this directly as a MVC extension method in my IDE, so a slight variation of the syntax may be required.

like image 92
mclark1129 Avatar answered Sep 20 '22 04:09

mclark1129