.NET has build-in dependency injection (DI). It’s used primarily for ASP.NET Core apps, but there’s no reason not to use it with WinUI apps too.
To use DI you need to add package reference to the Microsoft.Extensions.DependencyInjection in the project file:
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.1" />
</ItemGroup>
To create IHostBuilder
we need to take control over application initialization. For this purpose add the following constraint to the project file:
<PropertyGroup>
<!-- Use our own Main entry point so we can control the IHostBuilder -->
<DefineConstants>DISABLE_XAML_GENERATED_MAIN</DefineConstants>
<!-- We use App class to place Main method -->
<StartupObject>DependencyInjection.App</StartupObject>
</PropertyGroup>
This constraint disables Main
method in the auto-generated App
class. You can check this class out by the following path: obj/x64/Debug/App.g.i.cs
.
To initialize IHostBuilder
add Main
static method to the App
class:
public static void Main(string[] args)
{
var builder = CreateBuilder(args);
var host = builder.Build();
host.Run();
}
CreateBuilder
method creates and initializes HostApplicationBuilder
class:
private static HostApplicationBuilder CreateBuilder(string[] args)
{
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton<IApp, App>();
builder.Services.AddHostedService<AppService>();
return builder;
}
This is all about initializing the dependency injection infrastructure. To run the application (or just to compile successfully), we need to implement the application lifecycle infrastructure.
An application lifecycle is controlled by AppService
service class. It implements IHostedService
:
internal class AppService : IHostedService
{
private readonly ILogger<AppService> logger;
private readonly IServiceProvider serviceProvider;
private readonly IHostApplicationLifetime appLifetime;
private IApp? app;
private Task? appTask;
public AppService(
ILogger<AppService> logger,
IServiceProvider serviceProvider,
IHostApplicationLifetime appLifetime)
{
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
this.serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider));
this.appLifetime = appLifetime ?? throw new ArgumentNullException(nameof(appLifetime));
}
public Task StartAsync(CancellationToken cancellationToken)
{
XamlCheckProcessRequirements();
WinRT.ComWrappersSupport.InitializeComWrappers();
appTask = Task.Factory
.StartNew(() => Application.Start(InitAndStartApp))
.ContinueWith(_ => OnAppClosed());
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
app?.Exit();
return Task.CompletedTask;
}
private void InitAndStartApp(ApplicationInitializationCallbackParams p)
{
try
{
var context = new DispatcherQueueSynchronizationContext(DispatcherQueue.GetForCurrentThread());
SynchronizationContext.SetSynchronizationContext(context);
app = serviceProvider.GetRequiredService<IApp>();
app.UnhandledException += OnAppOnUnhandledException;
}
catch (Exception ex)
{
logger.LogError(ex, "An error occurred while initializing the application");
}
}
private void OnAppClosed()
{
appTask = null;
if (app != null)
{
app.UnhandledException -= OnAppOnUnhandledException;
app = null;
}
appLifetime.StopApplication();
}
private void OnAppOnUnhandledException(object sender, UnhandledExceptionEventArgs args)
{
logger.LogError(args.Exception, "An unhandled application exception occured");
args.Handled = false;
}
[DllImport("microsoft.ui.xaml.dll")]
private static extern void XamlCheckProcessRequirements();
}
When the program starts, the StartAsync
method is called. This method initializes the WinUI infrastructure and starts the application. Start
method blocks the thread until the application is exitted. Therefore, we run it in a task with continuation to call the OnAppClosed
method. OnAppClosed
method is used to stop the service when application is exitted.
The InitAndStartApp
method is called from the UI thread, so we create an instance of the App
class there. How? App
class implements the following IApp
interface:
public interface IApp
{
void Exit();
event UnhandledExceptionEventHandler UnhandledException;
}
When we request the IApp
interface, instance of the App
class is created. IApp
interface provides Exit
method to terminate the application (it’s used in StopAsync
method) and UnhandledException
event to log exceptions (OnAppOnUnhandledException
method).
That’s it. We implemented DI infrastructure and the application lifecycle service. From this moment we can register services in the CreateBuilder
method and refer to them in a class constructor.
As example, let’s change initialization of the main window from creating via constructor to initialize it with DI.
To retrieve App
interface in MainWindow
class we need to add parameterized constructor as following:
private IApp app;
public MainWindow(IApp app)
{
this.app = app ?? throw new ArgumentNullException(nameof(app));
InitializeComponent();
}
Then register the class in CreateBuilder
method:
// We use a singleton because the application has only one main window.
builder.Services.AddSingleton<MainWindow>();
OnLaunched
method in App
class looks like the following:
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
base.OnLaunched(args);
mainWindow = serviceProvider.GetRequiredService<MainWindow>();
// The application is exitted when the main window closes.
mainWindow.Closed += OnMainWindowClosed;
mainWindow.AppWindow.Show(true);
}
private void OnMainWindowClosed(object sender, WindowEventArgs args)
{
Exit();
}
The demo project is available here.
Learn more about .NET dependency injection.