Why is delivery over NuGet feed used for .NET Core?

From OPC Labs Knowledge Base

Effective with version 2018.3, QuickOPC supports development for Microsoft .NET Core. QuickOPC libraries for .NET Core are only delivered through the public NuGet feed (http://www.nuget.org ). This is in contrast to the .NET Framework, where the developer can reference one or more QuickOPC .NET assemblies (there are only 5 or so of them at maximum) that are delivered in product's installation package (and the use of NuGet packages is simply a possible alternative for .NET Framework development). This article attempts to explain why NuGet is the only delivery vehicle for QuickOPC .libraries that target .NET Core.

TL;DR: NuGet feed is used because it is, in our understanding, the only approach that works well for the developer.

Introduction: How it works in .NET Framework

Let's explain the physical composition of QuickOPC when targeting the .NET Framework first. We will explain it on QuickOPC for OPC Unified Architecture, because it is the most relevant aspect needed for comparison with .NET Core. Your project developed with QuickOPC looks like this:

  • You reference the OpcLabs.BaseLib and OpcLabs.EasyOpcUA assemblies from one or more of your project's assemblies.
  • These two OpcLabs.* assemblies only reference assemblies that are part of the .NET Framework itself, and assemblies of OPC Foundation's UA .NET Stack/SDK which are embedded inside the OpcLabs.* assemblies (alternatively, they can be deployed alongside).
  • The .NET Framework, with all its assemblies, is one monolithic piece, sitting pre-installed on the Windows computer (depending on the version, it either comes with the OS or is installed separately).

By shipping a handful of assemblies (in the above example, just OpcLabs.BaseLib and OpcLabs.EasyOpcUA), and instructing the user to have proper .NET Framework installed on the Windows computer, we basically have everything in place to run your project. The number of files we (and you) need to ship is small, stable, and completely predictable.

The .NET Core is different

.NET Core uses NuGet packages as its primary referencing mechanism for itself and all the software that builds upon it, and therefore we could almost dismiss the question by saying that using NuGet is simply the way to do things. But there are good reasons for using NuGet on our side as well - in fact, it would be difficult to do it in any other way.

With .NET Core, you are entering a landscape that is much more modularized, more diverse, and also dynamic. More modularized: .NET Core does not come in one piece, but in many separate modules that are referenced separately. More diverse: Because it does not run only on Windows, but also on Linux, and other systems - and different distributions and versions of them too. In addition, the modules and their composition and mutual references differ depending on the target system! Dynamic: The referencing mechanism does not necessarily require that precise versions of certain packages and assemblies are always used; for example, newer version of a package can be pulled in if available, and the referencing package allows it.

Let's try to build a QuickOPC project for .NET Core that is equivalent to the .NET Framework project described above, and doing it the same way - by referencing specific assemblies and trying to ship them with the product:

  • You reference the OpcLabs.BaseLib and OpcLabs.EasyOpcUA assemblies from one or more of your project's assemblies. So far so good.
  • Right on the next level, the approach will break. We cannot just reference assemblies of OPC UA .NET Stack/SDK, because they are not delivered as assemblies - they are already delivered as NuGet packages. OK - maybe we can try to extract those assemblies from their NuGet package? Yes - but each of them comes in multiple forms, depending on the target platform. Which one should we reference? We do not know upfront... so the only solution would be to build as many forms of our assemblies as there are those of the assemblies we want to reference, and then leave the developer to choose those he wants to reference. This is already bad, because that would force the developer to pick the target upfront - either preventing him/her from building a multitarget solution, or forcing him to build it separately for each possible combination.
  • But it does not stop here. The OPC UA .NET Stack/SDK references many pieces of .NET Core and also other NuGet packages (which are not part of .NET Core), and we have no control over them. The other NuGet packages have further dependencies, and so on. The total composition may come to tens or hundreds of modules (that would actually be the smaller problem), which will differ based on the target operating system and its distribution and version, .NET runtime and its version, and possibly even change over time as new versions of referenced NuGet packages become available. These last aspects are the show-stopper - it is realistically impossible to manage that: we cannot replicate the logic of this complex fabric of conditions and relations on our side, and even less maintain it over time. It wouldn't be wise anyway - obviously it is the authors of the referenced packages that can best maintain them (and their dependencies).

You might say that we could switch to the package referencing model, and still deliver our packages using an installer or similar means - but not over the public NuGet feed. That would, however, only bring complications:

  • The developer would have to study the documentation in order to figure out how to download the packages, and configure a local NuGet feed just for them. That is not what the developers expect today (installing a NuGet package from the public feed is almost a one-click experience), and many of them will even hesitate to do it just for this purpose.
  • We would be able to deliver only the assemblies of the QuickOPC product, and possibly the assemblies of OPC UA .NET Stack/SDK, in this way. The remaining referenced packages and their assemblies will still have to be pulled from the Web (the public NuGet feed). For this reason, there will be little gain with this approach - and only the disadvantages mentioned above will remain.

Consequences, non-consequences, and the future


The usage of package reference model and public NuGet feed for QuickOPC delivery means that we no longer have full control over the software that gets referenced/pulled in/executed/used (or however you call it) when you reference the QuickOPC package(s). We only have some control over the first level of packages we reference (which, at the time of writing this article, is basically only the OPC UA .NET Stack/SDK), and that is still a partial control only. The packages may change without us having chance to learn about it or influence it (in our software, we always reference a precise version of any package; but that package may have dependencies that it chooses to specify in looser terms).

If the directly/indirectly packages are licensed, the developer then becomes responsible for complying with their license terms, in addition to any license terms of QuickOPC itself. NuGet clients typically require the developer to accept the individual package licenses, as the packages are being imported into the user's project (package authors can control this aspect the way they like).

Physically, the QuickOPC assemblies for .NET Core (in the NuGet package) no longer contain code of OPC UA .NET Stack/SDK, or other packages. We just *reference* them.


By placing our software on the public NuGet feed, it does not become open source or anything alike.

The technical license enforcement of QuickOPC is not influenced by the fact that the code resides on the public NuGet feed. Without a license key, the libraries still run in 30-minute trial mode.

The future

Unless there is a compelling reason, we do not plan to remove the traditional delivery method of assemblies for .NET Framework by the means of an installer. We currently know about one reason why we may want to do so nevertheless: It would be in case that some NuGet package becomes essential for future development of the .NET Framework part of the product (because referencing a NuGet package basically implies a need to publish the software as a NuGet package as well - as explained earlier above).