locked
Pass lambda to custom attribute?

    Question

  • I have a great validation scheme in mind that would use extension methods and lambda expressions specified in attributes on business entity class properties (i.e., on 'dumb' data class properties).

     

    The only problem is that it seems like I can't pass a lambda to an attribute.

     

    E.g.,

    Code Block

    class Car

    {

    private int _year;

     

    [ValidationClause(c => c.Year > 2000)]

    public int Year

    {

    get { return _year;}

    set {_year = value;}

    }

    }

     

     

    This lambda would then be used by a validation method to evaluate the validity of each property on the instance of car. This would probably be through an extension method, like car.Validate(), and the Validate() method would then have the instance of car, and could reflect on the attribute to get the lambda to use for validation.

     

    I found these related links, but I was hoping that this was changed in 3.5. Anyone know?

    http://www.ayende.com/Blog/archive/2006/05/12/7612.aspx

    https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=91066

     

    If this is still an issue in 3.5, please follow the 2nd link and vote for this issue to be included. This would be a really cool feature.

     

    Thanks

     

    Saturday, November 10, 2007 1:48 AM

Answers

  • Hi vtcoder

     

    I'm afraid this is not included in our 3.5 release

     

    -David

    Wednesday, November 14, 2007 2:29 AM
  • I'm using a workaround

    Code Block



        [AttributeUsage(AttributeTargets.Field)]
        public class ValidatorAttribute: Attribute
        {
            public ValidatorAttribute()
            {
                
            }
        }

        public class Car
        {
            [Validator]
            private static Predicate<Car> ValidateYear = x => x.Year > 2000;

            private int fYear;

            public int Year
            {
                get
                {
                    return fYear;
                }
                set
                {
                    fYear = value;
                }
            }
        }


        class Program
        {
            static bool ValidateObject<T>(T obj)
            {
                return
                    typeof(T).GetFields(BindingFlags.NonPublic | BindingFlags.Static).
                    Where(fi => fi.GetCustomAttributes(typeof(ValidatorAttribute), false).Length > 0).
                    Select(fi => fi.GetValue(null) as Predicate<T>).
                    Where(predicate => predicate != null).
                    Aggregate(true, (seed, predicate) => seed && predicate(obj));
            }

            static void Main(string[] args)
            {
                Car a = new Car
                {
                    Year = 1999
                };
                Car b = new Car
                {
                    Year = 2008
                };

                Console.WriteLine(ValidateObject(a));
                Console.WriteLine(ValidateObject(b));
                Console.ReadLine();
            }
        }


    Friday, November 16, 2007 5:44 AM

All replies

  • Hi vtcoder

     

    I'm afraid this is not included in our 3.5 release

     

    -David

    Wednesday, November 14, 2007 2:29 AM
  • That's what I was afraid of. Thanks for the info.

     

    BTW - does this sound feasible for a future release? I'm wondering a) if there are any technical limitations that would prevent this, and b) do you think it would ever make it in as a feature (in terms of demand, etc).

     

    Thanks

     

    Thursday, November 15, 2007 7:17 PM
  • I'm using a workaround

    Code Block



        [AttributeUsage(AttributeTargets.Field)]
        public class ValidatorAttribute: Attribute
        {
            public ValidatorAttribute()
            {
                
            }
        }

        public class Car
        {
            [Validator]
            private static Predicate<Car> ValidateYear = x => x.Year > 2000;

            private int fYear;

            public int Year
            {
                get
                {
                    return fYear;
                }
                set
                {
                    fYear = value;
                }
            }
        }


        class Program
        {
            static bool ValidateObject<T>(T obj)
            {
                return
                    typeof(T).GetFields(BindingFlags.NonPublic | BindingFlags.Static).
                    Where(fi => fi.GetCustomAttributes(typeof(ValidatorAttribute), false).Length > 0).
                    Select(fi => fi.GetValue(null) as Predicate<T>).
                    Where(predicate => predicate != null).
                    Aggregate(true, (seed, predicate) => seed && predicate(obj));
            }

            static void Main(string[] args)
            {
                Car a = new Car
                {
                    Year = 1999
                };
                Car b = new Car
                {
                    Year = 2008
                };

                Console.WriteLine(ValidateObject(a));
                Console.WriteLine(ValidateObject(b));
                Console.ReadLine();
            }
        }


    Friday, November 16, 2007 5:44 AM
  • Good suggestion for a workaround. One subtlety that might be challenging using this approach is that I wanted to devise a scheme for "and/or" based on the order of the attributes. This also relies on each validation attribute/lambda being tied to a particular field.

     

    E.g.

    Code Block

    [ValidationClause(car => car.Year >= 2000)]

    [ValidationClause(car => car.Year <= 2008)]

    [ValidationClause(car => car.Year == 1998, LogicOps.Or)]

    public int Year

    {

    get { return _year; }

    set { _year = value; }

    }

     

    [ValidationClause(car => car.Make == "Ford")]

    [ValidationClause(car => car.Make == "Chevy"), LogicOps.Or]

    public string Make

    {

    get { return _make; }

    set { _make = value; }

    }

     

    In this scheme, the property to which the attribute is applied is important, as well as the order in which they are applied. I was envisioning the default to be AND, and you could specify OR when needed, or something along these lines (I hadn't nailed this as the best way yet). So for example, for the Year field above, a valid year would be between 2000 and 2008, or 1998. So the workaround doesn't fully support this scheme (at least without some modifications).

     

    I'm going to play with the suggested workaround though because I think we could probably get close with some additions. Perhaps creating our own generic delegate that could also specify the field or something.

     

    But I just wanted to show some of the subtleties that fully allowing lamba expressions to be passed to custom attributes would solve.

     

    Thanks for the workaround suggestion though.

     

     

    Friday, November 16, 2007 7:19 PM