Deploying an ASP.NET Core application to Windows IIS

This article explains how to deploy an ASP.NET Core application to a Windows hosted IIS Server. Most applications are now deployed to a cloud hosted solutions like Azure App Services, but sometimes it is required to deploy to an IIS. I ran into an number of issues, which weren’t very well documented, which surprised me a bit, but then who doesn’t deploy to Azure.

Code: https://github.com/damienbod/AspNetCoreIISDeployment

Start with the Docs

Microsoft provide good documentation here:

https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.1

This works really well, until you reach the web deployment part. Then it starts going wrong.

What goes wrong?

At first I tried to setup the recommended web deployment solution, and it just didn’t work. Could not find the correct links, of find the way to configure this. I recommend using a publish to folder and then copy this to the target folder on the deployment server. If you are using VSTS, or Bamboo to automate this later in the CI build, this will work really well.

Then it is really important to match the dotnet run-time version with the version used to build the application. You could fix the APP version in the csproj file. If you use the .NET SDK 2.1.4 to develop, then use the same run-time on the deployment servers. This should be then documented as part of the release process, saves you time if you need to re-deploy this 6 months later.

Configure the website, Host

As a demo software, we would like to deploy 2 applications:

  • demo.deployment.com
  • stsdemo.deployment.com

The STS is the secure token server, and the demo application is an ASP.NET Core MVC application which uses the STS to login. The STS uses a simple Microsoft SQL Server database.

Define the website names, and create the certs for the website domain names. You could use a self signed cert, but if deploying for use on the internet, then you must use a proper cert!

If deploying on an internal network, try using a sub domain of a proper cert. Avoid using the self signed cert if possible.

The IIS website needs to be configured and setup. Like in the Microsoft documentation, create the website, and switch the application pool. Configure for HTTPS.

Create the self signed certs for development.

Using powershell:

New-SelfSignedCertificate -DnsName "demo.deployment.com", "demo.deployment.com" -CertStoreLocation "cert:\LocalMachine\My"

New-SelfSignedCertificate -DnsName "stsdemo.deployment.com", "stsdemo.deployment.com" -CertStoreLocation "cert:\LocalMachine\My"

Now create the website in the IIS manager with the HTTPS set. The Microsoft docs describes this well:

Microsoft Docs / Create the IIS site

Here’s an example of a IIS website settings using HTTPS:

Set the application pool correctly for the ASP.NET Core application:

Configure the HTTPS server hosts file. The hosts file can be found at:

C:\Windows\System32\drivers\etc

Open a notepad editor as an administrator and configure the hosts files.

Add the website mapping:

127.0.0.1 demo.deployment.com
127.0.0.1 stsdemo.deployment.com

Add Logging to the application

You must add logging to the applications before deployment, otherwise you will have no chance of figuring out why the application does not run.

I use Serilog here with a rolling flat file, like in the Serilog documentation.

Add the packages to the csproj file:

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.1</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" Version="2.1.4" />
    <PackageReference Include="Serilog.AspNetCore" Version="2.1.1" />
    <PackageReference Include="Serilog.Sinks.RollingFile" Version="3.3.0" />
    
    ...

Add the logging to the program.cs

public class Program
{
	public static int Main(string[] args)
	{
		Log.Logger = new LoggerConfiguration()
	   .MinimumLevel.Debug()
	   .MinimumLevel.Override("Microsoft", LogEventLevel.Information)
	   .Enrich.FromLogContext()
	   .WriteTo.RollingFile("logs/log-{Date}.txt")
	   .CreateLogger();

		try
		{
			Log.Information("Starting web host");
			BuildWebHost(args).Run();
			return 0;
		}
		catch (Exception ex)
		{
			Log.Fatal(ex, "Host terminated unexpectedly");
			return 1;
		}
		finally
		{
			Log.CloseAndFlush();
		}

	}

	public static IWebHost BuildWebHost(string[] args) =>
		  WebHost.CreateDefaultBuilder(args)
		  .UseStartup<Startup>()
			  .UseSerilog() // <-- Add this line
			  .Build();

}

Remove the logging configuration from the startup.cs

Configure the Database

If the IIS hosted application does not connect to the database due to a failed login, this needs to be configured.

Here’s the logs, if this goes wrong:

2018-09-21 11:07:23.003 +02:00 [Error] An unhandled exception has occurred while executing the request.
System.Data.SqlClient.SqlException (0x80131904): Cannot open database "STSDemoDeploymentProd" requested by the login. The login failed.
Login failed for user 'XXXXXXX'.
   at System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling)
   at System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)
   at System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)
   at 

To connect to the database, the login used in the Application Pool of the website, needs to be included in the logins of the database. Here are very useful links:

https://www.loganfranken.com/blog/1345/why-your-web-application-cant-connect-to-sql-server/

https://blogs.msdn.microsoft.com/ericparvin/2015/04/14/how-to-add-the-applicationpoolidentity-to-a-sql-server-login/

Change the user in the application pool or get the configured, and add or configure this in the database server to allow a database login.

Configure the Deployed Applications to work with self signed Certs

When using a self signed certificate, the demo application login will fail, because the certificate cannot be used to validate.

The following logs shows this:

2018-09-21 10:55:39.513 +02:00 [Error] An unhandled exception has occurred while executing the request.
System.InvalidOperationException: IDX20803: Unable to obtain configuration from: 'https://stsdemo.deployment.com/.well-known/openid-configuration'. ---> System.IO.IOException: IDX20804: Unable to retrieve document from: 'https://stsdemo.deployment.com/.well-known/openid-configuration'. ---> System.Net.Http.HttpRequestException: The SSL connection could not be established, see inner exception. ---> System.Security.Authentication.AuthenticationException: The remote certificate is invalid according to the validation procedure.
   at System.Net.Security.SslState.StartSendAuthResetSignal(ProtocolToken message, AsyncProtocolRequest asyncRequest, ExceptionDispatchInfo exception)
   at System.Net.Security.SslState.CheckCompletionBeforeNextReceive(ProtocolToken message, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartSendBlob(Byte[] incoming, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.ProcessReceivedBlob(Byte[] buffer, Int32 count, AsyncProtocolRequest asyncRequest)
   at System.Net.Security.SslState.StartReadFrame(Byte[] buffer, Int32 readBytes, AsyncProtocolRequest asyncRequest)

To fix this, you need to add this to the OpenID Connect client configuration, and add the BackchannelHttpHandler definition:

.AddOpenIdConnect(options =>
{
	options.SignInScheme = "Cookies";
	options.SignOutScheme = "OpenIdConnect";
	options.Authority = "your_authority";
	...
	options.TokenValidationParameters = new TokenValidationParameters
	{
		NameClaimType = "name"
	};
	options.BackchannelHttpHandler = new HttpClientHandler()
	{
	    // CAUTION USING THIS !!!
	    ServerCertificateCustomValidationCallback = 
			HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
	};
});

Set the environment of the target system

The application settings need to be deployed, set to match the deployment target. There is a number of ways of doing this, for example you could use variables in the app.settings, which well be replaced in the VSTS build. Or you could use different app.settings files, which are then used for the different targets.

Andrew Lock provides a good article about this.

Important

When setting the ASPNETCORE_ENVIRONMENT, you need to set this for the machine and not just the user.

Open a cmd as an administrator:

setx ASPNETCORE_ENVIRONMENT Development /M

The APP_POOL also needs to use the User profile:

See this issue:

https://stackoverflow.com/questions/40085077/asp-net-core-hosting-environment-variable-ignored

Answer Text from Stack Overflow:

As said in this similar question, the trick was simply to set the app pool to load the user variables (IIS -> Server -> App Pools -> Right click on pool -> Set application pool defaults… -> Load User Profile = True).

I configured only one of my app pools accordingly, thus only one of the sites could access the environment variables.

Summary

Once everything is working, now you should automatic the whole process and use a CI for this.

For further reading about ASP.NET Core IIS deployment, Rick Strahl provides an excellent article on the subject.

Links

https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/?view=aspnetcore-2.1

https://www.loganfranken.com/blog/1345/why-your-web-application-cant-connect-to-sql-server/

https://weblog.west-wind.com/posts/2016/Jun/06/Publishing-and-Running-ASPNET-Core-Applications-with-IIS

https://andrewlock.net/how-to-set-the-hosting-environment-in-asp-net-core/

https://github.com/serilog/serilog-aspnetcore

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/environments?view=aspnetcore-2.1

https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/?view=aspnetcore-2.1&tabs=aspnetcore2x

https://stackoverflow.com/questions/44460472/issue-with-website-deployed-to-iis-and-connecting-to-sql-server-2012-getting-ne

https://docs.microsoft.com/en-us/aspnet/core/host-and-deploy/iis/troubleshoot?view=aspnetcore-2.1

https://docs.microsoft.com/en-us/powershell/module/pkiclient/new-selfsignedcertificate?view=win10-ps

Advertisements

2 comments

  1. […] Deploying an ASP.NET Core application to Windows IIS – Damien Bowden […]

  2. […] Deploying an ASP.NET Core application to Windows IIS (Damien Bowden) […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: