Retrieving nested PropertyInfo via expressions - c#

I'm trying to create a function where I can pass in an expression to say which properties I'm interested in. I have it working for top level properties but not for nested properties.
Example model
public class Foo {
public string Name { get; set; }
public List<Foo> List { get; set; }
}
What I have so far
private PropertyInfo GetPropertyInfo<TModel>(Expression<Func<TModel, object>> selector)
{
if (selector.NodeType != ExpressionType.Lambda)
{
throw new ArgumentException("Selector must be lambda expression", nameof(selector));
}
var lambda = (LambdaExpression)selector;
var memberExpression = ExtractMemberExpression(lambda.Body);
if (memberExpression == null)
throw new ArgumentException("Selector must be member access expression", nameof(selector));
if (memberExpression.Member.DeclaringType == null)
{
throw new InvalidOperationException("Property does not have declaring type");
}
return memberExpression.Member.DeclaringType.GetProperty(memberExpression.Member.Name);
}
private static MemberExpression ExtractMemberExpression(Expression expression)
{
if (expression.NodeType == ExpressionType.MemberAccess)
{
return ((MemberExpression)expression);
}
if (expression.NodeType == ExpressionType.Convert)
{
var operand = ((UnaryExpression)expression).Operand;
return ExtractMemberExpression(operand);
}
return null;
}
So:
GetPropertyInfo<Foo>(x => x.Name); // works
GetPropertyInfo<Foo>(x => x.List.Select(y => y.Name); <-- how do I get this?
I'm looking for a way to pick any property from a complex object.

You need to extend your ExtractMemberExpression just a bit to accept Select call expression:
private MemberExpression ExtractMemberExpression(Expression expression) {
if (expression.NodeType == ExpressionType.MemberAccess) {
return ((MemberExpression) expression);
}
if (expression.NodeType == ExpressionType.Convert) {
var operand = ((UnaryExpression) expression).Operand;
return ExtractMemberExpression(operand);
}
if (expression.NodeType == ExpressionType.Lambda) {
return ExtractMemberExpression(((LambdaExpression) expression).Body);
}
if (expression.NodeType == ExpressionType.Call) {
var call = (MethodCallExpression) expression;
// any method named Select with 2 parameters will do
if (call.Method.Name == "Select" && call.Arguments.Count == 2) {
return ExtractMemberExpression(call.Arguments[1]);
}
}
return null;
}

Related

Unable to compile decompiled code via dotPeek

I'm looking at the following decompiled code and it wouldn't compile because of memberExpression1 and memberExpression2. How do I put the puzzles back, so it compiles?
using System.Globalization;
using System.Linq.Expressions;
using System.Reflection;
namespace Test;
public class RoutingDetail
{
protected RoutingDetail(IEnumerable<KeyValuePair<string, string>> routingParameters)
{
RoutingKeyParameters = routingParameters;
}
public IEnumerable<KeyValuePair<string, string>> RoutingKeyParameters { get; }
}
public class RoutingDetail<TMessage> : RoutingDetail
{
public RoutingDetail()
: base(new Dictionary<string, string>())
{
}
private RoutingDetail(IEnumerable<KeyValuePair<string, string>> routingParameters)
: base(routingParameters)
{
}
public RoutingDetail<TMessage> WithRoutingHeader(
KeyValuePair<string, string> routingHeader)
{
var dictionary = RoutingKeyParameters.ToDictionary(k => k.Key, k => k.Value);
dictionary.Add(routingHeader.Key, routingHeader.Value);
return new RoutingDetail<TMessage>(dictionary);
}
public RoutingDetail<TMessage> WithRoutingHeader<TRoutingValue>(
Expression<Func<TMessage, TRoutingValue>> property,
TRoutingValue value)
{
var dictionary = RoutingKeyParameters.ToDictionary(k => k.Key, k => k.Value);
var nameValueFromFunc = GetValidPropertyNameValueFromFunc(property, value);
dictionary.Add(nameValueFromFunc.Key, nameValueFromFunc.Value);
return new RoutingDetail<TMessage>(dictionary);
}
private KeyValuePair<string, string> GetValidPropertyNameValueFromFunc<TProperty>(
Expression<Func<TMessage, TProperty>> property,
TProperty value)
{
if (property.Body is not MemberExpression memberExpression1)
{
if (property.Body.NodeType is ExpressionType.Convert or ExpressionType.ConvertChecked &&
property.Body is UnaryExpression body)
{
memberExpression1 = body.Operand as MemberExpression;
}
if (memberExpression1 == null)
{
throw new ArgumentException($"Expression '{property}' refers to a method, not a property.");
}
}
var member1 = memberExpression1.Member as PropertyInfo;
var key = (object)member1 != null
? member1.Name
: throw new ArgumentException($"Expression '{property}' refers to a field, not a property.");
if (CustomAttributeExtensions.GetCustomAttributes(member1, typeof(RouteOnAttribute), true)
.FirstOrDefault() is not RouteOnAttribute routeOnAttribute1)
{
throw new ArgumentException(
$"Property {key} does not have the RouteOn attribute, and will not be used when routing. ");
}
memberExpression2 = memberExpression1;
while (memberExpression2.Expression is MemberExpression memberExpression2)
{
var member2 = memberExpression2.Member as PropertyInfo;
if ((object)member2 == null)
{
throw new ArgumentException($"Expression '{property}' refers to a field, not a property.");
}
key = $"{member2.Name}.{key}";
if (CustomAttributeExtensions.GetCustomAttributes(member2, typeof(RouteOnAttribute), true)
.FirstOrDefault() is not RouteOnAttribute routeOnAttribute2)
{
throw new ArgumentException(
$"Property {member1.Name} does not have the RouteOn attribute, and will not be used when routing. ");
}
if (!routeOnAttribute2.UseInnerProperties)
{
throw new ArgumentException(
$"Property {member1.Name} does not have the UseInnerProperties attribute set, and will not be used when routing.");
}
}
return new KeyValuePair<string, string>(key,
string.Format(CultureInfo.InvariantCulture, routeOnAttribute1.FormatString, value));
}
}
[AttributeUsage(AttributeTargets.Property)]
public class RouteOnAttribute : Attribute
{
public RouteOnAttribute()
{
FormatString = "{0}";
UseInnerProperties = false;
}
public string FormatString { get; set; }
public bool UseInnerProperties { get; set; }
}
Convert if (property.Body is not MemberExpression memberExpression1){ ... } to
var memberExpression1 = property.Body as MemberExpression;
if (memberExpression1 is null) {
if (property.Body.NodeType is ExpressionType.Convert or ExpressionType.ConvertChecked &&
property.Body is UnaryExpression body) {
memberExpression1 = body.Operand as MemberExpression;
}
if (memberExpression1 == null) {
throw new ArgumentException($"Expression '{property}' refers to a method, not a property.");
}
}
Convert
memberExpression2 = memberExpression1;
while (memberExpression2.Expression is MemberExpression memberExpression2) { ... }
to
MemberExpression memberExpression2 = memberExpression1;
while (memberExpression2.Expression is MemberExpression) { ... }
It is not advisable to copy/paste code that you don't understand.

Expression and Automapper

Somewhere in the internet I found below class which I am using to convert Expression<Func<T, bool>> from DTO to Domain:
public class EvaluateVariableVisitor<TEntity, TDto> : ExpressionVisitor
{
private readonly ParameterExpression _dtoParameter;
private readonly ParameterExpression _entityParameter;
private readonly IMapper _mapper;
public EvaluateVariableVisitor()
{
_entityParameter = Expression.Parameter(typeof(TEntity));
_dtoParameter = Expression.Parameter(typeof(TDto));
_mapper = AutoMapperConfig.Initialize();
}
protected override Expression VisitMember(MemberExpression node)
{
try
{
//change dto to entity type.
if (node.Expression.Type == _dtoParameter.Type)
{
var reducedExpression = Visit(node.Expression);
//TypeMap typeMap = Mapper.Configuration.FindTypeMapFor<TDto, TEntity>();
TypeMap typeMap = _mapper.ConfigurationProvider.FindTypeMapFor<TDto, TEntity>();
//find the correct name of the property in the destination object by using the name of the source objekt
string destinationPropertyName = typeMap.GetPropertyMaps() //GetCustomPropertyMaps()
.Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
.Select(propertyMap => propertyMap.DestinationProperty.Name).FirstOrDefault();
//find the correct name of the property in the destination object by using the name of the source objekt
//string destinationPropertyName = typeMap.GetPropertyMaps() //GetCustomPropertyMaps()
// .Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
// .Select(propertyMap => propertyMap.DestinationProperty.Name).Single();
var newMember = _entityParameter.Type.GetMember(destinationPropertyName).First();
return Expression.MakeMemberAccess(reducedExpression, newMember);
}
//Recurse down to see if we can simplify...
var expression = Visit(node.Expression);
//If we've ended up with a constant, and it's a property or a field,
//we can simplify ourselves to a constant
var constantExpression = expression as ConstantExpression;
if (constantExpression != null)
{
object container = constantExpression.Value;
object value = null;
var memberAsFieldInfo = node.Member as FieldInfo;
var memberAsPropertyInfo = node.Member as PropertyInfo;
if (memberAsFieldInfo != null)
{
value = memberAsFieldInfo.GetValue(container);
}
if (memberAsPropertyInfo != null)
{
value = memberAsPropertyInfo.GetValue(container, null);
}
if (value != null)
{
return Expression.Constant(value);
}
}
return base.VisitMember(node);
}
catch (System.Exception exc)
{
var ex = exc.Message;
throw;
}
}
//change type from dto to entity --> otherwise the generated expression tree would throw an exception, because of missmatching types(Dto can't be used in Entity expression).
protected override Expression VisitParameter(ParameterExpression node)
{
return node.Type == _dtoParameter.Type ? _entityParameter : node;
}
}
It was working great but I faced an issue when I had property of the same type inside my DTO:
public class InventoryApplicationDto
{
public int Id { get; set; }
public string Name{ get; set; }
public InventoryApplicationDto ParentApplication { get; set; }
}
First of all I had to change:
string destinationPropertyName = typeMap.GetPropertyMaps()
.Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
.Select(propertyMap => propertyMap.DestinationProperty.Name).Single();
To:
string destinationPropertyName = typeMap.GetPropertyMaps()
.Where(propertyMap => propertyMap.SourceMember.Name == node.Member.Name)
.Select(propertyMap => propertyMap.DestinationProperty.Name).FirstOrDefault();
My problem is that I am getting error:
System.ArgumentException: Property 'Int32 ID' is not defined for type
'InventoryApplicationDto'
on line:
return Expression.MakeMemberAccess(reducedExpression, newMember);
Is MakeMemberAccess method case sensitive or there is another issue?

Getting sub property names strongly typed

With databinding objects to controls and grids I hated how the property names would be magic strings, so I created a very simple method as follows:
public static string GetPropertyName<PropertyType>(Expression<Func<T, PropertyType>> expressionForProperty)
{
MemberExpression expression = expressionForProperty.Body as MemberExpression;
return expression.Member.Name;
}
This lets me use code such as:
Product.GetPropertyName(m => m.Name)
to return "Name".
This works perfectly for basic objects. However if I change the method call to be:
Product.GetPropertyName(m => m.ProductCategory.Name)
This also returns "Name". But in order for the databinding to work, I would need it to return "ProductCategory.Name". Is there a way I can get to this by changing the method "GetPropertyName"?
A possible workaround would be to do this:
string test = Product.GetPropertyName(p => p.ProductCategory) + "." + ProductCategory.GetPropertyName(pc => pc.Name)
However, this isn't a neat solution.
This is a modified version of something I might have found here on StackOVerflow:
public static class GetPropertyNameExtension
{
public static string GetPropertyName<TArg, TProperty>(this Expression<Func<TArg, TProperty>> propertyExpression)
{
return propertyExpression.Body.GetMemberExpression().GetPropertyName();
}
public static string GetPropertyName<TProperty>(this Expression<Func<TProperty>> propertyExpression)
{
return propertyExpression.Body.GetMemberExpression().GetPropertyName();
}
public static string GetPropertyName(this MemberExpression memberExpression)
{
if (memberExpression == null)
{
return null;
}
if (memberExpression.Member.MemberType != MemberTypes.Property)
{
return null;
}
var child = memberExpression.Member.Name;
var parent = GetPropertyName(memberExpression.Expression.GetMemberExpression());
return parent == null ?
child
: parent + "." + child;
}
public static MemberExpression GetMemberExpression(this Expression expression)
{
if (expression is MemberExpression)
return (MemberExpression)expression;
if (expression is UnaryExpression)
return (MemberExpression)((UnaryExpression)expression).Operand;
return null;
}
}
I came up with the following which seems to work:
public static string GetComplexPropertyName<PropertyType>(Expression<Func<T, PropertyType>> expressionForProperty)
{
// get the expression body
Expression expressionBody = expressionForProperty.Body as MemberExpression;
string expressionAsString = null;
// all properties bar the root property will be "convert"
switch (expressionBody.NodeType)
{
case ExpressionType.Convert:
case ExpressionType.ConvertChecked:
UnaryExpression unaryExpression = expressionBody as UnaryExpression;
if (unaryExpression != null)
{
expressionAsString = unaryExpression.Operand.ToString();
}
break;
default:
expressionAsString = expressionBody.ToString();
break;
}
// makes ure we have got an expression
if (!String.IsNullOrWhiteSpace(expressionAsString))
{
// we want to get rid of the first operand as it will be the root type, so get the first occurence of "."
int positionOfFirstDot = expressionAsString.IndexOf('.');
if (positionOfFirstDot != -1)
{
return expressionAsString.Substring(positionOfFirstDot + 1, expressionAsString.Length - 1 - positionOfFirstDot);
}
}
return string.Empty;
}

Creating a typed ModelState.AddModelError()

In a controller I can perform DB lookups etc and add some error message associated with a model property:
public ActionResult CreateJob(CreateJobModel viewModel)
{
var call = FindCall(viewModel.CallNumber);
if (call == null)
{
ModelState.AddModelError("CallNumber", "Idiot User!");
}
}
I don't like that CallNumber is a string, when ideally it should refer directly to viewModel.CallNumber, and if I change the name of that property, it should be changed too.
How can I achieve this?
I'd imagine the code would end up something like this, which would take a property access expression:
AddModelFieldError(() => viewModel.CallNumber, "Idiot User!");
But I'm not sure how to create a method like that, or in the case where it's a sub/inner-property that needs the error message.
I would write my own generic extension method:
public static class ModelStateDictionaryHelper
{
public static void AddModelError<TViewModel>(
this ModelStateDictionary me,
Expression<Func<TViewModel, object>> lambdaExpression, string error)
{
me.AddModelError(GetPropertyName(lambdaExpression), error);
}
private static string GetPropertyName(Expression lambdaExpression)
{
IList<string> list = new List<string>();
var e = lambdaExpression;
while (true)
{
switch (e.NodeType)
{
case ExpressionType.Lambda:
e = ((LambdaExpression)e).Body;
break;
case ExpressionType.MemberAccess:
var propertyInfo = ((MemberExpression)e).Member as PropertyInfo;
var prop = propertyInfo != null
? propertyInfo.Name
: null;
list.Add(prop);
var memberExpression = (MemberExpression)e;
if (memberExpression.Expression.NodeType != ExpressionType.Parameter)
{
var parameter = GetParameterExpression(memberExpression.Expression);
if (parameter != null)
{
e = Expression.Lambda(memberExpression.Expression, parameter);
break;
}
}
return string.Join(".", list.Reverse());
default:
return null;
}
}
}
private static ParameterExpression GetParameterExpression(Expression expression)
{
while (expression.NodeType == ExpressionType.MemberAccess)
{
expression = ((MemberExpression)expression).Expression;
}
return expression.NodeType == ExpressionType.Parameter ? (ParameterExpression)expression : null;
}
}
and the usage:
ModelState.AddModelError<CreateJobModel>(x => x.CallNumber,
"some kind attention");
It looks a bit differently from the version you've asked about, but I hope it can be acceptable alternative.
As of C# 6, you can use the nameof operator.
public ActionResult CreateJob(CreateJobModel viewModel)
{
var call = FindCall(viewModel.CallNumber);
if (call == null)
{
ModelState.AddModelError(nameof(CreateJobModel.CallNumber), "Idiot User!");
}
}

reflection v expression

I was looking at another question that stated how Expression can be significantly faster than reflection since it can be precompiled to IL.
I'm not really sure how to use it though. Here is some code used in a base class for a Value Oject (in the DDD sense) where the basic idea is to use the values of all public properties to determine equality, which it gets via reflection. By using this base class, you needn't implement equality for subclasses that have Value Object.
protected virtual bool HasSameObjectSignatureAs(BaseObject compareTo)
{
var signatureProperties = GetType().GetProperties();
foreach (var property in signatureProperties)
{
var valueOfThisObject = property.GetValue(this, null);
var valueOfCompareTo = property.GetValue(compareTo, null);
if (valueOfThisObject == null && valueOfCompareTo == null) {
continue;
}
if ((valueOfThisObject == null ^ valueOfCompareTo == null) ||
(!valueOfThisObject.Equals(valueOfCompareTo))) {
return false;
}
}
How would this code be re-written using Expression?
Cheers,
Berryl
You can do this by building up a nested And expression for each property you want to compare:
protected Expression<Func<BaseObject, bool>> CreatePropertiesEqualExpression(BaseObject other)
{
if (! other.GetType().IsSubclassOf(this.GetType())) throw new ArgumentException();
var properties = this.GetType().GetProperties();
Expression trueExpr = Expression.Constant(true);
Expression thisExpr = Expression.Constant(this);
ParameterExpression paramExpr = Expression.Parameter(typeof(BaseObject), "compareTo");
Expression downCastExpr = Expression.Convert(paramExpr, other.GetType());
MethodInfo eqMethod = typeof(object).GetMethod("Equals", BindingFlags.Public | BindingFlags.Static);
Expression propCompExpr = properties.Aggregate(trueExpr, (expr, prop) =>
{
Expression thisPropExpr = Expression.Property(thisExpr, prop);
Expression compPropExpr = Expression.Property(downCastExpr, prop);
Expression eqExpr = Expression.Call(null, eqMethod, Expression.Convert(thisPropExpr, typeof(object)), Expression.Convert(compPropExpr, typeof(object)));
return Expression.And(expr, eqExpr);
});
return Expression.Lambda<Func<BaseObject, bool>>(propCompExpr, paramExpr);
}
You can then use it like this:
public class SubObject : BaseObject
{
public int Id { get; set; }
public string Name { get; set; }
private Func<BaseObject, bool> eqFunc;
public bool IsEqualTo(SubObject other)
{
if(this.eqFunc == null)
{
var compExpr = this.CreatePropertiesEqualExpression(other);
this.eqFunc = compExpr.Compile();
}
return this.eqFunc(other);
}
}

Categories

Resources