How do I package a .NET library that has the following properties in a modern general-purpose way?
- Offers some shared functionality to both .NET Framework 4.6 and Universal Windows Platform.
- Offers some platform-specific functionality to each (e.g. specialized classes or APIs, including XAML user controls for UWP) with potential platform-specific dependencies on external libraries.
- Is architecture-agnostic (AnyCPU).
- The portable subset of which can be used by other portable libraries that target a compatible API surface.
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 portable .NET library targeting .NET Core?
- 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 .NET Framework libraries, the principles used to package Universal Windows Platform libraries and the principles used to package portable libraries. Read the linked answers first to better understand the following.
To serve the described set of platforms, you need to structure your solution accordingly into multiple class library projects:
- A platform-specific class library for .NET Framework 4.6.
- A platform-specific class library for Universal Windows Platform.
- A portable library targeting the common API surface.
You will want to achieve the following NuGet package structure:
\---lib
+---dotnet
| MyPortableLibrary.dll
| MyPortableLibrary.pdb
| MyPortableLibrary.XML
|
+---net46
| MyDotNetLibrary.dll
| MyDotNetLibrary.pdb
| MyDotNetLibrary.XML
| MyPortableLibrary.dll
| MyPortableLibrary.pdb
| MyPortableLibrary.XML
|
\---uap10.0
| MyPortableLibrary.dll
| MyPortableLibrary.pdb
| MyPortableLibrary.XML
| MyUwpLibrary.dll
| MyUwpLibrary.pdb
| MyUwpLibrary.pri
| MyUwpLibrary.XML
|
\---MyUwpLibrary
HashControl.xaml
MyUwpLibrary.xr.xml
If you have familiarized yourself with the other answers linked above, this should seem relatively familiar to you. The only special part is that the portable library exists in three copies since its contents are to be offered for all target platforms.
The desired package structure can be achieved by using a nuspec file based on the following template:
<?xml version="1.0"?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
<metadata minClientVersion="3.2">
<id>Example.MyMultiSurfaceLibrary</id>
<version>1.0.0</version>
<authors>Firstname Lastname</authors>
<description>Example of a multi-platform library that exposes different API surfaces to .NET 4.6 and UWP and also includes a portable component.</description>
<dependencies>
<!-- UWP has more dependencies than other platforms (Newtonsoft.Json). -->
<group targetFramework="uap10.0">
<dependency id="Newtonsoft.Json" version="8.0.1" />
<dependency id="System.Linq" version="4.0.0" />
<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" />
</group>
<!-- All other platforms - just the dependencies of the portable library here. -->
<group>
<dependency id="System.Linq" version="4.0.0" />
<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" />
</group>
</dependencies>
</metadata>
<files>
<file src="..\bin\Release\MyPortableLibrary.*" target="lib\net46" />
<file src="..\bin\Release\MyPortableLibrary.*" target="lib\uap10.0" />
<file src="..\bin\Release\MyPortableLibrary.*" target="lib\dotnet" />
<file src="..\..\MyDotNetLibrary\bin\Release\MyDotNetLibrary.*" target="lib\net46" />
<!-- Double wildcard also ensures that the subdirectory is packaged. -->
<file src="..\..\MyUwpLibrary\bin\Release\MyUwpLibrary**" target="lib\uap10.0" />
</files>
</package>
Note that two separate sets of dependencies are defined: one general-purpose group and one specific to Universal Windows Platform, as the UWP library has an extra dependency on the Newtonsoft.Json package. You can extend the same pattern to any number of platforms with platform-specific dependencies.
That's it - this NuGet package can now be installed into .NET Framework 4.6 projects, Universal Windows Platform projects and portable library projects that target a compatible API surface. The functionality of the portable library will be exported on all platforms, with the platform-specific libraries also used on the appropriate platforms.
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 MultiSurfaceLibrary.