The web is full of guides on how to switch from .Net Framework to .Net Core. As someone who had the chance to migrate a web app from Framework to Core, I realized that each article gives only a few parts of the whole picture. In order to switch between these frameworks, I had to understand the fundamentals changes (and the reasons behind these changes) in Microsoft's .Net projects.
In this article I give my recommendation on how one should approach this large task, and what I believe is important to know prior to starting a migration of a .Net Framework project. Each subject has a reference to a thorough documentation/article worth reading.
From Framework to Core
It is important to understand that .Net Framework and .Net Core aren’t two separate frameworks with their own life-cycles and goals, but rather one is an extension of the other. .Net Core is meant to extend its predecessor framework and offer solutions to the same problems (desktop, web, mobile, cloud-native and more).
Ok, then why Core ?
Apart from restructure and refactor of most .Net libraries, .Net Core brings a couple of key changes:
- It’s fully open-source. Microsoft’s teams and contributors publish all code.
- It’s cross-platform. It supports Linux, MacOS and Windows (obviously) runtimes for many application types (even desktop apps 💪).
As mentioned above, even apps intended to find themselves running on windows should choose .Net Core simply because it’s the latest .Net offers and supports.
Why I migrated to .Net Core
There are many reasons to make such a large change other than the will to get the latest .Net (new is always better? 🤔). In my case, I was working on a large web app that didn't scale that well. We had to throw in more and more servers to keep up with user traffic which made us more maintainers and less developers. In order to set us free of VMs babysitting, we decided to bring in Docker containers to the table. At the time, we didn't want to go for Windows Containers because it was still fresh and unknown territory, so we were left with Linux containers. So, this is how .Net Core came in.
.Net Framework/Standard/Core
In order to allow compatibility across multiple runtime environments, libraries had to find a common low level API that works on both Framework and Core. Here comes in .Net Standard, a common base build target that exposes interfaces that are eventually implemented for each OS and run environment.
Check out the official docs here and here.
C# is same ol’ C#
Syntax, features and patterns are all the same. It doesn’t matter whichever .Net target you choose, C# stays the same on every level. Since C# has its own people working on it and making sure the language itself stays compatible with all .Net BCL and targets. This is done at the Roslyn compiler and by referencing common types present in all targets as a standard library (more on the relationship between language and frameworks here).
One thing to keep in mind is that as of C# 8, the language specifically targets .Net Core and a few features are only partially supported by .Net Framework (as mentioned here).
I Recommend checking out this awesome stack overflow answer:
Yes, C# 8 can be used with the .NET Framework and other targets older than .NET Core 3.0/.NET Standard 2.1 in Visual Studio 2019 (or older versions of Visual Studio if you install a Nuget package).
The language version must be set to 8.0
in the csproj file.
Most…
OK, then it's easy
If C# is the same, and all we have to do is target .Net Standard, then where is the hassle?
Well, the real pickle is at the platform level. Each platform (such as Asp.Net, Web Api, WPF, etc) had its infrastructure refurbished and open-sourced. As a result, developers had to make adjustments to use the new .Net Core versions of their platform.
Some common noticeable changes
-
Broader build output types are now available. EXE is no longer the obvious output to a console application, as it depends on the target OS. DLLs are still here since they are OS-agnostic (to a certain degree).
Compiling to a linux target is as easy as:
dotnet publish -r linux-x64
And right away you can see a new build:
$ ls LinuxConsole\bin\Debug\netcoreapp3.1\linux-x64\ createdump System.IO.Compression.Native.so libclrjit.so System.IO.Compression.ZipFile.dll* libcoreclr.so System.IO.dll* libcoreclrtraceptprovider.so System.IO.FileSystem.AccessControl.dll* libdbgshim.so System.IO.FileSystem.dll* libhostfxr.so System.IO.FileSystem.DriveInfo.dll* libhostpolicy.so System.IO.FileSystem.Primitives.dll* libmscordaccore.so System.IO.FileSystem.Watcher.dll* libmscordbi.so System.IO.IsolatedStorage.dll* LinuxConsole System.IO.MemoryMappedFiles.dll* LinuxConsole.deps.json System.IO.Pipes.AccessControl.dll* LinuxConsole.dll* System.IO.Pipes.dll* LinuxConsole.pdb System.IO.UnmanagedMemoryStream.dll* LinuxConsole.runtimeconfig.dev.json System.Linq.dll* LinuxConsole.runtimeconfig.json System.Linq.Expressions.dll* Microsoft.CSharp.dll* System.Linq.Parallel.dll* Microsoft.VisualBasic.Core.dll* System.Linq.Queryable.dll* Microsoft.VisualBasic.dll* System.Memory.dll* Microsoft.Win32.Primitives.dll* System.Native.a Microsoft.Win32.Registry.dll* System.Native.so mscorlib.dll* System.Net.dll* netstandard.dll* System.Net.Http.dll* publish/ System.Net.Http.Native.a SOS_README.md System.Net.Http.Native.so System.AppContext.dll* System.Net.HttpListener.dll* ...
-
Dependencies and their versions should be determined by support to the .Net Standard package version. Nuget packages compatibility used to be determined by simply the .Net Framework version of your project, but now we need to make sure our .Net Core version supports the .Net Standard library version. For example, take a look at the very popular Newtonsoft package. Head over to the dependencies section and you can see which .Net Framework/Standard versions are supported:
Now check out the .Net Standard supported versions of all .Net Core releases here to see which .Net Standard version each .Net core version supports.
you can find out more about base .Net Core Nuget packages here. -
.csproj is greatly simplified. Just by creating two class library projects, one in .Net Core and one in .Net Framework, you can see the difference:
.Net Framework .csproj
<?xml version="1.0" encoding="utf-8"?> <Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup> <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ProjectGuid>abd460f8-54fb-4f34-abf0-3cc184253329</ProjectGuid> <OutputType>Library</OutputType> ... </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> ... </PropertyGroup> <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> ... </PropertyGroup> <ItemGroup> <Reference Include="System"/> <Reference Include="System.Core"/> <Reference Include="System.Xml.Linq"/> <Reference Include="System.Data.DataSetExtensions"/> <Reference Include="Microsoft.CSharp"/> <Reference Include="System.Data"/> <Reference Include="System.Net.Http"/> <Reference Include="System.Xml"/> </ItemGroup> <ItemGroup> <Compile Include="Class1.cs" /> <Compile Include="Properties\AssemblyInfo.cs" /> </ItemGroup> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> </Project>
.Net Core .csproj
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> </PropertyGroup> </Project>
Notice the
<Compile Include="...">
. .Net Core's csproj no longer requires you to specify every single file related to the project, if it’s in the folder it’s in the project (no more build fails after you forget to commit your new shinny class file 🎉).
Microsoft published a basic guideline article on porting .Net Framework projects to .Net Core, you might want to take a look.
Platform changes
Let's assume we have an Asp.Net Web Api project. What would it take to migrate it to .Net Core?
Well, first of all, .Net Core does not have a new Web Api support. All .Net web applications development was mostly unified under the new Asp.Net Core project. That means you need to migrate to an almost new platform. But fear not, Asp.Net Core left most of the project structure the same.
dotnet / aspnetcore
ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
The main difference is the new classes and libraries used to create the application, such as controllers routing attributes, middlewares and more.
Controllers
See how this .Net Framework Web Api controller:
[RoutePrefix("forecast")] // <-
public class WeatherForecastController : ApiController
{
private static readonly string[] Summaries = {
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm"
};
[HttpGet] // <-
[Route("route")] // <-
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
Turns to this .Net Core controller:
[ApiController] // <-
[Route("forecast")] // <-
public class WeatherForecastController : Controller
{
private static readonly string[] Summaries = {
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm"
};
[HttpGet("route")] // <-
public IEnumerable<WeatherForecast> Get()
{
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
})
.ToArray();
}
}
Changes are highlighted with (<-)
Middleware & infrastructure code
Middleware pipelining stays pretty much the same in .Net Core (as does the startup file we all ❤️ so much), the changes you might find yourself doing are switching 'native' .Net libraries to their newer alternatives. Take this example:
A tale of Windows & Linux
Like many .Net Framework (on top of Windows) developers, you might have set up Windows Authentication which Worked perfectly with your User is WindowsPrincipal
and your Request.IsAuthenticated
with IIS. And be assured that a similar code will give you the same with services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
on .Net Core and Windows. However, if you intend to switch to Linux servers, more modifications are required, as Linux needs extra work in order to integrate with Active Directory.
Check out the official Microsoft guide and this Stackify guide.
Kestrel
Another important feature you can notice in these guides is Kestrel. Remember how you had to create a self-host console app project for your development environment? Well no more, Asp.Net Core ships with its own server implementations (for various runtime targets), the most popular of which is Kestrel which supports both Linux and Windows runtimes. Take a look at this overview of hosting with .Net Core.
The Roadmap
I really encourage Checking up .Net Core's roadmap from time to time. It gives a clear map of where the .Net world is headed to.
Top comments (2)
I really like the simplification of csproj files.
Yeah, really made csproj files look like they should look