Bereitstellen von Dependency Injection, Logging und Konfiguration in einer Console Anwendung
Manchmal möchte man einfach eine Consolen Applikation benutzen, um ein komplexes Script oder Tool zu bauen. Dabei möchte man nicht auf Dependency Injection oder Logging verzichten. Auch möchte ich den Code testen. Da kommt das coole Interface IHost ins Spiel.
Dafür muss ich die beiden Nuget Packages:
- Microsoft.Extensions.Hosting.Abstractions
- Microsoft.Extensions.Hosting
installieren.
Wenn Du dir die csproj Datei anschaust, sollten diese beiden Einträge enthalten sein:
Dann die beiden usages in die Program.cs einfügen.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
Die root Klasse NumberService anlegen.
namespace ConsoleHost;
public class NumberService(INumberRepository numberRepository) : INumberService
{
public int GetPositiveNumber()
{
return Math.Abs(numberRepository.GetNumber());
}
}
public interface INumberService
{
int GetPositiveNumber();
}
Diese Klasse erhält als Abhängigkeit das INumberRepository, welches vom Type NumberRepository implementiert wird.
namespace ConsoleHost;
public class NumberRepository : INumberRepository
{
public int GetNumber()
{
return -8;
}
}
public interface INumberRepository
{
int GetNumber();
}
Dann in der Program.cs diesen Code einfügen:
// See https://aka.ms/new-console-template for more information
var host = CreateHost();
var numberService = host.Services.GetRequiredService();
Console.WriteLine($“The positive number is {numberService.GetPositiveNumber()}“);
return;
static IHost CreateHost() =>
Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.AddSingleton<inumberrepository, numberrepository=““>();
services.AddSingleton<inumberservice, numberservice=““>();
})
.Build();</inumberservice,></inumberrepository,>
Die CreateDefaultBuilder Methode liefert eine IHostBuilder Instanz. Es gibt weitere Ausprägungen wie z.B. die IWebHostBuilder Instanz. Diese hat dann auch noch den ganzen Web Space dabei, den wir hier natürlich nicht brauchen.
Dann fügen wir die Abhängigkeiten als Singleton hinzu. Danach rufen wir nur noch Build() auf und wir bekommen die Host Instanz. Über diese könne wir dann z.B. den NumberService auflösen und die Methode GetPositivNumber() aufrufen.
Das sind wenige Zeilen Code, um Dependency Injection zu etablieren.
Auch das Lesen von Config Informationen lässt sich über diesen Ansatz verfolgen.
Zuerst fügen wir eine zwei neue Datein hinzu:
- appsettings.json
- NumberConfig.cs (in diese Klasse wird der Inhalt der appsettings.json deserialisiert)
Appsettings.json:
{
„Number“: {
„DefaultNumber“:-100
}
}
Bei den File Properties der appsettings.json stellen wir „Copy always“ ein.
In der Progam.cs erweitern wir jetzt folgendermaßen:
static IHost CreateHost() =>
Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
//das ist die veränderung services.Configure(context.Configuration.GetSection(„Number“));
services.AddSingleton<inumberrepository, numberrepository=““>();
services.AddSingleton<inumberservice, numberservice=““>();
})
.Build();</inumberservice,></inumberrepository,>
Dann müssen wir nur noch unsere Klasse, die mit diesem Config Wert arbeiten soll, erweitern und den Config Wert verarbeiten lassen.
using Microsoft.Extensions.Options;
namespace ConsoleHost;
public class NumberRepository(IOptions numberConfig) : INumberRepository
{
private NumberConfig _numberConfig = numberConfig.Value;
public int GetNumber() => _numberConfig.DefaultNumber;
}
public interface INumberRepository
{
int GetNumber();
}
Wie man hier sehen kann wird die Versorgung über das Interface IOptions gewährleistet. Das wars. So schnell kann man seine Klasse mit den Config Werten aus den appsettings versorgen.
Jetzt noch ein wenig Logging gefällig? Ich mag sehr gerne Serilog. So packt man das dazu:
Folgende Nuget Packages hinzufügen:
- <PackageReference Include=“Serilog.Extensions.Hosting“ Version=“9.0.0″ />
- <PackageReference Include=“Serilog.Settings.Configuration“ Version=“9.0.0″ />
- <PackageReference Include=“Serilog.Sinks.Console“ Version=“6.0.0″ />
- <PackageReference Include=“Serilog.Sinks.File“ Version=“6.0.0″ />
Dann die Program.cs erweitern:
static IHost CreateHost() =>
Host.CreateDefaultBuilder()
.ConfigureServices((context, services) =>
{
services.Configure(context.Configuration.GetSection(„Number“));
services.AddSingleton<inumberrepository, numberrepository=““>();
services.AddSingleton<inumberservice, numberservice=““>();</inumberservice,></inumberrepository,>
}).UseSerilog((_, _, configuration) => configuration
.WriteTo.Console()
.WriteTo.File($“report-{DateTimeOffset.UtcNow:yyyy-MM-dd-HH-mm-ss}.txt“, restrictedToMinimumLevel: LogEventLevel.Information)
)
.Build();
Wir wollen Serilog nutzen (UseSerilog). Wir wollen die Log Statements in die Console und in eine Textdatei schreiben. Das sind die beiden anderen Anweisungen.
Wir müssen jetzt noch die Klassen, die Log Statements schreiben sollen, mit dem ILogger Interface versorgen.
Das sieht dann so aus:
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
namespace ConsoleHost;
public class NumberRepository(IOptions numberConfig, ILogger logger) : INumberRepository
{
private NumberConfig _numberConfig = numberConfig.Value;
public int GetNumber()
{
logger.LogInformation($“Returning the default number: {_numberConfig.DefaultNumber}“);
return _numberConfig.DefaultNumber;
}
}
public interface INumberRepository
{
int GetNumber();
}
Mehr ist nicht zu tun.