diff --git a/samples/Shared/IOperator.cs b/samples/Shared/IOperator.cs new file mode 100644 index 0000000..06bcfd9 --- /dev/null +++ b/samples/Shared/IOperator.cs @@ -0,0 +1,7 @@ +namespace Shared +{ + public interface IOperator + { + int Calculate(int x, int y); + } +} diff --git a/samples/Shared/IPlugin.cs b/samples/Shared/IPlugin.cs index d52e839..20f74d0 100644 --- a/samples/Shared/IPlugin.cs +++ b/samples/Shared/IPlugin.cs @@ -11,9 +11,4 @@ public interface IOutPlugin { string Get(); } - - public interface IOperator - { - int Calculate(int x, int y); - } } diff --git a/samples/Shared/RemainderOperator.cs b/samples/Shared/RemainderOperator.cs new file mode 100644 index 0000000..e8612b7 --- /dev/null +++ b/samples/Shared/RemainderOperator.cs @@ -0,0 +1,10 @@ +namespace Shared +{ + public class RemainderOperator : IOperator + { + public int Calculate(int x, int y) + { + return x % y; + } + } +} diff --git a/samples/SharedPlugins/MultiplyOperator.cs b/samples/SharedPlugins/MultiplyOperator.cs index 629f815..bbbe87c 100644 --- a/samples/SharedPlugins/MultiplyOperator.cs +++ b/samples/SharedPlugins/MultiplyOperator.cs @@ -1,7 +1,9 @@ -using Shared; +using System.ComponentModel; +using Shared; namespace SharedPlugins { + [DisplayName("The multiplier plugin")] public class MultiplyOperator : IOperator { public int Calculate(int x, int y) @@ -9,4 +11,4 @@ public int Calculate(int x, int y) return x * y; } } -} \ No newline at end of file +} diff --git a/samples/WebApp/Controllers/CalculatorController.cs b/samples/WebApp/Controllers/CalculatorController.cs new file mode 100644 index 0000000..dfc7d3d --- /dev/null +++ b/samples/WebApp/Controllers/CalculatorController.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Mvc; +using Shared; +using Weikio.PluginFramework.Abstractions; + +namespace WebApp.Controllers +{ + [ApiController] + [Route("[controller]")] + public class CalculatorController : ControllerBase + { + private readonly IEnumerable<IOperator> _operators; + private readonly IEnumerable<Plugin> _plugins; + + public CalculatorController(IEnumerable<IOperator> operators, IEnumerable<Plugin> plugins, IOperator myOperator) + { + _operators = operators; + _plugins = plugins; + } + + [HttpGet] + public string Get() + { + var pluginsList = string.Join(", ", _plugins); + + return pluginsList; + } + } +} diff --git a/samples/WebApp/Program.cs b/samples/WebApp/Program.cs new file mode 100644 index 0000000..f9af38d --- /dev/null +++ b/samples/WebApp/Program.cs @@ -0,0 +1,21 @@ +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace WebApp +{ + 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<Startup>(); + }); + } +} diff --git a/samples/WebApp/Startup.cs b/samples/WebApp/Startup.cs new file mode 100644 index 0000000..49ae288 --- /dev/null +++ b/samples/WebApp/Startup.cs @@ -0,0 +1,60 @@ +using System.Collections.Generic; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Shared; +using Weikio.PluginFramework.Abstractions; +using Weikio.PluginFramework.Catalogs; + +namespace WebApp +{ + public class Startup + { + public Startup(IConfiguration configuration) + { + Configuration = configuration; + } + + public IConfiguration Configuration { get; } + + // This method gets called by the runtime. Use this method to add services to the container. + public void ConfigureServices(IServiceCollection services) + { + var folderPluginCatalog = new FolderPluginCatalog(@"..\SharedPlugins\bin\debug\netcoreapp3.1", type => + { + type.Implements<IOperator>(); + }); + + services.AddPluginFramework() + .AddPluginCatalog(folderPluginCatalog) + .AddPluginType<IOperator>(); + + // Alternatively + // services.AddPluginFramework<IOperator>(@"..\SharedPlugins\bin\debug\netcoreapp3.1"); + + services.AddControllers(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + 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/WebApp/WebApp.csproj b/samples/WebApp/WebApp.csproj new file mode 100644 index 0000000..0130830 --- /dev/null +++ b/samples/WebApp/WebApp.csproj @@ -0,0 +1,16 @@ +<Project Sdk="Microsoft.NET.Sdk.Web"> + + <PropertyGroup> + <TargetFramework>netcoreapp3.1</TargetFramework> + </PropertyGroup> + + <ItemGroup> + <ProjectReference Include="..\..\src\Weikio.PluginFramework.Abstractions\Weikio.PluginFramework.Abstractions.csproj" /> + <ProjectReference Include="..\..\src\Weikio.PluginFramework.AspNetCore\Weikio.PluginFramework.AspNetCore.csproj" /> + <ProjectReference Include="..\..\src\Weikio.PluginFramework\Weikio.PluginFramework.csproj" /> + <ProjectReference Include="..\SharedPlugins\SharedPlugins.csproj" /> + <ProjectReference Include="..\Shared\Shared.csproj" /> + </ItemGroup> + + +</Project> diff --git a/samples/WebApp/appsettings.Development.json b/samples/WebApp/appsettings.Development.json new file mode 100644 index 0000000..8983e0f --- /dev/null +++ b/samples/WebApp/appsettings.Development.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + } +} diff --git a/samples/WebApp/appsettings.json b/samples/WebApp/appsettings.json new file mode 100644 index 0000000..d9d9a9b --- /dev/null +++ b/samples/WebApp/appsettings.json @@ -0,0 +1,10 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft": "Warning", + "Microsoft.Hosting.Lifetime": "Information" + } + }, + "AllowedHosts": "*" +} diff --git a/samples/WpfApp/MainWindow.xaml.cs b/samples/WpfApp/MainWindow.xaml.cs index b08af9f..0701575 100644 --- a/samples/WpfApp/MainWindow.xaml.cs +++ b/samples/WpfApp/MainWindow.xaml.cs @@ -22,9 +22,10 @@ private async void MainWindow_OnLoaded(object sender, RoutedEventArgs e) type.Implements<IOperator>(); }); - var assemblyPluginCatalog = new AssemblyPluginCatalog(typeof(MainWindow).Assembly); + var assemblyPluginCatalog = new AssemblyPluginCatalog(typeof(MainWindow).Assembly, type => typeof(IOperator).IsAssignableFrom(type)); + var typePluginCatalog = new TypePluginCatalog(typeof(RemainderOperator)); - var pluginCatalog = new CompositePluginCatalog(folderPluginCatalog, assemblyPluginCatalog); + var pluginCatalog = new CompositePluginCatalog(folderPluginCatalog, assemblyPluginCatalog, typePluginCatalog); await pluginCatalog.Initialize(); var allPlugins = pluginCatalog.GetPlugins(); diff --git a/src/PluginFramework.sln b/src/PluginFramework.sln index 6b82da4..78e15c1 100644 --- a/src/PluginFramework.sln +++ b/src/PluginFramework.sln @@ -32,6 +32,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfApp", "..\samples\WpfApp EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SharedPlugins", "..\samples\SharedPlugins\SharedPlugins.csproj", "{6E19B5B2-9106-4EB3-8B0E-1C735FB4C815}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebApp", "..\samples\WebApp\WebApp.csproj", "{30C02979-E0C0-4AD0-B88F-23EF28807473}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Weikio.PluginFramework.AspNetCore", "Weikio.PluginFramework.AspNetCore\Weikio.PluginFramework.AspNetCore.csproj", "{E4F3E21D-653A-4AE7-B1DA-63B3F041367C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -90,6 +94,18 @@ Global {6E19B5B2-9106-4EB3-8B0E-1C735FB4C815}.Debug|Any CPU.Build.0 = Debug|Any CPU {6E19B5B2-9106-4EB3-8B0E-1C735FB4C815}.Release|Any CPU.ActiveCfg = Release|Any CPU {6E19B5B2-9106-4EB3-8B0E-1C735FB4C815}.Release|Any CPU.Build.0 = Release|Any CPU + {30C02979-E0C0-4AD0-B88F-23EF28807473}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {30C02979-E0C0-4AD0-B88F-23EF28807473}.Debug|Any CPU.Build.0 = Debug|Any CPU + {30C02979-E0C0-4AD0-B88F-23EF28807473}.Release|Any CPU.ActiveCfg = Release|Any CPU + {30C02979-E0C0-4AD0-B88F-23EF28807473}.Release|Any CPU.Build.0 = Release|Any CPU + {7FAF2032-2445-42BF-BB0C-A2ECBAEAE56A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FAF2032-2445-42BF-BB0C-A2ECBAEAE56A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FAF2032-2445-42BF-BB0C-A2ECBAEAE56A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FAF2032-2445-42BF-BB0C-A2ECBAEAE56A}.Release|Any CPU.Build.0 = Release|Any CPU + {E4F3E21D-653A-4AE7-B1DA-63B3F041367C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E4F3E21D-653A-4AE7-B1DA-63B3F041367C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E4F3E21D-653A-4AE7-B1DA-63B3F041367C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E4F3E21D-653A-4AE7-B1DA-63B3F041367C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {6CDBFF3C-932B-4A66-8779-B5C72B33AF7F} = {DE041517-4760-4748-97F6-DB71D6AFFF5B} @@ -104,5 +120,6 @@ Global {DBC0AD60-7261-4CEB-BE4F-3AD12C39F813} = {FF3A507D-D242-44F6-8940-49E0B51B6F02} {91A58039-85B5-4B2C-8F25-10DD6033F3AA} = {FF3A507D-D242-44F6-8940-49E0B51B6F02} {6E19B5B2-9106-4EB3-8B0E-1C735FB4C815} = {FF3A507D-D242-44F6-8940-49E0B51B6F02} + {30C02979-E0C0-4AD0-B88F-23EF28807473} = {FF3A507D-D242-44F6-8940-49E0B51B6F02} EndGlobalSection EndGlobal diff --git a/src/Weikio.PluginFramework.Abstractions/IPluginCatalog.cs b/src/Weikio.PluginFramework.Abstractions/IPluginCatalog.cs index 3f7dd4d..167d368 100644 --- a/src/Weikio.PluginFramework.Abstractions/IPluginCatalog.cs +++ b/src/Weikio.PluginFramework.Abstractions/IPluginCatalog.cs @@ -7,15 +7,26 @@ namespace Weikio.PluginFramework.Abstractions { public interface IPluginCatalog { + /// <summary> + /// Initializes the catalog + /// </summary> Task Initialize(); + + /// <summary> + /// Gets if the catalog is initialized + /// </summary> bool IsInitialized { get; } - Task<List<PluginOld>> GetPluginsOld(); - Task<PluginOld> GetPlugin(string name, Version version); - Task<Assembly> GetAssembly(PluginOld definition); - bool SupportsUnload { get; } - Task Unload(); - bool Unloaded { get; } + + /// <summary> + /// Gets all the plugins + /// </summary> + /// <returns>List of <see cref="Plugin"/></returns> List<Plugin> GetPlugins(); + + /// <summary> + /// Gets a single plugin based on its name and version + /// </summary> + /// <returns>The <see cref="Plugin"/></returns> Plugin Get(string name, Version version); } } diff --git a/src/Weikio.PluginFramework.Abstractions/PluginDefinition.cs b/src/Weikio.PluginFramework.Abstractions/PluginDefinition.cs index 5f7e9d2..9c1c455 100644 --- a/src/Weikio.PluginFramework.Abstractions/PluginDefinition.cs +++ b/src/Weikio.PluginFramework.Abstractions/PluginDefinition.cs @@ -32,16 +32,16 @@ public override string ToString() // return await GetPlugins(new Dictionary<string, Predicate<Type>>()); // } - public async Task<List<Type>> GetTypes() - { - return await GetTypes(new Dictionary<string, Predicate<Type>>()); - } - - public async Task<List<Type>> GetTypes(Predicate<Type> filter) - { - var taggedFilters = new Dictionary<string, Predicate<Type>>() { { string.Empty, filter } }; - return await GetTypes(taggedFilters); - } + // public async Task<List<Type>> GetTypes() + // { + // return await GetTypes(new Dictionary<string, Predicate<Type>>()); + // } + // + // public async Task<List<Type>> GetTypes(Predicate<Type> filter) + // { + // var taggedFilters = new Dictionary<string, Predicate<Type>>() { { string.Empty, filter } }; + // return await GetTypes(taggedFilters); + // } // public async Task<Plugin> GetPlugins(Predicate<Type> filter) // { @@ -79,31 +79,31 @@ public async Task<List<Type>> GetTypes(Predicate<Type> filter) // return result; // } - public async Task<List<Type>> GetTypes(Dictionary<string, Predicate<Type>> taggedFilters) - { - var assembly = await Source.GetAssembly(this); - - if (assembly == null) - { - throw new ArgumentException(); - } - - var allTypes = assembly.GetExportedTypes(); - var taggedTypes = new List<(string Tag, Type Type)>(); - - if (taggedFilters?.Any() == true) - { - foreach (var taggedFilter in taggedFilters) - { - taggedTypes.AddRange(allTypes.Where(x => taggedFilter.Value(x)).Select(x => (taggedFilter.Key, x))); - } - } - else - { - taggedTypes.AddRange(allTypes.Select(x => (string.Empty, x))); - } - - return taggedTypes.Select(x => x.Type).ToList(); - } + // public async Task<List<Type>> GetTypes(Dictionary<string, Predicate<Type>> taggedFilters) + // { + // var assembly = await Source.GetAssembly(this); + // + // if (assembly == null) + // { + // throw new ArgumentException(); + // } + // + // var allTypes = assembly.GetExportedTypes(); + // var taggedTypes = new List<(string Tag, Type Type)>(); + // + // if (taggedFilters?.Any() == true) + // { + // foreach (var taggedFilter in taggedFilters) + // { + // taggedTypes.AddRange(allTypes.Where(x => taggedFilter.Value(x)).Select(x => (taggedFilter.Key, x))); + // } + // } + // else + // { + // taggedTypes.AddRange(allTypes.Select(x => (string.Empty, x))); + // } + // + // return taggedTypes.Select(x => x.Type).ToList(); + // } } } diff --git a/src/Weikio.PluginFramework.AspNetCore/PluginExtensions.cs b/src/Weikio.PluginFramework.AspNetCore/PluginExtensions.cs new file mode 100644 index 0000000..5f6a61e --- /dev/null +++ b/src/Weikio.PluginFramework.AspNetCore/PluginExtensions.cs @@ -0,0 +1,14 @@ +using System; +using Weikio.PluginFramework.Abstractions; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.DependencyInjection +{ + public static class PluginExtensions + { + public static T Create<T>(this Plugin plugin, IServiceProvider serviceProvider) where T : class + { + return ActivatorUtilities.CreateInstance(serviceProvider, plugin) as T; + } + } +} diff --git a/src/Weikio.PluginFramework.AspNetCore/PluginFrameworkInitializer.cs b/src/Weikio.PluginFramework.AspNetCore/PluginFrameworkInitializer.cs new file mode 100644 index 0000000..42819e5 --- /dev/null +++ b/src/Weikio.PluginFramework.AspNetCore/PluginFrameworkInitializer.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; +using Weikio.PluginFramework.Abstractions; + +namespace Weikio.PluginFramework.AspNetCore +{ + public class PluginFrameworkInitializer : BackgroundService + { + private readonly IEnumerable<IPluginCatalog> _pluginCatalogs; + private readonly ILogger<PluginFrameworkInitializer> _logger; + + public PluginFrameworkInitializer(IEnumerable<IPluginCatalog> pluginCatalogs, ILogger<PluginFrameworkInitializer> logger) + { + _pluginCatalogs = pluginCatalogs; + _logger = logger; + } + + protected override async Task ExecuteAsync(CancellationToken stoppingToken) + { + try + { + _logger.LogInformation("Initializing {PluginCatalogCount} plugin catalogs", _pluginCatalogs.Count()); + + foreach (var pluginCatalog in _pluginCatalogs) + { + try + { + _logger.LogDebug("Initializing {PluginCatalog}", pluginCatalog); + + await pluginCatalog.Initialize(); + + _logger.LogDebug("Initialized {PluginCatalog}", pluginCatalog); + _logger.LogTrace("Found the following plugins from {PluginCatalog}:", pluginCatalog); + + foreach (var plugin in pluginCatalog.GetPlugins()) + { + _logger.LogTrace(plugin.ToString()); + } + } + catch (Exception e) + { + _logger.LogError(e, "Failed to initialize {PluginCatalog}", pluginCatalog); + } + } + + _logger.LogInformation("Initialized {PluginCatalogCount} plugin catalogs", _pluginCatalogs.Count()); + } + catch (Exception e) + { + _logger.LogError(e, "Failed to initialize plugin catalogs"); + + throw; + } + } + } +} diff --git a/src/Weikio.PluginFramework.AspNetCore/ServiceCollectionExtensions.cs b/src/Weikio.PluginFramework.AspNetCore/ServiceCollectionExtensions.cs new file mode 100644 index 0000000..152e140 --- /dev/null +++ b/src/Weikio.PluginFramework.AspNetCore/ServiceCollectionExtensions.cs @@ -0,0 +1,115 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Weikio.PluginFramework.Abstractions; +using Weikio.PluginFramework.AspNetCore; +using Weikio.PluginFramework.Catalogs; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.DependencyInjection +{ + public static class ServiceCollectionExtensions + { + public static IServiceCollection AddPluginFramework(this IServiceCollection services, Action<TypeFinderCriteriaBuilder> configureTypeFinder = null, string folderPath = "") + { + services.AddHostedService<PluginFrameworkInitializer>(); + + services.AddSingleton(sp => + { + var result = new List<Plugin>(); + var catalogs = sp.GetServices<IPluginCatalog>(); + + foreach (var catalog in catalogs) + { + var plugins = catalog.GetPlugins(); + + result.AddRange(plugins); + } + + return result.AsEnumerable(); + }); + + if (configureTypeFinder == null) + { + return services; + } + + + + return services; + } + + public static IServiceCollection AddPluginFramework<TType>(this IServiceCollection services, string dllPath = "") where TType : class + { + services.AddPluginFramework(); + + if (string.IsNullOrWhiteSpace(dllPath)) + { + dllPath = Environment.CurrentDirectory; + } + + var typeFinderCriteria = TypeFinderCriteriaBuilder.Create() + .AssignableTo(typeof(TType)) + .Build(); + + var catalog = new FolderPluginCatalog(dllPath, typeFinderCriteria); + services.AddPluginCatalog(catalog); + + services.AddPluginType<TType>(); + + return services; + } + + public static IServiceCollection AddPluginCatalog(this IServiceCollection services, IPluginCatalog pluginCatalog) + { + services.TryAddEnumerable(ServiceDescriptor.Singleton(typeof(IPluginCatalog), pluginCatalog)); + + return services; + } + + public static IServiceCollection AddPluginType<T>(this IServiceCollection services, ServiceLifetime serviceLifetime = ServiceLifetime.Transient) where T : class + { + var serviceDescriptorEnumerable = new ServiceDescriptor(typeof(IEnumerable<T>), sp => + { + var result = GetTypes<T>(sp); + + return result.AsEnumerable(); + + }, serviceLifetime); + + var serviceDescriptorSingle = new ServiceDescriptor(typeof(T), sp => + { + var result = GetTypes<T>(sp); + + return result.FirstOrDefault(); + + }, serviceLifetime); + + services.Add(serviceDescriptorEnumerable); + services.Add(serviceDescriptorSingle); + + return services; + } + + private static List<T> GetTypes<T>(IServiceProvider sp) where T : class + { + var result = new List<T>(); + var catalogs = sp.GetServices<IPluginCatalog>(); + + foreach (var catalog in catalogs) + { + var plugins = catalog.GetPlugins(); + + foreach (var plugin in plugins.Where(x => typeof(T).IsAssignableFrom(x))) + { + var op = plugin.Create<T>(sp); + + result.Add(op); + } + } + + return result; + } + } +} diff --git a/src/Weikio.PluginFramework.AspNetCore/ServiceProviderExtensions.cs b/src/Weikio.PluginFramework.AspNetCore/ServiceProviderExtensions.cs new file mode 100644 index 0000000..f6314ce --- /dev/null +++ b/src/Weikio.PluginFramework.AspNetCore/ServiceProviderExtensions.cs @@ -0,0 +1,14 @@ +using System; +using Weikio.PluginFramework.Abstractions; + +// ReSharper disable once CheckNamespace +namespace Microsoft.Extensions.DependencyInjection +{ + public static class ServiceProviderExtensions + { + public static T Create<T>(this IServiceProvider serviceProvider, Plugin plugin) where T : class + { + return ActivatorUtilities.CreateInstance(serviceProvider, plugin) as T; + } + } +} diff --git a/src/Weikio.PluginFramework.AspNetCore/Weikio.PluginFramework.AspNetCore.csproj b/src/Weikio.PluginFramework.AspNetCore/Weikio.PluginFramework.AspNetCore.csproj new file mode 100644 index 0000000..ddb0f06 --- /dev/null +++ b/src/Weikio.PluginFramework.AspNetCore/Weikio.PluginFramework.AspNetCore.csproj @@ -0,0 +1,41 @@ +<Project Sdk="Microsoft.NET.Sdk"> + + <PropertyGroup> + <TargetFramework>netcoreapp3.1</TargetFramework> + <IsPackable>true</IsPackable> + <GeneratePackageOnBuild>true</GeneratePackageOnBuild> + <Authors>Weik.io</Authors> + <Company>Weik.io</Company> + <Description>Plugin Framework for ASP.NET Core.</Description> + <PackageDescription>Plugin Framework for ASP.NET Core.</PackageDescription> + <PackageProjectUrl>https://www.pluginframework.com</PackageProjectUrl> + <Copyright>©2020 Weik.io</Copyright> + <PackageId>Weikio.PluginFramework.AspNetCore</PackageId> + <Product>Weikio.PluginFramework.AspNetCore</Product> + <AssemblyName>Weikio.PluginFramework.AspNetCore</AssemblyName> + <PackageLicenseExpression>MIT</PackageLicenseExpression> + <RepositoryUrl>https://github.com/weikio/PluginFramework</RepositoryUrl> + <PackageTags>plugins;addons;aspnetextensions;plugin framework</PackageTags> + <PackageIcon>logo_256.png</PackageIcon> + <Title>Plugin Framework for ASP.NET Core</Title> + <MinVerMinimumMajorMinor>1.0</MinVerMinimumMajorMinor> + </PropertyGroup> + + <ItemGroup> + <FrameworkReference Include="Microsoft.AspNetCore.App" /> + </ItemGroup> + + <ItemGroup> + <None Include="../../docs/logo_256.png" Pack="true" Visible="false" PackagePath="" /> + </ItemGroup> + + <ItemGroup> + <PackageReference Include="MinVer" Version="2.0.0" /> + </ItemGroup> + + <ItemGroup> + <ProjectReference Include="..\Weikio.PluginFramework.Abstractions\Weikio.PluginFramework.Abstractions.csproj" /> + <ProjectReference Include="..\Weikio.PluginFramework\Weikio.PluginFramework.csproj" /> + </ItemGroup> + +</Project> \ No newline at end of file diff --git a/src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalog.cs b/src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalog.cs index e2203f1..0fe7e96 100644 --- a/src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalog.cs +++ b/src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalog.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Reflection; @@ -14,9 +13,36 @@ public class AssemblyPluginCatalog : IPluginCatalog { private readonly string _assemblyPath; private Assembly _assembly; - private PluginOld _pluginOld; private readonly AssemblyPluginCatalogOptions _options; private PluginAssemblyLoadContext _pluginAssemblyLoadContext; + private List<TypePluginCatalog> _plugins = null; + + // TODO: Remove the duplicate code from constructors + public AssemblyPluginCatalog(string assemblyPath) : this(assemblyPath, options: null) + { + } + + public AssemblyPluginCatalog(Assembly assembly) : this(assembly, options: null) + { + } + + public AssemblyPluginCatalog(string assemblyPath, AssemblyPluginCatalogOptions options = null) + { + if (string.IsNullOrWhiteSpace(assemblyPath)) + { + throw new ArgumentNullException(nameof(assemblyPath)); + } + + _assemblyPath = assemblyPath; + _options = options ?? new AssemblyPluginCatalogOptions(); + } + + public AssemblyPluginCatalog(Assembly assembly, AssemblyPluginCatalogOptions options = null) + { + _assembly = assembly; + _assemblyPath = _assembly.Location; + _options = options ?? new AssemblyPluginCatalogOptions(); + } public AssemblyPluginCatalog(string assemblyPath, TypeFinderCriteria criteria = null, AssemblyPluginCatalogOptions options = null) { @@ -33,6 +59,27 @@ public AssemblyPluginCatalog(string assemblyPath, TypeFinderCriteria criteria = _options.TypeFinderCriterias.Add(string.Empty, criteria); } } + + public AssemblyPluginCatalog(string assemblyPath, Action<TypeFinderCriteriaBuilder> configureFinder = null, AssemblyPluginCatalogOptions options = null) + { + if (string.IsNullOrWhiteSpace(assemblyPath)) + { + throw new ArgumentNullException(nameof(assemblyPath)); + } + + _assemblyPath = assemblyPath; + _options = options ?? new AssemblyPluginCatalogOptions(); + + if (configureFinder != null) + { + var builder = new TypeFinderCriteriaBuilder(); + configureFinder(builder); + + var criteria = builder.Build(); + + _options.TypeFinderCriterias.Add("", criteria); + } + } public AssemblyPluginCatalog(string assemblyPath, Predicate<Type> filter = null, AssemblyPluginCatalogOptions options = null) { @@ -46,9 +93,7 @@ public AssemblyPluginCatalog(string assemblyPath, Predicate<Type> filter = null, if (filter != null) { - var criteria = new TypeFinderCriteria(); - - criteria.Query = (context, type) => filter(type); + var criteria = new TypeFinderCriteria { Query = (context, type) => filter(type) }; _options.TypeFinderCriterias.Add(string.Empty, criteria); } @@ -62,9 +107,7 @@ public AssemblyPluginCatalog(Assembly assembly, Predicate<Type> filter = null, A if (filter != null) { - var criteria = new TypeFinderCriteria(); - - criteria.Query = (context, type) => filter(type); + var criteria = new TypeFinderCriteria { Query = (context, type) => filter(type) }; _options.TypeFinderCriterias.Add(string.Empty, criteria); } @@ -80,12 +123,7 @@ public AssemblyPluginCatalog(string assemblyPath, Dictionary<string, Predicate<T _assemblyPath = assemblyPath; _options = options ?? new AssemblyPluginCatalogOptions(); - foreach (var taggedFilter in taggedFilters) - { - var criteria = new TypeFinderCriteria { Query = (context, type) => taggedFilter.Value(type) }; - - _options.TypeFinderCriterias.Add(taggedFilter.Key, criteria); - } + SetFilters(taggedFilters); } public AssemblyPluginCatalog(Assembly assembly, Dictionary<string, Predicate<Type>> taggedFilters, AssemblyPluginCatalogOptions options = null) @@ -94,6 +132,11 @@ public AssemblyPluginCatalog(Assembly assembly, Dictionary<string, Predicate<Typ _assemblyPath = _assembly.Location; _options = options ?? new AssemblyPluginCatalogOptions(); + SetFilters(taggedFilters); + } + + private void SetFilters(Dictionary<string, Predicate<Type>> taggedFilters) + { foreach (var taggedFilter in taggedFilters) { var criteria = new TypeFinderCriteria { Query = (context, type) => taggedFilter.Value(type) }; @@ -102,7 +145,7 @@ public AssemblyPluginCatalog(Assembly assembly, Dictionary<string, Predicate<Typ } } - public Task Initialize() + public async Task Initialize() { if (!string.IsNullOrWhiteSpace(_assemblyPath) && _assembly == null) { @@ -115,138 +158,63 @@ public Task Initialize() _assembly = _pluginAssemblyLoadContext.Load(); } - _pluginOld = AssemblyToPluginDefinitionConverter.Convert(_assembly, this); - - IsInitialized = true; - - return Task.CompletedTask; - } - - public bool IsInitialized { get; private set; } - - public Task<List<PluginOld>> GetPluginsOld() - { - if (Unloaded) - { - throw new CatalogUnloadedException(); - } - - var result = new List<PluginOld>() { _pluginOld }; + _plugins = new List<TypePluginCatalog>(); - return Task.FromResult(result); - } + var finder = new TypeFinder(); - public Task<PluginOld> GetPlugin() - { - if (Unloaded) + if (_options.TypeFinderCriterias?.Any() != true) { - throw new CatalogUnloadedException(); - } - - return Task.FromResult(_pluginOld); - } + var findAll = new TypeFinderCriteria() + { + Query = (context, type) => true + }; - public Task<PluginOld> GetPlugin(string name, Version version) - { - if (Unloaded) - { - throw new CatalogUnloadedException(); + if (_options.TypeFinderCriterias == null) + { + _options.TypeFinderCriterias = new Dictionary<string, TypeFinderCriteria>(); + } + + _options.TypeFinderCriterias.Add(string.Empty, findAll); } - if (!string.Equals(name, _pluginOld.Name, StringComparison.InvariantCultureIgnoreCase) || - version != _pluginOld.Version) + foreach (var typeFinderCriteria in _options.TypeFinderCriterias) { - return Task.FromResult<PluginOld>(null); - } - - return Task.FromResult(_pluginOld); - } - - public Task<Assembly> GetAssembly(PluginOld definition) - { - return Task.FromResult(_assembly); - } + var pluginTypes = finder.Find(typeFinderCriteria.Value, _assembly, _pluginAssemblyLoadContext); - public bool SupportsUnload { get; } = true; + foreach (var type in pluginTypes) + { + var typePluginCatalog = new TypePluginCatalog(type, new TypePluginCatalogOptions() { PluginNameOptions = _options.PluginNameOptions }); + await typePluginCatalog.Initialize(); - public Task Unload() - { - if (Unloaded) - { - return Task.CompletedTask; + _plugins.Add(typePluginCatalog); + } } - _pluginAssemblyLoadContext.Unload(); - - Unloaded = true; - - return Task.CompletedTask; + IsInitialized = true; } - public bool Unloaded { get; private set; } + public bool IsInitialized { get; private set; } public List<Plugin> GetPlugins() { - var finder = new TypeFinder(); + return _plugins.SelectMany(x => x.GetPlugins()).ToList(); + } - var result = new List<Plugin>(); - - foreach (var typeFinderCriteria in _options.TypeFinderCriterias) + public Plugin Get(string name, Version version) + { + foreach (var pluginCatalog in _plugins) { - var pluginTypes = finder.Find(typeFinderCriteria.Value, _assembly, _pluginAssemblyLoadContext); + var foundPlugin = pluginCatalog.Get(name, version); - foreach (var type in pluginTypes) + if (foundPlugin == null) { - var opt = new TypePluginCatalogOptions(); - var version = opt.PluginVersionGenerator(opt, type); - var pluginName = opt.PluginNameGenerator(opt, type); - var description = opt.PluginDescriptionGenerator(opt, type); - var productVersion = opt.PluginProductVersionGenerator(opt, type); - - var plugin = new Plugin(type.Assembly, type, pluginName, version, this, description, productVersion); - result.Add(plugin); + continue; } - } - return result; - - // - // var allTypes = assembly.GetExportedTypes(); - // var taggedTypes = new List<(string Tag, Type Type)>(); - // - // if (_filters?.Any() == true) - // { - // foreach (var taggedFilter in _filters) - // { - // taggedTypes.AddRange(allTypes.Where(x => taggedFilter.Value(x)).Select(x => (taggedFilter.Key, x))); - // } - // } - // else - // { - // taggedTypes.AddRange(allTypes.Select(x => (string.Empty, x))); - // } - // - // var result = new List<Plugin>(); - // - // foreach (var taggedType in taggedTypes) - // { - // var versionInfo = FileVersionInfo.GetVersionInfo(assembly.Location); - // var pluginName = taggedType.Type.FullName; - // var fileVersion = Version.Parse(versionInfo.FileVersion); - // var description = versionInfo.Comments; - // var productVersion = versionInfo.ProductVersion; - // - // var p = new Plugin(_assembly, taggedType.Type, pluginName, fileVersion, this, description, productVersion, taggedType.Tag); - // - // result.Add(p); - // } - // - // return result; - } + return foundPlugin; + } - public Plugin Get(string name, Version version) - { - throw new NotImplementedException(); + return null; } } } diff --git a/src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalogOptions.cs b/src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalogOptions.cs index 2532f6c..fa0f1c8 100644 --- a/src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalogOptions.cs +++ b/src/Weikio.PluginFramework/Catalogs/AssemblyPluginCatalogOptions.cs @@ -7,5 +7,6 @@ public class AssemblyPluginCatalogOptions { public PluginLoadContextOptions PluginLoadContextOptions = new PluginLoadContextOptions(); public Dictionary<string, TypeFinderCriteria> TypeFinderCriterias = new Dictionary<string, TypeFinderCriteria>(); + public PluginNameOptions PluginNameOptions { get; set; } = new PluginNameOptions(); } } diff --git a/src/Weikio.PluginFramework/Catalogs/CompositePluginCatalog.cs b/src/Weikio.PluginFramework/Catalogs/CompositePluginCatalog.cs index 21c6418..24fc4f1 100644 --- a/src/Weikio.PluginFramework/Catalogs/CompositePluginCatalog.cs +++ b/src/Weikio.PluginFramework/Catalogs/CompositePluginCatalog.cs @@ -12,59 +12,34 @@ public class CompositePluginCatalog : IPluginCatalog private readonly List<IPluginCatalog> _catalogs; public bool IsInitialized { get; private set; } - public async Task<List<PluginOld>> GetPluginsOld() + public List<Plugin> GetPlugins() { - var result = new List<PluginOld>(); + var result = new List<Plugin>(); foreach (var pluginCatalog in _catalogs) { - var pluginsInCatalog = await pluginCatalog.GetPluginsOld(); + var pluginsInCatalog = pluginCatalog.GetPlugins(); result.AddRange(pluginsInCatalog); } return result; } - public async Task<Assembly> GetAssembly(PluginOld definition) + public Plugin Get(string name, Version version) { - if (definition == null) - { - throw new ArgumentNullException(nameof(definition)); - } - - if (definition.Source == null) + foreach (var pluginCatalog in _catalogs) { - throw new ArgumentNullException(nameof(definition.Source)); - } - - var result = await definition.Source.GetAssembly(definition); - - return result; - } - - public bool SupportsUnload { get; } - public Task Unload() - { - throw new NotImplementedException(); - } + var plugin = pluginCatalog.Get(name, version); - public bool Unloaded { get; } - public List<Plugin> GetPlugins() - { - var result = new List<Plugin>(); + if (plugin == null) + { + continue; + } - foreach (var pluginCatalog in _catalogs) - { - var pluginsInCatalog = pluginCatalog.GetPlugins(); - result.AddRange(pluginsInCatalog); + return plugin; } - return result; - } - - public Plugin Get(string name, Version version) - { - throw new NotImplementedException(); + return null; } public CompositePluginCatalog(params IPluginCatalog[] catalogs) @@ -77,27 +52,6 @@ public void AddCatalog(IPluginCatalog catalog) _catalogs.Add(catalog); } - public async Task<PluginOld> GetPlugin(string name, Version version) - { - PluginOld result = null; - - foreach (var pluginCatalog in _catalogs) - { - var plugin = await pluginCatalog.GetPlugin(name, version); - - if (plugin == null) - { - continue; - } - - result = plugin; - - break; - } - - return result; - } - public async Task Initialize() { if (_catalogs?.Any() != true) diff --git a/src/Weikio.PluginFramework/Catalogs/EmptyPluginCatalog.cs b/src/Weikio.PluginFramework/Catalogs/EmptyPluginCatalog.cs index d5af37b..a23726f 100644 --- a/src/Weikio.PluginFramework/Catalogs/EmptyPluginCatalog.cs +++ b/src/Weikio.PluginFramework/Catalogs/EmptyPluginCatalog.cs @@ -15,36 +15,14 @@ public Task Initialize() public bool IsInitialized { get; } = true; - public Task<List<PluginOld>> GetPluginsOld() - { - return Task.FromResult(new List<PluginOld>()); - } - - public Task<PluginOld> GetPlugin(string name, Version version) - { - return null; - } - - public Task<Assembly> GetAssembly(PluginOld definition) - { - return null; - } - - public bool SupportsUnload { get; } - public Task Unload() - { - throw new NotImplementedException(); - } - - public bool Unloaded { get; } public List<Plugin> GetPlugins() { - throw new NotImplementedException(); + return new List<Plugin>(); } public Plugin Get(string name, Version version) { - throw new NotImplementedException(); + return null; } } } diff --git a/src/Weikio.PluginFramework/Catalogs/FolderPluginCatalog.cs b/src/Weikio.PluginFramework/Catalogs/FolderPluginCatalog.cs index 0d096ee..0d86b7a 100644 --- a/src/Weikio.PluginFramework/Catalogs/FolderPluginCatalog.cs +++ b/src/Weikio.PluginFramework/Catalogs/FolderPluginCatalog.cs @@ -11,133 +11,99 @@ using System.Threading.Tasks; using Weikio.PluginFramework.Abstractions; using Weikio.PluginFramework.Context; -using AssemblyExtensions = System.Reflection.AssemblyExtensions; namespace Weikio.PluginFramework.Catalogs { public class FolderPluginCatalog : IPluginCatalog { private readonly string _folderPath; - private readonly List<(PluginOld PluginDefinition, Assembly Assembly)> _pluginsOld = new List<(PluginOld, Assembly)>(); - private readonly List<Plugin> _plugins = new List<Plugin>(); private readonly FolderPluginCatalogOptions _options; private readonly List<PluginAssemblyLoadContext> _contexts = new List<PluginAssemblyLoadContext>(); + private readonly List<AssemblyPluginCatalog> _catalogs = new List<AssemblyPluginCatalog>(); public bool IsInitialized { get; private set; } - public FolderPluginCatalog(string folderPath, TypeFinderCriteria finderCriteria = null, FolderPluginCatalogOptions options = null) + private List<Plugin> Plugins { - _folderPath = folderPath; - _options = options ?? new FolderPluginCatalogOptions(); - - if (finderCriteria != null) + get { - _options.TypeFinderCriterias.Add("", finderCriteria); + return _catalogs.SelectMany(x => x.GetPlugins()).ToList(); } } - public FolderPluginCatalog(string folderPath, Action<TypeFinderCriteriaBuilder> configureFinder = null, FolderPluginCatalogOptions options = null) + public FolderPluginCatalog(string folderPath) : this(folderPath, new FolderPluginCatalogOptions()) { - _folderPath = folderPath; - _options = options ?? new FolderPluginCatalogOptions(); - - if (configureFinder != null) - { - var builder = new TypeFinderCriteriaBuilder(); - configureFinder(builder); + } - var criteria = builder.Build(); + public FolderPluginCatalog(string folderPath, FolderPluginCatalogOptions options) : this(folderPath, null, null, options) + { + } - _options.TypeFinderCriterias.Add("", criteria); - } + public FolderPluginCatalog(string folderPath, Action<TypeFinderCriteriaBuilder> configureFinder) : this(folderPath, configureFinder, null, null) + { } - public Task<List<PluginOld>> GetPluginsOld() + public FolderPluginCatalog(string folderPath, TypeFinderCriteria finderCriteria) : this(folderPath, finderCriteria, null) { - if (Unloaded) - { - throw new CatalogUnloadedException(); - } + } - return Task.FromResult(_pluginsOld.Select(x => x.PluginDefinition).ToList()); + public FolderPluginCatalog(string folderPath, TypeFinderCriteria finderCriteria, FolderPluginCatalogOptions options) : this(folderPath, null, finderCriteria, options) + { } - public Task<PluginOld> GetPlugin(string name, Version version) + public FolderPluginCatalog(string folderPath, Action<TypeFinderCriteriaBuilder> configureFinder, FolderPluginCatalogOptions options) : this(folderPath, configureFinder, null, options) { - if (Unloaded) - { - throw new CatalogUnloadedException(); - } + } + + public FolderPluginCatalog(string folderPath, Action<TypeFinderCriteriaBuilder> configureFinder, TypeFinderCriteria finderCriteria, FolderPluginCatalogOptions options) + { + _folderPath = folderPath; + _options = options ?? new FolderPluginCatalogOptions(); - foreach (var plugin in _pluginsOld) + if (configureFinder != null) { - if (string.Equals(name, plugin.PluginDefinition.Name, StringComparison.InvariantCultureIgnoreCase) && - version == plugin.PluginDefinition.Version) - { - return Task.FromResult(plugin.PluginDefinition); - } - } + var builder = new TypeFinderCriteriaBuilder(); + configureFinder(builder); - return null; - } + var criteria = builder.Build(); - public Task<Assembly> GetAssembly(PluginOld definition) - { - foreach (var plugin in _pluginsOld) + _options.TypeFinderCriterias.Add("", criteria); + } + + if (finderCriteria != null) { - if (string.Equals(definition.Name, plugin.PluginDefinition.Name, StringComparison.InvariantCultureIgnoreCase) && - definition.Version == plugin.PluginDefinition.Version) - { - return Task.FromResult(plugin.Assembly); - } + _options.TypeFinderCriterias.Add("", finderCriteria); } - return null; - } - - public bool SupportsUnload { get; } = true; - - public Task Unload() - { - foreach (var pluginLoadContext in _contexts) + if (_options.TypeFinderCriteria != null) { - pluginLoadContext.Unload(); + _options.TypeFinderCriterias.Add("", _options.TypeFinderCriteria); } - - _contexts.Clear(); - - Unloaded = true; - - return Task.CompletedTask; } - public bool Unloaded { get; private set; } - public List<Plugin> GetPlugins() { - return _plugins; + return Plugins; } public Plugin Get(string name, Version version) { - if (Unloaded) + foreach (var assemblyPluginCatalog in _catalogs) { - throw new CatalogUnloadedException(); - } + var plugin = assemblyPluginCatalog.Get(name, version); - foreach (var plugin in _plugins) - { - if (string.Equals(name, plugin.Name, StringComparison.InvariantCultureIgnoreCase) && - version == plugin.Version) + if (plugin == null) { - return plugin; + continue; } + + return plugin; } return null; } - public Task Initialize() + public async Task Initialize() { var foundFiles = new List<string>(); @@ -160,34 +126,20 @@ public Task Initialize() continue; } - var loadContext = new PluginAssemblyLoadContext(assemblyPath, _options.PluginLoadContextOptions); - var assembly = loadContext.Load(); - _contexts.Add(loadContext); - - var finder = new TypeFinder(); - - foreach (var typeFinderCriteria in _options.TypeFinderCriterias) + var assemblyCatalogOptions = new AssemblyPluginCatalogOptions { + PluginLoadContextOptions = _options.PluginLoadContextOptions, + TypeFinderCriterias = _options.TypeFinderCriterias, + PluginNameOptions = _options.PluginNameOptions + }; - var pluginTypes = finder.Find(typeFinderCriteria.Value, assembly, loadContext); + var assemblyCatalog = new AssemblyPluginCatalog(assemblyPath, assemblyCatalogOptions); + await assemblyCatalog.Initialize(); - foreach (var type in pluginTypes) - { - var opt = new TypePluginCatalogOptions(); - var version = opt.PluginVersionGenerator(opt, type); - var pluginName = opt.PluginNameGenerator(opt, type); - var description = opt.PluginDescriptionGenerator(opt, type); - var productVersion = opt.PluginProductVersionGenerator(opt, type); - - var plugin = new Plugin(type.Assembly, type, pluginName, version, this, description, productVersion); - _plugins.Add(plugin); - } - } + _catalogs.Add(assemblyCatalog); } IsInitialized = true; - - return Task.CompletedTask; } private bool IsPluginAssembly(string assemblyPath) @@ -200,92 +152,52 @@ private bool IsPluginAssembly(string assemblyPath) return false; } - // First try to resolve plugin assemblies using MetadataLoadContext - if (_options.TypeFinderCriterias?.Any() == true) + if (_options.TypeFinderCriterias?.Any() != true) { - var coreAssemblyPath = typeof(int).Assembly.Location; - var corePath = Path.GetDirectoryName(coreAssemblyPath); - - var coreLocation = Path.Combine(corePath, "mscorlib.dll"); - - if (!File.Exists(coreLocation)) - { - throw new FileNotFoundException(coreLocation); - } - - var runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll"); - var paths = new List<string>(runtimeAssemblies); - paths.Add(assemblyPath); - - if (_options.PluginLoadContextOptions.UseHostApplicationAssemblies == UseHostApplicationAssembliesEnum.Always) - { - var hostApplicationPath = Environment.CurrentDirectory; - var hostDlls = Directory.GetFiles(hostApplicationPath, "*.dll", SearchOption.AllDirectories); - - paths.AddRange(hostDlls); - } - else if (_options.PluginLoadContextOptions.UseHostApplicationAssemblies == UseHostApplicationAssembliesEnum.Never) - { - var pluginPath = Path.GetDirectoryName(assemblyPath); - var dllsInPluginPath = Directory.GetFiles(pluginPath, "*.dll", SearchOption.AllDirectories); - - paths.AddRange(dllsInPluginPath); - } - else if (_options.PluginLoadContextOptions.UseHostApplicationAssemblies == UseHostApplicationAssembliesEnum.Selected) - { - foreach (var hostApplicationAssembly in _options.PluginLoadContextOptions.HostApplicationAssemblies) - { - var assembly = Assembly.Load(hostApplicationAssembly); - paths.Add(assembly.Location); - } - } - - var resolver = new PathAssemblyResolver(paths); - - using (var metadataContext = new MetadataLoadContext(resolver)) - { - var metadataPluginLoadContext = new MetadataTypeFindingContext(metadataContext); - var readonlyAssembly = metadataContext.LoadFromAssemblyPath(assemblyPath); + // If there are no resolvers, assume that each DLL is a plugin + return true; + } - var typeFinder = new TypeFinder(); + var runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll"); + var paths = new List<string>(runtimeAssemblies) { assemblyPath }; - foreach (var finderCriteria in _options.TypeFinderCriterias) - { - var typesFound = typeFinder.Find(finderCriteria.Value, readonlyAssembly, metadataPluginLoadContext); + if (_options.PluginLoadContextOptions.UseHostApplicationAssemblies == UseHostApplicationAssembliesEnum.Always) + { + var hostApplicationPath = Environment.CurrentDirectory; + var hostDlls = Directory.GetFiles(hostApplicationPath, "*.dll", SearchOption.AllDirectories); - if (typesFound?.Any() == true) - { - return true; - } - } - } + paths.AddRange(hostDlls); + } + else if (_options.PluginLoadContextOptions.UseHostApplicationAssemblies == UseHostApplicationAssembliesEnum.Never) + { + var pluginPath = Path.GetDirectoryName(assemblyPath); + var dllsInPluginPath = Directory.GetFiles(pluginPath, "*.dll", SearchOption.AllDirectories); - if (_options.PluginResolvers.Any() != true) + paths.AddRange(dllsInPluginPath); + } + else if (_options.PluginLoadContextOptions.UseHostApplicationAssemblies == UseHostApplicationAssembliesEnum.Selected) + { + foreach (var hostApplicationAssembly in _options.PluginLoadContextOptions.HostApplicationAssemblies) { - // If there is assembly resolvers but no plugin resolvers, return false by default if not assembly resolver did not match. - return false; + var assembly = Assembly.Load(hostApplicationAssembly); + paths.Add(assembly.Location); } } - if (_options.PluginResolvers?.Any() != true) - { - // If there are no resolvers, assume that each DLL is a plugin - return true; - } + var resolver = new PathAssemblyResolver(paths); - // Then using the PEReader - var metadata = reader.GetMetadataReader(); + using (var metadataContext = new MetadataLoadContext(resolver)) + { + var metadataPluginLoadContext = new MetadataTypeFindingContext(metadataContext); + var readonlyAssembly = metadataContext.LoadFromAssemblyPath(assemblyPath); - var publicTypes = metadata.TypeDefinitions - .Select(metadata.GetTypeDefinition) - .Where(t => t.Attributes.HasFlag(TypeAttributes.Public)) - .ToArray(); + var typeFinder = new TypeFinder(); - foreach (var type in publicTypes) - { - foreach (var pluginResolver in _options.PluginResolvers) + foreach (var finderCriteria in _options.TypeFinderCriterias) { - if (pluginResolver(assemblyPath, metadata, type)) + var typesFound = typeFinder.Find(finderCriteria.Value, readonlyAssembly, metadataPluginLoadContext); + + if (typesFound?.Any() == true) { return true; } @@ -307,6 +219,7 @@ public List<Type> Find(TypeFinderCriteria criteria, Assembly assembly, ITypeFind } var result = new List<Type>(); + var types = assembly.GetExportedTypes(); foreach (var type in types) @@ -343,7 +256,7 @@ public List<Type> Find(TypeFinderCriteria criteria, Assembly assembly, ITypeFind if (string.IsNullOrWhiteSpace(criteria.Name) == false) { - var regEx = new Regex(criteria.Name, RegexOptions.Compiled); + var regEx = NameToRegex(criteria.Name); if (regEx.IsMatch(type.FullName) == false) { @@ -370,18 +283,36 @@ public List<Type> Find(TypeFinderCriteria criteria, Assembly assembly, ITypeFind continue; } } + + if (criteria.AssignableTo != null) + { + var assignableToType = typeFindingContext.FindType(criteria.AssignableTo); + + if (assignableToType.IsAssignableFrom(type) == false) + { + continue; + } + } result.Add(type); } return result; } + + private static Regex NameToRegex(string nameFilter) + { + // https://stackoverflow.com/a/30300521/66988 + var regex = "^" + Regex.Escape(nameFilter).Replace("\\?", ".").Replace("\\*", ".*") + "$"; + return new Regex(regex, RegexOptions.Compiled); + } } public class TypeFinderCriteria { public Type Inherits { get; set; } public Type Implements { get; set; } + public Type AssignableTo { get; set; } public bool? IsAbstract { get; set; } public bool? IsInterface { get; set; } public string Name { get; set; } @@ -392,6 +323,7 @@ public class TypeFinderCriteriaBuilder { private Type _inherits; private Type _implements; + private Type _assignable; private bool _isAbstract; private bool _isInterface; private string _name; @@ -403,6 +335,7 @@ public TypeFinderCriteria Build() IsInterface = _isInterface, Implements = _implements, Inherits = _inherits, + AssignableTo = _assignable, Name = _name, IsAbstract = _isAbstract }; @@ -464,6 +397,13 @@ public TypeFinderCriteriaBuilder IsInterface(bool isInterface) return this; } + + public TypeFinderCriteriaBuilder AssignableTo(Type assignableTo) + { + _assignable = assignableTo; + + return this; + } } public interface ITypeFindingContext diff --git a/src/Weikio.PluginFramework/Catalogs/FolderPluginCatalogOptions.cs b/src/Weikio.PluginFramework/Catalogs/FolderPluginCatalogOptions.cs index 1bebaf9..3001917 100644 --- a/src/Weikio.PluginFramework/Catalogs/FolderPluginCatalogOptions.cs +++ b/src/Weikio.PluginFramework/Catalogs/FolderPluginCatalogOptions.cs @@ -10,10 +10,10 @@ public class FolderPluginCatalogOptions { public bool IncludSubfolders { get; set; } = true; public List<string> SearchPatterns = new List<string>() {"*.dll"}; - public List<Func<string, MetadataReader, TypeDefinition, bool>> PluginResolvers = new List<Func<string, MetadataReader, TypeDefinition, bool>>(); - // public List<Func<ITypeFindingContext, Type, bool>> TypePluginResolvers = new List<Func<ITypeFindingContext, Type, bool>>(); + // public List<Func<string, MetadataReader, TypeDefinition, bool>> PluginResolvers = new List<Func<string, MetadataReader, TypeDefinition, bool>>(); public PluginLoadContextOptions PluginLoadContextOptions = new PluginLoadContextOptions(); public TypeFinderCriteria TypeFinderCriteria = null; public Dictionary<string, TypeFinderCriteria> TypeFinderCriterias = new Dictionary<string, TypeFinderCriteria>(); + public PluginNameOptions PluginNameOptions { get; set; } = new PluginNameOptions(); } } diff --git a/src/Weikio.PluginFramework/Catalogs/PluginNameOptions.cs b/src/Weikio.PluginFramework/Catalogs/PluginNameOptions.cs new file mode 100644 index 0000000..6fc8ab7 --- /dev/null +++ b/src/Weikio.PluginFramework/Catalogs/PluginNameOptions.cs @@ -0,0 +1,74 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Reflection; + +namespace Weikio.PluginFramework.Catalogs +{ + public class PluginNameOptions + { + public Func<PluginNameOptions, Type, string> PluginNameGenerator { get; set; } = (options, type) => + { + var displayNameAttribute = type.GetCustomAttribute(typeof(DisplayNameAttribute), true) as DisplayNameAttribute; + + if (displayNameAttribute == null) + { + return type.FullName; + } + + if (string.IsNullOrWhiteSpace(displayNameAttribute.DisplayName)) + { + return type.FullName; + } + + return displayNameAttribute.DisplayName; + }; + + public Func<PluginNameOptions, Type, Version> PluginVersionGenerator { get; set; } = (options, type) => + { + var assemblyLocation = type.Assembly.Location; + Version version; + + if (!string.IsNullOrWhiteSpace(assemblyLocation)) + { + var versionInfo = FileVersionInfo.GetVersionInfo(assemblyLocation); + + version = Version.Parse(versionInfo.FileVersion); + } + else + { + version = new Version(1, 0, 0, 0); + } + + return version; + }; + + public Func<PluginNameOptions, Type, string> PluginDescriptionGenerator { get; set; } = (options, type) => + { + var assemblyLocation = type.Assembly.Location; + + if (string.IsNullOrWhiteSpace(assemblyLocation)) + { + return string.Empty; + } + + var versionInfo = FileVersionInfo.GetVersionInfo(assemblyLocation); + + return versionInfo.Comments; + }; + + public Func<PluginNameOptions, Type, string> PluginProductVersionGenerator { get; set; } = (options, type) => + { + var assemblyLocation = type.Assembly.Location; + + if (string.IsNullOrWhiteSpace(assemblyLocation)) + { + return string.Empty; + } + + var versionInfo = FileVersionInfo.GetVersionInfo(assemblyLocation); + + return versionInfo.ProductVersion; + }; + } +} diff --git a/src/Weikio.PluginFramework/Catalogs/TypePluginCatalog.cs b/src/Weikio.PluginFramework/Catalogs/TypePluginCatalog.cs index 7135abc..423088f 100644 --- a/src/Weikio.PluginFramework/Catalogs/TypePluginCatalog.cs +++ b/src/Weikio.PluginFramework/Catalogs/TypePluginCatalog.cs @@ -13,61 +13,66 @@ public class TypePluginCatalog : IPluginCatalog private readonly TypePluginCatalogOptions _options; private Plugin _plugin; - public TypePluginCatalog(Type pluginType, TypePluginCatalogOptions options = null) + public TypePluginCatalog(Type pluginType) : this(pluginType, null, null, null) { - _pluginType = pluginType; - _options = options ?? new TypePluginCatalogOptions(); + } - - public Task Initialize() + + public TypePluginCatalog(Type pluginType, PluginNameOptions nameOptions) : this (pluginType, null, nameOptions, null) { - var version = _options.PluginVersionGenerator(_options, _pluginType); - var pluginName = _options.PluginNameGenerator(_options, _pluginType); - var description = _options.PluginDescriptionGenerator(_options, _pluginType); - var productVersion = _options.PluginProductVersionGenerator(_options, _pluginType); - - _pluginOld = new PluginOld(pluginName, version, this, description, productVersion); - _plugin = new Plugin(_pluginType.Assembly, _pluginType, pluginName, version, this, description, productVersion); - IsInitialized = true; - - return Task.CompletedTask; } - public bool IsInitialized { get; private set; } - - public Task<List<PluginOld>> GetPluginsOld() + public TypePluginCatalog(Type pluginType, Action<PluginNameOptions> configure) : this(pluginType, configure, null, null) { - var result = new List<PluginOld>() { _pluginOld }; + } - return Task.FromResult(result); + public TypePluginCatalog(Type pluginType, TypePluginCatalogOptions options) : this(pluginType, null, null, options) + { } - public Task<PluginOld> GetPlugin(string name, Version version) + public TypePluginCatalog(Type pluginType, Action<PluginNameOptions> configure, PluginNameOptions nameOptions, TypePluginCatalogOptions options) { - if (!string.Equals(name, _pluginOld.Name, StringComparison.InvariantCultureIgnoreCase) || - version != _pluginOld.Version) + if (pluginType == null) { - return Task.FromResult<PluginOld>(null); + throw new ArgumentNullException(nameof(pluginType)); } - return Task.FromResult(_pluginOld); - } + _pluginType = pluginType; + _options = options ?? new TypePluginCatalogOptions(); - public Task<Assembly> GetAssembly(PluginOld definition) - { - return Task.FromResult(_pluginType.Assembly); + if (nameOptions == null) + { + nameOptions = new PluginNameOptions(); + } + + if (configure != null) + { + configure(nameOptions); + } + + _options.PluginNameOptions = nameOptions; } - public bool SupportsUnload { get; } - public Task Unload() + public Task Initialize() { - throw new NotImplementedException(); + var namingOptions = _options.PluginNameOptions; + var version = namingOptions.PluginVersionGenerator(namingOptions, _pluginType); + var pluginName = namingOptions.PluginNameGenerator(namingOptions, _pluginType); + var description = namingOptions.PluginDescriptionGenerator(namingOptions, _pluginType); + var productVersion = namingOptions.PluginProductVersionGenerator(namingOptions, _pluginType); + + _pluginOld = new PluginOld(pluginName, version, this, description, productVersion); + _plugin = new Plugin(_pluginType.Assembly, _pluginType, pluginName, version, this, description, productVersion); + IsInitialized = true; + + return Task.CompletedTask; } - public bool Unloaded { get; } + public bool IsInitialized { get; private set; } + public List<Plugin> GetPlugins() { - return new List<Plugin>(){_plugin}; + return new List<Plugin>() { _plugin }; } public Plugin Get(string name, Version version) diff --git a/src/Weikio.PluginFramework/Catalogs/TypePluginCatalogOptions.cs b/src/Weikio.PluginFramework/Catalogs/TypePluginCatalogOptions.cs index 2556ec3..728d7f4 100644 --- a/src/Weikio.PluginFramework/Catalogs/TypePluginCatalogOptions.cs +++ b/src/Weikio.PluginFramework/Catalogs/TypePluginCatalogOptions.cs @@ -1,74 +1,7 @@ -using System; -using System.ComponentModel; -using System.Diagnostics; -using System.Reflection; - -namespace Weikio.PluginFramework.Catalogs +namespace Weikio.PluginFramework.Catalogs { public class TypePluginCatalogOptions { - public Func<TypePluginCatalogOptions, Type, string> PluginNameGenerator { get; set; } = (options, type) => - { - var displayNameAttribute = type.GetCustomAttribute(typeof(DisplayNameAttribute), true) as DisplayNameAttribute; - - if (displayNameAttribute == null) - { - return type.FullName; - } - - if (string.IsNullOrWhiteSpace(displayNameAttribute.DisplayName)) - { - return type.FullName; - } - - return displayNameAttribute.DisplayName; - }; - - public Func<TypePluginCatalogOptions, Type, Version> PluginVersionGenerator { get; set; } = (options, type) => - { - var assemblyLocation = type.Assembly.Location; - Version version; - - if (!string.IsNullOrWhiteSpace(assemblyLocation)) - { - var versionInfo = FileVersionInfo.GetVersionInfo(assemblyLocation); - - version = Version.Parse(versionInfo.FileVersion); - } - else - { - version = new Version(1, 0, 0, 0); - } - - return version; - }; - - public Func<TypePluginCatalogOptions, Type, string> PluginDescriptionGenerator { get; set; } = (options, type) => - { - var assemblyLocation = type.Assembly.Location; - - if (string.IsNullOrWhiteSpace(assemblyLocation)) - { - return string.Empty; - } - - var versionInfo = FileVersionInfo.GetVersionInfo(assemblyLocation); - - return versionInfo.Comments; - }; - - public Func<TypePluginCatalogOptions, Type, string> PluginProductVersionGenerator { get; set; } = (options, type) => - { - var assemblyLocation = type.Assembly.Location; - - if (string.IsNullOrWhiteSpace(assemblyLocation)) - { - return string.Empty; - } - - var versionInfo = FileVersionInfo.GetVersionInfo(assemblyLocation); - - return versionInfo.ProductVersion; - }; + public PluginNameOptions PluginNameOptions { get; set; } = new PluginNameOptions(); } } diff --git a/tests/Assemblies/JsonNetNew/JsonNetNew.csproj b/tests/Assemblies/JsonNetNew/JsonNetNew.csproj index fefa2f1..060466c 100644 --- a/tests/Assemblies/JsonNetNew/JsonNetNew.csproj +++ b/tests/Assemblies/JsonNetNew/JsonNetNew.csproj @@ -9,7 +9,7 @@ </PropertyGroup> <ItemGroup> - <PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> + <PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> </ItemGroup> <ItemGroup> diff --git a/tests/unit/Weikio.PluginFramework.Tests/AssemblyPluginCatalogTests.cs b/tests/unit/Weikio.PluginFramework.Tests/AssemblyPluginCatalogTests.cs index 76c0033..22a5bf7 100644 --- a/tests/unit/Weikio.PluginFramework.Tests/AssemblyPluginCatalogTests.cs +++ b/tests/unit/Weikio.PluginFramework.Tests/AssemblyPluginCatalogTests.cs @@ -7,161 +7,138 @@ using Weikio.PluginFramework.Catalogs; using Weikio.PluginFramework.Context; using Xunit; +using Xunit.Abstractions; namespace Weikio.PluginFramework.Tests { public class AssemblyPluginCatalogTests { - [Fact] - public async Task CanInitialize() + private readonly ITestOutputHelper _testOutputHelper; + + public AssemblyPluginCatalogTests(ITestOutputHelper testOutputHelper) { - throw new NotImplementedException();; - // var catalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\netstandard2.0\TestAssembly1.dll"); - // await catalog.Initialize(); - // - // var allPlugins = await catalog.GetPluginsOld(); - // - // Assert.Single(allPlugins); + _testOutputHelper = testOutputHelper; } - + [Fact] - public async Task CanUnload() + public async Task CanInitialize() { - throw new NotImplementedException();; - // var catalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\netstandard2.0\TestAssembly1.dll"); - // await catalog.Initialize(); - // - // var pluginDefinition = (await catalog.GetAll()).Single(); - // - // var pluginExporter = new PluginExporter(); - // var plugin = await pluginExporter.Get(pluginDefinition); - // - // dynamic jsonResolver = Activator.CreateInstance(plugin.Types.First()); - // jsonResolver.RunMe(); - // - // await catalog.Unload(); - // - // await Assert.ThrowsAsync<CatalogUnloadedException>(async () => await pluginExporter.Get(pluginDefinition)); + var catalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\netstandard2.0\TestAssembly1.dll"); + await catalog.Initialize(); + + var allPlugins = catalog.GetPlugins(); + + Assert.Single(allPlugins); } - + [Fact] public async Task ThrowsIfAssemblyNotFound() { - throw new NotImplementedException();; - // var catalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\netstandard2.0\notexists.dll"); - // - // await Assert.ThrowsAsync<ArgumentException>(async () => await catalog.Initialize()); + var catalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\netstandard2.0\notexists.dll"); + + await Assert.ThrowsAsync<ArgumentException>(async () => await catalog.Initialize()); } - + [Fact] public void ThrowsIfAssemblyPathMissing() { - throw new NotImplementedException();; - // Assert.Throws<ArgumentNullException>(() => new AssemblyPluginCatalog("")); - // - // string path = null; - // Assert.Throws<ArgumentNullException>(() => new AssemblyPluginCatalog(path)); + Assert.Throws<ArgumentNullException>(() => new AssemblyPluginCatalog("")); + + string path = null; + Assert.Throws<ArgumentNullException>(() => new AssemblyPluginCatalog(path)); } - + [Fact] public async Task CanUseReferencedDependencies() { - throw new NotImplementedException();; - - // // Make sure that the referenced version of JSON.NET is loaded into memory - // var json = Newtonsoft.Json.JsonConvert.SerializeObject(1); - // - // var assemblyCatalog1 = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonNew\netstandard2.0\JsonNetNew.dll"); - // await assemblyCatalog1.Initialize(); - // - // var assemblyCatalog2 = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonOld\netstandard2.0\JsonNetOld.dll"); - // await assemblyCatalog2.Initialize(); - // - // var newPluginDefinition = (await assemblyCatalog1.GetAll()).Single(); - // var oldPluginDefinition = (await assemblyCatalog2.GetAll()).Single(); - // - // var pluginExporter = new PluginExporter(); - // var newPlugin = await pluginExporter.Get(newPluginDefinition); - // var oldPlugin = await pluginExporter.Get(oldPluginDefinition); - // - // dynamic newPluginJsonResolver = Activator.CreateInstance(newPlugin.Types.First()); - // var newPluginVersion = newPluginJsonResolver.GetVersion(); - // - // dynamic oldPluginJsonResolver = Activator.CreateInstance(oldPlugin.Types.First()); - // var oldPluginVersion = oldPluginJsonResolver.GetVersion(); - // - // Assert.Equal("12.0.0.0", newPluginVersion); - // Assert.Equal("9.0.0.0", oldPluginVersion); + // Make sure that the referenced version of JSON.NET is loaded into memory + var json = Newtonsoft.Json.JsonConvert.SerializeObject(1); + _testOutputHelper.WriteLine(json); + + var options = new AssemblyPluginCatalogOptions() + { + PluginLoadContextOptions = new PluginLoadContextOptions() { UseHostApplicationAssemblies = UseHostApplicationAssembliesEnum.Never } + }; + + var assemblyCatalog1 = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonNew\netstandard2.0\JsonNetNew.dll", options); + await assemblyCatalog1.Initialize(); + + var assemblyCatalog2 = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonOld\netstandard2.0\JsonNetOld.dll", options); + await assemblyCatalog2.Initialize(); + + var newPlugin = assemblyCatalog1.Single(); + var oldPlugin = assemblyCatalog2.Single(); + + dynamic newPluginJsonResolver = Activator.CreateInstance(newPlugin); + var newPluginVersion = newPluginJsonResolver.GetVersion(); + + dynamic oldPluginJsonResolver = Activator.CreateInstance(oldPlugin); + var oldPluginVersion = oldPluginJsonResolver.GetVersion(); + + Assert.Equal("10.0.0.0", newPluginVersion); + Assert.Equal("9.0.0.0", oldPluginVersion); } [Fact] public async Task CanUseHostsDependencies() { - throw new NotImplementedException();; - - // // Make sure that the referenced version of JSON.NET is loaded into memory - // var json = Newtonsoft.Json.JsonConvert.SerializeObject(1); - // - // var options = new AssemblyPluginCatalogOptions(); - // options.PluginLoadContextOptions = new PluginLoadContextOptions() { UseHostApplicationAssemblies = UseHostApplicationAssembliesEnum.Always }; - // - // var folder1Catalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonNew\netstandard2.0\JsonNetNew.dll", options); - // await folder1Catalog.Initialize(); - // - // var folder2Catalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonOld\netstandard2.0\JsonNetOld.dll", options); - // await folder2Catalog.Initialize(); - // - // var newPluginDefinition = (await folder1Catalog.GetAll()).Single(); - // var oldPluginDefinition = (await folder2Catalog.GetAll()).Single(); - // - // var pluginExporter = new PluginExporter(); - // var newPlugin = await pluginExporter.Get(newPluginDefinition); - // var oldPlugin = await pluginExporter.Get(oldPluginDefinition); - // - // dynamic newPluginJsonResolver = Activator.CreateInstance(newPlugin.Types.First()); - // var newPluginVersion = newPluginJsonResolver.GetVersion(); - // - // dynamic oldPluginJsonResolver = Activator.CreateInstance(oldPlugin.Types.First()); - // var oldPluginVersion = oldPluginJsonResolver.GetVersion(); - // - // Assert.Equal("10.0.0.0", newPluginVersion); - // Assert.Equal("10.0.0.0", oldPluginVersion); + // Make sure that the referenced version of JSON.NET is loaded into memory + var json = Newtonsoft.Json.JsonConvert.SerializeObject(1); + _testOutputHelper.WriteLine(json); + + var options = new AssemblyPluginCatalogOptions + { + PluginLoadContextOptions = new PluginLoadContextOptions() { UseHostApplicationAssemblies = UseHostApplicationAssembliesEnum.Always } + }; + + var folder1Catalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonNew\netstandard2.0\JsonNetNew.dll", options); + await folder1Catalog.Initialize(); + + var folder2Catalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonOld\netstandard2.0\JsonNetOld.dll", options); + await folder2Catalog.Initialize(); + + var newPlugin = folder1Catalog.Single(); + var oldPlugin = folder2Catalog.Single(); + + dynamic newPluginJsonResolver = Activator.CreateInstance(newPlugin); + var newPluginVersion = newPluginJsonResolver.GetVersion(); + + dynamic oldPluginJsonResolver = Activator.CreateInstance(oldPlugin); + var oldPluginVersion = oldPluginJsonResolver.GetVersion(); + + Assert.Equal("12.0.0.0", newPluginVersion); + Assert.Equal("12.0.0.0", oldPluginVersion); } - + [Fact] public async Task CanUseSelectedHoststDependencies() { - throw new NotImplementedException();; - // - // // Make sure that the referenced version of JSON.NET is loaded into memory - // var json = Newtonsoft.Json.JsonConvert.SerializeObject(1); - // // Make sure that the referenced version of Microsoft.Extensions.Logging is loaded into memory - // var logging = new Microsoft.Extensions.Logging.LoggerFactory(); - // - // var options = new AssemblyPluginCatalogOptions();; - // options.PluginLoadContextOptions = new PluginLoadContextOptions() - // { - // UseHostApplicationAssemblies = UseHostApplicationAssembliesEnum.Selected, - // HostApplicationAssemblies = new List<AssemblyName>() - // { - // typeof(Microsoft.Extensions.Logging.LoggerFactory).Assembly.GetName() - // } - // }; - // - // var catalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonOld\netstandard2.0\JsonNetOld.dll", options); - // await catalog.Initialize(); - // - // var oldPluginDefinition = (await catalog.GetAll()).Single(); - // - // var pluginExporter = new PluginExporter(); - // var oldPlugin = await pluginExporter.Get(oldPluginDefinition); - // - // dynamic oldPluginJsonResolver = Activator.CreateInstance(oldPlugin.Types.First()); - // var oldPluginVersion = oldPluginJsonResolver.GetVersion(); - // var loggerVersion = oldPluginJsonResolver.GetLoggingVersion(); - // - // Assert.Equal("3.1.2.0", loggerVersion); - // Assert.Equal("9.0.0.0", oldPluginVersion); + // Make sure that the referenced version of JSON.NET is loaded into memory + var json = Newtonsoft.Json.JsonConvert.SerializeObject(1); + // Make sure that the referenced version of Microsoft.Extensions.Logging is loaded into memory + var logging = new Microsoft.Extensions.Logging.LoggerFactory(); + + var options = new AssemblyPluginCatalogOptions();; + options.PluginLoadContextOptions = new PluginLoadContextOptions() + { + UseHostApplicationAssemblies = UseHostApplicationAssembliesEnum.Selected, + HostApplicationAssemblies = new List<AssemblyName>() + { + typeof(Microsoft.Extensions.Logging.LoggerFactory).Assembly.GetName() + } + }; + + var catalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonOld\netstandard2.0\JsonNetOld.dll", options); + await catalog.Initialize(); + + var oldPlugin = catalog.Single(); + + dynamic oldPluginJsonResolver = Activator.CreateInstance(oldPlugin); + var oldPluginVersion = oldPluginJsonResolver.GetVersion(); + var loggerVersion = oldPluginJsonResolver.GetLoggingVersion(); + + Assert.Equal("3.1.2.0", loggerVersion); + Assert.Equal("9.0.0.0", oldPluginVersion); } } } diff --git a/tests/unit/Weikio.PluginFramework.Tests/DefaultOptionsTests.cs b/tests/unit/Weikio.PluginFramework.Tests/DefaultOptionsTests.cs index e6dcc0f..ba473f1 100644 --- a/tests/unit/Weikio.PluginFramework.Tests/DefaultOptionsTests.cs +++ b/tests/unit/Weikio.PluginFramework.Tests/DefaultOptionsTests.cs @@ -15,46 +15,32 @@ public class DefaultOptionsTests [Fact] public async Task CanConfigureDefaultOptions() { - throw new NotImplementedException();; - // - // // Make sure that the referenced version of JSON.NET is loaded into memory - // var json = Newtonsoft.Json.JsonConvert.SerializeObject(1); - // - // PluginLoadContextOptions.Defaults.UseHostApplicationAssemblies = UseHostApplicationAssembliesEnum.Always; - // - // var assemblyPluginCatalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonNew\netstandard2.0\JsonNetNew.dll"); - // await assemblyPluginCatalog.Initialize(); - // - // var folderPluginCatalog = new FolderPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonOld\netstandard2.0", new FolderPluginCatalogOptions() - // { - // AssemblyPluginResolvers = new List<Func<Assembly, bool>>() - // { - // assembly => - // { - // var result = assembly.ExportedTypes.Any(x => x.Name.EndsWith("JsonResolver")); - // - // return result; - // } - // } - // }); - // - // await folderPluginCatalog.Initialize(); - // - // var assemblyPluginDefinition = (await assemblyPluginCatalog.GetAll()).Single(); - // var folderPluginDefinition = (await folderPluginCatalog.GetAll()).Single(); - // - // var pluginExporter = new PluginExporter(); - // var newPlugin = await pluginExporter.Get(assemblyPluginDefinition); - // var oldPlugin = await pluginExporter.Get(folderPluginDefinition); - // - // dynamic newPluginJsonResolver = Activator.CreateInstance(newPlugin.Types.First()); - // var newPluginVersion = newPluginJsonResolver.GetVersion(); - // - // dynamic oldPluginJsonResolver = Activator.CreateInstance(oldPlugin.Types.First()); - // var oldPluginVersion = oldPluginJsonResolver.GetVersion(); - // - // Assert.Equal("10.0.0.0", newPluginVersion); - // Assert.Equal("10.0.0.0", oldPluginVersion); + // Make sure that the referenced version of JSON.NET is loaded into memory + var json = Newtonsoft.Json.JsonConvert.SerializeObject(1); + PluginLoadContextOptions.Defaults.UseHostApplicationAssemblies = UseHostApplicationAssembliesEnum.Always; + + Action<TypeFinderCriteriaBuilder> configureFinder = configure => + { + configure.HasName("*JsonResolver"); + }; + + var assemblyPluginCatalog = new AssemblyPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonNew\netstandard2.0\JsonNetNew.dll", configureFinder); + var folderPluginCatalog = new FolderPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonOld\netstandard2.0", configureFinder); + + await assemblyPluginCatalog.Initialize(); + await folderPluginCatalog.Initialize(); + + var newPlugin = assemblyPluginCatalog.Single(); + var oldPlugin = folderPluginCatalog.Single(); + + dynamic newPluginJsonResolver = Activator.CreateInstance(newPlugin); + var newPluginVersion = newPluginJsonResolver.GetVersion(); + + dynamic oldPluginJsonResolver = Activator.CreateInstance(oldPlugin); + var oldPluginVersion = oldPluginJsonResolver.GetVersion(); + + Assert.Equal("12.0.0.0", newPluginVersion); + Assert.Equal("12.0.0.0", oldPluginVersion); } } } diff --git a/tests/unit/Weikio.PluginFramework.Tests/FolderCatalogTests.cs b/tests/unit/Weikio.PluginFramework.Tests/FolderCatalogTests.cs index 10a5196..2d7a662 100644 --- a/tests/unit/Weikio.PluginFramework.Tests/FolderCatalogTests.cs +++ b/tests/unit/Weikio.PluginFramework.Tests/FolderCatalogTests.cs @@ -18,190 +18,87 @@ public class FolderCatalogTests [Fact] public async Task CanInitialize() { - throw new NotImplementedException();; - // var plugins = new FolderPluginCatalog(_pluginFolder); - // await plugins.Initialize(); - // - // var dllCount = Directory.GetFiles(_pluginFolder, "*.dll").Length; - // var pluginCount = (await plugins.GetPluginsOld()).Count; - // - // Assert.Equal(dllCount, pluginCount); - } - - [Fact] - public async Task CanUnload() - { - throw new NotImplementedException();; - // var catalog = new FolderPluginCatalog(_pluginFolder); - // await catalog.Initialize(); - // - // await catalog.GetPluginsOld(); - // - // await catalog.Unload(); - // - // await Assert.ThrowsAsync<CatalogUnloadedException>(async () => await catalog.GetPluginsOld()); + var catalog = new FolderPluginCatalog(_pluginFolder); + await catalog.Initialize(); + + var plugins = catalog.GetPlugins(); + + Assert.NotEmpty(plugins); } [Fact] - public async Task CanInitializeWithPluginResolver() + public async Task CanInitializeWithCriteria() { - throw new NotImplementedException();; + var catalog = new FolderPluginCatalog(_pluginFolder, configure => + { + configure.HasName("*Plugin"); + }); + + await catalog.Initialize(); - // var options = new FolderPluginCatalogOptions(); - // - // options.PluginResolvers.Add((assembly, metadata, type) => - // { - // var typeName = metadata.GetString(type.Name); - // - // if (typeName.EndsWith("Plugin")) - // { - // return true; - // } - // - // return false; - // }); - // - // var plugins = new FolderPluginCatalog(_pluginFolder, options); - // await plugins.Initialize(); - // - // var pluginCount = (await plugins.GetPluginsOld()).Count; - // - // Assert.Equal(2, pluginCount); - } - - [Fact] - public async Task CanInitializeWithMultiplePluginResolver() - { - throw new NotImplementedException();; - // var options = new FolderPluginCatalogOptions(); - // - // options.PluginResolvers.Add((assembly, metadata, type) => - // { - // var typeName = metadata.GetString(type.Name); - // - // if (typeName.EndsWith("Plugin")) - // { - // return true; - // } - // - // return false; - // }); - // - // options.PluginResolvers.Add((assembly, metadata, type) => assembly.ToLowerInvariant().EndsWith("testassembly3.dll")); - // - // var plugins = new FolderPluginCatalog(_pluginFolder, options); - // await plugins.Initialize(); - // - // var pluginCount = (await plugins.GetPluginsOld()).Count; - // - // Assert.Equal(3, pluginCount); - } - - [Fact] - public async Task CanInitializeWithAssemblyPluginResolver() - { - throw new NotImplementedException();; - // var options = new FolderPluginCatalogOptions(); - // - // options.AssemblyPluginResolvers.Add(assembly => - // { - // var types = assembly.GetExportedTypes(); - // - // if (types.Any(x => x.Name.EndsWith("Plugin"))) - // { - // return true; - // } - // - // return false; - // }); - // - // var plugins = new FolderPluginCatalog(_pluginFolder, options); - // await plugins.Initialize(); - // - // var pluginCount = (await plugins.GetPluginsOld()).Count; - // - // Assert.Equal(2, pluginCount); + var pluginCount = catalog.GetPlugins().Count; + + Assert.Equal(2, pluginCount); } [Fact] public async Task CanUseReferencedDependencies() { - throw new NotImplementedException();; + PluginLoadContextOptions.Defaults.UseHostApplicationAssemblies = UseHostApplicationAssembliesEnum.Never; + + Action<TypeFinderCriteriaBuilder> configureFinder = configure => + { + configure.HasName("*JsonResolver"); + }; + + var folder1Catalog = new FolderPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonNew\netstandard2.0", configureFinder); + var folder2Catalog = new FolderPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonOld\netstandard2.0", configureFinder); + + await folder1Catalog.Initialize(); + await folder2Catalog.Initialize(); - // var options = new FolderPluginCatalogOptions(); - // - // options.AssemblyPluginResolvers.Add(assembly => - // { - // var result = assembly.ExportedTypes.Any(x => x.Name.EndsWith("JsonResolver")); - // - // return result; - // }); - // - // var folder1Catalog = new FolderPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonNew\netstandard2.0", options); - // await folder1Catalog.Initialize(); - // - // var folder2Catalog = new FolderPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonOld\netstandard2.0", options); - // await folder2Catalog.Initialize(); - // - // var newPluginDefinition = (await folder1Catalog.GetAll()).Single(); - // var oldPluginDefinition = (await folder2Catalog.GetAll()).Single(); - // - // var pluginExporter = new PluginExporter(); - // var newPlugin = await pluginExporter.Get(newPluginDefinition); - // var oldPlugin = await pluginExporter.Get(oldPluginDefinition); - // - // dynamic newPluginJsonResolver = Activator.CreateInstance(newPlugin.Types.First()); - // var newPluginVersion = newPluginJsonResolver.GetVersion(); - // - // dynamic oldPluginJsonResolver = Activator.CreateInstance(oldPlugin.Types.First()); - // var oldPluginVersion = oldPluginJsonResolver.GetVersion(); - // - // Assert.Equal("12.0.0.0", newPluginVersion); - // Assert.Equal("9.0.0.0", oldPluginVersion); + var newPlugin = folder1Catalog.Single(); + var oldPlugin = folder2Catalog.Single(); + + dynamic newPluginJsonResolver = Activator.CreateInstance(newPlugin); + var newPluginVersion = newPluginJsonResolver.GetVersion(); + + dynamic oldPluginJsonResolver = Activator.CreateInstance(oldPlugin); + var oldPluginVersion = oldPluginJsonResolver.GetVersion(); + + Assert.Equal("10.0.0.0", newPluginVersion); + Assert.Equal("9.0.0.0", oldPluginVersion); } [Fact] public async Task CanUseSelectedHoststDependencies() { - throw new NotImplementedException();; + // Make sure that the referenced version of JSON.NET is loaded into memory + var json = Newtonsoft.Json.JsonConvert.SerializeObject(1); + // Make sure that the referenced version of Microsoft.Extensions.Logging is loaded into memory + var logging = new Microsoft.Extensions.Logging.LoggerFactory(); + + var options = new FolderPluginCatalogOptions + { + TypeFinderCriteria = new TypeFinderCriteria() { Name = "*JsonResolver" }, + PluginLoadContextOptions = new PluginLoadContextOptions() + { + UseHostApplicationAssemblies = UseHostApplicationAssembliesEnum.Selected, + HostApplicationAssemblies = new List<AssemblyName>() { typeof(Microsoft.Extensions.Logging.LoggerFactory).Assembly.GetName() } + } + }; + + var catalog = new FolderPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonOld\netstandard2.0", options); + await catalog.Initialize(); - // - // // Make sure that the referenced version of JSON.NET is loaded into memory - // var json = Newtonsoft.Json.JsonConvert.SerializeObject(1); - // // Make sure that the referenced version of Microsoft.Extensions.Logging is loaded into memory - // var logging = new Microsoft.Extensions.Logging.LoggerFactory(); - // - // var options = new FolderPluginCatalogOptions(); - // options.AssemblyPluginResolvers.Add(assembly => - // { - // var result = assembly.ExportedTypes.Any(x => x.Name.EndsWith("JsonResolver")); - // - // return result; - // }); - // - // options.PluginLoadContextOptions = new PluginLoadContextOptions() - // { - // UseHostApplicationAssemblies = UseHostApplicationAssembliesEnum.Selected, - // HostApplicationAssemblies = new List<AssemblyName>() - // { - // typeof(Microsoft.Extensions.Logging.LoggerFactory).Assembly.GetName() - // } - // }; - // - // var catalog = new FolderPluginCatalog(@"..\..\..\..\..\Assemblies\bin\JsonOld\netstandard2.0", options); - // await catalog.Initialize(); - // - // var oldPluginDefinition = (await catalog.GetAll()).Single(); - // - // var pluginExporter = new PluginExporter(); - // var oldPlugin = await pluginExporter.Get(oldPluginDefinition); - // - // dynamic oldPluginJsonResolver = Activator.CreateInstance(oldPlugin.Types.First()); - // var oldPluginVersion = oldPluginJsonResolver.GetVersion(); - // var loggerVersion = oldPluginJsonResolver.GetLoggingVersion(); - // - // Assert.Equal("3.1.2.0", loggerVersion); - // Assert.Equal("9.0.0.0", oldPluginVersion); + var oldPlugin = catalog.Single(); + + dynamic oldPluginJsonResolver = Activator.CreateInstance(oldPlugin); + var oldPluginVersion = oldPluginJsonResolver.GetVersion(); + var loggerVersion = oldPluginJsonResolver.GetLoggingVersion(); + + Assert.Equal("3.1.2.0", loggerVersion); + Assert.Equal("9.0.0.0", oldPluginVersion); } } } diff --git a/tests/unit/Weikio.PluginFramework.Tests/TestClass.cs b/tests/unit/Weikio.PluginFramework.Tests/TestClass.cs index d3bfdef..ac0b76f 100644 --- a/tests/unit/Weikio.PluginFramework.Tests/TestClass.cs +++ b/tests/unit/Weikio.PluginFramework.Tests/TestClass.cs @@ -1,7 +1,10 @@ using System; +using System.Linq; +using System.Linq.Expressions; using System.Reflection; using System.Threading.Tasks; using Xunit; +using Xunit.Abstractions; namespace Weikio.PluginFramework.Tests { @@ -12,6 +15,13 @@ public class TestClass public class Test { + private readonly ITestOutputHelper _testOutputHelper; + + public Test(ITestOutputHelper testOutputHelper) + { + _testOutputHelper = testOutputHelper; + } + // Roslyn generated? public dynamic Configuration { get; set; } @@ -30,15 +40,31 @@ public void MyTest() var y = s + i; }); + var simpleAction = new Action(() => + { + _testOutputHelper.WriteLine("Hello action"); + }); + + var simpleFunc = new Func<string>(() => + { + return "Hello func"; + }); + var asyncTest = new Action<Task<string>, int>(async (Task<string> s, int i) => { var d = await s; var y = d + i; }); - + Create(myFuncTest); Create(myActionTest); Create(asyncTest); + + var type = CreateType(simpleFunc); + + var hmmh = Activator.CreateInstance(type); + + } public void Create(MulticastDelegate multicastDelegate) @@ -48,5 +74,25 @@ public void Create(MulticastDelegate multicastDelegate) var parameters = methodInfo.GetParameters(); var returnType = methodInfo.ReturnType; } + + public Type CreateType(MulticastDelegate multicastDelegate) + { + var methodInfo = multicastDelegate.GetMethodInfo(); + var types = methodInfo.GetParameters().Select(p => p.ParameterType); + types = types.Concat(new[] { methodInfo.ReturnType }); + + if (methodInfo.ReturnType == typeof(void)) + { + var actionType = Expression.GetActionType(types.ToArray()); + } + else + { + var funcType = Expression.GetFuncType(types.ToArray()); + + return funcType; + } + + return null; + } } } diff --git a/tests/unit/Weikio.PluginFramework.Tests/TypePluginCatalogTests.cs b/tests/unit/Weikio.PluginFramework.Tests/TypePluginCatalogTests.cs index 7ece9dd..35076ac 100644 --- a/tests/unit/Weikio.PluginFramework.Tests/TypePluginCatalogTests.cs +++ b/tests/unit/Weikio.PluginFramework.Tests/TypePluginCatalogTests.cs @@ -1,5 +1,5 @@ -using System.Linq; -using System.Threading.Tasks; +using System.Threading.Tasks; +using Weikio.PluginFramework.Abstractions; using Weikio.PluginFramework.Catalogs; using Weikio.PluginFramework.Tests.Plugins; using Xunit; @@ -14,9 +14,8 @@ public async Task CanInitialize() var catalog = new TypePluginCatalog(typeof(TypePlugin)); await catalog.Initialize(); - var allPlugins = await catalog.GetPluginsOld(); - - Assert.Single(allPlugins); + var plugins = catalog.GetPlugins(); + Assert.Single(plugins); } [Fact] @@ -25,31 +24,35 @@ public async Task NameIsTypeFullName() var catalog = new TypePluginCatalog(typeof(TypePlugin)); await catalog.Initialize(); - var thePlugin = (await catalog.GetPluginsOld()).First(); - + var thePlugin = catalog.Single(); + Assert.Equal("Weikio.PluginFramework.Tests.Plugins.TypePlugin", thePlugin.Name); } - + [Fact] public async Task CanConfigureNameResolver() { - var catalog = new TypePluginCatalog(typeof(TypePlugin), new TypePluginCatalogOptions() { PluginNameGenerator = (options, type) => "HelloOptions" }); - + var catalog = new TypePluginCatalog(typeof(TypePlugin), configure => + { + configure.PluginNameGenerator = (opt, type) => "HelloOptions"; + }); + await catalog.Initialize(); - var thePlugin = (await catalog.GetPluginsOld()).First(); - + var thePlugin = catalog.Single(); + Assert.Equal("HelloOptions", thePlugin.Name); } - + + [Fact] public async Task CanSetNameByAttribute() { var catalog = new TypePluginCatalog(typeof(TypePluginWithName)); await catalog.Initialize(); - var thePlugin = (await catalog.GetPluginsOld()).First(); - + var thePlugin = catalog.Single(); + Assert.Equal("MyCustomName", thePlugin.Name); } } diff --git a/tests/unit/Weikio.PluginFramework.Tests/Weikio.PluginFramework.Tests.csproj b/tests/unit/Weikio.PluginFramework.Tests/Weikio.PluginFramework.Tests.csproj index 50b3073..b63b8b7 100644 --- a/tests/unit/Weikio.PluginFramework.Tests/Weikio.PluginFramework.Tests.csproj +++ b/tests/unit/Weikio.PluginFramework.Tests/Weikio.PluginFramework.Tests.csproj @@ -8,7 +8,7 @@ <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.*" /> <PackageReference Include="xunit" Version="2.4.*" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.*" /> - <PackageReference Include="Newtonsoft.Json" Version="10.0.3" /> + <PackageReference Include="Newtonsoft.Json" Version="12.0.2" /> <PackageReference Include="Microsoft.Extensions.Logging" Version="3.1.2" /> </ItemGroup>