Multi-targeting source code packages with Nuget


Time goes on and change is constant. What we thought it worked, it may not now; sometimes for a good reason, sometimes not.

This time I'll dig on what I had to do to attach code to a solution, even if the solution is targeting the classic .NET Framework (net)or the newer .NET Core Framework (netcore). Because that is what I had to do myself.

Because I had to

I recently wrote about the lifting NMoneys third-party serialization packages got.
The lifting was motivated by the will to check whether such serialization code worked in newer versions of the serialization libraries and, furthermore, in the new .NET Core Framework (netcore).
Long story short: code kind of worked, but the package did not, which kind of diminish the "working" moniker of the code because, what good is working code that cannot be executed?

Why didn't it?

Third party serialization packages are deployed as "source code packages", that is, a NuGet package that does not include any binary but, instead, packs some source code files written in C# that, when the package is installed, they get added to your solution and compiled along.

When authoring such packages, one would include some files under a /content/* folder for convention and declare them in the <files> element.


When I installed such package on a netcore solution, no matter which method of adding the package:

  • dotnet add package ... from command line
  • Add it from the Manage Project Packages dialog
  • Install-Package ... from the Package Manager Console

It did-not-work. I could neither see the code as part of my solution nor use the classes after compilation.

I was kind of puzzled until I found this post from NuGet's blog itself. The post is kind of "old", but I have not had the need to dig into the differences between NuGet 2.x and 3.x because: a) I barely worked with netcore solutions b) it just worked for me in classic net solutions (as it should).
What the post says is that from Nuget 3.0 and on (and Nuget3 is the one that supports netcore), language-specific files need to be located under folders following the convention /contentFiles/{codeLanguage}/{target_moniker}/* declared in the <metadata>/<contentFiles> element, but also in <files>, which is not that obvious from reading the post or the specific documentation about the subject.

Out with the old

Putting the good-old trial-and-error algorithm to work I finally found out a way to get my code compiled and usable from netcore solutions. Not without caveats, mind you. But I will rant later on those.

However, when following the new way of doing things, people that installed the package from an old net solution, would not get the code added to their solution
There has to be a way to make it work, but I could not find anything on the web, so I write this post.

Multi-targeting source files

The key is do both things:

  • for the old net projects: place the files under /content/* and declare them in <files><file src="content\..."></files>
  • for the new netcore projects: place the files under /contentFiles/cs/any/* and declare them in <metadata>/<contentFiles> and <files><file src="contentFiles\..."></files>

You can check the .nuspec file for NuGet:

And this is how the files look when packing the .nuspec file:

And this is how the package looks like:


When the package is created this way and installed to a net project, the files under /content will be added to the solution; and when installed to a netcore project, the files under /contentFiles/cs/any will be "referenced". In both classes, source code will be compiled along your project and classes can be used.

Is that it? For the "uncurious", yes. But if you are reading this, obviously, you are not. Things get more complicated.

Caveats, please

I mentioned that the same package can be installed and code compiled from both net and netcore projects, what else you could possibly have to tell?

When the package is installed to a net project, source code files are copied "physically" to the subfolder within the project.
One can see the code and change it at will (upgrading packages with content has always been a sensitive topic, to the point of suggesting a non-enforced folder convention that includes locating the source files under /content/App_Packages/{package_id}.{package_version} to tackle the problem -a nasty workaround, if you ask me-).

When the package is installed to a netcore project, source code files are not copied to the project folder. They are deflated to the local NuGet package cache and "magically" they are available for consumption for your solution. Magic is relative, though.
If Visual Studio is used to install the package, nothing seems to happen. However, when the project is opened again, the source files will "appear" as linked files. I believe this is a bug and it might be solved in further releases.
But, for the moment, the end-user experience is terrible: install a package and nothing happens. First thing a user will think is "broken package, uninstalling" and that is really unfair for the package creator.
I, for one, will be adding a warning in my description and updating the project documentation, but strongly believe the team have not solved a bad scenario (package upgrade) that happened only under certain circumstances (when source code was mutated and package upgraded) only to introduce a terrible scenario that occurs every time a source-code package is installed.


After closing and re-opening the solution:


If Visual Studio is not used (for example, VSCode), there is absolutely no hint that those files are available for the solution and they not appear in the .csproj file.

Another caveat is the fact that those source code files are not meant to be changed (hence the static files nick-name). However... nothing prevents you from navigating to their content and change them. Any guess at what will happen? Yes, they will be changed for every project that links to them. Talking about surprises.

Working for the moment

I still believe source code files can play a role in code distribution. And I got mine working in a multi-targeting manner. But I think the caveats are serious enough to got me thinking moving onto a binary distribution instead.

share class ,