Deploying an Application Which Was Updated to .NET Core 2.0

Today’s header image was created by Kevin at Unsplash
This post is going to be about the issues I had when deploying my dwCheckApi application after upgrading it to .NET Core 2.0
not sure what dwCheckApi is? Check out everything I’ve written about it before, by clicking here
Back on August 27th, I decided that it was time to upgrade dwcheckApi to .NET Core 2.0 – what with it having been officially released the week before.
although I’d been playing with the previews of version 2 since they were first released.
The first thing I decided to do was draw a line in the sand and create a release for version 1.0 of dwCheckApi, which meant creating a branch to keep v1.0 separate from any development I was about to do.
It’s common practise to do this. That way, if I need to do any bug fixes specific to that branch, I can
Before we continue, I’m going to split this post into two parts:
- upgrading dwCheckApi to .NET Core 2.0
- the issues I had when deploying dwCheckApi once I’ve upgraded it
If you want to jump straight to the issues, then you can click here.
I’d recommend that you read the whole post, but I’m not the boss of you.
We’re Off To See The Wizard
Converting the whole project to .NET Core 2.0 was mostly quite simple.
The first thing I needed to do change my csproj to reference the new packages. Here is the relevant section of my old csproj file:
<ItemGroup> | |
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.2" /> | |
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="1.0.2" /> | |
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.0.1" /> | |
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.*"> | |
<PrivateAssets>All</PrivateAssets> | |
</PackageReference> | |
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.0"> | |
<PrivateAssets>All</PrivateAssets> | |
</PackageReference> | |
</ItemGroup> |
Talk about a lot of references. In ASP.NET Core 2.0, there’s a single meta package
one meta package to rule them all… sort of
called Microsoft.AspNetCore.All. This package contains all of ASP.NET Core version 2.0, which means that I no longer needed to reference 20 packages just to get ASP.NET Core running nicely.
I’ll talk about all of the packages we don’t need, in a minute
So replacing all of those references with a single reference to the meta package, we get:
<ItemGroup> | |
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> | |
<PackageReference Include="NETStandard.Library" Version="2.0.0" /> | |
</ItemGroup> |
if you’re not sure what .NET Standard is, take a look at these articles
But That Meta Package Has Everything!
You’re absolutely right, and if you look at the dependencies for the meta package you can see that, too.

Click to enlarge
That’s a lot of packages, and I don’t need most of them. So what’s a dev to do?
Well, you don’t have to do anything because of the Runtime Store. I covered the Runtime Store back in May when I covered the events at Build 2017.
you can click here to read that post
Here’s the description of the Runtime Store that Dan Roth gave, way back then:
We have a Runtime store now that ships with .NET Core that includes all of ASP.NET Core in it. That Runtime Store has already been cross gen (compiled into native code).
Combine that with the tree shaking that .NET Core does by default, and you don’t have to worry about any unused references in your project. How great is that?
Program.cs
Then there was the matter of the WebHostBuilder.
Back in ASP.NET Core 1.x days, we had to manually build our web host using the startup class. As a little reminder, we used to do this in our program.cs:
using System.IO; | |
using Microsoft.AspNetCore.Hosting; | |
using Microsoft.AspNetCore.Builder; | |
namespace dwCheckApi | |
{ | |
public class Program | |
{ | |
public static void Main(string[] args) | |
{ | |
var host = new WebHostBuilder() | |
.UseKestrel() | |
.UseContentRoot(Directory.GetCurrentDirectory()) | |
// .UseUrls("http://0.0.0.0:5002") //<-for docker | |
//.UseUrls("http://0.0.0.0:5000") //<-for docker | |
.UseIISIntegration() | |
.UseStartup<Startup>() | |
.Build(); | |
host.Run(); | |
} | |
} | |
} |
Then, in startup.cs we would need to create the web host in the constructor like this:
public class Startup | |
{ | |
public Startup(IHostingEnvironment env) | |
{ | |
var builder = new ConfigurationBuilder() | |
.SetBasePath(env.ContentRootPath) | |
.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) | |
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) | |
.AddEnvironmentVariables(); | |
Configuration = builder.Build(); | |
} | |
public IConfigurationRoot Configuration { get; } |
Well, the folks over at ASP.NET Core towers
it’s not really a set of towers, it’s a github repo
decided that, since we were all doing the same stuff in our WebHostBuilders, that they would take the common stuff out and do it for us. This means that the WebHostBuilder in an ASP.NET Core 2.0 application exists in the program.cs and looks like this:
using System.IO; | |
using Microsoft.AspNetCore; | |
using Microsoft.AspNetCore.Hosting; | |
namespace dwCheckApi | |
{ | |
public class Program | |
{ | |
public static void Main(string[] args) | |
{ | |
BuildWebHost(args).Run(); | |
} | |
public static IWebHost BuildWebHost(string[] args) => | |
WebHost.CreateDefaultBuilder(args) | |
.UseStartup<Startup>() | |
// might need this anyway | |
.UseContentRoot(Directory.GetCurrentDirectory()) | |
.Build(); | |
} | |
} |
And the startup.cs constructor looks like this:
public class Startup | |
{ | |
public Startup(IConfiguration configuration) | |
{ | |
Configuration = configuration; | |
} | |
public IConfiguration Configuration { get; } |
I don’t know about you, but this is much simpler.
you can still alter the defaults in the WebHostBuilder, but most of the defaults are fine for standard applications
The WebHost Class
Taking a look over at the source for the WebHost class, we can see exactly how the CreateDefaultBuilder actually works
the glory of open source development
public static IWebHostBuilder CreateDefaultBuilder(string[] args) | |
{ | |
var builder = new WebHostBuilder() | |
.UseKestrel() | |
.UseContentRoot(Directory.GetCurrentDirectory()) | |
.ConfigureAppConfiguration((hostingContext, config) => | |
{ | |
var env = hostingContext.HostingEnvironment; | |
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) | |
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: true); | |
if (env.IsDevelopment()) | |
{ | |
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName)); | |
if (appAssembly != null) | |
{ | |
config.AddUserSecrets(appAssembly, optional: true); | |
} | |
} | |
config.AddEnvironmentVariables(); | |
if (args != null) | |
{ | |
config.AddCommandLine(args); | |
} | |
}) | |
.ConfigureLogging((hostingContext, logging) => | |
{ | |
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging")); | |
logging.AddConsole(); | |
logging.AddDebug(); | |
}) | |
.UseIISIntegration() | |
.UseDefaultServiceProvider((context, options) => | |
{ | |
options.ValidateScopes = context.HostingEnvironment.IsDevelopment(); | |
}); | |
if (args != null) | |
{ | |
builder.UseConfiguration(new ConfigurationBuilder().AddCommandLine(args).Build()); | |
} | |
return builder; | |
} |
Which, pretty much, covers the majority of our configuration options. right?
Going N-Tier
The original design for dwCheckApi was to be a simple API which called a database and returned some JSON.
But that doesn’t mean that I should have had all of the code in one project, in fact doing that is an anti-pattern for sure. Separation of Concerns is pretty much shot when you do this, and the entire code base is then tightly coupled.
I like examples, so here’s the directory structure for version 1 of dwCheckApi:

As you can see, everything which isn’t a test, GitHuib repo setup, or docker/deployment stuff is in the src directory and everything within the src directory is included in a single csproj file.
not good
Refactoring dwCheckApi to N-Tier had a bunch of positives, the best of which was the ability to separate my concerns
if you’re not separating your concerns, then you might be doing it wrong.
This lead to an architecture which looks a little like the following:

By separating my concerns and applying an N-Tier architecture
even to an application as small as dwCheckApi
I was able to build and maintain parts of the system in a much more agile way. Plus applying namespacing to everything, once it had been split out was a great help.
y’all shouldn’t need me to tell you how great this is
As a side note:
This was before I started getting into Domain Driven Design and the Onion Architecture
That’s about enough about the changes, I think. You can take a look through the GitHub repo to see the list of changes that I made in order to create version 2
including at least one pull request from GitHub user hatalaef. So my thanks to them 😀
Publishing
This is the part you all came here to read, right?
Cast your mind back to when I wrote about continuous delivery using AppVeyor and you’ll remember that we set up an AppVeyor yml file. This file would be read by AppVeyor each time that we commit a change to master, it would:
- restore packages
- create a release build
- run all unit tests
- publish to Azure
As a quick reminder, here’s the file that we built:
version: 1.1.{build} | |
image: Visual Studio 2017 | |
branches: | |
only: | |
- master | |
init: | |
- git config --global core.autocrlf true | |
before_build: | |
# Display minimal restore text | |
- cmd: dotnet restore ./src/src.csproj --verbosity m | |
build_script: | |
# output will be in ./src/bin/debug/netcoreapp1.1/publish | |
- cmd: dotnet publish ./src/src.csproj | |
artifacts: | |
- path: '\src\bin\Debug\netcoreapp1.1\publish' | |
name: WebSite | |
type: WebDeployPackage | |
test_script: | |
# tests in here | |
- cmd: dotnet restore ./tests/tests.csproj --verbosity m | |
- cmd: cd tests | |
- cmd: dotnet xunit | |
deploy: | |
- provider: Environment | |
name: dwCheckApi-Production |
Upgrading dwCheckApi to .NET Core 2.0 was a pretty seamless process – change a few package references, change the layout of the csproj, change the program and startup classes, alter the Appveyor yml file. And even moving it to N-Tier architecture was easy.
Publishing was not.
Errors, Errors Everywhere
Before I go into what happened, I want to cover what the AppVeyor script was doing. The big issue was in the following line:
- cmd: dotnet publish ./src/src.csproj |
dotnet publish does a lot of stuff for us, in fact:
dotnet publish compiles the application, reads through its dependencies specified in the project file, and publishes the resulting set of files to a directory.
source: https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-publish
Remember when I talked about publishing to a Digital Ocean droplet and how I mentioned that there were two different types of publishing options?
you don’t? well, click here and find out what it was that I said
Well, what happens when you do a publish is that .NET Core’s tooling ships the framework dependencies with your application. It basically says:
You rely on ASP.NET Core v1.1? Well, I’d better package that up with your application
That sort of thing.
there are some of you who will be starting to cringe right about now, because you know what’s coming
The problem is, though. That the contents of the publish directory on the server are not cleared out when you do a publish to it. This isn’t a huge issue when you’re using the same version of packages, or
and here comes the crucial part
the packages contain the same number of DLLs.
I’ll go into how I solved it in a moment, but to stop you from pulling your hair out, here’s what happened:
MetaPackages
ASP.NET Core 2.0 brought with it the Microsoft.AspNetCore.All meta package.
as I mentioned earlier
This package was created in order to make tracking dependencies easier. Back in the ASP.NET Core 1.x days, you had to manage all of your ASP.NET Core dependencies manually.
Remember V1 of dwCheckApi’s csproj from earlier?
<ItemGroup> | |
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="1.0.2" /> | |
<PackageReference Include="Microsoft.AspNetCore.Routing" Version="1.0.2" /> | |
<PackageReference Include="Microsoft.AspNetCore.Server.IISIntegration" Version="1.0.1" /> | |
<PackageReference Include="Microsoft.AspNetCore.Server.Kestrel" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.Extensions.Logging" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.Extensions.Logging.Console" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.Extensions.Configuration.Json" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.Extensions.Configuration.EnvironmentVariables" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="1.1.*" /> | |
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Design" Version="1.1.*"> | |
<PrivateAssets>All</PrivateAssets> | |
</PackageReference> | |
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="1.1.0"> | |
<PrivateAssets>All</PrivateAssets> | |
</PackageReference> | |
</ItemGroup> |
If you wanted to consume an updated version of any of those packages meant that you had to go through and change each of the version numbers manually.
a new version of one package might have required a new version of another, etc.
Then along came the meta package which abstracted all of that away, but requiring only one reference.
Now take another look at V2 of dwCheckApi’s csproj:
<ItemGroup> | |
<PackageReference Include="Microsoft.AspNetCore.All" Version="2.0.0" /> | |
<PackageReference Include="NETStandard.Library" Version="2.0.0" /> | |
</ItemGroup> |
One reference to keep on top of; one package to restore; one dependency.
Here’s What Happened
The problem with that, is that dwCheckApi would ask the .NET Core runtime for version 2.0 of one of the AspNetCore DLLs
Microsoft.AspNetCore.Mvc was the first DLL requested for dwCheckApi
And the .NET Core runtime would do what the .NET runtime does:
- start in the directory of the app and search there
- then search the equivalent of the GAC (which is the runtime store in .NET Core)
- throw an error if it can’t be found
Because the publish directory on Azure hadn’t been cleared out before publishing to it, there were V1.x versions of some of the ASPNET.Core dependencies still present. This meant that when the app booted, it would look in the current directory for V2.x versions of it’s dependencies, find V1.x versions throw a 502.5 errors
which are pretty vague errors, to be honest
Deleting all V1.x DLLs from the publish directory on Azure would fix issue, as I would soon find.
However, the automatic way to stop this from happening when you deploy from AppVeyor
which is the real reason that you’ve stuck around so long, right?
is to take a look at the config in AppVeyor and check this little box:

PSA: AppVeyor will combine your yml and config settings when running a build/deploy job
How I Fixed It
Before I start: this is not how I would recommend that you fix the issue I ran into. I would recommend that you use the setting from the previous section.
Read on, only if you want to see how I figured out how to fix the issue.
What I Should Have Done
I should have used the checkbox from the previous section.
I didn’t
Failing that, once I figured out what was going on, I should have used the FTP access that Azure gives you. I’d have been able to clear out the old files quicker.
I didn’t
I used the terminal.
you know how much I love the terminal, right?
Azure gives you access to the terminal for an app instance. It allows you to do almost anything that you can do with the terminal in your operating system.
no, I didn’t try wiping the hard drive that my app was running on… maybe next time 😛
The first thing I did was try to start the app, that way I’d get much more detailed output on the error:
dotnet dwCheckApi.dll |
you should always know the bear minimum required to get you app up and running
Which gave the following response:
D:\home\site\wwwroot | |
Unhandled Exception: System.IO.FileLoadException: Could not load file or assembly 'Microsoft.AspNetCore.Hosting.Abstractions, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. The located assembly's manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040) | |
at dwCheckApi.Program.Main(String[] args) |
Which told me everything I needed to know: that I was in DLL hell.
look at the version number of the DLL in that output
I then needed to delete all of the old DLLs. Again, I should have dropped out of the terminal and used an FTP client for this, it would have been so simple.
Yet, I persisted on with the terminal. This was the long way to fixing the deployment:
- run the app from the terminal
- delete any dependency with didn’t match and caused the above exception to be raised
- goto 10 until no exceptions
- redeploy the application (to make sure that I hadn’t broken the deployed version)
It was a long process, and I had to us rm
to delete files and rmdir /S /Q
for any directories which needed to be deleted. The /Q switch means “quite mode” (i.e. delete directories without confirmation).
this is required because the Azure terminal will prompt you with a Yes/No, but not actually wait for your response.
Extra Hints
Azure App Insights! Seriously, enable this on all of your Azure Applications. You can thank me later. This will even allow you to do offline debugging which, if I’m honest, is wizardry.
The other thing that I’d recommend is using the Kudu functionality for your applications. Did you know that if you add “scm” to the web address of your app, you’ll get a bunch of useful logs and tools?
the location of “scm” will be dependant on a bunch of things, check out this article for more information
Here’s what dwCheckApi’s Kudu tools looks like:

So there you have it: dwCheckApi has been upgraded to .NET Core 2.0, deployed to Azure, and actually booted and running.
You can even access it be clicking here
Have you upgraded any applications to .NET Core 2.0? Did you have any issues in deploying it or was it smooth sailing?
Do you think .NET Core 3.0 will be as smooth an upgrade path?
If you’ve not deployed anything that you’ve written in .NET Core yet, why not?