Skip to content

Commit

Permalink
Support keyed Auto Inject #7
Browse files Browse the repository at this point in the history
  • Loading branch information
vipwan committed Jun 19, 2024
1 parent 581b06a commit 9c3b200
Show file tree
Hide file tree
Showing 6 changed files with 221 additions and 17 deletions.
16 changes: 16 additions & 0 deletions Biwen.AutoClassGen.Attributes/AutoInjectAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,20 @@ public AutoInjectAttribute(ServiceLifetime serviceLifetime = ServiceLifetime.Sco

#endif

#if NET8_0_OR_GREATER

[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class AutoInjectKeyedAttribute<T> : AutoInjectAttribute
{

public string Key { get; set; }

public AutoInjectKeyedAttribute(string key, ServiceLifetime serviceLifetime = ServiceLifetime.Scoped) : base(typeof(T), serviceLifetime)
{
Key = key ?? throw new ArgumentNullException(nameof(key));
}
}

#endif

}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>netstandard2.0;net7.0</TargetFrameworks>
<TargetFrameworks>netstandard2.0;net7.0;net8.0</TargetFrameworks>
<PackageVersion>1.3.2.3</PackageVersion>
<PackageProjectUrl>https://github.com/vipwan/Biwen.AutoClassGen</PackageProjectUrl>
<Authors>万雅虎</Authors>
Expand Down
201 changes: 190 additions & 11 deletions Biwen.AutoClassGen.Gen/AutoInjectSourceGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,19 @@ public class AutoInjectSourceGenerator : IIncrementalGenerator
/// </summary>
private const string AutoInjectAttributeName = "Biwen.AutoClassGen.Attributes.AutoInjectAttribute";


private const string AutoInjectKeyedMetadataNameInject = "AutoInjectKeyed";

/// <summary>
/// .NET8.0以上支持Keyed
/// </summary>
private const string AutoInjectKeyedAttributeName = "Biwen.AutoClassGen.Attributes.AutoInjectKeyedAttribute`1";



public void Initialize(IncrementalGeneratorInitializationContext context)
{

#region 非泛型

var nodesAutoInject = context.SyntaxProvider.ForAttributeWithMetadataName(
Expand All @@ -43,21 +54,40 @@ public void Initialize(IncrementalGeneratorInitializationContext context)

var nodesAutoInjectG = context.SyntaxProvider.ForAttributeWithMetadataName(
GenericAutoInjectAttributeName,
(context, attributeSyntax) => true,
(syntaxContext, _) => syntaxContext.TargetNode).Collect();
(context, attributeSyntax) => true,
(syntaxContext, _) => syntaxContext.TargetNode).Collect();

IncrementalValueProvider<(Compilation, ImmutableArray<SyntaxNode>)> compilationAndTypesInjectG =
context.CompilationProvider.Combine(nodesAutoInjectG);

#endregion

var join = compilationAndTypesInject.Combine(compilationAndTypesInjectG);
#region Keyed

var nodesAutoInjectKeyed = context.SyntaxProvider.ForAttributeWithMetadataName(
AutoInjectKeyedAttributeName,
(context, attributeSyntax) => true,
(syntaxContext, _) => syntaxContext.TargetNode).Collect();

IncrementalValueProvider<(Compilation, ImmutableArray<SyntaxNode>)> compilationAndTypesInjectKeyed =
context.CompilationProvider.Combine(nodesAutoInjectKeyed);

#endregion


var join = compilationAndTypesInject.Combine(compilationAndTypesInjectG).Combine(compilationAndTypesInjectKeyed);

context.RegisterSourceOutput(join, (ctx, nodes) =>
context.RegisterSourceOutput(join,
(ctx,
nodes) =>
{
var nodes1 = GetAnnotatedNodesInject(nodes.Left.Item1, nodes.Left.Item2);
var nodes2 = GetGenericAnnotatedNodesInject(nodes.Right.Item1, nodes.Right.Item2);
GenSource(ctx, [.. nodes1, .. nodes2]);
//程序集命名空间
var @namespace = nodes.Left.Left.Item1.AssemblyName ?? nodes.Left.Right.Item1.AssemblyName ?? nodes.Right.Item1.AssemblyName;

var nodes1 = GetAnnotatedNodesInject(nodes.Left.Left.Item1, nodes.Left.Left.Item2);
var nodes2 = GetGenericAnnotatedNodesInject(nodes.Left.Right.Item1, nodes.Left.Right.Item2);
var nodes3 = GetAnnotatedNodesInjectKeyed(nodes.Right.Item1, nodes.Right.Item2);
GenSource(ctx, [.. nodes1, .. nodes2, .. nodes3], @namespace);
});
}

Expand Down Expand Up @@ -293,30 +323,173 @@ private static List<AutoInjectDefine> GetAnnotatedNodesInject(Compilation compil

}


//获取keyed的Define
private static List<AutoInjectDefine> GetAnnotatedNodesInjectKeyed(Compilation compilation, ImmutableArray<SyntaxNode> nodes)
{
if (nodes.Length == 0) return [];
// 注册的服务
List<AutoInjectDefine> autoInjects = [];
List<string> namespaces = [];

foreach (ClassDeclarationSyntax node in nodes.AsEnumerable().Cast<ClassDeclarationSyntax>())
{
AttributeSyntax? attributeSyntax = null;
foreach (var attr in node.AttributeLists.AsEnumerable())
{
var attrName = attr.Attributes.FirstOrDefault(x => x.Name.ToString().Contains(AutoInjectKeyedMetadataNameInject))?.Name.ToString();
if (attrName is null) { continue; }

attributeSyntax = attr.Attributes.First(x => x.Name.ToString().IndexOf(AutoInjectKeyedMetadataNameInject, System.StringComparison.Ordinal) == 0);

if (attrName?.IndexOf(AutoInjectKeyedMetadataNameInject, System.StringComparison.Ordinal) == 0)
{
//转译的Entity类名
var baseTypeName = string.Empty;

string pattern = @"(?<=<)(?<type>\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<NamespaceDeclarationSyntax>().Single().Name.ToString();
var symbols = compilation.GetSymbolsWithName(implTypeName);
foreach (ITypeSymbol symbol in symbols.Cast<ITypeSymbol>())
{
implTypeName = symbol.ToDisplayString();
break;
}

var baseSymbols = compilation.GetSymbolsWithName(baseTypeName);
foreach (ITypeSymbol baseSymbol in baseSymbols.Cast<ITypeSymbol>())
{
baseTypeName = baseSymbol.ToDisplayString();
break;
}

string? key = attributeSyntax.ArgumentList?.Arguments[0].Expression.ToString();

//使用正则表达式取双引号中的内容:
//字符串的情况: "test2"
string keyPattern1 = "\"(.*?)\"";

if (Regex.IsMatch(key, keyPattern1))
{
key = Regex.Match(key, keyPattern1).Groups[1].Value;
}

//使用正则表达式取括号中的内容:
//nameof的情况: nameof(TestService2)
string keyPattern2 = @"\((.*?)\)";
if (Regex.IsMatch(key, keyPattern2))
{
key = Regex.Match(key, keyPattern2).Groups[1].Value;
//分割.取最后一个
key = key.Split(['.']).Last();
}


string lifeTime = "AddKeyedScoped"; //default
{
if (attributeSyntax.ArgumentList != null)
{
for (var i = 1; 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" => "AddKeyedSingleton",
"Transient" => "AddKeyedTransient",
"Scoped" => "AddKeyedScoped",
_ => "AddKeyedScoped",
};
break;
}
}
}

autoInjects.Add(new AutoInjectDefine
{
ImplType = implTypeName,
BaseType = baseTypeName,
LifeTime = lifeTime,
Key = key,
});

//命名空间
symbols = compilation.GetSymbolsWithName(baseTypeName);
foreach (ITypeSymbol symbol in symbols.Cast<ITypeSymbol>())
{
var fullNameSpace = symbol.ContainingNamespace.ToDisplayString();
// 命名空间
if (!namespaces.Contains(fullNameSpace))
{
namespaces.Add(fullNameSpace);
}
}
}
}
}
}

return autoInjects;
}

//private static readonly object _lock = new();
/// <summary>
/// 所有的注入定义
/// </summary>
//private static List<AutoInjectDefine> _injectDefines = [];
//private static List<string> _namespaces = [];

private static void GenSource(SourceProductionContext context, IEnumerable<AutoInjectDefine> injectDefines)
private static void GenSource(SourceProductionContext context, IEnumerable<AutoInjectDefine> injectDefines, string? rootNamespace)
{
// 生成代码
StringBuilder classes = new();
injectDefines.Distinct().ToList().ForEach(define =>
{
if (define.ImplType != define.BaseType)
//NET8.0以上支持Keyed
if (define.Key != null)
{
classes.AppendLine($@"services.{define.LifeTime}<{define.BaseType}, {define.ImplType}>();");
if (define.ImplType != define.BaseType)
{
classes.AppendLine($@"services.{define.LifeTime}<{define.BaseType}, {define.ImplType}>(""{define.Key}"");");
}
else
{
classes.AppendLine($@"services.{define.LifeTime}<{define.ImplType}>(""{define.Key}"");");
}
}
//非Keyed
else
{
classes.AppendLine($@"services.{define.LifeTime}<{define.ImplType}>();");
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;
if (rootNamespace != null)
{
rawNamespace += $"namespace {rootNamespace};\r\n";
}

//_namespaces.Distinct().ToList().ForEach(ns => rawNamespace += $"using {ns};\r\n");
var envSource = Template.Replace("$services", classes.ToString());
envSource = envSource.Replace("$namespaces", rawNamespace);
Expand All @@ -332,6 +505,12 @@ private record AutoInjectDefine
public string BaseType { get; set; } = null!;

public string LifeTime { get; set; } = null!;

/// <summary>
/// 针对NET8.0以上的Keyed Service,默认为NULL
/// </summary>
public string? Key { get; set; }

}


Expand Down
4 changes: 2 additions & 2 deletions Biwen.AutoClassGen.Gen/Biwen.AutoClassGen.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@
</ItemGroup>

<ItemGroup>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.9.2" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.10.0" PrivateAssets="all" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.10.0" PrivateAssets="all" />

<PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.556">
<PrivateAssets>all</PrivateAssets>
Expand Down
9 changes: 8 additions & 1 deletion Biwen.AutoClassGen.TestConsole/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Biwen.AutoClassGen.TestConsole.Dtos;
using Biwen.AutoClassGen.TestConsole.Entitys;
using Biwen.AutoClassGen.TestConsole.Services;
using Microsoft.Extensions.DependencyInjection;


var builder = WebApplication.CreateBuilder();
Expand All @@ -18,7 +19,8 @@
builder.Services.AddAutoDecor();

// add auto inject
builder.Services.AddAutoInject();
Biwen.AutoClassGen.TestConsole.AutoInjectExtension.AddAutoInject(builder.Services);



var app = builder.Build();
Expand Down Expand Up @@ -47,6 +49,11 @@
var result4 = myService.Hello("from my service");
Console.WriteLine(result4);

// get keyed service
var keyedService = scope.ServiceProvider.GetRequiredKeyedService<ITest2Service>("test2");
var result5 = keyedService.Say2("from keyed service");
Console.WriteLine(result5);




Expand Down
6 changes: 4 additions & 2 deletions Biwen.AutoClassGen.TestConsole/Services/TestService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ public string Say2(string message)
[AutoInject]
[AutoInject(serviceLifetime: ServiceLifetime.Transient)]
[AutoInject(typeof(ITest2Service), ServiceLifetime.Scoped)]

//NET8.0+ 支持keyed
[AutoInjectKeyed<ITest2Service>("test2")]
[AutoInjectKeyed<ITest2Service>(nameof(TestService2))]
public class TestService2 : ITest2Service
{
public string Say2(string message)
Expand All @@ -43,8 +47,6 @@ public string Say2(string message)
}




public partial class TestServiceDto
{
}
Expand Down

0 comments on commit 9c3b200

Please sign in to comment.