Migrating Asp.Net 5 rc1 to Asp.Net Core 1.0.0

Net Core 1.0.0 has been released some time ago and is here to stay. Maybe it's time to move "old" dnx projects forward and switch to dotnet? Because, as someone avesome said: new is always better!

New is always better

There is a pretty good official Migration gude, but if you want some more detailed explanation, read on.

What's new?

The first change you will immediately notice are the commandline tools. Dnu and dnx were replaced with one, omnipotent, extensible dotnet command. Dnvm is gone too - installation is handled by installers or install scripts and the proper version selection is baked right into dotnet. You can still have multiple versions of SDK installed, but there is nothing similar to dnvm use - you have to specify the desired version in global.json file.

How stable is stable?

Versioning of .Net Core (the framework) and versioning of commandline tools (SDK) are two different stories now. As .Net Core by itself is RTM, CLI tools are still in preview.

But you cannot compile anything without the toolchain, right? Right.. and once you manage to compile and run your code against .Net Core RTM, then it should be stable from there and can be safely deployed on a server.

It's also worth noting that ASP.NET Core 1.0 and Entity Framework Core 1.0 have also been released - so you're not left with a stable core framework and unstable libraries.

What has changed?

These are a few areas that have changed. Some of them will require more work to get them working, some will be just a matter of simple find-and-replace:

  • project.json
    • project dependencies
    • commands => tools
    • framework monikers
    • some minor section reorganizations
  • application loading and startup model
  • ASP.NET Core - namespace, nuget versions and some APIs

Project.json file

Project dependencies

For me, the most important breaking change in project.json file (at least for me) is the notion of project dependencies. Before, if you wanted to reference another project in your source tree, you could just specify an empty version string to hint the tooling that this is a project dependency. Now, you need to explicitly name it as a project dependency, like this:

"dependencies": {
    "my.other.project": { "target": "project" }
}

This will instruct dotnet to look only for a source project, not a package. Althouth this is fairly easy to fix, it immediatly broken most of my projects compilation, because I had project references with empty version strings all over the place.

You may also specify a version number, just like with package dependencies and if you have a project with that name in one of the src directories listed in global.json, it will also be treated as a project dependency. In case you want to explicitly avoid thatand force using a package, you can specify "target": "package".

Commands vs. Tools

Commands section has now been removed - instead there is tools section now, but it works quite differently than old commands. Since the application startup model has changed and is now more bare-bone - so are the tools. .Net Core CLI extensibility model explains in-depth, how the tools work.

There are basically 3 types of tools that can extend dotnet command.

The simplest one is PATH tools - every executable file named dotnet-something that is on PATH can be called as dotnet something - pretty much how git does it with git subcommands. All the invocation arguments are passed to that command, then it's up to the tool to do it's work.

Second type of tools are the project tools that are specified in tools section. These are portable console appications, which are restored with dotnet restore and have a separate dependency graph. They can only be run in project's context. This means that when run, they can easily locate project files and outputs and manipulate them. An example of such command is dotnet-publish-iis.

This brings us to the third type of tools - tools that need to load project output binaries, like for example dotnet-test-xunit. As mentioned before, tools have a separate dependency graph. But in order to load project output binaries, the tool dependency graph and project dependency graph have to be unified - otherwise all sort of bad things may happen. In order to achieve this, a simple trick is made - the package in tools section is just a simple invoker that loads a specific library (named excatly as the command, e.g. dotnet-test-xunit) from project dependencies. Because you have to add this library to project's dependencies, it is part of the overall dependency graph and is able to load project binaries. If there are any conflicts between the tool library and your project, they will come out during restore.

This works a little bit like the old commands section, but you cannot embed any commandline arguments in project.json (this part of functionality seem to be gone for good).

Scripts

The scripts section is still available, but the amount of available events has been drastically cut down. The only events supported now are 'precompile' and 'postcompile'. So, say goodbye to postrestore, prepare, etc. There were a lot of cases where 'postrestore' was really useful and I don't fully understand why they're gone. Hope this will be fixed in some future version of SDK or at least there will be a good replacement.

Section renames

Other changes in project.json are rather cosmetic:

  • compileOptions were renamed to buildOptions
  • metadata properties, like summary, releaseNotes, iconUrl were moved to packOptions section

The renames are not breaking (at least not yet) - you will just get a warning that a specific entry is deprecated and you should use the new one instead.

Frameworks

Migration docs state that dnx451 should be renamed to net451. In practice, dnx451 still works just fine. dnxcore50 should become netcoreapp1.0 and must add a dependency to Microsoft.NETCore.App package.

A framework declaration in project.json may also include imports section:

"frameworks": {
  "netcoreapp1.0": { 
    "imports": [ 
        "dnxcore50",
        "portable-net45+win8"
      ] 
    }
}

This allow packages supporting these frameworks to be installed in the target framework target, regardless of the compatibility rules. In other words, it's telling the package manager: "If you don't find a package version targeting netcoreapp1.0 or other compatible framework, I wan't you to install a version that targets one of the imported frameworks".

Tests

Unit testing seems to be the area that has the most moving pieces. It is depends strongly on how dotnet runs applications. It seems like with every preview of dotnet tool, a matching xunit runner is released. And it's really easy to get confused. Fortunately, xunit team keeps xunit docs up to date.

There are 2 things you have to do to configure a unit test project:

  1. Reference xunit and the test tool in project dependencies, like this:

    "dependencies": { "xunit": "2.1.0",
    "dotnet-test-xunit": "1.0.0-rc2-192208-24" }

    This will include testing tool in your dependency chain.

  2. Set testRunner property in project.json:

    "testRunner": "xunit"

That's it. If you now run dotnet test, dotnet will know it should invoke dotnet-test-xunit. If you want to quickly create an new unit test project, you can use dotnet new:

> dotnet new -t xunittest

If you want to know more about dotnet new, there's a great post on exploring dotnet new by Scott Hanselman.

By default, it will create a project that targets .Net Core, but the CLI runner is capable of running tests which target any other framework. If you have more than one target framewrok specified, dotnet test will run tests for all of them.

Application startup

RC1 supported two launch modes:

  • "emitEntryPoint":true - the application is compiled to an .exe, and has to have a static Main method. This hasn't change.
  • "emitEntryPoint":false - a dll project could also be run as a console app, if you included an appropriate command in project.json, like this:

    "commands": { "run": "My.Project.Name" }

In this mode, the loading process was similiar to how Asp.Net Startup class works. The ApplicationHost was responsible for running console applications. It would look for Main method (non-static), create the containing class instance and inject dependencies into it's constructor, then run Main. This was very convienient, because you had access to things like IApplicationEnvironment or IServiceProvider. This also meant that additional work had to be done by dnx to locate the startup class and provide all of the dependencies. And it was kind of "magic" too.

Apart of the additional logic required in the second startup mode, there is also the issue of dependencies. IServiceProvider is defined in Microsoft.Extensions.DependencyInjection.Abstractions and to provide it, the runtime would also have to create some default instance. This adds a dependency on Microsoft.Extensions.DependencyInjection in the runtime - which means that your application should depend on exact same version. It may work with a newer version, but considering the rapid development of APIs in .Net Core and possible namespace/method name changes, it most probably won't. That was a problem for dnx - versions of you're dependencies and dnx version had to match (you couldn't have beta5 dependencies and use dnx v.1.0.0-beta4).

Because dotnet is aimed to be more universal, this mode is now gone. We're back to the "raw" static void Main. So is the automatic Startup lookup in Asp.Net - you have to add the boilerplate code yourself). In Asp.Net Core 1.0.0, it would look like this:

public static void Main(string[] args)
{
    var host = new WebHostBuilder()
        .UseKestrel()
        .UseContentRoot(Directory.GetCurrentDirectory())
        .UseStartup<Startup>()
        .Build();

    host.Run();
}

Now, the WebHostBuilder is responsible for creating instance of IApplicationEnvironment, etc. and passing these dependencies to Startup class. So the Startup class does not have to change, the only change is in the Main method.

Some of these services that were provided in dnx has moved to other places, like IApplicationEnvironment: https://github.com/dotnet/cli/issues/216, https://github.com/aspnet/Announcements/issues/171, https://github.com/aspnet/PlatformAbstractions/issues/37. Most of them, including ApplicationBasePath can now be accessed by a static field: Microsoft.Extensions.PlatformAbstractions.PlatformServices.Default.Application. That's it for "dependency injection as a first class citizen" (at least for barebone console apps) and we're back to static classes.

Migration process

In the following section I will break down the process of migration from RC1 to RTM, along with some explanation of changes and problems you might encounter. If you wan't a more step-by-step approach, see Shawn Wildermuth's Converting an ASP.NET Core RC1 Project to RC2 (RC2 does not differ much from RTM) and the Official migration guide.

Using dotnet with old projects

As mentioned before, the tooling/SDK is now completely separate from Core Framework libraries. This also means that you're dependencie versions does no longer have to match tooling version (previously, you weren't able to have a dependency on, say, -beta8 libraries and compile with -rc1 dnu). The consequence is that you can still depend on older -rc1 libraries and use new dotnet commands. This gives you a little more flexibility in the migration process.

Let's get down to business and see, how does the migration process go. The experience can vary depending on how complicated is your project and how much (if any) dependencies does it have on .Net Core libraries.

We'll break it down into three types of projects:

  1. A simple library project that has no other project dependencies
  2. A console application
  3. Aspnet MVC application with some project dependencies
  4. Tests

A library project, with no project dependencies, targeting net451 and dnx451

This is the simplest case - no dependencies on other projects, only nugets, no dnxcore, just good old .Net Framework.

Surprisingly (or maybe not), dotnet restore and dotnet build have no problems. The only difference: all the required dll dependencies are copied to output bin folder.

A console app

Same as with a library project - no real changes needed. That is, unless you were using the startup model without emitEntryPoint. In that case, you have to add a static Main method and possibly create a dependency injection container by yourself (as described in Application Startup).

ASP.NET MVC application

A full ASP.NET MVC application will require some more work. Follow these steps:

  1. In project.json dependendcies section, make these renames:
    • AspNetCore namespace: Microsoft.AspNet.* => Microsoft.AspNetCore.*
    • AspNet versions: 1.0.0-rc1* => 1.0.0
    • MVC versions: 6.0.0-rc1* => 1.0.0
    • Microsoft.Framework.* => Microsoft.Extensions.*

Some of the versions or namespaces may actually be incorrect. If there's just an issue, just try referencing the newest one (preferrably stable). Continue to fix dependencies until dotnet restore suceeds. 2. Get rid of project.json commands section. 3. In code, rename namespaces: * Microsoft.AspNet.* => Microsoft.AspNetCore.* * Microsoft.Framework.* => Microsoft.Extensions.* 4. Try doing dotnet build and fix the remaining compilation errors. 5. In project.json set buildOptions:emitEntryPoint to true and add static Main method or fix the existing one. 6. In projectJson, set buildOptions:preserveCompilationContext to true - this is required for Razor views compilation. 7. If the project compiles, try running it. There will probably be some minor problems in views, but they should'n be hard to fix.

If you encounter strange errors with loading dependency assemblies or anything that seems like rc1 leftover, try Hunting down rc1 references.

Tests

Unit testing seems to be the most moving part of the whole thing. It is a part of tooling and depends strongly on how dotnet runs applications. It seems like with every preview of dotnet tool, a matching xunit runner has to be released. And it's really easy to get confused. Fortunately, xunit team keeps the docs up to date: https://xunit.github.io/docs/getting-started-dotnet-core.html.

Targeting desktop .NET

The .NET CLI runner is capable of running tests which target desktop .NET (minimum version 4.5.1), in addition to .NET Core. To target desktop .NET, use this frameworks section instead:

{
    "frameworks": {
        "net451": {
            "dependencies": {
                "Microsoft.NETCore.Platforms": "1.0.1"
            }
        }
    }
}

You can target both net4xx and netcoreapp simply by adding both frameworks together in your project.json file. When you run dotnet test with multiple framework entries, the system will run tests with all the frameworks, one after another.

Hunt down old references

If you have a decent dependency tree, it is possible that some of the dependencies will still be using older -rc1 libraries. You can use project.lock.json to hunt them down. project.lock.json contains the whole resolved dependency tree. It's a json, and it's a big one - I don't recommend trying to parse it in your memory (or even starring at it to long) - your eyes may soon start bleeding. But you can search it for any version strings that contain -rc1 - these are the bad dependencies that we have to get rid of.

.Net Core on server

If you want to run your app on IIS, there is a "windows (server hosting)" position on the .Net Core download page. This will install .Net Core and the ASP.NET Core Module for IIS, which is a replacement for the old HttpPaltformHandler (and which I'm waiting to see being open sourced).

Publishing to IIS

There is a new tool: , which adds dotnet-publish-iis command. As promising as it may seem, it doesn't handle IIS deployment (i.e. with MSDeploy). What it does is just add or modify web.config file to inclide Asp.Net Core Module for IIS with the right parameters. The deployment part you have to handle yourself, but Visual Studio will make it easy for you.

Server-mode Garbage Collection

When targeting full .Net Framework, if you want server garbage collection, you have to enable it in project.json or app.config:

{
    "runtimeOptions": {
        "configProperties": {
            "System.GC.Server": true
        }
    }
}

There are also some more settings that you can put in runtimeOptions.