From ef72e43f50eafe71647c7dfde1f465a365436d77 Mon Sep 17 00:00:00 2001 From: Joes Date: Fri, 26 Jul 2024 09:01:28 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E5=BA=93?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Directory.Packages.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 0d8a8a2b..0bef72f2 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -22,8 +22,8 @@ - - + + From a6def45d0f04ffbb2551a62fb96ccb81c62df010 Mon Sep 17 00:00:00 2001 From: Joes Date: Tue, 30 Jul 2024 09:10:42 +0800 Subject: [PATCH 2/2] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0SG=E7=9A=84?= =?UTF-8?q?=E5=86=85=E5=AE=B9,=E6=9A=82=E6=97=B6=E6=9C=AA=E6=88=90?= =?UTF-8?q?=E5=8A=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- EasilyNET.sln | 14 +++ .../AppWebModule.cs | 43 ++++++++ .../Controllers/WeatherForecastController.cs | 16 +++ sample/WebApi.SourceGenerator.Test/Program.cs | 15 +++ .../Properties/launchSettings.json | 15 +++ sample/WebApi.SourceGenerator.Test/README.md | 2 + .../ServiceModules/ControllersModule.cs | 35 ++++++ .../ServiceModules/CorsModule.cs | 25 +++++ .../ServiceModules/SwaggerModule.cs | 65 ++++++++++++ .../WeatherForecast.cs | 13 +++ .../WeatherForecastService.cs | 32 ++++++ .../WebApi.SourceGenerator.Test.csproj | 18 ++++ .../appsettings.Development.json | 8 ++ .../appsettings.json | 9 ++ .../WebApi.Test.Unit/WebApi.Test.Unit.csproj | 2 +- src/Directory.Packages.props | 2 +- .../AppModuleSourceGenerator.cs | 93 ++++++++++++++++ .../AutoInjectionSourceGenerator.cs | 100 ++++++++++++++++++ .../Directory.Packages.props | 22 ++++ ...DependencyInjection.SourceGenerator.csproj | 10 ++ .../README.md | 4 + .../EasilyNET.AutoDependencyInjection.csproj | 2 + .../Modules/DependencyAppModule.cs | 2 + .../Modules/ModuleApplicationBase.cs | 5 +- 24 files changed, 548 insertions(+), 4 deletions(-) create mode 100644 sample/WebApi.SourceGenerator.Test/AppWebModule.cs create mode 100644 sample/WebApi.SourceGenerator.Test/Controllers/WeatherForecastController.cs create mode 100644 sample/WebApi.SourceGenerator.Test/Program.cs create mode 100644 sample/WebApi.SourceGenerator.Test/Properties/launchSettings.json create mode 100644 sample/WebApi.SourceGenerator.Test/README.md create mode 100644 sample/WebApi.SourceGenerator.Test/ServiceModules/ControllersModule.cs create mode 100644 sample/WebApi.SourceGenerator.Test/ServiceModules/CorsModule.cs create mode 100644 sample/WebApi.SourceGenerator.Test/ServiceModules/SwaggerModule.cs create mode 100644 sample/WebApi.SourceGenerator.Test/WeatherForecast.cs create mode 100644 sample/WebApi.SourceGenerator.Test/WeatherForecastService.cs create mode 100644 sample/WebApi.SourceGenerator.Test/WebApi.SourceGenerator.Test.csproj create mode 100644 sample/WebApi.SourceGenerator.Test/appsettings.Development.json create mode 100644 sample/WebApi.SourceGenerator.Test/appsettings.json create mode 100644 src/EasilyNET.AutoDependencyInjection.SourceGenerator/AppModuleSourceGenerator.cs create mode 100644 src/EasilyNET.AutoDependencyInjection.SourceGenerator/AutoInjectionSourceGenerator.cs create mode 100644 src/EasilyNET.AutoDependencyInjection.SourceGenerator/Directory.Packages.props create mode 100644 src/EasilyNET.AutoDependencyInjection.SourceGenerator/EasilyNET.AutoDependencyInjection.SourceGenerator.csproj create mode 100644 src/EasilyNET.AutoDependencyInjection.SourceGenerator/README.md diff --git a/EasilyNET.sln b/EasilyNET.sln index 73b678d7..c594c374 100644 --- a/EasilyNET.sln +++ b/EasilyNET.sln @@ -66,6 +66,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebApi.Test.Unit", "sample\ EndProject Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{771BAFD8-69AB-4F2E-9FBF-0280E83953BF}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EasilyNET.AutoDependencyInjection.SourceGenerator", "src\EasilyNET.AutoDependencyInjection.SourceGenerator\EasilyNET.AutoDependencyInjection.SourceGenerator.csproj", "{F8F0840A-001A-4621-80C5-B3735E1337E4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApi.SourceGenerator.Test", "sample\WebApi.SourceGenerator.Test\WebApi.SourceGenerator.Test.csproj", "{4A011ECD-3C24-4818-8C42-A9361BF4119A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -143,6 +147,14 @@ Global {771BAFD8-69AB-4F2E-9FBF-0280E83953BF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {771BAFD8-69AB-4F2E-9FBF-0280E83953BF}.Debug|Any CPU.Build.0 = Debug|Any CPU {771BAFD8-69AB-4F2E-9FBF-0280E83953BF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8F0840A-001A-4621-80C5-B3735E1337E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F8F0840A-001A-4621-80C5-B3735E1337E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F8F0840A-001A-4621-80C5-B3735E1337E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F8F0840A-001A-4621-80C5-B3735E1337E4}.Release|Any CPU.Build.0 = Release|Any CPU + {4A011ECD-3C24-4818-8C42-A9361BF4119A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4A011ECD-3C24-4818-8C42-A9361BF4119A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4A011ECD-3C24-4818-8C42-A9361BF4119A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4A011ECD-3C24-4818-8C42-A9361BF4119A}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -168,6 +180,8 @@ Global {7E493CA2-CB0C-77E0-B442-5ED204493F13} = {8D626EA8-CB54-BC41-363A-217881BEBA6E} {8F9C18F9-A526-DCA0-597F-90D64AE5C6C2} = {8D626EA8-CB54-BC41-363A-217881BEBA6E} {9B426136-DC85-603B-94FB-F3C0B2E72713} = {4F9DEAE5-078F-E77A-2E4A-FEB6FFE226FF} + {F8F0840A-001A-4621-80C5-B3735E1337E4} = {E0AD1809-C64F-3D39-45E0-E424261CE16D} + {4A011ECD-3C24-4818-8C42-A9361BF4119A} = {4F9DEAE5-078F-E77A-2E4A-FEB6FFE226FF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BF2C0069-ED43-44A1-A66C-2CC1B62E3EA3} diff --git a/sample/WebApi.SourceGenerator.Test/AppWebModule.cs b/sample/WebApi.SourceGenerator.Test/AppWebModule.cs new file mode 100644 index 00000000..4a23c20e --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/AppWebModule.cs @@ -0,0 +1,43 @@ +using EasilyNET.AutoDependencyInjection.Attributes; +using EasilyNET.AutoDependencyInjection.Contexts; +using EasilyNET.AutoDependencyInjection.Modules; +using EasilyNET.WebCore.Handlers; +using WebApi.SourceGenerator.Test.ServiceModules; + +// ReSharper disable ClassNeverInstantiated.Global + +namespace WebApi.SourceGenerator.Test; + +/// +/// +[DependsOn(typeof(DependencyAppModule), + typeof(CorsModule), + typeof(ControllersModule), + typeof(SwaggerModule))] +public sealed class AppWebModule : AppModule +{ + /// + public override void ConfigureServices(ConfigureServicesContext context) + { + base.ConfigureServices(context); + // 添加 ProblemDetails 服务 + context.Services.AddProblemDetails(); + context.Services.AddExceptionHandler(); + // 添加HttpContextAccessor + context.Services.AddHttpContextAccessor(); + } + + /// + public override void ApplicationInitialization(ApplicationContext context) + { + base.ApplicationInitialization(context); + var app = context.GetApplicationHost() as IApplicationBuilder; + // 全局异常处理中间件 + app?.UseExceptionHandler(); + app?.UseResponseTime(); + // 先认证 + app?.UseAuthentication(); + // 再授权 + app?.UseAuthorization(); + } +} \ No newline at end of file diff --git a/sample/WebApi.SourceGenerator.Test/Controllers/WeatherForecastController.cs b/sample/WebApi.SourceGenerator.Test/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000..ea27f939 --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/Controllers/WeatherForecastController.cs @@ -0,0 +1,16 @@ +using Microsoft.AspNetCore.Mvc; + +namespace WebApi.SourceGenerator.Test.Controllers; + +/// +/// +/// +[ApiController, Route("[controller]")] +public class WeatherForecastController(WeatherForecastService wfs) : ControllerBase +{ + /// + /// + /// + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() => wfs.Get(); +} \ No newline at end of file diff --git a/sample/WebApi.SourceGenerator.Test/Program.cs b/sample/WebApi.SourceGenerator.Test/Program.cs new file mode 100644 index 00000000..2d083c00 --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/Program.cs @@ -0,0 +1,15 @@ +using WebApi.SourceGenerator.Test; + +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. +builder.Services.AddControllers(); +// Զעģ +builder.Services.AddApplicationModules(); +var app = builder.Build(); +if (app.Environment.IsDevelopment()) app.UseDeveloperExceptionPage(); + +// ԶעһЩм. +app.InitializeApplication(); +app.MapControllers(); +app.Run(); \ No newline at end of file diff --git a/sample/WebApi.SourceGenerator.Test/Properties/launchSettings.json b/sample/WebApi.SourceGenerator.Test/Properties/launchSettings.json new file mode 100644 index 00000000..23542338 --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/Properties/launchSettings.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "launchUrl": "swagger", + "applicationUrl": "http://localhost:5143", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/sample/WebApi.SourceGenerator.Test/README.md b/sample/WebApi.SourceGenerator.Test/README.md new file mode 100644 index 00000000..6451c864 --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/README.md @@ -0,0 +1,2 @@ +#### 测试SG + diff --git a/sample/WebApi.SourceGenerator.Test/ServiceModules/ControllersModule.cs b/sample/WebApi.SourceGenerator.Test/ServiceModules/ControllersModule.cs new file mode 100644 index 00000000..cf35a544 --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/ServiceModules/ControllersModule.cs @@ -0,0 +1,35 @@ +using System.Text.Json.Serialization; +using EasilyNET.AutoDependencyInjection.Contexts; +using EasilyNET.AutoDependencyInjection.Modules; +using EasilyNET.WebCore.JsonConverters; + +// ReSharper disable UnusedType.Local +// ReSharper disable ClassNeverInstantiated.Global + +namespace WebApi.SourceGenerator.Test.ServiceModules; + +/// +/// 注册一些控制器的基本内容 +/// +public sealed class ControllersModule : AppModule +{ + /// + public override void ConfigureServices(ConfigureServicesContext context) + { + context.Services.AddControllers() + .AddJsonOptions(c => + { + c.JsonSerializerOptions.Converters.Add(new DecimalNullConverter()); + c.JsonSerializerOptions.Converters.Add(new IntNullConverter()); + c.JsonSerializerOptions.Converters.Add(new BoolNullConverter()); + c.JsonSerializerOptions.Converters.Add(new DateTimeConverter()); + c.JsonSerializerOptions.Converters.Add(new DateTimeNullConverter()); + c.JsonSerializerOptions.Converters.Add(new TimeOnlyJsonConverter()); + c.JsonSerializerOptions.Converters.Add(new TimeOnlyNullJsonConverter()); + c.JsonSerializerOptions.Converters.Add(new DateOnlyJsonConverter()); + c.JsonSerializerOptions.Converters.Add(new DateOnlyNullJsonConverter()); + c.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter()); + }); + context.Services.AddEndpointsApiExplorer(); + } +} \ No newline at end of file diff --git a/sample/WebApi.SourceGenerator.Test/ServiceModules/CorsModule.cs b/sample/WebApi.SourceGenerator.Test/ServiceModules/CorsModule.cs new file mode 100644 index 00000000..666ada35 --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/ServiceModules/CorsModule.cs @@ -0,0 +1,25 @@ +using EasilyNET.AutoDependencyInjection.Contexts; +using EasilyNET.AutoDependencyInjection.Modules; + +namespace WebApi.SourceGenerator.Test.ServiceModules; + +/// +/// 配置跨域服务及中间件 +/// +public sealed class CorsModule : AppModule +{ + /// + public override void ConfigureServices(ConfigureServicesContext context) + { + var config = context.Services.GetConfiguration(); + var allow = config["AllowedHosts"] ?? "*"; + context.Services.AddCors(c => c.AddPolicy("AllowedHosts", s => s.WithOrigins(allow.Split(",")).AllowAnyMethod().AllowAnyHeader())); + } + + /// + public override void ApplicationInitialization(ApplicationContext context) + { + var app = context.GetApplicationHost() as IApplicationBuilder; + app?.UseCors("AllowedHosts"); + } +} \ No newline at end of file diff --git a/sample/WebApi.SourceGenerator.Test/ServiceModules/SwaggerModule.cs b/sample/WebApi.SourceGenerator.Test/ServiceModules/SwaggerModule.cs new file mode 100644 index 00000000..93d787e9 --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/ServiceModules/SwaggerModule.cs @@ -0,0 +1,65 @@ +using EasilyNET.AutoDependencyInjection.Contexts; +using EasilyNET.AutoDependencyInjection.Modules; +using Microsoft.OpenApi.Models; + +namespace WebApi.SourceGenerator.Test.ServiceModules; + +/// +/// Swagger文档的配置 +/// +public sealed class SwaggerModule : AppModule +{ + /** + * https://github.com/domaindrivendev/Swashbuckle.AspNetCore + */ + private const string name = $"{title}-{version}"; + + private const string version = "v1"; + private const string title = "WebApi.Test"; + + /// + public SwaggerModule() + { + Enable = true; + } + + /// + public override void ConfigureServices(ConfigureServicesContext context) + { + // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle + context.Services.AddSwaggerGen(c => + { + // 配置默认的文档信息 + c.SwaggerDoc(name, new() + { + Title = title, + Version = version, + Description = "Console.WriteLine(\"🐂🍺\")" + }); + // 这里使用EasilyNET提供的扩展配置. + c.EasilySwaggerGenOptions(name); + // 配置认证方式 + c.AddSecurityDefinition("Bearer", new() + { + Description = "JWT Authorization header using the Bearer scheme. Example: \"Authorization: Bearer {token}\"", + Name = "Authorization", + In = ParameterLocation.Header, + Type = SecuritySchemeType.ApiKey, + Scheme = "Bearer" + }); + }); + } + + /// + public override void ApplicationInitialization(ApplicationContext context) + { + var app = context.GetApplicationHost() as IApplicationBuilder; + app?.UseSwagger().UseSwaggerUI(c => + { + // 配置默认文档 + c.SwaggerEndpoint($"/swagger/{name}/swagger.json", $"{title} {version}"); + // 使用EasilyNET提供的扩展配置 + c.EasilySwaggerUIOptions(); + }); + } +} \ No newline at end of file diff --git a/sample/WebApi.SourceGenerator.Test/WeatherForecast.cs b/sample/WebApi.SourceGenerator.Test/WeatherForecast.cs new file mode 100644 index 00000000..22e86f00 --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/WeatherForecast.cs @@ -0,0 +1,13 @@ +namespace WebApi.SourceGenerator.Test; + +#pragma warning disable CS1591 // ȱٶԹɼͻԱ XML ע +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} \ No newline at end of file diff --git a/sample/WebApi.SourceGenerator.Test/WeatherForecastService.cs b/sample/WebApi.SourceGenerator.Test/WeatherForecastService.cs new file mode 100644 index 00000000..3a15bfc8 --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/WeatherForecastService.cs @@ -0,0 +1,32 @@ +using EasilyNET.AutoDependencyInjection.Core.Attributes; + +// ReSharper disable ClassNeverInstantiated.Global + +namespace WebApi.SourceGenerator.Test; + +/// +/// 天气服务 +/// +[DependencyInjection(ServiceLifetime.Transient)] +public class WeatherForecastService +{ + private static readonly string[] Summaries = + [ + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + ]; + + /// + /// 获取天气服务 + /// + /// + // ReSharper disable once MemberCanBeMadeStatic.Global + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }).ToArray(); + } +} \ No newline at end of file diff --git a/sample/WebApi.SourceGenerator.Test/WebApi.SourceGenerator.Test.csproj b/sample/WebApi.SourceGenerator.Test/WebApi.SourceGenerator.Test.csproj new file mode 100644 index 00000000..f32442d7 --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/WebApi.SourceGenerator.Test.csproj @@ -0,0 +1,18 @@ + + + + net9.0 + enable + enable + + + + + + + + + + + + \ No newline at end of file diff --git a/sample/WebApi.SourceGenerator.Test/appsettings.Development.json b/sample/WebApi.SourceGenerator.Test/appsettings.Development.json new file mode 100644 index 00000000..0c208ae9 --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/sample/WebApi.SourceGenerator.Test/appsettings.json b/sample/WebApi.SourceGenerator.Test/appsettings.json new file mode 100644 index 00000000..10f68b8c --- /dev/null +++ b/sample/WebApi.SourceGenerator.Test/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/sample/WebApi.Test.Unit/WebApi.Test.Unit.csproj b/sample/WebApi.Test.Unit/WebApi.Test.Unit.csproj index 941de508..0e81f71f 100644 --- a/sample/WebApi.Test.Unit/WebApi.Test.Unit.csproj +++ b/sample/WebApi.Test.Unit/WebApi.Test.Unit.csproj @@ -38,7 +38,7 @@ - + diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 0bef72f2..95767415 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -16,7 +16,7 @@ - + diff --git a/src/EasilyNET.AutoDependencyInjection.SourceGenerator/AppModuleSourceGenerator.cs b/src/EasilyNET.AutoDependencyInjection.SourceGenerator/AppModuleSourceGenerator.cs new file mode 100644 index 00000000..126fd4af --- /dev/null +++ b/src/EasilyNET.AutoDependencyInjection.SourceGenerator/AppModuleSourceGenerator.cs @@ -0,0 +1,93 @@ +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace EasilyNET.AutoDependencyInjection.SourceGenerator; + +/// +/// ModuleSourceGenerator +/// +[Generator] +public class AppModuleSourceGenerator : IIncrementalGenerator +{ + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // 启动调试器 + //if (!Debugger.IsAttached) + //{ + // Debugger.Launch(); + //} + // 注册语法接收器 + var classDeclarations = context.SyntaxProvider + .CreateSyntaxProvider(static (s, _) => IsClassDeclarationWithAttributes(s), + static (ctx, _) => GetClassDeclaration(ctx)) + .Where(static m => m is not null); + var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect()); + context.RegisterSourceOutput(compilationAndClasses, static (spc, source) => Execute(source.Left, source.Right!, spc)); + } + + private static bool IsClassDeclarationWithAttributes(SyntaxNode node) + { + var result = node is ClassDeclarationSyntax { AttributeLists.Count: > 0 }; + return result; + } + + private static ClassDeclarationSyntax? GetClassDeclaration(GeneratorSyntaxContext context) + { + var result = context.Node as ClassDeclarationSyntax; + return result; + } + + private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context) + { + var moduleTypes = (from classDeclaration in classes + let model = compilation.GetSemanticModel(classDeclaration.SyntaxTree) + select model.GetDeclaredSymbol(classDeclaration) as INamedTypeSymbol into symbol + where symbol != null && IsAppModule(symbol) + select symbol.ToDisplayString()).ToList(); + var sourceBuilder = new StringBuilder(); + sourceBuilder.AppendLine("// "); + sourceBuilder.AppendLine("using Microsoft.Extensions.DependencyInjection;"); + sourceBuilder.AppendLine("using EasilyNET.AutoDependencyInjection.Abstractions;"); + sourceBuilder.AppendLine(); + sourceBuilder.AppendLine("/// "); + sourceBuilder.AppendLine("/// 注册App模块"); + sourceBuilder.AppendLine("/// "); + sourceBuilder.AppendLine("public static class AppModuleRegistration"); + sourceBuilder.AppendLine("{"); + sourceBuilder.AppendLine(" /// "); + sourceBuilder.AppendLine(" /// 注册AppModule"); + sourceBuilder.AppendLine(" /// "); + sourceBuilder.AppendLine(" /// "); + sourceBuilder.AppendLine(" public static void RegisterModules(IServiceCollection services)"); + sourceBuilder.AppendLine(" {"); + foreach (var moduleType in moduleTypes) + { + sourceBuilder.AppendLine($" services.AddSingleton();"); + } + sourceBuilder.AppendLine(" }"); + sourceBuilder.AppendLine("}"); + context.AddSource("AppModuleRegistration.g.cs", sourceBuilder.ToString()); + } + + /// + /// 判断一个类型是否是符合条件的 AppModule。 + /// + /// 类型符号。 + /// 如果是符合条件的 AppModule,则返回 true;否则返回 false。 + private static bool IsAppModule(INamedTypeSymbol symbol) + { + var baseType = symbol.BaseType; + while (baseType != null) + { + if (baseType.ToDisplayString() == "EasilyNET.AutoDependencyInjection.Modules.AppModule") + { + return true; + } + baseType = baseType.BaseType; + } + return false; + } +} \ No newline at end of file diff --git a/src/EasilyNET.AutoDependencyInjection.SourceGenerator/AutoInjectionSourceGenerator.cs b/src/EasilyNET.AutoDependencyInjection.SourceGenerator/AutoInjectionSourceGenerator.cs new file mode 100644 index 00000000..c1c7c891 --- /dev/null +++ b/src/EasilyNET.AutoDependencyInjection.SourceGenerator/AutoInjectionSourceGenerator.cs @@ -0,0 +1,100 @@ +using System.Collections.Immutable; +using System.Text; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; + +namespace EasilyNET.AutoDependencyInjection.SourceGenerator; + +/// +/// 生成模块注册代码的 Source Generator。 +/// +[Generator] +public class AutoInjectionSourceGenerator : IIncrementalGenerator +{ + /// + public void Initialize(IncrementalGeneratorInitializationContext context) + { + // 注册语法接收器 + var classDeclarations = context.SyntaxProvider + .CreateSyntaxProvider(static (s, _) => IsClassDeclarationWithAttributes(s), + static (ctx, _) => GetClassDeclaration(ctx)) + .Where(static m => m is not null); + var compilationAndClasses = context.CompilationProvider.Combine(classDeclarations.Collect()); + context.RegisterSourceOutput(compilationAndClasses, static (spc, source) => Execute(source.Left, source.Right, spc)); + } + + private static bool IsClassDeclarationWithAttributes(SyntaxNode node) => node is ClassDeclarationSyntax { AttributeLists.Count: > 0 }; + + private static ClassDeclarationSyntax? GetClassDeclaration(GeneratorSyntaxContext context) => context.Node as ClassDeclarationSyntax; + + private static void Execute(Compilation compilation, ImmutableArray classes, SourceProductionContext context) + { + var moduleTypes = (from classDeclaration in classes + let model = compilation.GetSemanticModel(classDeclaration.SyntaxTree) + select model.GetDeclaredSymbol(classDeclaration) into symbol + where symbol != null && + symbol.GetAttributes().Any(ad => + ad.AttributeClass?.ToDisplayString() == "EasilyNET.AutoDependencyInjection.Core.Attributes.DependencyInjectionAttribute") + select symbol).ToList(); + + // 生成代码 + var sourceBuilder = new StringBuilder(""" + // + + using Microsoft.Extensions.DependencyInjection; + using EasilyNET.AutoDependencyInjection.Abstractions; + + /// + /// AutoInjectionRegistration + /// + public static class AutoInjectionRegistration + { + /// + /// 注册服务 + /// + /// + public static void RegisterServices(IServiceCollection services) + { + + """); + foreach (var moduleType in moduleTypes) + { + var typeName = moduleType.ToDisplayString(); + var attr = moduleType.GetAttributes().First(ad => + ad.AttributeClass?.ToDisplayString() == "EasilyNET.AutoDependencyInjection.Core.Attributes.DependencyInjectionAttribute"); + var lifetime = attr.NamedArguments.FirstOrDefault(na => + na.Key == "Lifetime").Value.Value?.ToString() ?? + "Transient"; + var addSelf = attr.NamedArguments.FirstOrDefault(na => + na.Key == "AddSelf").Value.Value as bool? ?? + false; + var selfOnly = attr.NamedArguments.FirstOrDefault(na => + na.Key == "SelfOnly").Value.Value as bool? ?? + false; + var serviceKey = attr.NamedArguments.FirstOrDefault(na => + na.Key == "ServiceKey").Value.Value as string; + if (addSelf || selfOnly) + { + sourceBuilder.AppendLine(!string.IsNullOrWhiteSpace(serviceKey) + ? $" services.Add(new ServiceDescriptor(typeof({typeName}), {typeName}, \"{serviceKey}\", ServiceLifetime.{lifetime}));" + : $" services.Add(new ServiceDescriptor(typeof({typeName}), {typeName}, ServiceLifetime.{lifetime}));"); + if (selfOnly) continue; + } + var serviceTypes = ((INamedTypeSymbol)moduleType).AllInterfaces.Where(i => + !i.GetAttributes().Any(ad => + ad.AttributeClass?.ToDisplayString() == "EasilyNET.AutoDependencyInjection.Core.Attributes.IgnoreDependencyAttribute")); + foreach (var serviceType in serviceTypes) + { + var serviceTypeName = serviceType.ToDisplayString(); + sourceBuilder.AppendLine(!string.IsNullOrWhiteSpace(serviceKey) + ? $" services.Add(new ServiceDescriptor(typeof({serviceTypeName}), {typeName}, \"{serviceKey}\", ServiceLifetime.{lifetime}));" + : $" services.Add(new ServiceDescriptor(typeof({serviceTypeName}), {typeName}, ServiceLifetime.{lifetime}));"); + } + } + sourceBuilder.AppendLine(" }"); + sourceBuilder.AppendLine("}"); + + // 添加生成的代码到编译 + context.AddSource("AutoInjectionRegistration.g.cs", sourceBuilder.ToString()); + } +} \ No newline at end of file diff --git a/src/EasilyNET.AutoDependencyInjection.SourceGenerator/Directory.Packages.props b/src/EasilyNET.AutoDependencyInjection.SourceGenerator/Directory.Packages.props new file mode 100644 index 00000000..b1d87d9f --- /dev/null +++ b/src/EasilyNET.AutoDependencyInjection.SourceGenerator/Directory.Packages.props @@ -0,0 +1,22 @@ + + + + + netstandard2.0 + enable + enable + preview + true + true + + Analyzer + portable + true + $(BaseIntermediateOutputPath)Generated + + + + + + + \ No newline at end of file diff --git a/src/EasilyNET.AutoDependencyInjection.SourceGenerator/EasilyNET.AutoDependencyInjection.SourceGenerator.csproj b/src/EasilyNET.AutoDependencyInjection.SourceGenerator/EasilyNET.AutoDependencyInjection.SourceGenerator.csproj new file mode 100644 index 00000000..0ce7c4a1 --- /dev/null +++ b/src/EasilyNET.AutoDependencyInjection.SourceGenerator/EasilyNET.AutoDependencyInjection.SourceGenerator.csproj @@ -0,0 +1,10 @@ + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + \ No newline at end of file diff --git a/src/EasilyNET.AutoDependencyInjection.SourceGenerator/README.md b/src/EasilyNET.AutoDependencyInjection.SourceGenerator/README.md new file mode 100644 index 00000000..bc8c3d96 --- /dev/null +++ b/src/EasilyNET.AutoDependencyInjection.SourceGenerator/README.md @@ -0,0 +1,4 @@ +#### 用于AutoDependencyInjection的SourceGenerator + +AutoDependencyInjection是一个用于自动注册依赖注入的库,它可以自动扫描程序集中的所有类,找到标记了AutoDependencyInjectionAttribute的类,并自动注册到IServiceCollection中。 +但是原有的实现使用了反射,会有一定的性能损耗,所以我尝试使用SourceGenerator来实现这个功能。 \ No newline at end of file diff --git a/src/EasilyNET.AutoDependencyInjection/EasilyNET.AutoDependencyInjection.csproj b/src/EasilyNET.AutoDependencyInjection/EasilyNET.AutoDependencyInjection.csproj index a36b4a3d..f7475cb1 100644 --- a/src/EasilyNET.AutoDependencyInjection/EasilyNET.AutoDependencyInjection.csproj +++ b/src/EasilyNET.AutoDependencyInjection/EasilyNET.AutoDependencyInjection.csproj @@ -32,6 +32,8 @@ + + diff --git a/src/EasilyNET.AutoDependencyInjection/Modules/DependencyAppModule.cs b/src/EasilyNET.AutoDependencyInjection/Modules/DependencyAppModule.cs index c73d4f18..672fb6f9 100644 --- a/src/EasilyNET.AutoDependencyInjection/Modules/DependencyAppModule.cs +++ b/src/EasilyNET.AutoDependencyInjection/Modules/DependencyAppModule.cs @@ -23,6 +23,8 @@ public override void ConfigureServices(ConfigureServicesContext context) { var services = context.Services; AddAutoInjection(services); + // TODO:希望替换成使用SG的方式来注入服务,将AddAutoInjection函数替换掉. + //AutoInjectionRegistration.RegisterServices(services); } /// diff --git a/src/EasilyNET.AutoDependencyInjection/Modules/ModuleApplicationBase.cs b/src/EasilyNET.AutoDependencyInjection/Modules/ModuleApplicationBase.cs index 5b07f675..637cf24c 100644 --- a/src/EasilyNET.AutoDependencyInjection/Modules/ModuleApplicationBase.cs +++ b/src/EasilyNET.AutoDependencyInjection/Modules/ModuleApplicationBase.cs @@ -1,9 +1,9 @@ +using System.Linq.Expressions; using EasilyNET.AutoDependencyInjection.Abstractions; using EasilyNET.AutoDependencyInjection.Contexts; using EasilyNET.Core.Misc; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using System.Linq.Expressions; // ReSharper disable AutoPropertyCanBeMadeGetOnly.Global @@ -24,6 +24,7 @@ protected ModuleApplicationBase(Type startupModuleType, IServiceCollection servi Services = services; services.AddSingleton(this); services.TryAddObjectAccessor(); + // TODO:希望替换成使用SG的方式来注入服务,将GetAllEnabledModule函数替换掉. Source = GetAllEnabledModule(services); Modules = LoadModules; } @@ -40,7 +41,7 @@ private IReadOnlyList LoadModules var module = Source.FirstOrDefault(o => o.GetType() == StartupModuleType) ?? throw new($"类型为“{StartupModuleType.FullName}”的模块实例无法找到"); modules.Add(module); var depends = module.GetDependedTypes(); - foreach (var dependType in depends.Where(AppModule.IsAppModule)) + foreach (var dependType in depends) { var dependModule = Source.ToList().Find(m => m.GetType() == dependType); if (dependModule is null) continue;