-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Description
This is a proposal to add new public API which would expose functionality to help with resolution of managed and unmanaged dependencies of components.
Proposed Surface Area
namespace System.Runtime.Loader
{
public sealed class ComponentDependencyResolver
{
public ComponentDependencyResolver(string componentAssemblyPath);
public string ResolveAssemblyToPath(AssemblyName assemblyName);
public string ResolveUnmanagedDllToPath(string unmanagedDllName);
}
}
Functionality
Given the path to a component assembly (the main .dll
of a given component, for example the build result of a class library project), the constructor creates a resolver object which can resolve managed and unmanaged dependencies of the component. The constructor would look for the .deps.json
file next to the main assembly and use it to compute the set of dependencies.
The ResolveAssemblyToPath
and ResolveUnmanagedDllToPath
methods are then used to resolve references to managed and unmanaged dependencies. These methods take the name of the dependency and return either null if such dependency can't be resolved by the component, or a full path to the file (managed assembly or unmanaged library).
The constructor is expected to catch most error cases and report them as exceptions. The Resolve
methods should in general not throw and instead return null if the dependency can't be resolved.
Scenario: Dynamic component loading
The proposed API can be used to greatly simplify dynamic loading of components. It provides a powerful building block to use for implementing custom AssemblyLoadContext
or event handlers for the binding events like AppDomain.AssemblyResolve
and AssemblyLoadContext.Resolving
.
Example of using the new API to load plugins with AssemblyLoadContext
in isolation:
class PluginLoadContext : AssemblyLoadContext
{
ComponentDependencyResolver _resolver;
public PluginLoadContext(string pluginPath)
{
_resolver = new ComponentDependencyResolver(pluginPath);
}
public override Assembly Load(AssemblyName assemblyName)
{
string assemblyPath = _resolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return LoadFromAssemblyPath(assemblyPath);
}
return null;
}
}
PluginLoadContext pluginContext = new PluginLoadContext("/pathtoplugin/plugin.dll");
Assembly pluginAssembly = pluginContext.LoadFromAssemblyName(new AssemblyName("Plugin"));
// ... use the pluginAssembly and reflection to invoke functionality from the plugin.
// Dependencies of the plugin are resolved by the event handler above using the resolver
// to provide the actual resolution from assembly name to file path.
Scenario: Inspecting IL metadata of components
Using the newly proposed MetadataLoadContext
API (see the proposal) to inspect IL metadata of components. This API requires an assembly resolver to resolve dependencies of the component. The proposed ComponentDependencyResolver
would be used to implement such resolver for components produced by the .NET Core SDK.
Example of using the new API to implement MetadataAssemblyResolver
:
public class ComponentMetadataAssemblyResolver : MetadataAssemblyResolver
{
private ComponentDependencyResolver dependencyResolver;
public override Assembly Resolve(MetadataLoadContext context, AssemblyName assemblyName)
{
string assemblyPath = dependencyResolver.ResolveAssemblyToPath(assemblyName);
if (assemblyPath != null)
{
return context.LoadFromAssemblyPath(assemblyPath);
}
// Code to load framework dependencies from the running app for example
// using Assembly.Load and so on.
return null;
}
}
Context
.NET Core SDK (used by VS, VS Code, VS for Mac and so on) describes component dependencies in the build output via the .deps.json
files (description). These files are consumed by the hosting components (dotnet.exe
or the app's executable) and they're used to compute the list of dependencies needed to run the application. This happens at startup and through this mechanism all static dependencies of the app are resolved.
Currently there's no such mechanism for components which are loaded dynamically. Applications can use Microsoft.Extensions.DependencyModel
package which provides object model of the .deps.json
file, but it's relatively complex to use this for dependency resolution. It's also very likely that the behavior of such custom solution would be somewhat different from what the hosting layer does for static dependencies.
Open issues
- Naming - The use of
Component
in the class name was chosen to differentiate fromAssembly
as the proposed API will only work on entire components produced by the SDK. UsingAssembly
seems to mean that the API would inspect the assembly itself to determine its dependencies, which is not the purpose of this API. That said it could be either. Also using the termResolver
can be seen as somewhat misleading. Resolve in the context of assembly binding typically means to find and actually load the dependency. The purpose of this API is to simply find the file, not to load it. So maybe it should be more explicit by using for examplePathResolver
. Candidates then could beAssemblyDepednencyResolver
,AssemblyDependencyPathResolver
,ComponentDependencyPathResolver
.
Notes
- As proposed the resolver would not resolve framework dependencies. For the typical case of dynamically loaded component, resolving framework dependencies would actually just introduce more issues and probably provide unwanted behavior. This is something we would look into in the future, as it's an important scenario for the
MetadataLoadContext
. - The resolver has no ties to the runtime. This means that the dependencies are resolved to file paths without any consideration to what assemblies are already loaded into the application. This is important for scenarios where full isolation is required. For partial or no isolation scenarios it is expected that the loading of the dependencies will be combined with the appropriate fallback to the default load context.