// // Copyright (c) Microsoft. All rights reserved. // Licensed under the MIT license. See LICENSE file in the project root for full license information. // using System; using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Composition.Convention; using System.Composition.Hosting; using System.IO; using System.Linq; using System.Reflection; using System.Runtime.Loader; using Microsoft.SqlTools.Hosting.Utility; namespace Microsoft.SqlTools.Hosting.Extensibility { /// /// A MEF-based service provider. Supports any MEF-based configuration but is optimized for /// service discovery over a set of DLLs in an application scope. Any service registering using /// the [Export(IServiceContract)] attribute will be discovered and used by this service /// provider if it's in the set of Assemblies / Types specified during its construction. Manual /// override of this is supported by calling /// and similar methods, since /// this will initialize that service contract and avoid the MEF-based search and discovery /// process. This allows the service provider to link into existing singleton / known services /// while using MEF-based dependency injection and inversion of control for most of the code. /// public class ExtensionServiceProvider : RegisteredServiceProvider { private Func config; public ExtensionServiceProvider(Func config) { Validate.IsNotNull(nameof(config), config); this.config = config; } /// /// Creates a service provider by loading a set of named assemblies, expected to be /// /// Directory to search for included assemblies /// full DLL names, case insensitive, of assemblies to include /// instance public static ExtensionServiceProvider CreateFromAssembliesInDirectory(string directory, IList inclusionList) { //AssemblyLoadContext context = new AssemblyLoader(directory); var assemblyPaths = Directory.GetFiles(directory, "*.dll", SearchOption.TopDirectoryOnly); List assemblies = new List(); foreach (var path in assemblyPaths) { // skip DLL files not in inclusion list bool isInList = false; foreach (var item in inclusionList) { if (path.EndsWith(item, StringComparison.OrdinalIgnoreCase)) { isInList = true; break; } } if (!isInList) { continue; } try { assemblies.Add(AssemblyLoadContext.Default.LoadFromAssemblyPath(path)); } catch (Exception) { // we expect exceptions trying to scan all DLLs since directory contains native libraries } } return Create(assemblies); } public static ExtensionServiceProvider Create(IEnumerable assemblies) { Validate.IsNotNull(nameof(assemblies), assemblies); return new ExtensionServiceProvider(conventions => new ContainerConfiguration().WithAssemblies(assemblies, conventions)); } public static ExtensionServiceProvider Create(IEnumerable types) { Validate.IsNotNull(nameof(types), types); return new ExtensionServiceProvider(conventions => new ContainerConfiguration().WithParts(types, conventions)); } protected override IEnumerable GetServicesImpl() { EnsureExtensionStoreRegistered(); return base.GetServicesImpl(); } private void EnsureExtensionStoreRegistered() { if (!services.ContainsKey(typeof(T))) { ExtensionStore store = new ExtensionStore(typeof(T), config); Register(() => store.GetExports()); } } /// /// Merges in new assemblies to the existing container configuration. /// public void AddAssembliesToConfiguration(IEnumerable assemblies) { Validate.IsNotNull(nameof(assemblies), assemblies); var previousConfig = config; this.config = conventions => { // Chain in the existing configuration function's result, then include additional // assemblies ContainerConfiguration containerConfig = previousConfig(conventions); return containerConfig.WithAssemblies(assemblies, conventions); }; } } /// /// A store for MEF exports of a specific type. Provides basic wrapper functionality around MEF to standarize how /// we lookup types and return to callers. /// public class ExtensionStore { private readonly CompositionHost host; private IList exports; private readonly Type contractType; /// /// Initializes the store with a type to lookup exports of, and a function that configures the /// lookup parameters. /// /// Type to use as a base for all extensions being looked up /// Function that returns the configuration to be used public ExtensionStore(Type contractType, Func configure) { Validate.IsNotNull(nameof(contractType), contractType); Validate.IsNotNull(nameof(configure), configure); this.contractType = contractType; ConventionBuilder builder = GetExportBuilder(); ContainerConfiguration config = configure(builder); host = config.CreateContainer(); } public IEnumerable GetExports() { if (exports == null) { exports = host.GetExports(contractType).ToList(); } return exports.Cast(); } private ConventionBuilder GetExportBuilder() { // Define exports as matching a parent type, export as that parent type var builder = new ConventionBuilder(); builder.ForTypesDerivedFrom(contractType).Export(exportConventionBuilder => exportConventionBuilder.AsContractType(contractType)); return builder; } } }