ASP.NET Core RC2 (Migration Guide)

Migrating from RC1 to RC2

David Pine

9 minute read

Introduction

On Monday, May 16th 2016 there was an amazing announcement from the .NET Blog team!

Announcing .NET Core RC2 and .NET Core SDK Preview 1

This post will serve as a guide that walks you through the steps as I experienced them from migrating existing ASP.NET Core RC1 applications to ASP.NET Core RC2. It is worth mentioning that I’m am targeting the .NET 4.6 framework. So, I do not cover the .netcoreapp or netstandard TFMs (target framework monikers).

Tooling

The RC2 release brings the .NET Core CLI to the forefront, and with that all the other command line tooling that you’ve grown accustomed to should be stricken from your environment, i.e.; DNX, DNU, and DNVM. Let’s start with the removal of all these utilities.

Removing DNVM

Believe it or not, you might have a dated version of the DNVM CLI and in order to invoke the uninstall you might have to first perform an update-self command. Doing so will ensure that you have the latest version, which will contain the uninstall command. From a cmd window running as admininistrator, execute the following:

dnvm update-self

Then execute the following:

dnvm uninstall

Verification

From a new cmd window, the dnvm command should result in the following:

λ dnvm
'dnvm' is not recognized as an internal or external command, operable program or batch file.

Removing DNX & DNU

From a cmd line window, execute dnx - you should see something similar to this as output:

λ dnx
Microsoft .NET Execution environment Clr-x86-1.0.0-rc1-16231

Usage: dnx [options]

Options:
  --project|-p <PATH>              Path to the project.json file or the application folder. Defaults to the current folder if not provided.
  --appbase <PATH>                 Application base directory path
  --lib <LIB_PATHS>                Paths used for library look-up
  --debug                          Waits for the debugger to attach before beginning execution.
  --bootstrapper-debug             Waits for the debugger to attach before bootstrapping runtime.
  --framework <FRAMEWORK_ID>       Set the framework version to use when running (i.e. dnx451, dnx452, dnx46, ...)
  -?|-h|--help                     Show help information
  --version                        Show version information
  --watch                          Watch file changes
  --packages <PACKAGE_DIR>         Directory containing packages
  --configuration <CONFIGURATION>  The configuration to run under
  --port <PORT>                    The port to the compilation server

Likewise enter dnu in the same cmd window, expecting the following output:

λ dnu
Microsoft .NET Development Utility Clr-x86-1.0.0-rc1-16231

Usage: dnu [options] [command]

Options:
  -v|--verbose  Show verbose output
  -?|-h|--help  Show help information
  --version     Show version information

Commands:
  build             Produce assemblies for the project in given directory
  clear-http-cache  Clears the package cache.
  commands          Commands related to managing application commands (install, uninstall)
  feeds             Commands related to managing package feeds currently in use
  install           Install the given dependency
  list              Print the dependencies of a given project
  pack              Build NuGet packages for the project in given directory
  packages          Commands related to managing local and remote packages folders
  publish           Publish application for deployment
  restore           Restore packages
  wrap              Wrap a csproj/assembly into a project.json, which can be referenced by project.json files

Use "dnu [command] --help" for more information about a command.

The DNX and DNU command line tools are simply exe's in your %USERPROFILE%\.dnx and %USERPROFILE%\.dnx\bin directories. Navigate to your %USERPROFILE% directory from windows explorer and delete the .dnx directory entirely. Once you have successfully deleted this directory you should be able to execute the dnx and dnu commands again from a cmd window and the system should complain that they are not recognized.

Verification

From a new cmd window, the dnx and dnu commands should result in the following:

λ dnx
'dnx' is not recognized as an internal or external command, operable program or batch file.
λ dnu
'dnu' is not recognized as an internal or external command, operable program or batch file.

Environment Variables

Unfortunately, the PATH environment variable is not cleaned up and this needs to be done manually.

Press the Windows key, then begin typing “environment”, then select either of the two top options returned:

Windows Search

You are then presented the “system properties” dialog, select Environment Variables.

System Properties

We will need to ensure that both PATH variables are cleaned up, including your %USERPROFILE% and the System variables. Let’s Edit both of them.

Environment Variables

From the Edit Variables dialog, remove any and all of the following:

C:\Users\[ your global identifier ]\.dnx\bin;
C:\Users\[ your global identifier ]\.dnx\runtimes\dnx-clr-win-x86.1.0.0-rc1-update1\bin;

Edit User Variables

.NET Core CLI

Simply follow these instuctions. Once you’re able to “Run the app” from the new CLI you’re good to go.

Review

You should the following checklist fully satisfied at this point.

DNX, DNU and DNVM completely removed / uninstalled Visual Studio 2015 Update 2 installed .NET Core CLI installed


Edit by Hand

There were changes to APIs which required changes to implementation aspects of the source code. Additionally, namespace changes such as (but not limited to) Microsoft.AspNet.* to Microsoft.AspNetCore.*. But the bulk of the work was really in the hand-editing of .sln, global.json, .xproj, project.json, etc. files. Let’s look at what changed.

The .sln file

Change the VisualStudioVersion from 14.0.24720.0 to 14.0.25123.0.

VisualStudioVersion = 14.0.25123.0

The global.json file

Change the version from 1.0.0-rc1-update1 to 1.0.0-preview1-002702.

{
  "projects": [ "src", "test" ],
  "sdk": {
    "version": "1.0.0-preview1-002702"
  }
}

The .xproj file

The .xproj files are a little more involved, there are a few items that are required to be changed. In RC1 we had proprerties that were specific to DNX, now we replace these with DOTNET.

RC1

<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.Props" 
        Condition="'$(VSToolsPath)' != ''" />

RC2

<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.Props" 
        Condition="'$(VSToolsPath)' != ''" />

Additionally, we need to change the targets. This one requires that you know the type of project you’re editing, for example “class library” vs. “web site”. In RC1 there was only one target, but with RC2 they are differeniated.

RC1

<Import Project="$(VSToolsPath)\DNX\Microsoft.DNX.targets" 
        Condition="'$(VSToolsPath)' != ''" />

RC2 (class library)

<Import Project="$(VSToolsPath)\DotNet\Microsoft.DotNet.targets" 
        Condition="'$(VSToolsPath)' != ''" />

RC2 (web)

<Import Project="$(VSToolsPath)\DotNet.Web\Microsoft.DotNet.Web.targets" 
        Condition="'$(VSToolsPath)' != ''" />

The project.json file

There has been a large reworking of the project.json file - detailed here. I will not be covering all of the changes here, but I do intend calling attention to some of the observations I have made and changes that were most pertinent. Here is the link for the official project.json schema.

We will look at various sections of the project.json file changes. Let’s start with replacing compilationOptionsbuildOptions with buildOptions, and notice that we are adding much more options than before.

RC1

"compilationOptions": {
  "emitEntryPoint": true,
  "warningsAsErrors": true
},

RC2

"buildOptions": {  
  "emitEntryPoint": true,
  "warningsAsErrors": true,
  "preserveCompilationContext": true,
  "embed": "Views/**", // Instead of ".:resource": "Views/**",
  "compile": {
    "exclude": [ "wwwroot", "node_modules" ] // Now here, instead of ".:exclude"
  }
},

Additionally, we have publishOptions that we can utilize - consider the following:

"publishOptions": {
  "include": [
    "wwwroot",
    "appsettings.json",
    "web.config"
  ]
},

Finally, we have to be a little more specific with how we want the server to handle garbage collection.

"runtimeOptions": {
  "gcServer": true, // Yes, please perform garbage collection
  "gcConcurrent": true // Yes, please do so concurrently...
},

The web.config file

Now, IIS is a little smarter and is starting to recognize ASP.NET Core a little more.

RC1

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="httpPlatformHandler" path="*" verb="*" modules="httpPlatformHandler" 
           resourceType="Unspecified"/>
    </handlers>
    <httpPlatform processPath="%DNX_PATH%" arguments="%DNX_ARGS%" stdoutLogEnabled="false" />
  </system.webServer>
</configuration>

RC2

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <system.webServer>
    <handlers>
      <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModule" 
           resourceType="Unspecified" />
    </handlers>
    <aspNetCore processPath="%LAUNCHER_PATH%" arguments="%LAUNCHER_ARGS%" 
                forwardWindowsAuthToken="false" stdoutLogEnabled="false" />
  </system.webServer>
</configuration>

The hosting.json file

With the release of RC2, the project.json schema removed the webroot property. Now in order to specify the websites’ static content directory we need to create a hosting.json file. This file should look like this.

{
  "webroot": "wwwroot"
}

The appsettings.json file

This was a minor change, but if you forgot to change this - then you’ll end up with a runtime exception. The Logging:LogLevel:Default should now be Debug as Verbose was removed altogether.

RC1

"Logging": {
      "IncludeScopes": false,
      "LogLevel": {
        "Default": "Verbose", // < -- Change "Verbose"
        "System": "Information",
        "Microsoft": "Information"
      }

RC2

"Logging": {
      "IncludeScopes": false,
      "LogLevel": {
        "Default": "Debug", // < --- To "Debug"
        "System": "Information",
        "Microsoft": "Information"
      }

The launchSettings.json file

The environmentVariables changed, such that the key name for the environment is no longer referred to as “Hosting:Environment”.

RC1

"environmentVariables": {
  "Hosting:Environment": "Development" // < -- Change "Hosting:Environment"
}

RC2

"environmentVariables": {
  "ASPNETCORE_ENVIRONMENT": "Development" // < -- To "ASPNETCORE_ENVIRONMENT"
}

The *.cs files

Some of these were very simple to fix. After updating your project.json with the latest RC2 versions - some changes are as simple as a namespace change (but others are more involed).

RC1

using Microsoft.AspNet.*;

RC2

using Microsoft.AspNetCore.*;

API changes

There was an attempt to unify some of the APIs as it pertains to consistency. As such, I had to make the following changes to my projects. The IApplicationBuilder.Use* pattern now takes a new *Options instance rather than an Action<*Options>, providing more control to the consumer.

RC1

app.UseCookieAuthentication(options =>
{
    options.AuthenticationScheme = "AspNetCookie";
    options.AutomaticAuthenticate = true;
    options.AutomaticChallenge = true;
    options.LoginPath = "/account/login";
    options.LogoutPath = "/account/logout";
    options.AccessDeniedPath = "/account/forbidden";
});

RC2

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AuthenticationScheme = AuthenticationScheme.JciCookie,
    AutomaticAuthenticate = true,
    AutomaticChallenge = true,
    LoginPath = "/account/login",
    LogoutPath = "/account/logout",
    AccessDeniedPath = "/account/forbidden",
});

The service locator pattern (or should I say “anti-pattern”) is slowing being removed from the framework. As such, if you were relying on the CallContextServiceLocator.Locator.ServiceProvider you can no longer do so with the RC2 release.

Wherever you were using the IApplication interface, you should be able to instead use the IWebHost interface.

Controllers as Services

In RC1, there was a nice little feature that allowed for any Type to exist in a list of plausible controller/services - meaning that you could basically register external controllers as valid API and View entry points into your hosting application. You simply needed to add the types via the following:

IMvcBuilder.AddControllersAsServices(params Type[] types);

This method signature changed with RC2 and it no longer accepts any arguments. In order to retain this functionality, you must first add the desired external assemblies as an AssemblyPart.

foreach (var assembly in GetExternalAssemblies())
{
    builder.PartManager.ApplicationParts.Add(new AssemblyPart(assembly));    
}

builder.AddControllersAsServices();

The Startup .ctor

One little change, you’re now required to explicitly set the base path and you can do so with the following.

public Startup(IHostingEnvironment env)
{
    var builder =
        new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath) // Explicity set base from the content root path
            .AddJsonFile("appsettings.json", true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", true)
            .AddEnvironmentVariables();
    // Omitted for brevity...
}

The Program.cs file

Yes, we are now a console application…so we’ll need this for our entry point.

public class Program
{
    public static void Main(string[] arguments)
    {
        using (var host = 
            new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .Build();)
        {
            host.Run();
        }
    }
}

Breaking changes

For a complete listing of all the announcements, please visit the official ASP.NET Repo announcements issue.

Further reading

The previous steps were really to get your environment in an RC2 ready state, but there is so much more you can learn that has changed. I encourage bookmarking and reading the following articles as time permits.

Conclusion

There, that really wasn’t too bad - was it? I’m certain that I didn’t cover everything and that I probably missed something. I hope that this was helpful, please feel free to share this!

comments powered by Disqus