Update on using HostBuilder, Dependency Injection and Service Provider with .NET Core 3.0 WPF applications
Some months ago we talked about how to use .NET Core 3.0 Dependency Injection and Service Provider with WPF. Now that the final version of .NET Core 3.0 has reached its GA, it’s time to update that article showing the right approach to use HostBuilder, Dependency Injection and Service Provider in WPF applications.
So, after creating a .NET Core WPF application, we need to add the NuGet package Microsoft.Extensions.Hosting to the project. It will allow us to use HostBuilder and, moreover, it automatically imports a bunch of other required packages.
Now, Let’s open the App.xaml file and remove the StartupUri property of the Application class. Then, write the following code in the App.xaml.cs file:
public partial class App : Application { private readonly IHost host; public App() { host = Host.CreateDefaultBuilder() .ConfigureServices((context, services) => { ConfigureServices(context.Configuration, services); }) .Build(); } private void ConfigureServices(IConfiguration configuration, IServiceCollection services) { // ... services.AddSingleton<MainWindow>(); } protected override async void OnStartup(StartupEventArgs e) { await host.StartAsync(); var mainWindow = host.Services.GetRequiredService<MainWindow>(); mainWindow.Show(); base.OnStartup(e); } protected override async void OnExit(ExitEventArgs e) { using (host) { await host.StopAsync(TimeSpan.FromSeconds(5)); } base.OnExit(e); } }
At line 7, we create a HostBuilder using the Host.CreateDefaultBuilder method. As we can read in the documentation, it automatically configures some defaults (that are tipically useful for every application):
- set the ContentRootPath to the result of GetCurrentDirectory()
- load host IConfiguration from “DOTNET_” prefixed environment variables
- load app IConfiguration from ‘appsettings.json’ and ‘appsettings.[EnvironmentName].json’
- load app IConfiguration from User Secrets when EnvironmentName is ‘Development’ using the entry assembly
- load app IConfiguration from environment variables
- configure the ILoggerFactory to log to the console, debug, and event source output
- enables scope validation on the dependency injection container when EnvironmentName is ‘Development’
If we don’t want these configuration, but instead we prefer to manually configure every setting, we can simply initialize a new HostBuilder. In any case, it is possible to invoke other methods to further configure our host:
host = Host.CreateDefaultBuilder() // Use default settings //new HostBuilder() // Initialize an empty HostBuilder .ConfigureAppConfiguration((context, builder) => { // Add other configuration files... builder.AddJsonFile("appsettings.local.json", optional: true); }).ConfigureServices((context, services) => { ConfigureServices(context.Configuration, services); }) .ConfigureLogging(logging => { // Add other loggers... }) .Build();
Going back to the first App.xaml.cs file, at line 8 we call HostBuilder.ConfigureService, that is responsible to add all our services in the .NET Core IoC Container. This method in turn calls our ConfigureServices implementation (lines 15-20), in which we register all the services used by the application in the exact same way of ASP. NET Core. We’ll complete this method in a moment, but for now let’s notice that we register also the MainWindow class (line 19). This is important because, in this way, the window itself becomes part of the Dependency Injection chain. It means that, after calling this method, at line 26-27 we can get it from the ServiceProvider and then show it. But, more important, it means that we can pass to the MainWindow constructor all the dependencies it needs, as we do for ASP.NET Core Controllers.
In the OnStartup override (lines 22-30), we call the StartAsync method to start the program (line 24): at this moment, it calls IHostedService.StartAsync on each implementation of IHostedService that it finds in the DI container (we don’t have any of this in our app, so in fact this method does nothing). Finally, as said before, at line 26-27 we get the MainWindow from the ServiceProvider and then show it.
As last step, in the OnExit method (lines 32-39), we call StopAsync to gracefully stop the host.
Even if the actual services aren’t yet registered, we can run the application and see that everything works as expected.
Now it’s time to complicate the things a bit. Let’s add a file named appsettings.json to the root folder of the project. Set its Build Action property to Content and Copy to Output Directory to Copy if newer:
{ "AppSettings": { "StringSetting": "Value", "IntegerSetting": 42, "BooleanSetting": true } }
This file is automatically loaded and made available to the application by the CreateDefaultBuilder method we talked before. Then, we create an AppSettings.cs file to hold configuration settings. This file will map the settings that we write in appsettings.json:
public class AppSettings { public string StringSetting { get; set; } public int IntegerSetting { get; set; } public bool BooleanSetting { get; set; } }
Moreover, create also a sample service with its interface:
public interface ISampleService { string GetCurrentDate(); } public class SampleService : ISampleService { public string GetCurrentDate() => DateTime.Now.ToLongDateString(); }
Now we must register these services in the IoC Container, as usual:
private void ConfigureServices(IConfiguration configuration, IServiceCollection services) { services.Configure<AppSettings> (configuration.GetSection(nameof(AppSettings))); services.AddScoped<ISampleService, SampleService>(); //... }
As said before, the MainWindow itself is in the IoC Container. So, when we get it from the Service Provider, it will automatically be injected with all the required services. So, we just need to modify its constructor:
public partial class MainWindow : Window { private readonly ISampleService sampleService; private readonly AppSettings settings; public MainWindow(ISampleService sampleService, IOptions<AppSettings> settings) { InitializeComponent(); this.sampleService = sampleService; this.settings = settings.Value; } // ... }
Running this code, we’ll obtain a result like the following:
You can download the sample app using the link below:
Oh, is MVVM dead on .NET Core 3.0?
Absolutely no 🙂 In fact, MVVM fits fine on this application model. I’ll write a post about this argument soon.
This is awesome. I had hoped that it would be possible to use the configuration and DI libraries in .NET Core 3.0 in a converted WPF application, so finding this made my day. I tried it, but it doesn’t appear to show anything. The shell shows up but nothing else gets rendered. I’ve registered Views and ViewModels but nothing seems to work. Any ideas?
Without seeing the code, it is difficult to figure out the issue. Can you can share it?
Hello, in the previous article https://marcominerva.wordpress.com/2019/03/06/using-net-core-3-0-dependency-injection-and-service-provider-with-wpf/ it was possible to configure Logging with ConfigureLogging method in the App constructor. How to do it in this version? I see that ConfigurationBuilder doesn’t have a way to do it.
You can configure Logging in the same way. Take a look at lines 11-14 in the second code snippet of the article
Thank you for your quick response! Actually the code I am using is from this ( https://marcominerva.wordpress.com/2019/05/21/a-simple-navigationservice-with-dependency-injection-for-wpf-on-net-core-3-0/ ) article. It was a great article which I followed to implement my WPF application. Should I replace ConfigurationBuilder() with HostBuilder() as in the current article?
Yes. From now on, you should follow the approach described in this article, as it is aligned to the final version of .NET Core 3.0
Thanks for your help. I managed to use this approach with navigation also. I have another question… which way to show MainWindow?
This
var navigationService = host.Services.GetRequiredService();
var task = navigationService.ShowAsync();
or this
var mainWindow = host.Services.GetRequiredService();
mainWindow.Show();
At first sight they do the same job. Are they equivalent?
As it is the Main Window of the application, you can safely use the second snippet.
Thank you! Great articles with the latest technologies and very quick reply from you Marco!
How to use MVVM pattern
I’ll write a post about using the MVVM Pattern as soon as possible!
Until then I have a very small POC application on github you may find helpful https://github.com/SirRufo/HostedWpf