How to package a portable .NET library targeting .NET Core?
Asked Answered
T

2

21

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:

Tapir answered 5/1, 2016 at 12:38 Comment(0)
T
13

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:

  1. Determine the .NET Core assemblies referenced by your library.
  2. 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.

Tapir answered 5/1, 2016 at 12:38 Comment(4)
it seems to me that this answer is obsolete, now it's lib\nestandard1.5 or 1.6 and so on, am I right ?Tipple
@Omu, you are correct. And the powershell command doesn't work and gives a "Exception calling "LoadFile" with "1" argument(s): "This assembly is built by a runtime newer than the currently loaded runtime and cannot be loaded.". This answer needs updating or deleting.Harbird
I agree - netstandard is the way to go instead of dotnet. Edits are welcome, as I have not had the time to review and re-document the topic myself yet!Tapir
@DavidAmo The PowerShell command worked fine for me and I'm targeting netstandard1.6. The error makes it sound like the runtime that executed was not the latest.Spiderwort
C
-1

There seems to have been a lot of churn about the best way to make portable .NET framework libraries/NuGet packages over the past several months. If you can wait a bit, you'll want to read up about the .NET Standard. Immo Landwerth wrote a detailed blog post introducing the .NET Standard in September of 2016. Support for .NET Standard 2.0 in .NET Core is expected to arrive in early 2017, in the same timeframe as Visual Studio 2017, which is currently in Release Candidate stage. The .NET Standard 2.0 will be implemented by .NET Framework, .NET Core, and Xamarin. It will also include compatibility shims for .NET Framework binaries, which should make adoption easier.

Update:

Oren Novotny has a very informative blog post on this subject: Multi-targeting the world: a single project to rule them all. He discusses .NET Standard and how to multi-target with Visual Studio 2017. The blog post was from before the RTM of Visual Studio 2017, but it's very helpful.

Coeternal answered 2/12, 2016 at 3:1 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.