C# expression - extract the string name (Deep) - c#

I have the function bellow, returning "Test" when I call it GetPropertyName<Model>(x => x.Test).
How can I return deep call eg: User.UserName.
I want to see User.UserName when I call it GetPropertyName<Model>(x => x.User.FullName)
Thanks
public static string GetPropertyName<T>(Expression<Func<T, object>> expression)
{
var body = expression.Body as MemberExpression;
if (body == null)
{
body = ((UnaryExpression)expression.Body).Operand as MemberExpression;
}
if (body != null)
{
return body.Member.Name;
}
return null;
}
Edit 1
this is my classes:
public class Place : BaseEntity
{
public virtual User UserManager { get; set; }
}
public class User : BaseEntity
{
public virtual string FullName { get; set; }
}

This method loops through the nodes in the expression, casts the node to a property and adds it to a list.
private IList<PropertyInfo> GetProperties<T>(Expression<Func<T, object>> fullExpression)
{
Expression expression;
switch (fullExpression.Body.NodeType)
{
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
var ue = fullExpression.Body as UnaryExpression;
expression = ((ue != null) ? ue.Operand : null) as MemberExpression;
break;
default:
expression = fullExpression.Body as MemberExpression;
break;
}
var props = new List<PropertyInfo>();
while(expression != null && expression.NodeType == ExpressionType.MemberAccess)
{
var memberExp = expression as MemberExpression;
if(memberExp.Member is FieldInfo)
throw InvalidExpressionException.FieldFound;
var prop = memberExp.Member as PropertyInfo;
props.Insert(0, prop);
expression = memberExp.Expression;
}
return props;
}
If you then want to get a string like this: User.UserName, you can do it this way:
var props = GetProperties<Model>(m => m.User.Username);
var propNames = props.Select(p => p.Name);
string names = string.Join(".", propNames);

Related

How to access property from lambda

I'd want to refactor this code to get rid of relying on TModel, TValue
public static string DescriptionFor<TModel, TValue>(this IHtmlHelper<TModel> html, Expression<Func<TModel, TValue>> expression)
{
// null checks
DescriptionAttribute descriptionAttribute = null;
if (expression.Body is MemberExpression memberExpression)
{
descriptionAttribute = memberExpression.Member
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.SingleOrDefault();
}
return descriptionAttribute?.Description ?? string.Empty;
}
which has invocation like that:
#Html.DescriptionFor(x => x.MyModel.MyOtherModel.Property)
to something like that
public static string DescriptionFor2<T>(Expression<Func<T>> expression)
{
if (expression == null)
throw new ArgumentNullException(nameof(expression));
if (!(expression.Body is MemberExpression memberExpression))
{
return string.Empty;
}
foreach (var property in typeof(T).GetProperties())
{
if (property == ((expression.Body as MemberExpression).Member as PropertyInfo))
{
var attr = property
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.FirstOrDefault();
return attr?.Description ?? string.Empty;
}
}
return string.Empty;
}
with invocation like that:
#MyHtml.DescriptionFor2<MyOtherModel>(x => x.MyProperty);
But here's error:
Delegate 'Func' does not take 1 arguments
You can do this:
void Main()
{
Console.WriteLine(MyHtml.DescriptionFor2<Test>((t) => t.StringMember));
Console.WriteLine(MyHtml.DescriptionFor2<Test>((t) => t.BooleanMember));
Console.WriteLine(MyHtml.DescriptionFor2<Test>((t) => t.IntegerMember));
}
public class Test
{
[Description("This is a string member")]
public string StringMember { get; set; }
[Description("This is a boolean member")]
public bool BooleanMember { get; set; }
[Description("This is a integer member")]
public int IntegerMember { get; set; }
}
public static class MyHtml
{
public static string DescriptionFor2<T>(Expression<Func<T, dynamic>> expression)
{
var result = string.Empty;
var member = GetMemberExpression(expression)?.Member?.Name;
if (member != null)
{
var property = typeof(T).GetProperty(member);
if (property != null)
{
var attr = property
.GetCustomAttributes(typeof(DescriptionAttribute), false)
.Cast<DescriptionAttribute>()
.FirstOrDefault();
result = attr?.Description ?? string.Empty;
}
}
return result;
}
private static MemberExpression GetMemberExpression<T>(Expression<Func<T, dynamic>> expression)
{
var member = expression.Body as MemberExpression;
var unary = expression.Body as UnaryExpression;
return member ?? (unary != null ? unary.Operand as MemberExpression : null);
}
}
public class DescriptionAttribute : Attribute
{
public string Description { get; set; }
public DescriptionAttribute(string description)
{
Description = description;
}
}
It involves getting the body of the expression as a member and using its name to resolve the property on the object. The dynamic data type is used to get rid of the second type parameter, however you can also define it explicitly for better compile-time error catching:
public static string DescriptionFor2<T, U>(Expression<Func<T, U>> expression)
and calling it:
MyHtml.DescriptionFor2<Test, string>((t) => t.StringMember);
MyHtml.DescriptionFor2<Test, bool>((t) => t.BooleanMember);
MyHtml.DescriptionFor2<Test, int>((t) => t.IntegerMember);
EDIT: edited answer because initial solution did not work for value types, see Expression for Type members results in different Expressions (MemberExpression, UnaryExpression)

EntityFramework LINQ Order using string and nested reflection

I saw already some of the similar questions, but cannot find an anwser how to solve this problem.
I want to have a possibility to order collection using string as property name.
Model:
public sealed class User : IdentityUser
{
#region Constructors
#endregion
#region Properties
[Required]
[Display(Name = "First name")]
public string FirstName { get; set; }
[Required]
[Display(Name = "Last name")]
public string LastName { get; set; }
[ForeignKey("Superior")]
public string SuperiorId { get; set; }
public User Superior{ get; set; }
[NotMapped]
public string FullName => this.LastName + " " + this.FirstName;
#endregion
}
And I want to make a call like this:
DbSet.Order("SuperiorUser.FullName", Asc/Desc)...
My below functions work for a Order("FullName", Asc/Desc), but dont when I want to go deeper.
public static IOrderedQueryable<T> Order<T>(this IQueryable<T> source, string propertyName,
ListSortDirection direction = ListSortDirection.Ascending)
{
return ListSortDirection.Ascending == direction
? source.OrderBy(ToLambda<T>(propertyName))
: source.OrderByDescending(ToLambda<T>(propertyName));
}
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
var parameter = Expression.Parameter(typeof(T));
var property = Expression.Property(parameter, propertyName);
return Expression.Lambda<Func<T, object>>(property, parameter);
}
So I made it look a little bit like this
private static Expression<Func<T, object>> ToLambda<T>(string propertyName)
{
var propertyNames = propertyName.Split('.');
var type = typeof(T)
ParameterExpression parameter;
MemberExpression property;
for (var propName in propertyNames)
{
parameter = Expression.Parameter(type);
property = Expression.Property(parameter, propName);
type = property.Type;
}
return Expression.Lambda<Func<T, object>>(property, parameter);
}
But this unfortunately returns error The parameter '' was not bound in the specified LINQ to Entities query expression. What am I missing?
You need to nest the Property access methods in the loop and then apply the parameter to the lambda.
private static Expression<Func<T, object>> ToLambda<T>(string propertyName) {
var propertyNames = propertyName.Split('.');
var parameter = Expression.Parameter(typeof(T));
Expression body = parameter;
foreach (var propName in propertyNames)
body = Expression.Property(body, propName);
return Expression.Lambda<Func<T, object>>(body, parameter);
}
I once wrote a extention method for IQueryables to sort by a property defined by string:
public static IOrderedQueryable<T> SortBy<T>(this IQueryable<T> source, string propertyName)
{
if (source == null)
{
throw new ArgumentNullException("source");
}
else
{
if (propertyName.EndsWith(" ASC", StringComparison.OrdinalIgnoreCase))
propertyName = propertyName.Replace(" ASC", "");
// DataSource control passes the sort parameter with a direction
// if the direction is descending
int descIndex = propertyName.IndexOf(" DESC", StringComparison.OrdinalIgnoreCase);
if (descIndex >= 0)
{
propertyName = propertyName.Substring(0, descIndex).Trim();
}
ParameterExpression parameter = Expression.Parameter(source.ElementType, String.Empty);
MemberExpression property = Expression.Property(parameter, propertyName);
LambdaExpression lambda = Expression.Lambda(property, parameter);
string methodName = (descIndex < 0) ? "OrderBy" : "OrderByDescending";
Expression methodCallExpression = Expression.Call(typeof(Queryable), methodName,
new Type[] { source.ElementType, property.Type },
source.Expression, Expression.Quote(lambda));
return source.Provider.CreateQuery<T>(methodCallExpression) as IOrderedQueryable<T>;
}
}
You may call then LIST.AsQueryable().SortBy("Surname").

Access properties by passing in a lambda

I am trying to write this method:
public static T Nullify<T>(T item, params Func<T, object> [] properties)
{
// Sets any specified properties to null, returns the object.
}
I will call it like this:
var kitten = new Kitten() { Name = "Mr Fluffykins", FurColour = "Brown" };
var anonymousKitten = Nullify(kitten, c => c.Name);
However I am unsure of how to do this. Any ideas?
Another approach is to do this (it doesn't need to be an extension method)
public static T Nullify<T>(this T item, params Expression<Func<T, object>> [] properties)
{
foreach(var property in properties)
{
var memberSelectorExpression = property.Body as MemberExpression;
if (memberSelectorExpression != null)
{
var propertyInfo = memberSelectorExpression.Member as PropertyInfo;
if (propertyInfo != null)
{
propertyInfo.SetValue(item, null, null);
}
}
}
return item;
}
Usage
item.Nullify(i => i.PropertyName, i => i.PropertyName2)
You'd need to pass a "setter method" not a "reader method" in properties.
static void Nullify<T, D>(T item, params Action<T, D>[] properties)
where D : class
{
foreach (var property in properties)
{
property(item, null);
}
}
usage:
Nullify<Kitten, string>(kitten, (c, d) => { c.Name = d; });
But this will just set the data for you. If you want a copy and then apply the properties, the items would probably have to be clonable (alternatively you can go though some hell with reflection):
static T Nullify<T, D>(T item, params Action<T, D>[] properties)
where D : class
where T : ICloneable
{
T copy = (T)item.Clone();
foreach (var property in properties)
{
property(copy, null);
}
return copy;
}
class Kitten : ICloneable
{
public string Name { get; set; }
public string FurColour { get; set; }
public object Clone()
{
return new Kitten() { Name = this.Name, FurColour = this.FurColour };
}
}
usage
var anonymousKitten = Nullify(kitten, (c, d) => { c.Name = d; });
Without modifying your method definition much:
namespace ConsoleApplication
{
public class Kitten : ISimpleClone<Kitten>
{
public string Name { get; set; }
public string FurColour { get; set; }
public int? Number { get; set; }
public Kitten SimpleClone()
{
return new Kitten { Name = this.Name, FurColour = this.FurColour, Number = this.Number };
}
}
public interface ISimpleClone<T>
{
T SimpleClone();
}
public class Program
{
public static PropertyInfo GetProperty<TObject, TProperty>(Expression<Func<TObject, TProperty>> propertyExpression)
{
MemberExpression body = propertyExpression.Body as MemberExpression;
if (body == null)
{
var unaryExp = propertyExpression.Body as UnaryExpression;
if (unaryExp != null)
{
body = ((UnaryExpression)unaryExp).Operand as MemberExpression;
}
}
return body.Member as PropertyInfo;
}
public static T Nullify<T>(T item, params Expression<Func<T, object>>[] properties)
where T : ISimpleClone<T>
{
// Creates a new instance
var newInstance = item.SimpleClone();
// Gets the properties that will be null
var propToNull = properties.Select(z => GetProperty<T, object>(z));
var filteredProp = propToNull
.Where(z => !z.PropertyType.IsValueType || Nullable.GetUnderlyingType(z.PropertyType) != null) // Can be null
.Where(z => z.GetSetMethod(false) != null && z.CanWrite); // Can be set
foreach (var prop in filteredProp)
{
prop.SetValue(newInstance, null);
}
return newInstance;
}
public static void Main(string[] args)
{
var kitten = new Kitten() { Name = "Mr Fluffykins", FurColour = "Brown", Number = 12 };
var anonymousKitten = Nullify(kitten, c => c.Name, c => c.Number);
Console.Read();
}
}
}
Looks a bit hacky though....

Transform a c# expression

I am trying to convert a c# expression. Here is an example result I am looking for. Convert:
Expression<Func<TestObj, bool>> filter = p => p.LastName == "Smith";
to this:
Expression<Func<DynamicItem, bool>> converted = p => p.FieldID == 5 && p.FieldValue == "Smith"
FieldID is actually taken via reflection of an attribute on the class as you can see in the code below. The error I'm getting right now is the return from Convert where the error is "Expression of type 'System.Int32' cannot be used for return type 'System.Boolean'"
I have hit a wall and thought maybe someone can see what I'm doing wrong here. Here is my code:
public class MongoReplaceAttribute : Attribute
{
public int FieldID { get; set; }
}
public class TestObj
{
[MongoReplaceAttribute(FieldID = 1)]
public string FirstName { get; set; }
[MongoReplaceAttribute(FieldID = 2)]
public string LastName { get; set; }
}
public class DynamicItem
{
public int FieldID { get; set; }
public string FieldValue { get; set; }
}
class Program
{
static void Main(string[] args)
{
string v = "Smith";
Expression<Func<TestObj, bool>> test = p => p.LastName == v;
var list = MagicFunction(test);
Console.ReadLine();
}
static IEnumerable<DynamicItem> MagicFunction(Expression<Func<TestObj, bool>> filter)
{
List<DynamicItem> rands = new List<DynamicItem>()
{
new DynamicItem
{
FieldID = 1,
FieldValue = "Bob"
},
new DynamicItem
{
FieldID = 2,
FieldValue = "Smith"
},
new DynamicItem
{
FieldID = 1,
FieldValue = "Alice"
},
new DynamicItem
{
FieldID = 2,
FieldValue = "Smith"
}
};
var f2 = Converter<DynamicItem>.Convert(filter);
return rands.AsQueryable<DynamicItem>().Where(f2);
}
}
class Converter<TTo>
{
class ConversionVisitor : ExpressionVisitor
{
private readonly ParameterExpression newParameter;
private readonly ParameterExpression oldParameter;
public ConversionVisitor(ParameterExpression newParameter, ParameterExpression oldParameter)
{
this.newParameter = newParameter;
this.oldParameter = oldParameter;
}
protected override Expression VisitLambda<T>(Expression<T> node)
{
return base.VisitLambda<T>(node);
}
protected override Expression VisitBinary(BinaryExpression node)
{
Expression left = this.Visit(node.Left);
Expression right = this.Visit(node.Right);
Expression conversion = this.Visit(node.Conversion);
if (left != node.Left)
{
return base.Visit(left);
}
else if (right != node.Right)
{
var r = base.Visit(right);
return r;
}
else
{
return node;
}
}
protected override Expression VisitParameter(ParameterExpression node)
{
return newParameter;
}
protected override Expression VisitMember(MemberExpression node)
{
if (node.Expression != oldParameter)
{
return base.VisitMember(node);
}
var s = node.Member;
var attrReader = s.GetCustomAttributes(typeof(MongoReplaceAttribute), true).Cast<MongoReplaceAttribute>();
//If we are a mongo attribute
if (attrReader != null && attrReader.Count() > 0)
{
var id = attrReader.First().FieldID;
Expression<Func<DynamicItem, bool>> ret = p => p.FieldID == id && p.FieldValue == (string)GetValue(node);
ParameterExpression param = Expression.Parameter(typeof(DynamicItem), "p");
var lambda = Expression.Lambda<Func<DynamicItem, bool>>(ret.Body, param);
return base.Visit(lambda.Body);
}
return base.VisitMember(node);
}
private object GetValue(MemberExpression member)
{
var objectMember = Expression.Convert(member, typeof(object));
var getterLambda = Expression.Lambda<Func<object>>(objectMember);
var getter = getterLambda.Compile();
return getter();
}
}
public static Expression<Func<TTo, TR>> Convert<TFrom, TR>(
Expression<Func<TFrom, TR>> e)
{
var oldParameter = e.Parameters[0];
var newParameter = Expression.Parameter(typeof(TTo), oldParameter.Name);
var converter = new ConversionVisitor(newParameter, oldParameter);
var newBody = converter.Visit(e.Body);
return Expression.Lambda<Func<TTo, TR>>(newBody, newParameter);
}
}
The newBody in the code below, doesn't seem to be correctly created. e.Body is p.LastName == "Smith"
but newBody is p.FieldID instead of expected p.FieldId==5. Notice the missing equality comparison.
Hence, in the last line, when you try to create a Lambda of Func<DynamicItem, bool>, from a body that returns int (p.FieldId), it throws an error.
This is most probably due to some bug in ConversionVisitor. Debug your ConversionVisitor to see why it creates incorrect body.
public static Expression<Func<TTo, TR>> Convert<TFrom, TR>(
Expression<Func<TFrom, TR>> e)
{
var oldParameter = e.Parameters[0];
var newParameter = Expression.Parameter(typeof(TTo), oldParameter.Name);
var converter = new ConversionVisitor(newParameter, oldParameter);
var newBody = converter.Visit(e.Body);
return Expression.Lambda<Func<TTo, TR>>(newBody, newParameter);
}

Get display annotation value on mvc3 server side code

Is there a way to get the value of an annotation in server side code? For example, I have:
public class Dummy
{
[Display(Name = "Foo")]
public string foo { get; set; }
[Display(Name = "Bar")]
public string bar { get; set; }
}
I want to be able to get the value "Foo" on server side with out posting it back to the page, but like an attribute of the class, or something of the sort. Like a #Html.LabelFor(model => model.Foo) But in c# server code.
Is that possible?
Thank you.
Something like this?
string displayName = GetDisplayName((Dummy x) => x.foo);
// ...
public static string GetDisplayName<T, U>(Expression<Func<T, U>> exp)
{
var me = exp.Body as MemberExpression;
if (me == null)
throw new ArgumentException("Must be a MemberExpression.", "exp");
var attr = me.Member
.GetCustomAttributes(typeof(DisplayAttribute), false)
.Cast<DisplayAttribute>()
.SingleOrDefault();
return (attr != null) ? attr.Name : me.Member.Name;
}
Or, if you want to be able to call the method against an instance and take advantage of type inference:
var dummy = new Dummy();
string displayName = dummy.GetDisplayName(x => x.foo);
// ...
public static string GetDisplayName<T, U>(this T src, Expression<Func<T, U>> exp)
{
var me = exp.Body as MemberExpression;
if (me == null)
throw new ArgumentException("Must be a MemberExpression.", "exp");
var attr = me.Member
.GetCustomAttributes(typeof(DisplayAttribute), false)
.Cast<DisplayAttribute>()
.SingleOrDefault();
return (attr != null) ? attr.Name : me.Member.Name;
}
You will need to use reflection. Here is a sample console program that does what you want.
class Program
{
static void Main(string[] args)
{
Dummy dummy = new Dummy();
PropertyInfo[] properties = dummy.GetType().GetProperties();
foreach (PropertyInfo property in properties)
{
IEnumerable<DisplayAttribute> displayAttributes = property.GetCustomAttributes(typeof(DisplayAttribute), false).Cast<DisplayAttribute>();
foreach (DisplayAttribute displayAttribute in displayAttributes)
{
Console.WriteLine("Property {0} has display name {1}", property.Name, displayAttribute.Name);
}
}
Console.ReadLine();
}
}
public class Dummy
{
[Display(Name = "Foo")]
public string foo { get; set; }
[Display(Name = "Bar")]
public string bar { get; set; }
}
This would produce the following result:
http://www.codetunnel.com/content/images/reflectresult.jpg

Categories

Resources