Introduction
This article is all about Aspect Orientated Programming (AOP).
Here is some tech speak about AOP, from some sources who are more than likely more eloquent than me:
Aspect-oriented programming entails breaking down program logic into distinct parts (so-called concerns, cohesive areas of functionality). All programming paradigms support some level of grouping and encapsulation of concerns into separate, independent entities by providing abstractions (e.g., procedures, modules, classes, methods) that can be used for implementing, abstracting, and composing these concerns. But some concerns defy these forms of implementation and are called crosscutting concerns because they "cut across" multiple abstractions in a program. Logging exemplifies a crosscutting concern because a logging strategy necessarily affects every logged part of the system. Logging thereby crosscuts all logged classes and methods.
All AOP implementations have some crosscutting expressions that encapsulate each concern in one place. The difference between implementations lies in the power, safety, and usability of the constructs provided. For example, interceptors that specify the methods to intercept express a limited form of crosscutting, without much support for type-safety or debugging.
-- http://en.wikipedia.org/wiki/Aspect-oriented_programming
Aspect-Oriented Software Development (AOSD), sometimes just called Aspect-Oriented Programming (AOP), is a new approach to software design that addresses modularity problems that are not handled well by other approaches, including Structured Programming and Object-Oriented Programming (OOP). AOSD complements, but doesn't replace those approaches. Typical enterprise and internet applications today have to address "concerns" like security, transactional behavior, logging, etc. The subsystems that provide these services can be implemented in a modular way. However, to use these services, you must insert the same "boilerplate" code fragments into various places in the rest of the application to invoke these services. This violates the "don't repeat yourself" (DRY) principle and it compromises the overall system modularity, because the same invocation code is scattered throughout the application. For example, if you want to control access to certain services in your application, you could insert authorization-checking boilerplate at the beginning of every method that needs this control. Because this redundant boilerplate appears in many places in the code, it would now be difficult and error prone to modify or replace this security approach later, should that become necessary. Also, your application code is now tangled with code for the security (and probably other) concerns, which both compromises clarity and makes it hard to reuse your code in another context, since you would have to drag along the same security approach, which may not be appropriate. Because concerns like security typically cut across a number of application module boundaries (e.g., classes). We call them cross-cutting concerns. Note that the loss of modularity is at the intersection between the different concerns. AOP restores modularity by developing the cross-cutting concerns, or aspects, in isolation and then combining them with other modules using declarative or programmatic mechanisms that are modular. That is, the points of intersection are defined once, in one place, making them easy to understand and maintain. The other modules require no modifications to be advised by the aspects. This "intersection" process, sometimes called weaving, can occur at build or run time. AOSD weaving is a key innovation that provides very fine grained query and composition semantics. Where traditional code linking has the ability to resolve method and variable names, weaving adds the ability to replace method bodies with new implementations, insert code before and after method calls, instrument variable reads and writes, and even associate new state and behavior with existing classes, typically for adding "mixin" behaviors.
-- What is Aspect-Oriented Software Development?
The Different Type of Aspect Frameworks Out There
There are a few different options available when doing aspect orientated programming (henceforth known as AOP) in .NET. These options/frameworks broadly fall into one of two categories:
Proxy Based AOP Frameworks
Some/a lot of you may have heard of Dependency Injection and even used IOC/Dependency Injection containers such as Castle/Unity/StructureMap/Spring before.
Well, it just so happens that it is quite common for some of these IOC/Dependency Injection containers to use proxies that wrap the real implementation (source object) that is being stored within the IOC/Dependency Injection container. For example, Castle uses a class called DynamicProxy
while Unity offers TransparentProxy
and VirtualMethodProxy
objects. So now that we know that there are some proxies in use (at least in some of these IOC/Dependency Injection containers), it is not so hard to imagine that we can use a proxy to intercept calls to the real object, where by virtual methods/properties (which are just methods really) can be intercepted by the proxy before it calls the real object. Where the user is free to create whatever type of interception code they wish to, which is usually accomplished by implementing a particular interface or extending a certain class etc.
This diagram may help to illustrate how proxy based AOP works:
The problem with using this type of AOP framework is that you are forced into using a DI/IOC paradigm which you may not want to use for any other reason than to allow AOP. What that means to you, the developer, is that any type you wish to use AOP with must exist in the IOC/Dependency Injection container and must be resolved from it. This forces a certain type of programming on you, which as I say you may not want. The other problem with proxy based AOP frameworks is the fact that they are using proxies usually means that any method/property you wish to allow to be intercepted must be marked as virtual
. Now, you may or may not be able to live with this, but it does open up certain dangers that some other bit of code could mistakenly override these methods/properties due to them being virtual, which could indicate an extension point to someone looking at the code who is not familiar with the overall picture.
One final thing that proxy based AOP frameworks do not seem to be able to cope with is to introduce aspects around backing fields, or around static types, methods, and properties.
IL Weaving Based AOP Frameworks
IL Weaving is an interesting new thing that has only become fairly mainstream (at least I think so anyway) over the past year or two. So what is this IL Weaving of which I speak?
Well, we all know that this is what the normal workflow for .NET code looks like, right?
Well, let's now consider the following diagram, which illustrates what happens in IL Weaving based AOP frameworks.
Essentially, what happens is that your existing code base is extended by either implementing special interfaces or inheriting from a particular base class, where you can enter your new code. Then at compile time, the new code you entered into these interfaces implementation/classes is obtained, and the IL for the original code and these new bits of code is obtained and literally written out into the Assembly instead of the original IL code.
Pure IL weaving is 100% possible using a not very well known DLL called Mono.Cecil which is part of the Mono project. Although IL weaving is quite advanced, it is something that I urge you all to have an investigative look at, as at least one of the existing AOP frameworks that I have chosen to look at uses Mono.Cecil internally. I will talk about how Mono.Cecil works in some detail when I discuss the AOP frameworks that rely on Mono.Cecil.
The main problem with this type of code is that the workflow of the original code is no longer clear, but saying that, that is also the case with Proxy based AOP frameworks. What IL weaving based frameworks have in their favour is the fact that they are not using a Proxy, they will literally write out new IL, so they do not require any interception method/properties to be virtual at all. This is the case as the IL weaving framework will just grab the original method's IL and pre/post pend to it, or possibly replace it all with new IL. The other neat thing with IL weaving based AOP frameworks (OK, some are more advanced than others) is that you can even introduce new members, fields, events, etc., and there are no problems working with static types, they are just types after all, and as such have IL too.
What Are We Going to Try and Achieve
OK, so now that you have a basic idea of what AOP is about and how some of the existing frameworks out there may provide us, .NET developers, with the tools to write our own aspects, let's briefly talk about what the attached demo app(s) do.
I have provided four different AOP framework demos; where possible I have tried to make them all do the same thing; that has not been possible in all cases, but generally it has been achieved.
So what has been achieved then?
Well, as some of you may know, I am quite into my WPF development, and as such, there is one interface above all others that I implement all the time. This interface is the System.ComponentModel.INotifyPropertyChanged
interface (INPC from here on) which looks like this:
namespace System.ComponentModel
{
public interface INotifyPropertyChanged
{
event PropertyChangedEventHandler PropertyChanged;
}
}
Which is typically implemented like this:
public class MainWindowViewModel : INotifyPropertyChanged
{
private int someProperty;
public int SomeProperty
{
get { return someProperty; }
set
{
someProperty=value;
RaisePropertyChanged("SomeProperty");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Now, this interface relies on a magic string being passed into the RaisePropertyChanged
method. And as we all know, strings are not refactorable. In fact, the use of the INPC interface is so common that many developers, including myself, have come up with different ways of getting rid of this magic string. Solutions range from using T4 templates, to using Expression Trees, to using StackFrame
s, believe me I have seen a few implementations.
The thing is, with all these approaches, you still need to do some work. Which got me thinking, wouldn't it be nice if we could just attribute up an auto property to tell if it was an INPC attribute, something like this:
public class MainWindowViewModel : INotifyPropertyChanged
{
[INPCAttribute]
public virtual string DummyProp1 { get; set; }
[INPCAttribute]
public virtual string DummyProp2 { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
Which got me thinking even more.Well, if I can fire the INPC PropertyChanged
event using some aspect orientated programming, perhaps I could even write an aspect that would implement the System.ComponentModel.INotifyPropertyChanged
interface automatically for me, which would lead to code like this:
public class DummyModel
{
[AddINPCAttribute]
public virtual string DummyModelProp1 { get; set; }
[AddINPCAttribute]
public virtual string DummyModelProp2 { get; set; }
public virtual string DummyModelProp3 { get; set; }
}
OK, this would not suit every property and sometimes you would want to manually control when to fire the INPC event, or add more code in the setter of the property, but for 80%, auto firing INPC events may be more than suitable.
Largely, I have been very successful and managed just that, and that is what the rest of the article is going to talk about.
However, before we carry on, I just wanted to go back to something I mentioned earlier, where I said that I tried to make all the different AOP frameworks do the same thing. Well, I tried, but in some cases, I either lacked the motivation or the skills. I have chosen to evaluate four AOP frameworks, and these, along with what I did with them, is shown in the table below:
AOP Framework | What I Managed to Achieve |
Castle |
- Created a property targeting attribute, that when applied to a class that already implements
INotifyPropertyChanged will call the INotifyPropertyChanged PropertyChanged event when the property is set - Created a property targeting attribute, that when applied to a class that does not implement
INotifyPropertyChanged will automatically implement the INotifyPropertyChanged interface and will call the PropertyChanged event when the property with the attribute is set
|
Unity |
- Created a property targeting attribute, that when applied to a class that already implements
INotifyPropertyChanged will call the INotifyPropertyChanged PropertyChanged event when the property is set - Globally applied an interception behavior that will cause any intercepted property setter to fire the
INotifyPropertyChanged PropertyChanged event when the property is set, if the target type already implements INotifyPropertyChanged
|
PostSharp (not free) |
- Created a class targeting attribute, that when applied to a class that does not implement
INotifyPropertyChanged will automatically implement the INotifyPropertyChanged interface and will call the PropertyChanged event when a property is set
|
LinFu.AOP * |
- Created a property targeting attribute, that when applied to a class that already implements
INotifyPropertyChanged will call the INotifyPropertyChanged PropertyChanged event when the property is set
|
* Has dependency on Mono.Cecil
Digging Deep Into the Demo Code Examples
The next subsection will outline how the various AOP frameworks that I chose to look at do what I just discussed in the table above.
Castle (Proxy Based)
Price | Version Used | Available From |
Free | 2.5.0.0 | http://www.castleproject.org/ |
Castle is an IOC/Dependency Injection Container. Now recall what I said I managed to achieve using Castle.
Objective 1
Created a property targeting attribute, that when applied to a class that already implements INotifyPropertyChanged
will call the INotifyPropertyChanged
PropertyChanged
event when the property is set
Objective 2
Created a property targeting attribute, that when applied to a class that does not implement INotifyPropertyChanged
will automatically implement the INotifyPropertyChanged
interface and will call the PropertyChanged
event when the property with the attribute is set
So now, let's look at how we managed to achieve both of these objectives, starting with Objective 1.
Objective 1, Step 1: Create a INPCAttribute
The firsst step is straightforward; we simply create a standard .NET attribute as follows:
[AttributeUsage(AttributeTargets.Property)]
public class INPCAttribute : Attribute
{
}
It can be seen that nothing much happens in this code; it is just a marker that we will use to mark properties that will need to call the INPC PropertyChanged
event when set.
Objective 1, Step 2: Creating an INPC IInterceptor
In Castle, method inception (and remember properties are just methods get_xxxx
/set_xxxx
) is achieved using a special castle interface called IInterceptor
, which for objective 1 can be implemented as follows:
This interceptor is automatically applied to any Type in the ViewModel's namespace, by the ViewModelInstaller
(we will see that soon). When this interceptor is called, it will then examine the method (and property changes are just get_xxx()
/set_xxx()
methods after all), and look for a INPCAttribute
, which is a standard Attribute which if found will cause the method invocation to also fire the INPC PropertyChanged
event on the target object.
public class NotifyPropertyChangedInterceptor : IInterceptor
{
#region IInterceptor Implementation
public void Intercept(IInvocation invocation)
{
invocation.Proceed();
if (invocation.Method.Name.StartsWith("set_"))
{
string propertyName = invocation.Method.Name.Substring(4);
var pi = invocation.TargetType.GetProperty(propertyName);
if (!pi.HasAttribute<INPCAttribute>())
return;
FieldInfo info = invocation.TargetType.GetFields(
BindingFlags.Instance | BindingFlags.NonPublic)
.Where(f => f.FieldType == typeof(PropertyChangedEventHandler))
.FirstOrDefault();
if (info != null)
{
PropertyChangedEventHandler evHandler =
info.GetValue(invocation.InvocationTarget)
as PropertyChangedEventHandler;
if (evHandler != null)
evHandler.Invoke(invocation.TargetType,
new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
}
Objective 1, Step 3: Using the INPCAttribute on a Target Type
Now that we have a INPCAttribute
, all we need to do is apply this INPCAttribute
to some target type. In the demo app, this INPCAttribute
is applied to the MainWindowViewModel
type as follows:
public class MainWindowViewModel : INotifyPropertyChanged
{
#region Ctor
public MainWindowViewModel()
{
DummyModel = ContainerWiring.Instance.Container.Resolve<DummyModel>();
}
#endregion
#region Public Properties
[INPCAttribute]
public virtual string DummyProp1 { get; set; }
[INPCAttribute]
public virtual string DummyProp2 { get; set; }
public string DummyProp3 { get; set; }
public DummyModel DummyModel { get; set; }
#endregion
#region INPC Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
Objective 1, Step 4: Configuring Castle for Inception for MainWindowViewModel
Now that we have a type that makes use of the INPCAttribute
, we need to make sure Castle is configured to use it. This is done within the ContainerWiring
class as follows:
public class ContainerWiring
{
#region Data
private static readonly Lazy<ContainerWiring> instance
= new Lazy<ContainerWiring>(() => new ContainerWiring());
private IWindsorContainer container;
#endregion
#region Ctor
private ContainerWiring()
{
container = new WindsorContainer();
}
#endregion
#region Public Methods/Properties
public static ContainerWiring Instance
{
get
{
return instance.Value;
}
}
public IWindsorContainer Container
{
get { return container; }
}
public void SetUp()
{
container.Install(FromAssembly.This());
}
public void TearDown()
{
if (container != null)
{
container.Dispose();
}
container = null;
}
#endregion
}
Note: I am using a Lazy<T>
singleton approach here, which is my latest favourite way of doing singletons as it is both Lazy and thread safe, and reads well as well, and does not rely on any compiler related tricks.
Mmmm, no mention of any inception code there, just some Setup()
method... curious. What actually happens is that the Setup()
method is responsible for installing everything that the demo app requires. But what is that actually doing? Well, in Castle, you can inherit from another interface called IWindsorInstaller
which when implemented will do whatever you specified in your implementation, and will allow the IWindsorInstaller
implementing class to be installed into the Castle container.
The demo app features three installers, two of which I will talk about now, and one I will talk about later.
ViewModelInstaller
Will install inception for any Type that is in the same namespace as the MainWindowViewModel
, and that is how we apply inception to the MainWindowViewModel
Type we just saw above.
public class ViewModelInstaller : IWindsorInstaller
{
#region IWindsorInstaller Implementation
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(AllTypes.FromThisAssembly()
.Where(Castle.MicroKernel.Registration.Component.
IsInSameNamespaceAs<MainWindowViewModel>())
.Configure(c => c.LifeStyle.Transient
.Interceptors(typeof(NotifyPropertyChangedInterceptor))));
}
#endregion
}
InterceptorInstaller
Any custom interceptors must also be installed into the Castle container in order to, well, um, actually intercept anything. So here is the InterceptorInstaller
from the demo app.
public class InterceptorInstaller : IWindsorInstaller
{
#region IWindsorInstaller Implementation
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(AllTypes.FromThisAssembly().BasedOn<IInterceptor>());
}
#endregion
}
Objective 1, Step 5 : Use the MainWindowViewModel
So now we have all the pieces, we just need to make use of this intercepted type. So in the demo app, this is done within MainWindow
as follows:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainWindowViewModel viewModel =
ContainerWiring.Instance.Container.Resolve<MainWindowViewModel>();
this.DataContext = viewModel;
(viewModel as INotifyPropertyChanged).PropertyChanged +=
MainWindowViewModel_PropertyChanged;
}
private void MainWindowViewModel_PropertyChanged(object sender,
PropertyChangedEventArgs e)
{
MessageBox.Show(string.Format("{0} property changed " +
"in MainWindowViewModel", e.PropertyName));
}
}
You can see above that we get the inception enabled MainWindowViewModel
from the Castle container, and since the MainWindowViewModel
type makes use of the special INPCAttribute
, we should end up calling the INPC PropertyChanged
event whenever a property is set in the inception enabled MainWindowViewModel
Type.
And to prove that works, here is a screenshot of that running:
So that was all we had to do to meet Objective 1. Let's now have a look at Objective 2, which just looked like this:
Objective 2
Created a property targeting attribute, that when applied to a class that does not implement INotifyPropertyChanged
will automatically implement the INotifyPropertyChanged
interface and will call the PropertyChanged
event when the property with the attribute is set
Objective 2, Step 1 : Create a AddINPCAttribute
As with objective 1, this first step is straightforward enough; we simple create a standard .NET attribute as follows:
[AttributeUsage(AttributeTargets.Property)]
public class AddINPCAttribute : Attribute
{
}
It can be seen that nothing much happens in this code, it is just a marker that we will use to mark properties that will need to call the INPC PropertyChanged
event when set. But this time, the target type will be made to implement the INotifyPropertyChanged
interface directly by the Castle Container.
Objective 2, Step 2: Creating an Add INPC IInterceptor
As before, we inherit from the special Castle interface called IInterceptor
, but this time, the implementation is very different. We actually want to add the implementation of the INotifyPropertyChanged
interface on the target object.
So what we have is this:
public class AddNotifyPropertyChangedInterceptor : IInterceptor
{
#region Data
private PropertyChangedEventHandler handler;
#endregion
#region IInterceptor Implementation
public void Intercept(IInvocation invocation)
{
string methodName = invocation.Method.Name;
object[] arguments = invocation.Arguments;
object proxy = invocation.Proxy;
bool isINPC = false;
try
{
if (invocation.TargetType != null)
{
PropertyInfo realProp = invocation.TargetType.
GetProperty(invocation.Method.Name.Substring(4));
isINPC = realProp.HasAttribute<AddINPCAttribute>();
}
}
catch { }
if (invocation.Method.DeclaringType.Equals(typeof(INotifyPropertyChanged)))
{
if (methodName == "add_PropertyChanged")
StoreHandler((Delegate)arguments[0]);
if (methodName == "remove_PropertyChanged")
RemoveHandler((Delegate)arguments[0]);
}
if (!ShouldProceedWithInvocation(methodName))
return;
invocation.Proceed();
if (isINPC)
NotifyPropertyChanged(methodName, proxy);
}
#endregion
#region Protected Methods
protected void OnPropertyChanged(Object sender, PropertyChangedEventArgs e)
{
var eventHandler = handler;
if (eventHandler != null) eventHandler(sender, e);
}
protected void RemoveHandler(Delegate @delegate)
{
handler = (PropertyChangedEventHandler)Delegate.Remove(handler, @delegate);
}
protected void StoreHandler(Delegate @delegate)
{
handler = (PropertyChangedEventHandler)Delegate.Combine(handler, @delegate);
}
protected void NotifyPropertyChanged(string methodName, object proxy)
{
if (methodName.StartsWith("set_"))
{
var propertyName = methodName.Substring(4);
var args = new PropertyChangedEventArgs(propertyName);
OnPropertyChanged(proxy, args);
}
}
protected bool ShouldProceedWithInvocation(string methodName)
{
var methodsWithoutTarget = new[] {
"add_PropertyChanged", "remove_PropertyChanged" };
return !methodsWithoutTarget.Contains(methodName);
}
#endregion
}
It can be seen that we handle Add/Remove of delegates for the INotifyPropertyChanged
interface implementation, and we also raise the INPC PropertyChanged
event, when a property is set that has our special AddINPCAttribute
.
Objective 2, Step 3: Using the AddINPCAttribute on a Target Type
Now that we have a AddINPCAttribute
, all we need to do is apply this AddINPCAttribute
to some target type. In the demo app, this AddINPCAttribute
is applied to the DummyModel
type as follows:
public class DummyModel
{
[AddINPCAttribute]
public virtual string DummyModelProp1 { get; set; }
[AddINPCAttribute]
public virtual string DummyModelProp2 { get; set; }
public virtual string DummyModelProp3 { get; set; }
}
Notice how this Type does not implement the INotifyPropertyChanged
interface at all.
Objective 2, Step 4: Configuring Castle for Inception for DummyModel
Now that we have a type that makes use of the AddINPCAttribute
, we need to make sure Castle is configured to use it. This is done within the ContainerWiring
class that we saw previously.
public class ContainerWiring
{
#region Data
private static readonly Lazy<ContainerWiring> instance
= new Lazy<ContainerWiring>(() => new ContainerWiring());
private IWindsorContainer container;
#endregion
#region Ctor
private ContainerWiring()
{
container = new WindsorContainer();
}
#endregion
#region Public Methods/Properties
public static ContainerWiring Instance
{
get
{
return instance.Value;
}
}
public IWindsorContainer Container
{
get { return container; }
}
public void SetUp()
{
container.Install(FromAssembly.This());
}
public void TearDown()
{
if (container != null)
{
container.Dispose();
}
container = null;
}
#endregion
}
And as I previously stated, most of the inception code is actually done via IWindsorInstaller
, and I already discussed 2 out of 3 of these from the demo app; we just need to discuss the last one, which adds the AddNotifyPropertyChangedInterceptor
to the DummyModel
type. This is done as follows:
ModelInstaller
Will install inception for any Type that is in the same namespace as DummYModel
, and that is how we apply inception to the DummyModel
Type we just saw above.
public class ModelInstaller : IWindsorInstaller
{
#region IWindsorInstaller Implementation
public void Install(IWindsorContainer container, IConfigurationStore store)
{
container.Register(AllTypes.FromThisAssembly()
.Where(Castle.MicroKernel.Registration.Component.
IsInSameNamespaceAs<DummyModel>())
.Configure(c => c.LifeStyle.Transient
.Proxy.AdditionalInterfaces(typeof(INotifyPropertyChanged))
.Interceptors(typeof(AddNotifyPropertyChangedInterceptor))));
}
#endregion
}
Objective 2, Step 5: Use the DummyModel
So now we have all the pieces; we just need to make use of this intercepted type. So in the demo app, this is done within MainWindowViewModel
which holds an instance of a DummyModel
:
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
DummyModel = ContainerWiring.Instance.Container.Resolve<DummyModel>();
}
.......
.......
.......
public DummyModel DummyModel { get; set; }
.......
.......
.......
}
Which we can listen to INPC PropertyChanged
event notification from, in the MainWindow
code-behind, as follows:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainWindowViewModel viewModel =
ContainerWiring.Instance.Container.Resolve<MainWindowViewModel>();
this.DataContext = viewModel;
(viewModel as INotifyPropertyChanged).PropertyChanged +=
MainWindowViewModel_PropertyChanged;
(viewModel.DummyModel as INotifyPropertyChanged).PropertyChanged +=
DummyModel_PropertyChanged;
}
private void MainWindowViewModel_PropertyChanged(object sender,
PropertyChangedEventArgs e)
{
MessageBox.Show(string.Format("{0} property changed " +
"in MainWindowViewModel", e.PropertyName));
}
private void DummyModel_PropertyChanged(object sender,
PropertyChangedEventArgs e)
{
MessageBox.Show(string.Format("{0} property changed " +
"in DummyModel", e.PropertyName));
}
}
And to prove that works, here is a screenshot of that running:
Unity (Proxy Based)
Price | Version Used | Available From |
Free | 2.0.414.0 | Part of Enterprise Library 5.0 |
Unity is an IOC/Dependency Injection Container that comes as a standalone application block, but I am using the one that comes with Enterprise Library 5.0.
Now recall what I said I managed to achieve using Unity.
Objective 1
Created a property targeting attribute, that when applied to a class that already implements INotifyPropertyChanged
will call the INotifyPropertyChanged
PropertyChanged
event when the property is set
Objective 2
Globally applied an interception behavior that will cause any intercepted property setter to fire the INotifyPropertyChanged
PropertyChanged
event when the property is set, if the target type already implements INotifyPropertyChanged
.
So now, let's look at how we managed to achieve both of these objectives, starting with Objective 1.
Objective 1, Step 1: Create a Special Inception Enabled Attribute
In Unity, inception can be enabled in a few different ways, but one common way is to inherit from a special Unity attribute, called the HandlerAttribute
. HandlerAttribute
is an attribute that when inherited from will allow your custom attributes to potentially inject aspects into the method call pipeline. The demo app uses this INPCAttribute
which inherits from HandlerAttribute
:
[AttributeUsage(AttributeTargets.Property)]
public class INPCAttribute : HandlerAttribute
{
public override ICallHandler CreateHandler(IUnityContainer container)
{
return new INPCHandler();
}
}
It can be seen that nothing much happens in this code; this code creates another Type, called INPCHandler
. So that must be what we should look at next.
Objective 1, Step 2: Creating a Handler for the INPCAttribute
As we just saw, we have a special INPCAttribute
which creates a INPCHandler
. So what does a INPCHandler
look like?
public class INPCHandler : ICallHandler
{
#region ICallHandler Members
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext)
{
IMethodReturn result = getNext()(input, getNext);
if (input.MethodBase.Name.StartsWith("set_"))
{
string propertyName = input.MethodBase.Name.Substring(4);
var pi = input.Target.GetType().GetProperty(propertyName);
if (pi.HasAttribute<INPCAttribute>())
{
FieldInfo info = input.Target.GetType().BaseType.GetFields(
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy)
.Where(f => f.FieldType == typeof(PropertyChangedEventHandler))
.FirstOrDefault();
if (info != null)
{
PropertyChangedEventHandler evHandler =
info.GetValue(input.Target) as PropertyChangedEventHandler;
if (evHandler != null)
evHandler.Invoke(input.Target.GetType(),
new PropertyChangedEventArgs(propertyName));
}
}
}
return result;
}
public int Order
{
get
{
return 0;
}
set
{
}
}
#endregion
}
It can be seen above that this INPCHandler
also inherits from a Unity interface called ICallHandler
. It is actually possible using Unity to apply handlers that implement ICallHandler
directly. But in this example, the INPCHandler
was created by applying the INPCAttribute
to some target type. So what does implementing ICallHandler
give us? Well, basically, inheriting from ICallHandler
gives us the correct pipeline hook to the actual code out aspect. Any ICallHandler
implementation will be called at the appropriate time, providing we have configured Unity correctly.
It can also be seen from the above code that we are looking to see if we are in a property setter, and if we are, we check to see if the property we are setting has the special INPCAttribute
on it, and if it does, we know we need to call the INPC PropertyChanged
event. So we just call it.
Objective 1, Step 3: Using the Special INPCAttribute on a Target Type
Now that we have a INPCAttribute
that we can apply that internally makes sure we get a INPCHandler
based ICallHandler
class, all we need to do is apply this INPCAttribute
to some target type. In the demo app, this INPCAttribute
is applied to the MainWindowViewModel
type as follows:
public class MainWindowViewModel : INotifyPropertyChanged
{
#region Ctor
public MainWindowViewModel()
{
DummyModel = ContainerWiring.Instance.Container.Resolve<DummyModel>();
}
#endregion
#region Public Properties
[INPC]
public virtual string DummyProp1 { get; set; }
[INPC]
public virtual string DummyProp2 { get; set; }
public string DummyProp3 { get; set; }
public DummyModel DummyModel { get; set; }
#endregion
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyChanged(params string[] propertyNames)
{
foreach (string name in propertyNames)
{
OnPropertyChanged(new PropertyChangedEventArgs(name));
}
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, e);
}
}
#endregion
}
Objective 1, Step 4: Configuring Unity for Inception for MainWindowViewModel
Now that we have a type that makes use of the INPCAttribute
, we need to make sure Unity is configured to use it. This is done within the ContainerWiring
class as follows:
public class ContainerWiring
{
#region Data
private static readonly Lazy<ContainerWiring> instance
= new Lazy<ContainerWiring>(() => new ContainerWiring());
private IUnityContainer container;
#endregion
#region Ctor
private ContainerWiring()
{
container = new UnityContainer();
}
#endregion
#region Public Methods/Properties
public static ContainerWiring Instance
{
get
{
return instance.Value;
}
}
public IUnityContainer Container
{
get { return container; }
}
public void SetUp()
{
container.AddNewExtension<Interception>();
container.RegisterType<MainWindowViewModel>();
container.RegisterType<DummyModel>();
PolicyDefinition policy = container.Configure<Interception>().
SetInterceptorFor<DummyModel>(new VirtualMethodInterceptor()).
SetInterceptorFor<MainWindowViewModel>(
new VirtualMethodInterceptor()).AddPolicy("NotifyPolicy");
}
public void TearDown()
{
if (container != null)
{
container.Dispose();
}
container = null;
}
#endregion
}
Objective 1, Step 5: Use the MainWindowViewModel
Now we have all the pieces, we just need to make use of this intercepted type. In the demo app, this is done within MainWindow
as follows:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainWindowViewModel viewModel =
ContainerWiring.Instance.Container.Resolve<MainWindowViewModel>();
this.DataContext = viewModel;
(viewModel as INotifyPropertyChanged).PropertyChanged +=
MainWindowViewModel_PropertyChanged;
}
private void MainWindowViewModel_PropertyChanged(object sender,
PropertyChangedEventArgs e)
{
string extraInfo = "Note as we are using an HandlerAttribute " +
"and a blanket PolicyDefinition \r\n " +
"of * we will get 2 * INPC events for MainWindowViewModel, " +
"But I am keeping this in to show you the options";
MessageBox.Show(string.Format(
"{0} property changed in MainWindowViewModel\r\n\r\n{1}",
e.PropertyName, extraInfo));
}
}
You can see above that we get the inception enabled MainWindowViewModel
from the UnityContainer, and since the MainWindowViewModel
type makes use of the special INPCAttribute
, we should end up calling the INPC PropertyChanged
event whenever a property is set in the inception enabled MainWindowViewModel
Type.
And to prove that works, here is a screenshot of that running:
So that was all we had to do to meet Objective 1. Let's now have a look at Objective 2, which looked like this:
Objective 2
Globally applied an interception behavior that will cause any intercepted property setter to fire the INotifyPropertyChanged
PropertyChanged
event when the property is set, if the target type already implements INotifyPropertyChanged
Objective 2, Step 1: Creating a Global INPC Handler
So this time the objective is to create a global handler that can be applied to an entire class, and will automatically call the INPC PropertyChanged
event whenever a property is set. So for that, we need to implement another Unity ICallHandler
which is shown below. Note: this time, there is no checking for any special attribute as soon as we realize we are setting a property, and attempt is made to call the target type's INPC PropertyChanged
event.
public class NonAttributedINPCHandler : ICallHandler
{
#region ICallHandler Members
public IMethodReturn Invoke(IMethodInvocation input,
GetNextHandlerDelegate getNext)
{
IMethodReturn result = getNext()(input, getNext);
if (input.MethodBase.Name.StartsWith("set_"))
{
string propertyName = input.MethodBase.Name.Substring(4);
var pi = input.Target.GetType().GetProperty(propertyName);
FieldInfo info = input.Target.GetType().BaseType.GetFields(
BindingFlags.Instance | BindingFlags.NonPublic |
BindingFlags.FlattenHierarchy).Where(
f => f.FieldType ==
typeof(PropertyChangedEventHandler)).FirstOrDefault();
if (info != null)
{
PropertyChangedEventHandler evHandler = #
info.GetValue(input.Target) as PropertyChangedEventHandler;
if (evHandler != null)
evHandler.Invoke(input.Target.GetType(),
new PropertyChangedEventArgs(propertyName));
}
}
return result;
}
public int Order
{
get
{
return 0;
}
set
{
}
}
#endregion
}
It can be seen above that this NonAttributedINPCHandler
also inherits from a Unity interface called ICallHandler
. And for this implementation, we intend to apply this globally to a certain target type, by providing Unity with the configuration information to apply it automatically to any property change.
Objective 2, Step 2: We Need a Target Type to Use This Global ICallHandler on
So we have a global ICallHandler
that we can apply in Unity whenever a property is set, but we still need a target type to apply it on to. So what does that target type look like? Well, for the demo code, it looks like this:
public class DummyModel : INotifyPropertyChanged
{
public virtual string DummyModelProp1 { get; set; }
public virtual string DummyModelProp2 { get; set; }
public string DummyModelProp3 { get; set; }
#region INotifyPropertyChanged Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void NotifyChanged(params string[] propertyNames)
{
foreach (string name in propertyNames)
{
OnPropertyChanged(new PropertyChangedEventArgs(name));
}
}
protected virtual void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, e);
}
}
#endregion
}
Objective 2, Step 3: Configuring Unity for Inception for DummyModel
Now that we have a type that we want to apply the NonAttributedINPCHandler
to, we need to make sure Unity is configured to use it. This is done within the ContainerWiring
class as follows:
public class ContainerWiring
{
#region Data
private static readonly Lazy<ContainerWiring> instance
= new Lazy<ContainerWiring>(() => new ContainerWiring());
private IUnityContainer container;
#endregion
#region Ctor
private ContainerWiring()
{
container = new UnityContainer();
}
#endregion
#region Public Methods/Properties
public static ContainerWiring Instance
{
get
{
return instance.Value;
}
}
public IUnityContainer Container
{
get { return container; }
}
public void SetUp()
{
container.AddNewExtension<Interception>();
container.RegisterType<MainWindowViewModel>();
container.RegisterType<DummyModel>();
PolicyDefinition policy = container.Configure<Interception>().
SetInterceptorFor<DummyModel>(new VirtualMethodInterceptor()).
SetInterceptorFor<MainWindowViewModel>(
new VirtualMethodInterceptor()).AddPolicy("NotifyPolicy");
policy.AddMatchingRule(new PropertyMatchingRule("*",
PropertyMatchingOption.Set));
policy.AddCallHandler<NonAttributedINPCHandler>();
}
public void TearDown()
{
if (container != null)
{
container.Dispose();
}
container = null;
}
#endregion
}
Note: that is the entire listing for the ContainerWiring
class, so it also includes the previous Unity setup we saw for MainWindowViewModel
for objective 1. See how this time we use an instance of the Unity PolicyDefinition
, and we specify that any property setter should be matched, and we add a call handler of our NonAttributedINPCHandler
handler, which calls the INPC PropertyChanged
event on the target object within the caller.
Now, one interesting thing of note here is that, because the MainWindowViewModel
also has properties that can be set, it is also included in this blanket setter policy, but it also makes use of the INPCAttribute
on two of its properties which we talked about above. Which means when changing either of these two MainWindowViewModel
properties that also has the INPCAttribute
applied, we will get two INPC PropertyChanged
event notifications. One for the INPCAttribute
, and one from the globally applied NonAttributedINPCHandler
.
For the DummyModel
type, this is not the case, as it only uses the blanket setter policy, so only that rule applies.
Objective 2, Step 4: Use the DummyModel
OK, so now that we have this DummyModel
type that will have a global Unity ICallHandler
applied to it, we just need to use one of these DummyModel
classes somewhere. For the demo app, the MainWindowViewModel
(itself set for inception) uses a DummyModel
as a property as follows:
public class MainWindowViewModel : INotifyPropertyChanged
{
public MainWindowViewModel()
{
DummyModel = ContainerWiring.Instance.Container.Resolve<DummyModel>();
}
......
......
......
public DummyModel DummyModel { get; set; }
......
......
......
......
......
}
So we can also listen to INPC PropertyChanged
events from a MainWindowViewModel
's DummyModel
instance, as follows, in the MainWindow
code-behind:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainWindowViewModel viewModel =
ContainerWiring.Instance.Container.Resolve<MainWindowViewModel>();
this.DataContext = viewModel;
(viewModel.DummyModel as INotifyPropertyChanged).PropertyChanged +=
DummyModel_PropertyChanged;
}
private void DummyModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
MessageBox.Show(string.Format("{0} property changed " +
"in DummyModel", e.PropertyName));
}
}
And here is a screenshot to show that working:
PostSharp (IL Weaving)
Price | Version Used | Available From |
£120-£220 | 2.0.0.0 | http://www.sharpcrafters.com/postsharp/download
Has a 45 day free trial
|
Postsharp is a premier league AOP framework, arguably the most mature one on the market, and it is very, very feature rich; some of you may recall that it also used to be free. Unfortunately, all good things come to an end, and now one has to pay for PostSharp, not that much though.
Anyway, if you recall what I said I managed to achieve with PostSharp:
Created a class targeting attribute, that when applied to a class that does not implement INotifyPropertyChanged
will automatically implement the INotifyPropertyChanged
interface and will call the PropertyChanged
event when a property is set.
All that I need to do to achieve this is carry out a few steps.
Step 1: Reference PostSharp
Add a reference to PostSharp.dll.
Step 2: Create an Object We Want to Make INPC
For the attached demo app, this looks like this where I am using a special INPC aspect class which is discussed in Step3. Notice below that I am not implementing INPC at all, nor do any of the properties have any attributes on them, and also note that the properties are standard auto properties, and that they are not virtual.
[INPC]
public class MainWindowViewModel
{
#region Public Properties
public string DummyProp1 { get; set; }
public string DummyProp2 { get; set; }
public string DummyProp3 { get; set; }
#endregion
}
Step 3: Implement the INPC Aspect
This is where the real work comes; we now have to implement the actual INPC aspect class, which looks like this:
[Serializable]
[IntroduceInterface(typeof(INotifyPropertyChanged),
OverrideAction = InterfaceOverrideAction.Ignore)]
[MulticastAttributeUsage(MulticastTargets.Class,
Inheritance = MulticastInheritance.Strict)]
public sealed class INPCAttribute : InstanceLevelAspect, INotifyPropertyChanged
{
#region Public Properties / Methods
[ImportMember("OnPropertyChanged", IsRequired = false,
Order = ImportMemberOrder.AfterIntroductions)]
public Action<string> OnPropertyChangedMethod;
[IntroduceMember(Visibility = Visibility.Family,
IsVirtual = true, OverrideAction = MemberOverrideAction.Ignore)]
public void OnPropertyChanged(string propertyName)
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this.Instance,
new PropertyChangedEventArgs(propertyName));
}
}
[IntroduceMember(OverrideAction = MemberOverrideAction.Ignore)]
public event PropertyChangedEventHandler PropertyChanged;
[OnLocationSetValueAdvice, MulticastPointcut(Targets = MulticastTargets.Property,
Attributes = MulticastAttributes.Instance | MulticastAttributes.NonAbstract)]
public void OnPropertySet(LocationInterceptionArgs args)
{
if (args.Value == args.GetCurrentValue()) return;
args.ProceedSetValue();
this.OnPropertyChangedMethod.Invoke(args.Location.Name);
}
#endregion
Now there is a lot going on there, but none of it is beyond us, so let's break it down a bit.
It can be seen below that this aspect not only calls NotifyPropertyChanged
whenever a property is set, but also introduces an actual implementation of the INotifyPropertyChanged
interface, and also introduces all the events and methods that are required by a INotifyPropertyChanged
implementation. This is mainly achieved using the IntroduceMemberAttribute
.
And that is pretty much all there is to it. If we run the demo where we have the MainWindow
set its DataContext
to a MainWindowViewModel
, we can indeed see that the MainWindowViewModel
is implementing INPC correctly.
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
MainWindowViewModel viewModel = new MainWindowViewModel();
this.DataContext = viewModel;
(viewModel as INotifyPropertyChanged).PropertyChanged +=
MainWindowViewModel_PropertyChanged;
}
private void MainWindowViewModel_PropertyChanged(object sender,
PropertyChangedEventArgs e)
{
string extraInfo =
"Note as we are using an PostSharp, we simply not " +
"even have to have virtual properties in our MainWindowViewModel";
MessageBox.Show(string.Format(
"{0} property changed in MainWindowViewModel\r\n\r\n{1}",
e.PropertyName, extraInfo));
}
}
LinFu.AOP (IL Weaving)
As LinFu.AOP internally relies on the Mono.Cecil DLL that I mentioned right back at the start of the article, I thought I would just go through a quick example of how Mono.Cecil works.
How Mono Cecil Works
Suppose I have a type that already implements INPC, and that we have a standard INPC property such as FirstName
shown below:
And we also have an auto property LastName
shown below, which as we can see has some compiler generated bits to it.
And we examine the IL for the StartName
full INPC property:
Compared to the IL for the auto generated LastName
property:
we can see that they are nearly the same, but we are just missing a few IL instructions:
So if we could somehow manage to get in there and introduce these IL instructions, and save the Assembly, both the full INPC property and the auto property should work the same. That is exactly what Mono.Cecil allows us to do, and here is some example code to do just that:
CilWorker MSILWorker = prop.SetMethod.Body.CilWorker;
Instruction ldarg0 = MSILWorker.Create(OpCodes.Ldarg_0);
Instruction propertyName = MSILWorker.Create(OpCodes.Ldstr, prop.Name);
Instruction callRaisePropertyChanged =
MSILWorker.Create(OpCodes.Call, raisePropertyChanged);
MSILWorker.InsertBefore(prop.SetMethod.Body.Instructions[0],
MSILWorker.Create(OpCodes.Nop));
MSILWorker.InsertBefore(
prop.SetMethod.Body.Instructions[
prop.SetMethod.Body.Instructions.Count - 1],ldarg0);
MSILWorker.InsertAfter(ldarg0, propertyName);
MSILWorker.InsertAfter(propertyName, callRaisePropertyChanged);
MSILWorker.InsertAfter(callRaisePropertyChanged, MSILWorker.Create(OpCodes.Nop));
Obviously, LinFu.AOP does a lot more than this, but it does use Cecil to do it. Which generally works as shown above. I will obviously outline exactly how LinFu does work in much finer detail, but I just felt that this little diversion was worth while.
OK, so now back to LinFu.
How LinFu Works
Anyway, if you recall what I said I managed to achieve with LinFu:
Created a property targeting attribute, that when applied to a class that already implements INotifyPropertyChanged
will call the INotifyPropertyChanged
PropertyChanged
event when the property is set.
All that I need to do to achieve this is carry out a few steps.
Step 1: Create the Aspect Intercepted Project
Unfortunately, I could not seem to get LinFu to work unless I split the project where aspects were to be woven and the aspects themselves into two projects. So I created a project which stored types that would be incepted, and made sure I referenced the correct LinFu DLLs.
Step 2: Edit the MSBuild File for the Aspect Intercepted Project
The next thing I had to do was unload the project with the types that would have the aspects woven into them. For the demo app, this means the LinFu.ViewModels project.
Then I had to add the following lines to the MSBUILD file for the project:
<PropertyGroup>
<PostWeaveTaskLocation>
C:\Users\WIN7LAP001\Desktop\Downloads\LinFu_Src\LinFu.Aop.Tasks.dll
</PostWeaveTaskLocation>
</PropertyGroup>
<UsingTask TaskName="PostWeaveTask" AssemblyFile="$(PostWeaveTaskLocation)" />
<Target Name="AfterBuild" Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU'">
<PostWeaveTask
TargetFile="$(MSBuildProjectDirectory)\
$(OutputPath)$(MSBuildProjectName).dll"
InjectConstructors="true" />
</Target>
This step is vital, as it points to the PostWeaver Task that LinFu uses to inject the aspects. After you have added this and made it to point to the correct location on your installation, you can reload the project.
Step 3: Marking Up a Class That You Want to be INPC
This is the easy part; we simply need to come up with a simple Attribute that we can use as a marker that is applied to properties, that we can later examine to see if our aspect should fire the PropertyChanged
event of the intercepted type.
This attribute simple looks like this:
[AttributeUsage(AttributeTargets.Property)]
public class INPCAttribute : Attribute
{
}
Step 3: Using the INPCAttribute on a Target Type
The next step is to actually use this INPCAttribute
on a target type. For the LinFu demo, this will be a MainWindowViewModel
type that simply looks like this:
public class MainWindowViewModel : INotifyPropertyChanged
{
#region Public Properties
[INPCAttribute]
public string DummyProp1 { get; set; }
[INPCAttribute]
public string DummyProp2 { get; set; }
public string DummyProp3 { get; set; }
#endregion
#region INPC Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
You will notice that for this class, we do actually implement the INotifyPropertyChanged
interface, so we are only expecting the LinFu aspect to call the INotifyPropertyChanged
interface's PropertyChanged
event that is already available within the MainWindowViewModel
.
That is all there is to this first LinFu AOP demo project, but next, we need to look at the actual project that contains the aspect. So let's carry on and look at that, shall we?
Step 4: Create the Aspect Weaving Project
As I mentioned in Step 1, I could not seem to get LinFu to work unless I split the project where aspects were to be woven and the aspects themselves into two projects. So I created a project which stored types that would be incepted. We have already created the first of these projects, which is the project which stored types that would be incepted; now we need to create the project that does the actual aspects and will produce the IL woven code.
Let's have a look at this other project. It is a WPF application that needs the following references:
Where the LinFu.ViewModels
reference is the project we created in Step 1.
Step 5: Create the Inception Code That Will Produce the Modified IL
LinFu uses interfaces for aspects, and has the special interface IInvokeAround
which you can implement to have your aspect run when you call a method/property (properties really are methods like get_XXXX
/set_XXXX
). So what we want is an aspect that will call the INotifyPropertyChanged
on a target type which implements the INotifyPropertyChanged
PropertyChanged
event when a property is set.
Here is the full code for the LinFu.AOP IInvokeAround
implementing inception code (it is quite similar to the Castle/Unity code):
public class INPCMethodInvocation : IAroundInvoke
{
#region IAroundInvoke Members
public void AfterInvoke(IInvocationContext context, object returnValue)
{
if (context.TargetMethod.Name.StartsWith("set_"))
{
string propertyName = context.TargetMethod.Name.Substring(4);
var pi = context.Target.GetType().GetProperty(propertyName);
if (!pi.HasAttribute<INPCAttribute>())
return;
FieldInfo info = context.Target.GetType().GetFields(
BindingFlags.Instance | BindingFlags.NonPublic)
.Where(f => f.FieldType == typeof(PropertyChangedEventHandler))
.FirstOrDefault();
if (info != null)
{
PropertyChangedEventHandler evHandler =
info.GetValue(context.Target) as PropertyChangedEventHandler;
if (evHandler != null)
evHandler.Invoke(context.Target.GetType(),
new PropertyChangedEventArgs(propertyName));
}
}
}
public void BeforeInvoke(IInvocationContext context)
{
}
#endregion
}
What then happens in LinFu is that when compilation occurs, the LinFu TaskWeaver
MSBUILD task will be run, which will examine any type that has Inception allowed, and will then use the existing type's method IL, and will then grab the IAroundInvoke
implementation's XXXXInvoke
code and get the IL for that, and it will then form a new Instruction list of IL internally to LinFu, and it will then use the Mono.Cecil DLL's IL Rewriting capabilities to rewrite the Assembly
to contain the original method's IL plus the IAroundInvoke
implementation's XXXXInvoke
IL. So in the case of IAroundInvoke
implementation's AfterInvoke(...)
, we would end up with the original method's IL plus the IAroundInvoke
implementation's AfterInvoke
IL being emitted to the modified Assembly IL.
Remember, all of this is happening at compile time; it is not a runtime thing; the Assembly is actually modified to have new IL woven into it. This is what Mono.Cecil allows. And that is what LinFu uses internally.
Step 6: Applying the Aspect to a Target Type
All is good so far; we have an aspect which we can apply to a type, we now just need to apply it. Here is how we do that with LinFu.AOP (note: I am using the same Lazy<T>
type singleton that I showed earlier). Let's see how we wire up inception in LinFu. This is done in the demo app in a single class called Inception
, nothing special about that name, it's just one I picked, and here is the full code for that class:
public class Inception
{
#region Data
private static readonly Lazy<Inception> instance
= new Lazy<Inception>(() => new Inception());
#endregion
#region Ctor
private Inception()
{
}
#endregion
#region Public Methods/Properties
public static Inception Instance
{
get
{
return instance.Value;
}
}
public void SetUp()
{
var mainWindowViewModelProvider =
new SimpleAroundInvokeProvider(new INPCMethodInvocation(),
c => c.TargetMethod.DeclaringType == typeof(MainWindowViewModel));
AroundInvokeRegistry.Providers.Add(mainWindowViewModelProvider);
}
#endregion
}
All we do is set up a new INPCMethodInvocation
interceptor which we created in step 5, and apply that to the MainWindowViewModel
type. Quite simple really... right?
Step 7: Using the Aspected Type
Now that we have all the pieces of the puzzle, all we need to do is get ourselves one of these intercepted MainWindowViewModel
types, and make sure it works.
So here is how we do that:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
var viewModel = new MainWindowViewModel();
this.DataContext = viewModel;
(viewModel as INotifyPropertyChanged).PropertyChanged +=
MainWindowViewModel_PropertyChanged;
viewModel.EnableInterception();
}
private void MainWindowViewModel_PropertyChanged(object sender,
PropertyChangedEventArgs e)
{
MessageBox.Show(string.Format("{0} property changed " +
"in MainWindowViewModel", e.PropertyName));
}
}
And to prove it all works, here is a screenshot:
Other Aspects
In this article, I have really just concentrated on performing one or two INPC based aspects, but I hope you can all see how you could roll your own aspects, such as a logging aspect where method calls are logged to a file, or a thread aspect where a call to the method would be marshaled to the UI thread, or even to throw an Exception if the current user's IWindowsPrincipal
object does not allow them access to a certain method etc.
That's It
This article took quite a bit of time to research and a bit of trial and error, so if you liked this article and think it could be useful for you, or has maybe just given you some idea of how aspects could work for you, could you maybe leave a comment/vote?
Many thanks folks... Sacha.