pamidur / aspect-injector

AOP framework for .NET (c#, vb, etc)
Apache License 2.0
770 stars 115 forks source link

Feature: Make `[assembly: Attribute]` work with virtual methods that don't have an override #222

Open Jinjinov opened 1 year ago

Jinjinov commented 1 year ago

Thank you for your great library! I am using it in my https://github.com/Jinjinov/BlazorWasmProfiler

Describe the solution you'd like

protected virtual void OnParametersSet() is a method defined in Microsoft.AspNetCore.Components.ComponentBase. If I define protected override void OnParametersSet() in a .razor file (Blazor component) then [assembly: Attribute] works.

I would like [assembly: Attribute] to work with every Blazor component (every Type derived from Microsoft.AspNetCore.Components.ComponentBase) even if there is no protected override void OnParametersSet() in a .razor file.

Describe alternatives you've considered

I tried using a Microsoft.CodeAnalysis.ISourceGenerator to add protected override void OnParametersSet() to every Blazor component, but it is not possible to chain source generators. Because all source generators run in parallel, the Razor source generator that generates C# classes from razor files was running in parallel with my source generator (which tried to find types derived from Microsoft.AspNetCore.Components.ComponentBase and could not find any).

Additional context

I also tried using Mono.Cecil to add protected virtual void OnParametersSet() to the Blazor assembly, but changing the dll file in \bin\Debug\net7.0 doesn't help. Then I also tried changing the dll file in \bin\Debug\net7.0\wwwroot\_framework but it still doesn't work. Then I tried changing the file in \obj\Debug\net7.0 but the build crashes.

Do you have any suggestions how I can make it work?

pamidur commented 1 year ago

Sorry for delay, this is interesting feature request that was actually requested several times by now. Could you please drop a source code sample where it does not work with comments where it should work? It would help immensely!

Jinjinov commented 1 year ago

Third party NuGet Assembly1

namespace Assembly1;

public class Base
{
    public virtual Foo()
    {
    }
}

My project Assembly2

MethodTimerAttribute.cs

using AspectInjector.Broker;
using System;

namespace Assembly2
{
    [Aspect(Scope.Global)]
    [Injection(typeof(MethodTimerAttribute))]
    [AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
    public class MethodTimerAttribute : Attribute
    {
        [Advice(Kind.Before)]
        public void OnEntry([Argument(Source.Name)] string methodName, [Argument(Source.Type)] Type declaringType)
        {
            Console.WriteLine($"Before {declaringType}.{methodName}");
        }

        [Advice(Kind.After)]
        public void OnExit([Argument(Source.Name)] string methodName, [Argument(Source.Type)] Type declaringType)
        {
            Console.WriteLine($"After {declaringType}.{methodName}");
        }
    }
}

Program.cs

namespace Assembly2;

[assembly: MethodTimer]

var ok = new DerivedMethodTimerWorking();
ok.Foo(); // MethodTimer is working

var notOk = new DerivedMethodTimerNotWorking();
notOk.Foo();  // MethodTimer is not working

Derived.cs

namespace Assembly2;

class DerivedMethodTimerWorking : Assembly1.Base
{
    public override Foo()
    {
    }
}

class DerivedMethodTimerNotWorking : Assembly1.Base
{
    public Bar()
    {
    }
}

I would like that MethodTimer would work in DerivedMethodTimerNotWorking.Foo even though it is not overriden in Assembly2 and the virtual method source is in NuGet Assembly1

What I tried to do was to add public override Foo() to DerivedMethodTimerNotWorking with Mono.Cecil but it doesn't compile / build in my Blazor project.