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!
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 inglobal.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 tobuildOptions
- metadata properties, like
summary
,releaseNotes
,iconUrl
were moved topackOptions
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:
-
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.
-
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 exploringdotnet 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 staticMain
method. This hasn’t change.-
"emitEntryPoint":false
- a dll project could also be run as a console app, if you included an appropriate command inproject.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 forMain
method (non-static), create the containing class instance and inject dependencies into it’s constructor, then runMain
. This was very convienient, because you had access to things likeIApplicationEnvironment
orIServiceProvider
. This also meant that additional work had to be done bydnx
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:
- A simple library project that has no other project dependencies
- A console application
- Aspnet MVC application with some project dependencies
- 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:
- 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. - AspNetCore namespace:
- Get rid of project.json
commands
section. - In code, rename namespaces:
Microsoft.AspNet.*
=>Microsoft.AspNetCore.*
Microsoft.Framework.*
=>Microsoft.Extensions.*
- Try doing
dotnet build
and fix the remaining compilation errors. - In project.json set
buildOptions:emitEntryPoint
totrue
and add staticMain
method or fix the existing one. - In projectJson, set
buildOptions:preserveCompilationContext
totrue
- this is required for Razor views compilation. - 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
.