One of the primary frameworks I used to use for DI was Unity Container. It’s a wonderful project but with support from Microsoft for host.services I’ve abandoned Unity Container in favor of the Microsoft solution. The main reason is that I see it everywhere. Maybe it’s the hot, new thing but using it and asking questions about it is just easy, if for no other reason than it is everywhere.
With that in mind I want to show you how to add support easily to your WPF application for host service so it will manage all your DI needs. The attached solution has quite a bit in it, beyond the scope of this application, but it will show you DI working.
Start by adding the following two nuget packages to your application.
Microsoft.Extensions.Hosting
Microsoft.Extensions.Hosting.AbstractionsAbstractions
PS D:\vs\BasicWPF\BasicWPF> dotnet add package Microsoft.Extensions.Hosting
PS D:\vs\BasicWPF\BasicWPF> dotnet add package Microsoft.Extensions.Hosting.Abstractions
Next is the tricky part. We want to replace how the app starts so we can create our host service on startup. To do that we have to circumvent a lot of the auto-wiring a basic WPF project has done for us.
Open up App.xaml.cs and replace the class decoration with:
public partial class App : Application
{
[STAThread]
public static void Main(string[] args)
{
using IHost host = CreateHostBuilder(args).Build();
host.Start();
App app = new();
app.InitializeComponent();
app.MainWindow = host.Services.GetRequiredService<MainWindow>();
app.MainWindow.Visibility = Visibility.Visible;
app.Run();
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureAppConfiguration((hostBuilderContext, configurationBuilder)
=> configurationBuilder.AddUserSecrets(typeof(App).Assembly))
.ConfigureServices((hostContext, services) =>
{
services.AddSingleton<MainWindow>();
//services.AddSingleton<MainWindowViewModel>();
});
}
And add the following using statements:
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
The [STAThread] (STA here means single-threaded apartment) tells the application that this has to run under a single thread. There can be only one main Dispatcher thread to manage the app. Now, when running the app you can throw threads around but if you interact with any UI elements you have to do it within the scope of the Dispatcher. Next in Main, it creates the host services and spins up the app.
You can see within ConfigureServices of the CreateHostBuilder is where you define your objects you want managed for DI. I’ve started you off with the MainWindow and the potential to add the MainWindowViewModel.
Next, in App.xaml you can see it specifies that when starting up (StartupUri), it should start with the MainWindow.Xaml.
<Application x:Class="BasicWPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BasicWPF"
StartupUri="MainWindow.xaml">
<Application.Resources>
</Application.Resources>
</Application>
Remove that StartupUri property as we want to tell the compiler we really want to start with all that code we added to the App.xaml.cs.
<Application x:Class="BasicWPF.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:BasicWPF">
<Application.Resources>
</Application.Resources>
</Application>
We’re getting close. So we need to next tell the compiler where to start the application. Now that we’ve removed the StartupURI the compiler may get confused and say:
Program has more than one entry point defined. Compile with /main to specify the type that contains the entry point.
There are two parts to solving this problem. One, we need to tell the compiler where to start and Two, we need to tell the compiler that it really shouldn’t consider App an application but really a regular page in the application.
Open your project file and it should looks something like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
</ItemGroup>
</Project>
To the property group we want to specify how to start up. So I’ll add a Startup property that tells it, “hey, call the main in my <PROJECT NAME>.App. The project I’m using while writing this is called, “BasicWPF” so I’ll want to add:
<StartupObject>BasicWPF.App</StartupObject>
Finally, we need to make sure we take care so that the compiler knows to treat App as a regular page in the application, since we’re managing it on our own. Underneath the PropertyGroup, we’ll add:
<ItemGroup>
<ApplicationDefinition Remove="App.xaml" />
<Page Include="App.xaml" />
</ItemGroup>
My final project file looks like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>net8.0-windows</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<UseWPF>true</UseWPF>
<StartupObject>BasicWPF.App</StartupObject>
</PropertyGroup>
<ItemGroup>
<ApplicationDefinition Remove="App.xaml" />
<Page Include="App.xaml" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
</ItemGroup>
</Project>
So why does any of that matter? Well, it gets the framework in place for DI and integrating into CommunityToolkit.MVVM with that DI.
Up next I’ll lightly touch on CommunityToolkit.MVVM and using DI with the CommunityToolkit.MVVM.
Thanks,
Brian
One thought on “Adding Host to WPF for Dependency Injection”