1

I'm looking for a way to get the name and value of a proptery in a POCO object. I've tried many solutions but can't seem to get them to work. I really liked this older solution but it causes a null ref error.

Here's kind of what I'm trying to do:

public class POCO
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Surname { get; set; }
        public string Description { get; set; }
    }

public class POCOValidationResult
{
    public bool IsValid { get; set; }
    public string Error { get; set; }
}

public abstract class Validator<T> where T : class
{
    public T Entity { get; set; }

    public abstract POCOValidationResult Validate();

    protected POCOValidationResult ValidateStringPropertyToLengthOf(Expression<Func<T, object>> expression, int maxLength)
    {
        var propertyName = getPropertyName(expression);
        var propertyValue = getPropertyValue(expression);

        if (propertyValue.Length > maxLength)
        {
            return new POCOValidationResult()
            {
                Error = string.Format("{0} value is too long. Must be less or equal to {1}", propertyName, maxLength.ToString())
            };
        }

        return new POCOValidationResult() { IsValid = true };
    }

    internal string getPropertyName(Expression<Func<T, object>> expression)
    {
        var memberExpersion = (MemberExpression)expression.Body;

        return memberExpersion.Member.Name;
    }
    internal string getPropertyValue<R>(Expression<Func<T, R>> expression)
    {
        //struggling to get this to work

        var me = (MemberExpression)expression.Body; // (MemberExpression)((MemberExpression)expression.Body).Expression;
        var ce = (ConstantExpression)me.Expression; // Error here!
        var fieldInfo = ce.Value.GetType().GetField(me.Member.Name, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
        var value = fieldInfo.GetValue(ce.Value);
    }
}

public class POCOValidator : Validator<POCO>
{
    public override POCOValidationResult Validate()
    {
        var surnameValidationResult = ValidateStringPropertyToLengthOf(p => p.Surname, 10);

        if (!surnameValidationResult.IsValid)
            return surnameValidationResult;

        //var descriptionValidationResult = ValidateStringPropertyToLengthOf(p => p.Description, 100);

        //if (!descriptionValidationResult.IsValid)
        //    return descriptionValidationResult;

        //var nameValidationResult = ValidateStringPropertyToLengthOf(p => p.Name, 15);

        //if (!nameValidationResult.IsValid)
        //    return nameValidationResult;

        return new POCOValidationResult() { IsValid = true };
    }
}

public class WorkerBee
{
    public void ImDoingWorkReally()
    {
        var pocoVallidation = new POCOValidator()
        {
            Entity = new POCO()
            { 
                ID = 1, 
                Name = "James", 
                Surname = "Dean", 
                Description = "I'm not 007!"
            }
        };

        var vallidationResult = pocoVallidation.Validate();

        if (!vallidationResult.IsValid)
        {
            return;
        }

        //continue to do work...
    }
}

class Program
{
    static void Main(string[] args)
    {
        var workerBee = new WorkerBee();

        workerBee.ImDoingWorkReally();
    }
}

So as you can see, I'm trying to get the Property's name and value [by using an Expression (p => p.Surname) as a parameter in the method ValidateStringPropertyToLengthOf(...)]. The problem is that I'm getting a null ref error in getPropertyValue(Expression<Func<T, object>> expression) when it calls var ce = (ConstantExpression)me.Expression;

So does anyone have ideas on how to get this to work?

Thanks for taking the time to look into this. I really appreciate it and hope that my question also is helpful for others as I think this can be rather useful if I can get this to work.

EDIT: I've made the change as mentioned below in the comments and still getting the error "Unable to cast object of type 'System.Linq.Expressions.TypedParameterExpression' to type 'System.Linq.Expressions.ConstantExpression" when I run my unit test.

Community
  • 1
  • 1
MaudDib
  • 39
  • 8
  • 3
    I am pretty sure this will explain the problem http://stackoverflow.com/questions/3567857/why-are-some-object-properties-unaryexpression-and-others-memberexpression/3573250#3573250 Please delete your question if that is the case. – leppie Mar 03 '15 at 07:47
  • This does look like a duplicate to me. Per leppie's advice, you should change the method declaration to `string getPropertyValue(Expression> expression)` so that the correct expression type is passed (i.e. one with a body). I would spend the 5 minutes to test it myself, but you didn't bother to include [a minimal, complete code example](http://stackoverflow.com/help/mcve). – Peter Duniho Mar 03 '15 at 08:08
  • I appreciate the help but changing the declaration did not help as I'm still getting the same error: "Unable to cast object of type 'System.Linq.Expressions.TypedParameterExpression' to type 'System.Linq.Expressions.ConstantExpression'." – MaudDib Mar 03 '15 at 08:32

2 Answers2

1

I worked out a solution (unfortunately the comments were not helpful). Here's the code that works:

public class POCO
{
    public int ID { get; set; }
    public string Name { get; set; }
    public string Surname { get; set; }
    public string Description { get; set; }
}

public class POCOValidationResult
{
    public bool IsValid { get; set; }
    public string Error { get; set; }
}

public abstract class Validator<T> where T : class
{
    public T Entity { get; set; }

    public abstract POCOValidationResult Validate();

    protected POCOValidationResult ValidateStringPropertyToLengthOf(Expression<Func<T, object>> expression, int maxLength)
    {
        var propertyName = getPropertyName(expression);
        var propertyValue = getPropertyValue(expression);

        if (propertyValue.Length > maxLength)
        {
            return new POCOValidationResult()
            {
                Error = string.Format("{0} value is too long. Must be less or equal to {1}", propertyName, maxLength.ToString())
            };
        }

        return new POCOValidationResult() { IsValid = true };
    }

    internal string getPropertyName(Expression<Func<T, object>> expression)
    {
        var memberExpersion = (MemberExpression)expression.Body;

        return memberExpersion.Member.Name;
    }
    internal string getPropertyValue(Expression<Func<T, object>> expression)
    {
        var memberExpression = expression.Body as MemberExpression;
        var propertyInfo = memberExpression.Member as PropertyInfo;

        return propertyInfo.GetValue(Entity, null).ToString();
    }
}

public class POCOValidator : Validator<POCO>
{
    public override POCOValidationResult Validate()
    {
        var surnameValidationResult = ValidateStringPropertyToLengthOf(p => p.Surname, 10);

        if (!surnameValidationResult.IsValid)
            return surnameValidationResult;

        var descriptionValidationResult = ValidateStringPropertyToLengthOf(p => p.Description, 100);

        if (!descriptionValidationResult.IsValid)
            return descriptionValidationResult;

        var nameValidationResult = ValidateStringPropertyToLengthOf(p => p.Name, 15);

        if (!nameValidationResult.IsValid)
            return nameValidationResult;

        return new POCOValidationResult() { IsValid = true };
    }
}

public class WorkerBee
{
    public void ImDoingWorkReally()
    {
        var pocoVallidation = new POCOValidator()
        {
            Entity = new POCO()
            {
                ID = 1,
                Name = "James",
                Surname = "Dean",
                Description = "I'm not 007!"
            }
        };

        var vallidationResult = pocoVallidation.Validate();

        if (!vallidationResult.IsValid)
        {
            return;
        }

        //continue to do work...
    }
}

class Program
{
    static void Main(string[] args)
    {
        var workerBee = new WorkerBee();

        workerBee.ImDoingWorkReally();
    }
}

Note the change for getPropertyValue:

    internal string getPropertyValue(Expression<Func<T, object>> expression)
    {
        var memberExpression = expression.Body as MemberExpression;
        var propertyInfo = memberExpression.Member as PropertyInfo;

        return propertyInfo.GetValue(Entity, null).ToString();
    }
MaudDib
  • 39
  • 8
0

I noticed that MaudDib's getPropertyValue method only works for properties directly on the object. e.g. it will work for myObj.Value, but not for myObj.Something.Value. The following works for both. (I don't know if there's a better way, though).

Usage: var myValue = GetPropertyValue(myObj, o => o.Something.Value);

public static object GetPropertyValue<T>(T obj, Expression<Func<T, object>> expression)
{
    var members = new CompositeExpressionVisitor().GetMembers(expression);
    object currentVal = obj;
    foreach (var part in members)
    {
        currentVal = GetPropertyValue(currentVal, part);
    }

    return currentVal;
}

private static object GetPropertyValue(object obj, MemberInfo member)
{
    var propertyInfo = (PropertyInfo)member;
    return propertyInfo.GetValue(obj, null);
}

private class CompositeExpressionVisitor : ExpressionVisitor
{
    private readonly List<MemberInfo> _members = new List<MemberInfo>();

    protected override Expression VisitMember(MemberExpression node)
    {
        _members.Add(node.Member);
        return base.VisitMember(node);
    }

    public IReadOnlyCollection<MemberInfo> GetMembers(Expression e)
    {
        Visit(e is LambdaExpression expression ? expression.Body : e);
        _members.Reverse();
        return _members;
    }
}

And, if you want the full path of the expression, e.g. you don't just want the leaf... you can do this:

Usage: var myValue = NameOf(() => myObj.Something.Value);

Returns: myObj.Something.Value

Or: var myValue = NameOf(() => myObj.Something.Value, 1);

Returns: Something.Value

public static string NameOf(Expression<Func<object>> expression, int startIndex = 0)
{
    return new CompositeExpressionVisitor().GetPath(expression, startIndex);
}

private class CompositeExpressionVisitor : ExpressionVisitor
{
    private readonly List<string> _parts = new List<string>();

    protected override Expression VisitMember(MemberExpression node)
    {
        _parts.Add(node.Member.Name);
        return base.VisitMember(node);
    }

    protected override Expression VisitParameter(ParameterExpression node)
    {
        _parts.Add(node.Name);
        return base.VisitParameter(node);
    }

    public string GetPath(Expression e, int startIndex = 0)
    {
        Visit(e is LambdaExpression expression ? expression.Body : e);
        _parts.Reverse();
        return string.Join(".", _parts.Skip(startIndex));
    }
}
Stephen Oberauer
  • 5,237
  • 6
  • 53
  • 75