Introducing ASP.NET Project “Helios”

levibroderick

In late 2013 we made available a prerelease NuGet package which allows running a managed web application directly on top of IIS without going through the normal ASP.NET (System.Web) request processing pipeline. This was a relatively quiet event without too much fanfare. At last month’s MVA Windows Azure Deep Dive, we spoke about this for the first time publicly to a global audience.

Today, I’d like to give a formal introduction to ASP.NET Project “Helios”. This post will talk about why we’re introducing this project, what we hope to accomplish with it, and how this might fit in to our ecosystem moving forward.

I assume that the reader has a basic understanding of OWIN and ASP.NET Project Katana. If you are not familiar with these, a brief overview can be found at http://www.asp.net/aspnet/overview/owin-and-katana/an-overview-of-project-katana.

The world today

Prescriptive behaviors

ASP.NET is an “everything and the kitchen sink” programming model. It’s very prescriptive, and developers are pushed to follow its recommended coding patterns. This makes it great for quickly designing and deploying LOB web applications, which was ASP.NET’s original intended audience. However, over the years frameworks like MVC, WebAPI, and SignalR have come online, and developers have begun writing more complex applications following newer standards. These applications don’t fit nicely into the LOB model which made ASP.NET famous. And while ASP.NET certainly allows writing this style of modern application, the simple truth is that the framework’s prescriptive patterns don’t always lend themselves to this style of application. Sometimes these behaviors are downright intrusive. Developers can end up spending a significant amount of time cajoling the framework into meeting their needs.

Below are some sample behaviors which made sense when they were first implemented, but nowadays they are a large source of complaints from our developer base:

  • You cannot mix Windows Authentication (NTLM, Kerberos) and Forms Authentication in the same application.
  • The built-in Forms Authentication module silently converts HTTP 401 Unauthorized responses into HTTP 302 Found (redirect) responses.
  • Request validation shuts down requests which contain ‘<’, ‘&’, or other “dangerous” characters, even if your application handles these requests correctly.
  • Using Task.Wait() can lead to deadlocks in ASP.NET due to the special SynchronizationContext we use. Channel 9 has a video where Brad, Damian, and I speak about this in more detail.

Why Helios?

When we look at our ecosystem, we’re pleased by the success of MVC, WebAPI, SignalR, and our other recent high-level frameworks. These are valuable tools, they have a low barrier to entry for most developers, and they’re deployed completely out-of-band. This allows us to innovate quickly. MVC and WebAPI have published new major releases annually; SignalR has approximately quarterly releases. It allows our customers to deploy immediately, even to shared hosters.

Yet because System.Web is part of the .NET Framework proper, the ASP.NET runtime itself cannot iterate as quickly as we would like it to. We are bound by the release schedules of the .NET Framework as a whole. If a developer asks us to add a feature to ASP.NET, he must wait for the entire framework to rev. And then he must wait for his hoster or IT administrator to update the .NET Framework version on the web server. And if there’s a bug he must again wait for us to provide a fix.

Our core runtime iterates on the scale of years. The state of web technologies is much more agile – much more nimble. A web technology can live its entire lifetime – conception to sunset – in the time that elapses between major releases of the .NET Framework. Our developer audience deserves a base on which they can build a new breed of modern web applications.

And it’s not just wanting more agile development. Recall the list of ASP.NET pain points from earlier: unwanted redirects, too-helpful security handholding resulting in requests being denied, and so on. We’ll never be able to make more than minor tweaks to these behaviors, as we can’t risk breaking customers who have deployed sites and are depending on the existing behaviors.

Finally, we’ll never be able to make the ASP.NET core runtime a “pay-for-play” model. We have experimented several times with moving Web Forms out of System.Web.dll and into its own out-of-band package. This would finally allow us finally fix bugs that have been plaguing us for years. But Web Forms defined ASP.NET for years. The ASP.NET core pipeline and Web Forms processing are inextricably linked.

On IIS and self-host

A developer who cares deeply about these matters may opt to self-host his application. This is perfectly valid, and the Katana stack makes it easy to write host-agnostic applications. But consider all the features that IIS brings to the table.

IIS handles application lifetime management, inspecting request patterns and frequencies to determine how long applications should run. It can suspend (rather than terminate) processes that are idle to help balance available system resources. For improved responsiveness, IIS offers a built-in user-mode cache and can automatically compress response content if appropriate. Application health monitoring is available via built-in logging and failed request tracing modules. IIS supports request filtering and transient worker process identities (least privilege) as part of its 10+ years of security hardening. And the inetmgr utility makes all of this (and more!) available to server administrators.

In a self-hosted scenario, you’re ultimately responsible for the process or Windows service, so you could absolutely achieve feature parity. But this is quite the undertaking to reimplement, and such an endeavor would be error-prone. Besides, if IIS already exists, why not just leverage it for our new host so that we get all of these automatically?

And there’s the crux of Project “Helios”. We want to combine the best of both worlds: pair the granular control offered by self-hosted scenarios with the benefits offered by being hosted inside IIS. And we want to do this while jettisoning the behaviors that developers have told us they dislike about ASP.NET.

Goals and non-goals

As with all things, we need to define our goals before we can determine whether we have been successful in this endeavor. It is not our intent to make a new framework that is everything to all developers. In particular:

  • It is not our goal to have screaming high throughput for “Hello World” scenarios. While Helios does in fact perform significantly better than the full ASP.NET pipeline for such scenarios, these metrics aren’t terribly useful for real-world applications.
  • It is not our goal to provide 100% compatibility with existing applications. In particular, Helios projects do not support .aspx or .ashx endpoints or other ASP.NET-isms.
  • It is not our goal to compete with self-host for developer mindshare. Each OWIN host has its own benefits and drawbacks, and developers should choose the host that meets their needs. We’ll discuss choosing a host later in this post.

On the flip side:

  • It is our goal to enable higher density on web servers. For a machine running a single application, this might be measured by allowing a greater number of concurrent requests on the machine. For a shared hoster, this might be measured by allowing more active sites on a single machine.
  • It is our goal to provide behavior that mimics self-host more than it mimics web-host. We’re trying to eliminate as much magic as possible from the new host.
  • It is our goal to make the Helios framework fully out-of-band. The framework should be able to run without requiring installation as long as the target machine meets the minimum system requirements called out below. Developers should be able to acquire bug fixes / feature additions by acquiring updated packages through NuGet and bin-deploying to their servers / hosters.
  • It is our goal to reduce the friction of deploying a web application built on the Helios host. It should be just as easy to deploy a Helios-hosted application as it is any typical ASP.NET application.

Getting started

Minimum system requirements

You’ll need the following on your development box in order to write a Helios-based application:

  • Windows 8 or Windows Server 2012
  • .NET Framework 4.5.1 (download)
  • Visual Studio 2012 (Visual Studio 2013 preferred but not required)

And your web server / web hoster needs the following in order to run a Helios-based application:

  • Windows Server 2012
  • .NET Framework 4.5.1
  • Full trust

Some hosters (such as Windows Azure Web Sites) already meet these minimum requirements, and developers can begin creating and deploying Helios-based applications to these hosters immediately. Contact your hoster if you’re unsure about their capabilities or system configuration.

We’re working on adding support for Windows 7 / Windows Server 2008 R2 in a future prerelease.

Finally, since this application will be hosted in IIS, you’ll need to make sure the application pool is configured as below:

  • Managed runtime version: v4.0
  • Pipeline mode: Integrated

Creating a new OWIN application on the Helios host

I’m using Visual Studio 2013 for this walkthrough, but the same general steps should work on Visual Studio 2012.

Important: Make sure NuGet Package Manager is fully up-to-date before you begin! The latest NuGet Package Manager for Visual Studio 2013 can be found at http://visualstudiogallery.msdn.microsoft.com/4ec1526c-4a8c-4a84-b702-b21a8f5293ca.

  1. In Visual Studio, select File -> New -> Project.
  2. Select Templates -> Visual C# -> Web, then ASP.NET Web Application. Ensure the target framework is set to .NET Framework 4.5 or later. Then hit OK.

    image

  3. Select the Empty template, then hit OK.

    image

  4. Install the Microsoft.Owin.Host.IIS NuGet package. If you use the NuGet Package Manager UI (in the Solution Explorer pane, right-click the project name, then select Manage NuGet Packages), remember to change the release type dropdown to Include Prerelease.

    You can also find installation instructions at https://www.nuget.org/packages/Microsoft.Owin.Host.IIS/. At the time of this writing, the latest prerelease version is 0.1.5-pre.

    image

  5. Add an OWIN startup class. Add a C# file named Startup.cs to your project with these contents:
    using System;
    using Owin;

    public class Startup {
        public void Configuration(IAppBuilder app) {
            // New code:
            app.Run(async context => {
                context.Response.ContentType = "text/plain";
                await context.Response.WriteAsync("Hello, world.");
            });
        }
    }

  6. Run the project via CTRL-F5. You should see Hello, world. written to the response.

    Note: If you instead see an HTTP 403 Forbidden error page, verify that you have .NET 4.5.1 installed on your machine. The download is located here.

That’s it! You’ve now created an OWIN application running atop the Helios host for IIS. You can easily verify that System.Web.dll isn’t involved anywhere in request processing by changing the code to print out all assemblies loaded into the current AppDomain along with a full stack trace.

public class Startup {
    public void Configuration(IAppBuilder app) {
        // New code:
        app.Run(async context => {
            context.Response.ContentType = "text/plain";

            await context.Response.WriteAsync("Assemblies in AppDomain:" + Environment.NewLine);
            foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) {
                await context.Response.WriteAsync(asm.FullName + Environment.NewLine);
            }
            await context.Response.WriteAsync(Environment.NewLine);

            await context.Response.WriteAsync("Stack trace:" + Environment.NewLine);
            await context.Response.WriteAsync(Environment.StackTrace);
        });
    }
}

The output for 0.1.5-pre will read:

Assemblies in AppDomain:
mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Microsoft.AspNet.Loader.IIS, Version=0.1.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
System.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
System.Configuration, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
System.Xml, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
Microsoft.Owin, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
Microsoft.Owin.Host.IIS, Version=0.1.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
Microsoft.Owin.Host.IIS.Security, Version=0.1.5.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
Microsoft.Owin.Hosting, Version=2.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
Owin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=f0ebd12fd5e55cc5
WebApplication32, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null
Microsoft.CSharp, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
System.Dynamic, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
Anonymously Hosted DynamicMethods Assembly, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null
System.Security, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a

Stack trace:
at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
at System.Environment.get_StackTrace()
at Startup.<<Configuration>b__0>d__2.MoveNext() in c:\Users\levib\Documents\Visual Studio 2013\Projects\WebApplication32\WebApplication32\Startup.cs:line 17
at System.Runtime.CompilerServices.AsyncMethodBuilderCore.Start[TStateMachine](TStateMachine& stateMachine)
at System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Start[TStateMachine](TStateMachine& stateMachine)
at Startup.<Configuration>b__0(IOwinContext context)
at Microsoft.Owin.Extensions.UseHandlerMiddleware.Invoke(IOwinContext context)
at Microsoft.Owin.Infrastructure.OwinMiddlewareTransition.Invoke(IDictionary`2 environment)
at Microsoft.Owin.Host.IIS.HeliosCallContext.Invoke(Func`2 app)
… boring intrinsic stuff removed …
at Microsoft.AspNet.Loader.IIS.Infrastructure.PipelineExecutor.MapRequestHandler(MapRequestHandlerData* pMapRequestHandlerData)

You can see that System.Web.dll and related assemblies (like System.Web.Extensions.dll) aren’t in the stack trace at all. In fact, they’re not even in the current AppDomain! They exert no influence over request processing, so you’re not paying their per-request memory overhead and aren’t battling their unwanted behaviors.

Note: If you attach a debugger and look at the loaded modules list (Debug -> Windows -> Modules), you’ll see that System.Web.dll is still loaded in the process. The reason for this is that it’s still ultimately involved with process management: application startup and shutdown, IIS idle process page-out, and a handful of other features. But even though System.Web.dll is present in the process, this only incurs a small fixed per-process overhead. There is no per-request overhead. (In fact, no System.Web.dll code paths are involved whatsoever in request processing.)

Adding Helios to an existing OWIN-based application

Have an existing OWIN-based application? No problem! Consider the SignalR-StockTicker sample on GitHub. This sample uses the System.Web host for OWIN.

  1. Open the SignalR-StockTicker project in Visual Studio 2013.
  2. Install the Microsoft.Owin.Host.IIS NuGet package to the project.
  3. [optional] Remove the Microsoft.Owin.Host.SystemWeb NuGet package from the project. This step is not strictly required, as the Helios framework will suppress loading this particular package at runtime anyway.
  4. In the Solution Explorer pane, open the SignalR.StockTicker folder, right-click StockTicker.html, and hit View in Browser.
  5. Hit the Open Market button on the page.

    image

That’s it! The only thing that’s needed to convert a web-hosted OWIN application into a Helios-hosted OWIN application is to pull the necessary NuGet package. As long as the application truly is OWIN-based (doesn’t rely on HttpRuntime, HttpContext, etc.), no other changes should be necessary.

There’s one more nifty thing I want to point out: notice that SignalR is continuing to utilize WebSockets under the covers, even after migrating to the Helios host.

Using the Helios runtime without OWIN

Finally, the option exists to run an application directly on top of Helios rather than go through the Helios host for OWIN. This provides the most granular control over application startup, shutdown, and request processing. More on this can be found in the supplemental post.

Caveats

Remember our stated non-goals: we do not intend 100% compatibility with existing applications. In particular, since Helios completely bypasses the typical ASP.NET pipeline, anything that has a hard dependency on the ASP.NET pipeline will not work correctly in a Helios application. Requests to Web Forms (.aspx) or Razor / Web Pages (.cshtml) endpoints will not be recognized. ASP.NET MVC endpoints will be ignored. Accessing HttpRuntime, HostingEnvironment, or other ASP.NET-hosting-intrinsic types could result in undefined behavior.

ASP.NET Web API endpoints will be reachable as long as Web API has been configured via the OWIN extensibility layer rather than as a typical ASP.NET IHttpModule. To use Web API atop OWIN:

  1. In your project, reference the Microsoft.AspNet.WebApi.Owin NuGet package.
  2. Use the IAppBuilder.UseWebApi extension method to register Web API as an OWIN middleware. A sample Startup.cs that defines a default Web API route is provided below:
    using System;
    using System.Web.Http;
    using Owin;

    public class Startup {
        // This code configures Web API. The Startup class is specified as a type
        // parameter in the WebApp.Start method.
        public void Configuration(IAppBuilder appBuilder) {
            // Configure Web API for self-host.
            HttpConfiguration config = new HttpConfiguration();
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );

            appBuilder.UseWebApi(config);
        }
    }

Further thoughts

Performance and resource consumption

The Helios architecture follows the modern trend of “pay-for-play” design. As much as possible, the framework tries to avoid magic behavior, opting instead to give the developer nearly complete control over the application. This is the same model followed by the Katana project. If your application doesn’t use a particular capability or feature, the unused functionality shouldn’t affect the application’s behavior or its performance characteristics.

The Helios runtime achieves around 2x – 3x the throughput for “Hello world” scenarios when compared to System.Web, but this honestly isn’t a terribly interesting measurement. These requests are best handled by caches rather than by the web server itself. Developer code tends to dominate in real-world applications, so throughput remains steady regardless of the underlying framework. What is interesting is considering other resources like available memory, where the framework does in fact tend to show up in profiles. In our tests the Helios runtime consumes around 96% less amortized per-request memory than does the System.Web runtime. See the supplemental post for more information on these measurements and their real-world impact.

Support

This is a very early prerelease. One might even call it pre-alpha. There is no official support provided for this. We do not guarantee that we’ll ever ship a release version. The full EULA can be found here.

The EULA does allow for deployment to a hosting provider. However, deploying such an early prerelease to a service that you care about is ill-advised.

Conclusion

We’re excited about what this could mean for the future of our platform, especially as more frameworks and components break their strict dependency on System.Web.dll. This new design promises to allow us to ship new functionality fully out-of-band and to avoid surprising developers with unwanted behaviors.

I also want to stress that this is strictly an option. The target audience for this package is a minority of our overall developer audience. The team has no plans to force our general developer audience on to this system.

Finally, there is a supplemental post available with further information available for more advanced developers.  That post discusses performance and resource utilization in more detail. It also discusses using the Helios APIs directly without going through OWIN.

As always, feedback is greatly appreciated! Feel free to sound off in the comments below. And thanks for reading!

Thanks to Eilon, Glenn, Damian, and Scott, who all graciously offered their time to help review this post.

0 comments

Discussion is closed.

Feedback usabilla icon