Running a console application using a .NET Generic Host is simple enough; doing the same with a WPF application is another thing altogether.
A WPF app, being a UI application, has its own internal “hosting” and lifetime mechanisms that are incompatible with the out-of-the-box .NET Generic Host.
But that doesn’t mean it cannot be done; and, in fact, it can be done relatively cleanly.
Generic Host: Using vs Running
This article demonstrates how to set up a Generic Host to run a WPF application. But you might not need to do this.
You can still use a Generic Host and take advantage of many of its features, such as dependency injection, configuration, and logging, all without needing it to run your application.
If that’s all you require, simply start the host and run your WPF application. Don’t run the host.
App.xaml.cs – Using the host without running it
[STAThread]
public static void Main()
{
var builder = Host.CreateApplicationBuilder();
builder.Services
.AddSingleton<MainWindow>()
.AddSingleton<MainViewModel>()
.AddTransient<FooViewModel>()
.AddTransient<BarViewModel>(); //etc.
IHost host = builder.Build();
host.Start();
var app = new App();
app.InitializeComponent();
app.MainWindow = host.Services.GetRequiredService<MainWindow>();
app.Run();
}
All you need is something similar to the code above, and you’ll be able to inject view models into views, loggers into view models, etc. You don’t need to do anything else described in this article.
Conversely, if you want the host to be in charge of the application’s lifetime (perhaps the application is meant to run in the background, whether a UI is currently displayed or not), or to take advantage of lifetime-related features such as IHostedService, then read on to find out how.
Running the Host: What’s the Problem?
The issue with running a host and a WPF application quickly becomes apparent.
To run the host, we want to call IHost.Run instead of IHost.Start. Doing so will cause the current thread to block until the host shuts down.
Consider the following WPF initialization code we were using previously:
var app = new App();
app.InitializeComponent();
app.MainWindow = host.Services.GetRequiredService<MainWindow>();
app.Run();
This code will, of course, never run, because IHost.Run blocks. OK, resolving that is simple enough: we’ll move it to some place that will execute after the host has started.
A perfect place for that is an IHostedService. We can start up our App instance in the service’s StartAsync implementation. Two big problems with that, however:
App.Run, likeIHost.Runalso blocks, which will cause theIHostedServicenever to return and lock up the host’s launch.- The WPF application, being a UI and all, can only be run on a STA (single-threaded apartment) thread.
Both of these issues will prevent the application from successfully launching.
Using a BackgroundService Instead?
Instead of our own IHostedService implementation, we could derive from BackgroundService, which offers the ExecuteAsync override.
Unlike a basic IHostedService.StartAsync implementation, ExecuteAsync (which will be called at host startup), can block as long as it wants without interfering with the host’s launch.
Unfortunately, we will run smack dab into our second issue: the STA thread requirement. ExecuteAsync is invoked via a Task.Run call (which is not awaited, but is still tracked by assigning the Task to a field in the service instance).
This will spawn a Task using the default TaskScheduler, which will not be on an STA thread.
As you can see, a little work is required here.
Creating a UI Context
The rest of the code presented in this article is part of the BadEcho.Presentation framework, where you can find the most up-to-date versions of said code.
Let’s first create a service/construct that provides a context in which UI components can execute:
UserInterfaceContext.cs
/// <summary>
/// Provides a context for hosting UI components.
/// </summary>
public sealed class UserInterfaceContext
{
private const int HRESULT_DISPATCHER_SHUTDOWN = unchecked((int)0x80131509);
private readonly Action _uiFunction;
private readonly Thread _uiThread;
private Dispatcher? _dispatcher;
/// <summary>
/// Initializes a new instance of the <see cref="UserInterfaceContext"/> class.
/// </summary>
/// <param name="uiFunction">The function to run in a UI-appropriate context.</param>
public UserInterfaceContext(Action uiFunction)
{
Require.NotNull(uiFunction, nameof(uiFunction));
_uiFunction = uiFunction;
_uiThread = new Thread(UIFunctionRunner)
{
IsBackground = true
};
_uiThread.SetApartmentState(ApartmentState.STA);
}
/// <summary>
/// Occurs when this context's UI-related functionality has finished executing.
/// </summary>
public event EventHandler<EventArgs>? Completed;
/// <summary>
/// Gets a value indicating if this context's UI-related functionality is executing.
/// </summary>
public bool IsExecuting
{ get; private set; }
/// <summary>
/// Gets the <see cref="System.Windows.Threading.Dispatcher"/> instance running within this
/// context.
/// </summary>
public Dispatcher Dispatcher
=> _dispatcher ?? throw new InvalidOperationException(Strings.ContextHasNoDispatcher);
/// <summary>
/// Starts the execution of UI-related functionality.
/// </summary>
public void Start()
=> _uiThread.Start();
/// <summary>
/// Blocks the calling thread until the UI thread represented by this context exits.
/// </summary>
public void Join()
=> _uiThread.Join();
private void UIFunctionRunner()
{
try
{
_dispatcher = Dispatcher.CurrentDispatcher;
var context = new DispatcherSynchronizationContext(_dispatcher);
SynchronizationContext.SetSynchronizationContext(context);
IsExecuting = true;
_uiFunction();
IsExecuting = false;
Completed?.Invoke(this, EventArgs.Empty);
}
catch (InvalidOperationException invalidEx)
{
if (invalidEx.HResult != HRESULT_DISPATCHER_SHUTDOWN)
throw;
Logger.Debug(Strings.ContextDispatcherManuallyShutdown);
}
}
}
This can be used to run any WPF component (such as a Window), ensuring it’s hosted on an STA thread. For our purposes, we’ll use it to start our WPF application’s App instance.
The method responsible for invoking App.Run will be passed as an argument to the UserInterfaceContext constructor.
Application Runner UI Function
The Bad Echo Presentation framework includes a class, UserInterface, that provides several management-related functions for WPF applications and objects.
Of note to us is the following function:
UserInterface.cs
/// <summary>
/// Runs the specified application in a context appropriate for hosting UI components.
/// </summary>
/// <typeparam name="TApplication">
/// The type of <see cref="Application"/> to instantiate and run in the context.
/// </typeparam>
/// <param name="appFactory">A factory method that creates the application.</param>
/// <param name="configureApp">A method that configures the application.</param>
/// <returns>The context hosting the application.</returns>
public static UserInterfaceContext RunApplication<TApplication>(Func<TApplication> appFactory,
Action<TApplication> configureApp)
where TApplication : Application
{
Require.NotNull(appFactory, nameof(appFactory));
Require.NotNull(configureApp, nameof(configureApp));
return new UserInterfaceContext(() =>
{
TApplication app = appFactory();
configureApp(app);
app.Run();
});
}
You could certainly simplify things by integrating this code directly into UserInterfaceContext. This code comes from my library and is used for additional things unrelated to this article.
Starting Things Up with a Hosted Service
Our WPF application cannot begin running until the host has started. The simplest way to hook into a host’s lifecycle events is via an IHostedService, so let’s create an implementation that starts up our UI context once the host starts.
ApplicationHostedService.cs
/// <summary>
/// Provides a hosted service that runs a WPF application.
/// </summary>
internal sealed class ApplicationHostedService : IHostedService
{
private readonly UserInterfaceContext _context;
private readonly IHostApplicationLifetime _lifetime;
/// <summary>
/// Initializes a new instance of the <see cref="ApplicationHostedService"/> class.
/// </summary>
public ApplicationHostedService(UserInterfaceContext context, IHostApplicationLifetime lifetime)
{
_context = context;
_lifetime = lifetime;
_context.Completed += HandleContextCompleted;
}
/// <inheritdoc/>
public Task StartAsync(CancellationToken cancellationToken)
{
_context.Start();
return Task.CompletedTask;
}
/// <inheritdoc/>
public async Task StopAsync(CancellationToken cancellationToken)
{
if (!_context.IsExecuting)
return;
await _context.Dispatcher.InvokeAsync(() => Application.Current?.Shutdown());
}
private void HandleContextCompleted(object? sender, EventArgs e)
{
_lifetime.StopApplication();
}
}
This hosted service starts a UserInterfaceContext, which spawns our WPF application and tears it down when our host shuts down.
This service will also stop our host if the WPF application has been shut down. This occurs because a WPF Application object cannot simply be restarted if it has stopped.
While this may sound like the WPF application will always control the host’s lifetime (as opposed to the other way around), the code in the next section will ensure that is not the case (when desired).
Extending the Service Collection
The last piece of our puzzle is a set of IServiceCollection extension methods that will bring all our previous code into play.
ServiceCollectionExtensions.cs
/// <summary>
/// Provides extensions methods for setting up services that integrate the Bad Echo Presentation
/// framework and Windows Presentation Foundation with a hosted application.
/// </summary>
public static class ServiceCollectionExtensions
{
/// <summary>
/// Adds services that enable support for hosting the specified Windows Presentation Foundation
/// application.
/// </summary>
/// <typeparam name="TApplication">The type of <see cref="Application"/> to host.</typeparam>
/// <param name="services">
/// The <see cref="IServiceCollection"/> instance to add services to.
/// </param>
/// <returns>
/// The current <see cref="IServiceCollection"/> instance so that additional calls
/// can be chained.
/// </returns>
/// <remarks>
/// This method will configure any running application host so that its lifetime is shared with
/// the WPF application. When the last window is closed, the application host will stop.
/// </remarks>
public static IServiceCollection AddApplication<TApplication>(this IServiceCollection services)
where TApplication : Application
{
return services.AddApplication<TApplication>(true);
}
/// <summary>
/// Adds services that enable support for hosting the specified Windows Presentation Foundation
/// application.
/// </summary>
/// <typeparam name="TApplication">The type of <see cref="Application"/> to host.</typeparam>
/// <param name="services">
/// The <see cref="IServiceCollection"/> instance to add services to.
/// </param>
/// <param name="useWpfLifetime">
/// Value indicating if the lifetime of the host is tied to the lifetime of the WPF application.
/// </param>
/// <returns>
/// The current <see cref="IServiceCollection"/> instance so that additional calls
/// can be chained.
/// </returns>
/// <remarks>
/// If <paramref name="useWpfLifetime"/> is <c>true</c>, the WPF application will shut down when
/// the last window is closed, and any running application host will stop.
/// </remarks>
public static IServiceCollection AddApplication<TApplication>(this IServiceCollection services,
bool useWpfLifetime)
where TApplication : Application
{
Require.NotNull(services, nameof(services));
var shutdownMode = useWpfLifetime
? ShutdownMode.OnLastWindowClose
: ShutdownMode.OnExplicitShutdown;
services.AddSingleton<Application, TApplication>();
services.AddSingleton(CreateContext);
services.AddHostedService<ApplicationHostedService>();
return services;
UserInterfaceContext CreateContext(IServiceProvider serviceProvider)
{
return UserInterface.RunApplication(serviceProvider.GetRequiredService<Application>,
app => app.ShutdownMode = shutdownMode);
}
}
}
Calling this will make the dependency injection container responsible for initializing the Application object and running it on a user interface context thread.
If useWpfLifetime is false, the host controls the lifetime of the overall application; otherwise, the WPF application does. This is achieved by setting the appropriate ShutdownMode for the Application object.
When the shutdown mode is OnExplicitShutdown, the Application will only shut down on the call to Application.Shutdown is made by our ApplicationHostedService when the host shuts down.
Additional Shutdown Protections
None of this prevents code within the WPF application from calling Application.Shutdown itself. If that happens, then ultimately the WPF application is in control of the host’s lifetime, since ApplicationHostedService will call StopApplication when the context thread is done running.
Because the WPF application is our own code, it’s up to us to ensure that doesn’t happen. But if we wanted greater assurance that the host is in complete control of its lifetime, we could add code that prevents ApplicationHostedService from calling StopApplication if the context thread is no longer running during a host shutdown.
However, doing that requires that our context thread be restartable, which, at the moment, I don’t feel is worth the effort or additional complexity.
Example Usage
We can now easily set up our WPF application to be hosted by a Generic Host:
Program.cs
var builder = Host.CreateApplicationBuilder();
builder.Services
.AddApplication<App>()
.AddSingleton<MainWindow>()
.AddSingleton<MainViewModel>()
.AddSingleton<NavigationPaneViewModel>()
.AddTransient<HomeViewModel>()
.AddTransient<ActionsViewModel>();
var host = builder.Build();
host.Run();
And that’s it! Nice and easy.
One Additional Caveat
This pertains more to the dependency injection container than to the Generic Host, but I thought it’d be helpful to address one issue you may run into.
Because the dependency injection container now creates the Application object, we’ll want to take advantage of that and have our main window (which will itself be injected with its own view model and other dependencies) as well as any other desired services injected into it.
To do that, we’d change our constructor to look like the following:
App.xaml.cs
/// <summary>
/// Initializes a new instance of the <see cref="App"/> class.
/// </summary>
public App(MainWindow window, ILogger<Application> logger)
{
_logger = logger;
InitializeComponent();
window.InitializeComponent();
window.Show();
}
We would, of course, also remove the StartupUri from the Application element in App.xaml.
However, errors will arise if we attempt to compile our project. They will originate from an MSBuild task that runs during WPF application compilation.
This task is responsible for creating the application entry point, and will run even though our own entry point in Program.cs (where we set up the host) will ultimately supersede it.
This MSBuild task generates code that initializes our Application class, and it expects the Application derivative to have a default constructor, which it normally does.
So, to rectify this issue, don’t forget to add a private default constructor to App.xaml.cs:
App.xaml.cs
private App()
{
InitializeComponent();
}
The call to InitializeComponent is required for the XAML designer to work properly at design time.

