Supporting third party libraries/frameworks
See original GitHub issueRecently, we had enquiries for a customer who wanted to deploy their own custom framework on client machines. This discussion is about possible solutions for this scenario in .Net Core 3.
Customer requirements
The developer wants to:
- Implement a suite of several libraries, that are used by several client apps.
- Deploy one copy of library-suite on client machines in a central location
- Push updates to the library-suite without having to recompile the client apps
- Is happy to change their library-suite/apps to target .net core 3
In particular, the customer specifically asked allowing third party frameworks (similar to standard frameworks like Microsoft.AspNetCore.App
).
Solution Options
Additional-deps
This is a simple solution to separate some of the app’s dependencies to a separate (possible shared) location. This option involves:
- App development: Remove references to the library-suite from the apps, instead use a different light-up option such as startup-hook.
- On client machines: Deploy the library-suite to a shared location, and set up
DOTNET_ADDITIONAL_DEPS
when apps are run.
Additional-deps have several known limitations, for example, as noted here.
Startup Hook
This option involves dynamically loading the library-suite into apps during application startup.
- This requires setting
DOTNET_STARTUP_HOOKS
to appropriate loader code on client machines at app-startup. The loader code can then use custom logic to load the library-suite from a possible shared/versioned location. - This may require some coordination with other parties (ex: profiler) that may use the startup-hook (multiple assemblies can be hooked up at startup).
Custom AppHost
The library developer provides a custom host to be used by the app-developers. The custom host will then setup the appropriate library dependencies during app startup, before starting the runtime.
Components
This option is to load the library-suite as a plugin, where:
- The library-suite exposes a minimal version (similar to reference assemblies) which only exposes the public API, and implements stub code to load the implementation assemblies dynamically.
- Apps build with a package-reference to the suite. At runtime, the above stub code loads the actual library code (say, using the AssemblyDependencyResolver), from a possibly shared location.
Third-party frameworks
In this option, the library-suite is published as a framework. This naturally achieves the customer requirements because:
- The library-suite is published as a nuget package that contains:
- Its reference assemblies
- A target file that can be imported by the apps to include this suite as an additional target framework
- Apps import/reference the above nuget package and ship independent of the library-suite’s implementation
- The implementation assemblies of the library-suite are installed to
dotnet/shared/<library-suite>/<version>
on client machines. (one-copy shared, versioned location).
The above approach is straight-forward for developers to use. However, support for third-party frameworks is not yet officially available.
I tried out a prototype, and was able to load a custom framework from a shared location (with some work-arounds for issues discussed below).
Pending work
Here’s the known required work, there will likely be more as we productize this feature:
- Tooling for adding frameworks: It should easy to add a framework to the list of dependent frameworks published in the
runtimeconfig.json
file (ex: add a framework toRuntimeFramework
item). However, currently doesn’t work in the CLI because the property is overwritten by one of the build tasks. This should be a straightforward issue to fix. - Mismatched shared framework versions can cause problems: dotnet/core-setup#4947. This is again a bug that should be fixed.
Reservations
While we agree that supporting third-party frameworks is useful for developers, there are some reservations about making this feature publicly available in .Net Core 3. The concerns are:
- Bugs: We have a controlled environment with known frameworks. Custom frameworks may trigger unforeseen bugs. It is better to let the multiple-frameworks feature bake with existing framework for a release cycle.
- Framework references: There are unresolved issues with supporting custom framework-references (which are not exposed by using third-party frameworks with package-references).
- Install location: Unanticipated issues with custom code within
dotnet
install location (dotnet/shared/…
). - Design limitation: We have previously excluded custom frameworks from .net core 3. We may redesign the issue in .net core 3.1. But exposing the feature now may create a compatibility requirement, and restrict design options. Note that users may themselves figure out and use custom frameworks – but these are not supported by the
dotnet
org.
Discussion
Please add your thoughts / considerations about:
- Exposing the possibility of third-party frameworks to customers, say as a sample.
- Any other options to solve the customer scenario.
- Similar usage scenarios
Issue Analytics
- State:
- Created 5 years ago
- Comments:8 (6 by maintainers)
I agree with @nguerrera. We haven’t designed a solution for custom frameworks (or whatever you want to call them). In general, we like the scenario, but it’s hard to design and get right. This is the “full complexity of NuGet” comment that @nguerrera made. We have a way to reason about that for the frameworks that we ship, exactly because they have no NuGet dependencies (by design). If custom frameworks had no NuGet dependencies, the feature would be much easier, but it would also be way too limiting for most of the code that people would want to use for the scenario.
This topic was raised way back in .NET Core 1.0. Along the way, we’ve had customers ask for it, multiple Microsoft teams and also from @normj (our friend at AWS). In short, this may be the hardest objectively reasonable un-delivered customer request we have. We can call that a HORUCR!
We plan on enabling the Xamarin frameworks on top of netcoreapp in the .NET 5 project. As we do that, I’m sure we’ll discuss this scenario again. That said, I don’t think any of the basic challenges will disappear.
Another way to think about this is with .NET Framework. It has the GAC, which satisfies the problem of sharing. The GAC, however, is completely disconnected from NuGet. So, when you deploy updates to the GAC, you are in control of validating that the NuGet graph is kept whole. I’m ignoring all the issues with .NET Framework assembly versioning and publisher policy for this discussion. You might wonder why it is so important to keep the NuGet graph whole. It’s our only way to reason about compatibility across indirect package references (meaning a graph).
Some apps and frameworks might be really simple, so all of this discussion is overkill, however, something like Visual Studio and all the plugins (VSIXs) it supports would be a horror show to get right. When we build features, we need to ensure that they work correctly for the edge cases. We strongly try to avoid 80% features/scenarios. Our strongest advo-critics are always in the 20% case and let us know when we mess up.
I was mostly referring to other software i have seen / worked with. Many of which have been started in the .NET 2.0 era and use the GAC. I am in no way suggesting that (it would likely make you unhappy, also .NET Core doesn’t have a GAC comparable with the .NET Framework GAC).
Recent versions of the roslyn compiler are able to emit reference assemblies and the functionality has been wired up in MSBuild, see refout.md for details. There is not yet a built-in way to create reference-assembly-only NuGet packages though, this would have to be done manually using targets. Rx.NET already ships NuGet packages with reference and implementation assemblies by using custom targets (see the PR that introduced them).