Building an indexed expression from another expression

asp.net-mvc c# expression-trees lambda

Question

Scenario

public class Element {
   public int Id {get;set;}
}

public class ViewModel {
   public IList<Element> Elements{get;set;}
}

I have a method with a parameter of type Expression<Func<Element, int>>, which looks like m => m.Id

I'd like to transform

m => m.Id (where m is an Element)

to

x => x.Elements[0].Id where x is a ViewModel, and 0 is an "index" parameter

What I have now (It's of course generic, I removed the generic part for clarity)

public static class Helpers {
    public static Expression<Func<ViewModel, int>> BuildExpressionArrayFromExpression(
                this Expression<Func<Element, int>> expression,
                ViewModel model,
                int index = 0, 
                string bindingPropertyName = "Elements"//the name of the "List" property in ViewModel class
                ) 
    {
       var parameter = Expression.Parameter(typeof(ViewModel), "x");
       var viewModelProperty = model.GetType().GetProperty(bindingPropertyName);
       Expression member = parameter;//x => x
       member = Expression.Property(member, viewModelProperty);//x => x.Elements

       var test1 =  Expression.Property(member, "Item", new Expression[]{Expression.Constant(index)});
       //x => x.Elements.Item[0], and I don't want Item

       var test2 = Expression.Call(member, viewModelProperty.PropertyType.GetMethod("get_Item"), new Expression[] {Expression.Constant(index)});
       //x 0> x.Elements.get_Item(0), and I don't want get_Item(0)

       //code to add Id property to expression, not problematic
       return Expression.Lambda<Func<ViewModel, int>(member, parameter);
    }
}

EDIT

I need x => x.Elements[0] and not x => x.Elements.Item[0], because the resulting expression must be called with an InputExtensions.TextBoxFor(<myIndexedExpression>)

Imagine a class like that

public class Test {
  public int Id {get;set;}
  public IList<Element> Elements {get;set;}
}

and a Post Action

[HttpPost]
public ActionResult Edit(Test model) {
 bla bla bla.
}

If the name attributes of my inputs are not well generated, I then have binding problems (model.Elements is empty in my Post Action).

Name attribute of my input should be

Elements[0]PropertyName

and I get (depending on my tries)

PropertyName

or (maybe not exact, I try to reproduce this case)

Elements.Item[0].PropertyName

EDIT2

Also tried a different solution, working with ViewData.TemplateInfo.HtmlFieldPrefix, but I then get

Elements.[0].PropertyName

(and Elements_0_PropertyName as Id).

The first dot is unwanted in name, and the first "double underscore" should be a simple one in id.

I actually use this solution, working with regex (argh) to remove the unwanteds . and _ , but I'd like to avoid this.

Accepted Answer

This is just a matter of the string representation of the expression tree, and you don't get to change that. The expression tree you're building is fine. You can see the same effect if you use a lambda expression to build the expression tree:

using System;
using System.Collections.Generic;
using System.Linq.Expressions;

class Test
{
    public static void Main()
    {
        Expression<Func<List<string>, string>> expression = list => list[0];
        Console.WriteLine(expression);
    }
}

Output:

list => list.get_Item(0)

I'd be very surprised if the result of calling ToString() on the expression tree was really the problem you're facing. Instead of telling us the result you think you need and the vague "I need it for MVC binding reasons" justification, you should explain what's actually going wrong. I strongly suspect the problem isn't where you think it is.


Popular Answer

Expression<Func<Element, int>> expr1 =
    m => m.Id;
Expression<Func<ViewModel, Element>> expr2 =
    x => x.Elements[0];

Expression<Func<ViewModel, int>> result =
    expr1.ComposeWith(expr2);

Result:

expr1 = m => m.Id
expr2 = x => x.Elements.get_Item(0)
result = x => x.Elements.get_Item(0).Id

It replaces the parameter of expr1 (m) with the body of expr2 (x.Elements[0]), and replaces the input parameter with that from expr2 (x).

Extension method ComposeWith:

public static class FunctionalExtensions
{
    public static Expression<Func<TInput,TResult>> ComposeWith<TInput,TParam,TResult>(
        this Expression<Func<TParam,TResult>> left, Expression<Func<TInput,TParam>> right)
    {
        var param = left.Parameters.Single();

        var visitor = new ParameterReplacementVisitor(p => {
            if (p == param)
            {
                return right.Body;
            }
            return null;
        });

        return Expression.Lambda<Func<TInput,TResult>>(
            visitor.Visit(left.Body),
            right.Parameters.Single());
    }

    private class ParameterReplacementVisitor : ExpressionVisitor
    {
        private Func<ParameterExpression, Expression> _replacer;

        public ParameterReplacementVisitor(Func<ParameterExpression, Expression> replacer)
        {
            _replacer = replacer;
        }

        protected override Expression VisitParameter(ParameterExpression node)
        {
            var replaced = _replacer(node);
            return replaced ?? node;
        }
    }
}


Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why