Lessons Learnt: Migrating From Net Framework to Net Core - Net Standard Libraries and Multi-Targeting
One of the first lessons I'm going to share from our recent migration happened very early on in the project but continually popped up as we went along and updating each service.
I've always been a fan of code reuse and sharing code within our solutions however I don't like everything being used from one location. I like to modularlise and componentise any shared code. To date this has always been via Class Libraries and Portable Class Libraries when these were shared with Mobile Applications built with Xamarin as well. I've always versioned these and published via an internal Nuget server to reduce the risk when changes are made and to enforce decoupling.
My migration strategy to .Net Core (we chose 2.1 originally) stipulated that we should be fully backwards compatible; this meant that every library that we needed to use had to ideallly continue to work in all other referenced projects. Ideally I wanted to just update all of our class libraries to .Net Standard 2.0 and push out the Nuget updates and leave it as that.
.Net Standard Class Libraries are the latest implementation of sharing code between your projects and applications. Where before Class Libraires were purely bound to a specific Framework version, i.e .Net 4.5 (which most of ours are) and Portable Class Libraries were a cross framework solution but always ended up being the lowest common denominator, Net Standard Libraries instead indicate they work on all framework (.Net Full Framework, Mono, .Net Core etc) versions that contain all of the API's defined by the Standard. I'm not going to detail .Net Standard any further but I do suggest if this is new to you to read the excellent Net Standard online documentation.
As I've mentioned above ideally we would just convert our class libraries to Net Standard 2.0 (.Net Core 2.0+ requires this), however I found that our Class Libraries are used in quite a few places which target .Net Framework 4.5. When you look at the compatiability table on the Net Standard Online Documentation, also shown below you find that Net Standard 2.0 requires .Net 4.6.1 as a minimum.
Simply updating these to a higher framework version also isn't possible for us (although highly recommended if you can, to do so for security updates etc) due to the environments these are deployed in. So what can be done?
The framework monikers can all be looked up on the Target Frameworks documentation page, however they are fairly straightforward to learn / update.
Once you have done this you'll notice the project UI locks the Target Framework box so you can now only modify it via the XML directly.
With this set when you perform builds of the project it is now effectively built twice, one Net Standard compatible and one Net Framework 4.5 compatible. If you look in your build output directory you should find multiple outputs. If you also build a Nuget package directly via your project build you'll also notice this now has both ouputs within it as well.
Withi this done the next question is how can we have some code only appear for .Net Framework 4.5 where a shim os such is required and what about different NuGet packages?
So how can we do this?
Well, we can use a combination of #ifdefs and MSBuild conditional statements to include the correct NuGet package dependant on the platform target.
For example if we want to solve the SQLite issue above we can do the following:
What this is doing is using the same Target Framework Monikers used earlier to optionally include NuGet packages as part of the MSBuild process.
This means in our code we can do:
And know the correct NuGet's are being referenced and used which is super powerful. One thing to note however is that if you use the NuGet UI you only see the first frameworks dependencies not any of the others.
For my example:
So be careful :)
I've always been a fan of code reuse and sharing code within our solutions however I don't like everything being used from one location. I like to modularlise and componentise any shared code. To date this has always been via Class Libraries and Portable Class Libraries when these were shared with Mobile Applications built with Xamarin as well. I've always versioned these and published via an internal Nuget server to reduce the risk when changes are made and to enforce decoupling.
My migration strategy to .Net Core (we chose 2.1 originally) stipulated that we should be fully backwards compatible; this meant that every library that we needed to use had to ideallly continue to work in all other referenced projects. Ideally I wanted to just update all of our class libraries to .Net Standard 2.0 and push out the Nuget updates and leave it as that.
.Net Standard Class Libraries are the latest implementation of sharing code between your projects and applications. Where before Class Libraires were purely bound to a specific Framework version, i.e .Net 4.5 (which most of ours are) and Portable Class Libraries were a cross framework solution but always ended up being the lowest common denominator, Net Standard Libraries instead indicate they work on all framework (.Net Full Framework, Mono, .Net Core etc) versions that contain all of the API's defined by the Standard. I'm not going to detail .Net Standard any further but I do suggest if this is new to you to read the excellent Net Standard online documentation.
As I've mentioned above ideally we would just convert our class libraries to Net Standard 2.0 (.Net Core 2.0+ requires this), however I found that our Class Libraries are used in quite a few places which target .Net Framework 4.5. When you look at the compatiability table on the Net Standard Online Documentation, also shown below you find that Net Standard 2.0 requires .Net 4.6.1 as a minimum.
Simply updating these to a higher framework version also isn't possible for us (although highly recommended if you can, to do so for security updates etc) due to the environments these are deployed in. So what can be done?
Lesson Learnt 1 - Multi-Targeting Class Libraries
With the new MSBuild project type you no longer have to specify a single target framework. Although the project UIs in both Visual Studio 2017 and Visual Studio 2019 only allow you to select a single framework you can manually indicate multiple targets.
To add multiple frameworks simply right click the project file in solution explorer and choose edit project file. Note: with the new MSBuild Project type you now no longer need to unload a project file before editing it.
With this file open you can change the <TargetFramework> XML element to <TargetFrameworks> and provide multiple values.
<PropertyGroup> <TargetFramework>netstandard2.0</TargetFramework> <AssemblyName>YourNameHere</AssemblyName> <RootNamespace>YourNameHere</RootNamespace> </PropertyGroup>becomes:
<PropertyGroup> <TargetFrameworks>netstandard2.0;net45</TargetFrameworks> <AssemblyName>YourNameHere</AssemblyName> <RootNamespace>YourNameHere</RootNamespace> </PropertyGroup>
The framework monikers can all be looked up on the Target Frameworks documentation page, however they are fairly straightforward to learn / update.
Once you have done this you'll notice the project UI locks the Target Framework box so you can now only modify it via the XML directly.
With this set when you perform builds of the project it is now effectively built twice, one Net Standard compatible and one Net Framework 4.5 compatible. If you look in your build output directory you should find multiple outputs. If you also build a Nuget package directly via your project build you'll also notice this now has both ouputs within it as well.
Withi this done the next question is how can we have some code only appear for .Net Framework 4.5 where a shim os such is required and what about different NuGet packages?
Lesson Learnt 2 - #ifdef your way to victory
If you find yourself in a position where on one target framework you need to use a specific function call or bespoke method you can use #ifdef 's (#if preprocessor if being formal ;) ) to include / exclude code. You simply specify the target framework moniker as part of your #if
It's worth noting that this can be used anywhere you can use an #if so you can even do it to classes. Also, Visual Studio gives you a handy drop down so you can toggle bewteen your Target Frameworks and thus get to see your #ifdefs in each state.
Lesson Learnt 3 - Conditional NuGet Packages
A lesson we learnt related to multi targetting and #ifdefs came in the form of having to target different NuGet packages dependant on our platform target. In our .Net Framework project we use System.Data.SQLite.Core for our local SQLite DB's but this isn't compatible with .Net Core and cross platform targets. In .Net Core you need to use the Microsoft.Data.Sqlite package which uses the amazing SQLitePCL project, it's what I use in all my mobile applications and is a great library.So how can we do this?
Well, we can use a combination of #ifdefs and MSBuild conditional statements to include the correct NuGet package dependant on the platform target.
For example if we want to solve the SQLite issue above we can do the following:
<ItemGroup Condition="'$(TargetFramework)' == 'net45'"> <PackageReference Include="System.Data.SQLite.Core"> <Version>1.0.108</Version> </PackageReference> </ItemGroup> <ItemGroup Condition="'$(TargetFramework)' != 'net45'"> <PackageReference Include="Microsoft.Data.Sqlite"> <Version>2.2.0</Version> </PackageReference> </ItemGroup>
What this is doing is using the same Target Framework Monikers used earlier to optionally include NuGet packages as part of the MSBuild process.
This means in our code we can do:
For my example:
So be careful :)
Lesson Learn 4 - Multi Target your Unit Test projects as well as your Class Libraries
The final lesson learnt for today and related to Multi Targetting is to also use Multi Targetting on your Unit Tests. With the targets potentially being very different implementations , .Net Framework, .Net Core etc you need to test your libraries in each framework. Previously in PCL days I would have had one project per target but that is quite overkill, now we can just use multi targetting to use .Net Framework 4.5 and .Net Core 2.1 for example.
You wouldn't however target .Net Standard as this doesn't mean anything for tests, it just a specification. XUnit explains this well.
With your test project setup to be multi targetted you will hit an issue with the Visual Studio Test Runner not running and showing test outputs for all of your targets. Infact it only shows and runs tests for the first framework moniker provided :(
In order to run all of the tests against all of the targets you have to instead run your tests from a terminal using dotnet test.
Summary
That's it for this post. Hopefully the four lessons:
- Multi-Targeting Class Libraries
- #ifdefs
- Conditional NuGet's
- Muti-Target your Unit Test Projects
Have been useful and will help you to get started porting your .Net Framework code bases over to .Net Core and other platforms!
Comments
Post a Comment