From ac1093aca58292964374010b560959a95922a820 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=87=E9=9B=85=E8=99=8E?= Date: Tue, 26 Mar 2024 17:50:39 +0800 Subject: [PATCH] #7 Support Auto Inject --- .../AutoInjectAttribute.cs | 31 +++ .../AutoInjectSourceGenerator.cs | 216 ++++++++++++++++++ .../Biwen.AutoClassGen.TestConsole.csproj | 3 +- Biwen.AutoClassGen.TestConsole/Program.cs | 4 + .../Services/ServiceCollectionExtension.cs | 27 +++ .../Services/TestService.cs | 31 +++ 6 files changed, 311 insertions(+), 1 deletion(-) create mode 100644 Biwen.AutoClassGen.Attributes/AutoInjectAttribute.cs create mode 100644 Biwen.AutoClassGen.Gen/AutoInjectSourceGenerator.cs create mode 100644 Biwen.AutoClassGen.TestConsole/Services/ServiceCollectionExtension.cs create mode 100644 Biwen.AutoClassGen.TestConsole/Services/TestService.cs diff --git a/Biwen.AutoClassGen.Attributes/AutoInjectAttribute.cs b/Biwen.AutoClassGen.Attributes/AutoInjectAttribute.cs new file mode 100644 index 0000000..eaf476a --- /dev/null +++ b/Biwen.AutoClassGen.Attributes/AutoInjectAttribute.cs @@ -0,0 +1,31 @@ +using System; + +namespace Biwen.AutoClassGen.Attributes +{ +#if NET7_0_OR_GREATER + + /// + /// 服务生命周期 + /// + public enum ServiceLifetime + { + Singleton = 1, + Transient = 2, + Scoped = 4, + } + + + [AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)] + public class AutoInjectAttribute : Attribute + { + public ServiceLifetime ServiceLifetime { get; set; } + + public AutoInjectAttribute(ServiceLifetime serviceLifetime = ServiceLifetime.Scoped) + { + ServiceLifetime = serviceLifetime; + } + } + +#endif + +} \ No newline at end of file diff --git a/Biwen.AutoClassGen.Gen/AutoInjectSourceGenerator.cs b/Biwen.AutoClassGen.Gen/AutoInjectSourceGenerator.cs new file mode 100644 index 0000000..3a1217f --- /dev/null +++ b/Biwen.AutoClassGen.Gen/AutoInjectSourceGenerator.cs @@ -0,0 +1,216 @@ +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.Text; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Linq; +using System.Text.RegularExpressions; +using System.Text; + +namespace Biwen.AutoClassGen +{ + [Generator(LanguageNames.CSharp)] + public class AutoInjectSourceGenerator : IIncrementalGenerator + { + private const string AttributeValueMetadataNameInject = "AutoInject"; + + /// + /// 泛型AutoInjectAttribute + /// + private const string GenericAutoInjectAttributeName = "Biwen.AutoClassGen.Attributes.AutoInjectAttribute`1"; + + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var nodesDtoG = context.SyntaxProvider.ForAttributeWithMetadataName( + GenericAutoInjectAttributeName, + (context, attributeSyntax) => true, + (syntaxContext, _) => syntaxContext.TargetNode).Collect(); + + IncrementalValueProvider<(Compilation, ImmutableArray)> compilationAndTypesInjectG = + context.CompilationProvider.Combine(nodesDtoG); + + context.RegisterSourceOutput(compilationAndTypesInjectG, static (spc, source) => HandleGenericAnnotatedNodesInject(source.Item1, source.Item2, spc)); + } + + /// + /// Gen AutoInjectAttribute G + /// + /// + /// + /// + private static void HandleGenericAnnotatedNodesInject(Compilation compilation, ImmutableArray nodes, SourceProductionContext context) + { + if (nodes.Length == 0) return; + + // 注册的服务 + List autoInjects = []; + List namespaces = []; + + foreach (ClassDeclarationSyntax node in nodes.AsEnumerable().Cast()) + { + AttributeSyntax? attributeSyntax = null; + foreach (var attr in node.AttributeLists.AsEnumerable()) + { + var attrName = attr.Attributes.FirstOrDefault()?.Name.ToString(); + + attributeSyntax = attr.Attributes.First(x => x.Name.ToString().IndexOf(AttributeValueMetadataNameInject, System.StringComparison.Ordinal) == 0); + + if (attrName?.IndexOf(AttributeValueMetadataNameInject, System.StringComparison.Ordinal) == 0) + { + //转译的Entity类名 + var baseTypeName = string.Empty; + + string pattern = @"(?<=<)(?\w+)(?=>)"; + var match = Regex.Match(attributeSyntax.ToString(), pattern); + if (match.Success) + { + baseTypeName = match.Groups["type"].Value.Split(['.']).Last(); + } + else + { + continue; + } + + var implTypeName = node.Identifier.ValueText; + var rootNamespace = node.AncestorsAndSelf().OfType().Single().Name.ToString(); + + var symbols = compilation.GetSymbolsWithName(implTypeName); + foreach (ITypeSymbol symbol in symbols.Cast()) + { + var fullNameSpace = symbol.ContainingNamespace.ToDisplayString(); + // 命名空间 + if (!namespaces.Contains(fullNameSpace)) + { + namespaces.Add(fullNameSpace); + } + } + + string lifeTime = "AddScoped"; + { + if (attributeSyntax.ArgumentList != null) + { + for (var i = 0; i < attributeSyntax.ArgumentList!.Arguments.Count; i++) + { + var expressionSyntax = attributeSyntax.ArgumentList.Arguments[i].Expression; + if (expressionSyntax.IsKind(SyntaxKind.SimpleMemberAccessExpression)) + { + var name = (expressionSyntax as MemberAccessExpressionSyntax)!.Name.Identifier.ValueText; + lifeTime = name switch + { + "Singleton" => "AddSingleton", + "Transient" => "AddTransient", + "Scoped" => "AddScoped", + _ => "AddScoped", + }; + break; + } + } + } + + autoInjects.Add(new AutoInjectDefine + { + ImplType = implTypeName, + BaseType = baseTypeName, + LifeTime = lifeTime, + }); + + //命名空间 + symbols = compilation.GetSymbolsWithName(baseTypeName); + foreach (ITypeSymbol symbol in symbols.Cast()) + { + var fullNameSpace = symbol.ContainingNamespace.ToDisplayString(); + // 命名空间 + if (!namespaces.Contains(fullNameSpace)) + { + namespaces.Add(fullNameSpace); + } + } + } + } + } + } + + // 生成代码 + StringBuilder classes = new(); + foreach (var define in autoInjects) + { + if (define.ImplType != define.BaseType) + { + classes.AppendLine($@"services.{define.LifeTime}<{define.BaseType}, {define.ImplType}>();"); + } + else + { + classes.AppendLine($@"services.{define.LifeTime}<{define.ImplType}>();"); + } + } + + string rawNamespace = string.Empty; + namespaces.ForEach(ns => rawNamespace += $"using {ns};\r\n"); + + var envSource = Template.Replace("$services", classes.ToString()); + envSource = envSource.Replace("$namespaces", rawNamespace); + // format: + envSource = FormatContent(envSource); + context.AddSource($"Biwen.AutoClassGenInjectG.g.cs", SourceText.From(envSource, Encoding.UTF8)); + } + + private class AutoInjectDefine + { + public string ImplType { get; set; } = null!; + public string BaseType { get; set; } = null!; + + public string LifeTime { get; set; } = null!; + } + + + #region tmpl + + private const string Template = """ + // + // author:vipwan@outlook.com 万雅虎 + // issue:https://github.com/vipwan/Biwen.AutoClassGen/issues + // 如果你在使用中遇到问题,请第一时间issue,谢谢! + // This file is generated by Biwen.AutoClassGen.AutoInjectSourceGenerator + + #pragma warning disable + namespace Microsoft.Extensions.DependencyInjection + { + $namespaces + + public static class ServiceCollectionExtension + { + /// + /// 自动注册标注的服务 + /// + /// + /// + public static IServiceCollection AddAutoInject(this IServiceCollection services) + { + $services + return services; + } + } + } + #pragma warning restore + """; + + #endregion + + + /// + /// 格式化代码 + /// + /// + /// + private static string FormatContent(string csCode) + { + var tree = CSharpSyntaxTree.ParseText(csCode); + var root = tree.GetRoot().NormalizeWhitespace(); + var ret = root.ToFullString(); + return ret; + } + + } +} \ No newline at end of file diff --git a/Biwen.AutoClassGen.TestConsole/Biwen.AutoClassGen.TestConsole.csproj b/Biwen.AutoClassGen.TestConsole/Biwen.AutoClassGen.TestConsole.csproj index 2c070df..5ad4e8a 100644 --- a/Biwen.AutoClassGen.TestConsole/Biwen.AutoClassGen.TestConsole.csproj +++ b/Biwen.AutoClassGen.TestConsole/Biwen.AutoClassGen.TestConsole.csproj @@ -1,4 +1,4 @@ - + Exe @@ -15,4 +15,5 @@ + diff --git a/Biwen.AutoClassGen.TestConsole/Program.cs b/Biwen.AutoClassGen.TestConsole/Program.cs index cf60db4..0abd6dc 100644 --- a/Biwen.AutoClassGen.TestConsole/Program.cs +++ b/Biwen.AutoClassGen.TestConsole/Program.cs @@ -16,6 +16,10 @@ // add auto decor builder.Services.AddAutoDecor(); + +builder.Services.AddAutoInject(); + + var app = builder.Build(); diff --git a/Biwen.AutoClassGen.TestConsole/Services/ServiceCollectionExtension.cs b/Biwen.AutoClassGen.TestConsole/Services/ServiceCollectionExtension.cs new file mode 100644 index 0000000..f543d13 --- /dev/null +++ b/Biwen.AutoClassGen.TestConsole/Services/ServiceCollectionExtension.cs @@ -0,0 +1,27 @@ +//// +//// author:vipwan@outlook.com 万雅虎 +//// issue:https://github.com/vipwan/Biwen.AutoClassGen/issues +//// 如果你在使用中遇到问题,请第一时间issue,谢谢! +//// This file is generated by Biwen.AutoClassGen.AutoInjectSourceGenerator +//#pragma warning disable +//namespace Microsoft.Extensions.DependencyInjection1 +//{ +// using Biwen.AutoClassGen.TestConsole.Services; + +// public static class ServiceCollectionExtension +// { +// /// +// /// 自动注册标注的服务 +// /// +// /// +// /// +// public static IServiceCollection AddAutoInjectX(this IServiceCollection services) +// { +// services.AddScoped(); +// services.AddSingleton(); +// services.AddScoped(); +// return services; +// } +// } +//} +//#pragma warning restore diff --git a/Biwen.AutoClassGen.TestConsole/Services/TestService.cs b/Biwen.AutoClassGen.TestConsole/Services/TestService.cs new file mode 100644 index 0000000..2eaa112 --- /dev/null +++ b/Biwen.AutoClassGen.TestConsole/Services/TestService.cs @@ -0,0 +1,31 @@ +namespace Biwen.AutoClassGen.TestConsole.Services +{ + + using Biwen.AutoClassGen.Attributes; + + public interface ITestService + { + string Say(string message); + } + + public interface ITest2Service + { + string Say2(string message); + } + + [AutoInject] + [AutoInject(ServiceLifetime.Singleton)] + [AutoInject(ServiceLifetime.Scoped)] + public class TestService : ITestService, ITest2Service + { + public string Say(string message) + { + return $"hello {message}"; + } + + public string Say2(string message) + { + return message; + } + } +} \ No newline at end of file