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.