The ASP.NET Core Module; Changes and Improvements over the Last 3 Years.

I been receiving many questions about the ASP.NET Core Module, and I decided that it would be useful to write down some of the design decisions and motivations behind changes and improvements we made.

A bit of history

The ASP.NET Core Module (ANCM) is an IIS component which allows ASP.NET Core applications to run with IIS. It originally started as a fork of the HttpPlatformHandler, a generic IIS component which starts a backend process and proxies HTTP requests to it. This model of proxying requests to a backend process is what we call the Out-Of-Process model, where a backend process accepts HTTP requests from the ANCM. The Out-Of-Process model was used throughout the 1.0, 1.1, 2.0, and 2.1 releases of ASP.NET Core.

However, there were downsides to this model.

  • The performance was not great. In this issue, it was reported that response times were very slow in comparison to just running Kestrel.
  • There was a lack of functional testing for the module, making any performance improvements risky without breaking customers.
  • All of the code lives in a global IIS module, meaning normal shipping and patching mechanisms in dotnet don’t work with the module.
  • Code needed to be forwards and backwards compatible as the module doesn’t version with dotnet.

To say the least, there were many issues with the IIS experience. In my opinion, customers porting from ASP.NET to ASP.NET Core were left hanging a bit.

Around two and a half years ago (July 2017), I started my full time career at Microsoft. Not sure what I would work on, I came in with an open mind and willing to try and listen to everyone. David Fowler, an architect on the ASP.NET Core team, is always tinkering and trying new things out. He recognized that our IIS experience needed improvements, and started working on a prototype of running ASP.NET Core applications within the IIS process (either w3wp.exe or iisexpress.exe). He asked that I start playing around with this experiment a bit more, validating the performance and maintainability of this model. This model would need to seamlessly integrate into the ASP.NET Core Module Out-Of-Process setup.

After getting a prototype setup, we immediately saw a performance boost of around 3x, from around 30k Requests per second to 90k Requests per second. It seemed clear that a value proposition was there. And after approval of adding this feature, we immediately started fleshing out the prototype to make it a product.

Architecture

To make both In-Process and Out-Of-Process applications work, we had to re-architect the ASP.NET Core Module to support both of these models. Before, the ASP.NET Core Module logic all lived within a single DLL, which was a global component. We decided to create three separate DLLs.

                               /--------> aspnetcorev2_outofprocess.dll (global)
                               |
aspnetcorev2.dll (Shim) -------|
                               |
                               \--------> aspnetcorev2_inprocess.dll (shared framework)

A main goal we had for the In-Process mode was to remove as much code as possible from the global component. What we realized is that we could refactor ANCM into multiple pieces to allow the In-Process component to live in a separate dll. With this refactor, we can now ship the In-Process dll as part of the ASP.NET Core runtime instead! This dramatically improves the ability to make changes each release without having to worry about regressions. The same was also done for Out-Of-Process, but the Out-Of-Process handler is still a global component due to many complications and backwards compatibly issues.

There are were some interesting implementation details we had to deal with due to limitations in IIS, including:

  • Capturing stdout and redirecting in a live process
  • How to allow duplex streaming when duplex stream isn’t well supported in IIS.

Result

The In-Process model shipped in the 2.2 release of ASP.NET Core, which was behind schedule from when we originally wanted to ship this feature in 2.1. It was a rough release as we had bad bugs, but after some maturity time and diagnostics improvements in 3.0 release, we saw that ANCM In-Process was generally successful as is the default experience for deploying websites on Azure App Service on Windows. It is also used by Azure Functions to power many of their application models.

Another added benefit we didn’t recognize in the moment was improved startup times for debugging in Visual Studio. Before, ANCM Out-Of-Process was rather slow to startup a debugging session with dotnet. With In-Process, the startup time dramatically decreased.

There are still on-going issues in ANCM that still cause me to be a bit uneasy at some points. Many times, ANCM is the first error returned to a user if their application is misconfigured or deployed incorrectly. As it’s the first error, many people search “ANCM Startup Failure” or something similar trying to find the issue, when in reality it’s rarely an issue in ANCM. We made some improvements in 3.0 to add enhanced diagnostics, however there are still improvements we can make here as well.

Continued investments

IIS is still a widely used technology, and providing a strong IIS experience in ASP.NET Core is crucial for people porting from ASP.NET/System.Web. We now maintain three different web servers in ASP.NET Core, Kestrel, IIS, and Http.Sys. Kestrel is still our main and most important server, as it works cross platform and has stellar performance. IIS and Http.Sys will continue to get improvements over time as well, based on scenario needs. For example, we are doing work to make sure IIS and Http.Sys works with gRPC.

Written on February 9, 2020