64

I am attempting to publish and consume versioned NuGet packages of class libraries while avoiding headaches for local development. Here is a sample Visual Studio solution layout:

| Libraries
  | LibraryA
  | LibraryB
  | LibraryC
| Applications
  | ApplicationD
  | ApplicationE

This is a single solution containing both shared class libraries and multiple applications. Currently references to the class libraries by the applications are local in-solution references.

What I would like to do is to publish the libraries (A,B,C) as versioned NuGet packages which are then referenced by the applications as needed (D,E). This allows a change to a shared library to be independent from an update to an application which is deployed. Without this, changing one library could cause the binaries to change in a dozen or more applications, all of which would technically need to be tested. This is undesirable, and versioning with NuGet fixes this.

However, let us say that I want to update the content of LibraryA and ApplicationD at the same time. In order to do this after we have switched to NuGet, I will have to make changes to LibraryA, commit them, wait for the package to be created, tell ApplicationD to update its reference to LibraryA, and then test or develop in ApplicationD. This is far more complicated than simply working with both at the same time using local in-solution references.

What is a better way to get both the robustness of versioned NuGet packages for my shared class libraries while also keeping development simple even if it spans over multiple projects and applications? The only other solutions I have found all involve too much overhead or headache, such as having to constantly change the references for ApplicationD between the NuGet package and the local project.

EDIT: To clarify the premise, this question assumes the following:

  • The architecture (solution and project organization) cannot be significantly reorganized
  • Shared libraries are going to change at a non-trivial frequency
  • Changing a shared library cannot force any application to be updated
  • Applications can reference different versions of shared libraries
jmsb
  • 4,846
  • 5
  • 29
  • 38
  • 1
    "let us say that I want to update the content of LibraryA and ApplicationD at the same time" If this is a concern, then I would highly recommend not using NuGet packages. Assemblies should be turned into NuGet packages when they are stable enough to rarely change and that their change process is separated from rest of the system. – Euphoric Dec 30 '14 at 19:57
  • 1
    @Euphoric Do you have any recommendations for how I could update shared LibraryA, which may be used by 50 separate applications, without having to force the new version on all 50 applications and trigger much regression testing? The stability you mention is a good ideal to strive for, but not practical for this use case. I am open to other approaches/tools of course. – jmsb Dec 30 '14 at 20:06
  • I think the fact that you have library that is not stable and is dependent upon by 50 other is the problem. The best way is to reengineer the architecture to increase the stability and then create a separate solution, with it's own requirements, change requests and development plan. And if you really have 50 applications that depend on it, then this would not be that big effort. – Euphoric Dec 30 '14 at 20:10
  • 2
    You can have version-specific NuGet references. You would want to ensure that the shared LibraryA has a sensible versioning scheme, and each of the applications that use it could upgrade (or not) when ready. You just have to ensure that all the releases are available, just as nuget.org does. – Andrew Dec 30 '14 at 20:15
  • @Andrew Yep! The ability to reference specific versions is why I am using NuGet to solve this problem in the first place. – jmsb Dec 30 '14 at 20:17
  • It's 2018 and I'm still looking for a good answer to this question. I have the exact same problem for a client that is migrating from Java to DotNet, except their libs are not all in 1 solution. /sigh – jgitter Apr 20 '18 at 15:33
  • Alternatively, use Git submodules. For inner-company development, I've yet to hear a good argument to using Nuget packages over submodules: https://stackoverflow.com/a/65817315/732673 – Josh Noe Dec 28 '22 at 21:50

7 Answers7

30

Although it takes some work, it is possible to hand-edit .csproj files in order to set up conditional referencing by adding a Condition attribute to the appropriate references.

EDIT I've moved these conditions into ItemGroups, as it seems this is how my mentioned production code is working, and there has been mention of this being a possible issue in VS 2013.

<ItemGroup Condition="'$(Configuration)' == 'Debug Local'">
    <!-- Library A reference as generated by VS for an in-solution reference, children unmodified -->
    <ProjectReference>...
</ItemGroup>

<ItemGroup Condition="'$(Configuration)' == 'Debug NuGet'">
    <!-- Library A reference as generated by NuGet, child nodes unmodified --> 
    <Reference Include="LibraryA">...
</ItemGroup>

This would allow you to have, on the Projects D & E, configurations of "Debug NuGet" vs. "Debug Local" which reference the libraries differently. If you then have multiple solution files which have their configurations mapped to the appropriate configurations on the projects within, the end user would never see more than "Debug" and "Release" for most operation, since those are the solution configs, and would only need to open the full solution for editing the A, B, & C projects.

Now, as for getting the A, B, & C projects out of the way, you could set them up under a folder marked as a subrepo (assuming you're using an SCM that supports this, such as Git). Most users would never need to pull the subrepo since they're not accessing the ABC projects, and are instead grabbing from NuGet.

Maintenance wise, I can guarantee that VS will not edit the conditional references, and will respect them during compilation -I have gone through both VS 2010 and 2013 (EDIT: Professional version, though I have delved into doing the same with express) with the same conditional reference projects at work. Keep in mind than in VS, references can be made version-agnostic, making NuGet the only place from which version need be maintained, and that can be done like any other NuGet package. While I'm hopeful, I have NOT tested whether NuGet will fight with the conditional references.

EDIT It may also be prudent to note that conditional references can cause warnings about missing DLLs, but does not actually hinder compilation or run.

EDIT For those still reading this, I'm now (7/2019) hearing that the IDE isn't as friendly to these changes anymore, and either it or the Package Manager may override them. Proceed with caution, and always read your commits!

David
  • 10,458
  • 1
  • 28
  • 40
  • 2
    Thanks, these conditional references are useful to know about. However, once I convert the existing references, all future library references would need to be manually set up with the same dual approach, right? My concern is that this would be hard to maintain. – jmsb Dec 30 '14 at 19:48
  • 1
    That's a possible break on the NuGet side, but easy on VS. I'll incorporate that info into my answer. – David Dec 30 '14 at 19:50
  • 1
    I know from having just done something similar in VS2013 that, at least for `` elements, the IDE does not handle multiple references to the same library gracefully even when only one reference is active due to `Condition` attributes. I don't know whether having both a `` and a `` presents the same issue. If so, it can be prevented by creating properties specific to the configuration and by using the property values in the tag instead of creating multiple tags. – Andrew Dec 30 '14 at 20:05
  • @Andrew actually, looking it over, it seems the different references in the production project are within `` elements, so I'll update my answer accordingly – David Dec 30 '14 at 20:07
  • I seem to recall trying the same thing, and it didn't help. But again, I do not know whether `ProjectReference` will exhibit the same behavior, and it was only a nuisance warning anyway. – Andrew Dec 30 '14 at 20:10
  • @Andrew also have the nuisance warning, should have mentioned so - one more update incoming. – David Dec 30 '14 at 20:11
  • 1
    I had a similar idea, but when I tried the IDE ended up writing the project file and losing my custom edits. I think it was when I updated the nuget package from the Package Manager. Very irritating. – waxingsatirical Jul 18 '19 at 09:04
  • Sorry this one didn't work out for you; it's been a few years since I tried this, and the package manager may very well be more aggressive than I anticipated at the time – David Jul 23 '19 at 16:35
  • What if `LibraryA` references yet another project `LibraryX`? Is it possible to setup `LibraryA` in a way that if it's referenced locally it also references `LibraryX` locally? – magic_al Jul 08 '21 at 07:13
12

Update for .NET Core (2.x ++)

.NET Core 2.x actually has this functionality built in!

If you have a project reference to project A in project B, and project A is a .NET Standard or Core project with proper package information (Properties -> Package with Package id set to your NuGet package ID), then you can have a regular project reference in project B's .csproj file:

<ItemGroup>
  <ProjectReference Include="..\..\A\ProjectA.csproj" />
</ItemGroup>

When you pack (dotnet pack) project B, because of the Package id in project A, the generated .nuspec file will be set up with a NuGet dependency to that Package ID, together with other NuGet references you might have, instead of just including the built DLL file.

<dependencies>
  <group targetFramework=".NETStandard2.0">
    <dependency id="Project.A" version="1.2.3" exclude="Build,Analyzers" />
    <dependency id="Newtonsoft.Json" version="12.0.2" exclude="Build,Analyzers" />
  </group>
</dependencies>
TheHvidsten
  • 4,028
  • 3
  • 29
  • 62
  • 1
    How would you control `Project.A`'s version which in your example is `1.2.3`? – magic_al Jul 08 '21 at 06:35
  • @magic_al : The used version number is in `Properties` -> `Package`, so I think it's possible to use only the last version (it is quite logic to me: since it's not a package that is referenced but directly the source code, the version number matches necessarily to the one of the source and can not matches to an earlier version). – Xav987 Aug 17 '21 at 16:37
  • 1
    That doesn't sound like it actually deals with the problem. `Project.A` is a class library, that I'm going to put in a NuGet package. `Project.B` is an application, that is never going to be `dotnet pack`ed. At this point, I think I'm fine with just sticking the project reference in there until the class library is stable, and then replacing the reference, so not a big deal for me. – Auspex Jun 17 '22 at 13:26
  • Agree with @Auspex. This is helpful information but doesn't address the question. – Josh Noe Dec 28 '22 at 21:51
9

I know this is a 2-years old post, but just found it while facing the same situation. Also found this for VS2015, I'm in the process of testing it. I'll come back and adjust my answer accordingly.

https://marketplace.visualstudio.com/items?itemName=RicoSuter.NuGetReferenceSwitcherforVisualStudio2015

JayR
  • 127
  • 1
  • 4
  • 1
    We were able to use this module for our purpose. It creates a mapping file for each csproj (MyProj.nugetreferenceswitcher) which essentially lists the different references with their project / nuget mapping. It allows to switch back and forth easily, but takes few secondes / minutes doing so. – JayR May 15 '17 at 12:33
  • In Q&A part. "Why would I be referencing a NuGet package of a project that's already in my solution? Wouldn't that typically be a straightforward project reference, with no need for NuGet? That may be a valid scenario in some cases, but a far more realistic scenario is when the code for the NuGet package is in another solution, and that's why you'd be referencing it as a NuGet package. And this tool does nothing to help that, does it?" This is so true :/ – ahmet Feb 24 '19 at 19:46
4

I also faced a similar problem. One approach that worked was using local repository (which is basically just a folder in local) and adding post-build script in the libraries. For example: let's say you need to update your implementation for LibraryA, then include following 3 steps in your post-build event for LibraryA:

  1. Check if local repository has that version of package; if yes then delete it

    rd /s /q %userprofile%\.nuget\packages\LibraryA\@(VersionNumber) -Recurse -ErrorAction Ignore

  2. Create a nuget package

    nuget pack LibraryA.csproj

  3. Push it to local repository

    nuget push LibraryA@(VersionNumber) -Source %userprofile%\.nuget\packages

These steps will make sure that the package is always updated for that version after each build (we had to do this since nuget packages are immutable)

Now in ApplicationD, you can point to local repository (%userprofile%.nuget\packages) to get LibraryA; such that after each build of LibraryA, you will receive an updated version of it in ApplicationD

PS: Inorder to get version number of you library you can use this : Determine assembly version during a post-build event

appsdownload
  • 751
  • 7
  • 20
2

Unfortunately, there really isn't a way to have the best of both worlds. Internally in my company, we've mitigated it somewhat with a fast build/deploy process, which counteracts most of the burdens with always referencing a NuGet package. Basically, all of our applications use a different version of the same library hosted in a local NuGet repository. Since we use our own software to build, deploy, and host the packages, it makes it pretty quick to update the library, then update its NuGet package in another solution. Essentially, the fastest workflow we've found is this:

  1. Make changes to library
  2. Automatically build and deploy version of library incremented by 1 to internal NuGet feed
  3. Update NuGet package in consumer application

The whole process from check-in to updating the consuming project takes around 3 minutes. The NuGet repository also has a symbol/source server which helps tremendously with debugging.

John Rasch
  • 62,489
  • 19
  • 106
  • 139
  • do you use 1.2.3-pre for this, or find that its easiest to stick with 1.2. and be less strictly semver about the whole thing? – steve Jan 19 '15 at 16:35
  • We never use pre-release versioning, it has only caused problems for us in the past. However, to handle a similar case, the alternative would be to promote the package from a development feed into a production feed, or something along those lines. – John Rasch Jan 19 '15 at 22:57
  • 1
    Have you found a good way to track dependencies? A lot of times we'll make a bug fix in a library (NuGet pkg) that is referenced by many different applications or other libraries. It would be great to have a way to know what all projects we need to go update references on. – scuba88 Dec 06 '21 at 14:50
2

In the properties of ApplicationD, go to the "Reference Paths" tab and add the path of the output folder of LibraryA. Then, if you change and build LibraryA, the next build of ApplicationD will use the modified LibraryA.

When you are finished, don't forget to remove the "Reference Paths" and update the referenced NuGet package version.

hectorct
  • 3,335
  • 1
  • 22
  • 40
2

My not-so-clean yet fastest solution so far is:

Assuming the following two separate solutions:

VS Solution 1: contains libraries published as nuget packages:

Solution1
|_ my.first.library
|_ my.second.library

VS Solution 2: contains applications, which consume one or more of the above libraries as PackageReferences:

Solution2
|_ my.first.application
|  |_ depends on nuget my.first.library (let us say v1.0.1)
|
|_ my.second.application

In case, I'm making changes to my.first.library

I proceed as follows:

  1. Make code changes to my.first.library and rebuild
  2. Navigate to the build output directory of my.first.library (e.g. <Solution1 directory>/my.first.library/bin/debug/netstandard2.0) and copy the .dll and .pdb files
  3. Navigate to the my.first.library's local directory of the currently being used nuget feed (for example at: C:\Users\user.name\.nuget\packages\my.first.library\1.0.1lib\netstandard2.0) and replace the .dll and .pdb files there with the ones generated in step 1 (possibly making backup).
  4. Changes get reflected in my.first.application. Continue working and repeat steps 1-4, when needed

Advantages:

  • being completely local. No secondary nuget feeds needed.
  • zero changes to .csproj/.sln files

Caution:

While this solution offers you flexibility, make sure you clear your nuget cache before acting on them, for example by publishing to a nuget server. Thanks @Benrobot

Bahaa
  • 1,577
  • 18
  • 31
  • This solution is working for me but it should be noted that the dangers of this method are mitigated by CI/CD pipelines. In other words, if you're building locally and publishing binaries from your local machine this is a very dangerous development practice because what you though was 1.0.1 turned out to be some DLL you were debugging. – Benrobot Feb 13 '21 at 16:25
  • Sure. Good point. Will add a warning to the solution. – Bahaa Feb 13 '21 at 16:40