This is a series on the .Net Core 1.0 bits. Looking for .Net Core 2 Series?
- Getting Started
- What’s in the box
- Using Multiple Projects
- Testing
- NuGet
- Multi-targeting <=(We are here)
- Publishing Portable Applications
- Self-contained Applications
Last time we learned how to package our libraries as NuGet packages. But it was only targeting netstandard1.6
, the new netstandard
target framework moniker.
Light reading
I recommend you go off and read through some of the architecture documents and NuGet pages to get a feel for the various targets that can or should be used. Starting with the .NET Platform Standard page.
In short, all the old profiles and various platforms have been mapped to this new netstandard
system, and if you implement this target in your package, it can be used by other libraries that use these targets at an equal or higher level.
Further reading can be found on the NuGet.org target frameworks page.
Setup
If you have been following the series right through, you should be able to follow what the next script does to set up our starting project:
mkdir mynewpackage
cd mynewpackage
dotnet new -t Lib
dotnet restore
dotnet build
Like before, you may wish to replace Library.cs
with a Calculator
class.
namespace mynewpackage
{
public class Calculator
{
public int Add(int first, int second)
{
return first + second;
}
}
}
One more:
dotnet build
On with the show!
This all boils down to the project.json
file. I’ll probably come back and revisit this when that finally goes away but for now this is what you need to do.
Starting with the initial project.json
file:
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable"
},
"dependencies": {},
"frameworks": {
"netstandard1.6": {
"dependencies": {
"NETStandard.Library": "1.6.0"
}
}
}
}
Say we wanted to target netstandard1.0
instead. It would be as simple as changing "netstandard1.6"
to "netstandard1.0"
.
Using some examples might help here. Let’s say we want to use some netstandard1.6
features, if they are available. But we also want to be compatible with netstandard1.0
, with limited functionality, or alternative features used. We would do this to our project.json
.
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable"
},
"dependencies": {},
"frameworks": {
"netstandard1.0": {
"dependencies": {
"NETStandard.Library": "1.6.0"
}
},
"netstandard1.6": {
"dependencies": {
"NETStandard.Library": "1.6.0"
}
}
}
}
By adding new frameworks, we can target multiple platforms. By default, a build with now build all the frameworks.
I mentioned in a previous article, "NETStandard.Library": "1.6.0"
is a Package, and version 1.6 actually contains the required parts for all netstandard*
dependencies in the same package. So you don’t change the version of this, even when we do change the netstandard*
one.
As whenever we change the project.json
we run restore and build.
dotnet restore
dotnet build
Project mynewpackage (.NETStandard,Version=v1.0) will be compiled because expected outputs are missing
Compiling mynewpackage for .NETStandard,Version=v1.0
Compilation succeeded.
0 Warning(s)
0 Error(s)
Time elapsed 00:00:01.5760786
Project mynewpackage (.NETStandard,Version=v1.6) will be compiled because inputs were modified
Compiling mynewpackage for .NETStandard,Version=v1.6
Compilation succeeded.
0 Warning(s)
0 Error(s)
Time elapsed 00:00:01.5280100
What does this do to our file tree?
mynewpackage
|-- Library.cs
|-- project.json
|-- project.lock.json
+-- bin
+-- Debug
+-- netstandard1.0
| |-- mynewpackage.deps.json
| |-- mynewpackage.dll
| +-- mynewpackage.pdb
+-- netstandard1.6
|-- mynewpackage.deps.json
|-- mynewpackage.dll
+-- mynewpackage.pdb
We now have two target outputs, and two dll
files, which target different frameworks.
Dependencies
As we can see from the above, there are three places where we can put dependencies. I’ve discussed this before, but let’s recap. If you put a dependency in the top level section, it will be available to all target frameworks. This means that it needs to contain a compatible target version to match every framework you have used. If you want to use a dependency only for a specific target, or you want to use different dependencies depending on the target, these can go in dependencies
under that specific framework.
You might use this feature to include a bridging library against an older framework when the newer framework might contain it. An example of this is the Microsoft.Bcl.Async package. Or you might want to use a full framework package when targeting net40
, and a dotnet core package when targeting netframework1.3
.
Conditional Compilation
If we are targeting different frameworks, we likely have to adjust the code as well. We do this using the define
buildOption
.
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable"
},
"dependencies": {},
"frameworks": {
"netstandard1.0": {
"buildOptions": {
"define": ["netstandard"]
},
"dependencies": {
"NETStandard.Library": "1.6.0"
}
},
"netstandard1.6": {
"buildOptions": {
"define": ["netstandard"]
},
"dependencies": {
"NETStandard.Library": "1.6.0"
}
}
}
}
By defining netstandard
above, we can include it as part of a conditional compile in our code:
#if netstandard
//Extra code for netstandard
var x = 0;
#endif
Like before, if we add defines to the to buildOption
section it will be defined for all targets. If we add it to a specific framework, it will only be defined when building against that target.
Packaging
Packaging into a nupkg
file works the same as before.
dotnet pack
Project mynewpackage (.NETStandard,Version=v1.0) was previously compiled. Skipping compilation.
Project mynewpackage (.NETStandard,Version=v1.6) was previously compiled. Skipping compilation.
Producing nuget package "mynewpackage.1.0.0" for mynewpackage
mynewpackage -> C:\dev\temp\multitargetting\mynewpackage\bin\Debug\mynewpackage.1.0.0.nupkg
Producing nuget package "mynewpackage.1.0.0.symbols" for mynewpackage
mynewpackage -> C:\dev\temp\multitargetting\mynewpackage\bin\Debug\mynewpackage.1.0.0.symbols.nupkg
And results in some new files:
mynewpackage
|-- Library.cs
|-- project.json
|-- project.lock.json
+-- bin
+-- Debug
|-- mynewpackage.1.0.0.nupkg
|-- mynewpackage.1.0.0.symbols.nupkg
+-- netstandard1.0
| |-- mynewpackage.deps.json
| |-- mynewpackage.dll
| +-- mynewpackage.pdb
+-- netstandard1.6
|-- mynewpackage.deps.json
|-- mynewpackage.dll
+-- mynewpackage.pdb
Instead of getting a package per target, we get one package that contains both targets.
mynewpackage.1.0.0.nupkg
|-- mynewpackage.nuspec
+-- lib
+-- netstandard1.0
| +-- mynewpackage.dll
+-- netstandard1.6
+-- mynewpackage.dll
A sidenote on running
You can also use multi-targeting on an application. When you want to run an application with multiple targets, you can use the dotnet run
command with the -f
flag.
dotnet run -f netcoreapp1.0
Of course, netcoreapp1.0
is the application target for the .Net Core runtime, compared to using net45
perhaps to target .Net 4.5 runtime instead.
net40
The target netstandard1.0
is compatible with .Net 4.5 and newer, UWP apps, Windows 8 & Windows Phone 8 or newer, Mono and Xamarin, and of course new .Net Core applications. But we all know that there are a ton of applications out there targeting .Net 4.0, and even .Net 3.5 that we still maintain and support that we might want our library packages to target.
So we can easily target net40 and net35 as well.
{
"version": "1.0.0-*",
"buildOptions": {
"debugType": "portable"
},
"dependencies": {},
"frameworks": {
"net35": {
"buildOptions": {
"define": ["net35"]
},
"dependencies": {
}
},
"net40": {
"buildOptions": {
"define": ["net40"]
},
"dependencies": {
}
},
"netstandard1.0": {
"buildOptions": {
"define": ["netstandard"]
},
"dependencies": {
"NETStandard.Library": "1.6.0"
}
},
"netstandard1.6": {
"buildOptions": {
"define": ["netstandard"]
},
"dependencies": {
"NETStandard.Library": "1.6.0"
}
}
}
}
This simple library doesn’t need anything outside the standard library, bu if it did, we would add in target-specific packages and libraries to the net35
and net40
targets as well.
dotnet restore
dotnet build
Project mynewpackage (.NETFramework,Version=v3.5) will be compiled because expected outputs are missing
Compiling mynewpackage for .NETFramework,Version=v3.5
Compilation succeeded.
0 Warning(s)
0 Error(s)
Time elapsed 00:00:01.1810785
Project mynewpackage (.NETFramework,Version=v4.0) will be compiled because expected outputs are missing
Compiling mynewpackage for .NETFramework,Version=v4.0
Compilation succeeded.
0 Warning(s)
0 Error(s)
Time elapsed 00:00:01.1155631
Project mynewpackage (.NETStandard,Version=v1.0) will be compiled because inputs were modified
Compiling mynewpackage for .NETStandard,Version=v1.0
Compilation succeeded.
0 Warning(s)
0 Error(s)
Time elapsed 00:00:01.0899710
Project mynewpackage (.NETStandard,Version=v1.6) will be compiled because inputs were modified
Compiling mynewpackage for .NETStandard,Version=v1.6
Compilation succeeded.
0 Warning(s)
0 Error(s)
Time elapsed 00:00:01.0806550
We can pack this.
dotnet pack
Project mynewpackage (.NETFramework,Version=v3.5) was previously compiled. Skipping compilation.
Project mynewpackage (.NETFramework,Version=v4.0) was previously compiled. Skipping compilation.
Project mynewpackage (.NETStandard,Version=v1.0) was previously compiled. Skipping compilation.
Project mynewpackage (.NETStandard,Version=v1.6) was previously compiled. Skipping compilation.
Producing nuget package "mynewpackage.1.0.0" for mynewpackage
mynewpackage -> C:\dev\temp\multitargetting\mynewpackage\bin\Debug\mynewpackage.1.0.0.nupkg
Producing nuget package "mynewpackage.1.0.0.symbols" for mynewpackage
mynewpackage -> C:\dev\temp\multitargetting\mynewpackage\bin\Debug\mynewpackage.1.0.0.symbols.nupkg
Then we will get:
mynewpackage
|-- Library.cs
|-- project.json
|-- project.lock.json
+-- bin
+-- Debug
|-- mynewpackage.1.0.0.nupkg
|-- mynewpackage.1.0.0.symbols.nupkg
+-- net35
| |-- mynewpackage.dll
| +-- mynewpackage.pdb
+-- net40
| |-- mynewpackage.dll
| +-- mynewpackage.pdb
+-- netstandard1.0
| |-- mynewpackage.deps.json
| |-- mynewpackage.dll
| +-- mynewpackage.pdb
+-- netstandard1.6
|-- mynewpackage.deps.json
|-- mynewpackage.dll
+-- mynewpackage.pdb
And:
mynewpackage.1.0.0.nupkg
|-- mynewpackage.nuspec
+-- lib
+-- net35
| +-- mynewpackage.dll
+-- net40
| +-- mynewpackage.dll
+-- netstandard1.0
| +-- mynewpackage.dll
+-- netstandard1.6
+-- mynewpackage.dll
What’s Left?
That should give you a reasonable (albeit simple) overview of multi-targeting libraries. The last focus is on applications, and how we publish and distribute these using the dotnet CLI tools. Up Next!