Easiest way to inject code to all methods and properties that don't have a custom attribute

后端 未结 2 682
无人及你
无人及你 2020-12-31 15:13

There are a a lot of questions and answers around AOP in .NET here on Stack Overflow, often mentioning PostSharp and other third-party products. So there seems to be qu

相关标签:
2条回答
  • 2020-12-31 15:34

    I'm not sure where you got that methods have to be virtual from. We use Postsharp to time and log calls to WCF service interface implementations utilizing the OnMethodBoundaryAspect to create an attribute we can decorate the classes with. Quick Example:

    [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;
        }
    }
    

    And then us it like this:

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

    Works like a charm without having to make any methods virtual in Postsharp 1.5.6. Maybe they have changed it for 2.x but I certainly don't hope so - it would make it way less useful.

    Update: I'm not sure if you can easily convince Postsharp to only inject code into certain methods based on with which attributes they are decorated. If you look at this tutorial it only shows ways of filtering on type and method names. We have solved this by passing the type we want to check on into the attribute and then in OnEntry you can use reflection to look for the attributes and decide whether to log or not. The result of that is cached so you only have to do it on the first call.

    I adjusted the code above to demonstrate the idea.

    0 讨论(0)
  • 2020-12-31 15:47

    I was able to solve the problem with Mono.Cecil. I am still amazed how easy to learn, easy to use, and powerful it is. The almost complete lack of documentation did not change that.

    These are the 3 sources of documentation I used:

    • static-method-interception-in-net-with-c-and-monocecil
    • Migration to 0.9
    • the source code itself

    The first link provides a very gentle introduction, but as it describes an older version of Cecil - and much has changed in the meantime - the second link was very helpful in translating the introduction to Cecil 0.9. After getting started, the (also not documented) source code was invaluable and answered every question I had - expect perhaps those about the .NET platform in general, but there's tons of books and material on that somewhere online I'm sure.

    I can now take a DLL or EXE file, modify it, and write it back to disk. The only thing that I haven't done yet is figuring out how to keep debugging information - file name, line number, etc. currently get lost after writing the DLL or EXE file. My background isn't .NET, so I'm guessing here, and my guess would be that I need to look at mono.cecil.pdb to fix that. Somewhere later - it's not that super important for me right now. I'm creating this EXE file, run the application - and it's a complex GUI application, grown over many years with all the baggage you would expect to find in such a piece of, ahem, software - and it checks things and logs errors for me.

    Here's the gist of my code:

    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;
    }
    

    If someone knows an easier way how to get this done, I'm still interested in an answer and I won't mark this as the solution - unless no easier solutions show up.

    0 讨论(0)
提交回复
热议问题