Jeder der sich mit Aspekt-orientierter Programmierung (AOP) in C# schon mal auseinandergesetzt hat, musste leider feststellen, dass AOP im .Net-Umfeld nicht wirklich unterstützt wird. Aus diesem Grund haben Drittanbieter wie SharpCrafters mit ihrem populären AOP-Framework PostSharp diese Nische ausgefüllt. Allerdings gibt es auch Wege diese Funktionalität ohne eine kostenpflichtige Komponente zu realisieren! 

Hinter AOP steckt die Idee, einen Aspekt wie bspw. Logging oder Sicherheit beim Aufruf einer Methode automatisch zu berücksichtigen. Unterm Strich bedeutet dieser Ansatz, dass immer dann, wenn eine Methode aufgerufen oder verlassen wird, eine zusätzliche Methode automatisch ausgeführt wird.  Dieses Prinzip ist auch als Interception bekannt. Für einen Logging-Aspekt (Tracing) würde das Ausführen der Interception-Methode bspw. wie folget aussehen:

· Pre-Processing: Trace-Logging des Methodenaufrufes mit Eingabe-Parametern

· Invoke: Aufruf der eigentlichen Methode

· Post-Processing: Tracing-Logging beim Verlassens der Methode mit Rückgabewert.

Warum nicht Drittanbieter?

Neben den anfallenden Lizenzkosten von Lösungen wie PostSharp oder Spring.Net ist oftmals auch schlicht der Einsatz von Drittanbietern im Kundenprojekt nicht erwünscht. Daher kommt als fertiges Framework meistens nur Unity, als kostenlose Variante aus den Microsoft „Pattern & Practise“-Bereich, in Frage. Den AOP-Ansatz mit Unity habe ich nachfolgend für das Trace-Logging prototypisch dargestellt.

 

AOP mit Unity

Unter Unity wird am IoC-Container ein Interceptor registriert, der dann den Aspekt abbilden kann. Nachfolgend ist zu sehen, wie ein InterceptionBehavoir vom Typ „Interceptor“ (mein Interceptor) für das Interface „IDAL“ registriert wird. Sobald über den IoC-Container eine Instanz einer Klasse mit der Implementierung von IDAL bezogen wird, greift der Interceptor beim Aufruf der Methoden.

 
      IUnityContainer container = new UnityContainer();

      container.AddNewExtension<Interception>();

      container.RegisterType<IDAL, DAL>(new Interceptor<VirtualMethodInterceptor>(),new InterceptionBehavior<Interceptor>());

      IDAL dal = container.Resolve<IDAL>();

      dal.MethodForLoggingA();

      dal.MethodForLoggingB();

      dal.MethodForLoggingC();

In meinem oben dargestellten Code-Beispiel werden drei Methoden der Klassen-Implementation von IDAL aufgerufen. Da für IDAL ein Interceptor registriert wurde, tritt dieser bei Methodenaufrufen künftig in Aktion. Nachfolgend meine Implementation des Interceptors für das Trace-Logging.

public class Interceptor : IInterceptionBehavior

{

 public IEnumerable<Type> GetRequiredInterfaces()

 {

 return Type.EmptyTypes;

  }

 public IMethodReturn Invoke(IMethodInvocation input, GetNextInterceptionBehaviorDelegate getNext)

 {

 /* Call the method that was intercepted */

 string className = input.MethodBase.DeclaringType.Name;

 string methodName = input.MethodBase.Name;

 string generic = input.MethodBase.DeclaringType.IsGenericType ? string.Format("<{0}>", input.MethodBase.DeclaringType.GetGenericArguments().ToStringList()) : string.Empty;

 string arguments = input.Arguments.ToStringList();

 string preMethodMessage = string.Format("{0}{1}.{2}({3})", className, generic, methodName, arguments);

  Console.WriteLine("PreMethodCalling: " + preMethodMessage);

 //Logging

  Logger.Instance.Log(preMethodMessage);

 //Invoke method

  IMethodReturn msg = getNext()(input, getNext);

 //Post method calling

 string postMethodMessage = string.Format("{0}{1}.{2}() -> {3}", className, generic, methodName, msg.ReturnValue);

  Console.WriteLine("PostMethodCalling: " + postMethodMessage);

 //Logging

  Logger.Instance.Log(postMethodMessage);

 return msg;

}

 public bool WillExecute

 {

 get { return true; }

  }
}

Die Ausgabe des Trace-Loggings gemäß die Methodenaufrufe sieht  wie folgt aus:

clip_image001

AOP mit nativem C#-Code

Einige Zeit war ich der Meinung, dass man unabdingbar ein zusätzliches AOP-fähiges Framework benötigt. Dem ist aber gar nicht der Fall! Dank des brillanten Artikels inklusive Code-Beispiel unter  (http://www.developerfusion.com/article/5307/aspect-oriented-programming-using-net/ ) und diesem Artikel aus der MSDN (http://msdn.microsoft.com/en-us/library/aa288717(v=vs.71).aspx ) habe ich eine neue Option für AOP entdeckt.

Die Klasseninstanzen, die man mit einem Aspekt versehen möchte, müssen allerdings von „ContextBoundObjekt“ erben; ansonsten funktioniert dieser Ansatz nicht. Da C# keine Mehrfachvererbung unterstützt und viele Klassen, die man verwendet bereits erben, ist es manchmal schwierig diesen Ansatz konsequent durchzusetzen. Hier helfen nur extra Helfer-Klassen, die dann die  Verarbeitungslogik übernehmen und von „ContextBoundObjekt“ erben.

Zudem ist die Klasse ContextBoundObjekt für AOP eigentlich zweckentfremdet! Da aber Microsoft selbst den AOP-Ansatz damit propagiert, halte ich den Einsatz für unbedenklich.

Nachfolgend ist mein derzeitiger Tracing-Aspekt ohne Unity dargestellt. Dabei habe ich mich durch das sehr hilfreiche Code-Beispiel von http://www.developerfusion.com/article/5307/aspect-oriented-programming-using-net/ inspirieren lassen.

     public class TracingAspect : IMessageSink

    {

         public TracingAspect(IMessageSink next)

        {

            m_next = next;

        }

        #region private variables

        private IMessageSink m_next;

        private String m_typeAndName;

        #endregion // private variables 

        #region IMessageSink implementation

         public IMessageSink NextSink

        {

               get { return m_next; }

        }

         public IMessage SyncProcessMessage(IMessage msg)

        {

            Preprocess(msg);

             IMessage returnMethod = m_next.SyncProcessMessage(msg);

            PostProcess(msg, returnMethod);

            return returnMethod;

        }

         public IMessageCtrl AsyncProcessMessage(IMessage msg, IMessageSink replySink)

        {

              throw new InvalidOperationException();

        }

        #endregion //IMessageSink implementation

        #region Helper methods 

       // Defines the AOP processing logic before the method will be invoked

        private void Preprocess(IMessage msg)

        {

             if (Logger.GetInstance().IsTracingEnabled)

            {

                 // We only want to process method calls

                 if (!(msg is IMethodMessage)) return;

                 IMethodMessage call = msg as IMethodMessage;

                 Type type = Type.GetType(call.TypeName);

                m_typeAndName = type.Name + „.“ + call.MethodName;

                string traceMsg = „PreProcessing: “ + m_typeAndName + „(„;

                // Loop through the [in] parameters

                for (int i = 0; i < call.ArgCount; ++i)

                {

                     if (i > 0) traceMsg += „, „;

                    traceMsg += call.GetArgName(i) + “ = “ + call.GetArg(i);

                }

                traceMsg += „)“;

                Logger.GetInstance().WriteTrace(TraceEventType.Start, traceMsg);

            }

        }

        //Handles the AOP logic after the method was invoked

        private void PostProcess(IMessage msg, IMessage msgReturn)

        {

             if (Logger.GetInstance().IsTracingEnabled)

            {

                   // We only want to process method return calls

                   if (!(msg is IMethodMessage) || !(msgReturn is IMethodReturnMessage)) return;

                     IMethodReturnMessage retMsg = (IMethodReturnMessage)msgReturn;

                     string traceMsg = „PostProcessing: „;

                     Exception e = retMsg.Exception;

                    if (e != null)

                    {

                          traceMsg += „Exception was thrown: “ + e.ToString();

                          return;

                   }

                  // Loop through all the [out] parameters

                traceMsg += m_typeAndName + „(„;

                 if (retMsg.OutArgCount > 0)

                {

                     traceMsg += „out parameters[„;

                     for (int i = 0; i < retMsg.OutArgCount; ++i)

                    {

                           if (i > 0) traceMsg += „, „;

                           traceMsg += retMsg.GetOutArgName(i) + “ = “ +

                            retMsg.GetOutArg(i);

                    }

                    traceMsg += „]“;

                }

                if (retMsg.ReturnValue.GetType() != typeof(void))

                {

                    traceMsg += “ returned [“ + retMsg.ReturnValue + „]“;

                }

                traceMsg += „)\n“;

                Logger.GetInstance().WriteTrace(TraceEventType.Stop, traceMsg);

            }

        }

        #endregion Helpers

    }

     //Defines the IContextProperty to register the tracing aspect

     public class TracingProperty : c, IContributeObjectSink

    {

        #region IContributeObjectSink implementation

        public IMessageSink GetObjectSink(MarshalByRefObject o, IMessageSink next)

       {

                return new TracingAspect(next);

        }

        #endregion // IContributeObjectSink implementation

         #region IContextProperty implementation

          public string Name

          {

                get

              {

                     return „CallTracingProperty“;

              }

        }

        public void Freeze(Context newContext)

        {

        }

        public bool IsNewContextOK(Context newCtx)

        {

               return true;

         }

         #endregion //IContextProperty implementation

    }

    //Defines the attribute to tag a type beeing traced

     [Conditional(„DEBUG“)]

     [AttributeUsage(AttributeTargets.Class)]

     public class TracingAttribute : ContextAttribute

    {

            public TracingAttribute() : base(„CallTracing“) { }

            public override void GetPropertiesForNewContext(IConstructionCallMessage ccm)

            {

                     ccm.ContextProperties.Add(new TracingProperty());

            }

    }

}

Eine Klasse muss nun wie folgt attributiert werden, um den Tracing-Aspekt greifen zu lassen. In meinem letzten Web-Projekt hatte ich einen MVC-Ansatz verwendet. Da bspw. die Controller-Klassen unter ASP.Net-MVC bereits von Controller erben, habe ich für jeden Controller, dessen Verarbeitung ich nachverfolgen möchte, eine Helper-Klasse erstellt.

     [Tracing()]

     public class RoomTracedHelper: ContextBoundObject

    {

               private static List<RoomData> Rooms { get; set; }

                ….

   }

Beide oben genannten AOP-Ansätze habe ich bereits erfolgreich in der Praxis eingesetzt und bin bis dato mit beiden sehr zufrieden.  Allerdings lassen beide in Punkto Performanz etwas zu wünschen übrig und sind daher für ein High-Performance-Szenario nicht zu empfehlen!

1 Comments

  1. Pingback: AOP .Net – Ohne Drittanbieter - SharePoint Blogs in German - Bamboo Nation

Leave a comment

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

Time limit is exhausted. Please reload the CAPTCHA.