/// <summary> /// Get instances of requested services topologicaly sorted by /// <see cref="DependsOnPluginAttribute"/>. /// </summary> /// <typeparam name="TService">Service type.</typeparam> /// <returns>A list of service instances.</returns> public static IEnumerable <TService> GetServices <TService>() { IEnumerable <Type> types = PluginAssembyLoader.GetTypes <TService>(); types = types.TopologicalSort(x => x .GetCustomAttributes(typeof(DependsOnPluginAttribute), true) .Cast <DependsOnPluginAttribute>() .Select(z => z.GetType()) .ExpandInterfaces()); foreach (Type type in types) { ConstructorInfo ctorInfo = type.GetConstructor(Type.EmptyTypes); if (ctorInfo == null) { throw new ApplicationException( $"Type \"{type.FullName}\" must have a parameterless constructor." ); } TService instance = (TService)Activator.CreateInstance(type); yield return(instance); } }
private static IEnumerable <string> GetAssemblyPathesForAllPlugins( string basePath, IEnumerable <string> whiteList = null) { // Return all the plugin folder names if white list is empty if (whiteList == null || whiteList.Count() == 0) { foreach (string pluginPath in Directory.GetDirectories(basePath)) { foreach (string assemblyPath in PluginAssembyLoader. GetAssemblyPathesForPlugin(pluginPath)) { yield return(assemblyPath); } } yield break; } List <PluginInfo> pluginInfos = new List <PluginInfo>(); // If white list is presend then return the names of plugin folders // while respecting the white list List <string> list = whiteList.Select(s => s).ToList(); foreach (string pluginPath in Directory.GetDirectories(basePath)) { string dirName = Path.GetFileName( pluginPath.RemoveTrailingSlash()); pluginInfos.Add(new PluginInfo { Name = dirName, BasePath = pluginPath }); string listItem = list.FirstOrDefault(s => s.Equals( dirName, StringComparison.InvariantCultureIgnoreCase)); if (!String.IsNullOrWhiteSpace(listItem)) { list.Remove(listItem); foreach (string assemblyPath in PluginAssembyLoader .GetAssemblyPathesForPlugin(pluginPath)) { yield return(assemblyPath); } } } PluginAssembyLoader.PluginInfos = pluginInfos.ToArray(); }
/// <summary> /// Adds services of each plugins to the /// <see cref="IApplicationBuilder"/> request execution pipeline. /// </summary> public static void UsePlugins( this IApplicationBuilder app) { IEnumerable <IConfigureAction> actions = PluginAssembyLoader.GetServices <IConfigureAction>(); foreach (IConfigureAction action in actions) { action.Execute(app); } }
/// <summary> /// Get all the assemblies from white listed plugins /// including all subdirectories into current app domain. /// </summary> /// <param name="basePath">Base directory path for plugins.</param> /// <param name="whiteList">List of plugin names that should be loded, /// all other plugins will be ignored.</param> public static void LoadAssemblies( string basePath, ILoggerFactory loggerFactory, IEnumerable <string> whiteList = null) { PluginAssembyLoader._logger = loggerFactory.CreateLogger(typeof(PluginAssembyLoader)); if (whiteList == null || whiteList.Count() == 0) { PluginAssembyLoader._logger.LogInformation( $"Loading plugin assemblies from \"{basePath}\""); } else { PluginAssembyLoader._logger.LogInformation( "Loading white listed plugin assemblies from \"{0}\"\n\t- {1}", basePath, String.Join("\n\t- ", whiteList)); } // Get all the pathes recursevly respecting whitelist string[] assemblyPathes = PluginAssembyLoader .GetAssemblyPathesForAllPlugins(basePath, whiteList) .ToArray(); PluginAssembyLoader._logger .LogDebug($"Found {assemblyPathes.Count()} plugin assemblies"); // Load all the assemblies PluginAssembyLoader.LoadAssemblies(assemblyPathes); PluginAssembyLoader._logger .LogDebug($"Loading plugin assemblies completed"); AssemblyLoadContext.Default.Resolving += (loadContext, name) => { PluginAssembyLoader._logger.LogDebug( $"Try to resolve \"{name.FullName}\""); return(null); }; }
/// <summary> /// Get all the assembly (*.dll) pathes from the plugin directory /// including all subdirectories. /// </summary> /// <param name="pluginPath">Base path of the plugin directory.</param> /// <returns>A list of assembly pathes.</returns> private static IEnumerable <string> GetAssemblyPathesForPlugin( string pluginPath) { foreach (string assemblyPath in Directory.EnumerateFiles(pluginPath, "*.dll")) { yield return(assemblyPath); } foreach (string subDirPath in Directory.GetDirectories(pluginPath)) { foreach (var subAssemblyPath in PluginAssembyLoader .GetAssemblyPathesForPlugin(subDirPath)) { yield return(subAssemblyPath); } } }
public static void AddPlugins( this IServiceCollection serviceCollection) { IServiceProvider serviceProvider = serviceCollection.BuildServiceProvider(); ILogger logger = serviceProvider .GetService <ILoggerFactory>() .CreateLogger(typeof(IServiceCollectionExtensions)); IEnumerable <IConfigureServicesAction> actions = PluginAssembyLoader.GetServices <IConfigureServicesAction>(); foreach (IConfigureServicesAction action in actions) { logger.LogInformation( "Executing ConfigureServices action \"{0}\"", action.GetType().FullName); action.Execute(serviceCollection); } }
/// <summary> /// Adds MVC to the <see cref="IApplicationBuilder"/> request /// execution pipeline, configured to work with plugin architecture. /// </summary> /// <param name="app"> /// Instance of <see cref="IApplicationBuilder"/>. /// </param> public static void UsePluginsMvc( this IApplicationBuilder app) { ILogger logger = app.ApplicationServices .GetService <ILoggerFactory>().CreateLogger("Plugins"); app.UseMvc(routeBuilder => { foreach (IUseMvcAction action in PluginAssembyLoader .GetServices <IUseMvcAction>()) { logger.LogInformation( "Executing UseMvc action '{0}'", action.GetType().FullName); action.Execute(routeBuilder); } routeBuilder.MapRoute( name: "default", template: "{controller=Home}/{action=Index}/{id?}"); }); }
public static void AddPluginsMvc( this IServiceCollection services, IViewLocationExpander viewLocationExpander = null) { ServiceProvider serviceProvider = services.BuildServiceProvider(); ILogger logger = serviceProvider .GetService <ILoggerFactory>() .CreateLogger(typeof(IServiceCollectionExtensions)); // Dont use uglycase urls ! services.AddRouting((options) => { options.LowercaseUrls = true; }); IMvcBuilder mvcBuilder = services .AddMvc() .SetCompatibilityVersion(CompatibilityVersion.Version_2_1) .AddViewLocalization() .AddDataAnnotationsLocalization() .ConfigureApplicationPartManager(manager => { IApplicationFeatureProvider toRemove = manager .FeatureProviders .First(f => f is MetadataReferenceFeatureProvider); manager.FeatureProviders .Remove(toRemove); manager.FeatureProviders .Add(new ReferencesMetadataReferenceFeatureProvider()); }); IEnumerable <Assembly> assemblies = PluginAssembyLoader.Assemblies; foreach (Assembly assembly in assemblies) { logger.LogDebug( "Adding mvc application part: \"{0}\"", assembly.FullName ); mvcBuilder.AddApplicationPart(assembly); } mvcBuilder.AddRazorOptions(razor => { if (viewLocationExpander != null) { logger.LogDebug( "Replacing default view location expander with: \"{0}\"", viewLocationExpander.GetType().FullName ); razor.ViewLocationExpanders.Clear(); razor.ViewLocationExpanders.Add(viewLocationExpander); } }); IEnumerable <IAddMvcAction> actions = PluginAssembyLoader .GetServices <IAddMvcAction>(); foreach (IAddMvcAction action in actions) { logger.LogDebug( "Executing add mvc action \"{0}\"", action.GetType().FullName ); action.Execute(mvcBuilder); } }