Resolving ASP.NET Core Startup class from the DI container

In ASP.NET Core, the most common setup is characterized by having a standalone Startup class, responsible for bootstrapping the services needed by your application, as well as setting up the application pipeline.

What most users of ASP.NET Core do not realize, is that at runtime, the Startup instance is actually being resolved from the DI container. This allows you to control some interesting aspects of how your application is bootstrapped, which can be really important i.e. in integration testing scenarios.

Let’s have a look.

The IStartup interface

Pretty much everyone working on ASP.NET Core applications is used to writing the application startup code in the following manner:

This is also how the templates generate the code – a WebHostBuilder pointed at a Startup class which is entirely convention based (no type safety enforced via interfaces or base classes). This approach is actually a carry over from the OWIN/Katana days that pre-dates ASP.NET Core.

However, turns out the startup code can also be written as an implementation of an IStartup interface. This little known interface is publicly available in the Microsoft.AspNetCore.Hosting namespace.

The way WebHostBuilder approaches the initialization of your application is that it will pick up the candidate Startup type (the one configured against the WebHostBuilder) and check if it implements IStartup interface. If that’s the case, it will just use it straight up. Otherwise, it uses convention based startup logic, where the candidate Startup is inspected using reflection and the relevant configuration methods are discovered. Then delegates representing them are stuffed into a type called ConventionBasedStartup, which ends up doing the thing your Startup never did in the first place – implementing IStartup – and that instance is then registered in the DI.

All of that logic can be viewed in the source code of the WebHostBuilder.

So what does it take for us to use IStartup? IStartup doesn’t perfectly match our original Startup class, but we could very easily rewrite our original code to fit into the constraints of IStartup by doing just 2 or 3 small changes. This is shown below:

In order to conform to the contract defined by IStartup we just need to build the IServiceProvider manually at the end of the ConfigureServices method, and drop the injection of dependencies into the Configure method. Instead, we have to resolve the necessary dependencies from the container by hand. And that’s it.

On a side note, there is also an abstract StartupBase class which you could subclass to achieve the same thing. If we use it, our implementation would look identical though.

The benefits

There are two main benefits of writing your startup code this way.

First, you will avoid runtime errors if your Startup class cannot be parsed into the convention based startup approach (for example if you had a typo in the method name). So no more unexpected runtime crashes – as the compiler would help us avoid them.

Second, it gives you interesting possibilities in mocking/overriding both IHostingEnvironment and the configuration – something that is rather difficult when using convention based startup.

Let’s consider a simple integration test scenario – because, as we all know ASP.NET Core is testable using the elegant in memory infrastructure.

A typical in-memory testing setup is shown below:

With such a setup, the testing infrastructure will wire in everything configured in your Startup and allow you to use HttpClient to interact with the in memory instance of your ASP.NET Core application.

Now imagine the following – what if your application uses some configuration settings that you’d like to override in those tests. This could be anything – for example a DB connection string, that should be changed to SQLite in memory string in case you are running integration tests?

Surely, there are plenty of ways of achieving this in ASP.NET Core – you could i.e. use environment variables to pass in the necessary values.

That said, I find it very useful to be able to tinker with configuration directly in the tests and inject things on demand directly from code level. This becomes possible when using startup classes that implement IStartup.

The example is shown:

The reason why this is possible, is that we can now interact with the instance of our startup class, before it’s consumed by the ASP.NET Core web host infrastructure, rather than relying on the convention based startup.

Hope this helps!

Be Sociable, Share!

  • Marcelo Volmaro

    Your ctor needs an IHostingEnvironment arg, but in the example you don’t provide one. How do you get access to IHostingEnvironment before anything else? It is needed to build most of the standard configurations (SetBasePath, AddJsonFiles, etc).

  • Marcelo Volmaro

    How do I get access to IHostingEnvironment in this new startup class?