If you’ve stumbled upon this post, it probably means that you’ve just tried to deploy your ASP.NET Core 1.x or 2.x web application to a production Windows Server 2012 or 2013 with IIS 8 (or later). Before doing that, if you’ve read the official MS guidelines on publishing .NET Core web apps on Windows/IIS, you’ve most likely equipped your server with the .NET Core Windows Server Hosting bundle, which installs the .NET Core Runtime, .NET Core Library & the ASP.NET Core Module and also creates the reverse-proxy between IIS and the Kestrel server which is required to host .NET Core web apps.
All good, right? Except that you’re facing the following error right after publishing your app:
An error occurred while processing your request.
Swapping to Development environment will display more detailed information about the error that occurred.
Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application.
Don’t worry, that’s quite a common scenario when publishing a .NET Core web application for the first time: despite the official documentation reassuring tone, the deployment task isn’t always easy, as the required reverse-proxy mechanism between IIS and Kestrel undeniably adds an additional level of complexity to the already-present potential issues (mostly depending on the server machine state) that can prevent it from going well.
Luckily enough, there are a lot of things we can do to diagnose the most common problems. Here are the most relevant ones:
- Read the browser’s output messages, optionally setting the ASPNETCORE_ENVIRONMENT variable to Development to print out the stack trace and/or the exception(s) details.
- Examine the Event Viewer’s application log.
- Enable the ASP.NET Core module stdout logging feature.
- Try to reproduce the error(s) on Kestrel
In this post we’ll take care of the first one of them, learning how we can set the ASPNETCORE_ENVIRONMENT environment variable to Development, being it the first time we should learn to do when dealing with .NET Core web apps within IIS.
The ASPNETCORE_ENVIRONMENT variable
As clearly explained in this official introduction to .NET Core environment fundamentals, ASP.NET Core provides support for controlling app behavior across multiple environments, such as development, staging, and production. Environment variables are used to indicate the runtime environment, allowing the app to be configured for that environment.
In our specific scenario, the ASPNETCORE_ENVIRONMENT variable is our buddy, as it’s being used to describe the environment the application is currently running in. This variable can be set to any value you like, but three values are used by convention, corresponding to the three environment defined above: Development, Staging, and Production. The default environment setting will be detected programmatically from within your application and can be overridden by setting up this variable accordingly.
We can do that in two ways:
- On server-level scope, by actually setting a system-wide environment variable, using the Control Panel > System > Advanced Settings > Environment Variables GUI interface.
On app-level scope, by altering our web app’s Web.config file and override that value there.
It goes without saying that the latter method is generally preferable, as it will only change that behaviour for our web app, without affecting the whole web server.
(Re)introducing The Web.config file
… Hey, wait a minute! Did we just say “our web app’s Web.config file”? What happened to the “no Web.config file required” tagline which was repeatedly spoken and sweared upon on almost all .NET Core webinars, demos, promos, hands-ons and highlights?
As a matter of fact, that’s still true, at least for the application settings part – which is now stored in the appsettings.json files. Sadly, that’s not the case for IIS, as it’s still require a Web.config file whenever we’re going to host our app using it. Or, to better say it: the <system.webServer> section of Web.config is always required to configure most IIS features, regardless of the server-side and client-side technologies we’ve been using – including those that apply to a reverse proxy configuration, such the one that we need to implement. It’s important to understand here that it’s not something strictly related to .NET Core, as it’s also true for PHP web apps, Phyton web apps and basically everything else that can be configured to run within IIS.
To better understand this, it can be useful to take a close look at the following lines from the MS official docs on .NET Core publishing with IIS:
[…] .NET Core apps are hosted via a reverse-proxy between IIS and the Kestrel server. In order to create the reverse-proxy, the web.config file must be present at the content root path (typically the app base path) of the deployed application, which is the website physical path provided to IIS. Sensitive files exist on the app’s physical path, including subfolders, such as my_application.runtimeconfig.json, my_application.xml (XML Documentation comments), and my_application.deps.json. The web.config file is required to create the reverse proxy to Kestrel, which prevents IIS from serving these and other sensitive files. Therefore, it’s important that the web.config file isn’t accidently renamed or removed from the deployment. […]
And also, later on:
[…] IIS configuration is still influenced by the <system.webServer> section of web.config for those IIS features that apply to a reverse proxy configuration. For example, you may have IIS configured at the system level to use dynamic compression, but you could disable that setting for an app with the <urlCompression> element in the app’s web.config file. For more information, see the configuration reference for <system.webServer>, ASP.NET Core Module Configuration Reference and Using IIS Modules with ASP.NET Core. If you need to set environment variables for individual apps running in isolated Application Pools (supported on IIS 10.0+), see the AppCmd.exe command section of the Environment Variables <environmentVariables> topic in the IIS reference documentation. […]
That said, let’s move on.
If the Web.config file is not present upon the application first run, it will be auto-generated in the root-level folder with the default settings that will best suit the application. That’s a typical example:
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="dotnet" arguments=".\OurWebApp.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" /> </system.webServer> </configuration>
Here’s how we can tweak it to set-up the ASPNETCORE_ENVIRONMENT environment variable (updated lines are highlighted):
<?xml version="1.0" encoding="utf-8"?> <configuration> <system.webServer> <handlers> <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" resourceType="Unspecified" /> </handlers> <aspNetCore processPath="dotnet" arguments=".\OurWebApp.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout"> <environmentVariables> <environmentVariable name="ASPNETCORE_ENVIRONMENT" value="Development" /> </environmentVariables> </aspNetCore> </system.webServer> </configuration>
IMPORTANT: Don’t forget to replace the OurWebApp.dll sample with your actual DLL file!
If we do that right after hitting an IIS error and then reload the affected URL, we should get some detailed info regarding our error who will most likely help us to understand what’s going on.
The “missing Node.js” scenario
The only thing we have to keep in mind is that switching to Development might also require Node.js – together with their relevant NPM modules – to be installed in production server as well: such requirement would most likely raise if we did use some switches – such as dev.IsDevelopment() – within our code to conditionally load NPM dependances in development mode.
If that’s the case, the first error you’ll receive would be the “failed to start Node process” one, just like in the screenshot below:
The above error is caused by the Webpack Dev Middleware, which was loaded inside a env.IsDevelopment() conditional block. If we don’t want to install Node.js in production, we can choose between reworking these middlewares loading strategies or (temporarily) excluding them.
Regardless of what we’ll pick, a thing is for certain: now we can properly address these kind of issues without getting an(other) headache!
IMPORTANT: Be sure to put that value back to Production once the issue has been fixed! Exposing these error details will leak potentially dangerous info regarding your web server configuration settings, thus opening it to harmful attacks. To get a better idea of that, just take a look to the above screenshot to see how many info could have been retrieved from these few lines if we hadn’t uglyfied most of them: the PATH environment variable content could show a lot of info regarding your server, such as installed software, version numbers, system folders and so on.