将 AOP 引入 MEF






4.86/5 (36投票s)
关于结合 AOP 和 MEF 的一个实验。
- 下载 AspectableMef.Castle (本文讨论的内容) - 1.22 MB
- 下载 AspectableMef (使用 LinFu.AOP 的替代框架。请注意,它不喜欢 ICommand) - 1.27 MB
目录
引言
很久以前,我写过一篇关于不同面向方面编程 (AOP) 方法的文章;这篇文章可以在这里找到,如果你对 AOP 完全不了解,建议先阅读那篇文章,再深入了解这篇文章。
总之,在那篇文章中,我做了很多为写这篇文章打下的基础;实际上,你可以把这篇文章看作是那篇文章的第二部分。
这篇文章有什么不同?
嗯,在这篇文章中,我想看看创建一个通用的框架来允许将 Aspect 添加到类型中,然后这些类型就可以与MEF一起使用,这有多么容易。好吧,Aspect 可以很容易地添加到任何类型中,但为了给给定类型提供 Aspect,需要做一些繁琐的工作;这是事实,无论你选择哪个 AOP 框架,这都很乏味。所以本质上,这篇文章是关于我提出的一个想法,即简化这个过程,并希望它能让你的生活更轻松,并能让你以最小的努力来处理 Aspect 和MEF。
MEF
Managed Extensibility Framework (MEF) 本质上是一个 IOC 容器,它允许通过一组MEF属性来从MEF容器(CompositionContainer
)解析依赖项。假设你有一个 ClassA
需要使用 ClassB
的实例来构造,你可能会这样做(注意使用MEF属性 [Export]
、[ImportingConstructor]
和 [PartCreationPolicy]
)
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)
{
}
}
}
这张图可能有助于你从高层次理解MEF的工作原理
要真正理解MEF的工作原理需要很多很多文章,这超出了本文的范围。如果你不知道MEF是如何工作的,我建议你阅读这篇MEF 编程指南。
Aspects
当我开始写这篇文章时,我已经知道了一些免费的 AOP 框架,例如
- Castle Dynamic Proxy 使用代理技术
- Microsoft's Unity IOC Container 使用代理技术
- LinFu.AOP,使用 IL 织入
- PostSharp (非免费),使用 IL 织入
这些都提供了不同风味的 AOP。
为了理解不同方法之间的区别,让我们考虑以下两个小节。
代理 AOP
对于使用代理技术的 AOP 框架,启用 Aspect 的方式是通过代理。代理通常继承于你想要添加 Aspect 的类型,并且代理通常是一个动态对象,使用 Reflection.Emit
API 编写。这些代理技术 AOP 框架依赖于要添加 Aspect 的任何属性/方法都被标记为 virtual
。
它们还依赖于实现某些供应商特定 AOP 接口的额外类。这些额外的类通常会添加到代理对象中,以便在调用代理对象上的方法/属性时,它会首先检查 AOP 类列表,并在调用原始方法之前运行它们。
这种方法的缺点是方法/属性必须被标记为 virtual
。
这张图说明了代理 AOP 框架可能如何工作
IL 织入
IL 织入是完全不同的故事,故事是这样的
AssemblyA 是一个类型,除了 AssemblyA 之外,还有一些供应商特定的 AOP 类被编写出来。在编译时,AssemblyA 会将任何被认为是 AOP 候选的类型进行重写,从而在 AssemblyA 中为该类型生成新的 IL。
这确实是一个更好的选择,也是我喜欢的方式,因为你的方法/属性不需要是 virtual 的,你只需要编写你的代码,然后让 AOP IL 重写过程来完成剩下的工作。
这种方法的缺点是它很难,我只知道两个框架这样做:LinFu.AOP 和 PostSharp (这个很贵)。
我最初写的大部分文章都是使用LinFu.AOP。当到了最后的障碍时,我引入了一个 ICommand
(基于委托),而LinFu.AOP悲惨地崩溃了,并实际创建了一个无效的程序集。这并不是说LinFu.AOP不好,我认为LinFu.AOP的作者是一位天才,它只是不喜欢 ICommand
(基于委托)对象,所以如果你对此没问题,就使用LinFu.AOP;事实上,Castle Dynamic Proxy也不允许将 AOP 添加到 ICommand
(基于委托)对象上,但它并没有因此而崩溃。现在,LinFu.AOP有一个新版本,可能解决了这个问题,但我尝试了一下,也遇到了其他问题。我相信假以时日,LinFu.AOP的作者会解决这些问题,他很棒。
我在下载中包含了一个基于LinFu.AOP的完整工作示例;唯一的限制是我发现它与 ICommand
(基于委托)对象不起作用。这足以让我转而使用Castle Dynamic Proxy。
如果你想修改这个解决方案,你需要先卸载项目 "AspectableTypes",编辑项目文件中的 PostWeaveTaskLocation 路径,然后重新加载项目。你会看到类似这样的行
<PostWeaveTaskLocation>C:\Users\WIN7LAP001\Desktop\In Progress\AspectableMef\AspectableMef\Lib\LinFu.Aop.Tasks.dll</PostWeaveTaskLocation>
总之,如果你想看看本文的 IL AOP 版本,它包含在本文顶部的下载内容中。
IL 织入 AOP 框架的工作原理如下
注意:本文的其余部分将使用基于代理的解决方案,即Castle Dynamic Proxy,我已将其集成以创建本文的代码。
Aspectable MEF
在本节中,我们将研究 AspectableMef 框架的工作原理。老实说,它并不复杂,它只是为你抽象了一些原本繁琐的代码。它真的不是什么火箭科学,更多的是一种节省时间的方式。
MEF 的工作原理
我认为下面的一系列要点概述了整个过程,我们稍后将逐一介绍。
- 对于你想要使用 Aspect 的类,请用我创建的
AspectsStatusExportAttribute
来装饰类。这告诉MEF这个ExportAttribute
将会被 Aspect 化,同时也可以作为标准的MEF导出项可用。 - 开发你的 Aspect 代码(通常,这包括三件事:实际的 Aspect、Aspect 将要查找的属性,以及实际的 Aspect 拦截处理代码)。
- 进一步用你特定的 Aspect 属性来装饰你的类型。
- 创建你的组合容器,并让小巧的
ApectableMef
框架来处理其余的事情。
所以,用通俗的话来说,这就是它的工作原理。我们现在来看一些代码,好吗?
让我们先看看MEF是如何知道如何创建这些支持 Aspect 的类的。这取决于两件事。
AspectsStatusExportAttribute
这是一个我创建的特殊MEF ExportAttribute
,它不仅允许你的对象被导出到MEF,还可以存储稍后将被检查的额外元数据。这是 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
如果你不了解MEF,这可能会有点困难,但了解MEF的人可能知道,你可以添加自定义的 ExportProvider
(s),它们位于 CompositionContainer
和 Export
的消费者之间。
知道创建一个自定义的 ExportProvider
来检查被导出的部分(对象)是否带有特定属性,如果找到了该特定属性,则不返回原始对象而是返回一个功能更强大的对象,这并不难。
这正是附带代码所做的,它提供了一个专门的 ExportProvider
,它查找特定属性(即 AspectsStatusExportAttribute
),如果找到了该属性并且元数据显示对象应该被 AOP 化,则返回一个功能更强大的代理(AOP 已启用)对象;如果该属性表明不应执行 AOP,则返回原始的非代理对象,就好像根本没有进行 AOP 一样。
我认为这非常酷;总之,这是自定义 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();
}
}
}
可以看出,这个类还使用了 AspectProxy
(它基于使用Castle's Dynamic Proxy技术),下面将展示它
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;
}
}
}
重要的是,我们生成了一个代理,它实现了Castle所谓的拦截,它们通过使用继承自 IInterceptor
接口的自定义类来实现(稍后将详细介绍)。
Castle DynamicProxy Aspect 设计
如上所述,本文重点介绍使用Castle为本文相关的代码提供 AOP Aspect。正如我们上面刚看到的,我们使用Castle的动态代理生成器(参见上面使用 ProxyGenerator
的地方)。那么 AOP 的东西呢?它是如何工作的?嗯,我们上面已经瞥见了它,当使用 ProxyGenerator
创建实际代理时,我们看到了如何添加 IInterceptor[]
。那么,这些 IInterceptor
到底有什么作用呢?
嗯,我认为学习Castle AOP 及其 IInterceptor
接口的最佳方法是使用一个例子。我将使用这个演示代码中的一个 Aspect 来说明它的工作原理。我将使用演示代码中的 INPCAspect
来描述它的工作原理。
IAspectFactory
这是一个我创建的接口,它允许 AspectableMef
创建正确类型的 IInterceptor
。这是这个接口
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Castle.DynamicProxy;
namespace AspectableMef.Castle
{
interface IAspectFactory
{
IInterceptor GetAroundInvokeApectInvoker { get; }
}
}
INPCAspect
拼图的下一部分是创建一个实际的 Aspect,这可以通过实现 IAspectFactory
接口并继承 Attribute
来完成,如下所示
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
拼图的下一部分是创建一个方法/属性标记属性,以后可以对其进行检查,以查看某个方法/属性是否希望被 Aspect 化。
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
最后(也是最重要的)一部分是创建一个继承自Castle的 IInterceptor
的类。当调用方法时(属性实际上就是方法,Get_xxx/Set_xxx
),实际运行的代码就是它。那么我们做什么呢?嗯,我们只需要实现 IInterceptor
接口,它提供了一个单一的方法
void Intercept(IInvocation invocation)
我们可以用它来进行 Aspect 代码的执行。
我们绝不能忘记做的重要事情是调用 invocation.Proceed()
,否则原始方法(调用以进入 IInterceptor
代码的方法)将不会完成。
总之,这里有一个 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
}
}
特别说明和用法演示
要使用 INPCAspect
,你正在使用的类必须已经实现了 INotifyPropertyChanged
接口。总之,这里有一个使用示例
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
}
}
我创建的示例 Aspect
我创建了几个演示 Aspect,下面将进行解释。现在,这些仅用于演示目的;如果你不喜欢它们或者会采取不同的做法,没关系,就这样做吧。这些 Aspect 只是为了向你展示如何编写自己的 Aspect。
INPC
我们刚才讨论过了,你记性真差,不是吗?是的,就像我们刚才讨论的那样。
日志记录
LogAspect 非常简单,它只是记录任何被标记为 LogAttribute
的方法的入口。我在此演示代码中提供的 LogAspect
类使用log4Net,我认为这是一个很棒的 .NET 日志记录框架,但如果你不喜欢,可以自己编写 Aspect。关键是思想,而不是实现。(虽然那也很酷,对吧?嗯,我喜欢它,但话说回来,我写的,我当然喜欢。)
代码如下所示
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
这可以用来装饰属性/方法,让 LogInterceptor
知道某个方法/属性是否应该被记录。
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
这段代码只有在被MEF化的类具有 LogAspectAttribute
并且方法/属性使用了 LogAttribute
时才会运行。
这是 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);
}
}
}
}
}
特别说明和用法演示
为了使用 LogAspect
(它依赖于 log4Net,用于演示代码),需要做几件事,如下所示(不过这些都包含在演示代码中,别担心)。
log4Net 日志记录器代码
你需要创建一个 log4Net 日志记录器类
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 配置
<?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>
告诉 log4Net 使用配置
你必须告诉 log4Net 使用配置文件
//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")));
告知 LogAspect 关于 log4Net
LogAspect.SetLogger(LogManager.Log);
完成以上所有步骤后,你现在需要做的就是在类中使用它,方法如下
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;
}
}
}
安全
SecurityAspect
稍微复杂一些,因为它与可以应用于线程的标准 WindowsPrincipal
安全性绑定在一起。因此,有几个移动的部分,如下所示。
ISecurityContextProvider
这是一个简单的接口,它将由一个能够实际为特定用户提供 SecurityContext
的类来实现(稍后你将看到一个模拟/假的/示例版本)。这个接口通常会由你自己的代码实现。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace AspectableMef.Castle
{
public interface ISecurityContextProvider
{
SecurityContext GetSecurityContextByLogon(String logon);
}
}
下面是一个实现 ISecurityContextProvider
的类的示例。这个类不是 AspectableMef
的一部分,应该放在你的代码库中。这显然是一个模拟示例,只是为了展示 AspectableMef
提供的安全性 Aspect 如何工作;你应该创建一个更有意义的类,我猜它会连接到 ActiveDirectory 或至少是数据库来获取用户特定的角色/权限。
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);
}
}
}
这个假的 ISecurityContextProvider
用于返回一个 SecurityContext
,它看起来像这样
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
现在我们已经看到了 ISecurityContextProvider
如何工作,让我们看看如何使用它。正如我所说,提供的代码使用基于 WindowsPrincipal
的安全性;因此,我们需要一个专门的 WindowsPrincipal
对象。代码包含一个名为 SecureWindowsPrincipal
的特殊 WindowsPrincipal
继承对象,如下所示。这段代码基本上利用了你刚才看到的 ISecurityContextProvider
实现类,来判断用户是否拥有某些角色/权限。这是通过使用 ISecurityContextProvider
实现类的 SecrurityContext
对象来实现的,我们上面也看到了它。
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
}
}
好的,那些是额外的部分。现在来看正常的 Aspect 代码,如下所示。
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
这可以用来装饰属性/方法,让 SecurityInterceptor
知道某个方法/属性是否需要进行安全检查。
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
这段代码只有在被MEF化的类具有 SecurityAspectAttribute
并且方法/属性使用了 SecurityAttribute
时才会运行。
这是 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
}
}
特别说明和用法演示
为了让 WindowsPrincipal
正确使用,你必须告诉线程使用 WindowsPrincipal
,方法如下
AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);
Thread.CurrentPrincipal = new SecureWindowsPrincipal(new FakeContextProvider());
完成以上所有步骤后,你现在需要做的就是在类中使用它,方法如下
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");
}
}
}
一个演示类
这是一个完整的演示类,它既支持 Aspect(通过Castle)也支持导出(通过MEF)。注意你可以如何使用普通的MEF PartCreationPolicyAttribute
来处理这个类。本质上,它只是一个类,因此是MEFable(如果这个词存在的话),但由于专门的 AspectsStatusExportAttribute
和 AOPExportProvider
,它也是 AOPable(不确定这个词是否存在)。
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
}
}
编写你自己的 Aspect
那么如何编写你自己的 Aspect 呢?嗯,这其实很简单,只需遵循这三个步骤
- 创建你自己的
XXXAspect
类,它继承自Attribute
并实现IAspectFactory
(使用INPCAspect
作为你自己的代码基础)。 - 创建你自己的标记属性
XXXAttribute
,它可以用来标记方法/属性(使用INPCAttribute
作为你自己的代码基础)。 - 创建你自己的
XXXInterceptor
代码,它是一个基于Castle的IInterceptor
的类(使用INPCInterceptor
作为你自己的代码基础)。 - 不要忘记用你的 Aspect 属性以及 AspectableMef
AspectsStatusExportAttribute
来标记你的 Aspectable 类。
警告
现在,值得注意的是,在某些情况下,所有我尝试过的 AOP 框架都会惨败,就像下面显示的示例代码一样。问题在于基于代理的 AOP 框架(Castle),你永远不会通过代理生成的类型来调用 Action<T>
或 Func<T,TR>
委托,因此无法为这两个方法创建拦截代码。
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
}
}
}
特别感谢
特别感谢
- CastleProject (for DynamicProxy) 没有它,本文就不可能实现。
- LinFu.AOP 感谢他出色的工作,尽管我没有使用它,但我仍然认为 Philip(LinFu.AOP 的作者)是一位天才。
就是这样
就这些了。希望本文和下载内容能激励你尝试创建自己的 Aspect。