I like very much the idea of a .NET Generic Host in the MSDN documentation:

An object that encapsulates an app’s resources, such as:

  • Dependency injection (DI)
  • Logging
  • Configuration
  • IHostedService implementations

The origin of .NET Generic Host is Microsoft.Extensions, a set of .NET APIs for commonly used programming patterns and utilities, such as dependency injection, logging, and application configuration.

Why do I like this idea? Because it means that whatever application you are building using .NET Generic Host, it will always bring all of those services freely in a consistent manner. The application can be an ASP.NET Core application, which is where the idea of Web Host emerged and was refined into a Generic Host.

But, why couldn’t a WPF application be build using it? The current WPF template doesn’t use .NET Generic Host! Never the less, let’s modify the code generated by the WPF template to leverage it.

The goal is to have a tiny .NET Core 3.0 WPF application demonstrating each of Dependency injection (DI), Logging and Configuration.

Default WPF template

We need to modify the App.xaml so that it doesn’t use StartupUri. Which sets the UI; MainWindow.xaml, that is automatically shown when an application starts. Our goal is to be able to create the Window ourself, using the IOC container, so that we can inject services!

We replace StartupUri by Startup calling Application_Startup and when we are at it, we also add Exit and Application_Exit. This will let us know when the application starts/stops so that we can start/stop the host.

App.xaml
1
2
3
4
5
6
7
8
<Application x:Class="wpfGenericHost.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:wpfGenericHost"
Startup="Application_Startup"
Exit="Application_Exit">
<Application.Resources />
</Application>
App.xaml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public partial class App : Application
{
public App()
{
}

private void Application_Startup(object sender, StartupEventArgs e)
{
}

private void Application_Exit(object sender, ExitEventArgs e)
{
}
}

The App constructor seems to be the right place to build our new Host. Application_Startup the place to start the host and Application_Exit the place to stop the host.

App.xaml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public partial class App : Application
{
private IHost _host;

public App()
{
_host = new HostBuilder().Build();
}

private async void Application_Startup(object sender, StartupEventArgs e)
{
await _host.StartAsync();
}

private async void Application_Exit(object sender, ExitEventArgs e)
{
using (_host)
{
await _host.StopAsync(TimeSpan.FromSeconds(5));
}
}
}

Dependency injection (DI)

A first capability that .NET Generic Host is bringing is Dependency injection. We can inject some dependencies to the MainWindow as we delegate it’s creation to the IOC container in Application_Startup. In this example, we are injecting a simple interface called ITextService with one method GetText().

In a real application, we definitely would be able to inject a View Model like MainWindowViewModel which would itself have other dependencies provided by the IOC container.

MainWindow.xaml.cs
1
2
3
4
5
6
7
8
9
public partial class MainWindow : Window
{
public MainWindow(ITextService textService)
{
InitializeComponent();

Label.Content = textService.GetText();
}
}
App.xaml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public partial class App : Application
{
private IHost _host;

public App()
{
_host = new HostBuilder()
.ConfigureServices((context, services) =>
{
services.AddSingleton<ITextService, TextService>();
services.AddSingleton<MainWindow>();
})
.Build();
}

private async void Application_Startup(object sender, StartupEventArgs e)
{
await _host.StartAsync();

var mainWindow = _host.Services.GetService<MainWindow>();
mainWindow.Show();
}

...
}

Configuration

The second capability of .NET Generic Host we want to explore is Configuration. We want to build the concrete implementation of ITextService with a dependency on an external configuration to get the text to display in the main window.

The configuration is handled by a concrete class called Settings and the appsettings.json configuration file. Both have a property called Text.

Settings.cs
1
2
3
4
public class Settings
{
public string Text { get; set; }
}
appsettings.json
1
2
3
{
"Text": "Hello WPF .NET Core 3.0\nfrom .NET Generic Host!"
}
TextService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class TextService : ITextService
{
private string _text;

public TextService(IOptions<Settings> options)
{
_text = options.Value.Text;
}

public string GetText()
{
return _text;
}
}

We register the Settings class in the IOC container so that it is resolved and injected as IOptions in our concrete class TextService.

App.xaml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public partial class App : Application
{
private IHost _host;
private readonly Settings _settings;


public App()
{
_host = new HostBuilder()
.ConfigureAppConfiguration((context, configurationBuilder) =>
{
configurationBuilder.SetBasePath(context.HostingEnvironment.ContentRootPath);
configurationBuilder.AddJsonFile("appsettings.json", optional: false);
})
.ConfigureServices((context, services) =>
{
services.Configure<Settings>(context.Configuration);

services.AddSingleton<ITextService, TextService>();
services.AddSingleton<MainWindow>();
})
.Build();
}
...
}

Logging

The final capability of .NET Generic Host we want to have a look at is Logging. In our simple example, we will just add the possibility to output logs to the Console. Again, we are injecting an ILogger into our concrete class TextService to be able to write a piece of information to our logs.

TextService.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class TextService : ITextService
{
private string _text;

public TextService(IOptions<Settings> options, ILogger<TextService> logger)
{
_text = options.Value.Text;

logger.LogInformation($"Text read from settings: '{options.Value.Text}'");
}

public string GetText()
{
return _text;
}
}
App.xaml.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public partial class App : Application
{
private IHost _host;

public App()
{
_host = new HostBuilder()
.ConfigureAppConfiguration((context, configurationBuilder) =>
{
configurationBuilder.SetBasePath(context.HostingEnvironment.ContentRootPath);
configurationBuilder.AddJsonFile("appsettings.json", optional: false);
})
.ConfigureServices((context, services) =>
{
services.Configure<Settings>(context.Configuration);

services.AddSingleton<ITextService, TextService>();
services.AddSingleton<MainWindow>();
})
.ConfigureLogging(logging =>
{
logging.AddConsole();
})
.Build();
}
...
}

Result

We can run the application and see that MainWindow is created using the TextService which read the Text property of appsettings.json and our WPF application correctly displays the text.

WPF and .NET Generic Host with .NET Core 3.0

And the output in the log

Conclusion

We have seen all the benefits the .NET Generic Host is bringing. It was easy to integrate into WPF applications, even if Microsoft is not currently providing a .NET Core template for that particular usage. But it seems that the idea is open to discussion, you can read more about it on Github issue “Make Future WPF IoC Friendly #499“.

You can get all the code on GitHub