Reputation: 2616
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
Upvotes: 5
Views: 1769
Reputation: 247323
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...
}
Upvotes: 5
Reputation: 53640
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.
Upvotes: 5
Reputation: 239380
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.
Upvotes: 2