Table of Contents
Introduction
ASP.NET was first released in 2002. In the last decade, it has undergone significant changes and investment by Microsoft. From Web Forms to MVC and Web API, from ASP.NET AJAX to SignalR, ASP.NET has never stood still. And yet the architecture that underpins the platform never really changed: ASP.NET was coupled to IIS, IIS was coupled to Windows. The .NET runtime is installed globally and new versions were only released once every couple of years.
ASP.NET 5 isn’t just a few new features with a major version stamp. It’s a significant architectural rebuild of the platform underneath ASP.NET. ASP.NET 5 is all about choice:
-
Choice of operating systems
ASP.NET 5 applications can target .NET Core, a cross-platform .NET runtime. .NET Core is supported on Windows, OSX and Linux, all by Microsoft. -
Choice of web servers
ASP.NET 5 is no longer coupled to IIS. Any OWIN-compatible server can host ASP.NET. You can run ASP.NET under IIS, or under a new, cross-platform HTTP server called Kestrel, or embedded in your own server applications. -
Choice of framework versions
.NET Core is portable. If you target .NET Core, the entire runtime can be bundled and deployed side by side with your application. When Microsoft make improvements to the framework, your application can use them immediately, without affecting other applications.
Goal of this book
Have you ever had this experience? You decide to learn a new programming language, and you set out to build an application with it. You learn the language, write the code, and slowly your application takes shape. It runs locally in your development environment just fine. You want to deploy it somewhere for others to try, or maybe put it into production, and then you realize: you have no idea how to go about deploying it. Plenty of tutorials will teach you the frameorks and development environments, but finding good information on how to deploy and run the application in a production-like environment is really difficult.
This book won’t teach you how to develop ASP.NET 5 applications. There are plenty of great resources that can do that, from the official ASP.NET 5 documentation to books and blog posts and presentations. The purpose of this book is to teach you how to deploy them. As you learn ASP.NET 5 and prepare to deploy your applications, you’ll find that many things have changed:
- The way ASP.NET is hosted has changed
- The way you compile and publish applications has changed
- The way you configure applications for production has changed
The goal of this book is to walk you through these changes, and to give you everything you need to successfully deploy your first ASP.NET 5 application to production.
Runtimes and Execution
To understand how ASP.NET 5.0 applications will run in production, it helps to first understand what .NET Core and DNX are. In this chapter, we’ll take some time to understand them, and to understand some of the changes to how .NET applications run under DNX.
Runtimes: .NET Framework vs. .NET Core
When .NET Framework 4.0 shipped, it came bundled with Windows Server 2012, and as a 48MB MSI for older versions of Windows. The .NET Framework contains just about everything, from UI toolkits (WPF and Windows Forms), to communication libraries (WCF/HTTP), to web application stacks (ASP.NET Web Forms). Because the framework ships as one big package, it’s difficult to tease components apart, to iterate on them, or to ever remove something from them.
To combat this, and to take .NET cross platform, “.NET Core” was created. This is a new take on the CLR, which has been mercilessly factored into small assemblies that are available as packages on NuGet. It has a few benefits:
-
Cross platform
.NET Core is supported on Windows, Linux and OSX. -
Portable
.NET Core can be packaged and deployed as part of your application; no global installation required. -
Lightweight
Only the packages you actually use are imported.
I believe that .NET Core is the future, and the only reason .NET Framework isn’t obsolete is that it will simply take a long time for types to be made cross platform, tested and ported. As the surface area of .NET Core grows, third party packages will start to target .NET Core, and eventually it will have a big enough surface area to become the default choice for all applications.
ASP.NET 5.0 targets both
For this reason, ASP.NET 5.0 targets both the .NET Framework, and .NET Core. That is, you can make use of new features and architectural improvements in ASP.NET 5.0, but still use the regular desktop .NET runtime without being limited to the .NET Core surface area.
When you sit down to build an ASP.NET 5.0 application, it’s very unlikely that you’ll target both .NET Core and .NET Framework. You’ll make a choice:
.NET Core | .NET Framework |
---|---|
Cross platform | Windows only |
Lightweight | Heavy |
Portable (bundle with your app) | Global, admin install |
Iterates quickly | Slow upgrade cycle |
Small surface area for now | Everything but the kitchen sink |
Class libraries and some utility tools, however, will likely targetting both runtimes. So what’s needed is some way to figure out what runtime to invoke when running an application. That’s where DNX comes in.
Execution: DNX
Whether your application targets the full .NET Framework or just .NET Core, it can be executed with DNX. Think of DNX as a host for your .NET applications, much like the way python.exe
hosts a Python script. It’s easier to understand this by looking at what it does at the process level Take this simple C# console application:
Before DNX, this code would get compiled into an .exe
file. The top of the .exe
file would contain a small bit of native code that would check the .NET runtime is installed globally, then load the runtime, and then the runtime would run the rest of the application. In task manager, you would see your executable as the top process:
Under DNX, this all changes. Firstly, I get to choose my runtime. Will this console application target the .NET Framework, .NET Core, or both?
Secondly, in development, there is no compilation step - code is compiled on the fly with Roslyn, so you’ll never see a .dll
or .exe
in the first place when you hit F5 locally. When you are ready to ship the application, though, you’ll eventually compile it. You do this using the dnu
tool:
Instead of creating a .exe
as you might expect, the code is actually compiled into a .dll
inside a directory structure. The directories contain the application bundled as a NuGet package, plus various JSON configuration files. At the root is a .cmd
batch file which invokes the application:
The batch file invokes DNX.exe
(the actual batch file is longer than this - snipped for brevity). This tool is called the DNX Application Host, and it takes care of running applications built for DNX:
The MyApp
file without the extension is a shell script, so the same application can run on Linux (again, snipped):
The trick that DNX uses is similar to how Java applications run. Java applications aren’t compiled into an .exe
, they just ship as a .jar
file which is then loaded by the Java executable:
Something you’ll notice is that if you look in task manager, you won’t see the name of your console app anywhere - just DNX. In fact I’m not even sure which one of these three DNX processes contains my application:
A good way to figure that out is by using SysInternals Process Explorer:
One of the great benefits of DNX is that if your application targets CoreCLR, the runtime can be distributed with your application. You can now see how this can work - since the CoreCLR includes DNX.exe, all you need to do is distribute the CoreCLR runtime files with your application, and your batch file will invoke that DNX version. You can bundle the runtime and have your batch file call that simply by specifying the option when publishing:
Now that you are familiar with how DNX invokes processes, let’s look at two HTTP server options for ASP.NET 5.0.
Summary
.NET Core is an ambitious attempt to tease apart the monolithic .NET Framework into something that is portable, cross-platform and iterates quickly. DNX is a new way to build and run applications, whether they run on .NET Core or .NET Framework. We’ve looked at how DNX fits in with console applications; in the next chapter we’ll look at how DNX executes ASP.NET applications.
Web Servers
Historically, there have been very few choices for deploying ASP.NET applications in production. ASP.NET was tightly coupled to IIS, which was tightly coupled to Windows, so the stack was chosen for you. As we discussed in chapter 1, ASP.NET 5.0 is about choice.
Of course, IIS is still supported in ASP.NET 5.0. But since ASP.NET 5.0 builds on top of OWIN, any HTTP server that can host OWIN can host ASP.NET 5.0. And since .NET Core is cross-platform and IIS is not, we need new choices for HTTP servers when running ASP.NET 5 outside of Windows.
In this chapter, we’ll focus on the two HTTP servers that we expect will comprise of the majority of production ASP.NET 5.0 deployments: the new kid on the block, Kestrel, and the old, reliable IIS.
Kestrel
Kestrel is a cross-platform, open source HTTP server for ASP.NET 5.0. It’s built by the same team at Microsoft that built ASP.NET 5.0, and it allows ASP.NET 5.0 applications to run consistently across Windows, Linux, and OSX.
Where web servers like IIS and Apache are designed to be general-purpose web servers, with support for many languages and features like directory browsing and static content serving, Kestrel is designed specifically for hosting ASP.NET 5.0. Architectually, Kestrel builds on top of:
-
libuv
, the open source asynchronous event library used by Node.js. This provides asynchronous TCP sockets to Kestrel in an OS-agnostic manner. -
SslStream
, a .NET framework class to turn an ordinary stream into a TLS stream. This allows Kestrel to support HTTPS, including client-side certificates. On Windows,SslStream
builds onSChannel
, the standard Windows TLS components also used by IIS. In practical terms, this means that HTTPS with Kestrel is as secure as HTTPS with IIS.
Kestrel is entirely user-mode and binds to a TCP port directly to listen for requests. Kestrel can be used to run your ASP.NET 5.0 application, or embedded inside your own process. This is handy when building long-running services that sometimes need to present a web interface.
IIS
IIS is a general purpose web server which can also host ASP.NET 5.0. However, the way IIS hosts ASP.NET 5.0 is dramatically different than previous versions of ASP.NET. Let’s first look at the architecture of IIS and how ASP.NET was traditionally hosted, and how it now works with ASP.NET 5.0.
IIS Architecture
Windows ships with a kernel-mode device driver called HTTP.sys, which listens for HTTP connections and hands them over to an appropriate application. It allows multiple applications to effectively share the same IP/port combinations by performing some HTTP parsing in the kernel before dispatching to the application. For example:
IIS builds on top of HTTP.sys - the request is initially received by HTTP.sys (which also performs some security filtering, like Windows Authentication or client certificates), which in turn hands it to IIS. It then sends the response from IIS back to the client.
Each application running on IIS belongs to an application pool, and a worker process is created to serve requests to that application pool. This is the W3WP process you’ll sometimes see in task manager. If the process runs for significant time, or experiences severe memory leaks and runs out of memory, or hangs, IIS can terminate it and create a new worker process.
In previous versions of ASP.NET, the CLR was loaded into the worker process1. Under ASP.NET 5.0, this changes.
- The worker process no longer contains the CLR - it’s entirely unmanaged
- All requests are routed to a new, unmanaged extension called the HTTP Platform Handler
- The HTTP Platform Handler launches a DNX process by invoking the batch files we saw in the section on DNX. It sets an environment variable to tell the launched process what port to listen on.
- DNX loads Kestrel!
- HTTP Platform Handler then proxies the request to Kestrel in the DNX process, and proxies the response back
Processing a request in IIS for ASP.NET 5 looks like this:
This architecture will be familar to users who have done development with platforms like Node.js, where it is common to have a server like NGINX accepting the initial request, then reverse proxying them to a node.exe process which listens on its own TCP port.
Here’s the process tree in SysInternals Process Explorer:
IIS setup notes
Due to these major architectural changes, the way you set up and configure IIS for ASP.NET 5 changes quite a bit.
First, you must download and install the HTTP Platform Handler extension. Eventually I expect this will ship out of the box with IIS, but for now it’s a separate download.
Second, since application pool worker processes no longer host the CLR (since all they do is call an extension to run a batch file), you can configure them to not load it:
Third, if your IIS server isn’t going to be running older versions of ASP.NET, you don’t even need to install the ASP.NET IIS components at all!
Benefits of IIS
Given that IIS effectively just proxies requests to and from Kestrel, what value does it provide? The main benefit would appear to be worker process management: if the DNX process becomes unresponsive, IIS can terminate the worker process tree, and a new one will be created, keeping the application responsive.
On the other hand, the responsibilities of IIS have been dramatically reduced to that of a process manager and reverse proxy server: a Microsoft equivalent of NGINX. It remains to be seen whether many .NET applications will run directly under Kestrel without IIS in the middle.
TODO: I need to confirm how other managed modules still work in this model. The HTTP Platform Handler extension is added and accepts all requests, but it doesn’t remove any other modules, so presumably the CLR is still loaded into the worker process to call some of the native IIS modules.
Summary
The hosting architecture of ASP.NET 5 is radically different to previous versions, and for good reasons. By decoupling ASP.NET from IIS, a lot of choice is available and the team can iterate much more quickly. And yet IIS is still supported, acting as a reverse proxy instead of a web server, borrowing from the model of other web stacks. And there’s still a lot of consistency: whichever approach you use, it’s will still be ASP.NET hosted in Kestrel/DNX.
Now that you know how applications will run in production, the next chapter will look at publishing applications.
- In Integrated pipeline mode, IIS hosts the CLR and all IIS modules are managed modules. With Classic pipeline mode, IIS used unmanaged modules, and then the CLR ran its own unmanaged modules. ↩
Publishing Appliations for Deployment
In this chapter, we’ll take a deep look into how to publish your ASP.NET 5 application so that you can deploy it for other people to use. Rather than spend time on wizards in Visual Studio, we’ll concentrate on using the command line to publish, so that we can automate it as part of our automated build and deployment process.
Prior to ASP.NET 5, publishing applications from the command line was arcane and fiddly. The good news is that it gets much, much easier with the new dnu
tool.
Publishing with DNU
DNU is a command-line tool that is part of DNX, and contains a number of utilities for building, packaging and publishing applications. When you’re past the “works on my machine” stage of development and you’re ready to run the application on another server, dnu
is your friend.
Publish a DNX application by calling dnu publish
from the project directory. Here’s what I recommend:
The structure of a published web application looks like this:
-
approot
-
packages
Any libraries from NuGet packages that you use will be copied to a directory structure under here. When you pass the--no-source
option (more below) your own application is also compiled into a NuGet package under here. -
runtimes
The CLR runtime that you are using is copied here, with a folder per runtime. For .NET Core, this will be the full runtime - every file you need to run the application. For .NET Framework, it will just contain a few DNX-related assemblies andDNX.exe
; it will rely on the full .NET Framework being installed globally.
-
packages
-
logs
By default this is an empty folder; you can enable logs manually. We’ll cover this in a later section. -
wwwroot
This will contain a small web.config that is used only when hosting your application under IIS; it simply tells IIS to call the HTTP Platform Handler and let DNX take care of the rest. The folder will also contain any static files (CSS, JavaScript, images, etc.) that your application uses.
Remember that DNX applications don’t simply run - they have to be run by DNX.exe
. DNX.exe
must either be bundled with your application (which is why we pass --runtime active
), or available on the PATH environment variable. As a developer, you have a version of DNX in your path already, but your production server won’t.
To compile, or not to compile
I also pass the --no-source
flag when publishing. This ensures that dnu publish
compiles the code and only publishes the compiled output, not the source code. Without this argument, DNX will dynamically compile the application on the fly as it runs with Roslyn. The Roslyn dynamic compilation features of ASP.NET 5 were designed to make for a better development experience; there’s really no benefit to using them in production.
In addition, publishing source code always leaves the chance that subsequent compilations could result in slightly different behavior. This is especially true if you target the full .NET Framework, since it is installed globally and could vary between machines depending on patches. When I test code in a pre-production environment and promote it to production, I want to feel confident that what I deploy to production is as similar as possible to what I tested. Compiling the source code into assemblies as part of the publish step removes one more possible cause of production problems.
Publish once, deploy many times
If you have more than one target environment, or more than one target server, then you should develop the habit of publishing once, deploying many times. In other words, publish the project once, then use the same published outputs when promoting between environments:
Running with Kestrel
Now that you have published your application, you can test that it runs. Running it under Kestrel is easy. Simply go to the approot
folder of your published directory, and run the web.cmd
batch script:
This is made possible by the commands
element inside your project.json
file. The dnu publish
tool takes any commands, and turns them into batch files. In my case, project.json
contains:
So the web.cmd
file that dnu publish
generates calls DNX over the Kestrel console application, as we saw in the first chapter. Kestrel then listens on a port and serves the application.
Running with IIS
The published output folder is also ready to be used by IIS. Simply point IIS at the wwwroot
folder of the published output:
This is made possible by the small web.config
file inside wwwroot
, which tells the worker process to dispatch all requests through the HTTP Platform Handler:
Notice how the processPath
attribute points back to the same web.cmd
file that we called when we ran the app directly under Kestrel.
Logging
Whether you run your application directly under Kestrel or via IIS, DNX is still running as a console application. This means that you can capture log output from the console and send it to the logs
folder that was created as part of the published output. Simply change the stdoutLogEnabled
attribute in web.config
to true
.
Summary
In this chapter we explored dnu publish
, a new standard way to prepare DNX applications for deployment. We reviewed some best practices like publish once, deploy many times, and saw how to run the application manually under Kestrel or IIS.
In the next chapters we’ll look at automating the publishing and deployment as part of a continuous delivery pipeline.
Deployment with MSDeploy
This book will explain how to use MSDeploy to publish applications to IIS servers, and whether anything is different for ASP.NET 5.0.
Deployment with Octopus
This section will talk about how to automate deployments with Octopus.
It will cover:
- Why Octopus is a better choice than MSDeploy
- Taking the
dnu publish
outputs and packaging them for deployment - Deployment process in Octopus
Environmental differences
This section will deal with configuration files and application settings, as well as sensitive values.