5

My Html helper method looks like following

public static MvcHtmlString Control<TModel>(this MyHtmlHelper<TModel> helper, 
    string propertyName, LayoutHelper layout, TemplateType templateType = TemplateType.Screen)
{
    //...        
}

I want to convert my property name into following

 Expression<Func<TModel, string>> expression

Any help will be much appreciated

5
  • How are you intending to use the resulting expression? Commented May 1, 2017 at 12:42
  • var propertyMetadata = ModelMetadata.FromStringExpression(expression, helper.Html.ViewData); Commented May 1, 2017 at 12:44
  • I need it in the form of model.[propertyName] Commented May 1, 2017 at 12:45
  • @Nkosi Looking to get expression when property name is given Commented May 1, 2017 at 12:49
  • @InTheWorldOfCodingApplications msdn.microsoft.com/en-us/library/mt654263.aspx Commented May 1, 2017 at 12:51

3 Answers 3

5

It looks like you want to call ModelMetadata.FromLambdaExpression, not FromStringExpression. You can create an expression like

x => x.PropertyName

from scratch, like this:

// Get a reference to the property
var propertyInfo = ExpressionHelper.GetPropertyInfo<TModel>(propertyName);
var model = ExpressionHelper.Parameter<TModel>();

// Build the LINQ expression tree backwards:
// x.Prop
var key = ExpressionHelper.GetPropertyExpression(model, propertyInfo);
// x => x.Prop
var keySelector = ExpressionHelper.GetLambda(typeof(TModel), propertyInfo.PropertyType, model, key);

To make the code more readable, the nitty-gritty expression tree manipulation is moved into this helper class:

public static class ExpressionHelper
{
    private static readonly MethodInfo LambdaMethod = typeof(Expression)
        .GetMethods()
        .First(x => x.Name == "Lambda" && x.ContainsGenericParameters && x.GetParameters().Length == 2);

    private static MethodInfo GetLambdaFuncBuilder(Type source, Type dest)
    {
        var predicateType = typeof(Func<,>).MakeGenericType(source, dest);
        return LambdaMethod.MakeGenericMethod(predicateType);
    }

    public static PropertyInfo GetPropertyInfo<T>(string name)
        => typeof(T).GetProperties()
        .Single(p => p.Name == name);

    public static ParameterExpression Parameter<T>()
        => Expression.Parameter(typeof(T));

    public static MemberExpression GetPropertyExpression(ParameterExpression obj, PropertyInfo property)
        => Expression.Property(obj, property);

    public static LambdaExpression GetLambda<TSource, TDest>(ParameterExpression obj, Expression arg)
        => GetLambda(typeof(TSource), typeof(TDest), obj, arg);

    public static LambdaExpression GetLambda(Type source, Type dest, ParameterExpression obj, Expression arg)
    {
        var lambdaBuilder = GetLambdaFuncBuilder(source, dest);
        return (LambdaExpression)lambdaBuilder.Invoke(null, new object[] { arg, new[] { obj } });
    }
}

Building the expression tree from scratch gives you the most flexibility in creating the lambda expression. Depending on the target property type, it may not always be an Expression<Func<TModel, string>> - the last type could be an int or something else. This code will build the proper expression tree no matter the target property type.

Sign up to request clarification or add additional context in comments.

2 Comments

Can this be used in ModelMetaData.FromLambdaExpression?
@InTheWorldOfCodingApplications Yes, that should work. If it doesn't let me know.
5

Referring to the following for reference

Creating Expression Trees by Using the API

Expression<Func<TModel, string>> GetPropertyExpression<TModel>(string propertyName) {
    // Manually build the expression tree for 
    // the lambda expression model => model.PropertyName.
    var parameter = Expression.Parameter(typeof(TModel), "model");
    var property = Expression.Property(parameter, propertyName);
    var expression = Expression.Lambda<Func<TModel, string>>(property, parameter);
    return expression;
}

Which would allow you to derive the expression in the helper

public static MvcHtmlString Control<TModel>(this MyHtmlHelper<TModel> helper, string propertyName,
    LayoutHelper layout, TemplateType templateType = TemplateType.Screen) {
    Expression<Func<TModel, string>> expression = GetPropertyExpression<TModel>(propertyName);
    var propertyMetadata = ModelMetadata.FromStringExpression(expression, helper.Html.ViewData);
    //...other code...
}

2 Comments

What's model in var parameter = Expression.Parameter(typeof(TModel), "model");
It is the name of the parameter. Just like when you do x => x.propertyName, the parameter name is x
2

Expression is just a wrapper around the lambda that creates a tree-style data structure. Things like HTML helpers need this so they can introspect the lambda to determine things like the name of the property. The meat of the type is in the Func<TModel, string>, which indicates that it requires a lambda that takes a class instance of some type (generic) and returns a string (the property value). In other words:

m => m.Foo

Where m is parameter to the lambda, and would likely be executed by passing in your model. The m, here, is analogous to a typed param to a normal method, so it can be named anything any other variable could be named. The return value, then, is Model.Foo, where Foo is the property you're accessing.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.