Porting an existing .Net 4.0 project to .Net Standard 2.0
In the introduction I discussed the objectives of this project and main issues we believe will need to be overcome. In this post I will discuss creating a .Net Standard Project from an existing .Net 4.0 codebase.
At the time of writing .Net Standard 2.0 and .Net Core 2.0 are both still in preview, as is the tooling support.
In order to get started, the first thing needed is Visual Studio 15.3, currently in preview and available to download here. While it is possible to do a side by side install with another Visual Studio 2017, bear in mind this is a preview so it generally not advisable to intall it on a production machine that you cannot live without.
Launch Visual Studio 15.3 Preview and create a new project. As I am porting a library I am going to select .Net Standard Class Library as the project type. While the current goal is .Net Core support, .Net Standard potentially enables us to support Mono and Xamarin as well.
This will create the new slimline style csproj. One of the great new features of it, for anyone who edits their csproj regularly is that you can now edit it without having to unload and reload. It sounds like a small thing but 1 click vs 3 that resulted in all your open code windows being closed is a vast improvement, especially in the early setup phase of a project. Editing the file can be a bit of a shock to the system for anyone used to the old csproj. It is now a very bare bones 5 lines.
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> </PropertyGroup> </Project>
All the Guids have been removed and standard properties are hidden away. It is also now including files by convention. You will notice Class1.cs is in your solution explorer but not listed here. That is because it is automatically picked up as part of your project folder. You might also notice that there is no AssemblyInfo.cs showing in your solution explorer. This is generated automatically when you compile. As we have a number of properties that we maintain on a shared basis and/or are fed in via the build system, it is a bit of a headache. If like us you want to maintain control of your own assemblyinfo independent of the tooling's conventions, you can disable the generation as follows:
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> </PropertyGroup> </Project>
If you have a complex build system, targets can be imported as standard. However, we encountered issues with some of our post build steps not being picked up. A bit of research and we discovered an open Github Issue that is tracking the problem and proposes an interim workaround which seems to work well. Instead of importing the sdk as part of the project tag, the targets and props for the sdk need to be explicitly imported as follows:
<Project> <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" /> <PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <GenerateAssemblyInfo>false</GenerateAssemblyInfo> </PropertyGroup> <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" /> </Project>
Now that the project and build system has been setup, we can finally start to build our code. As we plan to continue to support .Net 4.0 we have decided to inline the existing code, allowing to continue to only have one copy of the code base to maintain but gaining a way to build it in against newer frameworks. You can wildcard include a folder as follows.
<ItemGroup> <Compile Include="..\MyNet40Project\**\*.cs" Label="MyNet40Project" /> </ItemGroup>
There is an section within our project that we already know will have to be completely replaced and that is MEF 1.0. MEF was introduced in .Net 4.0 as part of the full framework. MEF 2.0 was introduced as a package supporting .Net 4.5. With .Net Core the decision was made to port MEF 2.0, but to date no port of MEF 1.0 has been made. The implementations are significantly different so we exclude that folder completely and log a github issue in our repository. As we need to continue supporting .Net 4.0, the MEF 1.0 implementation will be left in place for the older project and a new MEF 2.0 will be added to the new .Net Standard project.
<ItemGroup> <Compile Include="..\MyNet40Project\**\*.cs" Label="MyNet40Project" /> <Compile Remove="..\MyNet40Project\MEF\**\*.cs" Label="MEF10" /> </ItemGroup>
Finally, there are some small sections of code not compiling that we want to line up for later replacement work. OOTB a NETSTANDARD2_0 symbol is added to your project. There are some non critical path pieces of functionality in our system that aren't compiling because the namespace has changed, e.g. WCF calls or simply aren't supported e.g OS-specific P/Invoke calls. These we #ifdef out for now and log as issues. However, given the size of our code base, some of that was originally written against .Net 1.1, the number of items we need to defer for now is impressively small.