Click here to Skip to main content
15,881,746 members
Articles / Programming Languages / C#

AOP - Method and property interception in C#

Rate me:
Please Sign up or sign in to vote.
4.95/5 (18 votes)
3 Jan 2018MIT5 min read 54.1K   1.6K   23   9
This how-to shows the usage of Cauldron.Interception.Fody and its capabilities.

Introduction

AOP is widely used in the Java world, but it is still not that prominent in the .NET world. There are actually a lot of AOP frameworks for .NET out there, but those frameworks are mostly proxies and their usage are not very straight-forward.

In this how-to, I will be introducing the Fody plugin Cauldron.Interception.Fody. Cauldron.Interception.Fody provides basic interceptors to intercept methods, properties and constructors and its feature-set is aimed towards eliminating boilerplate code.

Background

Back in 2015 I was tasked to add a non-intrusive logging to a huge application that is already maintained and running for ages. For me, there was only one possibility of implementing it; Interceptors. That time I already knew PostSharp, Fody+Plugins, Spring.NET and Castle. What I wanted though are not Proxies, but IL-weavers. IL-weavers manipulate the code while building the assembly, instead of creating and inheriting from intercepted classes on runtime. After days of searching for PostSharp alternatives, I came to the conclussion that there are no any. Don't get me wrong, PostSharp is great, but it is not free (Yes... there is a free version, but it comes with limitations).

For that project though, I end up using PostSharp, but the lack of real alternative motivated me to create my own. Since beginning of 2017 I made it available as a Nuget package.

Getting Cauldron.Interception.Fody

You can get this plugin directly from the Nuget gallery or from the Visual Studio Nuget Manager.

Supported .NET version

The current version of Cauldron.Interception.Fody supports NET45, NETStandard and UWP.

Creating a your first method interceptor

In this example we will create a simple method interceptor that logs the execution of a method.

The method interceptor has to implement the 'IMethodInterceptor' interface and inherit the 'Attribute' class.

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
public class LoggerAttribute : Attribute, IMethodInterceptor
{
      public void OnEnter(Type declaringType, object instance, MethodBase methodbase, object[] values)
      {
            this.AppendToFile($"Enter -> {declaringType.Name} {methodbase.Name} {string.Join(" ", values)}");
      }

      public void OnException(Exception e)
      {
            this.AppendToFile($"Exception -> {e.Message}");
      }

      public void OnExit()
      {
            this.AppendToFile("Exit");
      }

      private void AppendToFile(string line)
      {
            File.AppendAllLines("log.txt", new string[] { line });
            Console.WriteLine(">> " + line);
      }
}

For this example we will create a method called 'Add' that we will decorate with the 'LoggerAttribute'.

[Logger]
private static int Add(int a, int b)
{
      return a + b;
}

Now every call of the 'Add' method will be logged in 'log.txt'.

The console will also show the following:

How does this works?

The weaver modifies your assembly and adds the interceptor to your 'Add' method. The resulting code will look like the following:

private static LoggerAttribute loggerAttribute;

private static int Add(int a, int b)
{
      if(loggerAttribute == null)
            loggerAttribute = new LoggerAttribute();

      try
      {
            loggerAttribute.OnEnter(typeof(Program), null, methodof(Add), new object { a, b });
            return a + b;
      }
      catch(Exception e)
      {
            loggerAttribute.OnException(e);
            throw;
      }
      finally
      {
            loggerAttribute.OnExit();
      }
}

The modification happens in the IL code of your assembly during build. This means that you will not see any modification in your code.

Introducing the AssignMethodAttribute

Everybody knows auto-properties in C#.

public string MyProperty { get; set; }

But what if you want to invoke an event in the setter?

In that case you need to implement the getter and setter like this:

private string _myProperty;

public string MyProperty 
{
      get { this._myProperty; }
      set
      {
            this._myProperty = value;
            this.MyPropertyChanged?.Invoke();
      }
}

Wouldn't it be easier if you could do this instead?

[OnPropertySet]
public string MyProperty { get; set; }

private void OnMyPropertySet()
{
      this.MyPropertyChanged?.Invoke();
}

The following sample implementation of the property interceptor is using the AssignMethodAttribute to invoke the associated method. The associated method in the case of the above example is the 'OnMyPropertySet' method.

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class OnPropertySetAttribute : Attribute, IPropertySetterInterceptor
{
      [AssignMethod("On{Name}Set", false)]
      public Action onSetMethod = null;

      public void OnException(Exception e)
      {
      }

      public void OnExit()
      {
      }

      public bool OnSet(PropertyInterceptionInfo propertyInterceptionInfo, object oldValue, object newValue)
      {
            this.onSetMethod?.Invoke();
            return false;
      }
}

The AssignMethodAttribute is decorating the field 'onSetMethod'. The field type 'Action' describes the method return type and parameters. In this case the associated method has to be void and parameterless.

The first parameter of the AssignMethodAttribute is the name of the associated method, while '{Name}' is a placeholder which will be replaced by the property's name. If the interceptor is for example decorating a property named 'OrderData', then the weaver will be searching for a method called 'OnOrderDataSet'.

The second parameter of the AssignMethodAttribute tells the weaver to throw an error if the described method is not found.

There are also cases where all properties are invoking the same event in their setters. A well known example would be the PropertyChanged event in WPF.

Let us modify our property interceptor to accept method names in its constructor.

[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class OnPropertySetAttribute : Attribute, IPropertySetterInterceptor
{
      [AssignMethod("{CtorArgument:0}", true)]
      public Action<string> onSetMethod = null;

      public OnPropertySetAttribute(string methodName)
      {
      }

      public void OnException(Exception e)
      {
      }

      public void OnExit()
      {
      }

      public bool OnSet(PropertyInterceptionInfo propertyInterceptionInfo, object oldValue, object newValue)
      {
            this.onSetMethod?.Invoke(propertyInterceptionInfo.PropertyName);
            return false;
      }
}

As you may have noticed the 'On{Name}Set' is changed to {CtorArgument:0}. The {CtorArgument:0} placeholder will be replaced by the weaver with the value of a constructor parameter. In this case the index 0 is the parameter named 'methodName'.

The delegate Action type is also changed to Action<string> to be able to pass the property's name.

The following code demonstate the usage of the interceptor:

[OnPropertySet(nameof(RaisePropertyChanged))]
public string DispatchDate { get; set; }

[OnPropertySet(nameof(RaisePropertyChanged))]
public string OrderDate { get; set; }

private void RaisePropertyChanged(string propertyName)
{
      this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Now every time a new value is assigned to the decorated properties, the method 'RaisePropertyChanged' is invoked. This is already much practical than calling the 'RaisePropertyChanged' method in every setter, but it is still impractical since it has to be added to every property.

Let us modify the AttributeUsage of the property interceptor and add 'Class' to the attribute's target.

[AttributeUsage(AttributeTargets.Property | AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
...

Now the interceptor can be applied to the class and it will intercept all properties in the class.

[OnPropertySet(nameof(RaisePropertyChanged))]
public class CoolViewModel
{
      public string DispatchDate { get; set; }
      public string OrderDate { get; set; }
      public int OrderCount { get; set; }

      private void RaisePropertyChanged(string propertyName) => this.PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}

Conclusion

IL-weaved interception is a very useful tool that can help minimize your code and make it more readable and maintainable, while not compromising your application's performance too much. Like everything else the over-usage of interceptors will lead to the opposite. If everything is happenning auto-magically it will be hard to understand what is happening in your code. So use it wisely.

While logging might be the most obvious usage of interceptors (In fact almost every example I saw in the internet is about logging), interception shows its full potential in scenarios where a lot of boilerplate code is involved. Just to give you an idea where I personally use interceptors, here a list of some interceptors I currently use:

  • PerformanceLoggerAttribute - A property and method interceptor, that logs the execution performance.
  • OnDemandAttribute - A property interceptor, that loads it's value when the getter is called; Some sort of LazyLoading.
  • DBInsureConnectionAttribute - A method interceptor that checks the DB connection and automatically established a connection if not connected.
  • RegistryValueAttribute - A property interceptor that get or set a defined registry value if the getter or setter is invoked.
  • PriviledgeAttribute - A method interceptor that checks if the user has the required privilege to execute a method; throws an exception otherwise.
  • RegisterChildrenAttribute - A property interceptor that registers the property value to the declaring viewmodel.

Links

https://github.com/Capgemini/Cauldron

https://github.com/Fody/Fody

https://github.com/Capgemini/Cauldron/wiki

License

This article, along with any associated source code and files, is licensed under The MIT License


Written By
Germany Germany
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionSupport of .NET 6 Pin
Dmitriy Rogovoy17-Oct-22 8:48
Dmitriy Rogovoy17-Oct-22 8:48 
QuestionInfrastructure coupling! Pin
Puresharper15-Aug-18 14:11
professionalPuresharper15-Aug-18 14:11 
QuestionDoes it allow me to modify parameters? Pin
Chengwei Lin at TW28-Mar-18 3:38
Chengwei Lin at TW28-Mar-18 3:38 
AnswerRe: Does it allow me to modify parameters? Pin
Alexei Stryker2-Aug-18 22:24
Alexei Stryker2-Aug-18 22:24 
GeneralMy vote of 5 Pin
Hyland Computer Systems4-Jan-18 19:46
Hyland Computer Systems4-Jan-18 19:46 
PraiseMuch needed contribution Pin
FemiAde4-Jan-18 7:50
FemiAde4-Jan-18 7:50 
GeneralRe: Much needed contribution Pin
Alexei Stryker2-Aug-18 22:25
Alexei Stryker2-Aug-18 22:25 
PraiseGood Pin
wmjordan2-Jan-18 13:49
professionalwmjordan2-Jan-18 13:49 
GeneralRe: Good Pin
Alex Schunk3-Jan-18 1:41
Alex Schunk3-Jan-18 1:41 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.