2

EDIT: Simplified Question the real-world version can be found below but perhaps it's not necessary to answer this question.

I have a MethodCallExpression which I would like to repurpose. The method referenced by the expression call's body is the correct method, however, one of the type arguments will have changed. Given an existing MethodCallExpression, I want to construct a new MethodCallExpression with the same body expression but a different type argument. Is this possible? How would one accomplish this? Thanks.

REAL WORLD QUESTION FOLLOWS (ignore this unless you need more concrete examples):

I have an expression Expression<Func<HtmlHelper<TModel>, Expression<Func<TModel, TValue>>, MvcHtmlString>> which is used as a display template (taking an HtmlHelper and Expression> as input and outputting an McvHtmlString). If TValue is a custom class instance, then it's properties must be fed into the expression delegate rather than the object itself. This is problematic because it means that TValue will have changed from the parent object to the type of the current property. (Code follows)

I'm not exactly sure how I should go about solving this problem. I understand that an ExpressionVisitor can be used to achieve this sort of change of an expression but I'm also considering the fact that my expression might be too verbose. Please help.

What I'm attempting to create is HtmlHelper extension methods (ASP.NET MVC) for rendering label/value field pairs. However, I have the concept of bound and unbound fields. A bound field is simply rendered with some knockout attributes. This is working well until I attempted to refactor some reusable components using the C# Expression API.

To begin, here are the two public extension methods used to render fieldsets as either bound or unbound elements.

public static class HtmlHelperExtensions
{
    public static MvcHtmlString FieldsetDisplayFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression)
    {
        return RenderFieldsetDisplayFor(htmlHelper, expression, (helper, ex) => helper.DisplayFor(ex));
    }

    public static MvcHtmlString BoundFieldsetDisplayFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TValue>> expression)
    {
        return RenderFieldsetDisplayFor(htmlHelper, expression, (helper, ex) => helper.BoundDisplayFor(ex));
    }
    // ...
}

Notice the two different expressions passed into the RenderFieldsetDisplayFor method. This is essentially a delegate that the lower-level method will use to render the value as seen here:

    internal static MvcHtmlString RenderFieldsetDisplayFor<TModel, TValue>(this HtmlHelper<TModel> htmlHelper,
                                                                    Expression<Func<TModel, TValue>> expression,
                                                                    Expression<Func<HtmlHelper<TModel>, Expression<Func<TModel, TValue>>, MvcHtmlString>> displayDelegate)
    {
        if (expression.ReturnType.IsContainerObject())
        {
            return DisplayChildFieldsetsFor(expression, htmlHelper, displayDelegate);
        }

        var labelBuilder = new TagBuilder("div");
        labelBuilder.AddCssClass("display-label");
        labelBuilder.InnerHtml = htmlHelper.LabelFor(expression).ToHtmlString();

        var inputBuilder = new TagBuilder("div");
        inputBuilder.AddCssClass("display-field");

        // expression delegate called here for the rendering display of the value
        inputBuilder.InnerHtml = displayDelegate.Compile().Invoke(htmlHelper, expression).ToHtmlString();

        return new MvcHtmlString(String.Format("{0}{1}", labelBuilder, inputBuilder));
    }

This method works fine until expression.ReturnType.IsContainerObject() == true. The DisplayChildFieldsetsFor method will get all the appropriate properties from the expression's ReturnType and call the RenderFieldsetDisplayFor method recursively. What I want to do is feed the properties from the parent type into the displayDelegate expression. The problem is that the type of TValue will change for each of the properties as it was originally constructed based on the parent type.

So, Expression gurus, what do you recommend? Do I need to create a visitor to change the lambda argument? Or, is the expression parameter the problem. I'm very new to the Expression API and I don't think I understand generics enough to know where I'm going wrong.

Vinney Kelly
  • 4,975
  • 1
  • 25
  • 31
  • You can try to [instantiate generic method with type you need](http://stackoverflow.com/questions/232535/how-to-use-reflection-to-call-generic-method) and than call that special instance of `DisplayChildFieldsetsFor` - no need to dig through expression, but some run-time reflection (not sure if you need to cache the resulting method) – Alexei Levenkov Jan 16 '13 at 17:33
  • I'm not sure we're on the same page. The purpose of `DisplayChildFieldsetsFor` is to get the appropriate properties from the type and pass each into the `RenderFieldsetDisplayFor` method. I've considered passing the MethodInfo object into `DisplayChildFiledsetsFor` but I don't know if that's the right move. Essentially, I have the method I want to call in the lambda expression but it can't be used as is. I'm thinking I may need to build a new expression using the displayDelegate.Body and the appropriate argument type. – Vinney Kelly Jan 16 '13 at 17:44
  • I thought that your problem is calling `DisplayChildFieldsetsFor` when you wanted `DisplayChildFieldsetsFor`, if not - ignore comment. – Alexei Levenkov Jan 16 '13 at 18:01
  • Yeah, I thought I might have confused you as such. `DisplayChildFieldsetsFor` is where the expression delegate is originated. I need to take the lambda form that expression and build a new expression with the same expression body but the argument proper for the current child property. Does this make sense? Thanks, @AlexeiLevenkov. – Vinney Kelly Jan 16 '13 at 18:09
  • Yes (I don't know how to do that). I may be better to provide very simplified sample (in addition to your real problem) in the beginning of the question and clearly indicate "ignore text below unless you really want to know why I need it" and add inline comment to code where you want the type of expression to change. – Alexei Levenkov Jan 16 '13 at 18:18
  • Good suggestion. I know it's a lot of info. Thanks again for your input. – Vinney Kelly Jan 16 '13 at 18:21

0 Answers0