How to create expression <Func <TModel, string >> expression from property name
MyHtml helper method looks like this
public static MvcHtmlString Control<TModel>(this MyHtmlHelper<TModel> helper,
string propertyName, LayoutHelper layout, TemplateType templateType = TemplateType.Screen)
{
//...
}
I want to convert my property name to the following
Expression<Func<TModel, string>> expression
Any help would be much appreciated
source to share
It sounds like you want to call ModelMetadata.FromLambdaExpression
, not FromStringExpression
. You can create an expression like
x => x.PropertyName
from scratch, for example:
// 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, manipulation of the expression tree with nullity is moved to 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 an expression tree from scratch gives you the most flexibility when creating a lambda expression. Depending on the type of the target property, it may not always be Expression<Func<TModel, string>>
- the latter type may be int
or something else. This code will build the correct expression tree regardless of the target property type.
source to share
Referring to the following for reference
Creating Expression Trees 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 get 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...
}
source to share
Expression
is just a wrapper around a lambda that creates a tree-style data structure. Things like HTML helpers need this so they can examine the lambda to figure out things like the property name. The meat of this type is in Func<TModel, string>
, indicating that it requires a lambda that takes an instance of a class of some type (generic) and returns a string (property value). In other words:
m => m.Foo
Where m
is the parameter for the lambda, and most likely it will be executed by passing in your model. m
, here, is similar to a typed parameter for a normal method, so it can be called anything that can be called any other variable. Then the return value is Model.Foo
, where Foo
is the property you are accessing.
source to share