Extension Methods + Attributes = A Whole New World!

5 ביולי 2008

9 תגובות

Earlier this week, a friend of mine (read his great blog here) had tried to explain me why attributes were useful. We ended up with an extension method that reads attributes of enum values.
Today I have the time to really think about that and I've just realized – this has tons of potential!

Consider the following scenario – you have the following class:

public string GetCityName(long userID)

{

    //

    // Get data from some datasource…

    //

 

    return "Ney York City";

}

And now you're told to add authentication mechanism, so only authenticated users will be able to get the information. What can you do?
Let's create a very simple attribute:

public class AuthenticationRequiredAttribute : Attribute

{

 

}

We'll use this attribute as a flag – if it's there, authenticate. Otherwise, do not. This is why we don't really need any information there. We'll add the attribute to our method as well:

[AuthenticationRequired]

public string GetCityName(long userID)

{

    //

    // Get data from some datasource…

    //

 

    return "Ney York City";

}

Now we have an attributed method! what's next? Let's create an extension method (remember, we don't want to change the original code). This method will check if the method requires authentication and do that if needed:

public static string GetCityNameAuthenticated(this MyClass myClass, long userId)

{

    // Get the method information

    MethodInfo mi = typeof(MyClass).GetMethod("GetCityName");

 

    // Check if our attribute appears

    object[] atts = mi.GetCustomAttributes(typeof(AuthenticationRequiredAttribute), false);

    if (atts != null && atts.Length > 0)

    {

        // Authenticate

        if (!AuthenticateSomehow(userId))

        {

            // User is not authenticated, raise an exception

            throw new UnauthorizedAccessException();

        }

    }

 

    return myClass.GetCityName(userId);

}

So what have we achieved? we've added authentication to a method without changing its original code! (I know it sounds a bit like AOP, but who cares? it's cool!).

I want to show you another example, maybe even more useful than the last one. It's about expanding enum values data (many thanks to Adrian who's helped me with that). The idea is the same – add an attribute and an extension method that reads it:

public enum Cars

{

    [CountryOfOrigin(Name="Japan")]

    Mazda,

    [CountryOfOrigin(Name="Sweden")]

    Volvo,

    [CountryOfOrigin(Name="France")]

    Peugeot,

    [CountryOfOrigin(Name="USA")]

    Chrysler

}

 

public class CountryOfOriginAttribute : Attribute

{

    public string Name { get; set; }

}

 

public static class Extension

{

    public static string GetCountryOfOrigin(this Cars car)

    {

        object[] atts = typeof(Cars).GetField(car.ToString()).GetCustomAttributes(typeof(CountryOfOriginAttribute), false);

 

        if (atts != null && atts.Length > 0)

        {

            return (atts[0] as CountryOfOriginAttribute).Name;

        }

 

        return String.Empty;

    }

}

Shay.


Share this post : del.icio.us it! digg it! dotnetkicks it! technorati!

הוסף תגובה
facebook linkedin twitter email

כתיבת תגובה

האימייל לא יוצג באתר. (*) שדות חובה מסומנים

9 תגובות

  1. Adrian Aisemberg5 ביולי 2008 ב 19:12

    Great!

    You can use the Attribute.GetCustomAttribute() method to get the instance of the required attribute, instead of an object array:

    AuthenticationRequiredAttribute att = (AuthenticationRequiredAttribute)Attribute.GetCustomAttribute(mi, typeof(AuthenticationRequiredAttribute));

    if (att!=null) …

    You can even write a static generic method that does the same thing:

    AuthenticationRequiredAttribute att = MyHelper.GetAttribute(mi);

    להגיב
  2. Eran KAmpf6 ביולי 2008 ב 11:02

    For the authentication example, why not just use .NET's built in CAS mechanism?

    Besides, I dont see the added value of using an extension method over a regular one in that example (unless, you can't change MyClass and then you'd have to use extension method for any added functionality which would be pretty ugly)

    להגיב
  3. shayf6 ביולי 2008 ב 11:13

    I agree that the first example is pretty stupid… that's what popped to my head at the time.
    I'm sure there are more rational and useful examples for this, though…

    להגיב
  4. liviu7 ביולי 2008 ב 13:04

    This tip is useless. Sorry. It requires you to hardcode when you need The property with or without authentication. I think this settings does not belong to the calling code, but to the context, the unit of work. It is again: USELESS.

    להגיב
  5. ZUB7 ביולי 2008 ב 15:29

    You wrote:
    "we've added authentication to a method without changing its original code!"

    Heh, you've added a new method, which is public along with the old one. So are you gonna tell everybody to use only new one?? :) or make a wrapper? but why to extend it then?
    The second example is more nicer.. but it doesn't look good to hardcode such properties as attributes

    להגיב
  6. Konrad Drukala7 ביולי 2008 ב 18:30

    I agree with others. Attributes are amazing things. About authenication.

    It's far better to spend smoetimes for implementing method call sink and perform authentication before method call. For more info, search for ContextBoundObject class and samples.

    להגיב
  7. Damon Wilder Carr7 ביולי 2008 ב 22:49

    NOTE: This leverages Linq expressions and a wide open extensible framework for entity validation. Just thought if anyone wanted to take this to the next production level, this has worked since .NET 2.0.. The C# 3.0 stuff is just for illustration…

    _______________________________________

    Extension methods don't enable anything new, just a more fluent API.

    First the test:

    [TestFixture]
    public class TestDriver {
    [Test]
    public void ValidateAuth() {
    var cust = new Customer(new PersonDAO()) {
    CityName = null
    };
    var GetCity = cust.PerformWithCheck(x => x.CityName);
    }
    }

    Now the plumbing:

    (see assume any number of attributes doing specific validations performed using the Visitor pattern shown below)

    public abstract class ValidateBaseAttribute : Attribute {
    public abstract ValidationTypes ValidationType { get; }
    }

    public class ValidateIsNotNullAttribute : ValidateBaseAttribute {
    public override ValidationTypes ValidationType {
    get {
    return ValidationTypes.ValidateIsNotNullAttribute;
    }
    }
    }

    public enum ValidationTypes {
    ValidateIsNotNullAttribute,
    ValidateIsNumeric … more as needed.. Link to attrib via overloaded on base
    }

    And the Extensions:

    public static void VisitorAuth(this MemberExpression target) {
    var attribs = target.Member.GetCustomAttributes(typeof (ValidateBaseAttribute),
    true);

    foreach (var attrib in attribs.Cast())
    switch (attrib.ValidationType) {
    case ValidationTypes.ValidateIsNotNullAttribute:
    VisitIsNotNull(target);
    break;
    default:
    throw new InvalidOperationException("Not a known validation type");
    }
    }

    private static void VisitIsNotNull(MemberExpression target) {
    // Perform validation here and throw exception if fails..
    }

    public static TMemberReturn PerformWithCheck(this TType myClass,
    Expression < Func
    TMemberReturn>>
    memberAccess)
    where TType : Person {
    var memberCast = memberAccess.Body as MemberExpression;

    if (memberCast == null)
    throw new InvalidOperationException("Not a member expression");

    memberCast.VisitorAuth();

    return memberAccess.Compile().Invoke(myClass);
    }

    More detail on your model showing Generic Constraints:

    public abstract class Person {
    private readonly IPersonDAO _daoStrategy;

    protected Person(IPersonDAO daoStrategy) {
    _daoStrategy = daoStrategy;
    }

    [ValidateIsNotNull]
    public string CityName {
    get {
    return _daoStrategy.CityName;
    }

    set {
    _daoStrategy.CityName = value;
    }
    }
    }

    public interface IPersonDAO {
    string CityName { get; set; }
    }

    public class PersonDAO : IPersonDAO {
    // Cannot be null
    private string _cityName = String.Empty;

    public virtual string CityName {
    get {
    return _cityName;
    }
    set {
    _cityName = value;
    }
    }

    }

    public class Customer : Person {
    public Customer(IPersonDAO daoStrategy) : base(daoStrategy) {}
    }

    Kind Regards,
    Damon Wilder Carr
    http://blog.domaindotnet.com/

    להגיב
  8. Mark Brackett8 ביולי 2008 ב 3:38

    CAS? Are you serious?
    1. CAS is an exceptionally hard and confusing subject.

    2. I don't think CAS was ever intended to be, or even able to be, the authentication and authorization pieces of your app. Though – I could be wrong (see #1).

    3. I'd love to see a CAS example, though. ;)

    להגיב
  9. Djordje8 ביולי 2008 ב 15:33

    Your RSS feed is not working. :(

    להגיב