Disclaimer: This website requires Please enable JavaScript in your browser settings for the best experience.

HomeDev GuideRecipesAPI Reference
Dev GuideAPI ReferenceUser GuideLegal TermsGitHubNuGetDev CommunityOptimizely AcademySubmit a ticketLog In
Dev Guide

Resolve NuGet dependency conflicts

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 resolve the issues. The following list summarizes the content.

  • Studying the dependency hierarchies and version restrictions of EPiServer.\* packages and understanding the NuGet dependency resolution rules can help you gain a better understanding of which dependencies you must add (and which ones you may be able to remove). See the hints about version relationships in the last section of this post (appendix).
  • Dependency issues are not specific to Optimizely packages, but Optimizely tries to balance dependencies and breaking changes, which can make the issues more challenging.
  • Dependency issues are not 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 not support classic ASP.NET projects 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 add 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.

📘

Note

For information about CMS packages, see NuGet package families in CMS.

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 shared a dependency with a package you had already installed, you had to install a version of that common dependency that was compatible with both. This was often automatic, such as when a package required a newer dependency version, so the dependency was upgraded). When you have done that, it was once again unambiguous which version 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 determine which versions of those packages are 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 unambiguous by the resolution rules, which is stricter and makes it difficult to fulfill requirements. You may have to add 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, 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 public (pubternal) APIs among packages Optimizely controls.
  • Decoupling and autonomy of teams.
  • Modularization 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 needed).

The first bullet requires closer control of dependency ranges between packages that use public APIs that may break between major versions. Optimizely ties versions 1:1 to know whether public 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 with multiple paths to the same dependency. For example, the EPiServer.CMS package (often called the "umbrella" package) is designed to get the dependencies you must 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 the "top" level for a plain CMS project. On the other end is a foundational EPiServer.Framework package. 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 restoration, 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. While traversing these dependency trees, it can detect version conflicts between the different paths but uses the version it has already selected from a range.

In the example above, the different versions are resolved compatible, using version 12.0.3 of the 1:1 version mapped packages in the "CMS Core" family. See Package families later in this topic.

Example: Add DXP support to plain CMS project

The mix between the complex dependency graph and the 1:1 requirements 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 must 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 pick the latest version 1.0.3.

  • Install EPiServer.CloudPlatform.Cms 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 you 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 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 must 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 states 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.

Install EPiServer.Framework 12.0.4

Although this still fails, continue to follow 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 succeeded, 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, be persistent and 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) its dependencies (for example, EPiServer.Framework). This is not the case with PackageReference. While transitive dependencies are implicit, direct dependencies are taken 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

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

Install EPiServer.CMS.AspNetCore.Templating 12.3.0

That conflicts 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 version of EPiServer.Framework so update those too:

  • Update EPiServer.Hosting and EPiServer.CMS.AspnetCore.HtmlHelpers to 12.3.0.

Now you 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 the extra packages you added, leaving only EPiServer.CMS and EPiServer.CloudPlatform.Cms? Would it still work? Indeed it does! Here is 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 you 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 can 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:

install EPiServer.CMS.Core 12.4.1

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 conflicting resolutions, you act as a tie-breaker by bringing it up to a top-level dependency. You can add version 12.4.1 manually in the csproj to add them 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 try to stay current on the main package dependencies. The method is the same to solve those issues.

  • 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 packages you reference directly.

Similarly, 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

If you have PackageReference elements to several packages in the same family, you can 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 (the convention is to use the .props suffix) and use the Import element in each project to access the parameter across dependencies and have a consistent dependency version throughout.

📘

Note

For information about CMS packages, see NuGet package families in CMS.