Running a .NET Core Generic Host App as a Windows Service

I received a question at the weekend via a comment from Vadim on my Implementing IHostedService in ASP.NET Core 2.0 post. Vadim asked the following:

We have a lot of console applications that are listening to a queue and performing some work. In .net 4.6 we would make some of these classes inherit from ServiceBase class. Then we would use the sc.exe tool on the server to install these as Windows Services.

We are looking for a similar solution using .net core. The problem is, ServiceBase does not exist in .net core.

It seems like IHostedService is a good alternative. However, the one thing I don’t understand is, how do we take a .net core application with one or more IHostedServices and install them as a windows service? We want to be able to start/stop these applications from some kind of UI and we want them to always be running when the server is up.

This piqued my interest. I could vaguely recall reading something which described the process required to run an ASP.NET Core application as a Windows service. Off I went to Google and found the documentation which I’d previously read. The documentation was focused on running an ASP.NET Core app, hosted on Windows, as a Windows service. This looked like a good starting point.

I created a basic .NET Console application, using the Generic Host pattern and took a look to see if the RunAsService extension, mentioned in the ASP.NET Core documentation, would work. Turns out, it doesn’t, as it’s not available on the IHostBuilder interface, only for the IWebHostBuilder. Next, I thought I’d take a look at the ASP.NET Core Hosting repository to see if I could figure out if this was available under a different name or failing that to see if there were any plans for similar functionality for IHostBuilder. I came across this issue where Chris Ross (aka Tracher) linked to his GenericHostSample application.

Armed with that sample code I set about adding the relevant classes to my own app. It was mostly a copy/paste exercise; so full credit goes to Chris Ross for his sample code.

My next steps were then based on the ASP.NET Core documentation. However, as that document is specific to ASP.NET Core, I thought it would be valuable to summarise the steps required for running a generic host .NET core app as a Windows service here on my blog. I won’t cover everything in as much detail as the full documentation which I suggest you also go and read for additional context and explanation.

NOTE: A complete sample for a basic generic host based .NET Core Windows service can be found on my GitHub repository.

Project File

After creating a new .NET Core console application, the first thing we need to do is to add a reference to the Microsoft.Extensions.Hosting package and the Microsoft.AspNetCore.Hosting.WindowsServices System.ServiceProcess.ServiceController package (thanks to David Fowler for a PR to tidy up my dependencies in the sample).

As well as these package reference, you’ll see that in the main PropertyGroup section we’ve also set the RuntimeIdentifier to win7-x64. The RID (Runtime Identifier) in this case identifies that the target platform for this application is Windows. For more information about RIDs see the documentation. Since Windows Services are only available on Windows, it makes sense to set a windows target platform which will also ensure we produce a .EXE rather than .DLL file when building our application.

In the above sample we can also set the LangVersion to 7.1 to allow me to use an async Main method.


Next we need to setup our application entry point in Program.cs. The Main method is our entry point and in my sample is as follows:

I’ve based this example on the “Host ASP.NET Core in a Windows Service” documentation.

First it performs a check to see if we’re either debugging or if the application has been started having been passed an argument of “–console”. In that case we set the isService flag as false. We’ll use this later to allow us to debug the application from Visual Studio as a standard console application.

Next we create a HostBuilder which we’ll use to create the generic host. In here we register one service which is an implementation of the IHostedService interface. This is how we’ll run code within our application. See my earlier IHostedService post for more detail on how this interface can be used to run background workloads. We’ll look at the implementation for this application a little further down.

Finally, using the isService flag, we now call either the RunAsServiceAsync extension on the builder, or the RunConsoleAsync. The later will run the application as a normal .NET Core console app, perfect for local testing and debugging. The former is the extension method I was missing earlier. This comes from two files I have added, copied from the GenericHostSample mentioned earlier.


I won’t go into the internals of this too deeply. This is entirely copied from the Microsoft sample. This class derives from the ServiceBase class used to define Windows services. It also implements the IHostLifetime interface from Microsoft.Extensions.Hosting.


This class is also copied from the GenericHostSample and adds the RunAsServiceAsync extension method which will ensure the ServiceBaseLifetime is registered as the implementation for IHostLifetime.


This class implements IHostedService and once registered will be started and stopped by the IHost. We follow the generic host documentation and include a Timer which will fire every minute. It will run the code in WriteTimeToFile method which appends the current time to a file. Not particularly useful, but for this example it’s enough to show that something is actually happening. In the original question from Vadim the requirement was to poll a queue and process messages. We could just as easily perform that work here too.

At this point we now have our complete sample application. We can run this directly in Visual Studio to test it, but our main goal is to register this as a Windows service.

Again, if you want to download the source yourself you can do so from GitHub.

Creating the Windows Service

The final step is to build our code and register a service from the executable. We’ll follow the ASP.NET Core documentation to build our code and register a Windows service. Again, I won’t cover the detail here since the referenced documentation provides ample explanation. To summarise though, the steps are as follows:

Build and publish the project from the command line. This command can be run in the directory in which the project resides…

dotnet publish --configuration Release

Next, use sc.exe to create a service, passing the full path of the built executable…

sc create MyFileService binPath= "E:\Projects\IHostedServiceAsAService\IHostedServiceAsAService\bin\Release\netcoreapp2.1\win7-x64\publish\IHostedServiceAsAService.exe"

Next, use sc.exe to start the service (this needs to occur in a command prompt running as Administrator)…

sc start MyService

At this point, our service is running. In my case, I confirmed that a text file called TestApplication.txt appeared in my D: drive (that destination and filename is hardcoded in my sample), which confirms that the service is running as expected. Every minute, while the service is running, I can see a new line added to the text file.


If you’ve followed along, you should now have a .NET Core based Windows service running on your machine. As per the requirement in the original question, you could easily modify this service to read from a queue and process the messages it receives. Personally, I tend to deploy such services as a basic console application, running in Linux based Docker containers. For production workloads, this enables easy scaling and I can manage the services through our container orchestration in AWS ECS. However, should you need a Windows service, then hopefully this post is enough to get you started.

Hopefully, in a future release of ASP.NET Core, this functionality will be included in the framework. If that happens we won’t need to include our own ServiceBaseLifetime and ServiceBaseLifetimeHostExtensions classes. Based on a reply from Chris in the original GitHub issue, that looks like something which may be considered in the 3.0 timeframe as part of some wider hosting work they plan to do.

ASP.NET Core Gotchas – No. 2 Why are settings from launchSettings.json now picked up when using dotnet run?

Following on from my first gotcha that we hit yesterday, here is another one which caught us out at the same time.

TL:DR; When using dotnet run (.NET Core 2.0), expect it to set some properties (including the port and environment) from launchSettings.json if it’s included in the Properties folder!

The issue we faced was that when running a new API using ASP.NET Core 2.0 inside a container, it was starting on the wrong port; not port 80 as we’d expected. The scenario where this can show itself is unlikely to be a common one, so hopefully only a few people will run into this exact gotcha.

In our case, prototyping the new API needed some front end involvement so we added a quick and dirty dockerfile and docker-compose.yml file to enable it to be spun up inside a container. Normally our production containers are all built on images which are based on the ASP.NET Core runtime image. We then copy in our published dlls and run the application inside the container. However, in this particular case we’d cheated a little and were using the SDK based image to allow us to build and then run the source inside a container.

In our case our dockerfile looked like this (note: do not use this in production!)

FROM microsoft/aspnetcore-build:2.0



COPY . .

RUN dotnet restore --verbosity quiet && dotnet build -c Release --verbosity quiet


WORKDIR /app/src/OurNewApi

ENTRYPOINT dotnet run

The details of what it does are not too important. If you’re interested your can read my Docker for .NET Developers series to learn more. What is important here is that we are copying the entire solution folder contents into the container and later using dotnet run to start it. Seems safe enough – right?

As stated earlier, we noticed that for some reason, instead of using the default ASP.NET Core environment variable (ASPNETCORE_URLS=”https://*:80) which defines the default port of 80 it was using a different port. We also noticed that the environment was showing as “Development” and not “Production”.

We then examined the console output and noticed the following information:

Using launch settings from C:\Projects\OurApi\OurApi\Properties\launchSettings.json…

We checked and indeed, the port being used was the one defined in launchSettings.json. This was a bit of a surprise since in the past that file has only been used by Visual Studio. We scratched our heads as we’d previously done something similar with an ASP.NET Core 1.0 project without hitting any issues. I started investigating and soon found a closed GitHub issue titled “Add support for launchSettings.json to dotnet run”. Reading through it, it seems that since 2.0, dotnet run will load up some settings from launchSettings.json if it finds one. This makes some sense from a developer tooling point of view as I guess there are cases where it could be useful. In our case, the fact we were blindly copying in our entire solution folder (including the launchSettings.json file) as well as starting our API using dotnet run, meant that we experienced this behaviour inside the container. It’s not something we normally face, but in this quick prototype, it showed itself.

The quick solution in our case was to include the Properties folder in our dockerignore file which specifies any folders/files that you do not want included in the build context. This then avoids them being copied into your image using the COPY command.

For the curious among you, this functionality is implemented inside ProjectLaunchSettingsProvider.cs in the .NET CLI repository. If the file is found then the properties from the environmentVariables section are used as well as the value of the applicationUrl property.

Long story short, be aware of the fact that since .NET 2.0 the “dotnet run” CLI command will look for and use a launchSettings.json file if it’s available. If you suspect this may be happening in your case you can check the console output to see if it has loaded from launchSettings.json.


Other posts in the ASP.NET Core Gotchas Series:

No. 1 – Why aren’t my Environment Variables loaded into ASP.NET Core 2.0 configuration on Linux?

Upgrading to ASP.NET Core 2.0 My experience of upgrading a real-world solution from ASP.NET Core 1.0 to 2.0

On the 14th of August, Microsoft announced the release of .NET Core 2.0, ASP.NET Core 2.0 and EF Core 2.0, the next major releases of their open source, cross platform frameworks and libraries. This is a very exciting release and one which I hope marks the stabilisation of the framework and enables more developers and businesses to begin really looking at using .NET Core 2.0.

One of the big changes with .NET Core 2.0 is support for the new .NET Standard 2.0 specification (also part of the release announcements) which defines the API surface that platforms should conform to. This brings back around 20,000 APIs that were not originally included in .NET Core 1.x. This should mean that porting existing full .NET Framework applications over to Core may now be a more realistic prospect with much greater parity between the frameworks.

As I have discussed a few times on this blog, I contribute to a fantastic project called allReady, managed by the charity, Humanitarian Toolbox. This project started originally in the early beta days of .NET Core and ASP.NET Core and has evolved along with the framework through the various changes and refinements. With the release of 2.0 we were keen to upgrade the application to use .NET Core 2.0 and ASP.NET Core 2.0. I took it upon myself to attempt to upgrade allReady and to document the experience as I went. Hopefully I’ve found the right balance of detail to readability for this one which has been a bit of an epic!

Installing .NET Core 2.0

The first step you will need to complete is to install the new 2.0 SDK and if you use Visual Studio as your IDE of choice, you will also need to install the latest version of Visual Studio 15.3.x in order to work with .NET Core 2.0. These steps are well documented and pretty easy.

Upgrading the MVC Project

Upon loading the allReady web solution in Visual Studio 15.3 (aka 2017 update 3), my first focus was on upgrading the web project and getting it to run. I therefore unloaded the test project so that I wasn’t distracted by errors from that.

Many of the main steps that I followed as I upgraded the solution can be found outlined in the Microsoft .NET Core 1.0 to 2.0 migration guide.

Upgrading the Project File and Dependencies

The first job was to upgrade the project to target .NET Core 2.0 and to upgrade its dependencies to request the ASP.NET Core 2.0 packages. To do this I right clicked my project and chose to edit the csproj file directly. With .NET Core projects we can now do this without having to unload the project first. .NET Core projects have a targetFramework node which in our case was set to netcoreapp1.0. To upgrade to target the latest Target Framework Moniker (TFM) for Core 2.0 I simply changed this to netcoreapp2.0.

Our project file also included a runtimeFrameworkVersion property set to 1.0.4 which I removed to ensure that the project would use the latest available runtime. The migration guide also specifies that the PackageTargetFallback node and variable should be renamed to AssetTargetFallback and so I made that change.

The next big change was to begin using a new ASP.NET Core meta package to define our dependencies. One of the drawbacks that people have experiences with depending on the many individual Nuget packages which make up ASP.NET Core platform is that management of the package versions can be a bit painful. Each package can have slightly different minor version numbers as they revision separately. During a patch release of ASP.NET Core for example, it can be hard to know which exact versions represent the latest of each of the packages as they don’t necessarily all update together.

The ASP.NET team are hoping to solve this with the availability of a new Microsoft.AspNetCore.All metapackage. This package contains dependencies to all of the common Microsoft.AspNetCore, Microsoft.EntityFrameworkCore and Microsoft.Extensions packages. You can now reference just this package to enable you to work with all of the ASP.NET Core and EF Core components.

One of the changes that enables this is in the inclusion of a .NET Core runtime store which contains all of the required runtime packages. Since the packages are part of the runtime, your app won’t need to download many tens of dependencies from Nuget. The runtime store assets are also precompiled which helps with performance.

To make use of the new meta package I first removed all existing ASP.NET related dependencies from my explicit project package references. I could then add in the following reference: <PackageReference Include=”Microsoft.AspNetCore.All” Version=”2.0.0″ />. 

The final change in the project file was to update the versions for the .NET Core CLI tools specified in the DotNetCliToolReferenence nodes for our project file. In each case I moved them to the 2.0.0 version. With this completed I was able to save and close the project file, which triggers a package restore.

Our project file went from this:

to this:

The next thing I needed to do was to remove a global.json file that we had in our solution which was forcing the use of a specific SDK version; in our case 1.0.1. We want our project to use the latest SDK so I removed this file entirely. At this point I was in a position to attempt to compile the web project. As expected the build failed and a number of errors were listed that needed to work through fixing.

Identity / Authentication Changes

With ASP.NET Core 2.0, some of the biggest breaking changes occur in the Identity namespace. Microsoft have adjusted quite a few things regarding the Identity models and authentication. These changes did require some fixes and restructuring of our code to comply with the new model. Microsoft put together a specific migration document which is worth reviewing if you need to migrate Identity code.

The first change was to temporarily comment out some code we have as an extension to the IApplicationBuilder. I would use this code to ensure I had fully replicated the required setup before removing it. We used this code to conditionally “use” the various 3rd party login providers within our project; for example – UseFacebookAuthentication. One of the changes made with Identity in ASP.NET Core 2.0 is that third party login providers are now configured when registering the Authentication services and are no longer added as individual middleware components.

To account for this change I updated our ConfigureServices method to use the new AddAuthentication extension method on the IServiceCollection. This also includes extension methods on the returned AuthenticationBuilder which we can use to add and configure the additional authentication providers. We conditionally register our providers only if the application configuration includes the required App / Client Id for each provider. We do this with multiple, optional calls to the AddAuthentication method. I’ve checked and this is a safe approach to meet this requirement. At this point I could replicate the 3rd party authentication configuration that we had previously setup using the UseXYZAuthentication IApplicationBuilder extensions.

With this complete, our Configure method could be updated to include the call to UseAuthentication which adds the authentication middleware. The commented code could now be removed.


Our account controller (based on the original ASP.NET Core MVC template) had a dependency on IOptions<IdentityCookieOptions> to get the ExternalCookieAuthenticationScheme name. This is now redundant in 2.0 as these are now available via constants and we can use that constant directly in our login action as per the authentication migration guide.

In 1.0 we set our AccessDeniedPath for the cookie options as one of the options on the AddIdentity extension for the IServiceCollection. Where we previpusly set it as follows:

There is now a specific extension to configure the application cookie where we set this value so I added that code to ConfigureServices.

The next change is that IdentityUser and IdentityRole have been moved from the Microsoft.AspNetCore.Identity.EntityFrameworkCore namespace to Microsoft.AspNetCore.Identity; so our using statements needed to be updated to reflect this change in any classes referencing either of these.

Next on my build error hit list was an error caused by Microsoft.AspNetCore.Authentication.FailureContext no longer being found. This has been renamed to RemoteFailureContext in ASP.NET Core 2.0 so I updated the affected code.

Another change as part of Identity 2.0 is that the Claims, Roles and Login navigation properties which we made use of have been removed from the base IdentityUser class. As a result I needed to add these back into our derived ApplicationUser class directly and update the OnModelCreating method inside our DbContext to define the correct foreign key relationships. This was as described in the migration guide for Authentication and Identity.

A small change I had to take care of is that GetExternalAuthenticationSchemes has been made Async (and renamed accordingly) so I updated our code to call and await the GetExternalAuthenticationSchemesAsync method – The return type has also changed, so I also needed to update one of our view models to take the resulting list of AuthenticationSchemes rather than AuthenticationDescriptions.

The final authentication change was the result of a new set of extension methods being added to HttpContext in Microsoft.AspNetCore.Authentication. These are intended to be used for calling the SingOutAsync and similar methods which were previously available via the IAuthenticationManager.

In places where we called these I changed from

await httpContext.Authentication.ChallengeAsync();


await httpContext.ChallengeAsync();

Other Changes / Build Errors

With the authentication and Identity related changes completed I still had a few build errors to take care of before the application would compile.

In 1.1.0 Microsoft added an additional result type of AcceptedResult (the issue is available here) and a helper method on ControllerBase to easily return this result. Since we had been target 1.0.x we had not faced this change before. Our SmsResponseController was exposing a constant string called “Accepted” which then hid the new inherited member on ControllerBase. I renamed our member to avoid this naming conflict.

We also found that Microsoft.Net.Http.Headers.ContentDispositionHeaderValue.FileName had changed from being defined as a string to a StringSegment instead. This meant we had to update code which was calling Trim on it to first call ToString on the StringSegment value.

In one place we were using a previously available TaskCache.CompletedTask to get a cached instance of a completed Task. However, since Task.CompletedTask is now available due to targeting NetStandard 2.0 this had been removed so our code could switch to using Task.CompletedTask instead.

Other Migration Changes

There are some other structural changes we can and should make to an existing ASP.NET Core 1.x project to take advantage of the ASP.NET Core 2.0 conventions. The first of these was to update program.cs to use the newer CreateDefaultBuilder functionality. This method is designed to simplify the setup of an ASP.NET Core WebHost by defining some common defaults which we previously had to setup manually in the Startup class. It adds in Kestrel and IISIntegration for example. The IWebHost in 2.0 now also sets up configuration and logging, registering them with DI earlier in the application lifecycle. The defaults work for basic applications but depending on your requirements you may need to use the ConfigureLogging and ConfigureAppConfiguration methods to apply additional setup of these components.

Out program.cs changed from:


Now that Configuration and Logging are setup on the IWebHost, we no longer need to define the setup for those components in the Startup.cs file, so I was able to strip out some code from Startup.cs. In 1.x we used the constructor of Startup to use the ConfigurationBuilder to setup Configuration. This could be taken out entirely. Instead we could ask for an IConfiguration object in the parameters which will be satisfied by DI as it is now registered by default.

I was also able to remove the logging setup which used an ILoggerFactory in the Configure method in 1.x. This is now also setup earlier by the IWebHost which feels like a better place for it. It also means we get more logging during the application bootstrapping. One change I made as a result of relying on the defaults for the logging setup was to rename our config.json file to appSettings.json. appsettings.json is included by default using the new CreateDefaultBuilder so it’s better that our config file matches this convention.

Finally, ApplictionInsights is now injected into our application by Visual Studio and Azure using a hook that lets them place code into the header and body tags, so we no longer need to manually wire up the ApplicationInsights functionality. This meant I could strip the registration of the service and also remove some code in our razor layout which was adding the javascript for ApplicationInsights.

From out ConfigureServices method I removed:


From our _ViewImports.cshtml file I removed

@inject Microsoft.ApplicationInsights.AspNetCore.JavaScriptSnippet JavaScriptSnippet

From the head section of our_Layout.cshtml file I removed


Partial Success!

At this point the code was able to compile but I hit some runtime errors when calling context.Database.Migrate in our Configure method:

“Both relationships between ‘CampaignContact.Contact’ and ‘Contact’ and between ‘CampaignContact’ and ‘Contact.CampaignContacts’ could use {‘ContactId’} as the foreign key. To resolve this configure the foreign key properties explicitly on at least one of the relationships.”


“Both relationships between ‘OrganizationContact.Contact’ and ‘Contact’ and between ‘OrganizationContact’ and ‘Contact.OrganizationContacts’ could use {‘ContactId’} as the foreign key. To resolve this configure the foreign key properties explicitly on at least one of the relationships.”

To solved these issues I updated our DbContext fluent configuration in OnModelCreating to explicitly define the relationships and foreign key.



This got me a step further but I then hit the following error:

System.Data.SqlClient.SqlException: ‘The name “Unknown” is not permitted in this context. Valid expressions are constants, constant expressions, and (in some contexts) variables. Column names are not permitted.’

I tracked this down to a migration which sets a default value on an integer column using an enum. I found that I needed to explicitly cast the enum to int to make this migration work as expected.

Another step forward; but I still ran into issues. The next error I received was System.ObjectDisposedException: ‘Cannot access a disposed object.’ from Startup.cs when calling await SampleData.CreateAdminUser();

This was caused by a naughty use of async void for the Configure method. I removed the async keyword and used GetAwaiter().GetResult() instead since async void is not a good idea!

By this point I was really hoping I was getting somewhere. However next I had some odd issues with our TagHelpers. We have two tag helpers used to aid some datetime functionality. The errors I was seeing seemed to be due to the TagHelpers getting invoked for the head and body elements of the page. I’ve yet to spend enough time to track down what causes this so have applied workarounds for now.

On our TimeZoneNameTagHelper we were getting a null object error when this tried to apply for the head tag. We expect a TimeZoneId to be supplied via an attribute which was not present on the head tag and so this resulted in null TimeZoneId when we tried to use it to lookup the time zone with FindSystemTimeZoneById. The temporary fix in this case was to check the TimeZoneId for null and just returning if so.

With our TimeTagHelper I had to do an explicit check within the Process method to ensure the TagName matched “time”. This avoided it being applied for the head and body tags. I have created follow-up issues to try to understand this behaviour.

With these changes in place, the code was finally compiling and running. Yay!

Upgrading the Test Project

With the main web application working I was ready to focus on upgrading the test project and making it compile (and seeing if the tests would pass!) The first step here was updating the project file to target netcoreapp2.0 as I had done with the web project. I also updated some of the dependencies to the latest stable versions. This was partially required in order to restore packages and it also made sense to do it at this point since I already had a lot of changes to include. Some of our dependencies were still old pre RTM packages. I also took the chance to clean out some unnecessary nodes in the project file.

With the packages restoring, attempting a build at this stage left me with 134 build errors! Some as a result of changes to Identity, some due to upgrading the dependencies and some due to code fixes made to the main project as a result of the migration.

The first broken tests I focused on where any that had broken due to the Identity changes. These were relatively quick to update such as fixing changes namespaces.


I then had a number of tests which were broken due to a change in Moq, the library we use for mocking objects in our tests. When setting up methods of mocked objects we could previously return a null quite simply passing null as the parameter to ReturnsAsync. However there is now another extension method also accepting a single parameter and the compiler is not sure which one we are intending to use. So this now requires that we explicitly cast this as a null of the correct type to indicate we are passing the expected value and not a delegate which returns the value. This resulted in me having to update 46 tests.

The remainder of build failures were mostly caused by changing the number of parameters for the AccountController constructor so our tests which were creating one as the subject under test needed to be updated also to match the correct number of parameters.

At this point I had compiling test code and I was then able to run my tests! Oh, 101 failed tests!

When I looked a little deeper I noticed these were nearly all tests which used our InMemoryContextTest abstract base class which includes a registered instance of an InMemory DbContext on an IServiceProvider. With a bit of trial and error I realised that my queries were not returning any results, where previously they had in 1.o. When I experimented I found that it was in cases where our query called Include to eager load some of the related entities. However, our seed data for the test which populated the InMemory database for each test had not set those related entities. The InMemory provider does not enforce referential integrity and so there are no errors thrown when saving objects with missing required navigational properties.

In 1.x the query behaviour worked under this scenario but in 2.0 something had changed. I raised an issue about this one and the EF team responded quickly with… “The reason for the behaviour change is that now include is using navigation rewrite logic to construct the queries (whereas before we manually crafted include statements). Navigation rewrite produces INNER JOIN pattern for required relationships and LEFT JOIN pattern for optional. Before we would always hand-craft LEFT JOIN pattern, regardless of the relationship requiredness between child and parent.”

To correct for this I needed to ensure our test setups added the required related entities so that they would be returned from the queries as expected. In our actual code, running using the SqlProvider this is not an issue since the saves enforce the referential integrity.

With the tests fixed up I was finally at a point where everything compiled, ran and the tests were passing. I considered this a good place and was able to submit my PR to get the allReady project to 2.0 which was promptly merged in.


For the most part the migration documentation provided by Microsoft where very good and covered many of the things I actually experienced. In a few cases I found little extra things I needed to solve. For the most part the issues were around the tests and the EF changes probably took longest to isolate and then fix-up. It’s great to have been able to help move the project forward and get it to 2.0 very soon after release. It’s a great reference project for developers wanting to view (and hopefully work on) a real-world ASP.NET Core 2.0 solution. Hopefully my experience will help others during their migrations.