Uso corretto di Autofac nell'applicazione console C#

Uso corretto di Autofac nell'applicazione console C#

L'elettricità statica è il problema

Il problema principale con un programma console è che il principale Program la classe è per lo più statica. Questo non va bene per i test unitari e non va bene per IoC; una classe statica non viene mai costruita, ad esempio, quindi non vi è alcuna possibilità per l'iniezione del costruttore. Di conseguenza, finisci per utilizzare new nella base di codice principale o istanze pull dal contenitore IoC, che è una violazione del modello (a quel punto è più un modello di localizzazione del servizio). Possiamo uscire da questo pasticcio tornando alla pratica di inserire il nostro codice in metodi di istanza, il che significa che abbiamo bisogno di un'istanza oggetto di qualcosa. Ma cosa?

Uno schema a due classi

Seguo uno schema particolare e leggero quando scrivo un'app per console. Puoi seguire questo schema che funziona abbastanza bene per me.

Il modello coinvolge due classi:

  1. Il Program originale class, che è statica, molto breve ed esclusa dalla copertura del codice. Questa classe funge da "pass through" dall'invocazione di O/S all'invocazione dell'applicazione vera e propria.
  2. Un Application istanziato classe, che è completamente iniettata e testabile per unità. È qui che dovrebbe risiedere il tuo vero codice.

La lezione del programma

Il sistema operativo richiede un Main punto di ingresso e deve essere statico. Il Program la classe esiste solo per soddisfare questo requisito.

Mantieni il tuo programma statico molto pulito; dovrebbe contenere (1) la radice della composizione e (2) un semplice punto di ingresso "pass-through" che chiama l'applicazione reale (che è istanziata, come vedremo).

Nessuno del codice in Program è degno di unit test, poiché tutto ciò che fa è comporre il grafico dell'oggetto (che sarebbe comunque diverso durante il test) e chiamare il punto di ingresso principale per l'applicazione. E sequestrando il codice non testabile per unità, ora puoi escludere l'intera classe dalla copertura del codice (usando ExcludeFromCodeCoverageAttribute).

Ecco un esempio:

[ExcludeFromCodeCoverage]
static class Program
{
    private static IContainer CompositionRoot()
    {
        var builder = new ContainerBuilder();
        builder.RegisterType<Application>();
        builder.RegisterType<EmployeeService>().As<IEmployeeService>();
        builder.RegisterType<PrintService>().As<IPrintService>();
        return builder.Build();
    }

    public static void Main()  //Main entry point
    {
        CompositionRoot().Resolve<Application>().Run();
    }
}

Come puoi vedere, estremamente semplice.

La classe dell'applicazione

Ora per implementare il tuo Application classe come se fosse l'Unico Programma. Solo ora, poiché è istanziato, puoi iniettare dipendenze secondo il solito schema.

class Application
{
    protected readonly IEmployeeService _employeeService;
    protected readonly IPrintService _printService;

    public Application(IEmployeeService employeeService, IPrintService printService)
    {
        _employeeService = employeeService; //Injected
        _printService = printService; //Injected
    }

    public void Run()
    {
        var employee = _employeeService.GetEmployee();
        _printService.Print(employee);
    }
}

Questo approccio mantiene la separazione delle preoccupazioni, evita troppe "cose" statiche e ti consente di seguire il modello IoC senza troppi problemi. E noterai:il mio esempio di codice non contiene una singola istanza di new parola chiave, tranne per creare un'istanza di un ContainerBuilder.

E se le dipendenze hanno dipendenze proprie?

Perché seguiamo questo schema, se PrintService o EmployeeService hanno le proprie dipendenze, il contenitore ora si occuperà di tutto. Non è necessario creare un'istanza o scrivere alcun codice per ottenere quei servizi iniettati, purché vengano registrati nell'interfaccia appropriata nella radice della composizione.

class EmployeeService : IEmployeeService
{
    protected readonly IPrintService _printService;

    public EmployeeService(IPrintService printService)
    {
        _printService = printService; //injected
    }

    public void Print(Employee employee)
    {
        _printService.Print(employee.ToString());
    }
}

In questo modo il container si occupa di tutto e tu non devi scrivere alcun codice, devi solo registrare i tuoi tipi e le tue interfacce.