Skip to content

Commit 97e7d38

Browse files
committed
add ConsoleAppBuilder.PostConfigureServices(Action<IServiceProvider>)
1 parent 6dacbdb commit 97e7d38

File tree

3 files changed

+43
-33
lines changed

3 files changed

+43
-33
lines changed

ReadMe.md

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1131,6 +1131,8 @@ app.Run(args);
11311131

11321132
When passing to a lambda expression or method, the `[FromServices]` attribute is used to distinguish it from command parameters. When passing a class, Constructor Injection can be used, resulting in a simpler appearance. Lambda, method, constructor, filter, etc, all DI supported parameter also supports `[FromKeyedServices]`.
11331133

1134+
`ConfigureServices` is called after command routing is completed and just before the actual parameter parsing process begins, and the generated ServiceProvider is set to the static `ConsoleApp.ServiceProvider`. If you set it without calling `ConfigureServices`, you can use DI without needing to reference `Microsoft.Extensions.DependencyInjection`.
1135+
11341136
Let's try injecting a logger and enabling output to a file. The libraries used are Microsoft.Extensions.Logging and [Cysharp/ZLogger](https://github.com/Cysharp/ZLogger/) (a high-performance logger built on top of MS.E.Logging). If you are referencing `Microsoft.Extensions.Logging`, you can call `ConfigureLogging` from `ConsoleAppBuilder`.
11351137

11361138
```csharp
@@ -1157,44 +1159,23 @@ public class MyCommand(ILogger<MyCommand> logger)
11571159
}
11581160
```
11591161

1160-
For building an `IServiceProvider`, `ConfigureServices/ConfigureLogging` uses `Microsoft.Extensions.DependencyInjection.ServiceCollection`. If you want to set a custom ServiceProvider or a ServiceProvider built from Host, or if you want to execute DI with `ConsoleApp.Run`, set it to `ConsoleApp.ServiceProvider`.
1161-
1162-
```csharp
1163-
// Microsoft.Extensions.DependencyInjection
1164-
var services = new ServiceCollection();
1165-
services.AddTransient<MyService>();
1166-
1167-
using var serviceProvider = services.BuildServiceProvider();
1168-
1169-
// Any DI library can be used as long as it can create an IServiceProvider
1170-
ConsoleApp.ServiceProvider = serviceProvider;
1171-
1172-
// When passing to a lambda expression/method, using [FromServices] indicates that it is passed via DI, not as a parameter
1173-
ConsoleApp.Run(args, ([FromServices]MyService service, int x, int y) => Console.WriteLine(x + y));
1174-
```
1175-
1176-
`ConsoleApp` has replaceable default logging methods `ConsoleApp.Log` and `ConsoleApp.LogError` used for Help display and exception handling. If using `ILogger<T>`, it's better to replace these as well.
1162+
`ConsoleApp` has replaceable default logging methods `ConsoleApp.Log` and `ConsoleApp.LogError` used for Help display and exception handling. If using `ILogger<T>`, it's better to replace these as well. `PostConfigureServices(Action<IServiceProvider> configure)` allows you to change own logger.
11771163

11781164
```csharp
1179-
app.UseFilter<ReplaceLogFilter>();
1180-
1181-
// inject logger to filter
1182-
internal sealed class ReplaceLogFilter(ConsoleAppFilter next, ILogger<Program> logger)
1183-
: ConsoleAppFilter(next)
1165+
app.PostConfigureServices((serviceProvider) =>
11841166
{
1185-
public override Task InvokeAsync(ConsoleAppContext context, CancellationToken cancellationToken)
1186-
{
1187-
ConsoleApp.Log = msg => logger.LogInformation(msg);
1188-
ConsoleApp.LogError = msg => logger.LogError(msg);
1167+
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
11891168

1190-
return Next.InvokeAsync(context, cancellationToken);
1191-
}
1192-
}
1169+
ConsoleApp.Log = msg => logger.LogInformation(msg);
1170+
ConsoleApp.LogError = msg => logger.LogError(msg);
1171+
});
11931172
```
11941173

11951174
> I don't recommend using `ConsoleApp.Log` and `ConsoleApp.LogError` directly as an application logging method, as they are intended to be used as output destinations for internal framework output.
11961175
> For error handling, it would be better to define your own custom filters for error handling, which would allow you to record more details when handling errors.
11971176
1177+
> Since the help display is executed before routing, `ConfigureServices` is not called. In other words, it is impossible to change the logger for the help display using this method.
1178+
11981179
DI can also be effectively used when reading application configuration from `appsettings.json`. For example, suppose you have the following JSON file.
11991180

12001181
```json
@@ -1275,6 +1256,7 @@ internal class ServiceProviderScopeFilter(IServiceProvider serviceProvider, Cons
12751256
// create Microsoft.Extensions.DependencyInjection scope
12761257
await using var scope = serviceProvider.CreateAsyncScope();
12771258

1259+
// replace static ServiceProvider
12781260
var originalServiceProvider = ConsoleApp.ServiceProvider;
12791261
ConsoleApp.ServiceProvider = scope.ServiceProvider;
12801262
try
Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,28 @@
11
using ConsoleAppFramework;
2+
using Microsoft.Extensions.DependencyInjection;
3+
using Microsoft.Extensions.Logging;
24

35

46
args = ["--x", "3", "--y", "5"];
57

6-
await ConsoleApp.RunAsync(args, async (int x, int y) => { });
8+
// args = ["--help"];
79

8-
static void Foo(int x, int y)
10+
var app = ConsoleApp.Create();
11+
12+
app.Add("", (int x, int y) => { throw new Exception(); });
13+
14+
app.ConfigureLogging(x =>
15+
{
16+
x.SetMinimumLevel(LogLevel.Trace);
17+
x.AddSimpleConsole();
18+
});
19+
20+
app.PostConfigureServices((serviceProvider) =>
921
{
10-
Console.WriteLine(x + y);
11-
}
22+
var logger = serviceProvider.GetRequiredService<ILogger<Program>>();
23+
24+
ConsoleApp.Log = msg => logger.LogInformation(msg);
25+
ConsoleApp.LogError = msg => logger.LogError(msg);
26+
});
27+
28+
app.Run(args);

src/ConsoleAppFramework/Emitter.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -819,6 +819,7 @@ public void EmitConfigure(SourceBuilder sb, DllReference dllReference)
819819
sb.AppendLine("Action<ConsoleAppContext, IServiceCollection>? configureServices;");
820820
}
821821
sb.AppendLine("Func<IServiceCollection, IServiceProvider>? createServiceProvider;");
822+
sb.AppendLine("Action<IServiceProvider>? postConfigureServices;");
822823

823824
// methods
824825
if (dllReference.HasConfiguration)
@@ -869,6 +870,14 @@ public void EmitConfigure(SourceBuilder sb, DllReference dllReference)
869870
}
870871
}
871872

873+
sb.AppendLine();
874+
using (sb.BeginBlock("public ConsoleApp.ConsoleAppBuilder PostConfigureServices(Action<IServiceProvider> configure)"))
875+
{
876+
sb.AppendLine("this.isRequireCallBuildAndSetServiceProvider = true;");
877+
sb.AppendLine("this.postConfigureServices = configure;");
878+
sb.AppendLine("return this;");
879+
}
880+
872881
sb.AppendLine();
873882
using (sb.BeginBlock("public ConsoleApp.ConsoleAppBuilder ConfigureContainer<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory, Action<TContainerBuilder>? configure = null) where TContainerBuilder : notnull"))
874883
{
@@ -1009,6 +1018,8 @@ public void EmitConfigure(SourceBuilder sb, DllReference dllReference)
10091018
{
10101019
sb.AppendLine("ConsoleApp.ServiceProvider = services.BuildServiceProvider();");
10111020
}
1021+
1022+
sb.AppendLine("postConfigureServices?.Invoke(ConsoleApp.ServiceProvider);");
10121023
}
10131024
}
10141025

0 commit comments

Comments
 (0)