ASP.NET 5 - A Deep Dive into the ASP.NET 5 Runtime

Last November Microsoft announced ASP.NET 5 as a new open source and cross-platform framework for building modern Web and cloud applications using the Microsoft .NET Framework. We (the ASP.NET development team, of which I’m a part) released ASP.NET 5 Preview along with Visual Studio 2015 Preview at the Connect(); event in New York City. I introduced you to the ASP.NET 5 runtime and its new Visual Studio project system in the “Introducing the ASP.NET 5 Preview” article from the special Dec. 15, 2014, Issue of MSDN Magazine (bit.ly/1K4PY4U). Since that last look at ASP.NET 5 in December there have been two more preview releases of ASP.NET 5: Beta2 with Visual Studio 2015 CTP 5, and Beta3 with Visual Studio 2015 CTP 6. With each release, the framework continues to evolve and improve. In many cases, these improvements were generously contributed by the .NET community through the public ASP.NET project on GitHub (bit.ly/1DaY7Cd). In this article, I’ll take a deeper look under the hood of the new ASP.NET 5 runtime to see what’s changed in the most recent release.

The K Runtime Environment (KRE)

As you saw back in December, ASP.NET 5 is based on a flexible, cross-platform runtime host that can host one of several .NET CLRs. You can run ASP.NET 5 applications on the .NET Framework with its full API set for maximum compatibility. You can also run ASP.NET 5 on the new .NET Core, which enables true side-by-side deployments that you can copy into existing environments without having to change anything else on the machine. In the future, you’ll also be able to run ASP.NET 5 cross-platform on .NET Core, and there’s community support for running cross-platform on Mono today.

The runtime hosting infrastructure for ASP.NET 5 is currently called the K Runtime Environment (KRE), which is a generic placeholder name until we finalize the official name. The KRE provides an environment that has everything a .NET app needs to run: a host process, CLR hosting logic, managed entry point discovery and so forth. The KRE was built for running cross-platform .NET Web applications, but it can run other types of .NET applications, too, such as console apps. The KRE is based on the same .NET CLR and base class libraries .NET developers have come to know and love, while enabling cross-platform support for running .NET applications on Windows, OS X and Linux.

Logically, the KRE has five layers of functionality. I’ll describe each of these layers and their responsibilities.

Layer 1. Native Process:The native process is a very thin layer with the responsibility to find and call the native CLR host, passing along arguments given to the process to be used by the rest of the stack. On Windows this is a native executable (klr.exe); on Mac or Linux it’s an executable bash script. Running on IIS is accomplished with either a new native HTTP module or by using the Helios loader. The Helios loader leverages extensibility hooks in the .NET Framework 4.5.1 to bootstrap the KRE without requiring installation of new IIS modules. With the native HTTP module you can run .NET Core-based Web apps on IIS without any dependency on the .NET Framework. While the native HTTP module is not yet publicly available, we do plan to make it available in a future preview.

Layers 2 and 3. Native CLR Host and CLR: The native CLR host has three main responsibilities:

Boot the CLR. How this is achieved depends on the version of the CLR. For example, to boot .NET Core involves loading coreclr.dll, configuring and starting the runtime, and creating the AppDomain that all managed code will run in. For Mono and the .NET Framework 4.5.1, the process varies somewhat, but the outcome is the same.

Call the managed entry point, which is the next layer.

When the entry point of the native host returns, this process will then clean up and shut down the CLR—that is, unload the app domain and stop the runtime.

Layer 4: Managed Entry Point: This layer is the first layer written in managed code. It’s responsible for:

Loading assemblies and satisfying dependencies from the lib folder.

Setting up the IApplicationEnvironment and the core dependency injection infrastructure.

Calling the main entry point of the specified application or application host.

At this layer, assemblies are loaded only from the application base path or the specified lib folders. It’s the next layer that adds additional loaders for resolving dependencies from NuGet packages or even code compiled at run time.

Layer 5: Application Host/Application: If a developer compiles an entire application to assemblies on disk in the lib folder, this layer is the application—the application the end user sees. If you want to do this, you can compile your application and pass the name of the DLL containing a standard Main entry point when launching layer 1.

However, in most scenarios, you’d use an application host to help resolve application dependencies and run your app. Microsoft.Framework.ApplicationHost is the application host provided in the KRE and its responsibilities include:

Walking the dependencies in project.json and building up the closure of dependencies the app will use. The dependency-­walking logic is described in more detail at bit.ly/1y5lZEm.

Adding additional assembly loaders that can load assemblies from various sources, such as installed NuGet packages, sources compiled at run time using Roslyn and so on.

Calling the entry point of the assembly whose name was given as an argument when the native process was started. The assembly can be anything with an entry point that the ApplicationHost knows how to load. The ApplicationHost that comes with the KRE knows how to find a public void Main method. This is the entry point used to set up the ASP.NET hosting layer, which knows how to find Startup.cs and run the Configure method for your Web application.

One thing to note as you’re learning about the KRE is that this is a low-level part of the stack. When operating at the KRE level, the world is still very much about finding and loading dynamic-link libraries (DLLs). It’s the KRE that contains the logic to allow you, at the application level, to think only about packages and other top-level dependencies.

Cross-Platform SDK Tools

The KRE is packaged with an SDK that contains everything you need to build cross-platform .NET applications. In my previous article on ASP.NET 5, I described how you can use the KRE Version Manager (KVM) tool to list the installed KREs on your machine, install new ones and select the KRE you want to use. You can find instructions for how to install the KVM for your OS at bit.ly/1y5mqyi.

The KVM installs KREs from a NuGet feed configured using the KRE_FEED environment variable. The KREs are not NuGet packages in the traditional sense in that they aren’t packages on which you’d ever depend; NuGet is just a convenient way to distribute and version the KREs. By default, a KRE is installed by copying and extracting the KRE .zip file into %USERPROFILE%\.k\runtimes.

I also previously introduced the K Package Manager (KPM) tool for installing, restoring and creating NuGet packages. We plan to rename the KPM tool to nuget, and align it with the existing NuGet client. As part of this alignment, some of the kpm sub-commands have already been renamed. You now use the bundle command to bundle an application for publishing and the pack command for building and creating NuGet packages for a project. The updated build command produces raw build outputs without packaging anything. There’s also a new wrap command that allows tooling to reference an existing csproj-based project in project.json. By default, packages are now installed in the %USERPROFILE%\.k\packages folder, but you can control this by setting a packages path in your global.json file.

A Cross-Platform .NET Console App

I will now show you how to create a simple cross-platform .NET console app using the KRE. First, I need to create a DLL with the entry point and I can do that using the ASP.NET 5 Console Application project template in Visual Studio 2015. The code should look like this:

This looks pretty familiar, but notice that the entry point is actually an instance method. In addition to a static Program.Main entry point, the KRE supports instance-based entry points. You can even make the main entry point asynchronous and return a Task. By having the main entry point be an instance method, you can have services injected into your application by the runtime environment.

You can run this application from within Visual Studio or on the command line by running “k run” from the directory containing the project.json file for the application. The K command is really just a simple batch file and the “k run” command can be expanded into the following:

The K command executes the native process (klr.exe), specifies the application base as the current directory and then specifies the default application host. The native process has no special knowledge of the default application host—it simply looks for a standard entry point in the Microsoft.Framework.ApplicationHost assembly and calls into it.

If you don’t want to use the default application host you can invoke the native layer to call into your application directly. To do this, first build the project to generate the DLL for the console application. Make sure you checked the “Produce outputs on build” option in the Build properties for the project. You can find the build output under the artifacts directory in your solution folder. Navigate to the directory containing the built-in DLL for the KRE flavor you’re using and call “klr.exe <DLL name>” to see the output from the console application.

Invoking a DLL directly is a very low-level and raw way to write an application. You aren’t using the default application host so you forego project.json support and the improved NuGet-based dependency management support. Instead, any libraries you depend on are simply loaded from the specified lib folders. For the rest of this article I’ll use the default application host.

You can use the IServiceManifest service to enumerate all of the services that are available from the runtime environment. You don’t have to specify any additional dependencies to use this assembly at all because it’s an Assembly Neutral Interface (ANI). ANIs are types that are identified solely by their name and namespace and they are a feature of the KRE. Two assembly-neutral types in different assemblies but with the same name and namespace are considered equivalent. This means that common abstractions can simply be declared locally instead of having to declare a dependency on a common component.

You can define your own assembly-neutral IServiceManifest interface in your console app like this:

The application host will enable support for project.json, add additional assembly resolvers (for dealing with NuGet packages, project references and the like) and make a number of additional services available to the application before calling the entry point for the application. When you run the console app, the output should now look like this:

There are services for doing file watching, traversing the structure of the “libraries” (projects, packages, assemblies) in the application, getting the compilation options being used, and for shutting down the application. Because ASP.NET 5 and the KRE are still in preview and under active development, the exact list of services you see might differ.

Hosting

The ASP.NET hosting layer runs on top of the KRE and is responsible for finding the Web server to run on, finding the startup logic for the application, hosting the application on the server and then cleaning up when the application is shut down. It also provides a number of additional hosting-related services to applications.

The ASP.NET hosting DLL (Microsoft.AspNet.Hosting, at bit.ly/­1uB6ulW) has an entry point method that gets called by the KRE application host. You can configure which Web server you want to use by specifying the --server command-line option or other sources of configuration data, like config.json or environment variables. The hosting layer then loads the specified server assembly/type to find an IServerFactory that can be used to initialize and start the server. Typically, the server must be listed in your dependencies in project.json so that it can be loaded.

Note that commands defined in project.json are really just sets of additional command-line arguments to klr.exe. For example, the default ASP.NET Starter Web App project template includes a bunch of commands in project.json that looks something like this:

Startup

The ASP.NET hosting layer is also responsible for finding the startup logic for your application. Typically, your application startup logic is defined in a Startup class, with a Configure method for setting up your request pipeline and a ConfigureServices method for configuring any services your application needs, as shown in Figure 2.

In your Configure method you use the IApplicationBuilder interface to build the request pipeline for your application. The application builder lets you “Use” middleware, create “New” application builders and “Build” a request delegate. The request delegate is the core runtime construct in ASP.NET 5. A request delegate takes an HttpContext and does something useful with it asynchronously:

ASP.NET 5 middleware takes as input the next request delegate in the pipeline and provides a request delegate with the middleware logic. The returned request delegate may or may not call the next request delegate in the pipeline. As a shortcut for running middleware logic that doesn’t call the next request delegate, you can use the Run extension method on IApplicationBuilder:

To create reusable middleware, you can write it as a class where, by convention, the next request delegate is injected into the constructor along with any additional services or parameters the middleware needs. You then implement the request delegate for the middleware as an asynchronous Invoke method, as shown in Figure 3.

You can use middleware following this convention using the UseMiddleware<T> extension method on IApplicationBuilder. Any additional options you pass into this method will get injected into the middleware constructor after the next request delegate and any injected services. By convention, middleware should also define its own specific Use extension method on IApplicationBuilder, like this:

ASP.NET 5 comes with a rich set of prebuilt middleware. There’s middleware for handling static files, routing, error handling, diagnostics and security. You can find, download and install middleware from Microsoft and the community as NuGet packages on nuget.org.

Configuring Services

As with all the other entry points you’ve seen in this stack, the Startup class can have services injected from the hosting layer in its constructor or as additional parameters on the Configure and ConfigureServices methods. If you enumerate the services available at startup (using IServiceManifest), you see there are now even more services available:

The IHostingEnvironment services gives you access to the Web root path for your application (typically your www folder) and also an IFileProvider abstraction for your Web root. The IProject­Resolver can be used to find other projects in your solution. There are logging and caching services and a type activator that’s dependency injection-aware. The IAssemblyLoadContextFactory gives you an abstraction for creating new assembly load contexts.

You configure existing services and add new ones for your application in the ConfigureServices method in your Startup class. The ConfigureServices method takes an IServiceCollection to which you can add additional services or modify existing ones. ASP.NET 5 comes with a simple built-in IoC container that’s set up in the Managed Entry Point layer in order to bootstrap the system, but you can easily replace the built-in container with your container of choice.

Frameworks typically provide an Add extension method for adding their services to the IServiceCollection. For example, here’s how you add the services used by ASP.NET MVC 6:

When you add services to the service collection, they can be one of three types: transient, scoped or singleton. Transient services are created each time they’re requested from the container. Scoped services are created only if they don’t already exist in the current scope. For Web applications, a container scope is created for each request, so you can think of scoped services as per request. Singleton services are only ever created once.

Web.config and System.Configuration-style app.config files aren’t supported in ASP.NET 5. Instead, ASP.NET 5 comes with a new, simplified Configuration API (Microsoft.Framework.ConfigurationModel, at bit.ly/1yxC7gA) for working with configuration data. The new Configuration API lets you retrieve and manipulate configuration data from a variety of sources. Default configuration providers are included for JSON, XML, INI, command-line parameters and environment variables. You can specify multiple providers and they’re used in the order they’re added. By supporting environment-based configuration, you can more easily deploy an application into an environment and have it pick up the appropriate settings for that environment.

To initialize the configuration data, add your desired configuration providers to a new Configuration instance:

You can make configuration data available in a strongly typed way throughout your application using the options model. Options are just Plain Old C# Object (POCO) classes that have a bunch of properties used for configuration. You make options available in your application by calling AddOptions, which adds the IOptions<TOption> service to the container. This service can be used to access options of different types wherever dependency injection is supported.

To configure options, you add IConfigureOptions<TOptions> services to the container. The default implementation of the IOptions<TOption> service will gather up all the IConfigure­Options<TOption> services and use them to configure the options instance before making the options instance available to consumers. As a shortcut for adding options configuration logic, you can use the Configure<TOption> method, like this:

The options model and the configuration model work hand-­in-hand. You can easily bind configuration data to options, matching by property name by using the Configure overloads that take a Configuration instance, like this:

Request Processing

Once the Web server is started, it starts listening for requests and invoking the application pipeline with each request. The server surfaces each request as a server environment object that exposes a set of fine-grained feature interfaces. There are feature interfaces for sending files, Web sockets, session support, client certificates and more. You can see the full list of supported feature interfaces on GitHub at bit.ly/1Dh7eBC. For example, here’s the feature interface for the HTTP request:

You don’t typically code directly against the various feature inter­faces. Web servers use feature interfaces to expose low-level functionality to the host layer. The hosting layer wraps these feature interfaces in a strongly typed HttpContext that’s then passed through to the application. This level of loose coupling between the Web server, the hosting layer and the applications allows servers to be implemented and reused without being tied to one specific hosting model, and it allows applications to be hosted on different servers. ASP.NET 5 comes with built-in support for running on IIS (Microsoft.AspNet.Server.IIS), directly on a thin HTTP.SYS wrapper (Microsoft.AspNet.Server.Web­Listener), and on a new .NET cross-platform Web server called Kestrel (Microsoft.AspNet.Server.Kestrel).

The Open Web Interface for .NET (OWIN) community standard (owin.org) shares similar goals of loose coupling for Web applications. OWIN standardizes how .NET servers and applications should talk to each other. ASP.NET 5 supports OWIN through the Microsoft.AspNet.Owin package (bit.ly/15IQwA5). You can host ASP.NET 5 on an OWIN-based Web server (bit.ly/1DaU4FZ), and you can use OWIN middleware within the ASP.NET 5 pipeline (bit.ly/1EqmoIB).

The Katana Project (katanaproject.codeplex.com) was Microsoft’s initial effort to implement support for OWIN, and many of the ideas and concepts in ASP.NET 5 were derived from it. Katana had a similar model for building up a middleware pipeline and for hosting on multiple servers. However, unlike Katana, which exposed the low-level OWIN abstractions directly to the developer, ASP.NET 5 instead bridges from OWIN to more convenient and user-friendly abstractions. With a little extra code you can still use Katana middleware in ASP.NET 5 using the OWIN bridge (see an example of how to do this at bit.ly/1BpaXe2).

Wrapping Up

The ASP.NET 5 runtime has been built from the ground up to support cross-platform Web applications. ASP.NET 5 has a flexible, layered architecture that can run on the .NET Framework, .NET Core or even cross-platform on Mono (and in the future on .NET Core as well!). The new ASP.NET 5 hosting model makes it easy to compose applications and host them on the Web server of your choice. The entire runtime is built to support dependency injection and to be easy to configure. I hope you’ve enjoyed learning about the new ASP.NET 5 runtime! Your feedback and contributions are welcome through our GitHub project at github.com/aspnet/home.

Daniel Rothis a senior program manager on the ASP.NET team currently working on ASP.NET 5. His passions include .NET development and delighting customers by making frameworks simple and easy-to-use.

Thanks to the following Microsoft technical experts for reviewing this article: Glenn Condron, David Fowler, Murat Girgin and Chris Ross