Closer Look: Hosting ASP.NET Core on Azure App Service
Sunday, April 3, 2016
Azure Web Apps on Azure App Service is probably the simplest way to host an ASP.NET Core app today. Currently, ASP.NET Core apps can be deployed seamlessly via Visual Studio publishing and Git deployment. The deployed application runs on App Service via the HttpPlatformHandler (soon to be replaced by the ASP.NET Core Module).
In this article, we'll take a closer look at what actually happens when an ASP.NET Core app is deployed and hosted in Azure App Service.
HttpPlatformHandler
An ASP.NET Core application is simply an executable that is able to listen and respond to HTTP requests without the need for an external web server. However, for production deployments, it's recommended that we still run our applications behind something like IIS or NGINX.
ASP.NET Core runs on IIS with the use of a module called HttpPlatformHandler. The same mechanism can be used to host Node.js and Java applications inside IIS. This is how Azure App Service hosts ASP.NET Core apps as well.
Note: The HttpPlatformHandler module will soon be replaced by the ASP.NET Core Module. The two modules function very similarly. Details can be found here.
HttpPlatformHandler does two things:
- It starts and manages a process. If the process exits, it will start it again.
- It reverse proxies HTTP requests to the process.
Here's how this looks with an ASP.NET Core application:
HttpPlatformHandler instructs the ASP.NET Core application host to use a dynamic port to listen to traffic. It passes this port to the application via an environment variable named HTTP_PLATFORM_PORT
.
In an ASP.NET Core app, the Microsoft.AspNet.Hosting.Internal.HostingEngine
is where the HTTP_PLATFORM_PORT
environment variable is read and used HostingEngine.cs in the aspnet/hosting project:
namespace Microsoft.AspNet.Hosting.Internal
{
public class HostingEngine : IHostingEngine
{
// This is defined by IIS's HttpPlatformHandler.
private static readonly string ServerPort = "HTTP_PLATFORM_PORT";
// ...
private void EnsureServer()
{
// ...
// Line 266
var port = _config[ServerPort];
if (!string.IsNullOrEmpty(port))
{
addresses.Add("http://localhost:" + port);
}
// ...
}
}
}
Web.config Transformation
While ASP.NET Core apps no longer use a Web.config for its settings, the file is still used by IIS. The Web.config is where the HttpPlatformHandler is configured.
This is the contents of the Web.config in the wwwroot folder of an ASP.NET Core RC1 app:
<configuration>
<system.webServer>
<handlers>
<add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/>
</handlers>
<httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" stdoutLogEnabled="false"/>
</system.webServer>
</configuration>
There are a couple of placeholders that get filled in when the application is published. But what is responsible for this?
It turns out that dnu publish
is what inserts the proper values. However, if we simply run dnu publish
, it'll set the following values, which are actually incorrect for running in Azure App Service:
<httpPlatform processPath="..\approot\web.cmd" arguments="" stdoutLogEnabled="false" stdoutLogFile="..\logs\stdout.log"></httpPlatform>
So how do the correct values get applied when the application is deployed via Visual Studio Publish or Git Deployment? This is yet another place where there's some magic happening.
When Visual Studio deploys an ASP.NET Core app to Azure App Service, it sets an environment variable called DNU_PUBLISH_AZURE
to 1
before calling dnu publish
. This tells dnu publish
to fill in the correct values, which are:
<httpPlatform processPath="%home%\site\approot\web.cmd" arguments="" stdoutLogEnabled="false" stdoutLogFile="\\?\%home%\LogFiles\stdout.log" startupTimeLimit="3600"></httpPlatform>
To see how this works, once again we can look at the source. This time the code we're interested in is in the PublishProject.cs file in the aspnet/dnx project:
// LINE 568
var azurePublishValue = Environment.GetEnvironmentVariable("DNU_PUBLISH_AZURE");
var publishingToAzure = string.Equals(azurePublishValue, "true", StringComparison.Ordinal) ||
string.Equals(azurePublishValue, "1", StringComparison.Ordinal) ||
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME"));
var basePath = publishingToAzure ? @"%home%\site" : "..";
var baseLogPath = publishingToAzure ? @"\\?\%home%\LogFiles" : @"..\logs";
Hosting in App Service
Now that we know how the magic happens, we can finally take a look at how the app is hosted in Azure.
We can find out a lot about our app by going to the site's SCM portal. This is done by adding .scm
to the URL:
Folder Stucture
We can look at the file structure by navigating to the PowerShell or CMD debug console. We can see that the approot and wwwroot folders are both there.
Processes
And we can also take a look at how the HttpPlatformHandler works by navigating to the Process Explorer:
We can see both the w3wp.exe and dnx.exe processes that was in our diagram at the beginning of this article.
And if we click on the properties of the dnx.exe process, we can find the HTTP_PLATFORM_PORT
environment variable that HttpPlatformHandler has set to tell Kestrel what port to use:
And remember that the HttpPlatformHandler is responsible for managing the dnx.exe process? Let's try killing dnx.exe by right clicking it and see what happens:
The process disappears! But if we try to access the application again, HttpPlatformHandler will create it again, passing it a new port to listen to.
Azure Web Apps on Azure App Service is probably the simplest way to host an ASP.NET Core app today. Currently, ASP.NET Core apps can be deployed seamlessly via Visual Studio publishing and Git deployment. The deployed application runs on App Service via the HttpPlatformHandler (soon to be replaced by the ASP.NET Core Module).
In this article, we'll take a closer look at what actually happens when an ASP.NET Core app is deployed and hosted in Azure App Service.
HttpPlatformHandler
An ASP.NET Core application is simply an executable that is able to listen and respond to HTTP requests without the need for an external web server. However, for production deployments, it's recommended that we still run our applications behind something like IIS or NGINX.
ASP.NET Core runs on IIS with the use of a module called HttpPlatformHandler. The same mechanism can be used to host Node.js and Java applications inside IIS. This is how Azure App Service hosts ASP.NET Core apps as well.
Note: The HttpPlatformHandler module will soon be replaced by the ASP.NET Core Module. The two modules function very similarly. Details can be found here.
HttpPlatformHandler does two things:
- It starts and manages a process. If the process exits, it will start it again.
- It reverse proxies HTTP requests to the process.
Here's how this looks with an ASP.NET Core application:
HttpPlatformHandler instructs the ASP.NET Core application host to use a dynamic port to listen to traffic. It passes this port to the application via an environment variable named HTTP_PLATFORM_PORT
.
In an ASP.NET Core app, the Microsoft.AspNet.Hosting.Internal.HostingEngine
is where the HTTP_PLATFORM_PORT
environment variable is read and used HostingEngine.cs in the aspnet/hosting project:
namespace Microsoft.AspNet.Hosting.Internal
{
public class HostingEngine : IHostingEngine
{
// This is defined by IIS's HttpPlatformHandler.
private static readonly string ServerPort = "HTTP_PLATFORM_PORT";
// ...
private void EnsureServer()
{
// ...
// Line 266
var port = _config[ServerPort];
if (!string.IsNullOrEmpty(port))
{
addresses.Add("http://localhost:" + port);
}
// ...
}
}
}
Web.config Transformation
While ASP.NET Core apps no longer use a Web.config for its settings, the file is still used by IIS. The Web.config is where the HttpPlatformHandler is configured.
This is the contents of the Web.config in the wwwroot folder of an ASP.NET Core RC1 app:
<configuration>
<system.webServer>
<handlers>
<add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" resourceType="Unspecified"/>
</handlers>
<httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" stdoutLogEnabled="false"/>
</system.webServer>
</configuration>
There are a couple of placeholders that get filled in when the application is published. But what is responsible for this?
It turns out that dnu publish
is what inserts the proper values. However, if we simply run dnu publish
, it'll set the following values, which are actually incorrect for running in Azure App Service:
<httpPlatform processPath="..\approot\web.cmd" arguments="" stdoutLogEnabled="false" stdoutLogFile="..\logs\stdout.log"></httpPlatform>
So how do the correct values get applied when the application is deployed via Visual Studio Publish or Git Deployment? This is yet another place where there's some magic happening.
When Visual Studio deploys an ASP.NET Core app to Azure App Service, it sets an environment variable called DNU_PUBLISH_AZURE
to 1
before calling dnu publish
. This tells dnu publish
to fill in the correct values, which are:
<httpPlatform processPath="%home%\site\approot\web.cmd" arguments="" stdoutLogEnabled="false" stdoutLogFile="\\?\%home%\LogFiles\stdout.log" startupTimeLimit="3600"></httpPlatform>
To see how this works, once again we can look at the source. This time the code we're interested in is in the PublishProject.cs file in the aspnet/dnx project:
// LINE 568
var azurePublishValue = Environment.GetEnvironmentVariable("DNU_PUBLISH_AZURE");
var publishingToAzure = string.Equals(azurePublishValue, "true", StringComparison.Ordinal) ||
string.Equals(azurePublishValue, "1", StringComparison.Ordinal) ||
!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("WEBSITE_SITE_NAME"));
var basePath = publishingToAzure ? @"%home%\site" : "..";
var baseLogPath = publishingToAzure ? @"\\?\%home%\LogFiles" : @"..\logs";
Hosting in App Service
Now that we know how the magic happens, we can finally take a look at how the app is hosted in Azure.
We can find out a lot about our app by going to the site's SCM portal. This is done by adding .scm
to the URL:
Folder Stucture
We can look at the file structure by navigating to the PowerShell or CMD debug console. We can see that the approot and wwwroot folders are both there.
Processes
And we can also take a look at how the HttpPlatformHandler works by navigating to the Process Explorer:
We can see both the w3wp.exe and dnx.exe processes that was in our diagram at the beginning of this article.
And if we click on the properties of the dnx.exe process, we can find the HTTP_PLATFORM_PORT
environment variable that HttpPlatformHandler has set to tell Kestrel what port to use:
And remember that the HttpPlatformHandler is responsible for managing the dnx.exe process? Let's try killing dnx.exe by right clicking it and see what happens:
The process disappears! But if we try to access the application again, HttpPlatformHandler will create it again, passing it a new port to listen to.