Updating to ASP.NET Core

Over the weekend, I updated this blog from ASP.NET to ASP.NET Core. I thought I would go over some of the issues I ran into in the process.

Porting the code

The actual porting of the code was reasonably straightforward, since there really isn't much code at all running this site. Since the project format is different, I started fresh with the ASP.NET Core MVC template. I removed what I didn't want and then copied in my existing code, making sure to preserve my Git history, of course. Then, there were just a few things that needed to be fixed in order to get it building and running correctly again:

  • Removing usage of RouteValueDictionary for calls to Html.ActionLink in my Razor files. I replaced those with anonymous types.
  • Replacing calls to HttpNotFound with NotFound in my controller.
  • Removing usage of HttpContext.Server.MapPath. There's no direct replacement, but I used IHostingEnvironment.ContentRootPath to build the paths I needed. You just have to let the framework inject IHostingEnvironment into the constructor of your controller. This is a change that I was happy to make because removing the dependency on HttpContext should make it a lot easier to unit test that controller, which I had avoided for that reason up to this point.

Porting the unit tests

The small number of unit tests that I have actually built without modification. However, they no longer passed. The issue was that apparently the DeploymentItem attribute isn't implemented for .NET Core (yet?). I was using that to deploy some input files for my tests. My workaround for now is to add a test initialization method that sets the current directory to where those files are located in the build output and then a test cleanup method that sets it back to where it was originally.

Recreating the CI/CD build

I have a continuous deployment build ("pipeline") on Azure DevOps that deploys the site automatically whenever I push changes to the repository. With the new project format and build system, I pretty much had to re-do that completely to use the dotnet CLI tool for all the main steps (dotnet restore, dotnet build, dotnet test, dotnet publish). I was surprised that my unit test workaround didn't give me problems here, but it seems to work fine on the build agent.

Fixing the publishing/deployment

This is the only part that really caused me problems. Everything was working fine locally, but I was having some strange issues when I deployed it to the Azure App Service. Most of the pages worked, but the main page appeared as a 404. Apparently the 404 was actually just hiding some exceptions, which I saw when I temporarily set the ASPNETCORE_ENVIRONMENT to Development for debugging purposes. The more straightforward problem was that some of my data files were missing from the published directory. I was able to fix that by adding this to my .csproj file:

<None Include="folder_name\**">
    <CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</None>

The other problem was that I was getting exceptions about missing types and assemblies. I debugged this by creating a brand new app service instance and seeing that it worked fine there. I eventually figured out that it had to do with extra files that were still on the server from my previous ASP.NET (non-Core) deployment. I fixed that by doing a manual publish with a publish profile (.pubxml file) containing a line like this:

<SkipExtraFilesOnServer>False</SkipExtraFilesOnServer>

That line tells the deployment to delete any extra files on the server. I'm not sure there's a way to set the deployment build step in Azure DevOps Pipelines to do that every time, but for me, doing it once manually worked.

Conclusion

Overall, updating wasn't too hard. Although there were a few annoying problems, I was able to figure them out in a weekend. There's no immediate benefit in this for me, but I always like to have my projects on the latest technology, if possible, in order to make future maintenance easier. Plus, it's just good learning experience. And if I ever want to try running this on Linux or inside of a container, that should be easy to do now.