Interceptors
Background
Okay, so I don’t really know why I’d use this, but I just came across Ninject.Extensions.Interception. Basically it allows you to intercept an arbitrary method call and run code before and after the call. Even replace the method entirely.
You could use this to achieve many things you would use Aspect Oriented Programming to do. Post Sharp has a great breakdown on what those use cases are and why it’s a valid approach to solving certain problems.
The canonical use case for this is the ability to easily add logging when you enter and exit a method.
For example, the following code would log all calls to the SomeSuperImportantMehod
[LogAllMethods]
public class CanonicalLogging
{
public virtual int SomeSuperImportantMehod(string input, int maxItems)
{
return CalcualteReallyImportantThing(input, maxItems);
}
}
And here’s an example of what the output could be
2017-05-05 13:45:54.64723 CanonicalLogging.SomeSuperImportantMehod called with parameters: {input: "Hello World", maxItems: 34 }
2017-05-05 13:56:56.94949 CanonicalLogging.SomeSuperImportantMehod returned "42" after running for 11:02:30226
Walkthrough
For the example I’m going to build an implementation of the Dispose Pattern with a base class that will handle all of the actual disposing logic. I’ll use a custom interceptor to ensure that once my objec thas been disposed all future calls to it will throw an ObjectDisposedException
.
To get started you first need to be using Ninject. I’m going to assume you have a bit of a background with Ninject. Just in case you don’t, the quick version is that it’s a .Net IoC container. Basically that means that after doing a little configuration you can ask it for an instance of any type you want and it will give it to you, creating it and its dependencies if it needs to.
Another thing to be aware of is that there are two options, either use LinFu or CastleCore for the proxy generation. As far as I can tell, this decision doesn’t really impact much. Just pick whichever framework you like, or one at random. I already use CastleCore in other projects (the Ninject factory extension depends on it), so I’m going with that one.
Install-Package Ninject.Extensions.Interception.DynamicProxy
ADisposable
Next I’ll create the base ADisposable
that all of my custom disposable objects will inherit from.
[Disposable]
public abstract class ADisposable : IDisposable
{
public ADisposable()
{
IsDisposed = false;
}
~ADisposable()
{
Dispose(false);
}
internal bool IsDisposed { get; private set; }
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected abstract void OnDispose();
private void Dispose(bool disposing)
{
if (IsDisposed) return;
if (disposing)
{
OnDispose();
}
IsDisposed = true;
}
}
There two things worth pointing out here. First, the internal IsDisposed
property. This will be used by our interceptor later to determine if the object has been disposed. The other thing worth noting is the Disposable
attribute. That’s a custom attribute I defined. It inherits from Ninject.Extensions.Interception.Attributes.InterceptAttribute
. This is the first step into using interceptors, so let’s take a look.
DisposableAttribute
using Ninject;
using Ninject.Extensions.Interception;
using Ninject.Extensions.Interception.Attributes;
using Ninject.Extensions.Interception.Request;
using System;
namespace Interceptors
{
[AttributeUsage(AttributeTargets.Class)]
public class DisposableAttribute : InterceptAttribute
{
public override IInterceptor CreateInterceptor(IProxyRequest request)
{
return request.Kernel.Get<DisposableInterceptor>();
}
}
}
There’s only one method that we have to worry about CreateInterceptor
. This just lets the system know what interceptor to use when creating the implementation of ADisposable. You can see here that I added the AttributeUsage to specifically target only classes. Be default InterceptAttributes can be put on anything, although they really only make sense on method (including properties) and class definitions. When the attribute is placed on the class, it impacts all of the methods. In fact there is a neat DoNotIntercept
attribute that you can use to exclude certain methods from the class wide interceptor.
In my example, I used the request to get an instance of the Kernel so I could resolve the interceptor I wanted. That was mostly just to illustrate that you can get a hold of the kernel. In this instance, I could have just as easily newed up the DisposableInterceptor. Speaking of which, DisposableInterceptor is where the most interesting code is. We’ll look at that next.
DisposableInterceptor
using Ninject.Extensions.Interception;
using System;
namespace Interceptors
{
public class DisposableInterceptor : SimpleInterceptor
{
protected override void BeforeInvoke(IInvocation invocation)
{
base.BeforeInvoke(invocation);
var disposable = invocation.Request.Target as ADisposable;
if (disposable != null && invocation.Request.Method.IsPublic)
{
if (disposable.IsDisposed)
{
string methodName = invocation.Request.Method.Name;
throw new ObjectDisposedException(disposable.GetType().Name, $"{methodName} called after object was disposed");
}
}
}
}
}
It inherits from SimpleInterceptor
and overrides BeforeInvoke
. As you may guess, there’s an AfterInvoke
as well. If you wanted to, you could set the return value of the method by setting invocation.ReturnValue
in BeforeInvoke
. Or in AfterInvoke
you could check to see if it’s within certain limits.
Anyway, in this example all I care about is overriding BeforeInvoke
to check if
- The instance being invoked is an instance of ADisposable
- The method being called is public (no need to worry about private methods being called since they’d have to be called from public ones to begin with)
- If the instance had already been disposed.
In order to do all of those things, I just need to inspect sub-properties of the passed in instance of IInvocation
. If all of those things are true, then we just throw an exception.
Now that we see the interception code is straight forward, let’s put it all together so that we can use it.
Sample Service
public interface ISomeService : IDisposable
{
void SomeMethod(string msg);
}
public class MyService : ADisposable, ISomeService
{
public void SomeMethod(string msg)
{
Console.WriteLine(msg);
}
protected override void OnDispose()
{
Console.WriteLine("Disposing MyService");
}
}
The first thing to look at is the sample service interface and implementation. ISomeService
is just a simple interface that inherits from IDisposable and has a single method to implement. MyService
inherits from ADisposable
and ISomeService
and pretty much does the minimum it needs to do to implement them.
Notice though that neither of them are aware of either Ninject or any of the interceptors. They are bog standard C# classes.
Resolving an Interface
private static void TestResolvingInterface()
{
Console.WriteLine("Testing resolving an Interface from the Ninject Kernel");
using (var kernel = new StandardKernel())
{
kernel.Bind<ISomeService>().To<MyService>();
ISomeService service = kernel.Get<ISomeService>();
TestService(service);
}
}
private static void TestService(ISomeService service)
{
Console.WriteLine(" " + service.GetType().Name); // Prints out "ISomeServiceProxy"
service.SomeMethod(" Hello World");
service.Dispose();
try
{
service.SomeMethod(" This call should fail");
}
catch (ObjectDisposedException odex)
{
Console.WriteLine(odex.Message);
}
}
In the first method, TestResolvingInterface
, we do some very standard Ninject setup. Create a standard kernel, add some bindings, and then resolve our interface. Again, none of this is even aware of the interceptors. Ninject picked up on it from the attribute and that was enough.
The output for this is:
Testing resolving an Interface from the Ninject Kernel
ISomeServiceProxy
Hello World
Disposing MyService
SomeMethod called after object was disposed
Object name: 'MyService'.
You’ll notice that service
is an instance of ISomeServiceProxy
, not MyService
. Ninject wrapped our instance of MyService
, adding the extra functionality to the proxy without actually impacting our class.
You’ll also see that the second call to SomeMethod
does indeed fail with an ObjectDisposedException
.
Resolving a Concrete Class
Now that worked all well and good. But what if we resolved just a class instead of an interface? Well that’s where this starts to break down as we’ll see in the code below.
private static void TestResolvingConcreteClass()
{
Console.WriteLine("Testing resolving a Concrete Class from the Ninject Kernel");
using (var kernel = new StandardKernel())
{
MyService service = kernel.Get<MyService>();
TestService(service);
}
}
private static void TestService(ISomeService service)
{
Console.WriteLine(" " + service.GetType().Name); // Prints out "MyServiceProxy"
service.SomeMethod(" Hello World");
service.Dispose();
try
{
service.SomeMethod(" This call should fail");
}
catch (ObjectDisposedException odex)
{
Console.WriteLine(odex.Message);
}
}
So now we’re resolving a MyService
directly and passing it to the same test function as the previous example. Let’s look at the output.
Testing resolving a Concrete Class from the Ninject Kernel
MyServiceProxy
Hello World
Disposing MyService
This call should fail
We’re getting a proxy wrapping our class again (MyServiceProxy
), which is a good sign, but then we see that the second call to SomeMethod
actually succeeds. Why is this?
This is probably the biggest caveat in the entire thing. The interceptors can only impact virtual methods. Interface methods are by nature virtual so they are no problem. But when it comes to concrete classes, unless the method is explicislty marked as abstract
or virtual
it can’t touch them. As I mentioned above, Ninject is dynamically creating an implementation of the requested object with custom implementations of all the methods. If MyService
had been sealed, Ninject would have thrown a TypeLoadException
when it was resolved because it couldn’t subclass the type.
Summary
As mentioned above, the major caveat is that it only works with virtual methods. If you always use interfaces, then you will be just fine, but if you don’t the interceptors will silently fail to be called.
Other than that, this is a pretty easy to use tool that can provide a lot of power. Another obvious use case for this is automating INotifyPropertyChanged. It’s so obvious that it comes premade. Just check out the NotifyOfChanges
attribute.