Multiple aspects on one method
Asked Answered
M

2

7

In my application I previously used regular C# attributes to "annotate" a method. E.g.:


[Foo(SomeKey="A", SomeValue="3")]
[Foo(SomeKey="B", SomeValue="4")]
public void TheMethod()
{
   SpecialAttributeLogicHere();
}


What SpecialAttributeLogicHere() did, was to reflectively look at all the Foo-attributes that annotated this particular method. It would then (on its own), create its own dictionary for all the keys and values.

I'm now trying to move to PostSharp, because the SpecialAttributeLogic could be put into an aspect (and removed from the method body which is much cleaner!), within OnEntry. Foo will be replaced by an aspect that extends OnMethodBoundaryAspect.

I would still like to use it the following way:


[Foo(SomeKey="A", SomeValue="3")]
[Foo(SomeKey="B", SomeValue="4")]

But if Foo has an OnEntry, that means that the "SpecialAttributeLogic" will be executed twice. I basically need to "gather" all the keys and values from each Foo(), into a dictionary, which I then apply some logic to.

How to do this (or best practices) with PostSharp? Thanks!

Marque answered 23/8, 2011 at 15:2 Comment(1)
added working example in my answer below.Patentor
P
2

It looks like you want to build a namevaluepair inside of your method. You cannot do this with an aspect. What I suggest is you use a MethodInterceptionAspect and reflect the attributes on the method then build your collection and pass it in the the method via a parameter (maybe using an overloaded method) or setting it as a class member.

You can reflect the values at compile time as to keep performance optimal.

Here is a quicky solution to your problem. It's a bit ugly (you will need to make modifications to fit). There are other ways but they aren't as "generic".

namespace ConsoleApplication12
{
    class Program
    {
        static void Main(string[] args)
        {
            MyExampleClass ec = new MyExampleClass();
            ec.MyMethod();
        }
    }

    public class MyExampleClass
    {
        [Special(Key = "test1", Value = "1234")]
        [Special(Key = "test2", Value = "4567")]
        [MyAspect]
        public void MyMethod()
        {
            MyMethod(new Dictionary<string, string>());
        }

        public void MyMethod(Dictionary<string, string> values)
        {
            //Do work
        }

    }

    [Serializable]
    public class MyAspect : MethodInterceptionAspect
    {
        Dictionary<string, string> values = new Dictionary<string, string>();
        MethodInfo target;

        public override void CompileTimeInitialize(System.Reflection.MethodBase method, AspectInfo aspectInfo)
        {
            target = method.DeclaringType.GetMethod(method.Name, new Type[] { typeof(Dictionary<string, string>) });

            foreach (Attribute a in method.GetCustomAttributes(false))
            {
                if (a is SpecialAttribute)
                {
                    values.Add(((SpecialAttribute)a).Key, ((SpecialAttribute)a).Value);
                }
            }
        }

        public override void OnInvoke(MethodInterceptionArgs args)
        {
            if (values == null || values.Count < 1)
            {
                args.Proceed();
            }
            else
            {
                target.Invoke(args.Instance, new object[] { values });
            }

        }
    }
    [AttributeUsage(AttributeTargets.Method, AllowMultiple = true) ]
    public class SpecialAttribute : Attribute
    {
        public string Key { get; set; }
        public string Value { get; set; }
    }
}

target and values are both initialized at compiletime (not runtime) for consumption at runtime. They get serialized with the aspect at compiletime. This way you save on the reflection hit at runtime.

Patentor answered 23/8, 2011 at 15:33 Comment(2)
Good answer. Alternatively I suppose I can do the same approach, but using OnMethodBoundaryAspect instead of MethodInterceptionAspect. Then, in OnEntry I would read the attributes for the method using reflection. But on the other hand, perhaps MethodInterceptionAspect is better for my needs. I'll have to give it some thought...Hulahula
Yes you can do that, but the issue is getting access to the data once you've colelcted it from the attributes. I wouldnt make the attribute an aspect because you'll end up having multiple aspects executing and really complicating your code. But using MethodBoundaryAspect prevents the need for a method overload. So either way should be ok.Patentor
M
0

Just as a note, I ended up using MethodInterceptionAspect and only overriding OnInvoke. Within OnInvoke I looked at args.Method.GetCustomAttributes(), giving me all the System.Attributes that I set (i.e. SpecialAttribute in DustinDavis' example).

Using those attributes and their properties, I can run the logic I needed to run. If the logic succeeds, I finish off with args.Proceed(), if not I throw an exception.

Marque answered 24/8, 2011 at 8:47 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.