Skip to content

Commit

Permalink
#7 Support Auto Inject
Browse files Browse the repository at this point in the history
  • Loading branch information
vipwan committed Mar 26, 2024
1 parent c2081fb commit ac1093a
Show file tree
Hide file tree
Showing 6 changed files with 311 additions and 1 deletion.
31 changes: 31 additions & 0 deletions Biwen.AutoClassGen.Attributes/AutoInjectAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
using System;

namespace Biwen.AutoClassGen.Attributes
{
#if NET7_0_OR_GREATER

/// <summary>
/// 服务生命周期
/// </summary>
public enum ServiceLifetime
{
Singleton = 1,
Transient = 2,
Scoped = 4,
}


[AttributeUsage(AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class AutoInjectAttribute<T> : Attribute
{
public ServiceLifetime ServiceLifetime { get; set; }

public AutoInjectAttribute(ServiceLifetime serviceLifetime = ServiceLifetime.Scoped)
{
ServiceLifetime = serviceLifetime;
}
}

#endif

}
216 changes: 216 additions & 0 deletions Biwen.AutoClassGen.Gen/AutoInjectSourceGenerator.cs
Original file line number Diff line number Diff line change
@@ -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";

/// <summary>
/// 泛型AutoInjectAttribute
/// </summary>
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<SyntaxNode>)> compilationAndTypesInjectG =
context.CompilationProvider.Combine(nodesDtoG);

context.RegisterSourceOutput(compilationAndTypesInjectG, static (spc, source) => HandleGenericAnnotatedNodesInject(source.Item1, source.Item2, spc));
}

/// <summary>
/// Gen AutoInjectAttribute G
/// </summary>
/// <param name="compilation"></param>
/// <param name="nodes"></param>
/// <param name="context"></param>
private static void HandleGenericAnnotatedNodesInject(Compilation compilation, ImmutableArray<SyntaxNode> nodes, SourceProductionContext context)
{
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()?.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 = @"(?<=<)(?<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>())
{
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<ITypeSymbol>())
{
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 = """
// <auto-generated />
// author:[email protected] 万雅虎
// 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
{
/// <summary>
/// 自动注册标注的服务
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddAutoInject(this IServiceCollection services)
{
$services
return services;
}
}
}
#pragma warning restore
""";

#endregion


/// <summary>
/// 格式化代码
/// </summary>
/// <param name="csCode"></param>
/// <returns></returns>
private static string FormatContent(string csCode)
{
var tree = CSharpSyntaxTree.ParseText(csCode);
var root = tree.GetRoot().NormalizeWhitespace();
var ret = root.ToFullString();
return ret;
}

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

<PropertyGroup>
<OutputType>Exe</OutputType>
Expand All @@ -15,4 +15,5 @@
<ProjectReference Include="..\Biwen.AutoClassGen.Gen\Biwen.AutoClassGen.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />
</ItemGroup>


</Project>
4 changes: 4 additions & 0 deletions Biwen.AutoClassGen.TestConsole/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
// add auto decor
builder.Services.AddAutoDecor();


builder.Services.AddAutoInject();


var app = builder.Build();


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
//// <auto-generated />
//// author:[email protected] 万雅虎
//// 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
// {
// /// <summary>
// /// 自动注册标注的服务
// /// </summary>
// /// <param name = "services"></param>
// /// <returns></returns>
// public static IServiceCollection AddAutoInjectX(this IServiceCollection services)
// {
// services.AddScoped<TestService>();
// services.AddSingleton<ITestService, TestService>();
// services.AddScoped<ITest2Service, TestService>();
// return services;
// }
// }
//}
//#pragma warning restore
31 changes: 31 additions & 0 deletions Biwen.AutoClassGen.TestConsole/Services/TestService.cs
Original file line number Diff line number Diff line change
@@ -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<TestService>]
[AutoInject<ITestService>(ServiceLifetime.Singleton)]
[AutoInject<ITest2Service>(ServiceLifetime.Scoped)]
public class TestService : ITestService, ITest2Service
{
public string Say(string message)
{
return $"hello {message}";
}

public string Say2(string message)
{
return message;
}
}
}

0 comments on commit ac1093a

Please sign in to comment.