如何使用 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));
}
}
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));
}
}
我想用 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));
}
}
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));
}
}