将代码注入到没有自定义属性的所有方法和属性的最简单方法

时间:2011-07-12 22:40:05

标签: c# .net aop code-injection

Stack Overflow上的AOP .NET周围有很多问题和答案,经常提到PostSharp和其他第三方产品。因此,在.NET和C#世界中似乎有相当多的AOP选项。但是每个都有它们的限制,在下载了有希望的PostSharp 之后,我在他们的文档中发现'方法必须是虚拟的'才能注入代码(编辑:请参阅ChrisWue的回答和我的评论 - 虚拟约束必须是其中一个竞争者,我想)。我还没有进一步研究这个陈述的准确性,但它的分类性让我回到Stack Overflow。

所以我想回答这个非常具体的问题:

我想注入简单的“if(some-condition)Console.WriteLine”样式代码到每个方法和属性(静态,密封,内部,虚拟,非 - 虚拟,没关系)在我的项目中没有自定义注释,以便在运行时动态测试我的软件。这个注入的代码不应该保留在发布版本中,它只是用于开发期间的动态测试(与线程相关)。

最简单的方法是什么?我偶然发现了Mono.Cecil,看起来很理想,除了你似乎必须编写你要在IL中注入的代码。这不是一个大问题,使用Mono.Cecil很容易获得用C#编写的IL版本的代码。但是,如果有更简单的东西,理想情况下甚至可以内置到.NET中(我仍然在.NET 3.5上),我想知道。 [更新:如果建议的工具不是.NET Framework的一部分,那么如果它是开源的,如Mono.Cecil,或者免费提供,那就太好了)

2 个答案:

答案 0 :(得分:11)

我能够用 Mono.Cecil 解决问题。我仍然惊讶于它易于学习,易于使用和强大。几乎完全缺乏文档并没有改变这一点。

这些是我使用的3个文档来源:

第一个链接提供了一个非常温和的介绍,但由于它描述了较早版本的Cecil - 并且在此期间发生了很大变化 - 第二个链接对于翻译Cecil 0.9的介绍非常有帮助。在开始之后,(也没有记录的)源代码是非常有价值的,并且回答了我所遇到的每一个问题 - 一般都是关于.NET平台的那些问题,但是我确信在网上有大量的书籍和材料。

我现在可以获取DLL或EXE文件,修改它,然后将其写回磁盘。我还没有做过的唯一事情是弄清楚如何保持调试信息 - 文件名,行号等目前在编写DLL或EXE文件后丢失。我的背景不是.NET,所以我猜这里,我的猜测是我需要查看mono.cecil.pdb来解决这个问题。某个地方 - 现在对我来说并不是那么重要。我正在创建这个EXE文件,运行应用程序 - 这是一个复杂的GUI应用程序,经过多年的发展,带着你期望在这样一个软件中找到的所有包袱 - 它会检查并记录错误我

以下是我的代码的要点:

DefaultAssemblyResolver assemblyResolver = new DefaultAssemblyResolver();
// so it won't complain about not finding assemblies sitting in the same directory as the dll/exe we are going to patch
assemblyResolver.AddSearchDirectory(assemblyDirectory);
var readerParameters = new ReaderParameters { AssemblyResolver = assemblyResolver };

AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyFilename, readerParameters);

foreach (var moduleDefinition in assembly.Modules)
{
    foreach (var type in ModuleDefinitionRocks.GetAllTypes(moduleDefinition))
    {
        foreach (var method in type.Methods)
        {
            if (!HasAttribute("MyCustomAttribute", method.method.CustomAttributes)
            {
              ILProcessor ilProcessor = method.Body.GetILProcessor();
              ilProcessor.InsertBefore(method.Body.Instructions.First(), ilProcessor.Create(OpCodes.Call, threadCheckerMethod));
// ...

private static bool HasAttribute(string attributeName, IEnumerable<CustomAttribute> customAttributes)
{
    return GetAttributeByName(attributeName, customAttributes) != null;
}

private static CustomAttribute GetAttributeByName(string attributeName, IEnumerable<CustomAttribute> customAttributes)
{
    foreach (var attribute in customAttributes)
        if (attribute.AttributeType.FullName == attributeName)
            return attribute;
    return null;
}

如果有人知道如何更好地完成这项工作,我仍然对答案感兴趣,我不会将此标记为解决方案 - 除非没有更简单的解决方案出现。

答案 1 :(得分:4)

我不确定你从哪里得到methods have to be virtual。我们使用Postsharp来定时调用WCF服务接口实现,并使用OnMethodBoundaryAspect创建一个我们可以用它来装饰类的属性。快速示例:

[Serializable]
public class LogMethodCallAttribute : OnMethodBoundaryAspect
{
    public Type FilterAttributeType { get; set; }

    public LogMethodCallAttribute(Type filterAttributeType)
    {
        FilterAttributeType = filterAttributeType;
    }

    public override void OnEntry(MethodExecutionEventArgs eventArgs)
    {
        if (!Proceed(eventArgs)) return;
        Console.WriteLine(GetMethodName(eventArgs));
    }

    public override void OnException(MethodExecutionEventArgs eventArgs)
    {
        if (!Proceed(eventArgs)) return;
        Console.WriteLine(string.Format("Exception at {0}:\n{1}", 
                GetMethodName(eventArgs), eventArgs.Exception));
    }

    public override void OnExit(MethodExecutionEventArgs eventArgs)
    {
        if (!Proceed(eventArgs)) return;
         Console.WriteLine(string.Format("{0} returned {1}", 
                GetMethodName(eventArgs), eventArgs.ReturnValue));
    }
    private string GetMethodName(MethodExecutionEventArgs eventArgs)
    {
        return string.Format("{0}.{1}", eventArgs.Method.DeclaringType, eventArgs.Method.Name);
    }
    private bool Proceed(MethodExecutionEventArgs eventArgs)
    {
         return Attribute.GetCustomAttributes(eventArgs.Method, FilterAttributeType).Length == 0;
    }
}

然后我们这样:

 [LogMethodCallAttribute(typeof(MyCustomAttribute))]
 class MyClass
 {
      public class LogMe()
      {
      }

      [MyCustomAttribute]
      public class DoNotLogMe()
      {
      }
 }

无需在Postsharp 1.5.6中创建任何虚拟方法,就像魅力一样。也许他们已经改变了2.x但我当然不希望如此 - 这会使它变得不那么有用。

更新:我不确定你是否可以轻易地说服Postsharp只根据它们被装饰的属性将代码注入某些方法。如果查看this tutorial,它只会显示对类型和方法名称进行过滤的方法。我们通过将要检查的类型传递给属性来解决这个问题,然后在OnEntry中,您可以使用反射来查找属性并决定是否记录。结果是缓存的,因此您只需在第一次调用时执行此操作。

我调整了上面的代码来证明这个想法。