DEV Community

Cover image for Extending an ASP.NET Core App with External Middleware Using "Hosting Startup"
jsakamoto
jsakamoto

Posted on

Extending an ASP.NET Core App with External Middleware Using "Hosting Startup"

Introduction

ASP.NET Core has a feature called "Hosting Startup". It allows us to run additional code when an ASP.NET Core Web application starts up. We can also add middleware through this feature.

Using Hosting Startup, we can add middleware to an already-built ASP.NET Core Web application without rebuilding it. The middleware is implemented in a separate assembly that is built independently.

The official documentation about Hosting Startup is available here.

https://learn.microsoft.com/en-us/aspnet/core/fundamentals/host/platform-specific-configuration

How Hosting Startup Works

Let me explain how Hosting Startup works in more detail.

  1. Prepare a pre-built assembly (a Hosting Startup assembly) that implements the IHostingStartup interface and is marked with the HostingStartup assembly attribute.
  2. Set the environment variable ASPNETCORE_HOSTINGSTARTUPASSEMBLIES to the name of the Hosting Startup assembly created in step 1.
  3. Run the separately-built ASP.NET Core application.
  4. When the ASP.NET Core application starts up, the IHostingStartup.Configure(IWebHostBuilder builder) method in the Hosting Startup assembly (specified by the environment variable) is called. Inside this method, we can use the IWebHostBuilder object passed as an argument to register additional services to the DI container and perform other initialization tasks.

To add middleware to the ASP.NET Core HTTP request pipeline, we need a few more steps.

  1. Inside the IHostingStartup.Configure(IWebHostBuilder) method of the Hosting Startup assembly, register a class that implements IStartupFilter with the DI container.
  2. After the host is built in the main ASP.NET Core application, when the HTTP request pipeline is being constructed, the IStartupFilter class registered above is instantiated and its IStartupFilter.Configure(Action<IApplicationBuilder> next) method is called. This method returns a function that registers middleware.
  3. The "function that registers middleware" returned from the above method is then executed. This is how the middleware specified in the Hosting Startup assembly gets added to the target ASP.NET Core application.

Implementing a Hosting Startup Assembly and Seeing It in Action

Let's actually implement a Hosting Startup assembly that adds middleware and see it load when an ASP.NET Core Web application starts up.

1. Prepare a Simple ASP.NET Core Web Application

First, let's prepare a simple ASP.NET Core Web application. Open a terminal and run the following dotnet commands to create a new ASP.NET Core Web application and run it.

$ dotnet new web -n MyAspNetCoreApp -f net10.0
$ dotnet build ./MyAspNetCoreApp
$ dotnet ./MyAspNetCoreApp/bin/Debug/net10.0/MyAspNetCoreApp.dll
Enter fullscreen mode Exit fullscreen mode

Open another terminal and check if it works using the curl command.

$ curl -i http://localhost:5000/
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Sat, 14 Feb 2026 02:17:10 GMT
Server: Kestrel
Transfer-Encoding: chunked

Hello World!
Enter fullscreen mode Exit fullscreen mode

Looks good! Now, press Ctrl + C to stop the running ASP.NET Core Web application for now.

2. Implement the Hosting Startup Assembly

Next, let's implement the Hosting Startup assembly. A Hosting Startup assembly is just a regular class library. So run the following dotnet commands to create a new class library project. Additionally, add the Microsoft.AspNetCore.Hosting package to the project, since we need the types for implementing a Hosting Startup assembly. Then open it in VSCode and start implementing.

$ dotnet new classlib -n MyHostingStartup -f net10.0
$ dotnet add package Microsoft.AspNetCore.Hosting --project ./MyHostingStartup
$ code ./MyHostingStartup
Enter fullscreen mode Exit fullscreen mode

We will work in reverse order from how things are called at runtime. Let's first implement the IStartupFilter class, which "returns a function that registers middleware".
Add a file named "MyStartupFilter.cs" to the Hosting Startup project and write the following code.

// MyHostingStartup/MyStartupFilter.cs
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;

public class MyStartupFilter : IStartupFilter
{
    public Action<IApplicationBuilder> Configure(Action<IApplicationBuilder> next)
    {
        // Return a function that adds middleware
        return app =>
        {
            app.Use(async (context, nextMiddleware) =>
            {
                // In this sample, add a custom header to the HTTP response
                context.Response.Headers.Add("X-Custom-Header", "MyCustomValue");

                // Call the next middleware in the HTTP request pipeline
                await nextMiddleware.Invoke();
            });

            // Call the next middleware registration process
            next(app);
        };
    }
}
Enter fullscreen mode Exit fullscreen mode

In this implementation, as an example, the middleware adds a response header called "X-Custom-Header" to every HTTP response.

Next, add a class that implements the IHostingStartup interface. This class is called when the ASP.NET Core Web application starts up, and it registers the IStartupFilter class above with the DI container. Add a file named "MyStartup.cs" to the project and write the following code.

// MyHostingStartup/MyStartup.cs
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;

[assembly: HostingStartup(typeof(MyStartup))]

public class MyStartup : IHostingStartup
{
    public void Configure(IWebHostBuilder builder)
    {
        // Register IStartupFilter to the service collection
        builder.ConfigureServices(services =>
        {
            services.AddSingleton<IStartupFilter, MyStartupFilter>();
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

That is all the source code we need. Let's build it with the dotnet command.

$ dotnet build ./MyHostingStartup
Enter fullscreen mode Exit fullscreen mode

3. Run the ASP.NET Core Web Application with the Hosting Startup Assembly

Now, let's set the environment variable ASPNETCORE_HOSTINGSTARTUPASSEMBLIES to the assembly name "MyHostingStartup" that we just built.

$ export ASPNETCORE_HOSTINGSTARTUPASSEMBLIES=MyHostingStartup
Enter fullscreen mode Exit fullscreen mode

If you are using PowerShell, run the following instead.

PS> $env:ASPNETCORE_HOSTINGSTARTUPASSEMBLIES="MyHostingStartup"
Enter fullscreen mode Exit fullscreen mode

With this setting in place, let's run the ASP.NET Core Web application that we created earlier again.

$ dotnet ./MyAspNetCoreApp/bin/Debug/net10.0/MyAspNetCoreApp.dll
Enter fullscreen mode Exit fullscreen mode

But unfortunately, it does not start successfully. We get an error saying "the assembly 'MyHostingStartup' was not found", as shown below.

info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5000
crit: Microsoft.AspNetCore.Hosting.Diagnostics[11]
      Hosting startup assembly exception
      System.InvalidOperationException: Startup assembly MyHostingStartup failed to execute. See the inner exception for more details.
       ---> System.IO.FileNotFoundException: Could not load file or assembly 'MyHostingStartup, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified.
      File name: 'MyHostingStartup, Culture=neutral, PublicKeyToken=null'
         at System.Reflection.RuntimeAssembly.InternalLoad(AssemblyName assemblyName, StackCrawlMark& stackMark, AssemblyLoadContext assemblyLoadContext, RuntimeAssembly requestingAssembly, Boolean throwOnFileNotFound)
         at System.Reflection.Assembly.Load(AssemblyName assemblyRef)
         at Microsoft.AspNetCore.Hosting.GenericWebHostBuilder.ExecuteHostingStartups()
         --- End of inner exception stack trace ---
Enter fullscreen mode Exit fullscreen mode

Actually, this is expected. The .NET runtime does not have enough information to determine where to load the assembly MyHostingStartup.dll, so it fails with a "file not found" error.

4. Make the Hosting Startup Assembly Loadable by Specifying the Dependency

So, we need to add MyHostingStartup.dll as a dependency of the ASP.NET Core Web application and tell the runtime where to find it (so it can be resolved to an absolute path). To do this, we need to take care of a couple of things.

First, to make things simple, let's place the Hosting Startup assembly in the same folder as the ASP.NET Core Web application assembly. We can either copy the built Hosting Startup assembly file manually or rebuild the Hosting Startup project with the output folder set to the same folder as the ASP.NET Core Web application output, like this.

$ dotnet build ./MyHostingStartup -o ./MyAspNetCoreApp/bin/Debug/net10.0
Enter fullscreen mode Exit fullscreen mode

Next, create a JSON file that describes the .NET dependency. The file name can be anything, but here we will call it "additional.deps.json". Let's create the file in the current folder and open it in an editor like VSCode.

$ echo "" > additional.deps.json
$ code ./additional.deps.json
Enter fullscreen mode Exit fullscreen mode

In this JSON file, write the following to declare that MyHostingStartup.dll is a dependency.

{
    "runtimeTarget": {
        "name": ".NETCoreApp,Version=v10.0"
    },
    "targets": {
        ".NETCoreApp,Version=v10.0": {
            "MyHostingStartup": {
                "runtime": {
                    "MyHostingStartup.dll": {}
                }
            }
        }
    },
    "libraries": {
        "MyHostingStartup": {
            "type": "project",
            "serviceable": false,
            "sha512": ""
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

After saving this JSON file and closing the editor, we set the environment variable DOTNET_ADDITIONAL_DEPS to the path of this dependency JSON file so that it is loaded at runtime.

$ export DOTNET_ADDITIONAL_DEPS=./additional.deps.json
Enter fullscreen mode Exit fullscreen mode

Or if you are using PowerShell,

PS> $env:DOTNET_ADDITIONAL_DEPS = './additional.deps.json'
Enter fullscreen mode Exit fullscreen mode

After doing this, let's run the ASP.NET Core Web application project again. This time, it should start without any errors.

$ dotnet ./MyAspNetCoreApp/bin/Debug/net10.0/MyAspNetCoreApp.dll
Enter fullscreen mode Exit fullscreen mode

Let's check with the curl command to see if the Hosting Startup assembly was really loaded and the middleware was added.

$ curl -i http://localhost:5000/
HTTP/1.1 200 OK
Content-Type: text/plain; charset=utf-8
Date: Sat, 14 Feb 2026 04:10:47 GMT
Server: Kestrel
Transfer-Encoding: chunked
X-Custom-Header: MyCustomValue

Hello World!
Enter fullscreen mode Exit fullscreen mode

Looking at the curl output above, we can see that the "X-Custom-Header" response header has been added. This confirms that the Hosting Startup assembly MyHostingStartup.dll was loaded and its IHostingStartup.Configure(IWebHostBuilder builder) method was called.

Conclusion

Using the Hosting Startup feature, we can add middleware to an existing ASP.NET Core Web application without rebuilding it. The middleware is implemented in a separately-built assembly and added at runtime.

The basic steps to implement a Hosting Startup assembly are to build a class library that contains a class implementing the required interfaces and the assembly attribute following the conventions, and then start the ASP.NET Core Web application with the assembly name specified in the ASPNETCORE_HOSTINGSTARTUPASSEMBLIES environment variable.

However, because of how .NET application execution works, we need to explicitly specify assembly dependencies along with the location of the assembly files in a JSON file. The Hosting Startup assembly is no exception. We need to declare the dependency in a JSON file from the perspective of the target ASP.NET Core Web application and specify that JSON file in the DOTNET_ADDITIONAL_DEPS environment variable.

So in short, the Hosting Startup feature lets us add middleware to an existing ASP.NET Core Web application to extend its functionality. I think this is a feature that opens up many possibilities depending on your ideas. Please give it a try!

Top comments (0)