Miroslav Holec
Premium

Podpora dependency injection v .NET Core konzolových aplikacích

Miroslav Holec   14. srpna 2019

Článek se vztahuje k verzi produktu .NET Core 1.0

Tento článek byl napsán v roce 2019. Vývojářské technologie se neustále inovují a článek již nemusí popisovat aktuální stav technologie, ideální řešení a můj současný pohled na dané téma.

Zatímco ve webových aplikacích ASP.NET Core je podpora DI automaticky přítomná, v případě konzolovek si ji musíme přidat ručně. V tomto článku ukážu, jak podporu IoC kontejneru do konzolovky přidat a k čemu je scoped lifestyle.

Na samotném začátku máme primitivní metodu Main(), která si musí ručně vyrábět instance potřebných tříd.

public class Program
{
    public static async Task Main(string[] args)
    {
        Application app = new Application(new WebTest());
        await app.Run("https://www.miroslavholec.cz");
    }
}

Řekněme, že stranou máme třídu Application s metodou Run(), kterou bychom chtěli registrovat do IoC kontejneru a následně o ni požádat, včetně závislostí:

public class Application
{
    private readonly WebTest webTest;

    public Application(WebTest webTest)
    {
        this.webTest = webTest;
    }

    public void Run(string url)
    {
        Console.WriteLine(webTest.Count(url));
    }
}

public class WebTest
{
    public async Task<int> Count(string url)
    {
        using (var webclient = new WebClient())
        {
            return (await webclient.DownloadStringTaskAsync(url)).Length;
        }
    }
}

Zapojení NuGet balíčku

Nejprve si zajistím podporu modulu DI skrze NuGet balíček. Webové aplikace jej mají automaticky obsažené ve frameworku, ale konzolovky ne. Přidám tedy do projektového souboru csproj následující NuGet balíček:

<ItemGroup>
  <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="2.2.0" />
</ItemGroup>

Vytvoření kolekce registrovaných služeb

Dále si vytvořím ServiceCollection, do které registruji všechny potřebné služby. V mém případě se jedná o třídy Application a WebTest. Obě registruji společně jako Scoped (později ukážu flexibilitu tohoto lifestyle).

IServiceCollection serviceCollection = new ServiceCollection();
serviceCollection.AddScoped<Application>();
serviceCollection.AddScoped<WebTest>();

Jakmile mám všechny služby registrované, nechám si sestavit ServiceProvider, který mi dokáže service lokací vrátit potřebnou instanci třídy. V našem případě budeme potřebovat vrátit tzv. rootovou službu (zavaděč). Všechny další služby mi do ní již injectne ServiceProvider. Nikde jinde už service location potřebovat nebudu.

var serviceProvider = serviceCollection.BuildServiceProvider(); // dej mi provider

Vrácení rootové služby ze ServiceProvideru

Dále je to snadné. Požádám service provider o vrácení potřebné služby.

var app = serviceProvider.GetService<Application>();
await app.Run("https://www.miroslavholec.cz");

Služba je definovaná jako scoped a já se automaticky nacházím také v rámci výchozího scope. Mohl bych si nicméně vytvořit explicitní scope a ten pak zahodit.

  using (var scope = serviceProvider.CreateScope())
  {
      // inner scope
      var app = serviceProvider.GetService<Application>();
      await app.Run("https://www.miroslavholec.cz");   
  }

  // out of scope
  var app2 = serviceProvider.GetService<Application>();

Mám tedy možnost nechat si v rámci různých scopes vrátit různé instance třídy. O vše se mi stará IoC kontejner. Stejně tak bych mohl použít lifestyle Transient nebo Singleton.

Všechno ostatní, co jste zvyklí používat v souvislosti s DI ve webových aplikacích lze používat i zde, jen je občas potřeba si k tomu připojit potřebné NuGet balíčky.