This article started with my need for a good and simple interception mechanism for
some requirements that could be solved using AOP techniques. There are plenty of
interceptors out there but most of them (Castle.net, Spring.net, LinFu, etc..) would
require to emit dynamic child classes through IL code at runtime and thus ends with
almost always the same limitations regarding the classes you can intercept : they
can not be static, must be non-sealed, properties and methods must be virtual, etc...
Other interceptions mechanism would require to change the build process or to buy
a license. I could not afford one or the other...
AOP stands for Aspect Oriented Programming. I guess every reader is familiar with
the OP part of the acronym so we will have to clarify what Aspect means and don’t
worry we’ll come to that later in the article.
I’ll (try to) keep this article at beginner level. Knowledge of Object Oriented
Programming concepts is the only requirement to read further!
In my opinion understanding properly a concept make you a better consumer of its
implementation. But, I do understand that the article is a bit long so if you feel
bored or discouraged I still encourage you to jump to the implementation part. You'll
still be able to come back to theory later.
If you are familiar with AOP then don't leave yet!
Let me already disclose straight away what this article has to offer...
I will introduce an interception technique which allows to intercept:
- any class (including sealed, static, value types)
- constructors
- type initializers
- instance methods, properties and eventsand events (even if they
are not marked as virtual)
- static methods, properties and events
Without
- Weaving your code or your assemblies
- emitting IL code
- using any dynamic stuff
- modifying your target class
- forcing the weavers to implement anything (such as MarshalByRef)
Basically we are talking about a pure managed code technique which could run on
.Net 1.1 (actually I use bits of Linq but you can change t that easily) and which
allows you to intercept almost anything you might think of.
Let's be more clear. With the following technique:
you can intercept stuff such as System.DateTime.Now
or System.IO.File
operations!
you don't have the usual limitations of the most popular interceptions library.
Are you guys doubting ? then read further!!
Some might think they have not made their full journey into Object Oriented Programming
so why would they switch from OOP to AOP and abandoned all the concepts they hardly
have learnt over the years? Answer is simple: there is no switch. There is no:
OOP vs AOP! AOP is one of these concepts which name, in my opinion,
is misleading. Embracing the AOP principles lets you dealing with classes, objects,
interfaces, inheritance, polymorphism, abstraction etc… so there is no way you get
lost as you are still fully immersed in the OOP world.
When applying AOP concepts in your code you are attempting to relax one particular,
and not the least, principle of OOP which is encapsulation to adress cross-cutting
concerns (we'll come back to that later on).
Back in the old days, when internet was only yellow pages, bulleting boards and
usenet, you were better to read books if you wanted to learn anything (comment for
the y generation : a book is a thing with written sheets of paper stuffed in it).
And all these books would approximately remind you this regarding the OOP subject:
- Rule #1: you have to encapsulate all you data and code
- Rule #2: never ever break Rule #1
Encapsulation has been the ultimate goal for introducing OOP concepts
in 3rd generation languages (3GL).
Wikipedia:
Encapsulation is used to refer to one of two related but distinct
notions, and sometimes to their combination:
- A language mechanism for restricting access to some of the object’s components.
- A language construct that facilitates the bundling of data with the methods
(or other functions) operating on that data.
AOP, in a way, is claiming that it, sometimes, should be possible to use:
a language construct that facilitates the bundling of methods (or other functions)
operating with encapsulated data without the data.
Got that? Then you get all that is to know about the theory. Good job!
Now we are naturally leaded to the 2 following questions:
- When is “sometimes”?
- Why should it be possible?
Let's take a look at a the following situations:
You are software developer in a bank. The bank has a pretty working operational
system. The business is running smoothly. Government emits a policy which enforces
banks to commit on some sort of transparency. Whenever money goes in or out of the
bank it should be logged. The government publicly said that this is a first measure
towards transparency but that more is to be expected.
Your web application has been released to test team. All functional tests passed
but the application failed at load testing. A non-functional requirement stated
that no page should take more than 500 ms to process on the server. After analysis
there are dozens of queries made to the database that could be avoided by caching
the results.
You have spent the last 2 years modeling your domain model in a perfect library
consisting of 200+ classes. Lately you’ve been told that a new application front-end
will be written and this guy needs to bind your objects to the UI. But to facilitate
that task all your classes should now implement INotifyPropertyChanged
.
These examples are valid in terms of the why and when AOP could come to the rescue.
These scenarii have the following in common:
When is "sometimes”?
Some classes (Bank class, Data access services, Domain model classes, etc…) designed
to achieve a given functionality have to be modified to handle a requirement which
is basically “not their own business”.
- The Bank class purpose is money exchange. The logging concern is Government’s interest.
- The Data Services classes purpose is to retrieve data. The data caching concern
is a non-functional requirement.
- The domain model classes purpose is to handle your company’s business. The concern
about notification of property changed is in the UI interest.
It is whenever you have to write some code over different classes to fulfill an
interest external to these classes. In AOP dialect it is whenever you have a
cross-cutting concern.
The notion of cross-cutting concern is centric to AOP. No cross-cutting
concern = no need for AOP!
why should it be possible?
Let’s take a closer look at Scenario C.
Your problem is that you have an average of 5 properties exposed by each class in
your domain model. With 200+ classes you will have to implement (copy/paste) more
than 1,000 times some boiler plate code to transform something which looks like
this:
public class Customer
{
public string Name { get; set; }
}
Into something like that:
public class Customer : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private string _Name;
public string Name
{
get { return _Name; }
set
{
if (value != _Name)
{
_Name = value;
SignalPropertyChanged("Name");
}
}
}
void SignalPropertyChanged(string propertyName)
{
var pcEvent = this.PropertyChanged;
if (pcEvent != null) pcEvent(this, new PropertyChangedEventArgs(propertyName));
}
}
Uh! And that is for one property only! BTW, did you know the intern left 2 days
ago?
You surely get the why without further explanations
It should be possible "to bundle the methods operating with encapsulated data without
the data" in other words : externalise the implementation of the cross-cutting
INotifyPropertyChanged
concern with no or minimum impact on domain
model classes like Customer
.
If we can achieve that then we will:
- Have a proper separation of concerns
- Avoid code repetition and thus facilitate code maintenance
- Avoid hiding domain classes core business under tons of boiler plate code
Yeahh... ok, that's all nice and fancy and stuff but how can we do?
We have a cross-cutting concern which requires some code to be
executed in several classes (targets from now on).
The implementation (the code which implements Logging, or Cache, or whatever) is
simply called concern in the AOP world.
We should then be able to attach (inject, introduce, etc... choose your word) our
concern (I repeat because it is important: the concern
is the implementation for your cross-cutting concern) at any chosen
place of the target.
And we should be able to choose any of the following places of the target
to attach our concern:
- Static initializers
- Constructors
- Static Property getters and setters
- Instance Property getters and setters
- Static methods
- Instance Methods
- Destructors
In a perfect AOP world we should be able to attach our concern
at any line of code of the target.
Fine, but if we want to attach a concern we need a hook in the
target, don’t we? Yes captain!
In AOP the notion of that hook (the place where your concern is
going to be attached for execution) has a name: it is a pointcut.
And the place from where you actualy attach the code has also a name : it is a
joinpoint.
Clear enough? Maybe not.... Here is some pseudo-code that hopefully demonstate the
idea:
class BankAccount
{
public string AccountNumber {get;}
public int Balance {get; set;}
void Withdraw(int AmountToWithdraw)
{
:public pointcut1; Balance -= AmountToWithdraw;
}
}
concern LoggingConcern
{
void LogWithdraw(int AmountToWithdraw)
{
Console.WriteLine(this.AccountNumber + " withdrawal on-going...");
}
}
class Program
{
void Main()
{
pointcut = typeof(Bank).GetPointcut("pointcut1");
LoggingConcern.Join(cutpoint,
LogWithdraw);
}
}
Would'nt it be great to have such mechanism available out of the C# box???
Before we move on to our actual implementation let's introduce a few more definitions...
What is an Aspect?
It is the association of a concern, a point cut
and a joinpoint.
Think of it for a second and I hope it will be crystal clear: the fact that I have
a Logging mechanism (concern), that I register its log method to
be executed (joinpoint) at a given place of my application code
(pointcut) is one aspect of my application.
But wait a minute... What could/should a concern be allowed to
do once injected?
Concerns are categorized into 2 categories:
- Side effects:
A side effect is a concern which is not changing
the behavior of the code at pointcut. A side effect
just introduce an additional action to perform.
A Logging concern is a good example of a side effect:
whenever the runtime execute the targeted method (eg:Bank.Withdraw(int Amount)
)
the LoggingConcern.LogWithdraw(int Amount)
method will be executed
and the Bank.Withdraw(int Amount)
method will continue execution.
- Advices:
An advice is a concern which might change the
input/output of the method.
A Caching concern is a perfect example: Whenever the runtime execute
the targeted method (eg: CustomerService.GetById(int Id)
) the CachingConcern.TryGetCustomerById(int
Id)
will be executed and will force the CustomerService.GetById(int Id)
method to return with the value found in cache, if any, otherwise will let the execution
continue.
Advices should be allowed to:
- Inspect the input parameters at targeted pointcut and modify them if needed
- Cancel the execution of the targeted method and replace it with a different implementation
- Inspect the output result od the targeted method and modify or replace it
At that point if you are still reading then congratulations! Bravo! Parce que vous
le valez bien...
We are done with the general concepts and ideas of AOP. Let's move forward and see
how we can get close to that with C#...
The concern should have a magic this
behavior which
is of our target type.
That's no problemo!
public interface IConcern<T>
{
T This { get; } }
There is no easy way to get pointcuts for every single line of
code. But we can get one at each method call and that's fairly easy by using the
System.Reflection.MethodBase
class. MSDN is not really verbose
about it: Provides information about methods and constructors.[sic].
Between you and me using MethodBase
for getting reference to pointcuts
is the most powerful possibility at your disposition.
You can get reference to pointcuts for Constructors, Methods, Properties and Events
as for .Net almost anything you declare in your code (apart from fields) ends up
being a method...
See yourselves:
public class Customer
{
public event EventHandler<EventArgs> NameChanged;
public string Name { get; private set; }
public void ChangeName(string newName)
{
Name = newName;
NameChanged(this, EventArgs.Empty);
}
}
class Program
{
static void Main(string[] args)
{
var t = typeof(Customer);
var pointcut1 = t.GetConstructor(new Type[] { });
var pointcut2 = t.GetMethod("ChangeName");
var nameProperty = t.GetProperty("Name");
var pointcut3 = nameProperty.GetGetMethod();
var pointcut4 = nameProperty.GetSetMethod();
var NameChangedEvent = t.GetEvent("NameChanged");
var pointcut5 = NameChangedEvent.GetRaiseMethod();
var pointcut6 = NameChangedEvent.GetAddMethod();
var pointcut7 = NameChangedEvent.GetRemoveMethod();
}
}
Writing the code for joining is fairly easy as well. Look at the signature of the
method below :
void Join(System.Reflection.MethodBase pointcutMethod, System.Reflection.MethodBase
concernMethod);
We can add that signature to a sort of registry that we will provide later on and
we can already imagine writing code like this one!!!
public class Customer
{
public string Name { get; set;}
public void DoYourOwnBusiness()
{
System.Diagnostics.Trace.WriteLine(Name + " is doing is own business");
}
}
public class LoggingConcern : IConcern<Customer>
{
public Customer This { get; set; }
public void DoSomething()
{
System.Diagnostics.Trace.WriteLine(This.Name + " is going to do is own business");
This.DoYourOwnBusiness();
System.Diagnostics.Trace.WriteLine(This.Name + " has finished doing its own business");
}
}
class Program
{
static void Main(string[] args)h
{
var pointcut1 = typeof(Customer).GetMethod("DoSomething");
var concernMethod = typeof(LoggingConcern).GetMethod("DoSomething");
AOP.Registry.Join(pointcut1, concernMethod);
}
}
How far are we from our pseudo-code? Personally I would say not much...
What's next then?
That's where problems and fun start at the same time!
But let's start simple with
The registry will keep records about joinpoints. It's a singleton
list of joinpoint items.
A joinpoint is a simple struct :
public struct Joinpoint
{
internal MethodBase PointcutMethod;
internal MethodBase ConcernMethod;
private Joinpoint(MethodBase pointcutMethod, MethodBase concernMethod)
{
PointcutMethod = pointcutMethod;
ConcernMethod = concernMethod;
}
public static Joinpoint Create(MethodBase pointcutMethod, MethodBase concernMethod)
{
return new Joinpoint (pointcutMethod, concernMethod);
}
}
Nothing fancy... It should as well implement IEquatable<Joinpoint>
but for making the code shorter here I have intentionally removed it
And the registry: The class is called AOP
and implements the singleton
pattern. It exposes its unique instance through a public static property named
Registry
public class AOP : List<Joinpoint>
{
static readonly AOP _registry;
static AOP() { _registry = new AOP(); }
private AOP() { }
public static AOP Registry { get { return _registry; } }
[MethodImpl(MethodImplOptions.Synchronized)]
public void Join(MethodBase pointcutMethod, MethodBase concernMethod)
{
var joinPoint = Joinpoint.Create(pointcutMethod, concernMethod);
if (!this.Contains(joinPoint)) this.Add(joinPoint);
}
}
With the AOP
class we can now write construct like :
AOP.Registry.Join(pointcut, concernMethod);
We've got an obvious and serious problem now to cope with. If a developer writes
code like...
var customer = new Customer {Name="test"};
customer.DoYourOwnBusiness();
... there is just no reason why our registry would be consulted, so there is no
way that our LoggingConcern.DoSomething()
method is executed...
Our problem is that .Net does not provide us with a simple way to intercept such
calls out of the box.
As there is no native way then some work around must be implemented. The capabilities
of your work around is going to drive the capabilities of your AOP implementation.
The goal of this aricle is not to discuss all possible interception techniques but
take note that the interception model is the key differentiator
between all AOP implementations.
The SharpCrafters website (owners of PostSharp) is providing some clear information
on the 2 major techniques:
There is not much of a secret if you want to intercept all calls made to a class
you have 3 choices:
- Create your own language and compiler to produce .net assemblies: when compiling
you can inject whatever you want everywhere you want.
- Implement a solution which modifies the runtime behavior of assemblies
- Give a proxy to your consumer and intercept calls using an Interceptor class while
marshaling the real subject (target)
For advanced guys: I voluntarily don't mention Debugger API and Profiler API possibilities
which are not viable for production scenarii.
For very advanced one: An hybrid of solutions 1 and 2 using the Roslyn API should
be feasible and, as far as I know, it is still to be invented. A bon entendeur...
Apart if you need to provide pointcuts at any single line of code
then it seems that the 2 first solutions are a bit of over-engineering.
We'll go for the 3rd solution. Take note that usage of proxying technique comes
with a good and a bad news:
The bad news is that your target object must be swapped at runtime
with a proxy object instance. Implying that if you want to intercept things such
as constructors you'll have to delegate construction of your target
class instances to a factory (that's a cross-cutting concern that
this implementation won't solve. If you already have an instance of the target
class then you will have to explicitely ask for the swap to happen. For
the IOC and Dependency Injection ninjas the delegation of objects creation will
be less than an issue. For others it means they'll have to use a factory if they
want to use our interception technique at its full extent. But don't worry we are
going to implement that factory.
The good news is that we have nothing to do to implement a proxy. The class System.Runtime.Remoting.Proxies.RealProxy
will build it for us in a highly optimized way.
In my opinion the class name does not reflect its use. This class is not a Proxy
it is an Interceptor. But anyway that class will provide us with a Proxy by calling
its method GetTransparentProxy()
and that's the only thing we need.
So the skeleton for our interceptor is :
public class Interceptor : RealProxy, IRemotingTypeInfo
{
object theTarget { get; set; }
public Interceptor(object target) : base(typeof(MarshalByRefObject))
{
theTarget = target;
}
public override System.Runtime.Remoting.Messaging.IMessage Invoke(System.Runtime.Remoting.Messaging.IMessage msg)
{
IMethodCallMessage methodMessage = (IMethodCallMessage) msg;
MethodBase method = methodMessage.MethodBase;
object[] arguments = methodMessage.Args;
object returnValue = null;
return new ReturnMessage(returnValue, methodMessage.Args, methodMessage.ArgCount, methodMessage.LogicalCallContext, methodMessage);
}
#region IRemotingTypeInfo
public string TypeName { get; set; }
public bool CanCastTo(Type fromType, object o) { return true; }
#endregion
}
Some explanations are required here as we are now touching the heart of the implementation....
The RealProxy
class exists to serve the purpose of intercepting calls
from remote objects and marshal a targeted object. By remote here you must understand
really remote like : objects living in another application, another AppDomain, another
server, etc...). I am not going to go too much in details but there were 2 ways
to marshal objects in the .net Remoting infrastructure : by reference or by value.
Basically it means you can only marshal remote objects if they are inheriting
MarshalByRef
or if they implement ISerializable
. Our plan
is not to use the remoting capabilities at all but we still need to let the RealProxy
class think our target is acceptable for remoting. That's why we pass typeof(MarshalByRef)
to the RealProxy
base constructor.
The RealProxy
class is receiving all calls made on the transparent
proxy via the System.Runtime.Remoting.Messaging.IMessage Invoke(System.Runtime.Remoting.Messaging.IMessage
msg)
method. That's where we will implement the details about method
swapping. Read the comments in the code above.
About the implementation of IRemotingTypeInfo
: In a true remoting environment
the client side would request an object to the server. The client application runtime
might not know anything about the type of the marshalled remote object. So when
the client app makes a call to the method public object GetTransparentProxy()
the runtime must decide if the returned object (the transparent proxy) is boxable
to the client application expected contract. By implementing IRemotingTypeInfo
you give a hint to the client runtime telling if casting to a specified type is
allowed or not.
And guess what the trick is there, in front of your astonished gaze, right here...
public bool CanCastTo(Type fromType, object o) { return true; }
All our AOP implementation is only possible due to the possibility offered by remoting
to write these 2 words: return true;
Passed that point we can cast
the object returned by GetTransparentProxy()
to whatever interface
without any runtime check!!!.
The runtime just purely and simply gave us a "yes card" to play with!
We might want to revisit that code to return something more appropriate than true
to any type... But we could also imagine make use of this behaviour to provide a
Missing
Method implementation or a catch all interface... There is a lot of room
fot your creativity to express istself here!
At that point we have a decent interception mechanism for our target instance. We
are still mising interception of constructors and creation of the transparent proxy.
That's a job for a factory...
Not much to say about that one. Here is the skeleton of the class.
public static class Factory
{
public static object Create<T>(params object[] constructorArgs)
{
T target;
return GetProxyFor<T>(target);
}
public static object GetProxyFor<T>(object target = null)
{
return new Interceptor(target).GetTransparentProxy();
}
}
Note that the Factory
class is always returning an object of type
object
. We can't return an object of type T
because the
transparent proxy is simply not of type T
, it is of type System.Runtime.Remoting.Proxies.__TransparentProxy
.
But, remember the "Yes card", we can cast the returned object to whatever interface
without any runtime checking!
We will nest the Factory
class in the AOP
class hoping
to give a neat programming experience to our consumers. But you'll see that in the
Usage section below
If you have read the whole article till that point I must recognize you are almost
a hero! Bravissimo! Kudos!
For the sake of brevity and clarity of this article (damned... why are you smiling?)
I am not going to discuss the boring implementation details of method retrievals
and switching. There is actually not much fun in it. But if you are interested in
that piece then you can download the code and browse it : it is fully functional!.
The classes and methods signature might be a bit different as I am coding while
but no major change is to be expected.
Warning: Before deciding to use this code in your project please
read carefully the
paenultimus section. And if you don't know
the word paenultimus then I guess you have to click the link first!
I have been writing a lot but did not give you, yet, a proper hint on how we can
actually use all of this. And finally here we are : the moment of truth!
The attached zip file includes a project with 5 examples for the sake of demonstration.
So you will get examples of injecting aspects by:
- Intercepting a constructor
- Intercepting methods and properties
- Intercepting events
- Intercepting type initialization
- Intercepting
File.ReadAllText(string path)
Now I am going to show two out of these five: the most and the less obvious
First we need a domain model... Nothing fancy
public interface IActor
{
string Name { get; set; }
void Act();
}
public class Actor : IActor
{
public string Name { get; set; }
public void Act()
{
Console.WriteLine("My name is '{0}'. I am such a good actor!", Name);
}
}
Then we need a concern
public class TheConcern : IConcern<Actor>
{
public Actor This { get; set; }
public string Name
{
set
{
This.Name = value + ". Hi, " + value + " you've been hacked";
}
}
public void Act()
{
This.Act();
Console.WriteLine("You think so...!");
}
}
At application initialization we tell the Registry about our joinpoints
AOP.Registry.Join
(
typeof(Actor).GetProperty("Name").GetSetMethod(),
typeof(TheConcern).GetProperty("Name").GetSetMethod()
);
AOP.Registry.Join
(
typeof(Actor).GetMethod("Act"),
typeof(TheConcern).GetMethod("Act")
);
And finally we create an object via the Factory
var actor1 = (IActor) AOP.Factory.Create<Actor>();
actor1.Name = "the Dude";
actor1.Act();
Note that we requested the creation of an Actor
class but we can cast
the result to an interface so let's use IActor
as the class is implementing
it.
If you run that in a Console application the output will be the following:
My name is 'the Dude. Hi, the Dude you've been hacked'. I am such a good actor!
You think so...!
Here we have 2 slight issues:
- the
File
class is static
- and does not implement any interface
That's where we benefit from the "Yes card"! Remember? There is no runtime type
checking between the returned proxy and the interface.
Which means we can create any kind of interface... no one as to implement it anyway:
neither the target nor the concern. Basically
we are only using the interface as a contract...
Let's demonstrate that by creating a fake interface to mimic the static File
class
public interface IFile
{
string[] ReadAllLines(string path);
}
Our concern
public class TheConcern
{
public static string[] ReadAllLines(string path)
{
return File.ReadAllLines(path).Select(x => x + " hacked...").ToArray();
}
}
The registering of joinpoints
AOP.Registry.Join
(
typeof(File).GetMethods().Where(x => x.Name == "ReadAllLines" && x.GetParameters().Count() == 1).First(),
typeof(TheConcern).GetMethod("ReadAllLines")
);
And finally execution of the program
var path = Path.Combine(Environment.CurrentDirectory, "Examples", "data.txt");
var file = (IFile) AOP.Factory.Create(typeof(File));
foreach (string s in file.ReadAllLines(path)) Console.WriteLine(s);
In this case please note that we can not use the Factory.Create<T>
method as static types cannot be used as generic arguments.
In no particular order:
So far we have been able to achieve the primary goal of AOP : implement an aspect
and register it for execution. TinyAOP is born. But your journey in AOP land is not finished yet and you might want to dig further:
- Reason #1: Who wants to register Joinpoints the way we are now? Not me, for sure!
With a bit of introspection we can make things much more practical and build something
that look like a real AOP library. AOP is there to facilitate your life not to introduce
more pain.
- Reason #2: Matters such as mix-ins and composition are completely uncovered and we can expect tons of goodness there.
- Reason #3: We need performance and stability. Now the code is just a proof of concept.
It is far too slow when we can make it very fast. A bit of error checking would
not be bad either.
- Reason #4: We are intercepting almost any kind of class but what about interfaces
interception??
- Reason #5: Do we really need more reasons ?
Conclusion: We have a nice and tiny prototype which demonstrates the technical feasibility
of doing AOP purely with managed, non-dynamic, code without weaving, etc...
You have learnt AOP : you can start from here and roll your own implementation!
No secret that I am french... Nobody's perfect! While writing this article I was
googling for a place where the expression "A boire, ou je tue le chien!" would be
explained and I found that page called "French expressions you won't learn at school".
I am sure you might find some of these expressions pretty funny so I am sharing
the link : http://www.globule.org/~gpierre/french.php
A website such as Codeproject is only working because some guys are writing and
publishing articles. Whatever the reasons why these guys are doing it, they are!
And that takes a non-negligible amount of work and time.
Please do not neglect that time and work:
If you don't like the article please refrain to give your vote of 1 without further
explanations... I might have wrote a statement which is wrong or false, my english
surely needs rephrasing, maybe you are expecting more or less explanations, I don't
know... It is as simple as that: I don't know if you don't tell!
Your justified bad ratings are welcome I am not gonna hurt (ok, maybe a bit ) and
it will allow me to revise my judgment, make any necessary adjustments or corrections
to the article and also to improve myself for future ones.
Now if you liked the article, or you are using the code or if you have learned something
today then let me know as well : leave a comment, give me your
vote (of 5), drop me an email, connect on LinkedIn... Whatever form of feedback
is much appreciated!!!
Thanks for reading!