65.9K
CodeProject 正在变化。 阅读更多。
Home

将 AOP 引入 MEF

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.86/5 (36投票s)

2011年7月9日

CPOL

14分钟阅读

viewsIcon

79091

downloadIcon

1389

关于结合 AOP 和 MEF 的一个实验。

目录

引言

很久以前,我写过一篇关于不同面向方面编程 (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 框架,例如

这些都提供了不同风味的 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 的工作原理

我认为下面的一系列要点概述了整个过程,我们稍后将逐一介绍。

  1. 对于你想要使用 Aspect 的类,请用我创建的 AspectsStatusExportAttribute 来装饰类。这告诉MEF这个 ExportAttribute 将会被 Aspect 化,同时也可以作为标准的MEF导出项可用。
  2. 开发你的 Aspect 代码(通常,这包括三件事:实际的 Aspect、Aspect 将要查找的属性,以及实际的 Aspect 拦截处理代码)。
  3. 进一步用你特定的 Aspect 属性来装饰你的类型。
  4. 创建你的组合容器,并让小巧的 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),它们位于 CompositionContainerExport 的消费者之间。

知道创建一个自定义的 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

最后(也是最重要的)一部分是创建一个继承自CastleIInterceptor 的类。当调用方法时(属性实际上就是方法,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(如果这个词存在的话),但由于专门的 AspectsStatusExportAttributeAOPExportProvider,它也是 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 呢?嗯,这其实很简单,只需遵循这三个步骤

  1. 创建你自己的 XXXAspect 类,它继承自 Attribute 并实现 IAspectFactory(使用 INPCAspect 作为你自己的代码基础)。
  2. 创建你自己的标记属性 XXXAttribute,它可以用来标记方法/属性(使用 INPCAttribute 作为你自己的代码基础)。
  3. 创建你自己的 XXXInterceptor 代码,它是一个基于CastleIInterceptor 的类(使用 INPCInterceptor 作为你自己的代码基础)。
  4. 不要忘记用你的 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
        }
    }
}

特别感谢

特别感谢

就是这样

就这些了。希望本文和下载内容能激励你尝试创建自己的 Aspect。

© . All rights reserved.