Hosting DI Container with .NET Core 3.0

ASP.NET Core included the WebHost class that was used in the Main method to startup everything up – including the dependency injection container. With Non-ASP.NET Core applications I used the same DI container (Microsoft.Extensions.DependencyInjection), but I had to create the ServiceCollection on my own. This changes with .NET Core 3.0 and the new Host class that is independent of Web applications.
It’s clear how the Host class is used from ASP.NET Core Web applications. I was wondering how to take advantage of this class from console, UWP, WPF, and Xamarin applications. With these application types, I’ve been using the ServiceCollectionclass directly to register my services. Now I can take advantage of the Host class. In this article I’m changing a previous sample from Dependency Injection with .NET Core to use the new Host class from the namespace Microsoft.Extensions.Hosting. You should read the older article first if you don’t know the DI container from Microsoft.Extensions.DependencyInjection.

Chair

Host in ASP.NET Core Web Applications

Creating an ASP.NET Core 3.0 application, the template-generated code invokes the static method CreateDefaultBuilder and the extension method ConfigureWebHostDefaults where the Startup class is defined to be used for the startup. In the Startup class the services are configured in the DI container. The code is very similar to older versions of ASP.NET Core – just the Host class is used instead of the older WebHost.

public static void Main(string[] args)
{
    CreateHostBuilder(args).Build().Run();
}

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureWebHostDefaults(webBuilder =>
        {
            webBuilder.UseStartup<Startup>();
        });

Sample Application

The sample application defines the interface contract IGreetingService, an implementation GreetingService, and the class HelloController where the IGreetingService is injected to. This is the same structure as used with the sample from the previous referenced article.

public interface IGreetingService
{
    string Greeting(string name);
}
public class GreetingService : IGreetingService
{
    public string Greeting(string name) => $"Hello, {name}";
}
public class HelloController
{
    private readonly IGreetingService _greetingService;
    public HelloController(IGreetingService greetingService) => 
        _greetingService = greetingService;

    public string Action(string name) =>
        _greetingService.Greeting(name);
}

Using the Host Class

Next, let’s get into the changes of the previous application architecture. Instead of creating a method where a new object of the ServiceCollection is instantiated (the DI container), let’s use the new Host class. This class is defined in the NuGet package Microsoft.Extensions.Hosting with a namespace of the same name. The static method CreateDefaultBuilder returns a builder object implementing the interface IHostBuilder. Now, extension methods such as ConfigureServices can be used. ConfigureServices defines a delegate parameter Action, thus a IServiceCollection is received with the parameter where services can be configured with the DI container. The Build method initializes the host and returns the disposable IHost interface. Behind the scenes, the Build method invokes the method BuildServiceProvider with the DI container. The container can be accessed with the Services property of the host. The IServiceProvider interface is received with the method UsingAContainer. In the method implementation, the HelloController is retrieved via the container. The container injects the required IGreetingService in the constructor of the HelloController. This functionality is the same as it was with the older implementation.

The sample code uses the C# 8 syntax for the using declaration. With the using declaration, the variable referenced by it is disposed at the end of the scope. Contrary to the using statement, it’s not necessary to write curly brackets to define the scope.

public class Program
{
    public static void Main()
    {
        using IHost host = Host.CreateDefaultBuilder()
            .ConfigureServices(ConfigureServices)
            .Build();

        UsingAContainer(host.Services);
    }

    private static void ConfigureServices(IServiceCollection services)
    {
        services.AddTransient<IGreetingService, GreetingService>();
        services.AddTransient<HelloController>();
    }

    private static void UsingAContainer(IServiceProvider container)
    {
        var controller = container.GetService<HelloController>();
        string greeting = controller.Action("Katharina");
        Console.WriteLine(greeting);
    }
}

Extending the Use of the Host Class

The Host class also allows to define logging and configuration requirements. You can define the configuration for the application using ConfigureAppConfiguration, and define logging providers and filters with the extension method ConfigureLogging. Instead of just using the IHost object returned from the Build method, now the RunConsoleAsync method is used. This method builds and starts the host, and waits for a CTRL+C or SIGTERM to shutdown. The RunConsoleAsync method returns a Task, thus this method can be awaited.

public static async Task Main(string[] args)
{
    await Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration(config =>
            config.SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile("appsettings.json", optional: true)
                .AddEnvironmentVariables()
                .AddCommandLine(args))
        .ConfigureLogging(logging =>
        {
            logging.AddConsole().AddFilter(level => level >= LogLevel.Error);
        })
        .ConfigureServices(services =>
        {
            services.AddHostedService<GreetingHost>();
            services.AddTransient<IGreetingService, GreetingService>();
            services.AddTransient<HelloController>();
        }).RunConsoleAsync();
}

Using RunConsoleAsync needs a hosted service implementation – a class that implements the interface IHostedService. This interface defines the methods StartAsync and StopAsync that are invoked on starting and on ending the host. With the sample code, the HelloController is injected in the constructor and this object is used within the implementation of the StartAsync method. Because the application should not continue after writing of the greeting message, the interface IHostingApplicationLifetime is injected with the GreetingHost to send a StopApplication.

public class GreetingHost : IHostedService
{
    private readonly HelloController _controller;
    private readonly IHostApplicationLifetime _lifetime;
    public GreetingHost(HelloController controller, IHostApplicationLifetime lifetime) =>
        (_controller, _lifetime) = (controller, lifetime);

    public Task StartAsync(CancellationToken cancellationToken)
    {
        string greeting = _controller.Action("Matthias");
        Console.WriteLine(greeting);
        _lifetime.StopApplication();
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        Console.WriteLine("closing...");
        return Task.CompletedTask;
    }
}

Summary

Instead of the WebHost class that was in use with ASP.NET Core Web applications before .NET Core 3.0, now the Host class is available. With older versions of .NET Core I already used .NET Core features for configuration, logging, and dependency injection not only with my ASP.NET Core applications, but also with WPF, UWP, and Xamarin. Now I can also use the Host class to make this configuration for different application types. This can be as simple as just using it as a wrapper for the DI container Microsoft.Extensions.DependencyInjection, but also more advanced features e.g. to control the lifetime can be used.

If you’ve read this far, consider buying me a coffee which helps me staying up longer and writing more articles.

Buy Me A Coffee

Interesting Links for this article:

Complete code sample

Dependency Injection with .NET Core

.NET Core Dependency Injection with Options

.NET Core Dependency Injection with Configuration

Disposing Injected Services

HttpClientFactory with .NET Core 2.1

Configuration with .NET Core

Writing ILogger Diagnostics to a File

More information on EF Core and writing data-driven applications is in my book Professional C# 7 and .NET Core 2.0, and in my workshops.

Enjoy learning and programming!

Christian

5 thoughts on “Hosting DI Container with .NET Core 3.0

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.