diff --git a/.github/workflows/default.yml b/.github/workflows/default.yml index 8ba671fc..e99c2125 100644 --- a/.github/workflows/default.yml +++ b/.github/workflows/default.yml @@ -12,33 +12,25 @@ on: - "dev" jobs: - mac-build: - runs-on: macos-latest + build: + name: Running tests on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macOS-latest] steps: - uses: actions/checkout@v3 - name: Setup .NET SDK uses: actions/setup-dotnet@v3 with: dotnet-version: | - 6.0.x - 7.0.x - 8.0.x - - name: build - run: bash build.sh - - linux-build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - name: Setup .NET SDK - uses: actions/setup-dotnet@v3 - with: - dotnet-version: | - 6.0.x - 7.0.x - 8.0.x + 6.x + 7.x + 8.x + - name: dotnet info + run: dotnet --info - name: build - run: bash build.sh + run: bash build.sh --target=test windows-build: runs-on: windows-latest diff --git a/Directory.Packages.props b/Directory.Packages.props index 851637f7..e3dccd61 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -24,7 +24,8 @@ - + + diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 405c6088..e06a5a3d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -36,4 +36,3 @@ steps: displayName: 'Powershell Script' env: Nuget__ApiKey: $(nugetApiKey) - Nuget__SourceUrl: $(nugetSourceUrl) diff --git a/build.cake b/build.cake deleted file mode 100644 index 9f904019..00000000 --- a/build.cake +++ /dev/null @@ -1,156 +0,0 @@ -/////////////////////////////////////////////////////////////////////////////// -// ARGUMENTS -/////////////////////////////////////////////////////////////////////////////// - -var target = Argument("target", "Default"); -var configuration = Argument("configuration", "Release"); -var stable = Argument("stable", "false"); - -var branchName = EnvironmentVariable("BUILD_SOURCEBRANCHNAME") ?? "local"; -var isWindowsAgent = EnvironmentVariable("Agent_OS") == "Windows_NT" || branchName == "local"; - -var solutionPath = "./WeihanLi.Common.sln"; -var srcProjects = GetFiles("./src/**/*.csproj"); -var testProjects = GetFiles("./test/**/*.csproj"); -var packProjects = GetFiles("./src/**/*.csproj"); - -var artifacts = "./artifacts/packages"; - -/////////////////////////////////////////////////////////////////////////////// -// SETUP / TEARDOWN -/////////////////////////////////////////////////////////////////////////////// - -Setup(ctx => -{ - // Executed BEFORE the first task. - Information("Running tasks..."); - PrintBuildInfo(ctx); -}); - -Teardown(ctx => -{ - // Executed AFTER the last task. - Information("Finished running tasks."); -}); - -/////////////////////////////////////////////////////////////////////////////// -// TASKS -/////////////////////////////////////////////////////////////////////////////// - -Task("clean") - .Description("Clean") - .Does(() => - { - var deleteSetting = new DeleteDirectorySettings() - { - Force = true, - Recursive = true - }; - if (DirectoryExists(artifacts)) - { - DeleteDirectory(artifacts, deleteSetting); - } - }); - -Task("restore") - .Description("Restore") - .Does(() => - { - foreach(var project in srcProjects) - { - DotNetRestore(project.FullPath); - } - }); - -Task("build") - .Description("Build") - .IsDependentOn("clean") - .IsDependentOn("restore") - .Does(() => - { - var buildSetting = new DotNetBuildSettings - { - NoRestore = true, - Configuration = configuration - }; - foreach(var project in srcProjects) - { - DotNetBuild(project.FullPath, buildSetting); - } - }); - - -Task("test") - .Description("Tests") - .IsDependentOn("build") - .Does(() => - { - var testSettings = new DotNetTestSettings - { - NoRestore = false, - Configuration = "Debug" - }; - foreach(var project in testProjects) - { - DotNetTest(project.FullPath, testSettings); - } - }); - -Task("pack") - .Description("Pack package") - .IsDependentOn("test") - .Does((cakeContext) => - { - var settings = new DotNetPackSettings - { - Configuration = "Release", - OutputDirectory = artifacts, - VersionSuffix = "", - NoRestore = true, - }; - if(branchName != "master" && stable != "true"){ - settings.VersionSuffix = $"preview-{DateTime.UtcNow:yyyyMMdd-HHmmss}"; - } - foreach (var project in packProjects) - { - DotNetPack(project.FullPath, settings); - } - PublishArtifacts(cakeContext); - }); - -bool PublishArtifacts(ICakeContext context) -{ - if(context.Environment.Platform.IsUnix()) - { - Information($@"none windows build agent, do not publish packages"); - return false; - } - if(branchName == "master" || branchName == "preview") - { - var pushSetting =new DotNetNuGetPushSettings - { - SkipDuplicate = true, - Source = EnvironmentVariable("Nuget__SourceUrl") ?? "https://api.nuget.org/v3/index.json", - ApiKey = EnvironmentVariable("Nuget__ApiKey") - }; - var packages = GetFiles($"{artifacts}/*.nupkg"); - foreach(var package in packages) - { - DotNetNuGetPush(package.FullPath, pushSetting); - } - return true; - } - Information($@"branch name does not match, do not publish packages"); - return false; -} - -void PrintBuildInfo(ICakeContext context){ - Information($@"branch:{branchName},Platform: {context.Environment.Platform.Family}, IsUnix: {context.Environment.Platform.IsUnix()} - BuildID:{EnvironmentVariable("BUILD_BUILDID")},BuildNumber:{EnvironmentVariable("BUILD_BUILDNUMBER")},BuildReason:{EnvironmentVariable("BUILD_REASON")} - "); -} - -Task("Default") - .IsDependentOn("pack"); - -RunTarget(target); diff --git a/build.ps1 b/build.ps1 index c9fc00b4..f78ac380 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,8 +1,5 @@ -[string]$SCRIPT = '.\build.cake' - -# Install cake.tool -dotnet tool install --global cake.tool - -Write-Host "dotnet cake $SCRIPT $ARGS" -ForegroundColor GREEN +dotnet tool update -g dotnet-execute --prerelease -dotnet cake $SCRIPT $CAKE_ARGS $ARGS \ No newline at end of file +Write-Host 'dotnet-exec ./build/build.cs "--args=$ARGS"' -ForegroundColor GREEN + +dotnet-exec ./build/build.cs --args $ARGS diff --git a/build.sh b/build.sh index 1fbcf731..fd2874ef 100644 --- a/build.sh +++ b/build.sh @@ -1,10 +1,7 @@ #!/bin/sh -SCRIPT='./build.cake' -# Install cake.tool -dotnet tool install --global cake.tool +dotnet tool update -g dotnet-execute --prerelease export PATH="$PATH:$HOME/.dotnet/tools" -echo "dotnet cake $SCRIPT $@" - -dotnet cake $SCRIPT "$@" \ No newline at end of file +echo "dotnet-exec ./build/build.cs --args $@" +dotnet-exec ./build/build.cs --args "$@" diff --git a/build/build.cs b/build/build.cs new file mode 100644 index 00000000..c2000d54 --- /dev/null +++ b/build/build.cs @@ -0,0 +1,321 @@ +// Copyright (c) 2022-2023 Weihan Li. All rights reserved. +// Licensed under the Apache license version 2.0 http://www.apache.org/licenses/LICENSE-2.0 + +// r: "nuget: CliWrap, 3.6.4" + +using CliWrap; +using Newtonsoft.Json; + +// +var target = Guard.NotNull(Argument("target", "Default")); +var apiKey = Argument("apiKey", ""); +var stable = ArgumentBool("stable", false); +var noPush = ArgumentBool("noPush", false); +var branchName = Environment.GetEnvironmentVariable("BUILD_SOURCEBRANCHNAME") ?? "local"; + +var solutionPath = "./WeihanLi.Common.sln"; +string[] srcProjects = [ + "./src/WeihanLi.Common/WeihanLi.Common.csproj", + "./src/WeihanLi.Common.Aspect.AspectCore/WeihanLi.Common.Aspect.AspectCore.csproj", + "./src/WeihanLi.Common.Aspect.Castle/WeihanLi.Common.Aspect.Castle.csproj", + "./src/WeihanLi.Common.Logging.Serilog/WeihanLi.Common.Logging.Serilog.csproj", + "./src/WeihanLi.Data/WeihanLi.Data.csproj", + "./src/WeihanLi.Extensions.Hosting/WeihanLi.Extensions.Hosting.csproj", +]; +string[] testProjects = [ "./test/WeihanLi.Common.Test/WeihanLi.Common.Test.csproj" ]; + +await BuildProcess.CreateBuilder() + .WithSetup(() => + { + // cleanup artifacts + if (Directory.Exists("./artifacts/packages")) + Directory.Delete("./artifacts/packages", true); + + // args + Console.WriteLine("Arguments"); + Console.WriteLine($" {args.StringJoin(" ")}"); + + // dump runtime info + Console.WriteLine("RuntimeInfo:"); + Console.WriteLine(ApplicationHelper.RuntimeInfo.ToJson(new JsonSerializerSettings() + { + Formatting = Formatting.Indented + })); + }) + .WithTask("hello", b => b.WithExecution(() => Console.WriteLine("Hello dotnet-exec build"))) + .WithTask("build", b => + { + b.WithDescription("dotnet build") + .WithExecution(() => ExecuteCommandAsync($"dotnet build {solutionPath}")) + ; + }) + .WithTask("test", b => + { + b.WithDescription("dotnet test") + .WithDependency("build") + .WithExecution(async () => + { + foreach (var project in testProjects) + { + await ExecuteCommandAsync($"dotnet test {project}"); + } + }) + ; + }) + .WithTask("pack", b => b.WithDescription("dotnet pack") + .WithDependency("test") + .WithExecution(async () => + { + if (stable) + { + foreach (var project in srcProjects) + { + await ExecuteCommandAsync($"dotnet pack {project} -o ./artifacts/packages"); + } + } + else + { + var suffix = $"preview-{DateTime.UtcNow:yyyyMMdd-HHmmss}"; + foreach (var project in srcProjects) + { + await ExecuteCommandAsync($"dotnet pack {project} -o ./artifacts/packages --version-suffix {suffix}"); + } + } + + if (noPush) + { + Console.WriteLine("Skip push there's noPush specified"); + return; + } + + if (string.IsNullOrEmpty(apiKey)) + { + // try to get apiKey from environment variable + apiKey = Environment.GetEnvironmentVariable("NuGet__ApiKey"); + + if (string.IsNullOrEmpty(apiKey)) + { + Console.WriteLine("Skip push since there's no apiKey found"); + return; + } + } + + if (branchName != "master" && branchName != "preview") + { + Console.WriteLine($"Skip push since branch name {branchName} not support push packages"); + return; + } + + // push nuget packages + foreach (var file in Directory.GetFiles("./artifacts/packages/", "*.nupkg")) + { + await ExecuteCommandAsync($"dotnet nuget push {file} -k {apiKey} --skip-duplicate"); + } + })) + .WithTask("Default", b => b.WithDependency("hello").WithDependency("pack")) + .Build() + .ExecuteAsync(target); + + +bool ArgumentBool(string argumentName, bool defaultValue = default) +{ + var value = ArgumentInternal(argumentName); + if (value is null) return defaultValue; + if (value == string.Empty || value == "1") return true; + return value is "0" ? false : bool.Parse(value); +} + +string? Argument(string argumentName, string? defaultValue = default) +{ + return ArgumentInternal(argumentName) ?? defaultValue; +} + +string? ArgumentInternal(string argumentName) +{ + for (var i = 0; i < args.Length; i++) + { + if (args[i] == $"--{argumentName}" || args[i] == $"-{argumentName}") + { + if (((i + 1) == args.Length || args[i + 1].StartsWith('-'))) + return string.Empty; + + return args[i + 1]; + } + + if (args[i].StartsWith($"-{argumentName}=")) + return args[i].Substring($"-{argumentName}=".Length); + + if (args[i].StartsWith($"--{argumentName}=")) + return args[i].Substring($"--{argumentName}=".Length); + } + + return null; +} + +async Task ExecuteCommandAsync(string commandText) +{ + Console.WriteLine($"Executing command: \n {commandText}"); + Console.WriteLine(); + var splits = commandText.Split([' '], 2); + var result = await Cli.Wrap(splits[0]) + .WithArguments(splits.Length > 1 ? splits[1] : string.Empty) + .WithStandardErrorPipe(PipeTarget.ToStream(Console.OpenStandardError())) + .WithStandardOutputPipe(PipeTarget.ToStream(Console.OpenStandardOutput())) + .ExecuteAsync(); + Console.WriteLine(); + Console.WriteLine($"ExitCode: {result.ExitCode} ElapsedTime: {result.RunTime}"); +} + +file sealed class BuildProcess +{ + public IReadOnlyCollection Tasks { get; init; } = []; + public Func? Setup { private get; init; } + public Func? Cleanup { private get; init; } + + public async Task ExecuteAsync(string target) + { + var task = Tasks.FirstOrDefault(x => x.Name == target); + if (task is null) + throw new InvalidOperationException("Invalid target to execute"); + + try + { + if (Setup != null) + await Setup.Invoke(); + + await ExecuteTask(task); + } + finally + { + if (Cleanup != null) + await Cleanup.Invoke(); + } + } + + private static async Task ExecuteTask(BuildTask task) + { + foreach (var dependencyTask in task.Dependencies) + { + await ExecuteTask(dependencyTask); + } + + Console.WriteLine($"===== Task {task.Name} {task.Description} executing ======"); + await task.ExecuteAsync(); + Console.WriteLine($"===== Task {task.Name} {task.Description} executed ======"); + } + + public static BuildProcessBuilder CreateBuilder() + { + return new BuildProcessBuilder(); + } +} + +file sealed class BuildProcessBuilder +{ + private readonly List _tasks = []; + private Func? _setup, _cleanup; + + public BuildProcessBuilder WithTask(string name, Action buildTaskConfigure) + { + var buildTaskBuilder = new BuildTaskBuilder(name); + buildTaskBuilder.WithTaskFinder(s => _tasks.Find(t => t.Name == s) ?? throw new InvalidOperationException($"No task found with name {s}")); + buildTaskConfigure.Invoke(buildTaskBuilder); + var task = buildTaskBuilder.Build(); + _tasks.Add(task); + return this; + } + + public BuildProcessBuilder WithSetup(Action setupFunc) + { + _setup = setupFunc.WrapTask(); + return this; + } + + public BuildProcessBuilder WithSetup(Func setupFunc) + { + _setup = setupFunc; + return this; + } + + public BuildProcessBuilder WithCleanup(Action cleanupFunc) + { + _cleanup = cleanupFunc.WrapTask(); + return this; + } + + public BuildProcessBuilder WithCleanup(Func cleanupFunc) + { + _cleanup = cleanupFunc; + return this; + } + + internal BuildProcess Build() + { + return new BuildProcess() + { + Tasks = _tasks, + Setup = _setup, + Cleanup = _cleanup + }; + } +} + +file sealed class BuildTask(string name, string? description, Func? execution = null) +{ + public string Name => name; + public string Description => description ?? name; + + public IReadOnlyCollection Dependencies { get; init; } = []; + + public Task ExecuteAsync() => execution?.Invoke() ?? Task.CompletedTask; +} + +file sealed class BuildTaskBuilder(string name) +{ + private readonly string _name = name; + + private string? _description; + private Func? _execution; + private readonly List _dependencies = []; + + public BuildTaskBuilder WithDescription(string description) + { + _description = description; + return this; + } + + public BuildTaskBuilder WithExecution(Action execution) + { + _execution = execution.WrapTask(); + return this; + } + public BuildTaskBuilder WithExecution(Func execution) + { + _execution = execution; + return this; + } + + public BuildTaskBuilder WithDependency(string dependencyTaskName) + { + if (_taskFinder is null) throw new InvalidOperationException("Dependency task name is not supported"); + + _dependencies.Add(_taskFinder.Invoke(dependencyTaskName)); + return this; + } + + private Func? _taskFinder; + internal BuildTaskBuilder WithTaskFinder(Func taskFinder) + { + _taskFinder = taskFinder; + return this; + } + + public BuildTask Build() + { + var buildTask = new BuildTask(_name, _description, _execution) + { + Dependencies = _dependencies + }; + return buildTask; + } +} diff --git a/nuget.config b/nuget.config index 6ce97590..a4e1b7cd 100644 --- a/nuget.config +++ b/nuget.config @@ -1,5 +1,8 @@ + + + diff --git a/samples/DotNetCoreSample/AppHostTest.cs b/samples/DotNetCoreSample/AppHostTest.cs index 8547c87d..98d9741f 100644 --- a/samples/DotNetCoreSample/AppHostTest.cs +++ b/samples/DotNetCoreSample/AppHostTest.cs @@ -99,7 +99,7 @@ public async Task StartAsync(CancellationToken cancellationToken) _listener.Start(); var logger = _serviceProvider.GetRequiredService>(); logger.LogInformation("WebServer started"); - + while (!cancellationToken.IsCancellationRequested) { var listenerContext = await _listener.GetContextAsync(); @@ -135,7 +135,7 @@ public WebServerHostedService(IWebServer server) { _server = server; } - + public override async Task StopAsync(CancellationToken cancellationToken) { await _server.StopAsync(cancellationToken); diff --git a/samples/DotNetCoreSample/CommandExecutorTest.cs b/samples/DotNetCoreSample/CommandExecutorTest.cs index b3b823d5..f3ce2fe3 100644 --- a/samples/DotNetCoreSample/CommandExecutorTest.cs +++ b/samples/DotNetCoreSample/CommandExecutorTest.cs @@ -1,16 +1,24 @@ // Copyright (c) Weihan Li. All rights reserved. // Licensed under the MIT license. +using WeihanLi.Common.Helpers; +using WeihanLi.Extensions; using static WeihanLi.Common.Helpers.CommandExecutor; namespace DotNetCoreSample; -internal class CommandExecutorTest +internal static class CommandExecutorTest { public static void MainTest() { - var result = ExecuteAndCapture("hostname"); - result.EnsureSuccessfulExitCode(); - Console.WriteLine(result.StandardOut); + ExecuteAndCapture("hostname") + .PrintOutputToConsole() + .EnsureSuccessExitCode(); + + ExecuteAndOutput("hostname").EnsureSuccessExitCode(); + + ExecuteAndOutputAsync("hostname").Wait(); + + ExecuteCommandAndOutput("hostname").EnsureSuccessExitCode(); } } diff --git a/samples/DotNetCoreSample/DisposeTest.cs b/samples/DotNetCoreSample/DisposeTest.cs index 0340a652..82f008bf 100644 --- a/samples/DotNetCoreSample/DisposeTest.cs +++ b/samples/DotNetCoreSample/DisposeTest.cs @@ -30,7 +30,7 @@ public static void MainTest() Name = "MainTest2" }; Console.WriteLine(service.GetType()); - + service = null; Console.WriteLine(service is null); } @@ -39,7 +39,7 @@ public static void MainTest() Console.WriteLine(@$"---- {nameof(MainTest)} end"); } - + public static async ValueTask MainTestAsync() { Console.WriteLine(@$"---- {nameof(MainTestAsync)} start"); @@ -63,7 +63,7 @@ public static async ValueTask MainTestAsync() Name = "MainTestAsync2" }; Console.WriteLine(service.GetType()); - + service = null; Console.WriteLine(service is null); } @@ -77,12 +77,12 @@ public static async ValueTask MainTestAsync() file sealed class TestService : DisposableBase { public required string Name { get; init; } - + protected override void Dispose(bool disposing) { if (disposing) { - Console.WriteLine($@"<<{Name}>> disposes managed resources"); + Console.WriteLine($@"<<{Name}>> disposes managed resources"); } Console.WriteLine($@"<<{Name}>> disposes unmanaged resources"); base.Dispose(disposing); diff --git a/samples/DotNetCoreSample/NewtonJsonFormatter.cs b/samples/DotNetCoreSample/NewtonJsonFormatter.cs index a9108242..685f1455 100644 --- a/samples/DotNetCoreSample/NewtonJsonFormatter.cs +++ b/samples/DotNetCoreSample/NewtonJsonFormatter.cs @@ -11,14 +11,14 @@ namespace DotNetCoreSample; -public sealed class NewtonJsonFormatterOptions: ConsoleFormatterOptions +public sealed class NewtonJsonFormatterOptions : ConsoleFormatterOptions { } -public sealed class NewtonJsonFormatter: ConsoleFormatter +public sealed class NewtonJsonFormatter : ConsoleFormatter { public const string FormatterName = "NewtonJson"; - + private readonly NewtonJsonFormatterOptions _options; public NewtonJsonFormatter(IOptions options) : base(FormatterName) @@ -73,7 +73,7 @@ public override void Write(in LogEntry logEntry, IExternalScopeP } writer.WriteEndObject(); } - + writer.WriteEndObject(); writer.Flush(); textWriter.WriteLine(); @@ -82,7 +82,7 @@ public override void Write(in LogEntry logEntry, IExternalScopeP public static partial class LoggingBuilderExtensions { - public static ILoggingBuilder AddNewtonJsonConsole(this ILoggingBuilder loggingBuilder, + public static ILoggingBuilder AddNewtonJsonConsole(this ILoggingBuilder loggingBuilder, Action? optionsConfigure = null) { loggingBuilder.AddConsole(options => options.FormatterName = NewtonJsonFormatter.FormatterName); diff --git a/samples/DotNetCoreSample/Program.cs b/samples/DotNetCoreSample/Program.cs index 006cff1c..f3a56926 100644 --- a/samples/DotNetCoreSample/Program.cs +++ b/samples/DotNetCoreSample/Program.cs @@ -1,11 +1,11 @@ -using System.Net.Mime; -// Copyright (c) Weihan Li. All rights reserved. +// Copyright (c) Weihan Li. All rights reserved. // Licensed under the Apache license. using DotNetCoreSample; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; +using System.Net.Mime; using WeihanLi.Common; using WeihanLi.Common.Aspect; using WeihanLi.Common.Event; @@ -15,6 +15,8 @@ Console.WriteLine("----------DotNetCoreSample----------"); +InvokeHelper.OnInvokeException = ex => ConsoleHelper.ErrorWriteWithColor(ex.ToString(), ConsoleColor.DarkRed); + // ServiceDecoratorTest.MainTest(); // var dataLogger = LogHelper.GetLogger(typeof(DataExtension)); @@ -324,18 +326,20 @@ // await AppHostTest.MainTest(); // NewtonJsonFormatterTest.MainTest(); -DisposeTest.MainTest(); -Console.WriteLine(); -await DisposeTest.MainTestAsync(); -Console.WriteLine(); - -ConsoleHelper.ReadKeyWithPrompt("Press any key to continue"); - -await DisposeTest.MainTestAsync(); -Console.WriteLine(); +// DisposeTest.MainTest(); +// Console.WriteLine(); +// await DisposeTest.MainTestAsync(); +// Console.WriteLine(); +// +// ConsoleHelper.ReadKeyWithPrompt("Press any key to continue"); +// +// await DisposeTest.MainTestAsync(); +// Console.WriteLine(); +// +// GC.Collect(); +// GC.WaitForPendingFinalizers(); -GC.Collect(); -GC.WaitForPendingFinalizers(); +await InvokeHelper.TryInvokeAsync(TemplatingSample.MainTest); ConsoleHelper.ReadKeyWithPrompt("Press any key to exit"); diff --git a/samples/DotNetCoreSample/TemplatingSample.cs b/samples/DotNetCoreSample/TemplatingSample.cs index a00fd520..d774214b 100644 --- a/samples/DotNetCoreSample/TemplatingSample.cs +++ b/samples/DotNetCoreSample/TemplatingSample.cs @@ -1,17 +1,47 @@ // Copyright (c) Weihan Li. All rights reserved. // Licensed under the Apache license. +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; using WeihanLi.Common.Templating; namespace DotNetCoreSample; public class TemplatingSample { - public static void MainTest() + public static async Task MainTest() { - var engine = TemplateEngine.CreateDefault(); - var result = engine.RenderAsync("Hello {{Name}}", new { Name = ".NET" }); - result.Wait(); - Console.WriteLine(result.Result); + { + var engine = TemplateEngine.CreateDefault(); + var result = await engine.RenderAsync("Hello {{Name}}", new { Name = ".NET" }); + Console.WriteLine(result); + } + + { + var result = await TemplateEngine.CreateDefault().RenderAsync("Hello {{$env USERNAME}}"); + Console.WriteLine(result); + } + + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection([new("UserName", "Test")]) + .Build(); + var result = await TemplateEngine.CreateDefault(builder => builder.ConfigureOptions(options => options.Configuration = configuration)) + .RenderAsync("Hello {{$config UserName}}"); + Console.WriteLine(result); + } + + { + var services = new ServiceCollection(); + IConfiguration configuration = new ConfigurationBuilder() + .AddInMemoryCollection([new("UserName1", "Test1234")]) + .Build(); + services.AddSingleton(configuration); + services.AddTemplating(); + await using var provider = services.BuildServiceProvider(); + var result = await provider.GetRequiredService() + .RenderAsync("Hello {{$config UserName1}}"); + Console.WriteLine(result); + } } } diff --git a/src/WeihanLi.Common/Aspect/AspectDelegate.cs b/src/WeihanLi.Common/Aspect/AspectDelegate.cs index 7a4af19d..e6f37179 100644 --- a/src/WeihanLi.Common/Aspect/AspectDelegate.cs +++ b/src/WeihanLi.Common/Aspect/AspectDelegate.cs @@ -86,7 +86,7 @@ public static void InvokeInternal(IInvocation invocation, IReadOnlyList GetAspectDelegate(IInvocation invocation, IReadOnlyList? interceptors, Func? completeFunc) { diff --git a/src/WeihanLi.Common/Aspect/FluentAspectOptionsExtensions.cs b/src/WeihanLi.Common/Aspect/FluentAspectOptionsExtensions.cs index 05e73794..010e4927 100644 --- a/src/WeihanLi.Common/Aspect/FluentAspectOptionsExtensions.cs +++ b/src/WeihanLi.Common/Aspect/FluentAspectOptionsExtensions.cs @@ -67,7 +67,7 @@ public static IInterceptionConfiguration InterceptMethod(this FluentAspectOption return options.InterceptMethod(m => m.GetSignature().Equals(methodSignature)); } - public static IInterceptionConfiguration InterceptPropertyGetter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]T>(this FluentAspectOptions options, + public static IInterceptionConfiguration InterceptPropertyGetter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(this FluentAspectOptions options, Expression> expression) { var prop = expression.GetProperty(); @@ -84,7 +84,7 @@ public static IInterceptionConfiguration InterceptMethod(this FluentAspectOption return options.InterceptMethod(prop.GetMethod); } - public static IInterceptionConfiguration InterceptPropertySetter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]T>(this FluentAspectOptions options, + public static IInterceptionConfiguration InterceptPropertySetter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(this FluentAspectOptions options, Expression> expression) { var prop = expression.GetProperty(); @@ -234,7 +234,7 @@ public static FluentAspectOptions NoInterceptMethod(this FluentAspectOptions opt return options.NoInterceptMethod(m => m.GetSignature().Equals(methodSignature)); } - public static FluentAspectOptions NoInterceptProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]T>(this FluentAspectOptions options, + public static FluentAspectOptions NoInterceptProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(this FluentAspectOptions options, Expression> expression) { var prop = expression.GetProperty(); @@ -254,7 +254,7 @@ public static FluentAspectOptions NoInterceptMethod(this FluentAspectOptions opt return options; } - public static FluentAspectOptions NoInterceptPropertyGetter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]T>(this FluentAspectOptions options, + public static FluentAspectOptions NoInterceptPropertyGetter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(this FluentAspectOptions options, Expression> expression) { var prop = expression.GetProperty(); @@ -271,7 +271,7 @@ public static FluentAspectOptions NoInterceptMethod(this FluentAspectOptions opt return options.NoInterceptMethod(prop.GetMethod); } - public static FluentAspectOptions NoInterceptPropertySetter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]T>(this FluentAspectOptions options, + public static FluentAspectOptions NoInterceptPropertySetter<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(this FluentAspectOptions options, Expression> expression) { var prop = expression.GetProperty(); @@ -323,8 +323,8 @@ public static FluentAspectOptions UseProxyFactory(this FluentAspe options.ProxyFactory = new TProxyFactory(); return options; } - - public static FluentAspectOptions UseProxyFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TProxyFactory>(this FluentAspectOptions options, + + public static FluentAspectOptions UseProxyFactory<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TProxyFactory>(this FluentAspectOptions options, params object[] parameters) where TProxyFactory : class, IProxyFactory { options.ProxyFactory = ActivatorHelper.CreateInstance(parameters); diff --git a/src/WeihanLi.Common/Aspect/IInterceptionConfiguration.cs b/src/WeihanLi.Common/Aspect/IInterceptionConfiguration.cs index 7afa154d..c184ebb4 100644 --- a/src/WeihanLi.Common/Aspect/IInterceptionConfiguration.cs +++ b/src/WeihanLi.Common/Aspect/IInterceptionConfiguration.cs @@ -40,7 +40,7 @@ public static IInterceptionConfiguration With(this IInterceptionConfiguration in return interceptionConfiguration; } - public static IInterceptionConfiguration With<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TInterceptor>(this IInterceptionConfiguration interceptionConfiguration, params object?[] parameters) where TInterceptor : IInterceptor + public static IInterceptionConfiguration With<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TInterceptor>(this IInterceptionConfiguration interceptionConfiguration, params object?[] parameters) where TInterceptor : IInterceptor { if (Guard.NotNull(parameters, nameof(parameters)).Length == 0) { diff --git a/src/WeihanLi.Common/Aspect/ProxyUtils.cs b/src/WeihanLi.Common/Aspect/ProxyUtils.cs index e134227e..67d9dd2e 100644 --- a/src/WeihanLi.Common/Aspect/ProxyUtils.cs +++ b/src/WeihanLi.Common/Aspect/ProxyUtils.cs @@ -29,7 +29,7 @@ internal static class ProxyUtils "GetType", "Finalize", }; - + static ProxyUtils() { var asmBuilder = AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(ProxyAssemblyName), AssemblyBuilderAccess.Run); @@ -70,7 +70,7 @@ private static string GetFriendlyTypeName(this Type? type) return type.IsBasicType() ? type.Name : type.FullName ?? type.Name; } - + [RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")] [RequiresUnreferencedCode("Unreferenced code may be used")] public static Type CreateInterfaceProxy(Type interfaceType) @@ -315,7 +315,7 @@ public static Type CreateInterfaceProxy(Type interfaceType, Type? implementType) public static Type CreateClassProxy(Type serviceType, Type? implementType) { Guard.NotNull(serviceType); - + if (implementType is null) { implementType = serviceType; @@ -767,7 +767,7 @@ private static GenericParameterAttributes ToClassGenericParameterAttributes(Gene return GenericParameterAttributes.None; } } - + [RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")] [RequiresUnreferencedCode("Unreferenced code may be used")] private static CustomAttributeBuilder DefineCustomAttribute(CustomAttributeData customAttributeData) @@ -776,7 +776,7 @@ private static CustomAttributeBuilder DefineCustomAttribute(CustomAttributeData if (customAttributeData.NamedArguments is null) return new CustomAttributeBuilder(customAttributeData.Constructor, customAttributeData.ConstructorArguments.Select(c => c.Value).ToArray()); - + var attributeTypeInfo = customAttributeData.AttributeType.GetTypeInfo(); var constructorArgs = customAttributeData.ConstructorArguments .Select(ReadAttributeValue) diff --git a/src/WeihanLi.Common/CacheUtil.cs b/src/WeihanLi.Common/CacheUtil.cs index a0d6d0c0..2c8a241a 100644 --- a/src/WeihanLi.Common/CacheUtil.cs +++ b/src/WeihanLi.Common/CacheUtil.cs @@ -9,14 +9,14 @@ public static class CacheUtil { private static readonly ConcurrentDictionary TypePropertyCache = new(); private static readonly ConcurrentDictionary TypeFieldCache = new(); - - public static PropertyInfo[] GetTypeProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]Type type) + + public static PropertyInfo[] GetTypeProperties([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] Type type) { Guard.NotNull(type); return TypePropertyCache.GetOrAdd(type, _ => type.GetProperties()); } - - public static FieldInfo[] GetTypeFields([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]Type type) + + public static FieldInfo[] GetTypeFields([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type type) { Guard.NotNull(type); return TypeFieldCache.GetOrAdd(type, _ => type.GetFields()); diff --git a/src/WeihanLi.Common/Data/Repository.cs b/src/WeihanLi.Common/Data/Repository.cs index a4f89284..734e6abe 100644 --- a/src/WeihanLi.Common/Data/Repository.cs +++ b/src/WeihanLi.Common/Data/Repository.cs @@ -11,7 +11,7 @@ namespace WeihanLi.Common.Data; [CLSCompliant(false)] -public class Repository<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors|DynamicallyAccessedMemberTypes.PublicProperties)]TEntity> : IRepository where TEntity : new() +public class Repository<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicProperties)] TEntity> : IRepository where TEntity : new() { #region TODO: Cache External diff --git a/src/WeihanLi.Common/DependencyInjection/ServiceContainer.cs b/src/WeihanLi.Common/DependencyInjection/ServiceContainer.cs index d975e264..5cd999de 100644 --- a/src/WeihanLi.Common/DependencyInjection/ServiceContainer.cs +++ b/src/WeihanLi.Common/DependencyInjection/ServiceContainer.cs @@ -155,7 +155,7 @@ public void Dispose() [RequiresUnreferencedCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic constraints), trimming can't validate that the requirements of those annotations are met.")] private object? GetServiceInstance(Type serviceType, ServiceDefinition serviceDefinition) => EnrichObject(GetServiceInstanceInternal(serviceType, serviceDefinition)); - + [RequiresDynamicCode("The native code for this instantiation might not be available at runtime.")] [RequiresUnreferencedCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic constraints), trimming can't validate that the requirements of those annotations are met.")] private object? GetServiceInstanceInternal(Type serviceType, ServiceDefinition serviceDefinition) diff --git a/src/WeihanLi.Common/DependencyInjection/ServiceContainerBuilderExtensions.cs b/src/WeihanLi.Common/DependencyInjection/ServiceContainerBuilderExtensions.cs index 2c551b05..537996f5 100644 --- a/src/WeihanLi.Common/DependencyInjection/ServiceContainerBuilderExtensions.cs +++ b/src/WeihanLi.Common/DependencyInjection/ServiceContainerBuilderExtensions.cs @@ -7,7 +7,7 @@ namespace WeihanLi.Common.DependencyInjection; public static partial class ServiceContainerBuilderExtensions { - public static IServiceContainerBuilder AddSingleton<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TService>(this IServiceContainerBuilder serviceContainerBuilder, TService service) + public static IServiceContainerBuilder AddSingleton<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(this IServiceContainerBuilder serviceContainerBuilder, TService service) { Guard.NotNull(service, nameof(service)); serviceContainerBuilder.Add(new ServiceDefinition(service!, typeof(TService))); @@ -182,7 +182,7 @@ public static IServiceContainerBuilder RegisterAssemblyTypesAsImplementedInterfa /// service lifetime /// services public static IServiceContainerBuilder RegisterTypeAsImplementedInterfaces( - this IServiceContainerBuilder services, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]Type type, + this IServiceContainerBuilder services, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type type, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) => RegisterTypeAsImplementedInterfaces(services, type, null, serviceLifetime); @@ -194,7 +194,7 @@ public static IServiceContainerBuilder RegisterTypeAsImplementedInterfaces( /// interfaceTypeFilter /// service lifetime /// services - public static IServiceContainerBuilder RegisterTypeAsImplementedInterfaces(this IServiceContainerBuilder services, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]Type type, Func? interfaceTypeFilter, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) + public static IServiceContainerBuilder RegisterTypeAsImplementedInterfaces(this IServiceContainerBuilder services, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] Type type, Func? interfaceTypeFilter, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) { Guard.NotNull(type); foreach (var interfaceType in type.GetImplementedInterfaces()) diff --git a/src/WeihanLi.Common/DependencyInjection/ServiceDefinition.cs b/src/WeihanLi.Common/DependencyInjection/ServiceDefinition.cs index 5f2b6e4c..c9a4720c 100644 --- a/src/WeihanLi.Common/DependencyInjection/ServiceDefinition.cs +++ b/src/WeihanLi.Common/DependencyInjection/ServiceDefinition.cs @@ -58,27 +58,27 @@ public static ServiceDefinition Singleton(Func() where TServiceImplement : TService + public static ServiceDefinition Singleton() where TServiceImplement : TService { return new(typeof(TService), typeof(TServiceImplement), ServiceLifetime.Singleton); } - public static ServiceDefinition Singleton<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TService>() + public static ServiceDefinition Singleton<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>() { return new(typeof(TService), ServiceLifetime.Singleton); } - public static ServiceDefinition Scoped<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TService>(Func factory) + public static ServiceDefinition Scoped<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>(Func factory) { return new(typeof(TService), factory, ServiceLifetime.Scoped); } - public static ServiceDefinition Scoped() where TServiceImplement : TService + public static ServiceDefinition Scoped() where TServiceImplement : TService { return new(typeof(TService), typeof(TServiceImplement), ServiceLifetime.Scoped); } - public static ServiceDefinition Scoped<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TService>() + public static ServiceDefinition Scoped<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>() { return new(typeof(TService), ServiceLifetime.Scoped); } @@ -88,12 +88,12 @@ public static ServiceDefinition Transient(Func() + public static ServiceDefinition Transient<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TService>() { return new(typeof(TService), ServiceLifetime.Transient); } - public static ServiceDefinition Transient() where TServiceImplement : TService + public static ServiceDefinition Transient() where TServiceImplement : TService { return new(typeof(TService), typeof(TServiceImplement), ServiceLifetime.Transient); } diff --git a/src/WeihanLi.Common/DependencyResolver.cs b/src/WeihanLi.Common/DependencyResolver.cs index e6e2932f..1acb744b 100644 --- a/src/WeihanLi.Common/DependencyResolver.cs +++ b/src/WeihanLi.Common/DependencyResolver.cs @@ -62,6 +62,7 @@ public ServiceProviderDependencyResolver(ServiceProvider serviceProvider) return _serviceProvider.GetService(serviceType); } + [RequiresUnreferencedCode("Calls WeihanLi.Common.DependencyInjectionExtensions.GetServices(Type)")] public IEnumerable GetServices(Type serviceType) { return _serviceProvider.GetServices(serviceType); @@ -92,7 +93,7 @@ public async Task TryInvokeServiceAsync(Func act private sealed class DefaultDependencyResolver : IDependencyResolver { - public object? GetService([DynamicallyAccessedMembers((DynamicallyAccessedMemberTypes.PublicParameterlessConstructor))]Type serviceType) + public object? GetService([DynamicallyAccessedMembers((DynamicallyAccessedMemberTypes.PublicParameterlessConstructor))] Type serviceType) { // Since attempting to create an instance of an interface or an abstract type results in an exception, immediately return null // to improve performance and the debugging experience with first-chance exceptions enabled. diff --git a/src/WeihanLi.Common/Event/EventBusExtensions.cs b/src/WeihanLi.Common/Event/EventBusExtensions.cs index aef1ab4e..85c4913b 100644 --- a/src/WeihanLi.Common/Event/EventBusExtensions.cs +++ b/src/WeihanLi.Common/Event/EventBusExtensions.cs @@ -39,8 +39,8 @@ public static IEventBuilder AddEvents(this IServiceCollection services) return new EventBuilder(services); } - - public static IEventBuilder AddEventHandler(this IEventBuilder eventBuilder, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + + public static IEventBuilder AddEventHandler(this IEventBuilder eventBuilder, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where TEvent : class, IEventBase where TEventHandler : class, IEventHandler { @@ -54,7 +54,7 @@ public static IEventBuilder AddEventHandler(this IEventBuilder eventBuil eventBuilder.Services.TryAddEnumerable(new ServiceDescriptor(typeof(IEventHandler), eventHandler)); return eventBuilder; } - + [RequiresUnreferencedCode("Assembly.GetTypes() requires unreferenced code")] public static IEventBuilder RegisterEventHandlers(this IEventBuilder builder, Func? filter = null, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton, params Assembly[] assemblies) { diff --git a/src/WeihanLi.Common/Extensions/CoreExtension.cs b/src/WeihanLi.Common/Extensions/CoreExtension.cs index f58b4f9f..a4ce2655 100644 --- a/src/WeihanLi.Common/Extensions/CoreExtension.cs +++ b/src/WeihanLi.Common/Extensions/CoreExtension.cs @@ -1555,6 +1555,21 @@ public static int Sign(this int value) return Math.Sign(value); } + /// + /// Ensures the exitCode success + /// + /// exitCode + /// successCode + /// + /// Exception when exitCode not match the successCode + public static int EnsureSuccessExitCode(this int exitCode, int successCode = 0) + { + if (exitCode != 0) + throw new InvalidOperationException($"Unexpected exit code:{exitCode}"); + + return exitCode; + } + #endregion int #region long @@ -1667,6 +1682,7 @@ public static T AsOrDefault(this object? @this, Func defaultValueFactory) /// Generic type parameter. /// this. /// A T. + [RequiresUnreferencedCode("Generic TypeConverters may require the generic types to be annotated. For example, NullableConverter requires the underlying type to be DynamicallyAccessedMembers All.")] public static T To(this object? @this) { #nullable disable @@ -1704,6 +1720,7 @@ public static T To(this object? @this) /// this. /// The type. /// An object. + [RequiresUnreferencedCode("Generic TypeConverters may require the generic types to be annotated. For example, NullableConverter requires the underlying type to be DynamicallyAccessedMembers All.")] public static object? To(this object? @this, Type type) { if (@this == null || @this == DBNull.Value) @@ -2599,14 +2616,14 @@ public static StringBuilder AppendLineIf(this StringBuilder builder, FuncThe @this to act on. /// The arguments. /// The new instance. - public static T? CreateInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]T>([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]this Type @this, params object?[]? args) => (T?)Activator.CreateInstance(@this, args); + public static T? CreateInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type @this, params object?[]? args) => (T?)Activator.CreateInstance(@this, args); /// /// if a type has empty constructor /// /// type /// - public static bool HasEmptyConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]this Type type) + public static bool HasEmptyConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type type) => Guard.NotNull(type, nameof(type)).GetConstructors(BindingFlags.Instance).Any(c => c.GetParameters().Length == 0); public static bool IsNullableType(this Type type) diff --git a/src/WeihanLi.Common/Extensions/DataExtension.cs b/src/WeihanLi.Common/Extensions/DataExtension.cs index e625cf73..40de1eda 100644 --- a/src/WeihanLi.Common/Extensions/DataExtension.cs +++ b/src/WeihanLi.Common/Extensions/DataExtension.cs @@ -45,7 +45,7 @@ IEnumerator IEnumerable.GetEnumerator() #region DataTable - public static DataTable ToDataTable<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]T>(this IEnumerable entities) + public static DataTable ToDataTable<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] T>(this IEnumerable entities) { if (null == entities) { @@ -212,6 +212,7 @@ public static dynamic ToExpandoObject(this DataRow @this) /// /// The @this to act on. /// @this as a DataTable. + [RequiresUnreferencedCode("Members from types used in the expression column to be trimmed if not referenced directly.")] public static DataTable ToDataTable(this IDataReader @this) { var dt = new DataTable(); diff --git a/src/WeihanLi.Common/Extensions/ExpressionExtension.cs b/src/WeihanLi.Common/Extensions/ExpressionExtension.cs index a31c18d8..096c1b76 100644 --- a/src/WeihanLi.Common/Extensions/ExpressionExtension.cs +++ b/src/WeihanLi.Common/Extensions/ExpressionExtension.cs @@ -154,7 +154,7 @@ public static MemberInfo GetMemberInfo(this Expression /// /// - public static PropertyInfo GetProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]TEntity, TProperty>( + public static PropertyInfo GetProperty<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TEntity, TProperty>( this Expression> expression) { var member = GetMemberInfo(expression); diff --git a/src/WeihanLi.Common/Extensions/ILGeneratorExtensions.cs b/src/WeihanLi.Common/Extensions/ILGeneratorExtensions.cs index 2330af48..e1a74cee 100644 --- a/src/WeihanLi.Common/Extensions/ILGeneratorExtensions.cs +++ b/src/WeihanLi.Common/Extensions/ILGeneratorExtensions.cs @@ -18,7 +18,7 @@ public static void EmitMethod(this ILGenerator ilGenerator, MethodInfo method) { Guard.NotNull(ilGenerator); Guard.NotNull(method); - + EmitMethod(ilGenerator, method, method.DeclaringType!); } @@ -38,7 +38,7 @@ public static void EmitMethod(this ILGenerator ilGenerator, MethodInfo method, T [RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")] [RequiresUnreferencedCode("Unreferenced code may be used")] - public static void EmitConvertToObject(this ILGenerator ilGenerator, + public static void EmitConvertToObject(this ILGenerator ilGenerator, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type typeFrom) { Guard.NotNull(ilGenerator); @@ -56,7 +56,7 @@ public static void EmitConvertToObject(this ILGenerator ilGenerator, [RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")] [RequiresUnreferencedCode("Unreferenced code may be used")] - public static void EmitConvertFromObject(this ILGenerator ilGenerator, + public static void EmitConvertFromObject(this ILGenerator ilGenerator, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)] Type typeTo) { Guard.NotNull(ilGenerator); @@ -71,12 +71,12 @@ public static void EmitConvertFromObject(this ILGenerator ilGenerator, ilGenerator.EmitConvertToType(typeof(object), typeTo); } } - + [RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")] [RequiresUnreferencedCode("Unreferenced code may be used")] public static void EmitConvertToType(this ILGenerator ilGenerator, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]Type typeFrom, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)]Type typeTo, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type typeFrom, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)] Type typeTo, bool isChecked = true) { Guard.NotNull(ilGenerator); @@ -125,9 +125,9 @@ public static void EmitConvertToType(this ILGenerator ilGenerator, [RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")] [RequiresUnreferencedCode("Unreferenced code may be used")] - private static void EmitNullableConversion(this ILGenerator ilGenerator, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]Type typeFrom, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)]Type typeTo, + private static void EmitNullableConversion(this ILGenerator ilGenerator, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type typeFrom, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)] Type typeTo, bool isChecked) { var isTypeFromNullable = typeFrom.IsNullableType(); @@ -142,9 +142,9 @@ private static void EmitNullableConversion(this ILGenerator ilGenerator, [RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")] [RequiresUnreferencedCode("Unreferenced code may be used")] - private static void EmitNullableToNullableConversion(this ILGenerator ilGenerator, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]Type typeFrom, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)]Type typeTo, + private static void EmitNullableToNullableConversion(this ILGenerator ilGenerator, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type typeFrom, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)] Type typeTo, bool isChecked) { var locFrom = ilGenerator.DeclareLocal(typeFrom); @@ -176,8 +176,8 @@ private static void EmitNullableToNullableConversion(this ILGenerator ilGenerato [RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")] [RequiresUnreferencedCode("Unreferenced code may be used")] - private static void EmitNullableToNonNullableConversion(this ILGenerator ilGenerator, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]Type typeFrom, + private static void EmitNullableToNonNullableConversion(this ILGenerator ilGenerator, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type typeFrom, Type typeTo, bool isChecked) { if (typeTo.IsValueType) @@ -188,8 +188,8 @@ private static void EmitNullableToNonNullableConversion(this ILGenerator ilGener [RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")] [RequiresUnreferencedCode("Unreferenced code may be used")] - private static void EmitNullableToNonNullableStructConversion(this ILGenerator ilGenerator, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]Type typeFrom, + private static void EmitNullableToNonNullableStructConversion(this ILGenerator ilGenerator, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type typeFrom, Type typeTo, bool isChecked) { var locFrom = ilGenerator.DeclareLocal(typeFrom); @@ -209,9 +209,9 @@ private static void EmitNullableToReferenceConversion(this ILGenerator ilGenerat [RequiresDynamicCode("Defining a dynamic assembly requires dynamic code.")] [RequiresUnreferencedCode("Unreferenced code may be used")] - private static void EmitNonNullableToNullableConversion(this ILGenerator ilGenerator, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]Type typeFrom, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)]Type typeTo, + private static void EmitNonNullableToNullableConversion(this ILGenerator ilGenerator, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type typeFrom, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.PublicConstructors)] Type typeTo, bool isChecked) { var locTo = ilGenerator.DeclareLocal(typeTo); @@ -389,7 +389,7 @@ private static void EmitNumericConversion(this ILGenerator ilGenerator, Type typ public static void EmitCastToType(this ILGenerator ilGenerator, Type typeFrom, Type typeTo) { Guard.NotNull(ilGenerator); - + if (!typeFrom.IsValueType && typeTo.IsValueType) { ilGenerator.Emit(OpCodes.Unbox_Any, typeTo); @@ -534,21 +534,21 @@ public static void EmitBoolean(this ILGenerator ilGenerator, bool value) } } - public static void EmitHasValue(this ILGenerator ilGenerator, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]Type nullableType) + public static void EmitHasValue(this ILGenerator ilGenerator, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type nullableType) { Guard.NotNull(ilGenerator); - + var mi = nullableType.GetTypeInfo().GetMethod("get_HasValue", BindingFlags.Instance | BindingFlags.Public)!; ilGenerator.Emit(OpCodes.Call, mi); } - public static void EmitGetValueOrDefault(this ILGenerator ilGenerator, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]Type nullableType) + public static void EmitGetValueOrDefault(this ILGenerator ilGenerator, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type nullableType) { var mi = nullableType.GetTypeInfo().GetMethod("GetValueOrDefault", Type.EmptyTypes)!; ilGenerator.Emit(OpCodes.Call, mi); } - public static void EmitGetValue(this ILGenerator ilGenerator, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)]Type nullableType) + public static void EmitGetValue(this ILGenerator ilGenerator, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods)] Type nullableType) { var mi = nullableType.GetTypeInfo().GetMethod("get_Value", BindingFlags.Instance | BindingFlags.Public)!; ilGenerator.Emit(OpCodes.Call, mi); @@ -558,7 +558,7 @@ public static void EmitDefaultValue(this ILGenerator ilGenerator, Type type) { Guard.NotNull(ilGenerator); Guard.NotNull(type); - + switch (Type.GetTypeCode(type)) { case TypeCode.Object: diff --git a/src/WeihanLi.Common/Extensions/ProcessExtension.cs b/src/WeihanLi.Common/Extensions/ProcessExtension.cs index c8a454fa..5f9f1695 100644 --- a/src/WeihanLi.Common/Extensions/ProcessExtension.cs +++ b/src/WeihanLi.Common/Extensions/ProcessExtension.cs @@ -122,22 +122,21 @@ public static async Task GetResultAsync(this ProcessStartInfo psi public static int GetExitCode(this ProcessStartInfo psi, TextWriter? stdOut = null, TextWriter? stdErr = null) { - stdOut ??= Console.Out; - stdErr ??= Console.Error; + psi.RedirectStandardOutput = stdOut != null; + psi.RedirectStandardError = stdErr != null; psi.UseShellExecute = false; - psi.RedirectStandardError = true; - psi.RedirectStandardOutput = true; using var process = new Process { StartInfo = psi }; process.OutputDataReceived += (_, e) => { if (e.Data != null) - stdOut.WriteLine(e.Data); + stdOut?.WriteLine(e.Data); }; process.ErrorDataReceived += (_, e) => { if (e.Data != null) - stdErr.WriteLine(e.Data); + stdErr?.WriteLine(e.Data); }; + try { process.Start(); @@ -165,25 +164,23 @@ public static int GetExitCode(this ProcessStartInfo psi, TextWriter? stdOut = nu public static async Task GetExitCodeAsync(this ProcessStartInfo psi, TextWriter? stdOut = null, TextWriter? stdErr = null, CancellationToken cancellationToken = default) { - stdOut ??= Console.Out; - stdErr ??= Console.Error; + psi.RedirectStandardOutput = stdOut != null; + psi.RedirectStandardError = stdErr != null; psi.UseShellExecute = false; - psi.RedirectStandardError = true; - psi.RedirectStandardOutput = true; using var process = new Process { StartInfo = psi }; var stdOutComplete = new TaskCompletionSource(); var stdErrComplete = new TaskCompletionSource(); process.OutputDataReceived += (_, e) => { if (e.Data != null) - stdOut.WriteLine(e.Data); + stdOut?.WriteLine(e.Data); else stdOutComplete.SetResult(null); }; process.ErrorDataReceived += (_, e) => { if (e.Data != null) - stdErr.WriteLine(e.Data); + stdErr?.WriteLine(e.Data); else stdErrComplete.SetResult(null); }; diff --git a/src/WeihanLi.Common/Extensions/ReflectionExtension.cs b/src/WeihanLi.Common/Extensions/ReflectionExtension.cs index 3e5c97fe..e3fc119e 100644 --- a/src/WeihanLi.Common/Extensions/ReflectionExtension.cs +++ b/src/WeihanLi.Common/Extensions/ReflectionExtension.cs @@ -11,8 +11,8 @@ namespace WeihanLi.Extensions; public static class ReflectionExtension -{ - public static MethodInfo? GetMethodBySignature([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)]this Type type, MethodInfo method) +{ + public static MethodInfo? GetMethodBySignature([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods)] this Type type, MethodInfo method) { Guard.NotNull(type); Guard.NotNull(method); diff --git a/src/WeihanLi.Common/Extensions/ServiceCollectionExtension.cs b/src/WeihanLi.Common/Extensions/ServiceCollectionExtension.cs index 2bb09647..1d521351 100644 --- a/src/WeihanLi.Common/Extensions/ServiceCollectionExtension.cs +++ b/src/WeihanLi.Common/Extensions/ServiceCollectionExtension.cs @@ -182,8 +182,8 @@ public static IServiceCollection RegisterAssemblyTypesAsImplementedInterfaces(th /// type /// service lifetime /// services - public static IServiceCollection RegisterTypeAsImplementedInterfaces(this IServiceCollection services, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicConstructors)]Type type, + public static IServiceCollection RegisterTypeAsImplementedInterfaces(this IServiceCollection services, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) => RegisterTypeAsImplementedInterfaces(services, type, null, serviceLifetime); @@ -195,8 +195,8 @@ public static IServiceCollection RegisterTypeAsImplementedInterfaces(this IServi /// interfaceTypeFilter /// service lifetime /// services - public static IServiceCollection RegisterTypeAsImplementedInterfaces(this IServiceCollection services, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicConstructors)]Type type, + public static IServiceCollection RegisterTypeAsImplementedInterfaces(this IServiceCollection services, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces | DynamicallyAccessedMemberTypes.PublicConstructors)] Type type, Func? interfaceTypeFilter, ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) { Guard.NotNull(type); @@ -266,7 +266,7 @@ public static IServiceCollection RegisterAssemblyModules( /// decorator type /// services /// services - public static IServiceCollection Decorate(this IServiceCollection services) + public static IServiceCollection Decorate(this IServiceCollection services) where TService : class where TDecorator : class, TService { @@ -281,8 +281,8 @@ public static IServiceCollection RegisterAssemblyModules( /// decoratorType /// services /// throw exception when serviceType not registered - public static IServiceCollection Decorate(this IServiceCollection services, Type serviceType, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]Type decoratorType) + public static IServiceCollection Decorate(this IServiceCollection services, Type serviceType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] Type decoratorType) { var service = services.LastOrDefault(x => x.ServiceType == serviceType); if (service == null) diff --git a/src/WeihanLi.Common/Extensions/TypeExtension.cs b/src/WeihanLi.Common/Extensions/TypeExtension.cs index 18df5ff8..68198a6d 100644 --- a/src/WeihanLi.Common/Extensions/TypeExtension.cs +++ b/src/WeihanLi.Common/Extensions/TypeExtension.cs @@ -82,7 +82,8 @@ public static bool IsBasicType(this Type type) /// type /// /// Matching constructor or default one - public static ConstructorInfo? GetConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]this Type type, params Type[]? parameterTypes) + [RequiresUnreferencedCode("Unreferenced code may be used")] + public static ConstructorInfo? GetConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type type, params Type[]? parameterTypes) { if (parameterTypes == null || parameterTypes.Length == 0) return GetEmptyConstructor(type); @@ -91,7 +92,7 @@ public static bool IsBasicType(this Type type) return ctor; } - public static ConstructorInfo? GetEmptyConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]this Type type) + public static ConstructorInfo? GetEmptyConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type type) { var constructors = type.GetConstructors(); @@ -120,7 +121,7 @@ public static bool IsAssignableTo(this Type @this) /// The type being tested. /// The types of the contractor to find. /// The is a match is found; otherwise, null. - public static ConstructorInfo? GetMatchingConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]this Type type, Type[]? constructorParameterTypes) + public static ConstructorInfo? GetMatchingConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] this Type type, Type[]? constructorParameterTypes) { if (constructorParameterTypes == null || constructorParameterTypes.Length == 0) return GetEmptyConstructor(type); @@ -137,7 +138,7 @@ public static bool IsAssignableTo(this Type @this) /// /// type /// - public static IEnumerable GetImplementedInterfaces([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)]this Type type) + public static IEnumerable GetImplementedInterfaces([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.Interfaces)] this Type type) { return type.GetTypeInfo().ImplementedInterfaces; } diff --git a/src/WeihanLi.Common/Helpers/ActivatorHelper.cs b/src/WeihanLi.Common/Helpers/ActivatorHelper.cs index cb5ff17d..3dab28db 100644 --- a/src/WeihanLi.Common/Helpers/ActivatorHelper.cs +++ b/src/WeihanLi.Common/Helpers/ActivatorHelper.cs @@ -13,7 +13,7 @@ namespace WeihanLi.Common.Helpers; internal static class ParameterDefaultValue { private static readonly Type NullableOpenGenericType = typeof(Nullable<>); - + [RequiresUnreferencedCode("Unreferenced code may be used")] public static bool TryGetDefaultValue(ParameterInfo parameter, out object? defaultValue) { @@ -79,7 +79,7 @@ public static class ActivatorHelper /// type /// parameters /// T instance - public static T CreateInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]T>(params object?[] parameters) + public static T CreateInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] T>(params object?[] parameters) { return (T)(Activator.CreateInstance(typeof(T), parameters) ?? throw new InvalidOperationException()); } @@ -92,8 +92,8 @@ public static class ActivatorHelper /// Constructor arguments not provided by the . /// An activated object of type instanceType [RequiresUnreferencedCode("Unreferenced code may be used")] - public static object CreateInstance(this IServiceProvider provider, - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]Type instanceType, + public static object CreateInstance(this IServiceProvider provider, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type instanceType, params object?[] parameters) { return MatchConstructor(instanceType, parameters).CreateInstance(provider); @@ -151,13 +151,13 @@ private static ConstructorMatcher MatchConstructor( /// Constructor arguments not provided by di sys /// Best Constructor Matched [RequiresUnreferencedCode("Unreferenced code may be used")] - public static ConstructorInfo MatchBestConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]Type instanceType, params object[] parameters) + public static ConstructorInfo MatchBestConstructor([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type instanceType, params object[] parameters) { return MatchConstructor(instanceType, parameters).Constructor; } [RequiresUnreferencedCode("Unreferenced code may be used")] - public static object?[] GetBestConstructorArguments(IServiceProvider serviceProvider, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]Type instanceType, params object?[] parameters) + public static object?[] GetBestConstructorArguments(IServiceProvider serviceProvider, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type instanceType, params object?[] parameters) { return MatchConstructor(instanceType, parameters).GetConstructorArguments(serviceProvider); } @@ -198,7 +198,7 @@ public static ObjectFactory CreateFactory(Type instanceType, Type[] argumentType /// Constructor arguments not provided by the . /// An activated object of type T [RequiresUnreferencedCode("Unreferenced code may be used")] - public static T CreateInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]T>(this IServiceProvider provider, params object[] parameters) + public static T CreateInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>(this IServiceProvider provider, params object[] parameters) { return (T)CreateInstance(provider, typeof(T), parameters); } @@ -210,7 +210,7 @@ public static ObjectFactory CreateFactory(Type instanceType, Type[] argumentType /// The service provider used to resolve dependencies /// The resolved service or created instance [RequiresUnreferencedCode("Unreferenced code may be used")] - public static T GetServiceOrCreateInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]T>(this IServiceProvider provider) + public static T GetServiceOrCreateInstance<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] T>(this IServiceProvider provider) { return (T)GetServiceOrCreateInstance(provider, typeof(T)); } @@ -222,7 +222,7 @@ public static ObjectFactory CreateFactory(Type instanceType, Type[] argumentType /// The type of the service /// The resolved service or created instance [RequiresUnreferencedCode("Unreferenced code may be used")] - public static object GetServiceOrCreateInstance(this IServiceProvider provider, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]Type type) + public static object GetServiceOrCreateInstance(this IServiceProvider provider, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type type) { return provider.GetService(type) ?? CreateInstance(provider, type); } @@ -307,7 +307,7 @@ public static void FindApplicableConstructor( // Tries to find constructor based on provided argument types [RequiresUnreferencedCode("Unreferenced code may be used")] private static bool TryFindMatchingConstructor( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)]Type instanceType, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type instanceType, Type[] argumentTypes, ref ConstructorInfo? matchingConstructor, ref int?[]? parameterMap) diff --git a/src/WeihanLi.Common/Helpers/ApplicationHelper.cs b/src/WeihanLi.Common/Helpers/ApplicationHelper.cs index bf3fab5e..587cf52c 100644 --- a/src/WeihanLi.Common/Helpers/ApplicationHelper.cs +++ b/src/WeihanLi.Common/Helpers/ApplicationHelper.cs @@ -94,7 +94,7 @@ public static string GetDotnetDirectory() public static string? ResolvePath(string execName) { var executableName = execName; - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !Path.HasExtension(execName)) { executableName += ".exe"; diff --git a/src/WeihanLi.Common/Helpers/BoundedConcurrentQueue.cs b/src/WeihanLi.Common/Helpers/BoundedConcurrentQueue.cs index b0988855..1eff5ab9 100644 --- a/src/WeihanLi.Common/Helpers/BoundedConcurrentQueue.cs +++ b/src/WeihanLi.Common/Helpers/BoundedConcurrentQueue.cs @@ -28,13 +28,13 @@ public BoundedConcurrentQueue(int queueLimit, BoundedQueueFullMode mode = Bounde public int Count => _queue.Count; - public bool TryDequeue([MaybeNullWhen(false)]out T item) + public bool TryDequeue([MaybeNullWhen(false)] out T item) { if (_queueLimit == NonBounded) return _queue.TryDequeue(out item); var result = false; - + if (_queue.TryDequeue(out item)) { result = true; @@ -53,7 +53,7 @@ public bool TryEnqueue(T item) } var result = true; - + if (Interlocked.Increment(ref _counter) <= _queueLimit) { _queue.Enqueue(item); @@ -72,7 +72,7 @@ public bool TryEnqueue(T item) else { Interlocked.Decrement(ref _counter); - result = false; + result = false; } } diff --git a/src/WeihanLi.Common/Helpers/CommandExecutor.cs b/src/WeihanLi.Common/Helpers/CommandExecutor.cs index d851b5b0..d23340a7 100644 --- a/src/WeihanLi.Common/Helpers/CommandExecutor.cs +++ b/src/WeihanLi.Common/Helpers/CommandExecutor.cs @@ -8,6 +8,8 @@ namespace WeihanLi.Common.Helpers; public static class CommandExecutor { + private static readonly char[] SpaceSeparator = [' ']; + /// /// Execute command /// @@ -18,24 +20,42 @@ public static class CommandExecutor public static int ExecuteCommand(string command, string? workingDirectory = null, Action? configure = null) { Guard.NotNullOrEmpty(command); - var cmd = command.Split(new[] { ' ' }, 2); + var cmd = command.Split(SpaceSeparator, 2); return Execute(cmd[0], cmd.Length > 1 ? cmd[1] : null, workingDirectory, configure); } - + /// /// Execute command and capture output /// /// command with arguments /// working directory for the command /// configure the ProcessStartInfo - /// exit code + /// command execute result public static CommandResult ExecuteCommandAndCapture(string command, string? workingDirectory = null, Action? configure = null) { Guard.NotNullOrEmpty(command); - var cmd = command.Split([' '], 2); + var cmd = command.Split(SpaceSeparator, 2); return ExecuteAndCapture(cmd[0], cmd.Length > 1 ? cmd[1] : null, workingDirectory, configure); } + /// + /// Execute command with a process + /// + /// executable command and argument + /// stdout writer, write to console by default + /// stderr writer, write to console by default + /// working directory + /// configure the ProcessStartInfo + /// exit code + public static int ExecuteCommandAndOutput(string command, + TextWriter? stdout = null, TextWriter? stderr = null, + string? workingDirectory = null, Action? configure = null) + { + Guard.NotNullOrEmpty(command); + var cmd = command.Split(SpaceSeparator, 2); + return ExecuteAndOutput(cmd[0], cmd.Length > 1 ? cmd[1] : null, stdout, stderr, workingDirectory, configure); + } + /// /// Execute command async /// @@ -47,10 +67,10 @@ public static CommandResult ExecuteCommandAndCapture(string command, string? wor public static Task ExecuteCommandAsync(string command, string? workingDirectory = null, Action? configure = null, CancellationToken cancellationToken = default) { Guard.NotNullOrEmpty(command); - var cmd = command.Split([' '], 2); + var cmd = command.Split(SpaceSeparator, 2); return ExecuteAsync(cmd[0], cmd.Length > 1 ? cmd[1] : null, workingDirectory, configure, cancellationToken); } - + /// /// Execute command and capture output async /// @@ -58,14 +78,34 @@ public static Task ExecuteCommandAsync(string command, string? workingDirec /// working directory for the command /// configure the ProcessStartInfo /// cancellationToken - /// exit code + /// command execute result public static Task ExecuteCommandAndCaptureAsync(string command, string? workingDirectory = null, Action? configure = null, CancellationToken cancellationToken = default) { Guard.NotNullOrEmpty(command); - var cmd = command.Split(new[] { ' ' }, 2); + var cmd = command.Split(SpaceSeparator, 2); return ExecuteAndCaptureAsync(cmd[0], cmd.Length > 1 ? cmd[1] : null, workingDirectory, configure, cancellationToken); } + /// + /// Execute command with a process + /// + /// executable command and argument + /// command arguments + /// stdout writer, write to console by default + /// stderr writer, write to console by default + /// working directory + /// configure the ProcessStartInfo + /// cancellationToken + /// exit code + public static Task ExecuteCommandAndOutputAsync(string command, string? arguments = null, + TextWriter? stdout = null, TextWriter? stderr = null, + string? workingDirectory = null, Action? configure = null, CancellationToken cancellationToken = default) + { + Guard.NotNullOrEmpty(command); + var cmd = command.Split(SpaceSeparator, 2); + return ExecuteAndOutputAsync(cmd[0], cmd.Length > 1 ? cmd[1] : null, stdout, stderr, workingDirectory, configure, cancellationToken); + } + /// /// Execute command with a process /// @@ -87,7 +127,7 @@ public static int Execute(string commandPath, string? arguments = null, string? } /// - /// Execute command with a process + /// Execute command with a process async /// /// executable command path /// command arguments @@ -107,6 +147,55 @@ public static async Task ExecuteAsync(string commandPath, string? arguments return await processStartInfo.ExecuteAsync(cancellationToken); } + /// + /// Execute command with a process + /// + /// executable command path + /// command arguments + /// stdout writer, write to console by default + /// stderr writer, write to console by default + /// working directory + /// configure the ProcessStartInfo + /// exit code + public static int ExecuteAndOutput(string commandPath, string? arguments = null, + TextWriter? stdout = null, TextWriter? stderr = null, + string? workingDirectory = null, Action? configure = null) + { + var processStartInfo = new ProcessStartInfo(commandPath, arguments ?? string.Empty) + { + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory + }; + configure?.Invoke(processStartInfo); + return processStartInfo.GetExitCode(stdout ?? Console.Out, stderr ?? Console.Error); + } + + /// + /// Execute command with a process + /// + /// executable command path + /// command arguments + /// stdout writer, write to console by default + /// stderr writer, write to console by default + /// working directory + /// configure the ProcessStartInfo + /// cancellationToken + /// exit code + public static async Task ExecuteAndOutputAsync(string commandPath, string? arguments = null, + TextWriter? stdout = null, TextWriter? stderr = null, + string? workingDirectory = null, Action? configure = null, CancellationToken cancellationToken = default) + { + var processStartInfo = new ProcessStartInfo(commandPath, arguments ?? string.Empty) + { + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = workingDirectory ?? Environment.CurrentDirectory + }; + configure?.Invoke(processStartInfo); + return await processStartInfo.GetExitCodeAsync(stdout ?? Console.Out, stderr ?? Console.Error, cancellationToken); + } + /// /// Execute command with a process and capture the output /// @@ -170,28 +259,18 @@ public static async Task ExecuteAndCaptureAsync(this ProcessStart } } -public sealed class CommandResult +public sealed class CommandResult(int exitCode, string standardOut, string standardError) { - public CommandResult(int exitCode, string standardOut, string standardError) - { - ExitCode = exitCode; - StandardOut = standardOut; - StandardError = standardError; - } - - public string StandardOut { get; } - public string StandardError { get; } - public int ExitCode { get; } + public string StandardOut { get; } = standardOut; + public string StandardError { get; } = standardError; + public int ExitCode { get; } = exitCode; - [Obsolete("Please use EnsureSuccessExitCode() instead")] + [Obsolete("Please use EnsureSuccessExitCode() instead", true)] public CommandResult EnsureSuccessfulExitCode(int successCode = 0) => EnsureSuccessExitCode(successCode); - + public CommandResult EnsureSuccessExitCode(int successCode = 0) { - if (ExitCode != successCode) - { - throw new InvalidOperationException($"Unexpected exit code:{ExitCode} {StandardError}"); - } + ExitCode.EnsureSuccessExitCode(successCode); return this; } } diff --git a/src/WeihanLi.Common/Helpers/ConsoleHelper.cs b/src/WeihanLi.Common/Helpers/ConsoleHelper.cs index c1870645..5800715c 100644 --- a/src/WeihanLi.Common/Helpers/ConsoleHelper.cs +++ b/src/WeihanLi.Common/Helpers/ConsoleHelper.cs @@ -73,6 +73,20 @@ public static void WriteLineWithColor(string? output, ConsoleColor? foregroundCo InvokeWithConsoleColor(() => Console.WriteLine(output), foregroundColor, backgroundColor); } + public static void ErrorWriteWithColor(string? output, ConsoleColor? foregroundColor, ConsoleColor? backgroundColor = null) + { + if (!string.IsNullOrEmpty(output)) + InvokeWithConsoleColor(() => Console.Error.Write(output), foregroundColor, backgroundColor); + } + + public static void ErrorWriteLineWithColor(string error, ConsoleColor? foregroundColor, ConsoleColor? backgroundColor = null) + { + if (string.IsNullOrEmpty(error)) + Console.Error.WriteLine(); + else + InvokeWithConsoleColor(() => Console.Error.WriteLine(error), foregroundColor, backgroundColor); + } + public static string? GetInput(string? inputPrompt = null, bool insertNewLine = true) { var input = ReadLineWithPrompt(inputPrompt); @@ -163,13 +177,27 @@ public static void WriteIf(string? output, bool condition) if (condition) Console.Write(output); } + public static void ErrorWriteLineIf(string? output, bool condition) + { + if (condition) Console.Error.WriteLine(output); + } + + public static void ErrorWriteIf(string? output, bool condition) + { + if (condition) Console.Error.Write(output); + } + public static CommandResult PrintOutputToConsole(this CommandResult commandResult) { Guard.NotNull(commandResult); - + Console.WriteLine(commandResult.StandardOut); + if (!string.IsNullOrEmpty(commandResult.StandardError)) - WriteLineWithColor(commandResult.StandardError, ConsoleColor.DarkRed); + { + Console.WriteLine(); + ErrorWriteLineWithColor(commandResult.StandardError, ConsoleColor.DarkRed); + } return commandResult; } diff --git a/src/WeihanLi.Common/Helpers/DataSerializer.cs b/src/WeihanLi.Common/Helpers/DataSerializer.cs index 0b6be0b3..8db1519f 100644 --- a/src/WeihanLi.Common/Helpers/DataSerializer.cs +++ b/src/WeihanLi.Common/Helpers/DataSerializer.cs @@ -29,7 +29,7 @@ public interface IDataSerializer public class XmlDataSerializer : IDataSerializer { internal static readonly Lazy Instance = new(); - + [RequiresUnreferencedCode("Members from serialized types may be trimmed if not referenced directly")] public virtual byte[] Serialize(T obj) { @@ -46,7 +46,7 @@ public virtual byte[] Serialize(T obj) serializer.Serialize(ms, obj); return ms.ToArray(); } - + [RequiresUnreferencedCode("If some of the generic arguments are annotated (either with DynamicallyAccessedMembersAttribute, or generic constraints), trimming can't validate that the requirements of those annotations are met.")] public virtual T Deserialize(byte[] bytes) { @@ -69,11 +69,11 @@ public virtual byte[] Serialize(T obj) { if (typeof(Task).IsAssignableFrom(typeof(T))) { - throw new ArgumentException(Resource.TaskCanNotBeSerialized); + throw new ArgumentException(Resource.TaskCanNotBeSerialized); } return obj.ToJson().GetBytes(); } - + [RequiresUnreferencedCode("Members from serialized types may be trimmed if not referenced directly")] public virtual T Deserialize(byte[] bytes) { diff --git a/src/WeihanLi.Common/Helpers/DiagnosticHelper.cs b/src/WeihanLi.Common/Helpers/DiagnosticHelper.cs index c1165ec9..bce2d685 100644 --- a/src/WeihanLi.Common/Helpers/DiagnosticHelper.cs +++ b/src/WeihanLi.Common/Helpers/DiagnosticHelper.cs @@ -9,12 +9,12 @@ namespace WeihanLi.Common.Helpers; public static class DiagnosticHelper { private const string DiagnosticSourceName = "WeihanLi.Common"; - + [CLSCompliant(false)] public static readonly ActivitySource ActivitySource; [CLSCompliant(false)] public static readonly Meter Meter; - + static DiagnosticHelper() { var version = typeof(DiagnosticHelper).Assembly.GetName().Version?.ToString(3); diff --git a/src/WeihanLi.Common/Helpers/DisposableHelper.cs b/src/WeihanLi.Common/Helpers/DisposableHelper.cs index d943d8ae..37053b38 100644 --- a/src/WeihanLi.Common/Helpers/DisposableHelper.cs +++ b/src/WeihanLi.Common/Helpers/DisposableHelper.cs @@ -13,13 +13,13 @@ public void Dispose() { } - public ValueTask DisposeAsync() => + public ValueTask DisposeAsync() => #if NET6_0_OR_GREATER ValueTask.CompletedTask #else default #endif - ; + ; /// /// Gets the instance of . @@ -27,32 +27,27 @@ public ValueTask DisposeAsync() => public static NullDisposable Instance { get; } = new(); } -public sealed class DisposableAction : IDisposable, IAsyncDisposable +/// +/// Delegate-based Disposable implement +/// +/// dispose delegate +public sealed class DisposableAction(Action? disposeAction) : IDisposable, IAsyncDisposable { - public static readonly DisposableAction Empty = new(null); - - private Action? _disposeAction; - - public DisposableAction(Action? disposeAction) - { - _disposeAction = disposeAction; - } - public void Dispose() { - Interlocked.Exchange(ref _disposeAction, null)?.Invoke(); + Interlocked.Exchange(ref disposeAction, null)?.Invoke(); } public ValueTask DisposeAsync() { Dispose(); - return + return #if NET6_0_OR_GREATER ValueTask.CompletedTask #else default #endif - ; + ; } } @@ -64,11 +59,17 @@ public ValueTask DisposeAsync() /// public abstract class DisposableBase : IDisposable, IAsyncDisposable { + /// + /// Set to true when the inherits place dispose logics in Dispose method other than DisposeAsync + /// + protected virtual bool UseDispose { get; } + // To detect redundant calls - private bool _disposed; + private int _disposedStatus; + public void Dispose() { - if (_disposed) return; + if (Interlocked.CompareExchange(ref _disposedStatus, 1, 0) != 0) return; Dispose(disposing: true); GC.SuppressFinalize(this); @@ -76,11 +77,21 @@ public void Dispose() public async ValueTask DisposeAsync() { - if (_disposed) return; + if (Interlocked.CompareExchange(ref _disposedStatus, 1, 0) != 0) return; + // Perform async cleanup. await DisposeAsyncCore().ConfigureAwait(false); - // Dispose of unmanaged resources. - Dispose(disposing: false); + + if (UseDispose) + { + Dispose(); + } + else + { + // Dispose of unmanaged resources. + Dispose(disposing: false); + } + // Suppress finalization. GC.SuppressFinalize(this); } @@ -94,19 +105,18 @@ protected virtual void Dispose(bool disposing) // free unmanaged resources (unmanaged objects) and override finalizer // set large fields to null - _disposed = true; } protected virtual ValueTask DisposeAsyncCore() { // dispose managed state in async way (managed objects) - return + return #if NET6_0_OR_GREATER ValueTask.CompletedTask #else default #endif - ; + ; } ~DisposableBase() => Dispose(false); diff --git a/src/WeihanLi.Common/Helpers/EnumHelper.cs b/src/WeihanLi.Common/Helpers/EnumHelper.cs index 4dd95e31..406589d1 100644 --- a/src/WeihanLi.Common/Helpers/EnumHelper.cs +++ b/src/WeihanLi.Common/Helpers/EnumHelper.cs @@ -15,8 +15,8 @@ public static IReadOnlyList ToIdNameList() where TEnum : Enu Id = Convert.ToInt32(Enum.Parse(enumType, name)) }); } - - public static IReadOnlyList> ToIdNameList<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]TEnum, TValue>() where TEnum : Enum + + public static IReadOnlyList> ToIdNameList<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum, TValue>() where TEnum : Enum { var enumType = typeof(TEnum); return Array.ConvertAll(Enum.GetNames(enumType), name => new IdNameModel() @@ -25,8 +25,8 @@ public static IReadOnlyList ToIdNameList() where TEnum : Enu Name = name, }); } - - public static IReadOnlyList ToIdNameDescList<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]TEnum>() where TEnum : Enum + + public static IReadOnlyList ToIdNameDescList<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum>() where TEnum : Enum { var enumType = typeof(TEnum); return Array.ConvertAll(Enum.GetNames(enumType), converter: name => new IdNameDescModel() @@ -36,8 +36,8 @@ public static IReadOnlyList ToIdNameList() where TEnum : Enu Description = enumType.GetField(name)?.GetDescription() }); } - - public static IReadOnlyList> ToIdNameDescList<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)]TEnum, TValue>() where TEnum : Enum + + public static IReadOnlyList> ToIdNameDescList<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] TEnum, TValue>() where TEnum : Enum { var enumType = typeof(TEnum); return Array.ConvertAll(Enum.GetNames(enumType), converter: name => new IdNameDescModel() diff --git a/src/WeihanLi.Common/Helpers/Hosting/AppHostBuilderExtensions.cs b/src/WeihanLi.Common/Helpers/Hosting/AppHostBuilderExtensions.cs index 0f504b66..f14e1b5a 100644 --- a/src/WeihanLi.Common/Helpers/Hosting/AppHostBuilderExtensions.cs +++ b/src/WeihanLi.Common/Helpers/Hosting/AppHostBuilderExtensions.cs @@ -10,7 +10,7 @@ namespace WeihanLi.Common.Helpers.Hosting; public static class AppHostBuilderExtensions { - public static IAppHostBuilder AddHostedService<[DynamicallyAccessedMembers((DynamicallyAccessedMemberTypes.PublicConstructors))]TService>(this IAppHostBuilder appHostBuilder) + public static IAppHostBuilder AddHostedService<[DynamicallyAccessedMembers((DynamicallyAccessedMemberTypes.PublicConstructors))] TService>(this IAppHostBuilder appHostBuilder) where TService : class, IHostedService { Guard.NotNull(appHostBuilder); diff --git a/src/WeihanLi.Common/Helpers/MapHelper.cs b/src/WeihanLi.Common/Helpers/MapHelper.cs index 761c1ad3..aa4a6962 100644 --- a/src/WeihanLi.Common/Helpers/MapHelper.cs +++ b/src/WeihanLi.Common/Helpers/MapHelper.cs @@ -5,7 +5,7 @@ namespace WeihanLi.Common.Helpers; public static class MapHelper { - public static TTarget Map<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]TSource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]TTarget>(TSource source) where TTarget : new() + public static TTarget Map<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TSource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TTarget>(TSource source) where TTarget : new() { if (source is null) { @@ -41,7 +41,7 @@ public static class MapHelper return result; } - public static TTarget MapWith<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]TSource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]TTarget>(TSource source, params string[] propertiesToMap) where TTarget : new() + public static TTarget MapWith<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TSource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TTarget>(TSource source, params string[] propertiesToMap) where TTarget : new() { if (source is null) { @@ -78,7 +78,7 @@ public static class MapHelper return result; } - public static TTarget MapWithout<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]TSource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)]TTarget>(TSource source, params string[] propertiesNoMap) where TTarget : new() + public static TTarget MapWithout<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TSource, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicProperties)] TTarget>(TSource source, params string[] propertiesNoMap) where TTarget : new() { if (source is null) { diff --git a/src/WeihanLi.Common/Helpers/PipelineBuilder.cs b/src/WeihanLi.Common/Helpers/PipelineBuilder.cs index dd62f3ac..60cc8b90 100644 --- a/src/WeihanLi.Common/Helpers/PipelineBuilder.cs +++ b/src/WeihanLi.Common/Helpers/PipelineBuilder.cs @@ -27,7 +27,7 @@ public static IAsyncPipelineBuilder CreateAsync(Func CreateValueAsync() { - return new ValueAsyncPipelineBuilder(c => + return new ValueAsyncPipelineBuilder(c => #if NET6_0_OR_GREATER ValueTask.CompletedTask #else diff --git a/src/WeihanLi.Common/Helpers/Pipelines/PipelineBuilderExtensions.cs b/src/WeihanLi.Common/Helpers/Pipelines/PipelineBuilderExtensions.cs index 680246e5..bf7be0fc 100644 --- a/src/WeihanLi.Common/Helpers/Pipelines/PipelineBuilderExtensions.cs +++ b/src/WeihanLi.Common/Helpers/Pipelines/PipelineBuilderExtensions.cs @@ -191,7 +191,7 @@ public static IValueAsyncPipelineBuilder Use(this IValueAsyn return builder.Use(next => context => func(context, next)); } - + public static IValueAsyncPipelineBuilder UseMiddleware(this IValueAsyncPipelineBuilder builder, IValueAsyncPipelineMiddleware middleware) { Guard.NotNull(middleware); @@ -205,7 +205,7 @@ public static IValueAsyncPipelineBuilder UseMiddleware(this [RequiresUnreferencedCode("Unreferenced code may be used")] public static IValueAsyncPipelineBuilder UseMiddleware(this IValueAsyncPipelineBuilder builder) - where TMiddleware : class, IValueAsyncPipelineMiddleware + where TMiddleware : class, IValueAsyncPipelineMiddleware { return builder.UseMiddleware(DependencyResolver.Current.GetServiceOrCreateInstance()); } diff --git a/src/WeihanLi.Common/Helpers/ReflectHelper.cs b/src/WeihanLi.Common/Helpers/ReflectHelper.cs index 2487e4a9..4e0c0bf6 100644 --- a/src/WeihanLi.Common/Helpers/ReflectHelper.cs +++ b/src/WeihanLi.Common/Helpers/ReflectHelper.cs @@ -51,7 +51,7 @@ public AwaitableInfo( } [RequiresUnreferencedCode("Members from serialized types may be trimmed if not referenced directly")] - public static bool IsTypeAwaitable([DynamicallyAccessedMembers((DynamicallyAccessedMemberTypes.PublicMethods))]Type type, out AwaitableInfo? awaitableInfo) + public static bool IsTypeAwaitable([DynamicallyAccessedMembers((DynamicallyAccessedMemberTypes.PublicMethods))] Type type, out AwaitableInfo? awaitableInfo) { // Based on Roslyn code: http://source.roslyn.io/#Microsoft.CodeAnalysis.Workspaces/Shared/Extensions/ISymbolExtensions.cs,db4d48ba694b9347 diff --git a/src/WeihanLi.Common/Helpers/TaskHelper.cs b/src/WeihanLi.Common/Helpers/TaskHelper.cs index ed2bbddc..2ae2893b 100644 --- a/src/WeihanLi.Common/Helpers/TaskHelper.cs +++ b/src/WeihanLi.Common/Helpers/TaskHelper.cs @@ -4,14 +4,14 @@ namespace WeihanLi.Common.Helpers; public static class TaskHelper -{ +{ public static ValueTask ToTask(object? obj) { - var task = obj switch + var task = obj switch { ValueTask vt => vt, Task t => new ValueTask(t), - _ => + _ => #if NET6_0_OR_GREATER ValueTask.CompletedTask #else diff --git a/src/WeihanLi.Common/Http/MockHttpHandler.cs b/src/WeihanLi.Common/Http/MockHttpHandler.cs index ffb573ec..3ae744e8 100644 --- a/src/WeihanLi.Common/Http/MockHttpHandler.cs +++ b/src/WeihanLi.Common/Http/MockHttpHandler.cs @@ -42,7 +42,7 @@ protected override Task SendAsync(HttpRequestMessage reques { return _responseFactory(request); } - + public HttpClient GetHttpClient() => new(this) { BaseAddress = new Uri("http://localhost/") diff --git a/src/WeihanLi.Common/Logging/LogHelperExtensions.cs b/src/WeihanLi.Common/Logging/LogHelperExtensions.cs index 7514f3ba..647a76f5 100644 --- a/src/WeihanLi.Common/Logging/LogHelperExtensions.cs +++ b/src/WeihanLi.Common/Logging/LogHelperExtensions.cs @@ -165,7 +165,7 @@ public static ILogHelperLoggingBuilder WithProvider(this ILogHelperLoggingBuilde return loggingBuilder; } - public static ILogHelperLoggingBuilder WithProvider<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TLogProvider>(this ILogHelperLoggingBuilder loggingBuilder, params object[] ctorParams) where TLogProvider : ILogHelperProvider + public static ILogHelperLoggingBuilder WithProvider<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TLogProvider>(this ILogHelperLoggingBuilder loggingBuilder, params object[] ctorParams) where TLogProvider : ILogHelperProvider { Guard.NotNull(loggingBuilder, nameof(loggingBuilder)); @@ -173,7 +173,7 @@ public static ILogHelperLoggingBuilder WithProvider(this ILogHelperLoggingBuilde return loggingBuilder; } - public static ILogHelperLoggingBuilder WithEnricher<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TEnricher>(this ILogHelperLoggingBuilder loggingBuilder, + public static ILogHelperLoggingBuilder WithEnricher<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TEnricher>(this ILogHelperLoggingBuilder loggingBuilder, TEnricher enricher) where TEnricher : ILogHelperLoggingEnricher { Guard.NotNull(loggingBuilder, nameof(loggingBuilder)); @@ -190,7 +190,7 @@ public static ILogHelperLoggingBuilder WithProvider(this ILogHelperLoggingBuilde return loggingBuilder; } - public static ILogHelperLoggingBuilder WithEnricher<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)]TEnricher>(this ILogHelperLoggingBuilder loggingBuilder, params object[] ctorParams) where TEnricher : ILogHelperLoggingEnricher + public static ILogHelperLoggingBuilder WithEnricher<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] TEnricher>(this ILogHelperLoggingBuilder loggingBuilder, params object[] ctorParams) where TEnricher : ILogHelperLoggingEnricher { loggingBuilder.AddEnricher(ActivatorHelper.CreateInstance(ctorParams)); return loggingBuilder; diff --git a/src/WeihanLi.Common/Templating/ConfigurationRenderMiddleare.cs b/src/WeihanLi.Common/Templating/ConfigurationRenderMiddleare.cs new file mode 100644 index 00000000..06a2f3e4 --- /dev/null +++ b/src/WeihanLi.Common/Templating/ConfigurationRenderMiddleare.cs @@ -0,0 +1,23 @@ +// Copyright (c) Weihan Li. All rights reserved. +// Licensed under the Apache license. + +using Microsoft.Extensions.Configuration; + +namespace WeihanLi.Common.Templating; + +internal sealed class ConfigurationRenderMiddleware(IConfiguration? configuration = null) : IRenderMiddleware +{ + private const string Prefix = "$config "; + public Task InvokeAsync(TemplateRenderContext context, Func next) + { + if (configuration != null) + { + foreach (var variable in context.Variables.Where(x => x.StartsWith(Prefix) && !context.Parameters.ContainsKey(x))) + { + context.Parameters[variable] = configuration[variable[Prefix.Length..]]; + } + } + + return next(context); + } +} diff --git a/src/WeihanLi.Common/Templating/DefaultRenderMiddleware.cs b/src/WeihanLi.Common/Templating/DefaultRenderMiddleware.cs new file mode 100644 index 00000000..2d1956d2 --- /dev/null +++ b/src/WeihanLi.Common/Templating/DefaultRenderMiddleware.cs @@ -0,0 +1,23 @@ +// Copyright (c) Weihan Li. All rights reserved. +// Licensed under the Apache license. + +using WeihanLi.Extensions; + +namespace WeihanLi.Common.Templating; + +internal sealed class DefaultRenderMiddleware : IRenderMiddleware +{ + public async Task InvokeAsync(TemplateRenderContext context, Func next) + { + if (context.Text.IsNullOrWhiteSpace()) return; + + await next(context); + + context.RenderedText = context.Text; + foreach (var parameter in context.Parameters) + { + context.RenderedText = + context.RenderedText.Replace($"{{{{{parameter.Key}}}}}", parameter.Value?.ToString()); + } + } +} diff --git a/src/WeihanLi.Common/Templating/DefaultTemplateParser.cs b/src/WeihanLi.Common/Templating/DefaultTemplateParser.cs index 55d593ae..12c66b3c 100644 --- a/src/WeihanLi.Common/Templating/DefaultTemplateParser.cs +++ b/src/WeihanLi.Common/Templating/DefaultTemplateParser.cs @@ -5,9 +5,9 @@ namespace WeihanLi.Common.Templating; -public sealed class DefaultTemplateParser : ITemplateParser +internal sealed class DefaultTemplateParser : ITemplateParser { - private const string VariableRegexExp = @"\{\{\s*(?.+)\s*\}\}"; + private const string VariableRegexExp = @"\{\{(?.+)\}\}"; private static readonly Regex VariableRegex = new(VariableRegexExp, RegexOptions.Compiled); public Task ParseAsync(string text) { diff --git a/src/WeihanLi.Common/Templating/DefaultTemplateRenderer.cs b/src/WeihanLi.Common/Templating/DefaultTemplateRenderer.cs index 4a0cf661..4872d9a4 100644 --- a/src/WeihanLi.Common/Templating/DefaultTemplateRenderer.cs +++ b/src/WeihanLi.Common/Templating/DefaultTemplateRenderer.cs @@ -5,19 +5,12 @@ namespace WeihanLi.Common.Templating; -public sealed class DefaultTemplateRenderer : ITemplateRenderer +internal sealed class DefaultTemplateRenderer(Func renderFunc) : ITemplateRenderer { - private readonly Func _renderFunc; - - public DefaultTemplateRenderer(Func renderFunc) - { - _renderFunc = renderFunc; - } - - public async Task RenderAsync(TemplateRenderContext context, object globals) + public async Task RenderAsync(TemplateRenderContext context, object? globals) { context.Parameters = globals.ParseParamDictionary(); - await _renderFunc.Invoke(context); + await renderFunc.Invoke(context).ConfigureAwait(false); return context.RenderedText; } } diff --git a/src/WeihanLi.Common/Templating/DependencyInjectionExtensions.cs b/src/WeihanLi.Common/Templating/DependencyInjectionExtensions.cs new file mode 100644 index 00000000..4dd952a7 --- /dev/null +++ b/src/WeihanLi.Common/Templating/DependencyInjectionExtensions.cs @@ -0,0 +1,48 @@ +// Copyright (c) Weihan Li. All rights reserved. +// Licensed under the Apache license. + +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; +using WeihanLi.Common.Helpers; + +namespace WeihanLi.Common.Templating; + +public static class DependencyInjectionExtensions +{ + public static IServiceCollection AddTemplating(this IServiceCollection services, Action? optionsConfigure = null) + { + Guard.NotNull(services); + if (services.Any(x => x.ServiceType == typeof(ITemplateEngine))) + throw new InvalidOperationException("Templating services had been registered"); + + if (optionsConfigure != null) + services.AddOptions().Configure(optionsConfigure); + + services.TryAddSingleton(); + services.TryAddSingleton(); + services.TryAddSingleton(); + + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.TryAddEnumerable(ServiceDescriptor.Singleton()); + services.AddSingleton(sp => + { + var configuration = sp.GetService>()?.Value.Configuration + ?? sp.GetService(); + return new ConfigurationRenderMiddleware(configuration); + }); + + services.TryAddSingleton(sp => + { + var pipelineBuilder = PipelineBuilder.CreateAsync(); + foreach (var middleware in sp.GetServices()) + { + pipelineBuilder.UseMiddleware(middleware); + } + return pipelineBuilder.Build(); + }); + + return services; + } +} diff --git a/src/WeihanLi.Common/Templating/EnvRenderMiddleware.cs b/src/WeihanLi.Common/Templating/EnvRenderMiddleware.cs new file mode 100644 index 00000000..9a70e05d --- /dev/null +++ b/src/WeihanLi.Common/Templating/EnvRenderMiddleware.cs @@ -0,0 +1,17 @@ +// Copyright (c) Weihan Li. All rights reserved. +// Licensed under the Apache license. + +namespace WeihanLi.Common.Templating; + +internal sealed class EnvRenderMiddleware : IRenderMiddleware +{ + private const string Prefix = "$env "; + public Task InvokeAsync(TemplateRenderContext context, Func next) + { + foreach (var variable in context.Variables.Where(x => x.StartsWith(Prefix) && !context.Parameters.ContainsKey(x))) + { + context.Parameters[variable] = Environment.GetEnvironmentVariable(variable[Prefix.Length..]); + } + return next(context); + } +} diff --git a/src/WeihanLi.Common/Templating/IRenderMiddleware.cs b/src/WeihanLi.Common/Templating/IRenderMiddleware.cs new file mode 100644 index 00000000..ad28ce9a --- /dev/null +++ b/src/WeihanLi.Common/Templating/IRenderMiddleware.cs @@ -0,0 +1,10 @@ +// Copyright (c) Weihan Li. All rights reserved. +// Licensed under the Apache license. + +using WeihanLi.Common.Helpers; + +namespace WeihanLi.Common.Templating; + +public interface IRenderMiddleware : IAsyncPipelineMiddleware +{ +} diff --git a/src/WeihanLi.Common/Templating/TemplateOptions.cs b/src/WeihanLi.Common/Templating/ITemplateEngine.cs similarity index 56% rename from src/WeihanLi.Common/Templating/TemplateOptions.cs rename to src/WeihanLi.Common/Templating/ITemplateEngine.cs index b2875ff1..1adb10da 100644 --- a/src/WeihanLi.Common/Templating/TemplateOptions.cs +++ b/src/WeihanLi.Common/Templating/ITemplateEngine.cs @@ -2,6 +2,8 @@ // Licensed under the Apache license. namespace WeihanLi.Common.Templating; -public sealed class TemplateOptions + +public interface ITemplateEngine { + Task RenderAsync(string text, object? globals = null); } diff --git a/src/WeihanLi.Common/Templating/ITemplateRenderer.cs b/src/WeihanLi.Common/Templating/ITemplateRenderer.cs index b22b5b14..e3ba6029 100644 --- a/src/WeihanLi.Common/Templating/ITemplateRenderer.cs +++ b/src/WeihanLi.Common/Templating/ITemplateRenderer.cs @@ -4,5 +4,5 @@ namespace WeihanLi.Common.Templating; public interface ITemplateRenderer { - Task RenderAsync(TemplateRenderContext template, object globals); + Task RenderAsync(TemplateRenderContext template, object? globals); } diff --git a/src/WeihanLi.Common/Templating/ITemplateRendererBuilder.cs b/src/WeihanLi.Common/Templating/ITemplateRendererBuilder.cs index 5a38ae8b..5fa8917c 100644 --- a/src/WeihanLi.Common/Templating/ITemplateRendererBuilder.cs +++ b/src/WeihanLi.Common/Templating/ITemplateRendererBuilder.cs @@ -2,9 +2,14 @@ // Licensed under the Apache license. namespace WeihanLi.Common.Templating; + public interface ITemplateRendererBuilder { - ITemplateRendererBuilder Use(Func, Task> middleware); - ITemplateRendererBuilder ConfigureOptions(Action configureOptionsAction); - ITemplateRenderer Build(); + ITemplateRendererBuilder UseRenderMiddleware(TMiddleware middleware) + where TMiddleware : class, IRenderMiddleware; +} + +public interface ITemplateEngineBuilder : ITemplateRendererBuilder +{ + ITemplateRendererBuilder ConfigureOptions(Action optionsConfigure); } diff --git a/src/WeihanLi.Common/Templating/TemplateEngine.cs b/src/WeihanLi.Common/Templating/TemplateEngine.cs index 8c061de6..ec49cb2d 100644 --- a/src/WeihanLi.Common/Templating/TemplateEngine.cs +++ b/src/WeihanLi.Common/Templating/TemplateEngine.cs @@ -1,46 +1,33 @@ // Copyright (c) Weihan Li. All rights reserved. // Licensed under the Apache license. -using WeihanLi.Extensions; - namespace WeihanLi.Common.Templating; -public sealed class TemplateEngine : ITemplateRenderer, ITemplateParser +public sealed class TemplateEngine(ITemplateParser templateParser, ITemplateRenderer templateRenderer) + : ITemplateEngine, ITemplateRenderer, ITemplateParser { - private readonly ITemplateParser _templateParser; - private readonly ITemplateRenderer _templateRenderer; - public TemplateEngine(ITemplateParser templateParser, ITemplateRenderer templateRenderer) - { - _templateParser = templateParser; - _templateRenderer = templateRenderer; - } + private readonly ITemplateParser _templateParser = templateParser; + private readonly ITemplateRenderer _templateRenderer = templateRenderer; + public Task ParseAsync(string text) { return _templateParser.ParseAsync(text); } - public async Task RenderAsync(TemplateRenderContext context, object globals) + public async Task RenderAsync(TemplateRenderContext context, object? globals) { return await _templateRenderer.RenderAsync(context, globals); } - public async Task RenderAsync(string text, object parameters) + public async Task RenderAsync(string text, object? parameters = null) { var context = await _templateParser.ParseAsync(text); var result = await _templateRenderer.RenderAsync(context, parameters); return result; } - public static TemplateEngine CreateDefault() => - new TemplateEngine(new DefaultTemplateParser(), new DefaultTemplateRenderer(context => - { - if (context.Text.IsNullOrWhiteSpace()) - { - return Task.CompletedTask; - } - context.RenderedText = context.Text; - foreach (var parameter in context.Parameters) - { - context.RenderedText = context.RenderedText.Replace($"{{{{{parameter.Key}}}}}", parameter.Value?.ToString()); - } - return Task.CompletedTask; - })); + public static TemplateEngine CreateDefault(Action? builderConfigure = null) + { + var builder = new TemplateEngineBuilder(); + builderConfigure?.Invoke(builder); + return new TemplateEngine(builder.BuildParser(), builder.BuildRenderer()); + } } diff --git a/src/WeihanLi.Common/Templating/TemplateEngineOptions.cs b/src/WeihanLi.Common/Templating/TemplateEngineOptions.cs new file mode 100644 index 00000000..9b1409ee --- /dev/null +++ b/src/WeihanLi.Common/Templating/TemplateEngineOptions.cs @@ -0,0 +1,10 @@ +// Copyright (c) Weihan Li. All rights reserved. +// Licensed under the Apache license. + +using Microsoft.Extensions.Configuration; + +namespace WeihanLi.Common.Templating; +public sealed class TemplateEngineOptions +{ + public IConfiguration? Configuration { get; set; } +} diff --git a/src/WeihanLi.Common/Templating/TemplateRendererBuilder.cs b/src/WeihanLi.Common/Templating/TemplateRendererBuilder.cs index d5fcaa23..ca3e403e 100644 --- a/src/WeihanLi.Common/Templating/TemplateRendererBuilder.cs +++ b/src/WeihanLi.Common/Templating/TemplateRendererBuilder.cs @@ -5,29 +5,35 @@ namespace WeihanLi.Common.Templating; -internal sealed class TemplateRendererBuilder : ITemplateRendererBuilder +internal sealed class TemplateEngineBuilder : ITemplateEngineBuilder { private readonly IAsyncPipelineBuilder _pipelineBuilder = PipelineBuilder.CreateAsync(); - private Action? _optionsAction; + private Action? _optionsConfigure; - public ITemplateRendererBuilder Use(Func, Task> middleware) + public ITemplateRendererBuilder UseRenderMiddleware(TMiddleware middleware) where TMiddleware : class, IRenderMiddleware { - _pipelineBuilder.Use(middleware); + _pipelineBuilder.UseMiddleware(Guard.NotNull(middleware)); return this; } - public ITemplateRendererBuilder ConfigureOptions(Action configureOptionsAction) + public ITemplateRendererBuilder ConfigureOptions(Action optionsConfigure) { - Guard.NotNull(configureOptionsAction); - _optionsAction = configureOptionsAction; + _optionsConfigure = Guard.NotNull(optionsConfigure); return this; } - public ITemplateRenderer Build() + public ITemplateParser BuildParser() => new DefaultTemplateParser(); + + public ITemplateRenderer BuildRenderer() { - var options = new TemplateOptions(); - _optionsAction?.Invoke(options); + var options = new TemplateEngineOptions(); + _optionsConfigure?.Invoke(options); + _pipelineBuilder + .UseMiddleware(new DefaultRenderMiddleware()) + .UseMiddleware(new EnvRenderMiddleware()) + .UseMiddleware(new ConfigurationRenderMiddleware(options.Configuration)) + ; return new DefaultTemplateRenderer(_pipelineBuilder.Build()); } } diff --git a/src/WeihanLi.Extensions.Hosting/HostingExtensions.cs b/src/WeihanLi.Extensions.Hosting/HostingExtensions.cs index 791f2b58..2d1ab21f 100644 --- a/src/WeihanLi.Extensions.Hosting/HostingExtensions.cs +++ b/src/WeihanLi.Extensions.Hosting/HostingExtensions.cs @@ -14,7 +14,7 @@ public static IHostApplicationBuilder ConfigureHostOptions(this IHostApplication ArgumentNullException.ThrowIfNull(optionsConfigure); hostApplicationBuilder.Services.Configure(optionsConfigure); - + return hostApplicationBuilder; } } diff --git a/src/WeihanLi.Extensions.Hosting/TimerBaseBackgroundService.cs b/src/WeihanLi.Extensions.Hosting/TimerBaseBackgroundService.cs index ae12e75a..fc8ef4ef 100644 --- a/src/WeihanLi.Extensions.Hosting/TimerBaseBackgroundService.cs +++ b/src/WeihanLi.Extensions.Hosting/TimerBaseBackgroundService.cs @@ -12,7 +12,7 @@ public abstract class TimerBaseBackgroundService : BackgroundService, IMyHostedS { protected abstract TimeSpan Period { get; } protected abstract Task ExecuteTaskAsync(CancellationToken stoppingToken); - + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { using var timer = new PeriodicTimer(Period); diff --git a/test/WeihanLi.Common.Test/ExtensionsTest/ConfigurationExtensionTest.cs b/test/WeihanLi.Common.Test/ExtensionsTest/ConfigurationExtensionTest.cs index 2a864dbe..f0067a8f 100644 --- a/test/WeihanLi.Common.Test/ExtensionsTest/ConfigurationExtensionTest.cs +++ b/test/WeihanLi.Common.Test/ExtensionsTest/ConfigurationExtensionTest.cs @@ -10,7 +10,7 @@ public class ConfigurationExtensionTest [InlineData("false")] [InlineData("false-value")] [InlineData(null)] - public void FeatureFlagTest(string value) + public void FeatureFlagTest(string? value) { var featureName = "TestFeature"; var configuration = new ConfigurationBuilder() diff --git a/test/WeihanLi.Common.Test/ExtensionsTest/StringExtensionTest.cs b/test/WeihanLi.Common.Test/ExtensionsTest/StringExtensionTest.cs index 50ba29c2..d67da244 100644 --- a/test/WeihanLi.Common.Test/ExtensionsTest/StringExtensionTest.cs +++ b/test/WeihanLi.Common.Test/ExtensionsTest/StringExtensionTest.cs @@ -32,7 +32,7 @@ public void GetTypeByTypeName(Type type, string name) [InlineData("", "123")] [InlineData(" ", "123")] [InlineData(null, "123")] - public void GetNotEmptyValue(string value, string defaultValue) + public void GetNotEmptyValue(string? value, string defaultValue) { var expected = string.IsNullOrEmpty(value) ? defaultValue : value; Assert.Equal(expected, value.GetNotEmptyValueOrDefault(defaultValue)); @@ -43,7 +43,7 @@ public void GetNotEmptyValue(string value, string defaultValue) [InlineData("", "123")] [InlineData(" ", "123")] [InlineData(null, "123")] - public void StringGetValue(string value, string defaultValue) + public void StringGetValue(string? value, string defaultValue) { var expected = string.IsNullOrWhiteSpace(value) ? defaultValue : value; Assert.Equal(expected, value.GetValueOrDefault(defaultValue)); @@ -68,7 +68,7 @@ public void TrimStart(string? value, string? start) [InlineData(" ")] [InlineData("")] [InlineData(null)] - public void IsNullOrEmpty(string value) + public void IsNullOrEmpty(string? value) { Assert.Equal(string.IsNullOrEmpty(value), value.IsNullOrEmpty()); } @@ -79,7 +79,7 @@ public void IsNullOrEmpty(string value) [InlineData(" ")] [InlineData("")] [InlineData(null)] - public void IsNotNullOrEmpty(string value) + public void IsNotNullOrEmpty(string? value) { Assert.Equal(!string.IsNullOrEmpty(value), value.IsNotNullOrEmpty()); } @@ -90,7 +90,7 @@ public void IsNotNullOrEmpty(string value) [InlineData(" ")] [InlineData("")] [InlineData(null)] - public void IsNullOrWhiteSpace(string value) + public void IsNullOrWhiteSpace(string? value) { Assert.Equal(string.IsNullOrWhiteSpace(value), value.IsNullOrWhiteSpace()); } @@ -101,7 +101,7 @@ public void IsNullOrWhiteSpace(string value) [InlineData(" ")] [InlineData("")] [InlineData(null)] - public void IsNotNullOrWhiteSpace(string value) + public void IsNotNullOrWhiteSpace(string? value) { Assert.Equal(!string.IsNullOrWhiteSpace(value), value.IsNotNullOrWhiteSpace()); } diff --git a/test/WeihanLi.Common.Test/HelpersTest/BoundedConcurrentQueue.cs b/test/WeihanLi.Common.Test/HelpersTest/BoundedConcurrentQueue.cs index fa3d0e37..e8fe5839 100644 --- a/test/WeihanLi.Common.Test/HelpersTest/BoundedConcurrentQueue.cs +++ b/test/WeihanLi.Common.Test/HelpersTest/BoundedConcurrentQueue.cs @@ -17,7 +17,7 @@ public void FullQueue_DropWrite() Assert.False(queue.TryEnqueue(null)); Assert.Equal(1, queue.Count); } - + [Fact] public void FullQueue_DropOldest() { diff --git a/test/WeihanLi.Common.Test/WeihanLi.Common.Test.csproj b/test/WeihanLi.Common.Test/WeihanLi.Common.Test.csproj index a7a2b35b..c5d1c405 100644 --- a/test/WeihanLi.Common.Test/WeihanLi.Common.Test.csproj +++ b/test/WeihanLi.Common.Test/WeihanLi.Common.Test.csproj @@ -10,6 +10,10 @@ + + all + runtime; build; native; contentfiles; analyzers + all runtime; build; native; contentfiles; analyzers; buildtransitive