增量生成器(Incremental Generator)实现AOP代理
本文介绍使用增量生成器生成AOP代理类
nuget包安装
dotnet add package AutoAopProxyGenerator --version 0.0.4
该nuget包主要是为一些类生成对应的代理类,以便实现一些aop操作,例如异常处理、日志记录等等。
该nuget包提供了3个attribute进行配置,以及一个接口
AddAspectHandlerAttribute
只适用于接口/接口上的方法,顾名思义,添加AOP处理类
GenAspectProxyAttribute
只适用于类,告诉生成器,需要为他生成代理类
IgnoreAspectAttribute
只适用于接口中的方法,不生成代理方法
IAspectHandler
AOP处理类需要实现的接口
接下来依然是使用一些简单的伪代码进行说明
声明一个接口,并且配置两个处理类
[AddAspectHandler(AspectType = typeof(ExceptionAop))]
[AddAspectHandler(AspectType = typeof(TestAop))]
public interface IHelloService
{
Task<string> SayHelloAsync(string name);
[IgnoreAspect]
string SayHelloDirectly(string name);
}
实现该接口,并标注GenAspectProxyAttribute
[GenAspectProxy]
public class HelloService : IHelloService
{
public async Task<string> SayHelloAsync(string name)
{
if (name.IndexOf("M") > -1)
{
throw new Exception("Name Error");
}
await Task.Delay(500);
return $"Hello, {name}!";
}
public string SayHelloDirectly(string name)
{
if (name.IndexOf("M") > -1)
{
throw new Exception("Name Error");
}
return $"Hello, {name}!";
}
}
使用IHelloService
,只需要正常的注入IHelloService
即可
@page "/counter"
@using Blazor.Test.Client.Services
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<p role="status">Current count: @currentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>
SayHello Response: @response
@code {
private int currentCount = 0;
string? response;
[Inject, NotNull] IHelloService? Service { get; set; }
private async Task IncrementCount()
{
currentCount++;
response = await Service.SayHelloAsync($"Marvel{currentCount}");
}
}
当然,要想获取服务的时候能获取到咱们自己生成的代理类,还需要一步设置
// 服务注册不必多说
builder.Services.AddScoped<IHelloService, HelloService>();
builder.Services.AddScoped<TestAop>();
builder.Services.AddScoped<ExceptionAop>();
// 关键是这里
builder.Host.UseServiceProviderFactory(new AutoAopProxyGenerator.AutoAopProxyServiceProviderFactory());
最后附上生成的代码,处理类的调用逻辑跟Asp.Net Core的中间件模式一样
using AutoAopProxyGenerator;
// <auto-generated/>
#pragma warning disable
#nullable enable
namespace Blazor.Test.Client.Services
{
[global::System.CodeDom.Compiler.GeneratedCode("AutoAopProxyGenerator.AutoAopProxyClassGenerator", "1.0.0.0")]
/// <inheritdoc/>
public partial class HelloServiceGeneratedProxy : Blazor.Test.Client.Services.IHelloService
{
private readonly Blazor.Test.Client.Services.HelloService proxy;
private readonly Blazor.Test.Client.Aop.ExceptionAop ExceptionAop;
private readonly Blazor.Test.Client.Aop.TestAop TestAop;
public HelloServiceGeneratedProxy(Blazor.Test.Client.Services.HelloService proxy, Blazor.Test.Client.Aop.ExceptionAop ExceptionAop, Blazor.Test.Client.Aop.TestAop TestAop)
{
this.proxy = proxy;
this.ExceptionAop = ExceptionAop;
this.TestAop = TestAop;
}
[global::System.CodeDom.Compiler.GeneratedCode("AutoAopProxyGenerator.AutoAopProxyClassGenerator", "1.0.0.0")]
public async System.Threading.Tasks.Task<string> SayHelloAsync(string name)
{
string returnValue = default;
async System.Threading.Tasks.Task Done(ProxyContext ctx)
{
returnValue = await proxy.SayHelloAsync(name);
ctx.Executed = true;
ctx.ReturnValue = returnValue;
}
var builder = AsyncPipelineBuilder<ProxyContext>.Create(Done);
builder.Use(ExceptionAop.Invoke);
builder.Use(TestAop.Invoke);
var job = builder.Build();
var context = ContextHelper<Blazor.Test.Client.Services.IHelloService, Blazor.Test.Client.Services.HelloService>.GetOrCreate(nameof(SayHelloAsync), [typeof(string)]);
context.HasReturnValue = true;
context.Parameters = new object?[] {name};
await job.Invoke(context);
return returnValue;
}
[global::System.CodeDom.Compiler.GeneratedCode("AutoAopProxyGenerator.AutoAopProxyClassGenerator", "1.0.0.0")]
public string SayHelloDirectly(string name)
=> proxy.SayHelloDirectly(name);
}
}