Resolving NuGet dependency conflicts
This topic describes why package dependencies can create compatibility issues and how to work around those issues in the Project SDK (PackageReference) model.
Package dependencies can sometimes create incompatibility issues. This topic describes why this happens and provides methods to work around the issues. The following list summarizes the content.
- Studying the dependency hierarchies and version restrictions of EPiServer.* packages and understanding the NuGet depencency resolution rules can help you gain a better understanding for which dependencies you need to add (and which ones you may be able to remove). See the hints about version relationships in the last section (appendix) of this post.
- Dependency issues aren't specific to Optimizely packages, but Optimizely tries to balance dependencies and breaking changes which can make the issues more challenging.
- Dependency issues aren't related to Optimizely Content Management System (CMS 12) or ASP.NET Core, but they may be related to the change from the old style csproj+packages.config to SDK style projects with PackageReference (which does nt support classic ASP.NET project and hence wasn't viable until ASP.NET Core) which resolves transitive dependencies dynamically according to Microsoft dependency resolution rules.
- You can work around some issues by following the hints in the errors and warnings (NU1107 and NU1608). You may need multiple iterations and adding multiple packages before you get to a compatible set.
- Getting to the latest versions of all packages can be more complex than staying with the top-most "umbrella" packages such as EPiServer.CMS and EPiServer.Commerce (which may give you earlier versions).
- Multi-project solutions are more complex, especially if you reference lower-level dependencies rather than the "umbrella" packages.
Background 1 – Shift from packages.config to PackageReference
Previously, NuGet packages and their versions were listed in packages.config where all packages were listed, including transitive dependencies of packages you install (installed packages' dependencies, dependencies of those dependencies and so on). Because of this, the set of packages and versions was unambiguous.
If you wanted to install another package that shares a dependency with a package you already have installed, you had to make sure to install a version of that common dependency that was compatible with both. This was often automatic, such as when a new package required a newer version of the dependency, so the dependency was upgraded). Once you've done that, it was once again unambigous which verison should be used.
When SDK-style projects and PackageReference came along, PackageReference let you specify only a top-level dependency, and the transitive dependencies of that package are implicit. To figure out which versions of those packages that are actually used when packages are restored, NuGet follows a set of rules.
These rules are somewhat similar to what happened in the previous packages.config when installing, but the result of a dependency resolution is not (automatically) "documented" or "written to lock file" in the same way. The resolution happens by the rules each time there is a restore (such as when you open or build the project). The dependencies must be unambigous by the resolution rules, which is more strict and more difficult to fulfill requirements. You may have to resort to adding additional dependencies to act as a tie-breaker on the resolution rules, manually creating a (partial) lock file.
Background 2 – Optimizely dependency structure
Optimizely maintains a large set of packages across many teams. Some packages are more closely related and maintained by the same team and on the same release cycle. Other teams are on independent release cycles. Optimizely balances different perspectives through its dependencies, such as:
- Minimizing the number of breaking changes (to major versions), for example by using "pubternal" APIs among packages Optimizely controls.
- Decoupling and autonomy of teams.
- Modularizaiton and decoupling on a package level, depending on the simplest possible package (such as no UI where not required, or no web stack where not required).
The first bullet requires more close control of dependency ranges between packages that use pubternal APIs that may break between major verisons. Optimizely ties versions 1:1 to know whether pubternal APIs can be broken without risking incompatibilities.
The second and third bullets drive towards splitting packages smaller, creating chains of dependencies.
The third bullet also creates a complex dependency graph where there are multiple paths to the same dependency. For example, the EPiServer.CMS package, (often referred to as the "umbrella" package), is designed to get the dependencies you need to run a CMS website, with UI. If you don't want the UI, don't run a website (like building a library), you would reference something else. This is basically the "top" level for a plain CMS project. In the other end is our foundational EPiServer.Framework package. Let's take a look at three different paths between these:
EPiServer.CMS 12.1.0 -> EPiServer.Hosting \[12.0.3, 13) -> EPiServer.Framework 12.0.3
EPiServer.CMS 12.1.0 -> EPiServer.CMS.AspNetCore.HtmlHelpers \[12.0.3, 13) -> EPiServer.CMS.AspNetCore.Mvc 12.0.3 -> EPiServer.CMS.AspNetCore.Routing 12.0.3 -> EPiServer.CMS.AspNetCore.Templating 12.0.3 -> EPiServer.CMS.AspNetCore 12.0.3 -> EPiServer.CMS.Core 12.0.3 -> EPiServer.Framework 12.0.3
EPiServer.CMS 12.1.0 -> EPiServer.CMS.UI \[12.1.0, 13) -> EPiServer.CMS.UI.Core 12.1.0 -> EPiServer.CMS.AspNetCore.Templating \[12.0.3, 13) => EPiServer.CMS.AspNetCore 12.0.3 => EPiServer.CMS.Core 12.0.3 -> EPiServer.Framework 12.0.3
The semi-open version ranges [12.0.3, 13) mean "any 12.x version higher than 12.0.3".
On restore, NuGet processes these graphs from the top (of the project). When it finds a version range, it selects the _lowest _version in the range, and then moves on from there. While traversing these dependency trees it can detect version conflicts between the different paths, but always uses the version it already selected from a range.
In the example above, the different versions are resolved in a compatible way, using the version 12.0.3 of the 1:1 version mapped packages in the "CMS Core" family. See Package families.
Example: Adding DXP support to plain CMS project
The mix between the complex dependency graph and the 1:1 requirements is what creates issues with the NuGet algorithm.
The EPiServer.CMS package gives you what you need for a plain CMS site. However, in the interest of modularity, it does not automatically pull in the requirements to run on the DXP service (because you could be running elsewhere). To deploy your solution to the DXP service you need to add the EPiServer.CloudPlatform.Cms package.
You could start with a plain project referencing EPiServer.CMS 12.1.0. (This is already not the latest version, but it illustrates one of the dependency resolution problems well.) You want to be compatible with DXP, so you install EPiServer.CloudPlatform.Cms, and just pick the latest version 1.0.3.
- Install EPiServer.CloudPlatform.Cms 1.0.3.
This fails because of one specific path of the dependency graph of EPiServer.CloudPlatform.Cms:
EPiServer.CloudPlatform.Cms 1.0.3 -> EPiServer.CMS.AspNetCore \[12.0.4, 13) -> EPiServer.CMS.Core 12.0.4 -> EPiServer.Framework 12.0.4
Compare this to just one of the paths of EPiServer.CMS 12.1.0 we looked at in the previous section:
EPiServer.CMS 12.1.0 -> EPiServer.CMS.AspNetCore.HtmlHelpers \[12.0.3, 13) -> EPiServer.CMS.AspNetCore.Mvc 12.0.3 -> EPiServer.CMS.AspNetCore.Routing 12.0.3 -> EPiServer.CMS.AspNetCore.Templating 12.0.3 -> EPiServer.CMS.AspNetCore 12.0.3 -> EPiServer.CMS.Core 12.0.3 -> EPiServer.Framework 12.0.3
The first dependency (EPiServer.CMS 12.1.0 -> EPiServer.CMS.AspNetCore.HtmlHelpers [12.0.3, 13)) is fully compatible with the 12.0.4 version of EPiServer.CMS.AspNetCore.HtmlHelpers (because it accepts a range from 12.0.3 to 13) and all the other 12.0.4 packages. However, because NuGet picks the lowest version in the range and sticks to it, it ends up with a conflict on several packages, including EPiServer.Framework that it mentions (EPiServer.CMS path wants 12.0.3, EPiServer.CloudPlatform.Cms path wants 12.0.4).
At this stage, you have to add a direct dependency to the project you can break the tie in this conflict, because the "nearest wins" rule in the dependency resolution rules that a direct project dependency wins over transitive dependencies.
- Install EPiServer.Framework 12.0.4, then try again with EPiServer.CloudPlatform.Cms 1.0.3.
Although this still fails, continue to following the hints.
- Install EPiServer.CMS.AspNetCore.Routing 12.0.4, then try again with EPiServer.CloudPlatform.Cms 1.0.3.
Success! But even though the installation succeded, there are a couple of warnings.
warning NU1608: Detected package version outside of dependency constraint: EPiServer.Hosting 12.0.3 requires EPiServer.Framework (= 12.0.3) but version EPiServer.Framework 12.0.4 was resolved.
warning NU1608: Detected package version outside of dependency constraint: EPiServer.CMS.AspNetCore.Mvc 12.0.3 requires EPiServer.CMS.AspNetCore.Routing (= 12.0.3) but version EPiServer.CMS.AspNetCore.Routing 12.0.4 was resolved.
Look at a couple of the dependency paths again:
EPiServer.CMS 12.1.0 -> EPiServer.Hosting \[12.0.3, 13) -> EPiServer.Framework 12.0.3
EPiServer.CMS 12.1.0 -> EPiServer.CMS.AspNetCore.HtmlHelpers \[12.0.3, 13) -> EPiServer.CMS.AspNetCore.Mvc 12.0.3 -> EPiServer.CMS.AspNetCore.Routing 12.0.3 -> EPiServer.CMS.AspNetCore.Templating 12.0.3 -> EPiServer.CMS.AspNetCore 12.0.3 -> EPiServer.CMS.Core 12.0.3 -> EPiServer.Framework 12.0.3
Next, install the version of EPiServer.Hosting that depends on EPiServer.Framework 12.0.4, which is EPiServer.Hosting 12.0.4.
- Install EPiServer.Hosting 12.0.4.
One warning down, one to go!
warning NU1608: Detected package version outside of dependency constraint: EPiServer.CMS.AspNetCore.Mvc 12.0.3 requires EPiServer.CMS.AspNetCore.Routing (= 12.0.3) but version EPiServer.CMS.AspNetCore.Routing 12.0.4 was resolved.
- Install EPiServer.CMS.AspNetCore.Mvc 12.0.4.
warning NU1608: Detected package version outside of dependency constraint: EPiServer.CMS.AspNetCore.HtmlHelpers 12.0.3 requires EPiServer.CMS.AspNetCore.Mvc (= 12.0.3) but version EPiServer.CMS.AspNetCore.Mvc 12.0.4 was resolved.
There's a new warning? Ok, let's be persistent, install EPiServer.CMS.AspNetCore.HtmlHelpers 12.0.4 too:
- Install EPiServer.CMS.AspNetCore.HtmlHelpers 12.0.4.
Success! No more warnings!
You now have this set of compatible packages:
<PackageReference Include="EPiServer.CloudPlatform.Cms" Version="1.0.3" />
<PackageReference Include="EPiServer.CMS" Version="12.1.0" />
<PackageReference Include="EPiServer.CMS.AspNetCore.HtmlHelpers" Version="12.0.4" />
<PackageReference Include="EPiServer.CMS.AspNetCore.Mvc" Version="12.0.4" />
<PackageReference Include="EPiServer.CMS.AspNetCore.Routing" Version="12.0.4" />
<PackageReference Include="EPiServer.Framework" Version="12.0.4" />
<PackageReference Include="EPiServer.Hosting" Version="12.0.4" />
Optional dependency clean up
Because EPiServer.CMS.AspNetCore.Mvc and EPiServer.CMS.AspNetCore.Routing are transitive dependencies of (and in 1:1 version relationship with) EPiServer.CMS.AspNetCore.HtmlHelpers, you may be able to remove these two dependencies to leave you with this set of compatible packages:
<PackageReference Include="EPiServer.CloudPlatform.Cms" Version="1.0.3" />
<PackageReference Include="EPiServer.CMS" Version="12.1.0" />
<PackageReference Include="EPiServer.CMS.AspNetCore.HtmlHelpers" Version="12.0.4" />
<PackageReference Include="EPiServer.Framework" Version="12.0.4" />
<PackageReference Include="EPiServer.Hosting" Version="12.0.4" />
Updating with additional dependencies
When you updated something like the umbrella package EPiServer.CMS in the old packages.config world, NuGet would also update (overwrite) all its dependencies, (for example, EPiServer.Framework). This is not the case with PackageReference. While transitive dependencies are implicit, direct dependencies are taken very explicitly, which may cause problems when updating a higher level package like EPiServer.CMS. Starting from the state in the step before, try to update EPiServer.CMS to 12.3.2, the latest version at the time of writing.
Update EPiServer.CMS to 12.3.2
This fails, because EPiServer.CMS 12.3.2 requires 12.3.0 or higher of the CMS Core family packages. This is incompatible with the 12.0.4 versions you explicitly installed. The message recommends adding EPiServer.CMS.AspNetCore.Templating 12.3.0, so try that:
Install EPiServer.CMS.AspNetCore.Templating 12.3.0
That is in conflict with the currently installed EPiServer.Framework 12.0.4 so update EPiServer.Framework first.
- Update EPiServer.Framework to 12.3.0, then add EPiServer.CMS.AspnetCore.Templating 12.3.0.
This succeeds but you get new warnings because you still have the older versions of EPiServer.Hosting and EPiServer.CMS.AspNetCore.HtmlHelpers expecting an older verison of EPiServer.Framework so update those too:
- Update EPiServer.Hosting and EPiServer.CMS.AspnetCore.HtmlHelpers to 12.3.0.
Now we can finally update EPiServer.CMS:
- Update EPiServer.CMS to 12.3.2
You now have this set of compatible packages without warnings:
<PackageReference Include="EPiServer.CloudPlatform.Cms" Version="1.0.3" />
<PackageReference Include="EPiServer.CMS" Version="12.3.2" />
<PackageReference Include="EPiServer.CMS.AspNetCore.HtmlHelpers" Version="12.3.0" />
<PackageReference Include="EPiServer.CMS.AspNetCore.Templating" Version="12.3.0" />
<PackageReference Include="EPiServer.Framework" Version="12.3.0" />
<PackageReference Include="EPiServer.Hosting" Version="12.3.0" />
However, that update of EPiServer.CMS was a bit messy because EPiServer.CloudPlatform.Cms that needed EPiServer.Framework 1.0.4 and you have 12.3.0; and EPiServer.CMS 12.3.2 has EPiServer.Framework 12.3.0 as a transitive dependency.
So what if you try to remove extra packages you added, leaving only EPiServer.CMS and EPiServer.CloudPlatform.Cms. Would it still work? Indeed it does! Here's a minimal set of compatible packages:
<PackageReference Include="EPiServer.CloudPlatform.Cms" Version="1.0.3" />
<PackageReference Include="EPiServer.CMS" Version="12.3.2" />
Updating specific packages
Using the end state in the previous section, EPiServer.CMS 12.3.2 implicitly gives us version 12.3.0 of the CMS Core packages. However, if you want the bug fixes in a later version of EPiServer.CMS.Core, you have two options:
- Wait until there is a EPiServer.CMS package that has the later matching-or-higher version of EPiServer.CMS.Core 12.4.1 as a dependency.
- Or you reference the package you want directly. In this example, install EPiServer.CMS.Core 12.4.1.
It fails, once again with an error similar to the one you saw in the beginning:
So you could go through the same steps again, adding EPiServer.Framework and sifting through errors and warnings, or you can use a shortcut based on what you learned earlier.
You referenced EPiServer.Framework, EPiServer.CMS.AspNetCore.HtmlHelpers and EPiServer.Hosting because HtmlHelpers and Hosting are at top of the dependency chain from EPiServer.CMS, where NuGet would pick the lowest compatible version; Framework because it is at the bottom where different resolution paths could come to conflicing resolutions, and by bringing it up to a top-level dependency you act as a tie-breaker. You can add version 12.4.1 manually in the csproj to add them all at once, which preemptively breaks the tie, and installs EPiServer.Framework first:
Install EPiServer.Framework 12.4.1, EPiServer.Hosting 12.4.1 and EPiServer.CMS.AspNetCore.HtmlHelpers 12.4.1.
You have this set of compatible packages:
<PackageReference Include="EPiServer.CloudPlatform.Cms" Version="1.0.3" />
<PackageReference Include="EPiServer.CMS" Version="12.3.2" />
<PackageReference Include="EPiServer.CMS.AspNetCore.HtmlHelpers" Version="12.4.1" />
<PackageReference Include="EPiServer.Framework" Version="12.4.1" />
<PackageReference Include="EPiServer.Hosting" Version="12.4.1" />
More packages or projects mean more problems, but have the same solutions
If you add Optimizely Customized Commerce (or Forms, or others) you may have more dependency issues, especially if you are trying to stay current on the main packages dependencies. To solve those issues the method is the same.
- Follow the hints in errors and warnings and work your way through.
- Look at the dependencies of packages and you can work out how to simplify the set of packages you reference directly.
Similarily, if you have a multi-project solution, your lower-layer projects work the same as packages when your higher-level projects are built, (nearest wins rule takes those projects into account). You want to keep the same or compatible dependency versions across the projects.
Package families
This section describes packages that are versioned together and in most cases are dependent 1:1. It is a reference to know which packages to update to resolve a conflict in versions on one or more of the packages. If you have PackageReference elements to several packages in the same family, you can just manually update them at once to reference the same version.
You may even want to declare a PropertyGroup with a named property holding the version, and reference this in multiple places in your csproj. If you have a multi-project solution you can extract the PropertyGroup to a separate file (convention is to use the .props suffix) and use the Import element in each of your projects to have access to the parameter across dependencies and have a consistent dependency version throughout.
CMS Core family
- EPiServer.CMS.AspNetCore.*
- EPiServer.CMS.Core
- EPiServer.Framework
- EPiServer.Framework.AspNetCore
- EPiServer.Hosting
CMS UI family
- EPiServer.CMS.UI*
Commerce family
- EPiServer.Commerce.Core
- EPiServer.Commerce.UI*
Find family
- EPiServer.Find*
(Except EPiServer.Find.Commerce)
Content API family
- EPiServer.ContentDeliveryApi.*
- EPiServer.ContentManagementApi
Note
Not all packages in this family are updated to ASP.NET Core.
Product Recs family
- EPiServer.Tracking.Commerce
- EPiServer.Personalization.Commerce
Updated 2 months ago