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 ServiceCollection
class 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
.
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.
Interesting Links for this article:
Dependency Injection with .NET Core
.NET Core Dependency Injection with Options
.NET Core Dependency Injection with Configuration
HttpClientFactory with .NET Core 2.1
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”