Bringing AOP to MEF
- Download AspectableMef.Castle (What this article discusses)
- Download AspectableMef (Alternative framework using LinFu.AOP. Be warned doesn't like ICommand though)
Table Of Contents
- Introduction
- MEF
- Aspects
- Aspectable MEF
- Example Aspects I Have Created
- Writing Your Own Aspects
- Words Of Warning
- Special Thanks
- That's It
Introduction
A while back I wrote an article which talked about different aspect orientated programming (AOP) approaches, this article is available here and if you have no idea about AOP that would not be a bad article to read before I dive into this one.
Anyway in that article I did a lot of the ground work that I needed for this article in fact this article could almost be thought of as Part II of that article.
So what's different in this article?
Well, in this article what I wanted to do, was to see how easy it would be to come up with a generic framework that would allow aspects to be added to types that could then be used with MEF. Ok aspects can quite easily be added to any type, but there is a certain amount of plumbing that one must do in order to provide apsects for a given type, that is just a fact, no matter what AOP framework you go for its tedious. So in essense this article is all about an idea I had to dumb down this process, and hopefully it will make your life easier, and enable you to work with both aspects and MEF with a minimum of effort.
MEF
Managed Extensibility
Framework (MEF) is essentially an
IOC container, that allows dependencies to
be resolved from the MEF
container (CompositionContainer
) via the use of a set of
MEF attributes. Say you have a
ClassA
that needs
to be constructed with an instance of ClassB
, you may have something like this
(note the use of the [Export]
and [ImportingConstructor]
and
[PartCreationPolicy]
MEF attributes.
using System.ComponentModel.Composition;
using System;
namespace MefDemo
{
[PartCreationPolicy(CreationPolicy.NonShared)]
[Export(typeof(DemoClass)]
public class ClassB
{
}
}
using System.ComponentModel.Composition;
using System;
namespace MefDemo
{
[PartCreationPolicy(CreationPolicy.NonShared)]
[Export]
public class ClassA
{
[ImportingConstructor]
public Program(ClassB demoClass)
{
}
}
}
This diagram may help you understand how MEF works on a high level.
To truly get an understanding of how MEF works would take me many many articles, which is outside the scope of this article. If you do not know how MEF works, I would ask that you read this MEF programming guide
Aspects
When I first started this article I was already aware of a few freely available AOP frameworks like
- Castle Dynamic Proxy uses Proxy technology
- Microsofts Unity IOC Container uses Proxy technology
- LinFu.AOP, uses IL Weaving
- PostSharp (not free), uses IL Weaving
These all offer different flavours of AOP.
To understand the difference between the different approaches lets consider the following 2 subsections
Proxy AOP
With the AOP frameworks that use proxy based technologies, the way that
aspects are enabled is by a proxy. The proxy typically inherits from the type
you wish to add the aspects to, and the proxy is usually a dynamic object that
is written out using Reflection.Emit
APIs. These proxy technology
AOP frameworks rely on any property/method that aspects are to be added to be
marked as virtual.
They also rely on extra classes that implement certain vendor specific AOP interfaces. These extra classes typically get added to the proxied object, such that whenever a method/property is called on the proxy object it will 1st examine a list of AOP classes and run those prior to calling the original method.
The problem with this approach is that methods/properties MUST be marked as virtual.
This diagram illustrates how a proxy AOP framework may work
IL Weaving
IL Weaving is a completely different story and the story goes something like this:
AssemblyA is a type but as well as AssemblyA there are also a bunch of vendor specific AOP classes written. At compile time AssemblyA has any type that is a candidate for AOP literally rewritten to produce new IL for that type in AssemblyA.
This is really a better option, and would be my preferred way of doing things, as your methods/properties do not have to be virtual at all, you just write your code and let the AOP IL rewiting process do the rest.
Problem with this approach is that it is hard, and I know of only 2 frameworks that do this, LinFu.AOP and postSharp (which costs loads).
I had originally written most of this article using
LinFu.AOP, when I
got to the final hurdle I introduced a ICommand
(based on delegates)
and LinFu.AOP
died horribly and actually created an invalid Assembly. That is not to say
LinFu.AOP is bad,
I consider the author of
LinFu.AOP a
freekin genius, it just did not like ICommand
(based on delegates)
objects, so if that is ok with you use
LinFu.AOP, in
fact Castle
Dynamic Proxy does not allow AOP to be added to ICommand
(based
on delegates) objects either, but it did not die as a result. Now there is a new
version of
LinFu.AOP out
which may address this issue, however I played with that too, and had other
issues. Given some time I am confident the
LinFu.AOP author
will sort these issues out, he is the bomb.
I have included in the downloads a complete working sample based on
LinFu.AOP, the
only caveat, is that I found it did not work with ICommand
(based on delegates) objects. Which sadly was enough to sway me to use Castle
Dynamic Proxy
If you want to mess around with the solution you will need to 1st unload the project "AspectableTypes" and edit the path to the PostWeaveTaskLocation within the project file, and then reload the project. You will be looking for a line like this
<PostWeaveTaskLocation>C:\Users\WIN7LAP001\Desktop\In Progress\AspectableMef\AspectableMef\Lib\LinFu.Aop.Tasks.dll</PostWeaveTaskLocation>
Anyway if you want to look at an IL AOP version of this article, its included in the downloads at the top of this article.
Here is how a IL Weaving AOP framework works
NOTE :
The rest of this article will be using a proxy based solution, namely Castle Dynamic Proxy which I have integrated in order to create the code that goes with this article.
Aspectable MEF
In this section we will look at how the AspectableMef
framework
works. To be honest it is not that complicated, it just abstracts a certain
amount of otherwise tedious code from you. It really isn't rocket science, it's
more of a time saving thing really.
How The Mef Part Works
I think the following set of bullet points outlines the process, we will go through each of these later.
- For the class where you want to use aspects adorn the class with the
AspectsStatusExportAttribute
I have created, this tells MEF that thisExportAttribute
will be wanted to be aspected, as well as making it available as a standard MEF export - Develop your aspect code (typically this consists of 3 things, the actual aspect, an attribute that the aspect will look for, and also the actual aspect interception handling code)
- Further adorn your type with your specific aspect attributes
- Create your composition container, and allow the small
ApectableMef
framework to do the rest
So in lamens terms that is how it works, lets see some code now shall we.
Lets start with how MEF knows how to create these aspect enabled classes. This is down to 2 things
AspectsStatusExportAttribute
This is a special
MEF ExportAttribute
that I have created that allows you to not only allow your object to be
exported to
MEF but also to store
extra metadata that will be examined later. Here is the full code for the
AspectsStatusExportAttribute
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
namespace AspectableMef.Castle
{
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class AspectsStatusExportAttribute : ExportAttribute
{
public AspectsStatusExportAttribute(Type contractType) : base(contractType) { }
public bool AreAspectsEnabled { get; set; }
}
}
AOPExportProvider
Now if you do not know
MEF this may be a bit of
a struggle, but those that know
MEF may know that you can
add custom ExportProvider
(s) which sit between the
CompositionContainer
and the consumer of the Export
.
Knowing that it is not that hard to create a custom ExportProvider
that examines the part (object) that is being exported for a certain
attribute, and if that certain attribute is found, to not return the original
object but some other more powerful object.
This is exactly what the attached code does, it provided a specialised
ExportProvider
which looks for a certain attribute (namely the
AspectsStatusExportAttribute
) and if that is found and the
metdata states that the object is should be AOP'd a more powerful proxied (AOP
enabled) object is returned, if that attribute suggests that no AOP should be
performed the orginal non-proxied object is returned as if no AOP was done at
all.
I think this is pretty neat, anyway here is the entire code for the custom
AOPExportProvider
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition.Hosting;
using System.ComponentModel.Composition.Primitives;
namespace AspectableMef.Castle
{
public class AOPExportProvider : ExportProvider, IDisposable
{
private CatalogExportProvider _exportProvider;
public AOPExportProvider(Func<ComposablePartCatalog> catalogResolver)
{
_exportProvider = new CatalogExportProvider(catalogResolver());
//support recomposition
_exportProvider.ExportsChanged += (s, e) => OnExportsChanged(e);
_exportProvider.ExportsChanging += (s, e) => OnExportsChanging(e);
}
public ExportProvider SourceProvider
{
get
{
return _exportProvider.SourceProvider;
}
set
{
_exportProvider.SourceProvider = value;
}
}
protected override IEnumerable<Export> GetExportsCore(
ImportDefinition definition, AtomicComposition atomicComposition)
{
IEnumerable<Export> exports = _exportProvider.GetExports(definition, atomicComposition);
return exports.Select(export => new Export(export.Definition, () => GetValue(export)));
}
private object GetValue(Export innerExport)
{
var value = innerExport.Value;
if (innerExport.Metadata.Any(x => x.Key == "AreAspectsEnabled"))
{
KeyValuePair<String, Object> specificMetadata =
innerExport.Metadata.Where(x => x.Key == "AreAspectsEnabled").Single();
if ((Boolean)specificMetadata.Value == true)
{
return AspectProxy.Factory(value);
}
else
{
return value;
}
}
else
{
return value;
}
}
public void Dispose()
{
_exportProvider.Dispose();
}
}
}
It can be seen that this class also makes use of a AspectProxy
(
this is based on using Castles
Dynamic Proxy technology) which is shown below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Castle.DynamicProxy;
namespace AspectableMef.Castle
{
public class AspectProxy
{
public static Object Factory(object obj)
{
ProxyGenerator generator = new ProxyGenerator();
object[] attribs = obj.GetType().GetCustomAttributes(typeof(IAspectFactory), true);
IInterceptor[] interceptors =
(from x in attribs select
((IAspectFactory)x).GetAroundInvokeApectInvoker)
.Cast<IInterceptor>().ToArray();
object proxy = generator.CreateClassProxy(obj.GetType(), interceptors);
return proxy;
}
}
}
The important bit is that we generate a proxy which enables what Castle
call interception, which they enable by the use of custom classes that inherit
from a IInterceptor
interface (more on this is just a minute)
Castle DynamicProxy Aspect Design
As stated above this article focuses on using Castle to supply the AOP aspects for the code associated for this article. As we just saw above we use Castle dynamic proxy generator (see above where we use the ProxyGenerator
). So how about the AOP stuff, how does that work. Well we did see a glimpse of it above where we saw how we added a IInterceptor[]
when we used the ProxyGenerator
to create the actual proxy. Sow what do these IInterceptor
things do for us.
Well I think the best way to learn about how Castle AOP and its IInterceptor
interfaces work is to use an example. I will use one of the aspects that comes with this demo code to illustrate how it all works. I will use an INPCAspect from the demo code to describe how it all works.
IAspectFactory
This is a interface that I created to allow the AspectableMef to create the correct type of IInterceptor
. Here is this interface
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Castle.DynamicProxy;
namespace AspectableMef.Castle
{
interface IAspectFactory
{
IInterceptor GetAroundInvokeApectInvoker { get; }
}
}
INPCAspect
The next part of the puzzle is to create an actual aspect, which is done by implementing the IAspectFactory
interface, and inheriting from Attribute
as shown below
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Castle.DynamicProxy;
namespace AspectableMef.Castle
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class INPCAspect : Attribute, IAspectFactory
{
#region IAspectFactory Members
public IInterceptor GetAroundInvokeApectInvoker
{
get
{
return new INPCInterceptor();
}
}
#endregion
}
}
INPCAttribute
The next part of the puzzle is to create an a Method/Property marker attribute, that can be examined for later, to see if a certain method/property wishes to be aspectable or not.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AspectableMef.Castle
{
/// <summary>
/// This attribute is used by the Castle <c>NotifyPropertyChangedInterceptor</c> where
/// it examines the target types property being set for this attribute, and if it has
/// this attribute it will fire the NotifyChanged() method
/// on the target object when the property with the <c>INPCAttribute</c> is set.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class INPCAttribute : Attribute
{
}
}
INPCInterceptor
The last (but most important) part of the puzzle is to create a class that inherits from Castles IInterceptor
. It is this code that will be the actual code run when a method is called (properties are just methods really, Get_xxx/Set_xxx
). So what do we do, well we simply need to implement the IInterceptor
interface, which provides a single method
void Intercept(IInvocation invocation)
which we can use to carry out an aspect code in
The important thing that we MUST NEVER forget to do, is to call invocation.Proceed() without which the original method (the one that was called to get us into the IInterceptor
code) would not complete
Anyway here is an example INPCInterceptor
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using Castle.DynamicProxy;
namespace AspectableMef.Castle
{
public class INPCInterceptor : IInterceptor
{
#region IInterceptor members
public void Intercept(IInvocation invocation)
{
// let the original call go 1st
invocation.Proceed();
if (invocation.Method.Name.StartsWith("set_"))
{
string propertyName = invocation.Method.Name.Substring(4);
var pi = invocation.TargetType.GetProperty(propertyName);
// check for the special attribute
if (!pi.HasAttribute<INPCAttribute>())
return;
FieldInfo info = invocation.TargetType.GetFields(
BindingFlags.Instance | BindingFlags.NonPublic)
.Where(f => f.FieldType == typeof(PropertyChangedEventHandler))
.FirstOrDefault();
if (info != null)
{
//get the INPC field, and invoke it we managed to get it ok
PropertyChangedEventHandler evHandler =
info.GetValue(invocation.InvocationTarget) as PropertyChangedEventHandler;
if (evHandler != null)
evHandler.Invoke(invocation.InvocationTarget,
new PropertyChangedEventArgs(propertyName));
}
}
}
#endregion
}
}
Special Notes And Demo Of Usage
In order to use the INPCAspect
the class you are using it on must already implement the INotifyPropertyChanged
interface. Anyway here is an example of its usage
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.Composition;
using AspectableMef.Castle;
using System.Diagnostics;
using System.Windows;
namespace ConsoleApplication
{
[PartCreationPolicy(CreationPolicy.NonShared)]
[AspectsStatusExport(typeof(DemoClass), AreAspectsEnabled = true)]
[INPCAspect]
public class DemoClass : INotifyPropertyChanged
{
[INPCAttribute]
public virtual string Name { get; set; }
#region INPC Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
Example Aspects I Have Created
I have created serveral demo Aspects which I will explain below. Now these are for demonstration purposes only if you DO NOT like these or would do things differently fair enough, that is fine go for it, these Aspects are here just to show you how to write your own aspects.
INPC
We just discussed that, my you have a short memory don't you, yes it's as we just discussed.
Logging
The LogAspect
is pretty simple and simply logs the enter of any method that
is marked up with the LogAttribute
. The LogAspect
classes I have provided with
this demo code use Log4Net, which I think is an awesome logging framework for
.NET, but if you do not like this, just write your own aspects, the idea is the
important thing, not the implementation (although that's cool too right, well I
like it, but then again I would I wrote it).
The code is as shown below
LogAspect
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using Castle.DynamicProxy;
namespace AspectableMef.Castle
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class LogAspect : Attribute, IAspectFactory
{
private static ILog logger;
public static void SetLogger(ILog newLogger)
{
logger = newLogger;
}
public IInterceptor GetAroundInvokeApectInvoker
{
get
{
return new LogInterceptor(logger);
}
}
}
}
LogAttribute
This can be used to adorn a property/method which enables the LogInterceptor
to know if a particular method/property should log.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AspectableMef.Castle
{
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method)]
public class LogAttribute : Attribute
{
}
}
LogInterceptor
This code is only run if the class that is being
MEFed has a
LogAspectAttribute
and the method/property has the LogAttribute
used.
Here is the code for the LogInterceptor
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel.Composition;
using Castle.DynamicProxy;
using log4net;
namespace AspectableMef.Castle
{
public class LogInterceptor : IInterceptor
{
private static ILog logger;
public LogInterceptor(ILog logger)
{
if (LogInterceptor.logger == null)
LogInterceptor.logger = logger;
}
public void Intercept(IInvocation invocation)
{
DoLogging(invocation);
}
private void DoLogging(IInvocation invocation)
{
// let the original call go 1st
invocation.Proceed();
if (invocation.Method.HasAttribute<LogAttribute>())
{
try
{
StringBuilder sb = null;
sb = new StringBuilder(invocation.TargetType.FullName)
.Append(".")
.Append(invocation.Method)
.Append("(");
for (int i = 0; i < invocation.Arguments.Length; i++)
{
if (i > 0)
sb.Append(", ");
sb.Append(invocation.Arguments[i]);
}
sb.Append(")");
logger.Debug(sb.ToString());
invocation.Proceed();
logger.Debug("Result of " + sb + " is: " + invocation.ReturnValue);
}
catch (Exception e)
{
logger.Error(e.Message);
}
}
}
}
}
Special Notes And Demo Of Usage
In order to use the LogAspect
which for the demo code relies on Log4Net, there are several things that need to be done, which are shown below (these are all included in the demo code though, do not worry)
Log4Net Logger Code
You need to create a Log4Net logger class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using log4net;
using System.Reflection;
namespace ConsoleApplication
{
public static class LogManager
{
private static ILog log = null;
public static ILog Log
{
get { return log ?? (log = log4net.LogManager.GetLogger(
MethodBase.GetCurrentMethod().DeclaringType)); }
}
}
}
Log4Net Config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,
log4net" />
</configSections>
<log4net>
<appender name="ConsoleAppender" type="log4net.Appender.ConsoleAppender" >
<layout type="log4net.Layout.PatternLayout">
<param name="ConversionPattern" value="%d [%t] %-5p [%x] - %m%n" />
</layout>
</appender>
<appender name="RollingFileAppender" type="log4net.Appender.RollingFileAppender" >
<file type="log4net.Util.PatternString"
value="c:\temp\AspectableMef.log"/>
<lockingModel type="log4net.Appender.FileAppender+MinimalLock" />
<rollingStyle value="Composite"/>
<datePattern value="yyyyMMdd"/>
<maxSizeRollBackups value="100"/>
<maximumFileSize value="15MB"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %-5level %logger: %message%newline" />
</layout>
</appender>
<root>
<level value="ALL" />
<appender-ref ref="RollingFileAppender" />
<appender-ref ref="ConsoleAppender" />
</root>
</log4net>
</configuration>
Telling Log4Net To Use The Config
You MUST tell Log4Net to use the config file
//Could have configured Log4Net like this as well, in AssemblyInfo.cs
//[assembly: log4net.Config.XmlConfigurator(ConfigFile = "log4net.config", Watch = true)]
FileInfo assFile = new FileInfo(Assembly.GetEntryAssembly().Location);
XmlConfigurator.Configure(new FileInfo(string.Format("{0}{1}", assFile.Directory, @"\log4net.config")));
Tell LogAspect About Log4Net
LogAspect.SetLogger(LogManager.Log);
With all the above done all you now need to do is use it in a class which is done as follows
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.Composition;
using AspectableMef.Castle;
using System.Diagnostics;
using System.Windows;
namespace ConsoleApplication
{
[PartCreationPolicy(CreationPolicy.NonShared)]
[AspectsStatusExport(typeof(DemoClass), AreAspectsEnabled = true)]
[LogAspect]
public class DemoClass : INotifyPropertyChanged
{
private DateTime timeLast = DateTime.Now;
[Log]
public virtual void LoggedMethod(DateTime dt)
{
timeLast = dt;
}
}
}
Security
The SecurityAspect
is a little more complicated as I tied it in with the standard WindowsPrincipal security that can be applied to the thread. As such there are a few more moving parts, such as the ones shown below.
ISecurityContextProvider
This is a simple interface that would be implemented by a class that could actually provide a SecurityContext for a particular user (you will see a mock/fake/dummy version of this is just a minute). This interface would typically be implemented by your own code.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AspectableMef.Castle
{
public interface ISecurityContextProvider
{
SecurityContext GetSecurityContextByLogon(String logon);
}
}
And here is an example of a class that implements this ISecurityContextProvider
. This class is not part of the AspectableMef
, and should be in your code base. This is obviously a dummy example just to show how the security aspect that is provided with AspectableMef
works, you should create a more meaningful class, that I guess would hit ActiveDirectory or at least a database to fetch user specific roles/privileges.
using System.Threading;
using System.Collections.Generic;
using System;
using AspectableMef.Castle;
namespace ConsoleApplication
{
/// <summary>
/// This class is for demonstration purposes you would obviously not do this in a real, app
/// your implementation should provide real data read from some persistant store like a database etc etc
/// </summary>
public class FakeContextProvider : ISecurityContextProvider
{
/// <summary>
/// This method is returning mocked data, you would need to make this return meaningful data
/// for you real implementation
/// </summary>
public SecurityContext GetSecurityContextByLogon(String logon)
{
List<String> roles = new List<String>();
List<String> privileges = new List<String>();
for (int i = 0; i < 10; i++)
{
roles.Add(string.Format("role{0}", i.ToString()));
}
for (int i = 0; i < 10; i++)
{
privileges.Add(string.Format("privilege{0}", i.ToString()));
}
return new SecurityContext(Thread.CurrentPrincipal.Identity.Name, roles, privileges);
}
}
}
This fake ISecurityContextProvider
is used to return a SecurityContext
which looks like this
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AspectableMef.Castle
{
public class SecurityContext
{
public SecurityContext(string logon, List<String> roles,
List<String> privileges)
{
this.Logon = logon;
this.Roles = roles;
this.Privileges = privileges;
}
public String Logon { get; private set; }
public List<String> Roles { get; private set; }
public List<String> Privileges { get; private set; }
}
}
SecureWindowsPrincipal
Now that we have seen how that the ISecurityContextProvider
works, lets see how we can use it. Like I say the provided code uses WindowsPrincipal
based security, as such we need a specialised WindowsPrincipal
object. The code contains a specialised WindowsPrincipal
inheriting object called SecureWindowsPrincipal
, which is as shown below. This code basically make use of the ISecurityContextProvider
implementation class you just saw above, to work out whether a user has certain roles/privileges. This is done by using the ISecurityContextProvider
implementation classes SecrurityContext
object, which we also saw above
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Principal;
using System.Threading;
namespace AspectableMef.Castle
{
public class SecureWindowsPrincipal : WindowsPrincipal
{
#region Ctor
public SecureWindowsPrincipal(ISecurityContextProvider provider)
: base(WindowsIdentity.GetCurrent())
{
SecurityContext context =
provider.GetSecurityContextByLogon(Thread.CurrentPrincipal.Identity.Name);
if (context != null)
{
this.CurrentSecurityContext = context;
}
}
public SecureWindowsPrincipal(ISecurityContextProvider provider, String userName)
: base(WindowsIdentity.GetCurrent())
{
SecurityContext context = provider.GetSecurityContextByLogon(userName);
if (context != null)
{
this.CurrentSecurityContext = context;
}
}
#endregion
#region Public Properties
public SecurityContext CurrentSecurityContext { get; private set; }
#endregion
#region Public Methods
public override Boolean IsInRole(String role)
{
if ((this.CurrentSecurityContext != null) &&
(this.CurrentSecurityContext.Roles != null))
return this.CurrentSecurityContext.Roles.Contains(role);
return false;
}
public Boolean HasPrivilege(String privilege)
{
if ((this.CurrentSecurityContext != null) &&
(this.CurrentSecurityContext.Privileges != null))
return this.CurrentSecurityContext.Privileges.Contains(privilege);
return false;
}
#endregion
}
}
Ok so that's the extra parts, now on to the normal aspect code, which is as shown below.
SecurityAspect
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Castle.DynamicProxy;
namespace AspectableMef.Castle
{
[AttributeUsage(AttributeTargets.Class, AllowMultiple=false)]
public class SecurityAspect : Attribute, IAspectFactory
{
#region Ctor
public SecurityAspect()
{
}
#endregion
#region IAspectFactory Members
public IInterceptor GetAroundInvokeApectInvoker
{
get
{
return new SecurityInterceptor();
}
}
#endregion
}
}
SecurityAttribute
This can be used to adorn a property/method which enables the SecurityInterceptor to know if a particular method/property should be checked for security.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AspectableMef.Castle
{
[AttributeUsage(AttributeTargets.Property |
AttributeTargets.Method, AllowMultiple = true, Inherited = true)]
public class SecurityAttribute : Attribute
{
public String Role { get; set; }
public String Privilege { get; set; }
}
}
SecurityInterceptor
This code is only run if the class that is being MEFed has a SecurityAspectAttribute
and the method/property has the SecurityAttribute
used.
Here is the code for the SecurityInterceptor
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using System.ComponentModel;
using System.Threading;
using Castle.DynamicProxy;
namespace AspectableMef.Castle
{
public class SecurityInterceptor : IInterceptor
{
#region IInterceptor memers
public void Intercept(IInvocation invocation)
{
bool shouldCallOriginalMethod = false;
//check for the special attribute
if (!invocation.Method.HasAttribute<SecurityAttribute>())
shouldCallOriginalMethod = true;
if (Thread.CurrentPrincipal == null)
shouldCallOriginalMethod = true;
SecureWindowsPrincipal principal = null;
try
{
principal = (SecureWindowsPrincipal)Thread.CurrentPrincipal;
}
catch
{
shouldCallOriginalMethod = true;
}
if (principal != null)
{
foreach (SecurityAttribute securityAttrib in invocation.Method.GetAttributes<SecurityAttribute>())
{
if (!principal.IsInRole(securityAttrib.Role) &&
!principal.HasPrivilege(securityAttrib.Privilege))
{
String error = String.Format("The user with Logon : {0}, does not have the Role : {1}, or Privilege {2}",
Thread.CurrentPrincipal.Identity.Name, securityAttrib.Role, securityAttrib.Privilege);
throw new SecureWindowsPrincipalException(error);
}
}
shouldCallOriginalMethod = true;
}
else
{
shouldCallOriginalMethod = true;
}
if (shouldCallOriginalMethod)
invocation.Proceed();
}
#endregion
}
}
Special Notes And Demo Of Usage
In order to get WindowsPrincipal
used properly you must tell the thread to use WindowsPrincipal
, which is done as follow
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
Thread.CurrentPrincipal = new SecureWindowsPrincipal(new FakeContextProvider());
With all the above done all you now need to do is use it in a class which is done as follows
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.Composition;
using AspectableMef.Castle;
using System.Diagnostics;
using System.Windows;
namespace ConsoleApplication
{
[PartCreationPolicy(CreationPolicy.NonShared)]
[AspectsStatusExport(typeof(DemoClass), AreAspectsEnabled = true)]
[SecurityAspect]
public class DemoClass : INotifyPropertyChanged
{
[SecurityAttribute(Role = "dealer1", Privilege = "somePriv1")]
[SecurityAttribute(Role = "authoriser", Privilege = "somePriv2")]
public virtual void BadSecurityMethod()
{
//this should cause an Exception to be raised, and should not show MessageBox
MessageBox.Show("BadSecurityMethodCommand");
}
[SecurityAttribute(Role = "role0", Privilege = "privilege3")]
public virtual void GoodSecurityMethod()
{
MessageBox.Show("GoodSecurityMethodCommand");
}
}
}
A Demo Class
Here is a complete demo class that is both aspectable (via Castle
and also Exportable via
MEF). Note how you can
use the normal
MEF
PartCreationPolicyAttribute
with this class. In essence its just a class,
and as such is
MEFable (if such a word
were to exist), but thanks to the specialised AspectsStatusExportAttribute
and the AOPExportProvider
it is also AOPable (not sure that
word exists either).
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.ComponentModel.Composition;
using AspectableMef.Castle;
using System.Diagnostics;
using System.Windows;
namespace ConsoleApplication
{
[PartCreationPolicy(CreationPolicy.NonShared)]
[AspectsStatusExport(typeof(DemoClass), AreAspectsEnabled = true)]
[INPCAspect]
[LogAspect]
[SecurityAspect]
public class DemoClass : INotifyPropertyChanged
{
private DateTime timeLast = DateTime.Now;
public DemoClass()
{
Guid = Guid.NewGuid();
}
[INPCAttribute]
public virtual string Name { get; set; }
public Guid Guid { get; private set; }
[Log]
public virtual void LoggedMethod(DateTime dt)
{
timeLast = dt;
}
[SecurityAttribute(Role = "dealer1", Privilege = "somePriv1")]
[SecurityAttribute(Role = "authoriser", Privilege = "somePriv2")]
public virtual void BadSecurityMethod()
{
//this should cause an Exception to be raised, and should not show MessageBox
MessageBox.Show("BadSecurityMethodCommand");
}
[SecurityAttribute(Role = "role0", Privilege = "privilege3")]
public virtual void GoodSecurityMethod()
{
MessageBox.Show("GoodSecurityMethodCommand");
}
#region INPC Implementation
public event PropertyChangedEventHandler PropertyChanged;
protected void RaisePropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
Writing Your Own Aspects
So how do you write you own aspects? Well that is pretty easy actually, just follow these 3 steps
- Create your own XXXAspect class, which inherits from Attribute and also implements
IAspectFactory
(useINPCAspect
as a base for you own code) - Create your own marker XXXAttribute that can be used to mark methods/properties (use
INPCAttribute
as a base for you own code) - Create your own XXXInterceptor code which is a Castles
IInterceptor
based class (useINPCInterceptor
as a base for you own code) - Don't forget to mark up you apsectable class with your aspect attributes and also the AspectableMef
AspectsStatusExportAttribute
Words Of Warning
Now one thing of note is that there are some cases where ALL of the AOP
frameworks I tried fail miserably, which is with things like the code shown
below. The problem is with proxy style AOP frameworks (Castle) you are never going through the proxy
generated type to call the
Action<T>
or Func<T,TR>
delegates so no interception code can be made for those
2 methods.
using System.Windows.Input;
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Cinch
{
/// <summary>
/// Simple delegating command, based largely on DelegateCommand from PRISM/CAL
/// </summary>
/// <typeparam name="T">The type for the </typeparam>
public class SimpleCommand<T1, T2> : ICommand, ICompletionAwareCommand
{
private Func<T1, bool> canExecuteMethod;
private Action<T2> executeMethod;
public SimpleCommand(Func<T1, bool> canExecuteMethod,
Action<T2> executeMethod)
{
this.executeMethod = executeMethod;
this.canExecuteMethod = canExecuteMethod;
}
public SimpleCommand(Action<T2> executeMethod)
{
this.executeMethod = executeMethod;
this.canExecuteMethod = (x) => { return true; };
}
public bool CanExecute(T1 parameter)
{
if (canExecuteMethod == null) return true;
return canExecuteMethod(parameter);
}
public void Execute(T2 parameter)
{
if (executeMethod != null)
{
executeMethod(parameter);
}
}
public bool CanExecute(object parameter)
{
return CanExecute((T1)parameter);
}
public void Execute(object parameter)
{
Execute((T2)parameter);
}
#if SILVERLIGHT
/// <summary>
/// Occurs when changes occur that affect whether the command should execute.
/// </summary>
public event EventHandler CanExecuteChanged;
#else
/// <summary>
/// Occurs when changes occur that affect whether the command should execute.
/// </summary>
public event EventHandler CanExecuteChanged
{
add
{
if (canExecuteMethod != null)
{
CommandManager.RequerySuggested += value;
}
}
remove
{
if (canExecuteMethod != null)
{
CommandManager.RequerySuggested -= value;
}
}
}
#endif
/// <summary>
/// Raises the <see cref="CanExecuteChanged" /> event.
/// </summary>
[SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic",
Justification = "The this keyword is used in the Silverlight version")]
[SuppressMessage("Microsoft.Design", "CA1030:UseEventsWhereAppropriate",
Justification = "This cannot be an event")]
public void RaiseCanExecuteChanged()
{
#if SILVERLIGHT
var handler = CanExecuteChanged;
if (handler != null)
{
handler(this, EventArgs.Empty);
}
#else
CommandManager.InvalidateRequerySuggested();
#endif
}
}
}
Special Thanks
Special thanks go out to
- CastleProject (for DynamicProxy) without which this article would not be possible
- LinFu.AOP for his excellent work, even though I have not used it, I still think Philip (the LinFu.AOP author) is a freekin genius
That's It
That's it for now, I hope this article and the result downloads inspire you enough to have a go at creating your own aspects.
Post Comment
hello there paul if you still need of them here is the web address
and details ,check out there great prices,say martin binaford told you to ring himhello kevin if your still knoking around this is there web address
and details,ring them for advice ,tell them neesoms recommened youhi there paul this is the site details,give them a call , tell them gareth gail told you to ring