Search code examples
c#nugetnuget-packagenuspec

NuGet Dependency Strategy


I am splitting many of my application models and interfaces into its own nuGet package.

The question is: Is there any good practice recommended strategy when creating nuGet packages regarding the definition of dependencies?

Sometimes a package A depends on a package B. If I don't declare a specific package B version on my package A's nuspec, when I later on import package A nuGet package in any solution, it may fail at runtime because the the subdependency was not available.

Sometimes the dependency tree goes even deeper. But If I explicitly add dependencies onto each nuspec file, then I could find version conflicts later on.

The question is: What's the recommended strategy to deal with these things if everything is under my control? Should I only specify dependencies when the dependency is something "not obvious" for the project (e.g: a third party library that the project importing a nuGet wrapper needs at runtime but doesn't use directly)? Should I do it always and later on deal with version conflicts in a different way? Any good source of information regarding this would be much appreciated as I found several examples but not a recommendation of a certain way to do things.

ANNEXED: The way I create nuGet packages is by including a nuspec file which I edit manually. For example if I have the following solution structure:

ToolBelt.CityContracts.sln
-ToolBelt.CityContracts.NuGet
--ToolBelt.CityContracts.NuGet.nuspec
-ToolBelt.CityContractsOne
-ToolBelt.CityContractsTwo

In ToolBelt.CityContractsOne and ToolBelt.CityContractsTwo I have my c# source files. And From ToolBelt.CityContracts.NuGet project I add a reference to ToolBelt.CityContractsOne and ToolBelt.CityContractsTwo to make sure that the nuspec "knows" where the source files are.

Then the nuspec looks something like this:

<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://schemas.microsoft.com/packaging/2010/07/nuspec.xsd">
  <metadata>
    <id>ToolBelt.CityContracts</id>
    <version>1.0.0</version>
    <authors>iberodev</authors>
    <requireLicenseAcceptance>false</requireLicenseAcceptance>
    <description>Shared Contracts</description>
    <dependencies>
      <!--here sub-dependencies that I want to drag across-->
    </dependencies>
  </metadata>
  <files>
    <file src="bin/*/netstandard2.0/ToolBelt.CityContractsOne.dll" target="lib/netstandard2.0" />
    <file src="bin/*/netstandard2.0/ToolBelt.CityContractsOne.pdb" target="lib/netstandard2.0" />
    <file src="bin/*/netstandard2.0/ToolBelt.CityContractsTwo.dll" target="lib/netstandard2.0" />
    <file src="bin/*/netstandard2.0/ToolBelt.CityContractsTwo.pdb" target="lib/netstandard2.0" />
  </files>
</package>

Everything pretty standard until here.

Then I use nuget command tool to generate the nuget package inside a folder called nupkgs with the command:

nuget pack ToolBelt.CityContracts.NuGet.nuspec -Version $VERSION -OutputDirectory nupkgs -Prop Configuration=Release -NoDefaultExcludes -Verbosity detailed

UPDATE 1: For now my strategy is the following.

  1. If the package A exposes classes/interfaces in its public API that are part of a package B, then the package A does not add a dependency to package B in its nuspec. Therefore the project that adds package A must also explicitly add package B
  2. If the package A does not expose classes/interfaces in its public API that are part of a package B but it uses them internally, then the package A must add a dependency to package B in its nuspec, so that the project that adds package A will also have package B installed automatically and avoid the runtime exception risk.

The idea is: if the code compiles, it should run without runtime exceptions due to inexisting dependencies.

This makes sense to me, but I cannot find any reliable source to confirm that this is the way to go.


Solution

  • Since you're targeting netstandard2.0, an easier way to create the package is to use dotnet pack, rather than creating a nuspec yourself and using nuget pack. dotnet pack or msbuild /t:pack will pull metadata from the csproj and it automatically adds any PackageReference as a dependency to your package. if you have a build-time dependency that you don't want users of your package to get, you can use PrivateAssets.

    It is best practise to add all your runtime dependencies as dependencies to your package, so that anyone who references only your package, their program will not crash at runtime due to missing dlls. NuGet restore automatically handles selecting versions of dependencies when different packages request different versions, so it's something you generally shouldn't spend too much time worrying about, although sticking to Semantic Versioning helps.

    On a side note, my personal opinion is that fewer packages are better. There are absolutely great reasons why an implementation of an interface may be in a different assembly/package to the definition of the interface. But unless you can justify it with a good reason, I would suggest to keep the interface and implementation in the same assembly/package. One codebase I worked on in the past had code in multiple packages across multiple repositories because the team thought some of the generic code might be useful to other projects. But bug fixing meant you have to create a new version of the dependency, then update the reference in the deployed application, and debugging of the dependency was difficult. This slowed down development significantly when we had to work on that dependency package, and in the end nothing else used it, so there was really no benefit to splitting it.