How to package a portable .NET library targeting .NET Core?
How do I package a portable .NET library in a modern general-purpose way? Let's assume I have a single AnyCPU assembly that I wish to make available to any .NET platform that supports the .NET Core API surface, for example .NET Framework 4.6 and the Universal Windows Platform.
This is a series of questions and answers that document my findings on the topic of modern NuGet package authoring, focusing especially on the changes introduced with NuGet 3. You may also be interested in some related questions:
- How to package a .NET Framework library?
- How to package a .NET library targeting the Universal Windows Platform?
- How to package a .NET library targeting .NET Framework and Universal Windows Platform and include platform-specific functionality?
- How to package a multi-architecture .NET library that targets the Universal Windows Platform?
- How to package a .NET library that targets the Universal Windows Platform and depends on Visual Studio extension SDKs?
This answer builds upon the principles used to package libraries targeting the .NET Framework. Read the linked answer first to better understand the following.
To publish the portable .NET library, you will need to create a NuGet package with the following structure:
\---lib
\---dotnet
MyPortableLibrary.dll
MyPortableLibrary.pdb
MyPortableLibrary.XML
All three files will come from your project's build output directory under the Release build configuration.
The dotnet
directory in the structure above has a special meaning - it indicates to NuGet that the files in the directory are to be used on any platform that all your package's dependencies are compatible with. Therefore, your package is automatically usable on any .NET platform that supports all your dependencies (e.g. .NET Core).
The crucial next step is to determine the list of dependencies. Due to a package management issue it is not possible to simply declare a dependency on .NET Core itself (.NET Core is the API surface shared by all the .NET platforms). Instead, you must manually determine each .NET Core component dependency and add it to the nuspec file.
The dependency detection process for .NET Core packages consists of two steps:
- Determine the .NET Core assemblies referenced by your library.
- Determine the NuGet packages that contain these assemblies.
Visual Studio does not provide the information you need. Instead, you need to build your library and inspect the resulting DLL file. The following PowerShell script will display the references of a .NET assembly:
Get-ChildItem MyPortableLibrary.dll | % { [Reflection.Assembly]::LoadFile($_.FullName).GetReferencedAssemblies() | % { $_.Name + ".dll" } }
The output of this command will be a list of assembly names, for example:
System.Runtime.dll
System.Resources.ResourceManager.dll
System.Numerics.Vectors.dll
Once you have obtained the list, open the project.lock.json file in your project directory. This file contains information about all NuGet packages used by your project. You will find, among other data, various blocks of JSON such as the following:
"System.Numerics.Vectors/4.1.0": {
"dependencies": {
"System.Globalization": "[4.0.10, )",
"System.Resources.ResourceManager": "[4.0.0, )",
"System.Runtime": "[4.0.20, )",
"System.Runtime.Extensions": "[4.0.10, )"
},
"frameworkAssemblies": [
"mscorlib",
"System.Numerics"
],
"compile": {
"ref/net46/System.Numerics.Vectors.dll": {}
},
"runtime": {
"lib/net46/System.Numerics.Vectors.dll": {}
}
},
This block of JSON indicates that the assembly files listed under "compile" are provided by the package listed in the top level value (System.Numerics.Vectors version 4.1.0). Use this information to map every referenced assembly to a NuGet package. Note that while the package and assembly names are often the same, this is not always the case!
For any NuGet packages that are not part of .NET Core, you can skip the above process as you already know the exact package you have a dependency on. The dependency detection logic described here is only required because you cannot declare a dependency directly on .NET Core (the Microsoft.NETCore package) due to the issue linked above.
Now simply list all the dependencies in your nuspec file, based on the following example:
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="3.2">
<id>Example.MyPortableLibrary</id>
<version>1.0.0</version>
<authors>Firstname Lastname</authors>
<description>Example of a portable library with NuGet package dependencies.</description>
<dependencies>
<dependency id="System.Numerics.Vectors" version="4.1.0" />
<dependency id="System.Resources.ResourceManager" version="4.0.0" />
<dependency id="System.Runtime" version="4.0.20" />
</dependencies>
</metadata>
<files>
<file src="..\bin\Release\MyPortableLibrary.*" target="lib\dotnet" />
</files>
</package>
That's it! The resulting package is usable on any compatible .NET platform, such as .NET Framework 4.6 or Universal Windows Platform. Remember to build your solution using the Release configuration before creating the NuGet package.
A sample library and the relevant packaging files are available on GitHub. The solution corresponding to this answer is PortableLibrary.
Refer to Lucian Wischik's blog for a deeper dive into the logic that operates on such NuGet packages.