【问题标题】:Easiest way to inject code to all methods and properties that don't have a custom attribute向所有没有自定义属性的方法和属性注入代码的最简单方法
【发布时间】:2011-07-12 22:40:04
【问题描述】:

StackOverflow 上的.NET 这里有很多关于AOP 的问题和答案,经常提到 PostSharp 和其他第三方产品。因此,在 .NET 和 C# 世界中似乎有相当多的 AOP 选项。但是每个都有自己的限制,在下载了有前途的 PostSharp 我在他们的文档中发现“方法必须是虚拟的”才能注入代码(编辑:参见 ChrisWue 的回答和我的评论 - 我想,虚拟约束一定是在其中一个竞争者身上)。我没有进一步调查这个陈述的准确性,但它的分类让我回到了 StackOverflow。

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

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

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

【问题讨论】:

  • 您是否有机会升级您的解决方案并拥有更现代的解决方案? (例如,利用 Fody 或其扩展之一的东西?)

标签: c# .net aop code-injection


【解决方案1】:

我能够使用 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;
}

如果有人知道如何完成此任务的更简单方法,我仍然对答案感兴趣并且我不会将其标记为解决方案 - 除非没有更简单的解决方案出现。

【讨论】:

    【解决方案2】:

    我不确定你是从哪里得到的 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 中,您可以使用反射来查找属性并决定是否记录。结果会被缓存,因此您只需在第一次调用时执行此操作。

    我调整了上面的代码来演示这个想法。

    【讨论】:

    • 克里斯,你说得对,我很困惑。让我失望的是在 [sharpcrafters.com/postsharp/documentation] 上,它说:“要使此类成为方面,它必须派生自 Postsharp 定义的方面父类”。但当然这是有道理的,现在仔细阅读。只有方面需要推导bla-bla,而不是想要被“方面化”的方法。但是,我想向所有没有特定属性的方法注入代码。
    • @Eugene:我扩展了我的答案以涵盖这一点
    • 我不太明白“传递我们要检查的类型”如何解决“将代码注入所有内容(除非使用我的自定义属性注释)”问题。你有一个例子吗?我想要所有类型(减去几个),所以我不知道它会是什么类型。
    • 谢谢克里斯,我明白了。这确实简单易行 - 如果没有更容易的事情出现(我仍在等待 .net 框架的一部分),我会将其标记为答案。
    • 克里斯,我刚刚再次查看了第二个代码 sn-p - 这是否意味着我必须用 [LogMethodCallAttribute(typeof(MyCustomAttribute))] 注释每个类?这意味着我必须触及所有类才能使其工作,包括那些没有注释方法的类(因此是代码注入目标)。我不想那样做。或者当我想注释类中的方法时,我只需要注释类吗?我完全不明白为什么我必须在类级别进行注释,我只想对方法进行注释。
    猜你喜欢
    • 1970-01-01
    • 2013-04-10
    • 1970-01-01
    • 1970-01-01
    • 2011-03-22
    • 2020-03-12
    • 1970-01-01
    • 2020-12-11
    • 1970-01-01
    相关资源
    最近更新 更多