Get the Property, as a String, from an Expression<Func<Tmodel,Tproperty>>

Get the property, as a string, from an ExpressionFuncTModel,TProperty

Here's the trick: any expression of this form...

obj => obj.A.B.C // etc.

...is really just a bunch of nested MemberExpression objects.

First you've got:

MemberExpression: obj.A.B.C
Expression: obj.A.B // MemberExpression
Member: C

Evaluating Expression above as a MemberExpression gives you:

MemberExpression: obj.A.B
Expression: obj.A // MemberExpression
Member: B

Finally, above that (at the "top") you have:

MemberExpression: obj.A
Expression: obj // note: not a MemberExpression
Member: A

So it seems clear that the way to approach this problem is by checking the Expression property of a MemberExpression up until the point where it is no longer itself a MemberExpression.


UPDATE: It seems there is an added spin on your problem. It may be that you have some lambda that looks like a Func<T, int>...

p => p.Age

...but is actually a Func<T, object>; in this case, the compiler will convert the above expression to:

p => Convert(p.Age)

Adjusting for this issue actually isn't as tough as it might seem. Take a look at my updated code for one way to deal with it. Notice that by abstracting the code for getting a MemberExpression away into its own method (TryFindMemberExpression), this approach keeps the GetFullPropertyName method fairly clean and allows you to add additional checks in the future -- if, perhaps, you find yourself facing a new scenario which you hadn't originally accounted for -- without having to wade through too much code.


To illustrate: this code worked for me.

// code adjusted to prevent horizontal overflow
static string GetFullPropertyName<T, TProperty>
(Expression<Func<T, TProperty>> exp)
{
MemberExpression memberExp;
if (!TryFindMemberExpression(exp.Body, out memberExp))
return string.Empty;

var memberNames = new Stack<string>();
do
{
memberNames.Push(memberExp.Member.Name);
}
while (TryFindMemberExpression(memberExp.Expression, out memberExp));

return string.Join(".", memberNames.ToArray());
}

// code adjusted to prevent horizontal overflow
private static bool TryFindMemberExpression
(Expression exp, out MemberExpression memberExp)
{
memberExp = exp as MemberExpression;
if (memberExp != null)
{
// heyo! that was easy enough
return true;
}

// if the compiler created an automatic conversion,
// it'll look something like...
// obj => Convert(obj.Property) [e.g., int -> object]
// OR:
// obj => ConvertChecked(obj.Property) [e.g., int -> long]
// ...which are the cases checked in IsConversion
if (IsConversion(exp) && exp is UnaryExpression)
{
memberExp = ((UnaryExpression)exp).Operand as MemberExpression;
if (memberExp != null)
{
return true;
}
}

return false;
}

private static bool IsConversion(Expression exp)
{
return (
exp.NodeType == ExpressionType.Convert ||
exp.NodeType == ExpressionType.ConvertChecked
);
}

Usage:

Expression<Func<Person, string>> simpleExp = p => p.FirstName;
Expression<Func<Person, string>> complexExp = p => p.Address.State.Abbreviation;
Expression<Func<Person, object>> ageExp = p => p.Age;

Console.WriteLine(GetFullPropertyName(simpleExp));
Console.WriteLine(GetFullPropertyName(complexExp));
Console.WriteLine(GetFullPropertyName(ageExp));

Output:

FirstName
Address.State.Abbreviation
Age

How to create ExpressionFuncTModel, string expression from Property Name

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.

ExpressionFuncTModel,TProperty as Property for object initialization?

When you define something like Func<TModel, TProperty> it means there is a method that takes a TModel type and returns a TProperty type. So lambda expressions like

p => p.ProductId 

returns the value of the property rather than the property itself.

What you can do to make this syntax work is to define your ColumnProperty as follows:

public Expression<Func<TModel, object>> ColumnProperty { get; set; }

Then you can extract detailed information from the expression tree.

How to create ExpressionFuncTModel, TProperty without TProperty type explicit

You can in fact do this, but the type becomes unknown. The object returned from this method will be of the proper type Expression<Func<Test, string>> but cannot be strong typed at compile time:

    static LambdaExpression CreateExpression<TModel>(string propertyName)
{
var t = typeof(TModel);
var param = Expression.Parameter(typeof(TModel), "x");
//get the type for the 2nd generic arg
var propType = t.GetProperty(propertyName).PropertyType;
//make the generic type Func<TModel, TProp>
Type genericFuncType = typeof(Func<,>).MakeGenericType(new Type[] { typeof(TModel), propType });
//get the Expression.Lambda method
MethodInfo mi = typeof(Expression).GetMethods().First(a => a.Name == "Lambda" && a.GetParameters().Length == 2);
//get the Expression.Lambda<Func<TModel, TProp>> method
MethodInfo mi2 = mi.MakeGenericMethod(new Type[] { genericFuncType });
//Call Expression.Lambda<Func<TModel, TProp>>
return (LambdaExpression)mi2.Invoke(null, new object[] { Expression.PropertyOrField(param, propertyName), new ParameterExpression[] { param }});
}

However note that the return type is now somewhat unknown and will need cast to use (or use dynamic).

So now you need even more code to cast it. Perhaps this would be useful in some sort of factory or such - not sure your use case.

class Program
{
public static void Main(string[] args)
{
var theExpression = CreateExpression<Test>("Name");
var theExpressionStrongType = theExpression as Expression<Func<Test, string>>;
//now you could use theExpressionStrongType

//or do this and go wild. :)
dynamic d = theExpression;

Console.ReadKey();
}
}
class Test
{
public string Name { get; set; }
}

Disclaimer: if you seriously want to use this in a production environment, I would clean up the code a LOT to just get refection types once, etc, etc...

PropertyInfo to ExpressionFuncTModel, TProperty

Have you tried...

private MvcHtmlString GetCellHtml(HtmlHelper<TData> helper, TableColumn column, TData dataRow){

TagBuilder cellTagBuilder = new TagBuilder("td");
cellTagBuilder.InnerHtml = helper.Display(column.PropertyInfo.Name, dataRow);
...
}

...?

If you need DisplayFor only for your method GetCellHtml then you don't really need to build an expression from a PropertyInfo.

Retrieving Property name from lambda expression

I found another way you can do it was to have the source and property strongly typed and explicitly infer the input for the lambda. Not sure if that is correct terminology but here is the result.

public static RouteValueDictionary GetInfo<T,P>(this HtmlHelper html, Expression<Func<T, P>> action) where T : class
{
var expression = (MemberExpression)action.Body;
string name = expression.Member.Name;

return GetInfo(html, name);
}

And then call it like so.

GetInfo((User u) => u.UserId);

and voila it works.

How to create ExpressionFuncTModel, TProperty;

Sure:

static Expression<Func<TModel,TProperty>> CreateExpression<TModel,TProperty>(
string propertyName)
{
var param = Expression.Parameter(typeof(TModel), "x");
return Expression.Lambda<Func<TModel, TProperty>>(
Expression.PropertyOrField(param, propertyName), param);
}

then:

var lambda = CreateExpression<SomeModel, bool>("IsAlive");

Passing model property expression to view

It's as simple as using a LambdaExpression. No need to go the Expression<Func<TModel, TProperty>> way.

public static class HtmlExtensions
{
public static string IdFor(
this HtmlHelper htmlHelper,
LambdaExpression expression
)
{
var id = ExpressionHelper.GetExpressionText(expression);
return htmlHelper.ViewData.TemplateInfo.GetFullHtmlFieldId(id);
}

public static MvcHtmlString FocusScript(
this HtmlHelper htmlHelper
)
{
if (htmlHelper.ViewData["FieldToFocus"] != null)
{
return MvcHtmlString.Create(
@"<script type=""text/javascript"">
$(document).ready(function() {
setTimeout(function() {
$('#" + htmlHelper.IdFor((LambdaExpression)htmlHelper.ViewData["FieldToFocus"]) + @"').focus();
}, 500);
});
</script>");
}
else
{
return MvcHtmlString.Empty;
}
}
}

then in your controller:

public ActionResult Index()
{
FocusOnField((MyModel m) => m.IntProperty);
return View(new MyModel());
}

and in your view:

@model MyModel

@Html.FocusScript()

This being said I am leaving without comment the fact that a controller action is setting a focus.



Related Topics



Leave a reply



Submit