如何使用 Autofac.Extras.DynamicProxy 在 Blazor 组件生命周期方法上应用 AOP 日志记录?

How to apply AOP logging on Blazor component lifecycle method with Autofac.Extras.DynamicProxy?

我想用 Blazor WebAssembly 解决横切问题。 我想记录对特定组件的所有生命周期方法调用,如下所示。

<p>@nameof(BasicChild)</p>
<button @onclick="OnClick">StateHasChanged</button>

@code {
    public BasicChild()
    {
        Console.WriteLine("BasicChild()");
    }

    private int _initializedCount = 0;
    protected override void OnInitialized()
    {
        ++_initializedCount;
        var message = $"[{_initializedCount}]BasicChild.OnInitialized()";
        Console.WriteLine(message);
        base.OnInitialized();
    }

    private int _parametersSetCount = 0;
    protected override void OnParametersSet()
    {
        ++_parametersSetCount;
        var message = $"[{_parametersSetCount}]BasicChild.OnParametersSet()";
        Console.WriteLine(message);
        base.OnParametersSet();
    }

    private int _shouldRenderCount = 0;
    protected override bool ShouldRender()
    {
        ++_shouldRenderCount;
        var message = $"[{_shouldRenderCount}]BasicChild.ShouldRender()";
        Console.WriteLine(message);
        return base.ShouldRender();
    }

    private int _afterRenderCount = 0;
    protected override void OnAfterRender(bool firstRender)
    {
        ++_afterRenderCount;
        var message = $"[{_afterRenderCount}]BasicChild.OnAfterRender(firstRender: {firstRender})";
        Console.WriteLine(message);
        base.OnAfterRender(firstRender);
    }

    private void OnClick()
    {
        var message = $"BasicChild.StateHasChanged()";
        Console.WriteLine(message);
        StateHasChanged();
    }
}

我想用 Autofac.Extras.DynamicProxy 隐藏所有日志记录代码。但是我不确定如何将 Blazor 组件注册到 Container 并在生成 BuildRenderTree(RenderTreeBuilder) 方法时使用代理 class 而不是实际 class。

我知道我可以覆盖 BuildRenderTree(RenderTreeBuilder) 并从容器中解析代理组件。但我想继续使用 Blazor 语法。

有办法吗?

我想要的结果如下。

<p>@nameof(BasicChild)</p>
<button @onclick="OnClick">StateHasChanged</button>
public class Program
{
    private static void ConfigureContainer(ContainerBuilder builder)
    {
        builder.RegisterType<BasicChild>()
            .As<IComponentLifecycle>()
            // ...
            .EnableInterfaceInterceptors()
            .InterceptedBy(typeof(LoggingInterceptor));
    }
}

Github aspnetcore issue


2021.10.16更新

根据@javiercn,

If you are trying to inject dependencies into a component you can implement your own IComponentFactory and use your container inside to resolve dependencies if needed. If you are trying to render a component at runtime, you can use DynamicComponent.

我解决了!

public class ComponentActivator : IComponentActivator
{
    private readonly ILogger<ComponentActivator> _logger;
    private readonly IComponentFactory _componentFactory;

    public ComponentActivator(ILogger<ComponentActivator> logger, [NotNull] IComponentFactory componentFactory)
    {
        _logger = logger;
        _componentFactory = componentFactory ?? throw new ArgumentNullException(paramName: nameof(componentFactory));
    }

    public IComponent CreateInstance([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type componentType)
    {
        if (!typeof(IComponent).IsAssignableFrom(componentType))
            throw new ArgumentException($"{componentType.FullName} 타입은 {nameof(IComponent)} 인터페이스를 구현하지 않아, RenderTree 에 추가할 인스턴스를 만들 수 없습니다.", nameof(componentType));

        if(_componentFactory.CanCreate(componentType))
            return _componentFactory.Create(componentType);

        return (IComponent)Activator.CreateInstance(componentType)!;
    }
}
public interface IComponentFactory
{
    bool CanCreate(Type componentType);
    IComponent Create(Type componentType);
}
public class ComponentFactory : IComponentFactory
{
    private readonly ILogger<ComponentFactory> _logger;
    private readonly ILifetimeScope _container;

    public ComponentFactory(ILogger<ComponentFactory> logger, ILifetimeScope container)
    {
        _logger = logger;
        _container = container;
    }

    public bool CanCreate(Type componentType) => _container.TryResolve(componentType, out _);

    public IComponent Create(Type componentType)
    {
        var component = _container.Resolve(componentType);

        if (component is null)
            throw new InvalidOperationException($"{_container.GetType()} 으로 {componentType.FullName} 인스턴스를 만들지 못했습니다.");
        else if (component is not IComponent)
            throw new InvalidOperationException($"'{component.GetType()}' 은 {nameof(IComponent)} 인터페이스를 구현하지 않았습니다.");

        return (IComponent)component;
    }
}
public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);

        // https://autofac.readthedocs.io/en/latest/integration/blazor.html
        builder.ConfigureContainer(new AutofacServiceProviderFactory(ConfigureContainer));
        builder.Services.AddSingleton<IComponentFactory, ComponentFactory>();
        builder.Services.AddSingleton<IComponentActivator, ComponentActivator>();

        builder.RootComponents.Add<App>("#app");

        await builder.Build().RunAsync();
    }

    private static void ConfigureContainer(ContainerBuilder builder)
    {
        Castle.DynamicProxy.Generators.AttributesToAvoidReplicating.Add(typeof(System.Runtime.CompilerServices.AsyncStateMachineAttribute));

        builder.RegisterType<LoggingInterceptor>();
        
        builder
            .RegisterType<Account>()
            .EnableClassInterceptors()
            .InterceptedBy(typeof(LoggingInterceptor));
    }
}