diff --git a/samples/WebAppPluginsLibrary/CustomPlugin.cs b/samples/WebAppPluginsLibrary/CustomPlugin.cs new file mode 100644 index 0000000..de05486 --- /dev/null +++ b/samples/WebAppPluginsLibrary/CustomPlugin.cs @@ -0,0 +1,8 @@ +using System; + +namespace WebAppPluginsLibrary +{ + public class CustomPlugin + { + } +} diff --git a/samples/WebAppPluginsLibrary/WebAppPluginsLibrary.csproj b/samples/WebAppPluginsLibrary/WebAppPluginsLibrary.csproj new file mode 100644 index 0000000..2756020 --- /dev/null +++ b/samples/WebAppPluginsLibrary/WebAppPluginsLibrary.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/samples/WebAppWithAppSettings/Controllers/CalculatorController.cs b/samples/WebAppWithAppSettings/Controllers/CalculatorController.cs new file mode 100644 index 0000000..21d1ae0 --- /dev/null +++ b/samples/WebAppWithAppSettings/Controllers/CalculatorController.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; +using Weikio.PluginFramework.Abstractions; +using Weikio.PluginFramework.AspNetCore; +using Weikio.PluginFramework.Samples.Shared; + +namespace WebAppWithAppSettings.Controllers +{ + [ApiController] + [Route("[controller]")] + public class CalculatorController : ControllerBase + { + private readonly IEnumerable _plugins; + private readonly IServiceProvider _serviceProvider; + private readonly PluginProvider _pluginProvider; + + public CalculatorController(IEnumerable plugins, IServiceProvider serviceProvider, PluginProvider pluginProvider) + { + _plugins = plugins; + _serviceProvider = serviceProvider; + _pluginProvider = pluginProvider; + } + + [HttpGet] + public string Get() + { + var result = new StringBuilder(); + + result.AppendLine("All:"); + foreach (var plugin in _plugins) + { + result.AppendLine($"{plugin.Name}: {plugin.Version}, Tags: {string.Join(", ", plugin.Tags)}"); + } + + var mathPlugins = _pluginProvider.GetByTag("MathOperator"); + var value1 = 10; + var value2 = 20; + + result.AppendLine($"Math operations with values {value1} and {value2}"); + + foreach (var mathPlugin in mathPlugins) + { + var mathPluginInstance = _serviceProvider.Create(mathPlugin); + + var mathResult = mathPluginInstance.Calculate(value1, value2); + result.AppendLine($"{mathPlugin.Name}: {mathResult}"); + } + + return result.ToString(); + } + } +} diff --git a/samples/WebAppWithAppSettings/Program.cs b/samples/WebAppWithAppSettings/Program.cs new file mode 100644 index 0000000..2ee2dc8 --- /dev/null +++ b/samples/WebAppWithAppSettings/Program.cs @@ -0,0 +1,20 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Hosting; + +namespace WebAppWithAppSettings +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + }); + } +} diff --git a/samples/WebAppWithAppSettings/Properties/launchSettings.json b/samples/WebAppWithAppSettings/Properties/launchSettings.json new file mode 100644 index 0000000..6b1360f --- /dev/null +++ b/samples/WebAppWithAppSettings/Properties/launchSettings.json @@ -0,0 +1,14 @@ +{ + "$schema": "http://json.schemastore.org/launchsettings.json", + "profiles": { + "WebAppWithAppSettings": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "Calculator", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "applicationUrl": "https://localhost:5001;http://localhost:5000" + } + } +} diff --git a/samples/WebAppWithAppSettings/Startup.cs b/samples/WebAppWithAppSettings/Startup.cs new file mode 100644 index 0000000..ccaef96 --- /dev/null +++ b/samples/WebAppWithAppSettings/Startup.cs @@ -0,0 +1,40 @@ +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Weikio.PluginFramework.Samples.Shared; +using Weikio.PluginFramework.TypeFinding; + +namespace WebAppWithAppSettings +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + TypeFinderOptions.Defaults.TypeFinderCriterias.Add(TypeFinderCriteriaBuilder.Create().Implements().Tag("MathOperator")); + TypeFinderOptions.Defaults.TypeFinderCriterias.Add(TypeFinderCriteriaBuilder.Create().Tag("All")); + services.AddPluginFramework(); + + services.AddControllers(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseHttpsRedirection(); + + app.UseRouting(); + + app.UseAuthorization(); + + app.UseEndpoints(endpoints => + { + endpoints.MapControllers(); + }); + } + } +} diff --git a/samples/WebAppWithAppSettings/WebAppWithAppSettings.csproj b/samples/WebAppWithAppSettings/WebAppWithAppSettings.csproj new file mode 100644 index 0000000..0462758 --- /dev/null +++ b/samples/WebAppWithAppSettings/WebAppWithAppSettings.csproj @@ -0,0 +1,21 @@ + + + + netcoreapp3.1 + + + + + + + + + + + + Never + + + + + diff --git a/samples/WebAppWithAppSettings/appsettings.Development.json b/samples/WebAppWithAppSettings/appsettings.Development.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/samples/WebAppWithAppSettings/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/samples/WebAppWithAppSettings/appsettings.json b/samples/WebAppWithAppSettings/appsettings.json new file mode 100644 index 0000000..0ae908b --- /dev/null +++ b/samples/WebAppWithAppSettings/appsettings.json @@ -0,0 +1,14 @@ +{ + "PluginFramework": { + "Catalogs": [ + { + "Type": "Folder", + "Path": "..\\Shared\\Weikio.PluginFramework.Samples.SharedPlugins\\bin\\debug\\netcoreapp3.1" + }, + { + "Type": "Assembly", + "Path": ".\\bin\\Debug\\netcoreapp3.1\\WebAppPluginsLibrary.dll" + } + ] + } +} diff --git a/src/PluginFramework.sln b/src/PluginFramework.sln index 5035a3f..235d067 100644 --- a/src/PluginFramework.sln +++ b/src/PluginFramework.sln @@ -63,6 +63,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinFormsPluginsLibrary", ". EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Weikio.PluginFramework.Configuration", "Weikio.PluginFramework.Configuration\Weikio.PluginFramework.Configuration.csproj", "{882BB58E-D256-4BBF-8C5F-80C4FA39B775}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAppWithAppSettings", "..\samples\WebAppWithAppSettings\WebAppWithAppSettings.csproj", "{A5FAE1A5-74A0-4A83-9520-DCABF6610ADE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAppPluginsLibrary", "..\samples\WebAppPluginsLibrary\WebAppPluginsLibrary.csproj", "{38CCE0F7-F998-4766-A14A-44C047E8D0AA}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -173,6 +177,14 @@ Global {882BB58E-D256-4BBF-8C5F-80C4FA39B775}.Debug|Any CPU.Build.0 = Debug|Any CPU {882BB58E-D256-4BBF-8C5F-80C4FA39B775}.Release|Any CPU.ActiveCfg = Release|Any CPU {882BB58E-D256-4BBF-8C5F-80C4FA39B775}.Release|Any CPU.Build.0 = Release|Any CPU + {A5FAE1A5-74A0-4A83-9520-DCABF6610ADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A5FAE1A5-74A0-4A83-9520-DCABF6610ADE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A5FAE1A5-74A0-4A83-9520-DCABF6610ADE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A5FAE1A5-74A0-4A83-9520-DCABF6610ADE}.Release|Any CPU.Build.0 = Release|Any CPU + {38CCE0F7-F998-4766-A14A-44C047E8D0AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {38CCE0F7-F998-4766-A14A-44C047E8D0AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {38CCE0F7-F998-4766-A14A-44C047E8D0AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {38CCE0F7-F998-4766-A14A-44C047E8D0AA}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -200,6 +212,8 @@ Global {A7EC9D91-CED0-43E7-BAB3-3B72230BFC0B} = {FF3A507D-D242-44F6-8940-49E0B51B6F02} {6D392453-FC9E-45AD-A9A7-7596A93B3E6C} = {FF3A507D-D242-44F6-8940-49E0B51B6F02} {E92B0C5D-FFAC-4AB9-B069-869AEED565B0} = {FF3A507D-D242-44F6-8940-49E0B51B6F02} + {A5FAE1A5-74A0-4A83-9520-DCABF6610ADE} = {FF3A507D-D242-44F6-8940-49E0B51B6F02} + {38CCE0F7-F998-4766-A14A-44C047E8D0AA} = {FF3A507D-D242-44F6-8940-49E0B51B6F02} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {AAEBC2B4-FB52-4272-8C25-571FB3CA01E5} diff --git a/src/Weikio.PluginFramework.Abstractions/PluginFrameworkOptions.cs b/src/Weikio.PluginFramework.Abstractions/PluginFrameworkOptions.cs new file mode 100644 index 0000000..d8fe9ab --- /dev/null +++ b/src/Weikio.PluginFramework.Abstractions/PluginFrameworkOptions.cs @@ -0,0 +1,11 @@ +namespace Weikio.PluginFramework.Abstractions +{ + /// + /// Configures the options for Plugin Framework. + /// + public class PluginFrameworkOptions + { + public bool UseConfiguration { get; set; } = true; + public string ConfigurationSection { get; set; } = "PluginFramework"; + } +} diff --git a/src/Weikio.PluginFramework.AspNetCore/PluginProvider.cs b/src/Weikio.PluginFramework.AspNetCore/PluginProvider.cs new file mode 100644 index 0000000..901912a --- /dev/null +++ b/src/Weikio.PluginFramework.AspNetCore/PluginProvider.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Weikio.PluginFramework.Abstractions; + +namespace Weikio.PluginFramework.AspNetCore +{ + public class PluginProvider + { + private readonly IEnumerable _catalogs; + private readonly IServiceProvider _serviceProvider; + + public PluginProvider(IEnumerable catalogs, IServiceProvider serviceProvider) + { + _catalogs = catalogs; + _serviceProvider = serviceProvider; + } + + public List GetByTag(string tag) + { + var result = new List(); + + foreach (var pluginCatalog in _catalogs) + { + var pluginsByTag = pluginCatalog.GetByTag(tag); + result.AddRange(pluginsByTag); + } + + return result; + } + + public List GetPlugins() + { + var result = new List(); + foreach (var pluginCatalog in _catalogs) + { + result.AddRange(pluginCatalog.GetPlugins()); + } + + return result; + } + + public Plugin Get(string name, Version version) + { + foreach (var pluginCatalog in _catalogs) + { + var result = pluginCatalog.Get(name, version); + + if (result != null) + { + return result; + } + } + + return null; + } + + public List GetTypes() where T : class + { + var result = new List(); + var catalogs = _serviceProvider.GetServices(); + + foreach (var catalog in catalogs) + { + var plugins = catalog.GetPlugins(); + + foreach (var plugin in plugins.Where(x => typeof(T).IsAssignableFrom(x))) + { + var op = plugin.Create(_serviceProvider); + + result.Add(op); + } + } + + return result; + } + } +} diff --git a/src/Weikio.PluginFramework.AspNetCore/ServiceCollectionExtensions.cs b/src/Weikio.PluginFramework.AspNetCore/ServiceCollectionExtensions.cs index 2637dac..203ccee 100644 --- a/src/Weikio.PluginFramework.AspNetCore/ServiceCollectionExtensions.cs +++ b/src/Weikio.PluginFramework.AspNetCore/ServiceCollectionExtensions.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection.Extensions; +using Microsoft.Extensions.Options; using Weikio.PluginFramework.Abstractions; using Weikio.PluginFramework.AspNetCore; using Weikio.PluginFramework.Catalogs; @@ -20,9 +21,21 @@ namespace Microsoft.Extensions.DependencyInjection { public static class ServiceCollectionExtensions { - public static IServiceCollection AddPluginFramework(this IServiceCollection services) + public static IServiceCollection AddPluginFramework(this IServiceCollection services, Action configure = null) { + if (configure != null) + { + services.Configure(configure); + } + services.AddHostedService(); + services.AddTransient(); + + services.TryAddTransient(typeof(IPluginCatalogConfigurationLoader), typeof(PluginCatalogConfigurationLoader)); + services.AddTransient(typeof(IConfigurationToCatalogConverter), typeof(FolderCatalogConfigurationConverter)); + services.AddTransient(typeof(IConfigurationToCatalogConverter), typeof(AssemblyCatalogConfigurationCoverter)); + + services.AddConfiguration(); services.AddSingleton(sp => { @@ -47,7 +60,7 @@ public static IServiceCollection AddPluginFramework(this IServiceCollection serv } var aspNetCoreLocation = Path.GetDirectoryName(aspNetCoreControllerAssemblyLocation); - + if (PluginLoadContextOptions.Defaults.AdditionalRuntimePaths == null) { PluginLoadContextOptions.Defaults.AdditionalRuntimePaths = new List(); @@ -60,7 +73,7 @@ public static IServiceCollection AddPluginFramework(this IServiceCollection serv return services; } - + public static IServiceCollection AddPluginFramework(this IServiceCollection services, string dllPath = "") where TType : class { services.AddPluginFramework(); @@ -87,81 +100,74 @@ public static IServiceCollection AddPluginFramework(this IServiceCollecti services.AddPluginCatalog(catalog); services.AddPluginType(); - + return services; } /// - /// Add plugins from the provided object. + /// Add plugins from the IConfiguration. /// /// The on which the plugins will be added. - /// The object that contains the plugin configurations. /// This . - public static IServiceCollection AddPluginFramework( - this IServiceCollection services, - IConfiguration configuration) + private static IServiceCollection AddConfiguration(this IServiceCollection services) { - // Register the default implementation of IPluginCatalogConfigurationLoader with the provided configuration. - services.AddTransient(serviceProvider => - new PluginCatalogConfigurationLoader(configuration)); - - services.TryAddSingleton((Func)(serviceProvider => + services.TryAddSingleton(serviceProvider => { - // Grab all the IPluginCatalogConfigurationLoader implementations to laod catalog configurations. + var options = serviceProvider.GetService>().Value; + + if (options.UseConfiguration == false) + { + return new EmptyPluginCatalog(); + } + + // Grab all the IPluginCatalogConfigurationLoader implementations to load catalog configurations. var loaders = serviceProvider .GetServices() .ToList(); - var converters = serviceProvider.GetServices(); + var configuration = serviceProvider.GetService(); + + var converters = serviceProvider.GetServices().ToList(); var catalogs = new List(); foreach (var loader in loaders) { // Load the catalog configurations. - var catalogConfigs = loader.GetCatalogConfigurations(); + var catalogConfigs = loader.GetCatalogConfigurations(configuration); - if (catalogConfigs == null || catalogConfigs.Count == 0) + if (catalogConfigs?.Any() != true) { - // if no configurations were provided continue. continue; } for (var i = 0; i < catalogConfigs.Count; i++) { var item = catalogConfigs[i]; - var key = $"{loader.SectionKey}:{loader.CatalogsKey}:{i}"; + var key = $"{options.ConfigurationSection}:{loader.CatalogsKey}:{i}"; // Check if a type is provided. - var type = string.IsNullOrWhiteSpace(item.Type) - ? throw new ArgumentException($"A type must be provided for catalog at position {i + 1}") - : item.Type; + if (string.IsNullOrWhiteSpace(item.Type)) + { + throw new ArgumentException($"A type must be provided for catalog at position {i + 1}"); + } // Try to find any registered converter that can convert the specified type. - var foundConverter = converters.FirstOrDefault(converter => converter.CanConvert(type)); - - // Add a catalog to the list of catalogs. - catalogs.Add(foundConverter != null - // If a converter was found we call it's convert method. - ? foundConverter.Convert(loader.Configuration.GetSection(key)) - // If no converter was found (it's null) we proceed with the built-in type converters. - : type switch - { - // Assembly type. - CatalogTypes.Assembly => new AssemblyCatalogConfigurationCoverter().Convert(configuration.GetSection(key)), - - // Folder type. - CatalogTypes.Folder => new FolderCatalogConfigurationConverter().Convert(configuration.GetSection(key)), - - // Unkown type. - _ => throw new ArgumentException($"The type provided for catalog at position {i + 1} is unknown.") - }); + var foundConverter = converters.FirstOrDefault(converter => converter.CanConvert(item.Type)); + + if (foundConverter == null) + { + throw new ArgumentException($"The type provided for Plugin catalog at position {i + 1} is unknown."); + } + + var catalog = foundConverter.Convert(configuration.GetSection(key)); + + catalogs.Add(catalog); } } return new CompositePluginCatalog(catalogs.ToArray()); - })); + }); - services.AddPluginFramework(); return services; } @@ -172,22 +178,23 @@ public static IServiceCollection AddPluginCatalog(this IServiceCollection servic return services; } - public static IServiceCollection AddPluginType(this IServiceCollection services, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where T : class + public static IServiceCollection AddPluginType(this IServiceCollection services, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) + where T : class { var serviceDescriptorEnumerable = new ServiceDescriptor(typeof(IEnumerable), sp => { - var result = GetTypes(sp); + var pluginProvider = sp.GetService(); + var result = pluginProvider.GetTypes(); return result.AsEnumerable(); - }, serviceLifetime); - + var serviceDescriptorSingle = new ServiceDescriptor(typeof(T), sp => { - var result = GetTypes(sp); + var pluginProvider = sp.GetService(); + var result = pluginProvider.GetTypes(); return result.FirstOrDefault(); - }, serviceLifetime); services.Add(serviceDescriptorEnumerable); @@ -195,25 +202,5 @@ public static IServiceCollection AddPluginType(this IServiceCollection servic return services; } - - private static List GetTypes(IServiceProvider sp) where T : class - { - var result = new List(); - var catalogs = sp.GetServices(); - - foreach (var catalog in catalogs) - { - var plugins = catalog.GetPlugins(); - - foreach (var plugin in plugins.Where(x => typeof(T).IsAssignableFrom(x))) - { - var op = plugin.Create(sp); - - result.Add(op); - } - } - - return result; - } } } diff --git a/src/Weikio.PluginFramework.AspNetCore/ServiceProviderExtensions.cs b/src/Weikio.PluginFramework.AspNetCore/ServiceProviderExtensions.cs index 4b98d68..447ed92 100644 --- a/src/Weikio.PluginFramework.AspNetCore/ServiceProviderExtensions.cs +++ b/src/Weikio.PluginFramework.AspNetCore/ServiceProviderExtensions.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Generic; +using System.Linq; using Weikio.PluginFramework.Abstractions; // ReSharper disable once CheckNamespace diff --git a/src/Weikio.PluginFramework.Configuration/CatalogTypes.cs b/src/Weikio.PluginFramework.Configuration/CatalogTypes.cs deleted file mode 100644 index 16e58ff..0000000 --- a/src/Weikio.PluginFramework.Configuration/CatalogTypes.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Weikio.PluginFramework.Configuration -{ - /// - /// Static class tha contains the Types and their keys - /// that can be configured from a configuration. - /// - public static class CatalogTypes - { - public const string Assembly = "Assembly"; - public const string Folder = "Folder"; - } -} diff --git a/src/Weikio.PluginFramework.Configuration/Converters/AssemblyCatalogConfigurationCoverter.cs b/src/Weikio.PluginFramework.Configuration/Converters/AssemblyCatalogConfigurationCoverter.cs index 0a29d1f..d1f0ef9 100644 --- a/src/Weikio.PluginFramework.Configuration/Converters/AssemblyCatalogConfigurationCoverter.cs +++ b/src/Weikio.PluginFramework.Configuration/Converters/AssemblyCatalogConfigurationCoverter.cs @@ -1,3 +1,4 @@ +using System; using Microsoft.Extensions.Configuration; using Weikio.PluginFramework.Abstractions; using Weikio.PluginFramework.Catalogs; @@ -12,7 +13,7 @@ public class AssemblyCatalogConfigurationCoverter : IConfigurationToCatalogConve /// public bool CanConvert(string type) { - return type == CatalogTypes.Assembly; + return string.Equals(type, "Assembly", StringComparison.InvariantCultureIgnoreCase); } /// diff --git a/src/Weikio.PluginFramework.Configuration/Converters/FolderCatalogConfigurationConverter.cs b/src/Weikio.PluginFramework.Configuration/Converters/FolderCatalogConfigurationConverter.cs index 131a467..ecf92db 100644 --- a/src/Weikio.PluginFramework.Configuration/Converters/FolderCatalogConfigurationConverter.cs +++ b/src/Weikio.PluginFramework.Configuration/Converters/FolderCatalogConfigurationConverter.cs @@ -14,14 +14,14 @@ public class FolderCatalogConfigurationConverter : IConfigurationToCatalogConver /// public bool CanConvert(string type) { - return type == CatalogTypes.Folder; + return string.Equals(type, "Folder", StringComparison.InvariantCultureIgnoreCase); } /// public IPluginCatalog Convert(IConfigurationSection configuration) { var path = configuration.GetValue("Path") - ?? throw new ArgumentException("PuglinFramework FolderCatalog requires a Path."); + ?? throw new ArgumentException("Plugin Framework's FolderCatalog requires a Path."); var options = new CatalogFolderOptions(); configuration.Bind($"Options", options); diff --git a/src/Weikio.PluginFramework.Configuration/Providers/IPluginCatalogConfigurationLoader.cs b/src/Weikio.PluginFramework.Configuration/Providers/IPluginCatalogConfigurationLoader.cs index ab2206f..b3bb115 100644 --- a/src/Weikio.PluginFramework.Configuration/Providers/IPluginCatalogConfigurationLoader.cs +++ b/src/Weikio.PluginFramework.Configuration/Providers/IPluginCatalogConfigurationLoader.cs @@ -8,26 +8,16 @@ namespace Weikio.PluginFramework.Configuration.Providers /// public interface IPluginCatalogConfigurationLoader { - /// - /// The key of the section inside the configuration. - /// - string SectionKey { get; } - /// /// The key of the catalogs section inside the parent configuration section (). /// string CatalogsKey { get; } - /// - /// The configuration that contains the options to load. - /// - IConfiguration Configuration { get; } - /// /// Returns a list that contains catalog configurations. /// /// The configuration to use. /// A list that contains catalog configurations. - List GetCatalogConfigurations(); + List GetCatalogConfigurations(IConfiguration configuration); } } diff --git a/src/Weikio.PluginFramework.Configuration/Providers/PluginCatalogConfigurationLoader.cs b/src/Weikio.PluginFramework.Configuration/Providers/PluginCatalogConfigurationLoader.cs index 4bf63eb..68862bd 100644 --- a/src/Weikio.PluginFramework.Configuration/Providers/PluginCatalogConfigurationLoader.cs +++ b/src/Weikio.PluginFramework.Configuration/Providers/PluginCatalogConfigurationLoader.cs @@ -1,5 +1,7 @@ using System.Collections.Generic; using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Options; +using Weikio.PluginFramework.Abstractions; namespace Weikio.PluginFramework.Configuration.Providers { @@ -9,26 +11,22 @@ namespace Weikio.PluginFramework.Configuration.Providers /// public class PluginCatalogConfigurationLoader : IPluginCatalogConfigurationLoader { - /// - public virtual string SectionKey => "PluginFramework"; + private PluginFrameworkOptions _options; /// public virtual string CatalogsKey => "Catalogs"; - /// - public IConfiguration Configuration { get; private set; } - - public PluginCatalogConfigurationLoader(IConfiguration configuration) + public PluginCatalogConfigurationLoader(IOptions options) { - Configuration = configuration; + _options = options.Value; } /// - public List GetCatalogConfigurations() + public List GetCatalogConfigurations(IConfiguration configuration) { var catalogs = new List(); - Configuration.Bind($"{SectionKey}:{CatalogsKey}", catalogs); + configuration.Bind($"{_options.ConfigurationSection}:{CatalogsKey}", catalogs); return catalogs; } diff --git a/src/Weikio.PluginFramework.Configuration/Weikio.PluginFramework.Configuration.csproj b/src/Weikio.PluginFramework.Configuration/Weikio.PluginFramework.Configuration.csproj index a1db89b..2247c2f 100644 --- a/src/Weikio.PluginFramework.Configuration/Weikio.PluginFramework.Configuration.csproj +++ b/src/Weikio.PluginFramework.Configuration/Weikio.PluginFramework.Configuration.csproj @@ -3,10 +3,24 @@ netcoreapp3.1 enable + true + true + IConfiguration support for Plugin Framework. + IConfiguration support for Plugin Framework. + Weikio.PluginFramework.Configuration + Weikio.PluginFramework.Configuration + Weikio.PluginFramework.Configuration + plugins;addons;aspnetextensions;plugin framework;configuration + logo_transparent_color_256.png + IConfiguration support for Plugin Framework - + + + + + diff --git a/src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalog.cs b/src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalog.cs index fa9d109..7eebebc 100644 --- a/src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalog.cs +++ b/src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalog.cs @@ -185,12 +185,20 @@ public async Task Initialize() var finder = new TypeFinder(); + var handledPluginTypes = new List(); foreach (var typeFinderCriteria in _options.TypeFinderOptions.TypeFinderCriterias) { var pluginTypes = finder.Find(typeFinderCriteria, _assembly, _pluginAssemblyLoadContext); foreach (var type in pluginTypes) { + if (handledPluginTypes.Contains(type)) + { + // Make sure to create only a single type plugin catalog for each plugin type. + // The type catalog will add all the matching tags + continue; + } + var typePluginCatalog = new TypePluginCatalog(type, new TypePluginCatalogOptions() { @@ -202,6 +210,8 @@ public async Task Initialize() await typePluginCatalog.Initialize(); _plugins.Add(typePluginCatalog); + + handledPluginTypes.Add(type); } } diff --git a/tests/unit/Weikio.PluginFramework.Tests/TagTests.cs b/tests/unit/Weikio.PluginFramework.Tests/TagTests.cs index 50428be..a9dd454 100644 --- a/tests/unit/Weikio.PluginFramework.Tests/TagTests.cs +++ b/tests/unit/Weikio.PluginFramework.Tests/TagTests.cs @@ -137,6 +137,7 @@ public DefaultOptions() { TypeFinderOptions.Defaults.TypeFinderCriterias.Add(TypeFinderCriteriaBuilder.Create().Tag("CustomTag")); TypeFinderOptions.Defaults.TypeFinderCriterias.Add(TypeFinderCriteriaBuilder.Create().HasName(nameof(TypePlugin)).Tag("MyTag_1")); + TypeFinderOptions.Defaults.TypeFinderCriterias.Add(TypeFinderCriteriaBuilder.Create().HasName("*Json*").Tag("MyTag_1")); } [Fact] @@ -158,6 +159,32 @@ public async Task CanTagUsingDefaultOptions() TypeFinderOptions.Defaults.TypeFinderCriterias.Clear(); } + [Fact] + public async Task DefaultTagsWithFolderCatalogTypeShouldNotDuplicatePlugins() + { + var catalog = new FolderPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonNew\netstandard2.0"); + await catalog.Initialize(); + + Assert.Single(catalog.GetPlugins()); + var plugin = catalog.Get(); + + Assert.Equal(2, plugin.Tags.Count); + TypeFinderOptions.Defaults.TypeFinderCriterias.Clear(); + } + + [Fact] + public async Task DefaultTagsWithAssemblyCatalogTypeShouldNotDuplicatePlugins() + { + var catalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonNew\netstandard2.0\JsonNetNew.dll"); + await catalog.Initialize(); + + Assert.Single(catalog.GetPlugins()); + var plugin = catalog.Get(); + + Assert.Equal(2, plugin.Tags.Count); + TypeFinderOptions.Defaults.TypeFinderCriterias.Clear(); + } + public void Dispose() { TypeFinderOptions.Defaults.TypeFinderCriterias.Clear();