diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..1650286 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.{cs,vb}] + +# IDE0130: 命名空间与文件夹结构不匹配 +dotnet_diagnostic.IDE0130.severity = none diff --git a/.github/workflows/push_nuget.yml b/.github/workflows/push_nuget.yml index 1c1eeaf..ec9d1f2 100644 --- a/.github/workflows/push_nuget.yml +++ b/.github/workflows/push_nuget.yml @@ -49,5 +49,12 @@ jobs: - name: Push run: | dotnet nuget push 'publish/genmapper/*.nupkg' -s https://api.nuget.org/v3/index.json -k ${{secrets.GEN_MAPPER}} --skip-duplicate + # AutoAopProxyGenerator + - name: Pack + run: | + dotnet pack ./src/AutoAopProxyGenerator/AutoAopProxyGenerator.csproj -c Release -o publish/genaopproxy + - name: Push + run: | + dotnet nuget push 'publish/genaopproxy/*.nupkg' -s https://api.nuget.org/v3/index.json -k ${{secrets.GEN_AOPPROXY}} --skip-duplicate diff --git a/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs b/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs new file mode 100644 index 0000000..03a1dea --- /dev/null +++ b/src/AutoAopProxy.Roslyn/AutoAopProxyClassGenerator.cs @@ -0,0 +1,251 @@ +using Generators.Shared; +using Generators.Shared.Builder; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace AutoAopProxyGenerator; + +[Generator(LanguageNames.CSharp)] +public class AutoAopProxyClassGenerator : IIncrementalGenerator +{ + public const string Aspectable = "AutoAopProxyGenerator.GenAspectProxyAttribute"; + public const string AspectHandler = "AutoAopProxyGenerator.AddAspectHandlerAttribute"; + public const string IgnoreAspect = "AutoAopProxyGenerator.IgnoreAspectAttribute"; + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var source = context.SyntaxProvider.ForAttributeWithMetadataName( + Aspectable + , static (node, _) => node is ClassDeclarationSyntax + , static (ctx, _) => ctx); + context.RegisterSourceOutput(source, static (context, source) => + { + var targetSymbol = (INamedTypeSymbol)source.TargetSymbol; + //INamedTypeSymbol[] needToCheckSymbol = [targetSymbol, .. targetSymbol.AllInterfaces]; + + + var allInterfaces = targetSymbol.AllInterfaces.Where(a => a.HasAttribute(AspectHandler)).ToArray(); + + #region 检查Attribute标注是否合法 + bool pass = true; + foreach (var i in allInterfaces) + { + if (!pass) + { + break; + } + var all = i.GetAttributes(AspectHandler); + foreach (var a in all) + { + var at = a.GetNamedValue("AspectType"); + if (at == null) + { + context.ReportDiagnostic(DiagnosticDefinitions.AAPG00001(source.TargetNode.GetLocation())); + pass = false; + break; + } + var att = (INamedTypeSymbol)at; + if (!att.HasInterface("AutoAopProxyGenerator.IAspectHandler")) + { + context.ReportDiagnostic(DiagnosticDefinitions.AAPG00002(source.TargetNode.GetLocation())); + pass = false; + break; + } + } + } + if (!pass) + { + return; + } + #endregion + + #region 获取所有的AspectHandler + var allHandlers = allInterfaces.SelectMany(x => + { + var handlers = x.GetAttributes(AspectHandler).Select(a => + { + var at = a.GetNamedValue("AspectType"); + if (at == null) + { + context.ReportDiagnostic(DiagnosticDefinitions.AAPG00001(source.TargetNode.GetLocation())); + return null; + } + var att = (INamedTypeSymbol)at; + if (!att.HasInterface("AutoAopProxyGenerator.IAspectHandler")) + { + context.ReportDiagnostic(DiagnosticDefinitions.AAPG00002(source.TargetNode.GetLocation())); + return null; + } + return att; + }); + return handlers.Where(i => i != null).Cast(); + }).Distinct(EqualityComparer.Default).ToArray(); + #endregion + + var file = CreateGeneratedProxyClassFile(targetSymbol, allInterfaces, allHandlers); + if (file != null) + { + //var ss = file.ToString(); + context.AddSource(file); + } + }); + } + + private static CodeFile? CreateGeneratedProxyClassFile(INamedTypeSymbol classSymbol, INamedTypeSymbol[] interfaces, INamedTypeSymbol[] allHandlers) + { + // 代理字段和aspect handler 字段 + List members = [ + FieldBuilder.Default.MemberType(classSymbol.ToDisplayString()).FieldName("proxy") + , .. allHandlers.Select(n =>FieldBuilder.Default.MemberType(n.ToDisplayString()).FieldName(n.MetadataName)) + ]; + // 构造函数 + List ctorbody = [ + "this.proxy = proxy" + , ..allHandlers.Select(n => $"this.{n.MetadataName} = {n.MetadataName}") + ]; + var ctor = ConstructorBuilder.Default.MethodName($"{classSymbol.FormatClassName()}GeneratedProxy") + .AddParameter([$"{classSymbol.ToDisplayString()} proxy", .. allHandlers.Select(n => $"{n.ToDisplayString()} {n.MetadataName}")]).AddBody([.. ctorbody]); + members.Add(ctor); + // 接口方法 + foreach (var iface in interfaces) + { + members.AddRange(CreateProxyMethod(iface, classSymbol)); + } + + var proxyClass = ClassBuilder.Default + .ClassName($"{classSymbol.FormatClassName()}GeneratedProxy") + .AddMembers([.. members]) + .Interface([.. interfaces.Select(i => i.ToDisplayString())]) + .AddGeneratedCodeAttribute(typeof(AutoAopProxyClassGenerator)); + + return CodeFile.New($"{classSymbol.FormatFileName()}GeneratedProxyClass.g.cs") + .AddUsings("using AutoAopProxyGenerator;") + .AddMembers(NamespaceBuilder.Default.Namespace(classSymbol.ContainingNamespace.ToDisplayString()).AddMembers(proxyClass)); + } + + private static IEnumerable CreateProxyMethod(INamedTypeSymbol iface, INamedTypeSymbol classSymbol) + { + var handlers = iface.GetAttributes(AspectHandler).Select(a => a.GetNamedValue("AspectType")).OfType().ToArray(); + + foreach (var m in iface.GetMethods()) + { + MethodBuilder methodBuilder; + if (m.HasAttribute(IgnoreAspect)) + { + methodBuilder = CreateDirectInvokeMethod(); + } + else + { + methodBuilder = CreateProxyMethod(); + } + + yield return methodBuilder; + + MethodBuilder CreateDirectInvokeMethod() + { + var builder = MethodBuilder.Default + .MethodName(m.Name) + .Generic([.. m.GetTypeParameters()]) + .AddParameter([.. m.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}")]) + .ReturnType(m.ReturnType.ToDisplayString()) + .AddGeneratedCodeAttribute(typeof(AutoAopProxyClassGenerator)); + builder = builder + .Lambda($"proxy.{builder.ConstructedMethodName}({string.Join(", ", m.Parameters.Select(p => p.Name))})"); + + return builder; + } + + MethodBuilder CreateProxyMethod() + { + var method = m.IsGenericMethod ? m.ConstructedFrom : m; + var returnType = method.ReturnType.GetGenericTypes().FirstOrDefault() ?? method.ReturnType; + var isAsync = method.IsAsync || method.ReturnType.ToDisplayString().StartsWith("System.Threading.Tasks.Task"); + var builder = MethodBuilder.Default + .MethodName(method.Name) + .Async(isAsync) + .Generic([.. method.GetTypeParameters()]) + .AddParameter([.. method.Parameters.Select(p => $"{p.Type.ToDisplayString()} {p.Name}")]) + .ReturnType(method.ReturnType.ToDisplayString()) + .AddGeneratedCodeAttribute(typeof(AutoAopProxyClassGenerator)); + List statements = []; + var hasReturn = !method.ReturnsVoid && returnType.ToDisplayString() != "System.Threading.Tasks.Task"; + if (hasReturn) + { + statements.Add($"{returnType.ToDisplayString()} returnValue = default;"); + } + var done = LocalFunction.Default + .MethodName("Done") + .AddParameters("ProxyContext ctx") + .Async(isAsync) + .Return("System.Threading.Tasks.Task") + .AddBody([.. CreateLocalFunctionBody(method, builder.ConstructedMethodName, isAsync, hasReturn)]); + + statements.Add(done); + statements.Add("var builder = AsyncPipelineBuilder.Create(Done)"); + foreach (var handler in handlers) + { + statements.Add($"builder.Use({handler.MetadataName}.Invoke)"); + } + statements.Add("var job = builder.Build()"); + var ptypes = method.Parameters.Length > 0 ? $"[{string.Join(", ", method.Parameters.Select(p => $"typeof({p.Type.ToDisplayString()})"))}]" : "Type.EmptyTypes"; + statements.Add($"var context = ContextHelper<{iface.ToDisplayString()}, {classSymbol.ToDisplayString()}>.GetOrCreate(nameof({method.Name}), {ptypes})"); + if (hasReturn) + { + statements.Add("context.HasReturnValue = true"); + } + statements.Add($"context.Parameters = new object?[] {{{string.Join(", ", method.Parameters.Select(p => p.Name))}}};"); + if (isAsync) + { + statements.Add("await job.Invoke(context)"); + } + else + { + statements.Add("job.Invoke(context).GetAwaiter().GetResult()"); + } + if (hasReturn) + statements.Add("return returnValue"); + + builder.AddBody([.. statements]); + + return builder; + } + } + } + + private static IEnumerable CreateLocalFunctionBody(IMethodSymbol method, string proxyName, bool isAsync, bool hasReturn) + { + if (isAsync) + { + if (hasReturn) + { + yield return $"returnValue = await proxy.{proxyName}({string.Join(", ", method.Parameters.Select(p => p.Name))})"; + yield return "ctx.Executed = true"; + yield return "ctx.ReturnValue = returnValue"; + } + else + { + yield return $"await proxy.{proxyName}({string.Join(", ", method.Parameters.Select(p => p.Name))})"; + yield return "ctx.Executed = true"; + } + } + else + { + if (hasReturn) + { + yield return $"returnValue = proxy.{proxyName}({string.Join(", ", method.Parameters.Select(p => p.Name))})"; + yield return "ctx.Executed = true"; + yield return "ctx.ReturnValue = returnValue"; + yield return "return global::System.Threading.Tasks.Task.CompletedTask"; + } + else + { + yield return $"proxy.{proxyName}({string.Join(", ", method.Parameters.Select(p => p.Name))})"; + yield return "ctx.Executed = true"; + yield return "return global::System.Threading.Tasks.Task.CompletedTask"; + } + } + } +} diff --git a/src/AutoAopProxy.Roslyn/DiagnosticDefinitions.cs b/src/AutoAopProxy.Roslyn/DiagnosticDefinitions.cs new file mode 100644 index 0000000..9410d4b --- /dev/null +++ b/src/AutoAopProxy.Roslyn/DiagnosticDefinitions.cs @@ -0,0 +1,36 @@ +using Microsoft.CodeAnalysis; +using System; +using System.Collections.Generic; +using System.Text; + +namespace AutoAopProxyGenerator +{ + internal class DiagnosticDefinitions + { + /// + /// AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不能为null + /// + /// + /// + public static Diagnostic AAPG00001(Location? location) => Diagnostic.Create(new DiagnosticDescriptor( + id: "AAPG00001", + title: "AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不能为null", + messageFormat: "AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不能为null", + category: typeof(AutoAopProxyClassGenerator).FullName!, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true), location); + + /// + /// AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不实现IAspectHandler + /// + /// + /// + public static Diagnostic AAPG00002(Location? location) => Diagnostic.Create(new DiagnosticDescriptor( + id: "AAPG00002", + title: "AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不实现IAspectHandler", + messageFormat: "AutoAopProxyGenerator.AddAspectHandlerAttribute.AspectType不实现IAspectHandler", + category: typeof(AutoAopProxyClassGenerator).FullName!, + defaultSeverity: DiagnosticSeverity.Error, + isEnabledByDefault: true), location); + } +} diff --git a/src/AutoAopProxy.Roslyn/Properties/launchSettings.json b/src/AutoAopProxy.Roslyn/Properties/launchSettings.json new file mode 100644 index 0000000..dadeb32 --- /dev/null +++ b/src/AutoAopProxy.Roslyn/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "aop": { + "commandName": "DebugRoslynComponent", + "targetProject": "..\\TestProject1\\TestProject1.csproj" + } + } +} \ No newline at end of file diff --git a/src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.cs b/src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.cs new file mode 100644 index 0000000..2a57860 --- /dev/null +++ b/src/AutoAopProxyGenerator/Attributes/AddAspectHandlerAttribute.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace AutoAopProxyGenerator; + +/// +/// 在类或者接口上配置切面处理 +/// +[AttributeUsage(AttributeTargets.Interface, AllowMultiple = true)] +public sealed class AddAspectHandlerAttribute : Attribute +{ + public Type? AspectType { get; set; } +} diff --git a/src/AutoAopProxyGenerator/Attributes/AspectInterceptorAttribute.cs b/src/AutoAopProxyGenerator/Attributes/AspectInterceptorAttribute.cs deleted file mode 100644 index 5e751b3..0000000 --- a/src/AutoAopProxyGenerator/Attributes/AspectInterceptorAttribute.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using System.Threading.Tasks; - -namespace AutoAopProxyGenerator -{ - public abstract class AspectInterceptorAttribute : Attribute - { - public abstract Task Invoke(ProxyContext context, Func process); - } -} diff --git a/src/AutoAopProxyGenerator/Attributes/GenAspectProxyAttribute.cs b/src/AutoAopProxyGenerator/Attributes/GenAspectProxyAttribute.cs new file mode 100644 index 0000000..f1f113b --- /dev/null +++ b/src/AutoAopProxyGenerator/Attributes/GenAspectProxyAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace AutoAopProxyGenerator; + +/// +/// 标记需要生成代理类的类型 +/// +[AttributeUsage(AttributeTargets.Class)] +public sealed class GenAspectProxyAttribute : Attribute +{ + +} diff --git a/src/AutoAopProxyGenerator/Attributes/IgnoreAspectAttribute.cs b/src/AutoAopProxyGenerator/Attributes/IgnoreAspectAttribute.cs new file mode 100644 index 0000000..9cf2343 --- /dev/null +++ b/src/AutoAopProxyGenerator/Attributes/IgnoreAspectAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace AutoAopProxyGenerator; + +/// +/// 配置不需要切面的方法 +/// +[AttributeUsage(AttributeTargets.Method)] +public sealed class IgnoreAspectAttribute : Attribute +{ + +} diff --git a/src/AutoAopProxyGenerator/AutoAopProxyServiceProviderFactory.cs b/src/AutoAopProxyGenerator/AutoAopProxyServiceProviderFactory.cs index 70f8760..13348b2 100644 --- a/src/AutoAopProxyGenerator/AutoAopProxyServiceProviderFactory.cs +++ b/src/AutoAopProxyGenerator/AutoAopProxyServiceProviderFactory.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.DependencyInjection; using System; using System.Collections.Generic; +using System.Reflection; using System.Text; namespace AutoAopProxyGenerator @@ -15,11 +16,98 @@ public IServiceCollection CreateBuilder(IServiceCollection services) public IServiceProvider CreateServiceProvider(IServiceCollection containerBuilder) { IServiceCollection serviceCollection = new ServiceCollection(); - foreach (var service in containerBuilder) + foreach (var sd in containerBuilder) { - //ServiceDescriptor. + //service. + var implType = GetImplType(sd); + if (implType?.GetCustomAttribute() != null) + { + var proxyType = implType.Assembly.GetType($"{implType.FullName}GeneratedProxy"); + if (proxyType != null) + { + AddServiceDescriptors(serviceCollection, sd, implType, proxyType); + continue; + } + } + serviceCollection.Add(sd); } return serviceCollection.BuildServiceProvider(); + + static Type? GetImplType(ServiceDescriptor sd) + { +#if NET8_0_OR_GREATER + if (sd.IsKeyedService) + { + if (sd.KeyedImplementationType != null) + { + return sd.KeyedImplementationType; + } + else if (sd.KeyedImplementationInstance != null) + { + return sd.KeyedImplementationInstance.GetType(); + } + else if (sd.KeyedImplementationFactory != null) + { + var typeArguments = sd.KeyedImplementationFactory.GetType().GenericTypeArguments; + + return typeArguments[1]; + } + + return null; + } +#endif + if (sd.ImplementationType != null) + { + return sd.ImplementationType; + } + else if (sd.ImplementationInstance != null) + { + return sd.ImplementationInstance.GetType(); + } + else if (sd.ImplementationFactory != null) + { + var typeArguments = sd.ImplementationFactory.GetType().GenericTypeArguments; + + return typeArguments[1]; + } + + return null; + } + } + + + private static void AddServiceDescriptors(IServiceCollection serviceCollection, ServiceDescriptor sd, Type implType, Type proxyType) + { + // 将原实现注册为自身,在代理类中注入 + var nsd = sd.IsKeyedService + ? ServiceDescriptor.DescribeKeyed( + implType, + sd.ServiceKey, + implType, + sd.Lifetime + ) + : ServiceDescriptor.Describe( + implType, + implType, + sd.Lifetime + ); + serviceCollection.Add(nsd); + + var proxySd = sd.IsKeyedService + ? ServiceDescriptor.DescribeKeyed( + sd.ServiceType, + sd.ServiceKey, + proxyType, + sd.Lifetime + ) + : ServiceDescriptor.Describe( + sd.ServiceType, + proxyType, + sd.Lifetime + ); + + serviceCollection.Add(nsd); + serviceCollection.Add(proxySd); } } } diff --git a/src/AutoAopProxyGenerator/ContextHelper.cs b/src/AutoAopProxyGenerator/ContextHelper.cs new file mode 100644 index 0000000..aaf8245 --- /dev/null +++ b/src/AutoAopProxyGenerator/ContextHelper.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; + +namespace AutoAopProxyGenerator; + +public static class ContextHelper +{ + private static readonly ConcurrentDictionary caches = []; + private static readonly Type ServiceType = typeof(TService); + private static readonly Type ImplType = typeof(TImpl); + public static ProxyContext GetOrCreate(string methodName, Type[] types) + { + var key = $"{ServiceType.FullName}_{ImplType.FullName}_{methodName}_{string.Join("_", types.Select(t => t.Name))}"; + return caches.GetOrAdd(key, (k) => + { + var serviceMethod = ServiceType.GetMethod(methodName, types); + var implMethod = ImplType.GetMethod(methodName, types); + return new ProxyContext() + { + ServiceType = ServiceType, + ImplementType = ImplType, + ServiceMethod = serviceMethod, + ImplementMethod = implMethod + }; + }); + + } +} + diff --git a/src/AutoAopProxyGenerator/IAspectHandler.cs b/src/AutoAopProxyGenerator/IAspectHandler.cs new file mode 100644 index 0000000..250b069 --- /dev/null +++ b/src/AutoAopProxyGenerator/IAspectHandler.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Concurrent; +using System.Linq; +using System.Threading.Tasks; + +namespace AutoAopProxyGenerator; + +public interface IAspectHandler +{ + Task Invoke(ProxyContext context, Func process); +} + diff --git a/src/AutoAopProxyGenerator/Models/ProxyContext.cs b/src/AutoAopProxyGenerator/Models/ProxyContext.cs index bfe9adf..2982d90 100644 --- a/src/AutoAopProxyGenerator/Models/ProxyContext.cs +++ b/src/AutoAopProxyGenerator/Models/ProxyContext.cs @@ -1,10 +1,28 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Text; namespace AutoAopProxyGenerator { - public class ProxyContext + public record ProxyContext { + public ProxyContext(IServiceProvider services) + { + Services = services; + } + public ProxyContext() + { + + } + public bool Executed { get; set; } + public bool HasReturnValue { get; set; } + public object? ReturnValue { get; set; } + public object?[] Parameters { get; set; } = []; + public IServiceProvider? Services { get; } + public Type? ServiceType { get; set; } + public Type? ImplementType { get; set; } + public MethodInfo? ServiceMethod { get; set; } + public MethodInfo? ImplementMethod { get; set; } } } diff --git a/src/AutoAopProxyGenerator/PipelineBuilder.cs b/src/AutoAopProxyGenerator/PipelineBuilder.cs new file mode 100644 index 0000000..99aa62d --- /dev/null +++ b/src/AutoAopProxyGenerator/PipelineBuilder.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using System; +using System.Linq; + +namespace AutoAopProxyGenerator; + +public class PipelineBuilder +{ + private readonly Action completeAction; + private readonly IList, Action>> pipelines = []; + public static PipelineBuilder Create(Action completeAction) => new(completeAction); + public PipelineBuilder(Action completeAction) + { + this.completeAction = completeAction; + } + public PipelineBuilder Use(Func, Action> middleware) + { + pipelines.Add(middleware); + return this; + } + public Action Build() + { + var request = completeAction; + foreach (var pipeline in pipelines.Reverse()) + { + request = pipeline(request); + } + return request; + } +} + +public class AsyncPipelineBuilder +{ + private readonly Func completeFunc; + private readonly IList, Func>> pipelines = []; + public static AsyncPipelineBuilder Create(Func completeFunc) => new(completeFunc); + public AsyncPipelineBuilder(Func completeFunc) + { + this.completeFunc = completeFunc; + } + + public AsyncPipelineBuilder Use(Func, Func> middleware) + { + pipelines.Add(middleware); + return this; + } + + + public Func Build() + { + var request = completeFunc; + foreach (var pipeline in pipelines.Reverse()) + { + request = pipeline(request); + } + return request; + } +} + +public static class PipeLineBuilderExtensions +{ + public static PipelineBuilder Use(this PipelineBuilder builder, Action action) + { + builder.Use(next => + { + return context => + { + action(context, () => next(context)); + }; + }); + return builder; + } + + public static AsyncPipelineBuilder Use(this AsyncPipelineBuilder builder, Func, Task> asyncaction) + { + builder.Use(next => + { + return context => + { + return asyncaction(context, () => next(context)); + }; + }); + return builder; + } +} diff --git a/src/AutoAopProxyGenerator/readme.md b/src/AutoAopProxyGenerator/readme.md index f4fa8bc..4dd4f72 100644 --- a/src/AutoAopProxyGenerator/readme.md +++ b/src/AutoAopProxyGenerator/readme.md @@ -1,5 +1,7 @@ -`AutoInjectAttribute` +`AddAspectHandlerAttribute` -`AutoInjectContextAttribute` +`GenAspectProxyAttribute` -`AutoInjectConfiguration` \ No newline at end of file +`IgnoreAspectAttribute` + +`IAspectHandler` \ No newline at end of file diff --git a/src/AutoWasmApi.Roslyn/GeneratorHepers.cs b/src/AutoWasmApi.Roslyn/GeneratorHepers.cs index ed4fa1d..0414f24 100644 --- a/src/AutoWasmApi.Roslyn/GeneratorHepers.cs +++ b/src/AutoWasmApi.Roslyn/GeneratorHepers.cs @@ -57,13 +57,13 @@ public static IEnumerable GetAllMethods(this INamedTypeSymbol? sy } - public static CompilationUnitSyntax CreateCompilationUnit(this GeneratorAttributeSyntaxContext source) - { + //public static CompilationUnitSyntax CreateCompilationUnit(this GeneratorAttributeSyntaxContext source) + //{ //CompilationUnit() // .AddMembers(NamespaceDeclaration()) - return CompilationUnit(); - } + //return CompilationUnit(); + //} //public static NamespaceDeclarationSyntax CreateNamespaceDeclaration(this GeneratorAttributeSyntaxContext source, out UsingDirectiveSyntax[] usings) //{ diff --git a/src/AutoWasmApi.Roslyn/usings.cs b/src/AutoWasmApi.Roslyn/usings.cs index fe15e32..5764cf7 100644 --- a/src/AutoWasmApi.Roslyn/usings.cs +++ b/src/AutoWasmApi.Roslyn/usings.cs @@ -1,4 +1,3 @@ -global using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory; -using System.Runtime.CompilerServices; +using System.Runtime.CompilerServices; [assembly:InternalsVisibleTo("TestProject1")] \ No newline at end of file diff --git a/src/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj index 137532a..ded5bd7 100644 --- a/src/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj +++ b/src/AutoWasmApiGenerator/AutoWasmApiGenerator.csproj @@ -15,7 +15,7 @@ - + @@ -24,7 +24,7 @@ - + diff --git a/src/Blazor.Test/Blazor.Test.Client/Aop/ExceptionAop.cs b/src/Blazor.Test/Blazor.Test.Client/Aop/ExceptionAop.cs new file mode 100644 index 0000000..7045531 --- /dev/null +++ b/src/Blazor.Test/Blazor.Test.Client/Aop/ExceptionAop.cs @@ -0,0 +1,25 @@ +using AutoAopProxyGenerator; + +namespace Blazor.Test.Client.Aop +{ + public class ExceptionAop : IAspectHandler + { + private readonly ILogger logger; + + public ExceptionAop(ILogger logger) + { + this.logger = logger; + } + public async Task Invoke(ProxyContext context, Func process) + { + try + { + await process(); + } + catch (Exception ex) + { + logger.LogError("Exception {Message}", ex.Message); + } + } + } +} diff --git a/src/Blazor.Test/Blazor.Test.Client/Aop/TestAop.cs b/src/Blazor.Test/Blazor.Test.Client/Aop/TestAop.cs new file mode 100644 index 0000000..6623472 --- /dev/null +++ b/src/Blazor.Test/Blazor.Test.Client/Aop/TestAop.cs @@ -0,0 +1,20 @@ +using AutoAopProxyGenerator; + +namespace Blazor.Test.Client.Aop +{ + public class TestAop : IAspectHandler + { + private readonly ILogger logger; + + public TestAop(ILogger logger) + { + this.logger = logger; + } + public async Task Invoke(ProxyContext context, Func process) + { + logger.LogInformation("执行前"); + await process(); + logger.LogInformation("执行后"); + } + } +} diff --git a/src/Blazor.Test/Blazor.Test.Client/Blazor.Test.Client.csproj b/src/Blazor.Test/Blazor.Test.Client/Blazor.Test.Client.csproj index 6a4f756..b996675 100644 --- a/src/Blazor.Test/Blazor.Test.Client/Blazor.Test.Client.csproj +++ b/src/Blazor.Test/Blazor.Test.Client/Blazor.Test.Client.csproj @@ -13,7 +13,9 @@ - + + + diff --git a/src/Blazor.Test/Blazor.Test.Client/Pages/Counter.razor b/src/Blazor.Test/Blazor.Test.Client/Pages/Counter.razor index ef23cb3..608d4e4 100644 --- a/src/Blazor.Test/Blazor.Test.Client/Pages/Counter.razor +++ b/src/Blazor.Test/Blazor.Test.Client/Pages/Counter.razor @@ -1,4 +1,6 @@ @page "/counter" +@using Blazor.Test.Client.Services + Counter @@ -7,12 +9,15 @@

Current count: @currentCount

- +SayHello Response: @response @code { private int currentCount = 0; + string? response; + [Inject, NotNull] IHelloService? Service { get; set; } - private void IncrementCount() + private async Task IncrementCount() { currentCount++; + response = await Service.SayHelloAsync($"Marvel{currentCount}"); } } diff --git a/src/Blazor.Test/Blazor.Test.Client/Services/IHelloService.cs b/src/Blazor.Test/Blazor.Test.Client/Services/IHelloService.cs new file mode 100644 index 0000000..bf18308 --- /dev/null +++ b/src/Blazor.Test/Blazor.Test.Client/Services/IHelloService.cs @@ -0,0 +1,36 @@ +using AutoAopProxyGenerator; +using Blazor.Test.Client.Aop; + +namespace Blazor.Test.Client.Services; + +[AddAspectHandler(AspectType = typeof(ExceptionAop))] +[AddAspectHandler(AspectType = typeof(TestAop))] +public interface IHelloService +{ + Task SayHelloAsync(string name); + [IgnoreAspect] + string SayHelloDirectly(string name); +} + +[GenAspectProxy] +public class HelloService : IHelloService +{ + public async Task 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}!"; + } +} diff --git a/src/Blazor.Test/Blazor.Test.Client/_Imports.razor b/src/Blazor.Test/Blazor.Test.Client/_Imports.razor index d0955d6..c4903f9 100644 --- a/src/Blazor.Test/Blazor.Test.Client/_Imports.razor +++ b/src/Blazor.Test/Blazor.Test.Client/_Imports.razor @@ -7,3 +7,4 @@ @using Microsoft.AspNetCore.Components.Web.Virtualization @using Microsoft.JSInterop @using Blazor.Test.Client +@using System.Diagnostics.CodeAnalysis diff --git a/src/Blazor.Test/Blazor.Test/Components/App.razor b/src/Blazor.Test/Blazor.Test/Components/App.razor index 698deac..0a3e6d3 100644 --- a/src/Blazor.Test/Blazor.Test/Components/App.razor +++ b/src/Blazor.Test/Blazor.Test/Components/App.razor @@ -9,11 +9,11 @@ - + - + diff --git a/src/Blazor.Test/Blazor.Test/Program.cs b/src/Blazor.Test/Blazor.Test/Program.cs index d18c87a..d7befdd 100644 --- a/src/Blazor.Test/Blazor.Test/Program.cs +++ b/src/Blazor.Test/Blazor.Test/Program.cs @@ -1,4 +1,6 @@ +using Blazor.Test.Client.Aop; using Blazor.Test.Client.Pages; +using Blazor.Test.Client.Services; using Blazor.Test.Components; [assembly:AutoWasmApiGenerator.WebControllerAssembly] @@ -10,8 +12,16 @@ .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Services.AddScoped(); +builder.Host.UseServiceProviderFactory(new AutoAopProxyGenerator.AutoAopProxyServiceProviderFactory()); + + var app = builder.Build(); + + // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment()) { diff --git a/src/Generators.Shared/Builder/MethodBuilder.cs b/src/Generators.Shared/Builder/MethodBuilder.cs index be018a0..8e437e5 100644 --- a/src/Generators.Shared/Builder/MethodBuilder.cs +++ b/src/Generators.Shared/Builder/MethodBuilder.cs @@ -12,11 +12,11 @@ public MethodBuilder() Modifiers = "public"; } public override NodeType Type => NodeType.Method; - public bool IsAsync { get; set; } public bool IsLambdaBody { get; set; } + public bool IsAsync { get; set; } string Async => IsAsync ? " async " : " "; public string? ReturnType { get; set; } = "void"; - + public string ConstructedMethodName => $"{Name}{Types}"; public override string ToString() { if (IsLambdaBody) @@ -191,3 +191,28 @@ public override string ToString() """; } } + +internal class LocalFunction : Statement +{ + public LocalFunction() : base("") + { + + } + public static LocalFunction Default => new LocalFunction(); + public string? ReturnType { get; set; } + public string? Name { get; set; } + public bool IsAsync { get; set; } + string Async => IsAsync ? "async " : ""; + public List Parameters { get; set; } = []; + public List Body { get; set; } = []; + public override string ToString() + { + return +$$""" +{{Indent}}{{Async}}{{ReturnType}} {{Name}}({{string.Join(", ", Parameters)}}) +{{Indent}}{ +{{string.Join("\n", Body.Select(s => $" {s}"))}} +{{Indent}}} +"""; + } +} diff --git a/src/Generators.Shared/Builder/TypeBuilder.cs b/src/Generators.Shared/Builder/TypeBuilder.cs index dfda864..2a2c114 100644 --- a/src/Generators.Shared/Builder/TypeBuilder.cs +++ b/src/Generators.Shared/Builder/TypeBuilder.cs @@ -36,7 +36,7 @@ protected string TypeConstraints get { if (TypeArguments.Count == 0) return ""; - return $"\n{string.Join("\n", TypeArguments.Select(ta => $"{Indent} where {ta.Name} : {string.Join(", ", ta.Constraints)}"))}"; + return $"\n{string.Join("\n", TypeArguments.Where(ta => ta.Constraints.Length > 0).Select(ta => $"{Indent} where {ta.Name} : {string.Join(", ", ta.Constraints)}"))}"; } } } diff --git a/src/Generators.Shared/Extensions/MethodBuilderExtensions.cs b/src/Generators.Shared/Extensions/MethodBuilderExtensions.cs index f0a197f..cae8dd8 100644 --- a/src/Generators.Shared/Extensions/MethodBuilderExtensions.cs +++ b/src/Generators.Shared/Extensions/MethodBuilderExtensions.cs @@ -30,9 +30,9 @@ public static MethodBuilder ReturnType(this MethodBuilder builder, string return builder.ReturnType = returnType; return builder; } - public static MethodBuilder Async(this MethodBuilder builder) + public static MethodBuilder Async(this MethodBuilder builder, bool isAsync = true) { - builder.IsAsync = true; + builder.IsAsync = isAsync; return builder; } @@ -126,4 +126,49 @@ public static IfStatement AddStatement(this IfStatement ifStatement, params stri return ifStatement; } #endregion + + #region LocalFunction + + public static T AddLocalFunction(this T builder, Action action) where T : MethodBase + { + var lf = LocalFunction.Default; + action.Invoke(lf); + builder.AddBody(lf); + return builder; + } + + public static LocalFunction MethodName(this LocalFunction localFunction, string name) + { + localFunction.Name = name; + return localFunction; + } + + public static LocalFunction Async(this LocalFunction localFunction, bool isAsync = true) + { + localFunction.IsAsync = isAsync; + return localFunction; + } + + public static LocalFunction Return(this LocalFunction localFunction, string returnType) + { + localFunction.ReturnType = returnType; + return localFunction; + } + + public static LocalFunction AddParameters(this LocalFunction localFunction, params string[] parameters) + { + foreach(var parameter in parameters) + { + localFunction.Parameters.Add(parameter); + } + return localFunction; + } + + public static LocalFunction AddBody(this LocalFunction localFunction, params Statement[] body) + { + localFunction.Body.AddRange(body); + return localFunction; + } + + #endregion } diff --git a/src/Generators.Shared/RoslynExtensions.cs b/src/Generators.Shared/RoslynExtensions.cs index 4ed10f4..ef64af1 100644 --- a/src/Generators.Shared/RoslynExtensions.cs +++ b/src/Generators.Shared/RoslynExtensions.cs @@ -36,7 +36,13 @@ public static bool GetNamedValue(this AttributeData? a, string key, out object? value = t; return t != null; } - + /// + /// 获取指定索引的构造函数参数 + /// + /// + /// + /// + /// public static bool GetConstructorValue(this AttributeData a, int index, out object? value) { if (a.ConstructorArguments.Length <= index) @@ -47,7 +53,13 @@ public static bool GetConstructorValue(this AttributeData a, int index, out obje value = a.ConstructorArguments[index].Value; return true; } - + /// + /// 获取指定索引的构造函数参数 + /// + /// + /// + /// + /// public static bool GetConstructorValues(this AttributeData a, int index, out object?[] values) { if (a.ConstructorArguments.Length <= index) @@ -58,7 +70,6 @@ public static bool GetConstructorValues(this AttributeData a, int index, out obj values = a.ConstructorArguments[index].Values.Select(v => v.Value).ToArray(); return true; } - /// /// 根据名称获取attribute的值 /// @@ -109,6 +120,21 @@ public static bool HasInterfaceAll(this ITypeSymbol? symbol, string fullName) { return symbol?.AllInterfaces.Any(i => i.ToDisplayString() == fullName) == true; } + /// + /// 获取方法符号 + /// + /// + /// + public static IEnumerable GetMethods(this INamedTypeSymbol? symbol) + { + foreach(var item in symbol?.GetMembers() ?? []) + { + if (item.Kind == SymbolKind.Method && item is IMethodSymbol method) + { + yield return method; + } + } + } public static bool CheckDisableGenerator(this AnalyzerConfigOptionsProvider options, string key) { diff --git a/src/TestProject1/AopGeneratorTest/LogAop.cs b/src/TestProject1/AopGeneratorTest/LogAop.cs new file mode 100644 index 0000000..b27b7d0 --- /dev/null +++ b/src/TestProject1/AopGeneratorTest/LogAop.cs @@ -0,0 +1,19 @@ +using AutoAopProxyGenerator; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestProject1.AopGeneratorTest +{ + public class LogAop : IAspectHandler + { + public async Task Invoke(ProxyContext context, Func process) + { + Console.WriteLine("Before"); + await process.Invoke(); + Console.WriteLine("After"); + } + } +} diff --git a/src/TestProject1/AopGeneratorTest/PipeLineTest.cs b/src/TestProject1/AopGeneratorTest/PipeLineTest.cs new file mode 100644 index 0000000..b652404 --- /dev/null +++ b/src/TestProject1/AopGeneratorTest/PipeLineTest.cs @@ -0,0 +1,64 @@ +using AutoAopProxyGenerator; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestProject1.AopGeneratorTest +{ + [TestClass] + public class PipeLineTest + { + [TestMethod] + public void Test() + { + var builder = new PipelineBuilder(s => { Console.WriteLine("job done"); }); + builder.Use(next => + { + return s => + { + Console.WriteLine("job 1 before"); + next(s); + Console.WriteLine("job 1 after"); + }; + }).Use((context, next) => + { + next(); + }); + + var job = builder.Build(); + job.Invoke("hello"); + } + + public Task Done(ProxyContext ctx) + { + Console.WriteLine($"Invoke Actual Method Result: {ctx.Executed}"); + return Task.CompletedTask; + } + + public class LogAop + { + public Task Invoke(ProxyContext ctx, Func next) + { + Console.WriteLine("LogAop Invoke"); + return next(); + } + } + + [TestMethod] + public async Task AsyncTest() + { + var log = new LogAop(); + var builder = AsyncPipelineBuilder.Create(ctx => + { + ctx.Executed = true; + return Done(ctx); + }); + builder.Use(log.Invoke); + var job = builder.Build(); + ProxyContext context = new ProxyContext(); + await job.Invoke(context); + } + } +} diff --git a/src/TestProject1/AopGeneratorTest/ProxyTest.cs b/src/TestProject1/AopGeneratorTest/ProxyTest.cs new file mode 100644 index 0000000..47f5bef --- /dev/null +++ b/src/TestProject1/AopGeneratorTest/ProxyTest.cs @@ -0,0 +1,33 @@ +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestProject1.AopGeneratorTest +{ + [TestClass] + public class ProxyTest + { + [TestMethod] + public void TestReturn() + { + + } + + [TestMethod] + public async Task TestReturnAsync() + { + var services = new ServiceCollection(); + services.AddScoped(); + services.AddScoped(); + //services.AddScoped(); + + var provider = services.BuildServiceProvider(); + var hello = provider.GetService()!; + var i = await hello.CountAsync(); + Assert.IsTrue(3 == i); + } + } +} diff --git a/src/TestProject1/AopGeneratorTest/User.cs b/src/TestProject1/AopGeneratorTest/User.cs new file mode 100644 index 0000000..0cd10a6 --- /dev/null +++ b/src/TestProject1/AopGeneratorTest/User.cs @@ -0,0 +1,170 @@ +using AutoAopProxyGenerator; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace TestProject1.AopGeneratorTest +{ + [AddAspectHandler(AspectType = typeof(LogAop))] + public interface IHello + { + [IgnoreAspect] + void Hello(); + void Hello(string message); + Task HelloAsync(); + Task HelloAsync(string message); + int Count(); + int Count(string message); + Task CountAsync(); + [IgnoreAspect] + Task CountAsync(string message); + [IgnoreAspect] + Task RunJobAsync(); + [IgnoreAspect] + Task RunJobAsync(string message); + + } + + [GenAspectProxy] + public class User : IHello + { + public void Hello() + { + Console.WriteLine("Hello world"); + } + + public Task HelloAsync() + { + throw new NotImplementedException(); + } + public Task CountAsync() + { + return Task.FromResult(3); + } + + public int Count() + { + return 2; + } + + public Task RunJobAsync() + { + throw new NotImplementedException(); + } + + public void Hello(string message) + { + throw new NotImplementedException(); + } + + public Task HelloAsync(string message) + { + throw new NotImplementedException(); + } + + public int Count(string message) + { + throw new NotImplementedException(); + } + + public Task CountAsync(string message) + { + throw new NotImplementedException(); + } + + public Task RunJobAsync(string message) + { + throw new NotImplementedException(); + } + } + + //[GenAspectProxy] + ////[AddAspectHandler(AspectType = typeof(LogAop))] + //public class UserProxy : IHello + //{ + // private readonly User proxy; + // LogAop log; + // public UserProxy() + // { + // this.proxy = new User(); + // this.log = new LogAop(); + // } + + // public void Hello() + // { + // Task Done(ProxyContext ctx) + // { + // proxy.Hello(); + // ctx.Executed = true; + // return Task.CompletedTask; + // } + // var builder = AsyncPipelineBuilder.Create(Done); + // var job = builder.Build(); + // var context = ContextHelper.GetOrCreate(typeof(IHello), typeof(User), "Hello", Type.EmptyTypes); + + // job.Invoke(new ProxyContext()).GetAwaiter().GetResult(); + // } + + // public Task HelloAsync() + // { + // async Task done(ProxyContext ctx) + // { + // await proxy.HelloAsync(); + // ctx.Executed = true; + // } + // var builder = AsyncPipelineBuilder.Create(done); + // var job = builder.Build(); + // return job.Invoke(new ProxyContext()); + // } + + // public int Count() + // { + // int returnValue = default; + // Task done(ProxyContext ctx) + // { + // returnValue = proxy.Count(); + // ctx.ReturnValue = returnValue; + // ctx.Executed = true; + // return global::System.Threading.Tasks.Task.CompletedTask; + // } + // var builder = AsyncPipelineBuilder.Create(done); + // builder.Use(log.Invoke); + // var job = builder.Build(); + // job.Invoke(new ProxyContext()).GetAwaiter().GetResult(); + // return returnValue; + // } + + // public async Task CountAsync() + // { + // int returnValue = default; + // async Task done(ProxyContext ctx) + // { + // returnValue = await proxy.CountAsync(); + // ctx.ReturnValue = returnValue; + // ctx.Executed = true; + // } + // var builder = AsyncPipelineBuilder.Create(done); + // var job = builder.Build(); + // await job.Invoke(new ProxyContext()); + // return returnValue; + // } + + // public async Task RunJobAsync() + // { + // bool returnValue = default; + // async Task Done(ProxyContext ctx) + // { + // returnValue = await proxy.RunJobAsync(); + // ctx.ReturnValue = returnValue; + // ctx.Executed = true; + // } + // var builder = AsyncPipelineBuilder.Create(Done); + // var job = builder.Build(); + // await job.Invoke(new ProxyContext()); + // return returnValue; + // } + //} +} + diff --git a/src/TestProject1/Class1.cs b/src/TestProject1/Class1.cs deleted file mode 100644 index c58bde4..0000000 --- a/src/TestProject1/Class1.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace TestProject1 -{ - internal interface ITest - { - void Log(string message); - } - internal class Class1 : ITest - { - public void Log(string message) - { - throw new NotImplementedException(); - } - } -} diff --git a/src/TestProject1/TestProject1.csproj b/src/TestProject1/TestProject1.csproj index 9a372d8..e1e9b10 100644 --- a/src/TestProject1/TestProject1.csproj +++ b/src/TestProject1/TestProject1.csproj @@ -17,7 +17,9 @@ - + + +