.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.