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
Razor Views Not Seeing System.Web.Mvc.Htmlhelper
.Net Enumeration Allows Comma in the Last Field
Openxml Sdk Returning a Number for Cellvalue Instead of Cells Text
Pass Expression Parameter as Argument to Another Expression
Is SQLcommand.Dispose() Required If Associated SQLconnection Will Be Disposed
C# - Wcf - Inter-Process Communication
JSON.Net Cast Error When Serializing Mongo Objectid
C# an Established Connection Was Aborted by the Software in Your Host MAChine
Running Msbuild Programmatically
Save Settings in a .Net Winforms Application
Why the CPU Performance Counter Kept Reporting 0% CPU Usage
In Wpf Can You Filter a Collectionviewsource Without Code Behind
How to Detect a Process Start & End Using C# in Windows