blexin

Consulenza
Sviluppo e
Formazione IT


Blog

Evolvi la tua azienda

Come usare la dependency injection in una console application .Net Core per la gestione del logging con Serilog.

Loggare o non loggare? Nessun problema!

Martedì 12 Marzo 2019

Tra le numerose novità introdotte in ASP.NET Core, una delle più importanti é la presenza di un motore di "Dependency Injection" incorporato.

Mentre il template di Visual Studio per un progetto ASP.NET Core MVC include già la configurazione necessaria per utilizzarla, quando creiamo una console application .NET Core non abbiamo nessun aiuto.

Vediamo quindi come possiamo risolvere questo problema. Anzitutto creiamo nel nostro progetto due interfacce.

In questa demo ho utilizzato la versione 2.2 di .NET Core.

namespace DemoArticolo.Interfaces
{

    public interface InterfaceA
    {
        void DoSomething();
    }

    public interface InterfaceB
    {
        void DoSomethingElse();
    }
}

Creiamo quindi due classi che implementino queste interfacce.

 public class ClassA : InterfaceA
 {
     public void DoSomething()
     {
        Console.WriteLine("Writing something to the console from ClassA");
     } 
 }
 public class ClassB : InterfaceB
 {
        private readonly InterfaceA interfaceA;

        public ClassB(InterfaceA _interfaceA)
        {
            interfaceA = _interfaceA;
        }
        public void DoSomethingElse()
        {            
            interfaceA.DoSomething();
            Console.WriteLine("Writing something else to the console from ClassB");
        }
 }

Nel costruttore della classe ClassB è stata iniettata l'interfaccia interfaceA.

Occorre a questo punto installare tramite NuGet il seguente pacchetto: Microsoft.Extensions.DependencyInjection

alt text

Abbiamo quindi bisogno di un metodo che aggiunga e, eventualmente configuri, i nostri servizi.

 private static void ConfigureServices(ServiceCollection serviceCollection)
 {
            serviceCollection.AddSingleton<InterfaceA, ClassA>();
            serviceCollection.AddSingleton<InterfaceB, ClassB>();
 }

Abbiamo scelto il metodo AddSingleton() che utilizzerà sempre la stessa istanza di ClassA e ClassB per ogni richiesta.

E' possibile, comunque, fare scelte diverse quali ad esempio AddScoped() o AddTransient() in base al contesto in cui state inserendo il codice. La documentazione completa è disponibile al seguente indirizzo

Il metodo ConfigureServices() è invocato nel metodo Main() della nostra console application per creare la ServiceCollection che utilizzeremo nel corso dell'applicazione.

Va quindi costruito un ServiceProvider, ossia l'oggetto che è in grado di recuperare le istanze dei nostri servizi.

static void Main(string[] args)
{
    var serviceCollection = new ServiceCollection();
    ConfigureServices(serviceCollection);

    var serviceProvider = serviceCollection.BuildServiceProvider();
}

A questo punto dobbiamo solo utilizzare le istanze dei nostri servizi

static void Main(string[] args)
{
    var serviceCollection = new ServiceCollection();
    ConfigureServices(serviceCollection);

    var serviceProvider = serviceCollection.BuildServiceProvider();
    var implementazione = serviceProvider.GetService<InterfaceB>();
    implementazione.DoSomethingElse();
}

alt text

Un esempio più concreto di questa tecnica è quello del Logging in .NET Core, una API che è compatibile con un gran numero di provider di logging di terze parti.

E' necessario installare due altri pacchetti da NuGet: Microsoft.Extensions.Logging e Microsoft.Extensions.Logging.Console

alt text

Modifichiamo il metodo ConfigureService per registrare il servizio di Logging tramite console.

 private static void ConfigureServices(ServiceCollection serviceCollection)
 {
    serviceCollection.AddSingleton<InterfaceA, ClassA>();
    serviceCollection.AddSingleton<InterfaceB, ClassB>();
    serviceCollection.AddLogging(configure => configure.AddConsole());
 }

Modifichiamo la ClassB Iniettando tramite il costruttore il servizio di Logging.

public class ClassB : InterfaceB
{
    private readonly InterfaceA interfaceA;
    private readonly ILogger<Program> logger;

    public ClassB(InterfaceA _interfaceA, ILogger<Program> _logger)
    {
        interfaceA = _interfaceA;
        logger = _logger;
    }
    public void DoSomethingElse()
    {        
        interfaceA.DoSomething();
        logger.LogInformation("Logging from InterfaceB");
    }
}

Modifichiamo infine il method Main() per visualizzare l'effetto del logging nel terminale

static void Main(string[] args)
{
    var serviceCollection = new ServiceCollection();
    ConfigureServices(serviceCollection);

    var serviceProvider = serviceCollection.BuildServiceProvider();

    var logger = serviceProvider.GetService<ILogger<Program>>();
    logger.LogWarning("Hello, Dependency");

    var implementazione = serviceProvider.GetService<InterfaceB>();
    implementazione.DoSomethingElse();

    Console.ReadLine();
}

alt text

Come ultimo esempio, utilizziamo Serilog, una libreria di logging molto diffusa che è compatibile sia con .NET che con .NET Core. I pacchetti da installare sono i seguenti:

alt text

Modifichiamo il metodo ConfigureServices in maniera tale da registrare Serilog. Scegliamo inizialmente di eseguire il log solo su console.

private static void ConfigureServices(ServiceCollection serviceCollection)
{
    Log.Logger = new LoggerConfiguration()
     .Enrich.FromLogContext()
     .WriteTo.Console()
     .CreateLogger();

    serviceCollection.AddSingleton<InterfaceA, ClassA>();
    serviceCollection.AddSingleton<InterfaceB, ClassB>();
    // serviceCollection.AddLogging(configure => configure.AddConsole());

    serviceCollection.AddLogging(configure => configure.AddSerilog());
}

L'output viene modificato (e ricordiamo che può essere ulteriormente configurato).

alt text

Possiamo visualizzare i messaggi di log in console e contemporaneamente salvarli su file. La modifica di ConfigureServices() è molto semplice.

private static void ConfigureServices(ServiceCollection serviceCollection)
{
    Log.Logger = new LoggerConfiguration()
     .Enrich.FromLogContext()
     .WriteTo.Console()
     .WriteTo.File("log.txt", rollingInterval: RollingInterval.Day)
     .CreateLogger();

    serviceCollection.AddSingleton<InterfaceA, ClassA>();
    serviceCollection.AddSingleton<InterfaceB, ClassB>();
    // serviceCollection.AddLogging(configure => configure.AddConsole());

    serviceCollection.AddLogging(configure => configure.AddSerilog());
}

alt text

Esistono numerosi altri pacchetti che gestiscono il logging di Serilog. Tali pacchetti vengono definiti Sink. A questo indirizzo troverete l'elenco completo di sink disponibili per Serilog.

Il codice citato in questo articolo è disponibile su GitHub al seguente link

Autore

Servizi

Evolvi la tua azienda