From f7235bc7c38fe38d75400678f3b1c6454a37e5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E7=BA=BE?= Date: Mon, 19 Oct 2026 10:33:54 +0800 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20=E6=9C=AC=E6=9C=BA=E5=BA=93?= =?UTF-8?q?=E4=BC=98=E5=85=88=E4=BB=8E=20RID=20=E7=9B=AE=E5=BD=95=E8=A7=A3?= =?UTF-8?q?=E6=9E=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Snapshot of natemcmaster/DotNetCorePlugins at bcf9c8b, with the Hosihikari patch. --- .editorconfig | 118 +++ .github/CODEOWNERS | 1 + .github/ISSUE_TEMPLATE/Bug_report.md | 27 + .github/ISSUE_TEMPLATE/Feature_request.md | 21 + .github/ISSUE_TEMPLATE/Question.md | 10 + .github/ISSUE_TEMPLATE/config.yml | 1 + .github/workflows/ci.yml | 16 + .github/workflows/stale.yml | 41 + .gitignore | 9 + .vscode/launch.json | 11 + .vscode/tasks.json | 26 + CLAUDE.md | 74 ++ CODE_OF_CONDUCT.md | 76 ++ CONTRIBUTING.md | 31 + Directory.Build.props | 56 ++ DotNetCorePlugins.sln | 817 ++++++++++++++++++ FORK.md | 18 + LICENSE.txt | 176 ++++ README.md | 251 ++++++ build.ps1 | 30 + docs/design-doc.md | 94 ++ docs/what-are-shared-types.md | 112 +++ .../MvcApp/Controllers/HomeController.cs | 15 + samples/aspnetcore-mvc/MvcApp/MvcApp.csproj | 21 + samples/aspnetcore-mvc/MvcApp/Program.cs | 20 + samples/aspnetcore-mvc/MvcApp/Startup.cs | 37 + .../MvcApp/Views/Home/Index.cshtml | 9 + .../MvcApp/Views/_ViewImports.cshtml | 2 + .../MvcAppPlugin1/MvcAppPlugin1.csproj | 12 + .../MvcAppPlugin1/MyPluginController.cs | 12 + .../MvcAppPlugin1/Views/MyPlugin/Index.cshtml | 5 + samples/aspnetcore-mvc/README.md | 23 + samples/aspnetcore-mvc/aspnetcore-mvc.sln | 48 + .../Abstractions/Abstractions.csproj | 11 + samples/aspnetcore/Abstractions/IPlugin.cs | 14 + .../aspnetcore/Abstractions/IPluginLink.cs | 10 + .../aspnetcore/MainWebApp/MainWebApp.csproj | 30 + .../aspnetcore/MainWebApp/Pages/Index.cshtml | 11 + .../MainWebApp/Pages/Index.cshtml.cs | 24 + samples/aspnetcore/MainWebApp/Program.cs | 20 + samples/aspnetcore/MainWebApp/Startup.cs | 68 ++ samples/aspnetcore/README.md | 42 + .../WebAppPlugin1/WebAppPlugin1.csproj | 12 + .../aspnetcore/WebAppPlugin1/WebPlugin1.cs | 32 + .../WebAppPlugin2/WebAppPlugin2.csproj | 12 + .../aspnetcore/WebAppPlugin2/WebPlugin2.cs | 32 + samples/aspnetcore/aspnetcore.sln | 76 ++ .../DI.HostApp/DI.HostApp.csproj | 26 + .../DI.HostApp/Program.cs | 69 ++ .../DI.SharedAbstractions.csproj | 11 + .../DI.SharedAbstractions/Fruit.cs | 15 + .../DI.SharedAbstractions/IFruitConsumer.cs | 10 + .../DI.SharedAbstractions/IFruitProducer.cs | 12 + .../DI.SharedAbstractions/IPluginFactory.cs | 12 + .../MyPlugin1/MyFruitProducer.cs | 18 + .../MyPlugin1/MyPlugin1.csproj | 11 + .../MyPlugin1/PluginConfiguration.cs | 16 + .../MyPlugin2/MyFruitConsumer.cs | 30 + .../MyPlugin2/MyPlugin2.csproj | 11 + .../MyPlugin2/PluginConfiguration.cs | 16 + samples/dependency-injection/README.md | 18 + .../Contracts/Contracts.csproj | 12 + .../dynamic-implementation/Contracts/Fruit.cs | 10 + .../Contracts/IFruitService.cs | 12 + .../Contracts/IMixerService.cs | 10 + .../Contracts/IPluginFactory.cs | 12 + .../DynamicImplementation.sln | 43 + .../dynamic-implementation/Host/Host.csproj | 28 + .../dynamic-implementation/Host/Program.cs | 69 ++ .../dynamic-implementation/Mixer/Mixer.csproj | 12 + .../Mixer/MixerPlugin.cs | 17 + .../Mixer/MixerService.cs | 22 + .../Mixer/StandardFruiteService.cs | 21 + samples/dynamic-implementation/README.md | 21 + .../OverrideFruiteService.cs | 19 + .../ServiceImplementation/OverridePlugin.cs | 17 + .../ServiceImplementation.csproj | 12 + samples/hello-world/HostApp/HostApp.csproj | 21 + samples/hello-world/HostApp/Program.cs | 41 + samples/hello-world/MyPlugin/MyPlugin.csproj | 11 + samples/hello-world/MyPlugin/MyPlugin1.cs | 12 + samples/hello-world/PluginContract/IPlugin.cs | 16 + .../PluginContract/PluginContract.csproj | 7 + samples/hello-world/README.md | 17 + samples/hello-world/hello-world.sln | 62 ++ .../HotReloadApp/HotReloadApp.csproj | 16 + samples/hot-reload/HotReloadApp/Program.cs | 39 + samples/hot-reload/README.md | 13 + .../TimestampedPlugin/InfoDisplayer.cs | 32 + .../TimestampedPlugin.csproj | 18 + samples/hot-reload/hot-reload.sln | 48 + samples/hot-reload/run.ps1 | 42 + samples/hot-reload/run.sh | 33 + src/Directory.Build.targets | 14 + .../McMaster.NETCore.Plugins.Mvc.csproj | 21 + src/Plugins.Mvc/MvcPluginExtensions.cs | 74 ++ src/Plugins.Mvc/PublicAPI.Shipped.txt | 4 + src/Plugins.Mvc/PublicAPI.Unshipped.txt | 0 src/Plugins.Mvc/releasenotes.props | 17 + src/Plugins/Internal/Debouncer.cs | 42 + src/Plugins/Internal/PlatformInformation.cs | 47 + src/Plugins/Internal/RuntimeConfig.cs | 12 + src/Plugins/Internal/RuntimeOptions.cs | 12 + src/Plugins/LibraryModel/ManagedLibrary.cs | 73 ++ src/Plugins/LibraryModel/NativeLibrary.cs | 69 ++ .../Loader/AssemblyLoadContextBuilder.cs | 369 ++++++++ src/Plugins/Loader/ManagedLoadContext.cs | 436 ++++++++++ src/Plugins/Loader/RuntimeConfigExtensions.cs | 129 +++ src/Plugins/McMaster.NETCore.Plugins.csproj | 18 + src/Plugins/PluginConfig.cs | 124 +++ src/Plugins/PluginLoader.cs | 400 +++++++++ src/Plugins/PluginReloadedEventHandler.cs | 34 + src/Plugins/Properties/AssemblyInfo.cs | 7 + src/Plugins/PublicAPI.Shipped.txt | 72 ++ src/Plugins/PublicAPI.Unshipped.txt | 0 src/Plugins/releasenotes.props | 98 +++ src/StrongName.snk | Bin 0 -> 596 bytes src/common.psm1 | 10 + ...kari.McMaster.NETCore.Plugins.Tests.csproj | 19 + test/Hosihikari.Tests/NativeRidPathTests.cs | 40 + .../Plugins.Tests/BasicAssemblyLoaderTests.cs | 147 ++++ test/Plugins.Tests/DebouncerTests.cs | 60 ++ test/Plugins.Tests/ManageLoadContextTests.cs | 65 ++ .../McMaster.NETCore.Plugins.Tests.csproj | 41 + test/Plugins.Tests/PrivateDependencyTests.cs | 40 + test/Plugins.Tests/ShadowCopyTests.cs | 28 + test/Plugins.Tests/SharedTypesTests.cs | 107 +++ test/Plugins.Tests/TestProjectRefs.targets | 50 ++ .../TestProjectReferenceAttribute.cs | 20 + test/Plugins.Tests/Utilities/TestResources.cs | 20 + test/TestProjects/Banana/Banana.cs | 12 + test/TestProjects/Banana/Banana.csproj | 11 + test/TestProjects/Directory.Build.props | 7 + .../TestProjects/DrawingApp/DrawingApp.csproj | 11 + test/TestProjects/DrawingApp/Finder.cs | 13 + test/TestProjects/Libv1/Class1.cs | 9 + test/TestProjects/Libv1/Libv1.csproj | 9 + test/TestProjects/Libv2/Class1.cs | 9 + test/TestProjects/Libv2/Libv2.csproj | 9 + test/TestProjects/Libv3/Class1.cs | 9 + test/TestProjects/Libv3/Libv3.csproj | 9 + .../NativeDependency/NativeDependency.csproj | 11 + .../NativeDependencyLoader.cs | 41 + .../NetCoreApp2App/NetCoreApp2App.csproj | 12 + test/TestProjects/NetCoreApp2App/Program.cs | 17 + .../NetStandardClassLib/Class1.cs | 10 + .../NetStandardClassLib.csproj | 7 + .../Pl\303\241tano/Pl\303\241tano.cs" | 13 + .../Pl\303\241tano/Pl\303\241tano.csproj" | 11 + .../Pl\303\241tano/Strings.Designer.cs" | 55 ++ .../Pl\303\241tano/Strings.es.Designer.cs" | 1 + .../Pl\303\241tano/Strings.es.resx" | 24 + .../TestProjects/Pl\303\241tano/Strings.resx" | 24 + .../PowerShellPlugin/PowerShellPlugin.csproj | 11 + test/TestProjects/PowerShellPlugin/Program.cs | 25 + test/TestProjects/PrivateDepv1/Class1.cs | 8 + .../PrivateDepv1/PrivateDepv1.csproj | 12 + test/TestProjects/PrivateDepv2/Class1.cs | 8 + .../PrivateDepv2/PrivateDepv2.csproj | 12 + test/TestProjects/PrivateDepv3/Class1.cs | 8 + .../PrivateDepv3/PrivateDepv3.csproj | 12 + test/TestProjects/ReferencedLibv1/Class1.cs | 10 + test/TestProjects/ReferencedLibv1/IFruit.cs | 10 + .../ReferencedLibv1/ReferencedLibv1.csproj | 9 + test/TestProjects/ReferencedLibv2/Class1.cs | 10 + .../ReferencedLibv2/ReferencedLibv2.csproj | 13 + .../SharedAbstraction.v1.csproj | 13 + .../SharedAbstraction.v1/SharedType.cs | 13 + .../SharedAbstraction.v2.csproj | 14 + test/TestProjects/SqlClientApp/Program.cs | 31 + .../SqlClientApp/SqlClientApp.csproj | 12 + test/TestProjects/Strawberry/Strawberry.cs | 12 + .../TestProjects/Strawberry/Strawberry.csproj | 11 + .../TransitiveDep.v1/TransitiveDep.v1.csproj | 9 + .../TransitiveDep.v1/TransitiveSharedType.cs | 7 + .../TransitiveDep.v2/TransitiveDep.v2.csproj | 13 + .../TransitivePlugin/PluginConfig.cs | 12 + .../TransitivePlugin/TransitivePlugin.csproj | 11 + .../WithOurPluginsPluginA/Class1.cs | 12 + .../WithOurPluginsPluginA.csproj | 12 + .../WithOurPluginsPluginB/Class1.cs | 12 + .../WithOurPluginsPluginB.csproj | 12 + .../WithOurPluginsPluginContract/ISayHello.cs | 10 + .../WithOurPluginsPluginContract.csproj | 7 + .../WithOwnPlugins/WithOwnPlugins.cs | 70 ++ .../WithOwnPlugins/WithOwnPlugins.csproj | 15 + .../WithOwnPluginsContract/IWithOwnPlugins.cs | 12 + .../WithOwnPluginsContract.csproj | 7 + test/TestProjects/XunitSample/Class1.cs | 9 + .../XunitSample/XunitSample.csproj | 12 + 190 files changed, 7203 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/CODEOWNERS create mode 100644 .github/ISSUE_TEMPLATE/Bug_report.md create mode 100644 .github/ISSUE_TEMPLATE/Feature_request.md create mode 100644 .github/ISSUE_TEMPLATE/Question.md create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/stale.yml create mode 100644 .gitignore create mode 100644 .vscode/launch.json create mode 100644 .vscode/tasks.json create mode 100644 CLAUDE.md create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 Directory.Build.props create mode 100644 DotNetCorePlugins.sln create mode 100644 FORK.md create mode 100644 LICENSE.txt create mode 100644 README.md create mode 100644 build.ps1 create mode 100644 docs/design-doc.md create mode 100644 docs/what-are-shared-types.md create mode 100644 samples/aspnetcore-mvc/MvcApp/Controllers/HomeController.cs create mode 100644 samples/aspnetcore-mvc/MvcApp/MvcApp.csproj create mode 100644 samples/aspnetcore-mvc/MvcApp/Program.cs create mode 100644 samples/aspnetcore-mvc/MvcApp/Startup.cs create mode 100644 samples/aspnetcore-mvc/MvcApp/Views/Home/Index.cshtml create mode 100644 samples/aspnetcore-mvc/MvcApp/Views/_ViewImports.cshtml create mode 100644 samples/aspnetcore-mvc/MvcAppPlugin1/MvcAppPlugin1.csproj create mode 100644 samples/aspnetcore-mvc/MvcAppPlugin1/MyPluginController.cs create mode 100644 samples/aspnetcore-mvc/MvcAppPlugin1/Views/MyPlugin/Index.cshtml create mode 100644 samples/aspnetcore-mvc/README.md create mode 100644 samples/aspnetcore-mvc/aspnetcore-mvc.sln create mode 100644 samples/aspnetcore/Abstractions/Abstractions.csproj create mode 100644 samples/aspnetcore/Abstractions/IPlugin.cs create mode 100644 samples/aspnetcore/Abstractions/IPluginLink.cs create mode 100644 samples/aspnetcore/MainWebApp/MainWebApp.csproj create mode 100644 samples/aspnetcore/MainWebApp/Pages/Index.cshtml create mode 100644 samples/aspnetcore/MainWebApp/Pages/Index.cshtml.cs create mode 100644 samples/aspnetcore/MainWebApp/Program.cs create mode 100644 samples/aspnetcore/MainWebApp/Startup.cs create mode 100644 samples/aspnetcore/README.md create mode 100644 samples/aspnetcore/WebAppPlugin1/WebAppPlugin1.csproj create mode 100644 samples/aspnetcore/WebAppPlugin1/WebPlugin1.cs create mode 100644 samples/aspnetcore/WebAppPlugin2/WebAppPlugin2.csproj create mode 100644 samples/aspnetcore/WebAppPlugin2/WebPlugin2.cs create mode 100644 samples/aspnetcore/aspnetcore.sln create mode 100644 samples/dependency-injection/DI.HostApp/DI.HostApp.csproj create mode 100644 samples/dependency-injection/DI.HostApp/Program.cs create mode 100644 samples/dependency-injection/DI.SharedAbstractions/DI.SharedAbstractions.csproj create mode 100644 samples/dependency-injection/DI.SharedAbstractions/Fruit.cs create mode 100644 samples/dependency-injection/DI.SharedAbstractions/IFruitConsumer.cs create mode 100644 samples/dependency-injection/DI.SharedAbstractions/IFruitProducer.cs create mode 100644 samples/dependency-injection/DI.SharedAbstractions/IPluginFactory.cs create mode 100644 samples/dependency-injection/MyPlugin1/MyFruitProducer.cs create mode 100644 samples/dependency-injection/MyPlugin1/MyPlugin1.csproj create mode 100644 samples/dependency-injection/MyPlugin1/PluginConfiguration.cs create mode 100644 samples/dependency-injection/MyPlugin2/MyFruitConsumer.cs create mode 100644 samples/dependency-injection/MyPlugin2/MyPlugin2.csproj create mode 100644 samples/dependency-injection/MyPlugin2/PluginConfiguration.cs create mode 100644 samples/dependency-injection/README.md create mode 100644 samples/dynamic-implementation/Contracts/Contracts.csproj create mode 100644 samples/dynamic-implementation/Contracts/Fruit.cs create mode 100644 samples/dynamic-implementation/Contracts/IFruitService.cs create mode 100644 samples/dynamic-implementation/Contracts/IMixerService.cs create mode 100644 samples/dynamic-implementation/Contracts/IPluginFactory.cs create mode 100644 samples/dynamic-implementation/DynamicImplementation.sln create mode 100644 samples/dynamic-implementation/Host/Host.csproj create mode 100644 samples/dynamic-implementation/Host/Program.cs create mode 100644 samples/dynamic-implementation/Mixer/Mixer.csproj create mode 100644 samples/dynamic-implementation/Mixer/MixerPlugin.cs create mode 100644 samples/dynamic-implementation/Mixer/MixerService.cs create mode 100644 samples/dynamic-implementation/Mixer/StandardFruiteService.cs create mode 100644 samples/dynamic-implementation/README.md create mode 100644 samples/dynamic-implementation/ServiceImplementation/OverrideFruiteService.cs create mode 100644 samples/dynamic-implementation/ServiceImplementation/OverridePlugin.cs create mode 100644 samples/dynamic-implementation/ServiceImplementation/ServiceImplementation.csproj create mode 100644 samples/hello-world/HostApp/HostApp.csproj create mode 100644 samples/hello-world/HostApp/Program.cs create mode 100644 samples/hello-world/MyPlugin/MyPlugin.csproj create mode 100644 samples/hello-world/MyPlugin/MyPlugin1.cs create mode 100644 samples/hello-world/PluginContract/IPlugin.cs create mode 100644 samples/hello-world/PluginContract/PluginContract.csproj create mode 100644 samples/hello-world/README.md create mode 100644 samples/hello-world/hello-world.sln create mode 100644 samples/hot-reload/HotReloadApp/HotReloadApp.csproj create mode 100644 samples/hot-reload/HotReloadApp/Program.cs create mode 100644 samples/hot-reload/README.md create mode 100644 samples/hot-reload/TimestampedPlugin/InfoDisplayer.cs create mode 100644 samples/hot-reload/TimestampedPlugin/TimestampedPlugin.csproj create mode 100644 samples/hot-reload/hot-reload.sln create mode 100644 samples/hot-reload/run.ps1 create mode 100644 samples/hot-reload/run.sh create mode 100644 src/Directory.Build.targets create mode 100644 src/Plugins.Mvc/McMaster.NETCore.Plugins.Mvc.csproj create mode 100644 src/Plugins.Mvc/MvcPluginExtensions.cs create mode 100644 src/Plugins.Mvc/PublicAPI.Shipped.txt create mode 100644 src/Plugins.Mvc/PublicAPI.Unshipped.txt create mode 100644 src/Plugins.Mvc/releasenotes.props create mode 100644 src/Plugins/Internal/Debouncer.cs create mode 100644 src/Plugins/Internal/PlatformInformation.cs create mode 100644 src/Plugins/Internal/RuntimeConfig.cs create mode 100644 src/Plugins/Internal/RuntimeOptions.cs create mode 100644 src/Plugins/LibraryModel/ManagedLibrary.cs create mode 100644 src/Plugins/LibraryModel/NativeLibrary.cs create mode 100644 src/Plugins/Loader/AssemblyLoadContextBuilder.cs create mode 100644 src/Plugins/Loader/ManagedLoadContext.cs create mode 100644 src/Plugins/Loader/RuntimeConfigExtensions.cs create mode 100644 src/Plugins/McMaster.NETCore.Plugins.csproj create mode 100644 src/Plugins/PluginConfig.cs create mode 100644 src/Plugins/PluginLoader.cs create mode 100644 src/Plugins/PluginReloadedEventHandler.cs create mode 100644 src/Plugins/Properties/AssemblyInfo.cs create mode 100644 src/Plugins/PublicAPI.Shipped.txt create mode 100644 src/Plugins/PublicAPI.Unshipped.txt create mode 100644 src/Plugins/releasenotes.props create mode 100644 src/StrongName.snk create mode 100644 src/common.psm1 create mode 100644 test/Hosihikari.Tests/Hosihikari.McMaster.NETCore.Plugins.Tests.csproj create mode 100644 test/Hosihikari.Tests/NativeRidPathTests.cs create mode 100644 test/Plugins.Tests/BasicAssemblyLoaderTests.cs create mode 100644 test/Plugins.Tests/DebouncerTests.cs create mode 100644 test/Plugins.Tests/ManageLoadContextTests.cs create mode 100644 test/Plugins.Tests/McMaster.NETCore.Plugins.Tests.csproj create mode 100644 test/Plugins.Tests/PrivateDependencyTests.cs create mode 100644 test/Plugins.Tests/ShadowCopyTests.cs create mode 100644 test/Plugins.Tests/SharedTypesTests.cs create mode 100644 test/Plugins.Tests/TestProjectRefs.targets create mode 100644 test/Plugins.Tests/Utilities/TestProjectReferenceAttribute.cs create mode 100644 test/Plugins.Tests/Utilities/TestResources.cs create mode 100644 test/TestProjects/Banana/Banana.cs create mode 100644 test/TestProjects/Banana/Banana.csproj create mode 100644 test/TestProjects/Directory.Build.props create mode 100644 test/TestProjects/DrawingApp/DrawingApp.csproj create mode 100644 test/TestProjects/DrawingApp/Finder.cs create mode 100644 test/TestProjects/Libv1/Class1.cs create mode 100644 test/TestProjects/Libv1/Libv1.csproj create mode 100644 test/TestProjects/Libv2/Class1.cs create mode 100644 test/TestProjects/Libv2/Libv2.csproj create mode 100644 test/TestProjects/Libv3/Class1.cs create mode 100644 test/TestProjects/Libv3/Libv3.csproj create mode 100644 test/TestProjects/NativeDependency/NativeDependency.csproj create mode 100644 test/TestProjects/NativeDependency/NativeDependencyLoader.cs create mode 100644 test/TestProjects/NetCoreApp2App/NetCoreApp2App.csproj create mode 100644 test/TestProjects/NetCoreApp2App/Program.cs create mode 100644 test/TestProjects/NetStandardClassLib/Class1.cs create mode 100644 test/TestProjects/NetStandardClassLib/NetStandardClassLib.csproj create mode 100644 "test/TestProjects/Pl\303\241tano/Pl\303\241tano.cs" create mode 100644 "test/TestProjects/Pl\303\241tano/Pl\303\241tano.csproj" create mode 100644 "test/TestProjects/Pl\303\241tano/Strings.Designer.cs" create mode 100644 "test/TestProjects/Pl\303\241tano/Strings.es.Designer.cs" create mode 100644 "test/TestProjects/Pl\303\241tano/Strings.es.resx" create mode 100644 "test/TestProjects/Pl\303\241tano/Strings.resx" create mode 100644 test/TestProjects/PowerShellPlugin/PowerShellPlugin.csproj create mode 100644 test/TestProjects/PowerShellPlugin/Program.cs create mode 100644 test/TestProjects/PrivateDepv1/Class1.cs create mode 100644 test/TestProjects/PrivateDepv1/PrivateDepv1.csproj create mode 100644 test/TestProjects/PrivateDepv2/Class1.cs create mode 100644 test/TestProjects/PrivateDepv2/PrivateDepv2.csproj create mode 100644 test/TestProjects/PrivateDepv3/Class1.cs create mode 100644 test/TestProjects/PrivateDepv3/PrivateDepv3.csproj create mode 100644 test/TestProjects/ReferencedLibv1/Class1.cs create mode 100644 test/TestProjects/ReferencedLibv1/IFruit.cs create mode 100644 test/TestProjects/ReferencedLibv1/ReferencedLibv1.csproj create mode 100644 test/TestProjects/ReferencedLibv2/Class1.cs create mode 100644 test/TestProjects/ReferencedLibv2/ReferencedLibv2.csproj create mode 100644 test/TestProjects/SharedAbstraction.v1/SharedAbstraction.v1.csproj create mode 100644 test/TestProjects/SharedAbstraction.v1/SharedType.cs create mode 100644 test/TestProjects/SharedAbstraction.v2/SharedAbstraction.v2.csproj create mode 100644 test/TestProjects/SqlClientApp/Program.cs create mode 100644 test/TestProjects/SqlClientApp/SqlClientApp.csproj create mode 100644 test/TestProjects/Strawberry/Strawberry.cs create mode 100644 test/TestProjects/Strawberry/Strawberry.csproj create mode 100644 test/TestProjects/TransitiveDep.v1/TransitiveDep.v1.csproj create mode 100644 test/TestProjects/TransitiveDep.v1/TransitiveSharedType.cs create mode 100644 test/TestProjects/TransitiveDep.v2/TransitiveDep.v2.csproj create mode 100644 test/TestProjects/TransitivePlugin/PluginConfig.cs create mode 100644 test/TestProjects/TransitivePlugin/TransitivePlugin.csproj create mode 100644 test/TestProjects/WithOurPluginsPluginA/Class1.cs create mode 100644 test/TestProjects/WithOurPluginsPluginA/WithOurPluginsPluginA.csproj create mode 100644 test/TestProjects/WithOurPluginsPluginB/Class1.cs create mode 100644 test/TestProjects/WithOurPluginsPluginB/WithOurPluginsPluginB.csproj create mode 100644 test/TestProjects/WithOurPluginsPluginContract/ISayHello.cs create mode 100644 test/TestProjects/WithOurPluginsPluginContract/WithOurPluginsPluginContract.csproj create mode 100644 test/TestProjects/WithOwnPlugins/WithOwnPlugins.cs create mode 100644 test/TestProjects/WithOwnPlugins/WithOwnPlugins.csproj create mode 100644 test/TestProjects/WithOwnPluginsContract/IWithOwnPlugins.cs create mode 100644 test/TestProjects/WithOwnPluginsContract/WithOwnPluginsContract.csproj create mode 100644 test/TestProjects/XunitSample/Class1.cs create mode 100644 test/TestProjects/XunitSample/XunitSample.csproj diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..a045258 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,118 @@ +root = true + +[*] +indent_style = space +trim_trailing_whitespace = true +insert_final_newline = true +end_of_line = lf + +[*.{xml,csproj,props,targets,config}] +indent_size = 2 + +[*.json] +indent_size = 2 + +[*.yml] +indent_size = 2 + +[*.cs] +indent_size = 4 + +# Style I care about +csharp_style_expression_bodied_constructors = false +csharp_prefer_braces = true +dotnet_sort_system_directives_first = true +csharp_using_directive_placement = outside_namespace + +# Stuff that is usually best +csharp_style_inlined_variable_declaration = true +csharp_style_var_elsewhere = true +csharp_space_after_cast = false +csharp_style_pattern_matching_over_as_with_null_check = true +csharp_style_pattern_matching_over_is_with_cast_check = true +csharp_style_var_for_built_in_types = true +csharp_style_var_when_type_is_apparent = true +csharp_new_line_before_catch = true +csharp_new_line_before_else = true +csharp_new_line_before_finally = true +csharp_indent_case_contents = true +csharp_new_line_before_open_brace = all +csharp_indent_switch_labels = true +csharp_indent_labels = one_less_than_current +csharp_prefer_simple_default_expression = true +csharp_preserve_single_line_blocks = true +csharp_preserve_single_line_statements = true + +# Good defaults, but not always +dotnet_style_object_initializer = true +csharp_style_expression_bodied_indexers = true +csharp_style_expression_bodied_accessors = true +csharp_style_throw_expression = true + +# Default severity for analyzer diagnostics with category 'Style' (escalated to build warnings) +# dotnet_analyzer_diagnostic.category-Style.severity = suggestion + +# Required naming style +dotnet_diagnostic.IDE0006.severity = error + +# suppress warning aboud unused methods +dotnet_diagnostic.IDE0051.severity = none + +# Missing required header +dotnet_diagnostic.IDE0040.severity = error + +# Missing accessibility modifier +dotnet_diagnostic.IDE0073.severity = warning + +# Remove unnecessary parenthesis +dotnet_diagnostic.IDE0047.severity = warning + +# Parenthesis added for clarity +dotnet_diagnostic.IDE0048.severity = warning + +# Suppress explicit type instead of var +dotnet_diagnostic.IDE0008.severity = none + +# Suppress unused expression +dotnet_diagnostic.IDE0058.severity = none + + + +# Naming styles + +## Constants are PascalCase +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_symbols.constants.applicable_kinds = field, property +dotnet_naming_symbols.constants.applicable_accessibilities = * +dotnet_naming_symbols.constants.required_modifiers = const + +dotnet_naming_rule.constants_should_be_pascal_case.symbols = constants +dotnet_naming_rule.constants_should_be_pascal_case.style = pascal_case +dotnet_naming_rule.constants_should_be_pascal_case.severity = error + +## Private static fields start with s_ +dotnet_naming_style.s_underscore_camel_case.required_prefix = s_ +dotnet_naming_style.s_underscore_camel_case.capitalization = camel_case + +dotnet_naming_symbols.private_static_fields.applicable_kinds = field +dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private +dotnet_naming_symbols.private_static_fields.required_modifiers = static + +dotnet_naming_rule.private_static_fields_should_be_underscore.symbols = private_static_fields +dotnet_naming_rule.private_static_fields_should_be_underscore.style = s_underscore_camel_case +dotnet_naming_rule.private_static_fields_should_be_underscore.severity = error + +## Private fields are _camelCase +dotnet_naming_style.underscore_camel_case.required_prefix = _ +dotnet_naming_style.underscore_camel_case.capitalization = camel_case + +dotnet_naming_symbols.private_fields.applicable_kinds = field +dotnet_naming_symbols.private_fields.applicable_accessibilities = private + +dotnet_naming_rule.private_fields_should_be_underscore.symbols = private_fields +dotnet_naming_rule.private_fields_should_be_underscore.style = underscore_camel_case +dotnet_naming_rule.private_fields_should_be_underscore.severity = error + +# File header +file_header_template = Copyright (c) Nate McMaster.\nLicensed under the Apache License, Version 2.0. See License.txt in the project root for license information. diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..4fd9420 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @natemcmaster diff --git a/.github/ISSUE_TEMPLATE/Bug_report.md b/.github/ISSUE_TEMPLATE/Bug_report.md new file mode 100644 index 0000000..656c5ec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug_report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Using this version of the library '...' +2. Run this code '....' +3. With these arguments '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/Feature_request.md b/.github/ISSUE_TEMPLATE/Feature_request.md new file mode 100644 index 0000000..8a1e6df --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_request.md @@ -0,0 +1,21 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: enhancement +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. +Example. I'm am trying to do [...] but [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.github/ISSUE_TEMPLATE/Question.md b/.github/ISSUE_TEMPLATE/Question.md new file mode 100644 index 0000000..8f96977 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Question.md @@ -0,0 +1,10 @@ +--- +name: Question +about: Ask a question +title: "[Question] " +labels: question +assignees: '' + +--- + +If you're not exactly sure what to say, here are some suggestions: https://stackoverflow.com/help/how-to-ask diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..2485306 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,16 @@ +name: verify + +on: + push: + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + - name: 跑断言 + run: dotnet test test/Hosihikari.Tests/Hosihikari.McMaster.NETCore.Plugins.Tests.csproj -c Release --nologo diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 0000000..9237e4d --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,41 @@ +name: 'Close stale issues and PRs' +on: + workflow_dispatch: + inputs: + debug-only: + description: Run in debug mode + required: false + default: 'false' + schedule: + - cron: '30 1 * * *' + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v9 + with: + debug-only: ${{ github.event.inputs.debug-only == 'true' }} + days-before-stale: 365 + days-before-close: 14 + stale-issue-label: stale + close-issue-label: closed-stale + close-issue-reason: not_planned + exempt-issue-labels: announcement,planning + exempt-all-milestones: true + exempt-all-assignees: true + stale-issue-message: > + This issue has been automatically marked as stale because it has no recent activity. + It will be closed if no further activity occurs. Please comment if you believe this + should remain open, otherwise it will be closed in 14 days. + Thank you for your contributions to this project. + close-issue-message: > + Closing due to inactivity. + + If you are looking at this issue in the future and think it should be reopened, + please make a commented here and mention natemcmaster so he sees the notification. + stale-pr-message: > + This pull request appears to be stale. Please comment if you believe this should remain + open and reviewed. If there are no updates, it will be closed in 14 days. + close-pr-message: > + Thank you for your contributions to this project. This pull request has been closed due to inactivity. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bbe7087 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +bin/ +obj/ +.vs/ +artifacts/ +.idea/ +*.user +launchSettings.json +.dotnet/ +TestResults/ diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..396e7ed --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,11 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..935f9b1 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,26 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "command": "dotnet", + "label": "build", + "args": [ + "build", + "--no-restore" + ], + "presentation": { + "echo": true, + "reveal": "silent", + "focus": false, + "panel": "shared", + "showReuseMessage": false, + "clear": true + }, + "problemMatcher": "$msCompile", + "group": { + "kind": "build", + "isDefault": true + } + } + ] +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..8f44d8f --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,74 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +DotNetCorePlugins is a .NET library for dynamically loading .NET assemblies as plugins with fine-grained control over assembly isolation and type sharing. Version 2.0+ targets .NET 8.0. + +## Build Commands + +```bash +# Full build (format, build, pack, test) +pwsh ./build.ps1 + +# CI build (Release configuration) +pwsh ./build.ps1 -ci + +# Individual commands +dotnet format # Code formatting +dotnet build # Build all projects +dotnet test test/Plugins.Tests # Run tests +dotnet pack -o artifacts/ # Create NuGet packages +``` + +## CI + +Include `[ci skip]` in a commit message to skip CI builds. + +## Architecture + +### Core Components + +- **McMaster.NETCore.Plugins** (`src/Plugins/`): Main library providing `PluginLoader` API + - `PluginLoader.cs`: Primary entry point for loading plugins from assembly files + - `PluginConfig.cs`: Configuration options (shared types, unloadable, hot reload) + - `Loader/ManagedLoadContext.cs`: Custom AssemblyLoadContext implementation + - `Loader/AssemblyLoadContextBuilder.cs`: Builds the load context with dependencies + +- **McMaster.NETCore.Plugins.Mvc** (`src/Plugins.Mvc/`): ASP.NET Core MVC/Razor integration + +### Key Concepts + +**Type Sharing**: The central design concept. By default, each plugin has isolated assemblies. Shared types allow passing objects across the plugin/host boundary by unifying specified types to use the host's version. + +**AssemblyLoadContext**: .NET's built-in assembly isolation mechanism. The library creates custom contexts that read `.deps.json` and `.runtimeconfig.json` to resolve dependencies. + +### Test Structure + +- `test/Plugins.Tests/`: xUnit tests +- `test/TestProjects/`: 30+ test assemblies for various plugin scenarios + +## Code Conventions + +Enforced via `.editorconfig` with build errors: + +- **Private fields**: `_camelCase` +- **Private static fields**: `s_camelCase` +- **Constants**: `PascalCase` +- **Braces**: Always required +- **Using directives**: Outside namespace, System first + +Required file header on all C# files: +```csharp +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +``` + +## Build Configuration + +- Strong name signing enabled (`src/StrongName.snk`) +- Warnings as errors enabled +- Nullable reference types enabled +- C# 12 with implicit usings +- Build outputs go to `.build/bin/` and `.build/obj/` diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..7692b88 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,76 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as +contributors and maintainers pledge to making participation in our project and +our community a harassment-free experience for everyone, regardless of age, body +size, disability, ethnicity, sex characteristics, gender identity and expression, +level of experience, education, socio-economic status, nationality, personal +appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment +include: + +* Using welcoming and inclusive language +* Being respectful of differing viewpoints and experiences +* Gracefully accepting constructive criticism +* Focusing on what is best for the community +* Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +* The use of sexualized language or imagery and unwelcome sexual attention or + advances +* Trolling, insulting/derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or electronic + address, without explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable +behavior and are expected to take appropriate and fair corrective action in +response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or +reject comments, commits, code, wiki edits, issues, and other contributions +that are not aligned to this Code of Conduct, or to ban temporarily or +permanently any contributor for other behaviors that they deem inappropriate, +threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces +when an individual is representing the project or its community. Examples of +representing a project or community include using an official project e-mail +address, posting via an official social media account, or acting as an appointed +representative at an online or offline event. Representation of a project may be +further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported by contacting the project team at open-source@natemcmaster.com. All +complaints will be reviewed and investigated and will result in a response that +is deemed necessary and appropriate to the circumstances. The project team is +obligated to maintain confidentiality with regard to the reporter of an incident. +Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good +faith may face temporary or permanent repercussions as determined by other +members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, +available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see +https://www.contributor-covenant.org/faq diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..397565d --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,31 @@ +Contributing Guide +================== + +Contributions are welcome! If you would like to help out, here are some suggestions for how to get involved. + +## Get involved +[Watch][watchers] this repository to get notifications about all conversations. GitHub issues and pull requests are the authoritative +source of truth for design reviews, release schedules, and bug fixes. + +## You don't have to contribute code + +There are more ways to help that don't involve writing code. + +* Respond to new issues. Users often open an issue to ask a question. You are welcome to offer your answer on the thread. +* :+1: Up vote features that you think are important. +* Look through issues labeled [closed-stale][closed-stale] to see if there are feature requests worth reviving. +* Review pull requests. + +## Contributing code + +* Open issues labeled ["help wanted"][help-wanted] are issues that I think are worth doing, but no one has volunteered to do the work yet. + Make a comment on issues you want assigned to yourself. +* Pull requests are more likely to be accepted if I have first agreed to accept a feature or bug fix. Open an issue first if you aren't sure. + +## Questions? + +Open a GitHub issue if you'd like to help and don't know where to begin. + +[watchers]: https://github.com/natemcmaster/DotNetCorePlugins/watchers +[closed-stale]: https://github.com/natemcmaster/DotNetCorePlugins/labels/closed-stale +[help-wanted]: https://github.com/natemcmaster/DotNetCorePlugins/labels/help%20wanted diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..452fdc3 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,56 @@ + + + + Nate McMaster + DotNetCorePlugins + Copyright © Nate McMaster + en-US + false + $(MSBuildThisFileDirectory) + Apache-2.0 + https://github.com/natemcmaster/DotNetCorePlugins + https://github.com/natemcmaster/DotNetCorePlugins.git + README.md + git + snupkg + true + true + portable + false + false + 12 + true + true + enable + $(MSBuildThisFileDirectory)src\StrongName.snk + true + $(NoWarn);NU5105 + $(WarningsNotAsErrors);1591;0618 + + true + true + true + $(MSBuildThisFileDirectory).build\obj\$(MSBuildProjectName)\ + $(MSBuildThisFileDirectory).build\bin\$(MSBuildProjectName)\ + + + + 2.0.0 + beta + true + $([MSBuild]::ValueOrDefault($(GITHUB_RUN_NUMBER), 0)) + $(VersionSuffix).$(BuildNumber) + $(GITHUB_SHA) + $(VersionPrefix) + $(PackageVersion)-$(VersionSuffix) + $(PackageVersion)+$(SourceRevisionId) + + + + + + + + + diff --git a/DotNetCorePlugins.sln b/DotNetCorePlugins.sln new file mode 100644 index 0000000..e9bd38d --- /dev/null +++ b/DotNetCorePlugins.sln @@ -0,0 +1,817 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34622.214 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{471CA3C0-0BC9-4C0C-A013-D9356DBD0F38}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "McMaster.NETCore.Plugins", "src\Plugins\McMaster.NETCore.Plugins.csproj", "{2D251CFD-A9DA-421F-B516-B25873348C08}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{69958BAA-5D54-4F3F-82D6-F622F9491057}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "McMaster.NETCore.Plugins.Tests", "test\Plugins.Tests\McMaster.NETCore.Plugins.Tests.csproj", "{00D7FBF0-801B-4B0E-9FDD-5C367E71444B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestProjects", "TestProjects", "{98E964A2-55DA-4740-9F2E-B64FDF6715DB}" + ProjectSection(SolutionItems) = preProject + test\TestProjects\Directory.Build.props = test\TestProjects\Directory.Build.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCoreApp2App", "test\TestProjects\NetCoreApp2App\NetCoreApp2App.csproj", "{B9C61145-C010-4FAA-B9EA-4D1AEE20305F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetStandardClassLib", "test\TestProjects\NetStandardClassLib\NetStandardClassLib.csproj", "{4FDB7710-F4CA-45FB-9514-A4D288EDA44A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReferencedLibv1", "test\TestProjects\ReferencedLibv1\ReferencedLibv1.csproj", "{CC37B369-BA9E-4B82-A14D-68B58924885D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReferencedLibv2", "test\TestProjects\ReferencedLibv2\ReferencedLibv2.csproj", "{50F86C8F-E052-448C-84CE-08763945FCC7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XunitSample", "test\TestProjects\XunitSample\XunitSample.csproj", "{3CEF9F32-5887-4977-AB8A-1BBC48875A21}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Banana", "test\TestProjects\Banana\Banana.csproj", "{8797AFCA-1EE8-4270-8FC4-F93A85F9C825}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strawberry", "test\TestProjects\Strawberry\Strawberry.csproj", "{78D2B20D-B173-4CF2-A763-F2AA5347947E}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShellPlugin", "test\TestProjects\PowerShellPlugin\PowerShellPlugin.csproj", "{C0BA460C-C1AC-4C54-9C88-60E4B8681E94}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9E47E94A-3F50-4B3B-8937-1FCF832ABEB6}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + .gitignore = .gitignore + build.ps1 = build.ps1 + src\common.psm1 = src\common.psm1 + Directory.Build.props = Directory.Build.props + src\Directory.Build.targets = src\Directory.Build.targets + LICENSE.txt = LICENSE.txt + README.md = README.md + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plátano", "test\TestProjects\Plátano\Plátano.csproj", "{5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DrawingApp", "test\TestProjects\DrawingApp\DrawingApp.csproj", "{8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlClientApp", "test\TestProjects\SqlClientApp\SqlClientApp.csproj", "{0CDA2765-C9DF-45EE-A099-0E2D4A980E59}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcApp", "samples\aspnetcore-mvc\MvcApp\MvcApp.csproj", "{F934B760-4847-4BA8-9F6A-7F750ECF2328}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcAppPlugin1", "samples\aspnetcore-mvc\MvcAppPlugin1\MvcAppPlugin1.csproj", "{2DC38785-B7B6-49E9-84D4-103A56D3604A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F49FFE3B-E1AE-483E-951F-D498F6ABA08E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "aspnetcoremvc", "aspnetcoremvc", "{610E8065-1F29-41E8-A3FC-07ABDF16C8BB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "McMaster.NETCore.Plugins.Mvc", "src\Plugins.Mvc\McMaster.NETCore.Plugins.Mvc.csproj", "{263CD320-FAC5-4F0C-8FFB-788DD3C455D4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "aspnetcore", "aspnetcore", "{DD87D202-998B-4C20-8D68-C769797D7761}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Abstractions", "samples\aspnetcore\Abstractions\Abstractions.csproj", "{D1E1AEDC-4870-4863-A8D3-B6A184E651C1}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MainWebApp", "samples\aspnetcore\MainWebApp\MainWebApp.csproj", "{7DD1722B-B971-4322-BA74-C622E808B84D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebAppPlugin1", "samples\aspnetcore\WebAppPlugin1\WebAppPlugin1.csproj", "{A4C90265-C922-4145-8CDC-82EC5EBBC3A4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebAppPlugin2", "samples\aspnetcore\WebAppPlugin2\WebAppPlugin2.csproj", "{ADF569DC-E916-4F39-8ABF-8AA42F69266E}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "hello-world", "hello-world", "{3B0C6936-E777-4413-B68A-1686A768F928}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostApp", "samples\hello-world\HostApp\HostApp.csproj", "{DFC13FA1-509C-42AF-870F-543C7A3F231B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyPlugin", "samples\hello-world\MyPlugin\MyPlugin.csproj", "{2573526D-A764-4507-B2F6-1A2BF7A3FA93}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PluginContract", "samples\hello-world\PluginContract\PluginContract.csproj", "{6842C9FD-6720-43EA-83AD-DE315531DDA9}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dependency-injection", "dependency-injection", "{D17C19AC-EC11-4661-BD23-DC8308CF71C0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DI.HostApp", "samples\dependency-injection\DI.HostApp\DI.HostApp.csproj", "{5DBEE6E0-8744-46E4-B711-8FEC98D41A50}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyPlugin1", "samples\dependency-injection\MyPlugin1\MyPlugin1.csproj", "{BF78F36A-D072-4F1C-89B1-73D9C208ABB3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyPlugin2", "samples\dependency-injection\MyPlugin2\MyPlugin2.csproj", "{4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DI.SharedAbstractions", "samples\dependency-injection\DI.SharedAbstractions\DI.SharedAbstractions.csproj", "{40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransitiveDep.v2", "test\TestProjects\TransitiveDep.v2\TransitiveDep.v2.csproj", "{CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedAbstraction.v1", "test\TestProjects\SharedAbstraction.v1\SharedAbstraction.v1.csproj", "{D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransitiveDep.v1", "test\TestProjects\TransitiveDep.v1\TransitiveDep.v1.csproj", "{704EA4BF-71D9-442B-96D9-9B3E81068CC2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedAbstraction.v2", "test\TestProjects\SharedAbstraction.v2\SharedAbstraction.v2.csproj", "{3549E707-E4B0-428F-9CF4-5B5B86219B6D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransitivePlugin", "test\TestProjects\TransitivePlugin\TransitivePlugin.csproj", "{1EA2BACF-3D44-4B05-A0E7-727AE13E693D}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "hot-reload", "hot-reload", "{07E0093F-F05C-4AED-A489-1254C9854E21}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotReloadApp", "samples\hot-reload\HotReloadApp\HotReloadApp.csproj", "{D815817F-CC0D-42CC-9C9C-8E229B9C03A8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimestampedPlugin", "samples\hot-reload\TimestampedPlugin\TimestampedPlugin.csproj", "{FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WithOwnPlugins", "test\TestProjects\WithOwnPlugins\WithOwnPlugins.csproj", "{F47ED576-F347-44C4-BEC7-6C00A500555A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WithOwnPluginsContract", "test\TestProjects\WithOwnPluginsContract\WithOwnPluginsContract.csproj", "{F55E52BD-A846-4808-9482-E4AB0921328D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WithOurPluginsPluginContract", "test\TestProjects\WithOurPluginsPluginContract\WithOurPluginsPluginContract.csproj", "{7C7B19F2-5822-4DA2-80D2-E077CE705039}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WithOurPluginsPluginA", "test\TestProjects\WithOurPluginsPluginA\WithOurPluginsPluginA.csproj", "{3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WithOurPluginsPluginB", "test\TestProjects\WithOurPluginsPluginB\WithOurPluginsPluginB.csproj", "{D5B3DA06-E60D-4C85-8835-6A26003365AA}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeDependency", "test\TestProjects\NativeDependency\NativeDependency.csproj", "{2837A894-11D6-4DBF-B140-84C97C385A15}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dynamic-implementation", "dynamic-implementation", "{10109B62-DACC-42BA-AD8C-73DE4B93841B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Contracts", "samples\dynamic-implementation\Contracts\Contracts.csproj", "{39B9F0B5-3C53-4140-B158-96C67C03E876}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Host", "samples\dynamic-implementation\Host\Host.csproj", "{703ECD55-EF0E-4734-A28E-371EDD213EEF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mixer", "samples\dynamic-implementation\Mixer\Mixer.csproj", "{9E2178A0-5915-4308-B6C3-5C8758A47788}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceImplementation", "samples\dynamic-implementation\ServiceImplementation\ServiceImplementation.csproj", "{39BCFE6F-50C4-45AE-8D13-38FB423AB619}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libv1", "test\TestProjects\Libv1\Libv1.csproj", "{3761F08D-06AE-4E07-8B88-F19C465DC169}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libv2", "test\TestProjects\Libv2\Libv2.csproj", "{DB02518A-49A1-4836-80B7-D69A70DE68BA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libv3", "test\TestProjects\Libv3\Libv3.csproj", "{47C888A8-6887-4FA2-A202-8653684F3651}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrivateDepv1", "test\TestProjects\PrivateDepv1\PrivateDepv1.csproj", "{88691B89-0167-4E90-86F6-5FD6C974F7A3}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrivateDepv2", "test\TestProjects\PrivateDepv2\PrivateDepv2.csproj", "{63631241-3CA1-40E2-9545-01E019414E4F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrivateDepv3", "test\TestProjects\PrivateDepv3\PrivateDepv3.csproj", "{0C5BEA9A-9F07-49F5-8E84-80F3250E6516}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {2D251CFD-A9DA-421F-B516-B25873348C08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D251CFD-A9DA-421F-B516-B25873348C08}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D251CFD-A9DA-421F-B516-B25873348C08}.Debug|x64.ActiveCfg = Debug|Any CPU + {2D251CFD-A9DA-421F-B516-B25873348C08}.Debug|x64.Build.0 = Debug|Any CPU + {2D251CFD-A9DA-421F-B516-B25873348C08}.Debug|x86.ActiveCfg = Debug|Any CPU + {2D251CFD-A9DA-421F-B516-B25873348C08}.Debug|x86.Build.0 = Debug|Any CPU + {2D251CFD-A9DA-421F-B516-B25873348C08}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D251CFD-A9DA-421F-B516-B25873348C08}.Release|Any CPU.Build.0 = Release|Any CPU + {2D251CFD-A9DA-421F-B516-B25873348C08}.Release|x64.ActiveCfg = Release|Any CPU + {2D251CFD-A9DA-421F-B516-B25873348C08}.Release|x64.Build.0 = Release|Any CPU + {2D251CFD-A9DA-421F-B516-B25873348C08}.Release|x86.ActiveCfg = Release|Any CPU + {2D251CFD-A9DA-421F-B516-B25873348C08}.Release|x86.Build.0 = Release|Any CPU + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Debug|x64.ActiveCfg = Debug|Any CPU + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Debug|x64.Build.0 = Debug|Any CPU + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Debug|x86.ActiveCfg = Debug|Any CPU + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Debug|x86.Build.0 = Debug|Any CPU + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Release|Any CPU.Build.0 = Release|Any CPU + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Release|x64.ActiveCfg = Release|Any CPU + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Release|x64.Build.0 = Release|Any CPU + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Release|x86.ActiveCfg = Release|Any CPU + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Release|x86.Build.0 = Release|Any CPU + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Debug|x64.ActiveCfg = Debug|Any CPU + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Debug|x64.Build.0 = Debug|Any CPU + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Debug|x86.ActiveCfg = Debug|Any CPU + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Debug|x86.Build.0 = Debug|Any CPU + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Release|Any CPU.Build.0 = Release|Any CPU + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Release|x64.ActiveCfg = Release|Any CPU + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Release|x64.Build.0 = Release|Any CPU + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Release|x86.ActiveCfg = Release|Any CPU + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Release|x86.Build.0 = Release|Any CPU + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Debug|x64.ActiveCfg = Debug|Any CPU + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Debug|x64.Build.0 = Debug|Any CPU + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Debug|x86.ActiveCfg = Debug|Any CPU + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Debug|x86.Build.0 = Debug|Any CPU + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Release|Any CPU.Build.0 = Release|Any CPU + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Release|x64.ActiveCfg = Release|Any CPU + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Release|x64.Build.0 = Release|Any CPU + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Release|x86.ActiveCfg = Release|Any CPU + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Release|x86.Build.0 = Release|Any CPU + {CC37B369-BA9E-4B82-A14D-68B58924885D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC37B369-BA9E-4B82-A14D-68B58924885D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC37B369-BA9E-4B82-A14D-68B58924885D}.Debug|x64.ActiveCfg = Debug|Any CPU + {CC37B369-BA9E-4B82-A14D-68B58924885D}.Debug|x64.Build.0 = Debug|Any CPU + {CC37B369-BA9E-4B82-A14D-68B58924885D}.Debug|x86.ActiveCfg = Debug|Any CPU + {CC37B369-BA9E-4B82-A14D-68B58924885D}.Debug|x86.Build.0 = Debug|Any CPU + {CC37B369-BA9E-4B82-A14D-68B58924885D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC37B369-BA9E-4B82-A14D-68B58924885D}.Release|Any CPU.Build.0 = Release|Any CPU + {CC37B369-BA9E-4B82-A14D-68B58924885D}.Release|x64.ActiveCfg = Release|Any CPU + {CC37B369-BA9E-4B82-A14D-68B58924885D}.Release|x64.Build.0 = Release|Any CPU + {CC37B369-BA9E-4B82-A14D-68B58924885D}.Release|x86.ActiveCfg = Release|Any CPU + {CC37B369-BA9E-4B82-A14D-68B58924885D}.Release|x86.Build.0 = Release|Any CPU + {50F86C8F-E052-448C-84CE-08763945FCC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {50F86C8F-E052-448C-84CE-08763945FCC7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {50F86C8F-E052-448C-84CE-08763945FCC7}.Debug|x64.ActiveCfg = Debug|Any CPU + {50F86C8F-E052-448C-84CE-08763945FCC7}.Debug|x64.Build.0 = Debug|Any CPU + {50F86C8F-E052-448C-84CE-08763945FCC7}.Debug|x86.ActiveCfg = Debug|Any CPU + {50F86C8F-E052-448C-84CE-08763945FCC7}.Debug|x86.Build.0 = Debug|Any CPU + {50F86C8F-E052-448C-84CE-08763945FCC7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {50F86C8F-E052-448C-84CE-08763945FCC7}.Release|Any CPU.Build.0 = Release|Any CPU + {50F86C8F-E052-448C-84CE-08763945FCC7}.Release|x64.ActiveCfg = Release|Any CPU + {50F86C8F-E052-448C-84CE-08763945FCC7}.Release|x64.Build.0 = Release|Any CPU + {50F86C8F-E052-448C-84CE-08763945FCC7}.Release|x86.ActiveCfg = Release|Any CPU + {50F86C8F-E052-448C-84CE-08763945FCC7}.Release|x86.Build.0 = Release|Any CPU + {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Debug|x64.ActiveCfg = Debug|Any CPU + {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Debug|x64.Build.0 = Debug|Any CPU + {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Debug|x86.ActiveCfg = Debug|Any CPU + {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Debug|x86.Build.0 = Debug|Any CPU + {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Release|Any CPU.Build.0 = Release|Any CPU + {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Release|x64.ActiveCfg = Release|Any CPU + {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Release|x64.Build.0 = Release|Any CPU + {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Release|x86.ActiveCfg = Release|Any CPU + {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Release|x86.Build.0 = Release|Any CPU + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Debug|x64.ActiveCfg = Debug|Any CPU + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Debug|x64.Build.0 = Debug|Any CPU + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Debug|x86.ActiveCfg = Debug|Any CPU + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Debug|x86.Build.0 = Debug|Any CPU + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Release|Any CPU.Build.0 = Release|Any CPU + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Release|x64.ActiveCfg = Release|Any CPU + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Release|x64.Build.0 = Release|Any CPU + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Release|x86.ActiveCfg = Release|Any CPU + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Release|x86.Build.0 = Release|Any CPU + {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Debug|x64.ActiveCfg = Debug|Any CPU + {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Debug|x64.Build.0 = Debug|Any CPU + {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Debug|x86.ActiveCfg = Debug|Any CPU + {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Debug|x86.Build.0 = Debug|Any CPU + {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Release|Any CPU.Build.0 = Release|Any CPU + {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Release|x64.ActiveCfg = Release|Any CPU + {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Release|x64.Build.0 = Release|Any CPU + {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Release|x86.ActiveCfg = Release|Any CPU + {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Release|x86.Build.0 = Release|Any CPU + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Debug|x64.ActiveCfg = Debug|Any CPU + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Debug|x64.Build.0 = Debug|Any CPU + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Debug|x86.ActiveCfg = Debug|Any CPU + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Debug|x86.Build.0 = Debug|Any CPU + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Release|Any CPU.Build.0 = Release|Any CPU + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Release|x64.ActiveCfg = Release|Any CPU + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Release|x64.Build.0 = Release|Any CPU + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Release|x86.ActiveCfg = Release|Any CPU + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Release|x86.Build.0 = Release|Any CPU + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Debug|x64.ActiveCfg = Debug|Any CPU + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Debug|x64.Build.0 = Debug|Any CPU + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Debug|x86.ActiveCfg = Debug|Any CPU + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Debug|x86.Build.0 = Debug|Any CPU + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Release|Any CPU.Build.0 = Release|Any CPU + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Release|x64.ActiveCfg = Release|Any CPU + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Release|x64.Build.0 = Release|Any CPU + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Release|x86.ActiveCfg = Release|Any CPU + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Release|x86.Build.0 = Release|Any CPU + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Debug|x64.ActiveCfg = Debug|Any CPU + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Debug|x64.Build.0 = Debug|Any CPU + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Debug|x86.ActiveCfg = Debug|Any CPU + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Debug|x86.Build.0 = Debug|Any CPU + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Release|Any CPU.Build.0 = Release|Any CPU + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Release|x64.ActiveCfg = Release|Any CPU + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Release|x64.Build.0 = Release|Any CPU + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Release|x86.ActiveCfg = Release|Any CPU + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Release|x86.Build.0 = Release|Any CPU + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Debug|x64.ActiveCfg = Debug|Any CPU + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Debug|x64.Build.0 = Debug|Any CPU + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Debug|x86.ActiveCfg = Debug|Any CPU + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Debug|x86.Build.0 = Debug|Any CPU + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Release|Any CPU.Build.0 = Release|Any CPU + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Release|x64.ActiveCfg = Release|Any CPU + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Release|x64.Build.0 = Release|Any CPU + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Release|x86.ActiveCfg = Release|Any CPU + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Release|x86.Build.0 = Release|Any CPU + {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Debug|x64.ActiveCfg = Debug|Any CPU + {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Debug|x64.Build.0 = Debug|Any CPU + {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Debug|x86.ActiveCfg = Debug|Any CPU + {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Debug|x86.Build.0 = Debug|Any CPU + {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Release|Any CPU.Build.0 = Release|Any CPU + {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Release|x64.ActiveCfg = Release|Any CPU + {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Release|x64.Build.0 = Release|Any CPU + {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Release|x86.ActiveCfg = Release|Any CPU + {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Release|x86.Build.0 = Release|Any CPU + {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Debug|x64.ActiveCfg = Debug|Any CPU + {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Debug|x64.Build.0 = Debug|Any CPU + {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Debug|x86.ActiveCfg = Debug|Any CPU + {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Debug|x86.Build.0 = Debug|Any CPU + {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Release|Any CPU.Build.0 = Release|Any CPU + {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Release|x64.ActiveCfg = Release|Any CPU + {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Release|x64.Build.0 = Release|Any CPU + {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Release|x86.ActiveCfg = Release|Any CPU + {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Release|x86.Build.0 = Release|Any CPU + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Debug|x64.ActiveCfg = Debug|Any CPU + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Debug|x64.Build.0 = Debug|Any CPU + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Debug|x86.ActiveCfg = Debug|Any CPU + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Debug|x86.Build.0 = Debug|Any CPU + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Release|Any CPU.Build.0 = Release|Any CPU + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Release|x64.ActiveCfg = Release|Any CPU + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Release|x64.Build.0 = Release|Any CPU + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Release|x86.ActiveCfg = Release|Any CPU + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Release|x86.Build.0 = Release|Any CPU + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Debug|x64.ActiveCfg = Debug|Any CPU + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Debug|x64.Build.0 = Debug|Any CPU + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Debug|x86.ActiveCfg = Debug|Any CPU + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Debug|x86.Build.0 = Debug|Any CPU + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Release|Any CPU.Build.0 = Release|Any CPU + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Release|x64.ActiveCfg = Release|Any CPU + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Release|x64.Build.0 = Release|Any CPU + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Release|x86.ActiveCfg = Release|Any CPU + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Release|x86.Build.0 = Release|Any CPU + {7DD1722B-B971-4322-BA74-C622E808B84D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7DD1722B-B971-4322-BA74-C622E808B84D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7DD1722B-B971-4322-BA74-C622E808B84D}.Debug|x64.ActiveCfg = Debug|Any CPU + {7DD1722B-B971-4322-BA74-C622E808B84D}.Debug|x64.Build.0 = Debug|Any CPU + {7DD1722B-B971-4322-BA74-C622E808B84D}.Debug|x86.ActiveCfg = Debug|Any CPU + {7DD1722B-B971-4322-BA74-C622E808B84D}.Debug|x86.Build.0 = Debug|Any CPU + {7DD1722B-B971-4322-BA74-C622E808B84D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7DD1722B-B971-4322-BA74-C622E808B84D}.Release|Any CPU.Build.0 = Release|Any CPU + {7DD1722B-B971-4322-BA74-C622E808B84D}.Release|x64.ActiveCfg = Release|Any CPU + {7DD1722B-B971-4322-BA74-C622E808B84D}.Release|x64.Build.0 = Release|Any CPU + {7DD1722B-B971-4322-BA74-C622E808B84D}.Release|x86.ActiveCfg = Release|Any CPU + {7DD1722B-B971-4322-BA74-C622E808B84D}.Release|x86.Build.0 = Release|Any CPU + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Debug|x64.ActiveCfg = Debug|Any CPU + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Debug|x64.Build.0 = Debug|Any CPU + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Debug|x86.ActiveCfg = Debug|Any CPU + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Debug|x86.Build.0 = Debug|Any CPU + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Release|Any CPU.Build.0 = Release|Any CPU + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Release|x64.ActiveCfg = Release|Any CPU + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Release|x64.Build.0 = Release|Any CPU + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Release|x86.ActiveCfg = Release|Any CPU + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Release|x86.Build.0 = Release|Any CPU + {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Debug|x64.ActiveCfg = Debug|Any CPU + {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Debug|x64.Build.0 = Debug|Any CPU + {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Debug|x86.ActiveCfg = Debug|Any CPU + {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Debug|x86.Build.0 = Debug|Any CPU + {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Release|Any CPU.Build.0 = Release|Any CPU + {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Release|x64.ActiveCfg = Release|Any CPU + {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Release|x64.Build.0 = Release|Any CPU + {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Release|x86.ActiveCfg = Release|Any CPU + {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Release|x86.Build.0 = Release|Any CPU + {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Debug|x64.ActiveCfg = Debug|Any CPU + {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Debug|x64.Build.0 = Debug|Any CPU + {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Debug|x86.ActiveCfg = Debug|Any CPU + {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Debug|x86.Build.0 = Debug|Any CPU + {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Release|Any CPU.Build.0 = Release|Any CPU + {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Release|x64.ActiveCfg = Release|Any CPU + {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Release|x64.Build.0 = Release|Any CPU + {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Release|x86.ActiveCfg = Release|Any CPU + {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Release|x86.Build.0 = Release|Any CPU + {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Debug|x64.ActiveCfg = Debug|Any CPU + {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Debug|x64.Build.0 = Debug|Any CPU + {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Debug|x86.ActiveCfg = Debug|Any CPU + {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Debug|x86.Build.0 = Debug|Any CPU + {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Release|Any CPU.Build.0 = Release|Any CPU + {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Release|x64.ActiveCfg = Release|Any CPU + {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Release|x64.Build.0 = Release|Any CPU + {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Release|x86.ActiveCfg = Release|Any CPU + {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Release|x86.Build.0 = Release|Any CPU + {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Debug|x64.ActiveCfg = Debug|Any CPU + {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Debug|x64.Build.0 = Debug|Any CPU + {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Debug|x86.ActiveCfg = Debug|Any CPU + {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Debug|x86.Build.0 = Debug|Any CPU + {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Release|Any CPU.Build.0 = Release|Any CPU + {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Release|x64.ActiveCfg = Release|Any CPU + {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Release|x64.Build.0 = Release|Any CPU + {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Release|x86.ActiveCfg = Release|Any CPU + {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Release|x86.Build.0 = Release|Any CPU + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Debug|x64.ActiveCfg = Debug|Any CPU + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Debug|x64.Build.0 = Debug|Any CPU + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Debug|x86.ActiveCfg = Debug|Any CPU + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Debug|x86.Build.0 = Debug|Any CPU + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Release|Any CPU.Build.0 = Release|Any CPU + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Release|x64.ActiveCfg = Release|Any CPU + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Release|x64.Build.0 = Release|Any CPU + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Release|x86.ActiveCfg = Release|Any CPU + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Release|x86.Build.0 = Release|Any CPU + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Debug|x64.ActiveCfg = Debug|Any CPU + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Debug|x64.Build.0 = Debug|Any CPU + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Debug|x86.ActiveCfg = Debug|Any CPU + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Debug|x86.Build.0 = Debug|Any CPU + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Release|Any CPU.Build.0 = Release|Any CPU + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Release|x64.ActiveCfg = Release|Any CPU + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Release|x64.Build.0 = Release|Any CPU + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Release|x86.ActiveCfg = Release|Any CPU + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Release|x86.Build.0 = Release|Any CPU + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Debug|x64.ActiveCfg = Debug|Any CPU + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Debug|x64.Build.0 = Debug|Any CPU + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Debug|x86.ActiveCfg = Debug|Any CPU + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Debug|x86.Build.0 = Debug|Any CPU + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Release|Any CPU.Build.0 = Release|Any CPU + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Release|x64.ActiveCfg = Release|Any CPU + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Release|x64.Build.0 = Release|Any CPU + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Release|x86.ActiveCfg = Release|Any CPU + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Release|x86.Build.0 = Release|Any CPU + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Debug|Any CPU.Build.0 = Debug|Any CPU + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Debug|x64.ActiveCfg = Debug|Any CPU + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Debug|x64.Build.0 = Debug|Any CPU + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Debug|x86.ActiveCfg = Debug|Any CPU + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Debug|x86.Build.0 = Debug|Any CPU + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Release|Any CPU.ActiveCfg = Release|Any CPU + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Release|Any CPU.Build.0 = Release|Any CPU + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Release|x64.ActiveCfg = Release|Any CPU + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Release|x64.Build.0 = Release|Any CPU + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Release|x86.ActiveCfg = Release|Any CPU + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Release|x86.Build.0 = Release|Any CPU + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Debug|x64.ActiveCfg = Debug|Any CPU + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Debug|x64.Build.0 = Debug|Any CPU + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Debug|x86.ActiveCfg = Debug|Any CPU + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Debug|x86.Build.0 = Debug|Any CPU + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Release|Any CPU.Build.0 = Release|Any CPU + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Release|x64.ActiveCfg = Release|Any CPU + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Release|x64.Build.0 = Release|Any CPU + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Release|x86.ActiveCfg = Release|Any CPU + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Release|x86.Build.0 = Release|Any CPU + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Debug|x64.ActiveCfg = Debug|Any CPU + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Debug|x64.Build.0 = Debug|Any CPU + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Debug|x86.ActiveCfg = Debug|Any CPU + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Debug|x86.Build.0 = Debug|Any CPU + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Release|Any CPU.Build.0 = Release|Any CPU + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Release|x64.ActiveCfg = Release|Any CPU + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Release|x64.Build.0 = Release|Any CPU + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Release|x86.ActiveCfg = Release|Any CPU + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Release|x86.Build.0 = Release|Any CPU + {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Debug|x64.ActiveCfg = Debug|Any CPU + {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Debug|x64.Build.0 = Debug|Any CPU + {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Debug|x86.ActiveCfg = Debug|Any CPU + {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Debug|x86.Build.0 = Debug|Any CPU + {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Release|Any CPU.Build.0 = Release|Any CPU + {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Release|x64.ActiveCfg = Release|Any CPU + {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Release|x64.Build.0 = Release|Any CPU + {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Release|x86.ActiveCfg = Release|Any CPU + {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Release|x86.Build.0 = Release|Any CPU + {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Debug|x64.ActiveCfg = Debug|Any CPU + {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Debug|x64.Build.0 = Debug|Any CPU + {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Debug|x86.ActiveCfg = Debug|Any CPU + {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Debug|x86.Build.0 = Debug|Any CPU + {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Release|Any CPU.Build.0 = Release|Any CPU + {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Release|x64.ActiveCfg = Release|Any CPU + {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Release|x64.Build.0 = Release|Any CPU + {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Release|x86.ActiveCfg = Release|Any CPU + {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Release|x86.Build.0 = Release|Any CPU + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Debug|x64.ActiveCfg = Debug|Any CPU + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Debug|x64.Build.0 = Debug|Any CPU + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Debug|x86.ActiveCfg = Debug|Any CPU + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Debug|x86.Build.0 = Debug|Any CPU + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Release|Any CPU.Build.0 = Release|Any CPU + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Release|x64.ActiveCfg = Release|Any CPU + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Release|x64.Build.0 = Release|Any CPU + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Release|x86.ActiveCfg = Release|Any CPU + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Release|x86.Build.0 = Release|Any CPU + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Debug|x64.ActiveCfg = Debug|Any CPU + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Debug|x64.Build.0 = Debug|Any CPU + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Debug|x86.ActiveCfg = Debug|Any CPU + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Debug|x86.Build.0 = Debug|Any CPU + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Release|Any CPU.Build.0 = Release|Any CPU + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Release|x64.ActiveCfg = Release|Any CPU + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Release|x64.Build.0 = Release|Any CPU + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Release|x86.ActiveCfg = Release|Any CPU + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Release|x86.Build.0 = Release|Any CPU + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Debug|x64.ActiveCfg = Debug|Any CPU + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Debug|x64.Build.0 = Debug|Any CPU + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Debug|x86.ActiveCfg = Debug|Any CPU + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Debug|x86.Build.0 = Debug|Any CPU + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Release|Any CPU.Build.0 = Release|Any CPU + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Release|x64.ActiveCfg = Release|Any CPU + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Release|x64.Build.0 = Release|Any CPU + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Release|x86.ActiveCfg = Release|Any CPU + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Release|x86.Build.0 = Release|Any CPU + {F47ED576-F347-44C4-BEC7-6C00A500555A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F47ED576-F347-44C4-BEC7-6C00A500555A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F47ED576-F347-44C4-BEC7-6C00A500555A}.Debug|x64.ActiveCfg = Debug|Any CPU + {F47ED576-F347-44C4-BEC7-6C00A500555A}.Debug|x64.Build.0 = Debug|Any CPU + {F47ED576-F347-44C4-BEC7-6C00A500555A}.Debug|x86.ActiveCfg = Debug|Any CPU + {F47ED576-F347-44C4-BEC7-6C00A500555A}.Debug|x86.Build.0 = Debug|Any CPU + {F47ED576-F347-44C4-BEC7-6C00A500555A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F47ED576-F347-44C4-BEC7-6C00A500555A}.Release|Any CPU.Build.0 = Release|Any CPU + {F47ED576-F347-44C4-BEC7-6C00A500555A}.Release|x64.ActiveCfg = Release|Any CPU + {F47ED576-F347-44C4-BEC7-6C00A500555A}.Release|x64.Build.0 = Release|Any CPU + {F47ED576-F347-44C4-BEC7-6C00A500555A}.Release|x86.ActiveCfg = Release|Any CPU + {F47ED576-F347-44C4-BEC7-6C00A500555A}.Release|x86.Build.0 = Release|Any CPU + {F55E52BD-A846-4808-9482-E4AB0921328D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F55E52BD-A846-4808-9482-E4AB0921328D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F55E52BD-A846-4808-9482-E4AB0921328D}.Debug|x64.ActiveCfg = Debug|Any CPU + {F55E52BD-A846-4808-9482-E4AB0921328D}.Debug|x64.Build.0 = Debug|Any CPU + {F55E52BD-A846-4808-9482-E4AB0921328D}.Debug|x86.ActiveCfg = Debug|Any CPU + {F55E52BD-A846-4808-9482-E4AB0921328D}.Debug|x86.Build.0 = Debug|Any CPU + {F55E52BD-A846-4808-9482-E4AB0921328D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F55E52BD-A846-4808-9482-E4AB0921328D}.Release|Any CPU.Build.0 = Release|Any CPU + {F55E52BD-A846-4808-9482-E4AB0921328D}.Release|x64.ActiveCfg = Release|Any CPU + {F55E52BD-A846-4808-9482-E4AB0921328D}.Release|x64.Build.0 = Release|Any CPU + {F55E52BD-A846-4808-9482-E4AB0921328D}.Release|x86.ActiveCfg = Release|Any CPU + {F55E52BD-A846-4808-9482-E4AB0921328D}.Release|x86.Build.0 = Release|Any CPU + {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Debug|x64.ActiveCfg = Debug|Any CPU + {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Debug|x64.Build.0 = Debug|Any CPU + {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Debug|x86.ActiveCfg = Debug|Any CPU + {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Debug|x86.Build.0 = Debug|Any CPU + {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Release|Any CPU.Build.0 = Release|Any CPU + {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Release|x64.ActiveCfg = Release|Any CPU + {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Release|x64.Build.0 = Release|Any CPU + {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Release|x86.ActiveCfg = Release|Any CPU + {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Release|x86.Build.0 = Release|Any CPU + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Debug|x64.ActiveCfg = Debug|Any CPU + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Debug|x64.Build.0 = Debug|Any CPU + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Debug|x86.ActiveCfg = Debug|Any CPU + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Debug|x86.Build.0 = Debug|Any CPU + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Release|Any CPU.Build.0 = Release|Any CPU + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Release|x64.ActiveCfg = Release|Any CPU + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Release|x64.Build.0 = Release|Any CPU + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Release|x86.ActiveCfg = Release|Any CPU + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Release|x86.Build.0 = Release|Any CPU + {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Debug|x64.ActiveCfg = Debug|Any CPU + {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Debug|x64.Build.0 = Debug|Any CPU + {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Debug|x86.ActiveCfg = Debug|Any CPU + {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Debug|x86.Build.0 = Debug|Any CPU + {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Release|Any CPU.Build.0 = Release|Any CPU + {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Release|x64.ActiveCfg = Release|Any CPU + {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Release|x64.Build.0 = Release|Any CPU + {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Release|x86.ActiveCfg = Release|Any CPU + {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Release|x86.Build.0 = Release|Any CPU + {2837A894-11D6-4DBF-B140-84C97C385A15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2837A894-11D6-4DBF-B140-84C97C385A15}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2837A894-11D6-4DBF-B140-84C97C385A15}.Debug|x64.ActiveCfg = Debug|Any CPU + {2837A894-11D6-4DBF-B140-84C97C385A15}.Debug|x64.Build.0 = Debug|Any CPU + {2837A894-11D6-4DBF-B140-84C97C385A15}.Debug|x86.ActiveCfg = Debug|Any CPU + {2837A894-11D6-4DBF-B140-84C97C385A15}.Debug|x86.Build.0 = Debug|Any CPU + {2837A894-11D6-4DBF-B140-84C97C385A15}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2837A894-11D6-4DBF-B140-84C97C385A15}.Release|Any CPU.Build.0 = Release|Any CPU + {2837A894-11D6-4DBF-B140-84C97C385A15}.Release|x64.ActiveCfg = Release|Any CPU + {2837A894-11D6-4DBF-B140-84C97C385A15}.Release|x64.Build.0 = Release|Any CPU + {2837A894-11D6-4DBF-B140-84C97C385A15}.Release|x86.ActiveCfg = Release|Any CPU + {2837A894-11D6-4DBF-B140-84C97C385A15}.Release|x86.Build.0 = Release|Any CPU + {39B9F0B5-3C53-4140-B158-96C67C03E876}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39B9F0B5-3C53-4140-B158-96C67C03E876}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39B9F0B5-3C53-4140-B158-96C67C03E876}.Debug|x64.ActiveCfg = Debug|Any CPU + {39B9F0B5-3C53-4140-B158-96C67C03E876}.Debug|x64.Build.0 = Debug|Any CPU + {39B9F0B5-3C53-4140-B158-96C67C03E876}.Debug|x86.ActiveCfg = Debug|Any CPU + {39B9F0B5-3C53-4140-B158-96C67C03E876}.Debug|x86.Build.0 = Debug|Any CPU + {39B9F0B5-3C53-4140-B158-96C67C03E876}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39B9F0B5-3C53-4140-B158-96C67C03E876}.Release|Any CPU.Build.0 = Release|Any CPU + {39B9F0B5-3C53-4140-B158-96C67C03E876}.Release|x64.ActiveCfg = Release|Any CPU + {39B9F0B5-3C53-4140-B158-96C67C03E876}.Release|x64.Build.0 = Release|Any CPU + {39B9F0B5-3C53-4140-B158-96C67C03E876}.Release|x86.ActiveCfg = Release|Any CPU + {39B9F0B5-3C53-4140-B158-96C67C03E876}.Release|x86.Build.0 = Release|Any CPU + {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Debug|x64.ActiveCfg = Debug|Any CPU + {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Debug|x64.Build.0 = Debug|Any CPU + {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Debug|x86.ActiveCfg = Debug|Any CPU + {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Debug|x86.Build.0 = Debug|Any CPU + {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Release|Any CPU.Build.0 = Release|Any CPU + {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Release|x64.ActiveCfg = Release|Any CPU + {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Release|x64.Build.0 = Release|Any CPU + {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Release|x86.ActiveCfg = Release|Any CPU + {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Release|x86.Build.0 = Release|Any CPU + {9E2178A0-5915-4308-B6C3-5C8758A47788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9E2178A0-5915-4308-B6C3-5C8758A47788}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9E2178A0-5915-4308-B6C3-5C8758A47788}.Debug|x64.ActiveCfg = Debug|Any CPU + {9E2178A0-5915-4308-B6C3-5C8758A47788}.Debug|x64.Build.0 = Debug|Any CPU + {9E2178A0-5915-4308-B6C3-5C8758A47788}.Debug|x86.ActiveCfg = Debug|Any CPU + {9E2178A0-5915-4308-B6C3-5C8758A47788}.Debug|x86.Build.0 = Debug|Any CPU + {9E2178A0-5915-4308-B6C3-5C8758A47788}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9E2178A0-5915-4308-B6C3-5C8758A47788}.Release|Any CPU.Build.0 = Release|Any CPU + {9E2178A0-5915-4308-B6C3-5C8758A47788}.Release|x64.ActiveCfg = Release|Any CPU + {9E2178A0-5915-4308-B6C3-5C8758A47788}.Release|x64.Build.0 = Release|Any CPU + {9E2178A0-5915-4308-B6C3-5C8758A47788}.Release|x86.ActiveCfg = Release|Any CPU + {9E2178A0-5915-4308-B6C3-5C8758A47788}.Release|x86.Build.0 = Release|Any CPU + {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Debug|x64.ActiveCfg = Debug|Any CPU + {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Debug|x64.Build.0 = Debug|Any CPU + {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Debug|x86.ActiveCfg = Debug|Any CPU + {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Debug|x86.Build.0 = Debug|Any CPU + {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Release|Any CPU.Build.0 = Release|Any CPU + {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Release|x64.ActiveCfg = Release|Any CPU + {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Release|x64.Build.0 = Release|Any CPU + {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Release|x86.ActiveCfg = Release|Any CPU + {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Release|x86.Build.0 = Release|Any CPU + {3761F08D-06AE-4E07-8B88-F19C465DC169}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3761F08D-06AE-4E07-8B88-F19C465DC169}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3761F08D-06AE-4E07-8B88-F19C465DC169}.Debug|x64.ActiveCfg = Debug|Any CPU + {3761F08D-06AE-4E07-8B88-F19C465DC169}.Debug|x64.Build.0 = Debug|Any CPU + {3761F08D-06AE-4E07-8B88-F19C465DC169}.Debug|x86.ActiveCfg = Debug|Any CPU + {3761F08D-06AE-4E07-8B88-F19C465DC169}.Debug|x86.Build.0 = Debug|Any CPU + {3761F08D-06AE-4E07-8B88-F19C465DC169}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3761F08D-06AE-4E07-8B88-F19C465DC169}.Release|Any CPU.Build.0 = Release|Any CPU + {3761F08D-06AE-4E07-8B88-F19C465DC169}.Release|x64.ActiveCfg = Release|Any CPU + {3761F08D-06AE-4E07-8B88-F19C465DC169}.Release|x64.Build.0 = Release|Any CPU + {3761F08D-06AE-4E07-8B88-F19C465DC169}.Release|x86.ActiveCfg = Release|Any CPU + {3761F08D-06AE-4E07-8B88-F19C465DC169}.Release|x86.Build.0 = Release|Any CPU + {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Debug|x64.ActiveCfg = Debug|Any CPU + {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Debug|x64.Build.0 = Debug|Any CPU + {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Debug|x86.ActiveCfg = Debug|Any CPU + {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Debug|x86.Build.0 = Debug|Any CPU + {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Release|Any CPU.Build.0 = Release|Any CPU + {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Release|x64.ActiveCfg = Release|Any CPU + {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Release|x64.Build.0 = Release|Any CPU + {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Release|x86.ActiveCfg = Release|Any CPU + {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Release|x86.Build.0 = Release|Any CPU + {47C888A8-6887-4FA2-A202-8653684F3651}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {47C888A8-6887-4FA2-A202-8653684F3651}.Debug|Any CPU.Build.0 = Debug|Any CPU + {47C888A8-6887-4FA2-A202-8653684F3651}.Debug|x64.ActiveCfg = Debug|Any CPU + {47C888A8-6887-4FA2-A202-8653684F3651}.Debug|x64.Build.0 = Debug|Any CPU + {47C888A8-6887-4FA2-A202-8653684F3651}.Debug|x86.ActiveCfg = Debug|Any CPU + {47C888A8-6887-4FA2-A202-8653684F3651}.Debug|x86.Build.0 = Debug|Any CPU + {47C888A8-6887-4FA2-A202-8653684F3651}.Release|Any CPU.ActiveCfg = Release|Any CPU + {47C888A8-6887-4FA2-A202-8653684F3651}.Release|Any CPU.Build.0 = Release|Any CPU + {47C888A8-6887-4FA2-A202-8653684F3651}.Release|x64.ActiveCfg = Release|Any CPU + {47C888A8-6887-4FA2-A202-8653684F3651}.Release|x64.Build.0 = Release|Any CPU + {47C888A8-6887-4FA2-A202-8653684F3651}.Release|x86.ActiveCfg = Release|Any CPU + {47C888A8-6887-4FA2-A202-8653684F3651}.Release|x86.Build.0 = Release|Any CPU + {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Debug|x64.ActiveCfg = Debug|Any CPU + {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Debug|x64.Build.0 = Debug|Any CPU + {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Debug|x86.ActiveCfg = Debug|Any CPU + {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Debug|x86.Build.0 = Debug|Any CPU + {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Release|Any CPU.Build.0 = Release|Any CPU + {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Release|x64.ActiveCfg = Release|Any CPU + {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Release|x64.Build.0 = Release|Any CPU + {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Release|x86.ActiveCfg = Release|Any CPU + {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Release|x86.Build.0 = Release|Any CPU + {63631241-3CA1-40E2-9545-01E019414E4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {63631241-3CA1-40E2-9545-01E019414E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {63631241-3CA1-40E2-9545-01E019414E4F}.Debug|x64.ActiveCfg = Debug|Any CPU + {63631241-3CA1-40E2-9545-01E019414E4F}.Debug|x64.Build.0 = Debug|Any CPU + {63631241-3CA1-40E2-9545-01E019414E4F}.Debug|x86.ActiveCfg = Debug|Any CPU + {63631241-3CA1-40E2-9545-01E019414E4F}.Debug|x86.Build.0 = Debug|Any CPU + {63631241-3CA1-40E2-9545-01E019414E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {63631241-3CA1-40E2-9545-01E019414E4F}.Release|Any CPU.Build.0 = Release|Any CPU + {63631241-3CA1-40E2-9545-01E019414E4F}.Release|x64.ActiveCfg = Release|Any CPU + {63631241-3CA1-40E2-9545-01E019414E4F}.Release|x64.Build.0 = Release|Any CPU + {63631241-3CA1-40E2-9545-01E019414E4F}.Release|x86.ActiveCfg = Release|Any CPU + {63631241-3CA1-40E2-9545-01E019414E4F}.Release|x86.Build.0 = Release|Any CPU + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Debug|x64.ActiveCfg = Debug|Any CPU + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Debug|x64.Build.0 = Debug|Any CPU + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Debug|x86.ActiveCfg = Debug|Any CPU + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Debug|x86.Build.0 = Debug|Any CPU + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Release|Any CPU.Build.0 = Release|Any CPU + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Release|x64.ActiveCfg = Release|Any CPU + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Release|x64.Build.0 = Release|Any CPU + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Release|x86.ActiveCfg = Release|Any CPU + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {2D251CFD-A9DA-421F-B516-B25873348C08} = {471CA3C0-0BC9-4C0C-A013-D9356DBD0F38} + {00D7FBF0-801B-4B0E-9FDD-5C367E71444B} = {69958BAA-5D54-4F3F-82D6-F622F9491057} + {98E964A2-55DA-4740-9F2E-B64FDF6715DB} = {69958BAA-5D54-4F3F-82D6-F622F9491057} + {B9C61145-C010-4FAA-B9EA-4D1AEE20305F} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {4FDB7710-F4CA-45FB-9514-A4D288EDA44A} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {CC37B369-BA9E-4B82-A14D-68B58924885D} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {50F86C8F-E052-448C-84CE-08763945FCC7} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {3CEF9F32-5887-4977-AB8A-1BBC48875A21} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {8797AFCA-1EE8-4270-8FC4-F93A85F9C825} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {78D2B20D-B173-4CF2-A763-F2AA5347947E} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {C0BA460C-C1AC-4C54-9C88-60E4B8681E94} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {0CDA2765-C9DF-45EE-A099-0E2D4A980E59} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {F934B760-4847-4BA8-9F6A-7F750ECF2328} = {610E8065-1F29-41E8-A3FC-07ABDF16C8BB} + {2DC38785-B7B6-49E9-84D4-103A56D3604A} = {610E8065-1F29-41E8-A3FC-07ABDF16C8BB} + {610E8065-1F29-41E8-A3FC-07ABDF16C8BB} = {F49FFE3B-E1AE-483E-951F-D498F6ABA08E} + {263CD320-FAC5-4F0C-8FFB-788DD3C455D4} = {471CA3C0-0BC9-4C0C-A013-D9356DBD0F38} + {DD87D202-998B-4C20-8D68-C769797D7761} = {F49FFE3B-E1AE-483E-951F-D498F6ABA08E} + {D1E1AEDC-4870-4863-A8D3-B6A184E651C1} = {DD87D202-998B-4C20-8D68-C769797D7761} + {7DD1722B-B971-4322-BA74-C622E808B84D} = {DD87D202-998B-4C20-8D68-C769797D7761} + {A4C90265-C922-4145-8CDC-82EC5EBBC3A4} = {DD87D202-998B-4C20-8D68-C769797D7761} + {ADF569DC-E916-4F39-8ABF-8AA42F69266E} = {DD87D202-998B-4C20-8D68-C769797D7761} + {3B0C6936-E777-4413-B68A-1686A768F928} = {F49FFE3B-E1AE-483E-951F-D498F6ABA08E} + {DFC13FA1-509C-42AF-870F-543C7A3F231B} = {3B0C6936-E777-4413-B68A-1686A768F928} + {2573526D-A764-4507-B2F6-1A2BF7A3FA93} = {3B0C6936-E777-4413-B68A-1686A768F928} + {6842C9FD-6720-43EA-83AD-DE315531DDA9} = {3B0C6936-E777-4413-B68A-1686A768F928} + {D17C19AC-EC11-4661-BD23-DC8308CF71C0} = {F49FFE3B-E1AE-483E-951F-D498F6ABA08E} + {5DBEE6E0-8744-46E4-B711-8FEC98D41A50} = {D17C19AC-EC11-4661-BD23-DC8308CF71C0} + {BF78F36A-D072-4F1C-89B1-73D9C208ABB3} = {D17C19AC-EC11-4661-BD23-DC8308CF71C0} + {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9} = {D17C19AC-EC11-4661-BD23-DC8308CF71C0} + {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65} = {D17C19AC-EC11-4661-BD23-DC8308CF71C0} + {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {704EA4BF-71D9-442B-96D9-9B3E81068CC2} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {3549E707-E4B0-428F-9CF4-5B5B86219B6D} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {1EA2BACF-3D44-4B05-A0E7-727AE13E693D} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {07E0093F-F05C-4AED-A489-1254C9854E21} = {F49FFE3B-E1AE-483E-951F-D498F6ABA08E} + {D815817F-CC0D-42CC-9C9C-8E229B9C03A8} = {07E0093F-F05C-4AED-A489-1254C9854E21} + {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4} = {07E0093F-F05C-4AED-A489-1254C9854E21} + {F47ED576-F347-44C4-BEC7-6C00A500555A} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {F55E52BD-A846-4808-9482-E4AB0921328D} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {7C7B19F2-5822-4DA2-80D2-E077CE705039} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {D5B3DA06-E60D-4C85-8835-6A26003365AA} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {2837A894-11D6-4DBF-B140-84C97C385A15} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {10109B62-DACC-42BA-AD8C-73DE4B93841B} = {F49FFE3B-E1AE-483E-951F-D498F6ABA08E} + {39B9F0B5-3C53-4140-B158-96C67C03E876} = {10109B62-DACC-42BA-AD8C-73DE4B93841B} + {703ECD55-EF0E-4734-A28E-371EDD213EEF} = {10109B62-DACC-42BA-AD8C-73DE4B93841B} + {9E2178A0-5915-4308-B6C3-5C8758A47788} = {10109B62-DACC-42BA-AD8C-73DE4B93841B} + {39BCFE6F-50C4-45AE-8D13-38FB423AB619} = {10109B62-DACC-42BA-AD8C-73DE4B93841B} + {3761F08D-06AE-4E07-8B88-F19C465DC169} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {DB02518A-49A1-4836-80B7-D69A70DE68BA} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {47C888A8-6887-4FA2-A202-8653684F3651} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {88691B89-0167-4E90-86F6-5FD6C974F7A3} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {63631241-3CA1-40E2-9545-01E019414E4F} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + {0C5BEA9A-9F07-49F5-8E84-80F3250E6516} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {B1AF41DC-A03E-47B1-BBDB-3DC27ABD9F74} + EndGlobalSection +EndGlobal diff --git a/FORK.md b/FORK.md new file mode 100644 index 0000000..5059fd8 --- /dev/null +++ b/FORK.md @@ -0,0 +1,18 @@ +# Hosihikari fork of DotNetCorePlugins + +This repository is a fork of +[natemcmaster/DotNetCorePlugins](https://github.com/natemcmaster/DotNetCorePlugins) at commit +`bcf9c8bd6d50097cfea1c3eeba132b571410fabe`, used by the Hosihikari framework. + +## Changes + +- `ManagedLoadContext.LoadUnmanagedDll` probes + `runtimes//native` before `.deps.json`-based native + resolution, so a plugin package can ship RID-specific native assets without a deps file. +- Package ID is `Hosihikari.McMaster.NETCore.Plugins`. +- `test/Hosihikari.Tests` covers the RID-specific path probe. + +## Licence + +Apache-2.0, the same as upstream. The original copyright and licence notices are retained; +modified files carry a notice. See [LICENSE.txt](LICENSE.txt). diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..c383288 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,176 @@ +Apache License +Version 2.0, January 2004 +http://www.apache.org/licenses/ + +TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + +1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + +2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + +3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + +4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + +5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + +6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + +7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + +8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + +9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + +END OF TERMS AND CONDITIONS diff --git a/README.md b/README.md new file mode 100644 index 0000000..b2b700e --- /dev/null +++ b/README.md @@ -0,0 +1,251 @@ +.NET Plugins +============ + +> **Hosihikari fork.** This fork probes `runtimes//native` before deps.json-based native +> resolution (ADR-0034). See [FORK.md](FORK.md). + +[![Build Status][ci-badge]][ci] [![Code Coverage][codecov-badge]][codecov] +[![NuGet][nuget-badge] ![NuGet Downloads][nuget-download-badge]][nuget] + +[ci]: https://github.com/natemcmaster/DotNetCorePlugins/actions?query=workflow%3ACI+branch%3Amain +[ci-badge]: https://github.com/natemcmaster/DotNetCorePlugins/workflows/CI/badge.svg +[codecov]: https://codecov.io/gh/natemcmaster/DotNetCorePlugins +[codecov-badge]: https://codecov.io/gh/natemcmaster/DotNetCorePlugins/branch/main/graph/badge.svg?token=l6uSsHZ8nA +[nuget]: https://www.nuget.org/packages/McMaster.NETCore.Plugins/ +[nuget-badge]: https://img.shields.io/nuget/v/McMaster.NETCore.Plugins.svg?style=flat-square +[nuget-download-badge]: https://img.shields.io/nuget/dt/McMaster.NETCore.Plugins?style=flat-square + + +This project provides API for loading .NET assemblies dynamically, executing them as extensions to the main application, and finding and **isolating** the dependencies of the plugin from the main application. It allows fine-grained control over +assembly isolation and type sharing. Read [more details about type sharing below](#shared-types). + +> **Note** +> +> 2.0+ of library supports .NET 8. If you still need .NET < 8 support, look for an old 1.* version of this library. + +Blog post introducing this project, July 25, 2018: [.NET Core Plugins: Introducing an API for loading .dll files (and their dependencies) as 'plugins'](https://natemcmaster.com/blog/2018/07/25/netcore-plugins/). + +**Since 2018, .NET Core 3** +was released, and it added stdlib API to improve assembly loading. If you are interested in understanding that API, see "[Create a .NET Core application with plugins][plugin-tutorial]" on docs.microsoft.com. The result of this tutorial would be a simpler version of DotNetCorePlugins, but missing some features like an API for unifying types across the load context boundary, hot reload, and .NET Core 2 support. + +[plugin-tutorial]: https://docs.microsoft.com/dotnet/core/tutorials/creating-app-with-plugin-support + +## Getting started + +Install the [`McMaster.NETCore.Plugins` NuGet package.][nuget] + +``` +dotnet add package McMaster.NETCore.Plugins +``` + +The main API to use is `PluginLoader.CreateFromAssemblyFile`. + +```csharp +PluginLoader.CreateFromAssemblyFile( + assemblyFile: "./plugins/MyPlugin/MyPlugin1.dll", + sharedTypes: new [] { typeof(IPlugin), typeof(IServiceCollection), typeof(ILogger) }, + isUnloadable: true) +``` + +* assemblyFile = the file path to the main .dll of the plugin +* sharedTypes = a list of types which the loader should ensure are unified. (See [What is a shared type?](#shared-types)) +* isUnloadable = Allow this plugin to be unloaded from memory at some point in the future. (Requires ensuring that you have cleaned up all usages of types from the plugin before unloading actually happens.) + +See example projects in [samples/](./samples/) for more detailed, example usage. + +## Usage + +Using plugins requires at least two projects: (1) the 'host' app which loads plugins and (2) the plugin, +but typically also uses a third, (3) an contracts project which defines the interaction between the plugin +and the host. + +For a fully functional sample of this, see [samples/hello-world/](./samples/hello-world/) . + +### The plugin contract + +You can define your own plugin contract. A minimal contract might look like this. + +```csharp +public interface IPlugin +{ + string GetName(); +} +``` + +There is nothing special about the name "IPlugin" or the fact that it's an interface. This is just here to illustrate a concept. Look at [samples/](./samples/) for additional examples of ways you could define the interaction between host and plugins. + +### The plugins + +> **Warning** +> +> Using `netstandard2.0` as the TargetFramework for your plugin project has known issues. Use `net8.0` instead. + +A minimal implementation of the plugin could be as simple as this. + +```csharp +internal class MyPlugin1 : IPlugin +{ + public string GetName() => "My plugin v1"; +} +``` + +As mentioned above, this is just an example. This library doesn't require the use of "IPlugin" or interfaces or "GetName()" +methods. This code is only here to demonstrates how you can decouple hosts and plugins, but still use interfaces for type-safe +interactions. + +### The host + +The host application can load plugins using the `PluginLoader` API. The host app needs to define a way to find +the assemblies for the plugin on disk. One way to do this is to follow a convention, such as: + +``` +plugins/ + $PluginName1/ + $PluginName1.dll + (additional plugin files) + $PluginName2/ + $PluginName2.dll +``` + +**It is important that each plugin is published into a separate directory.** This will avoid contention between plugins +and duplicate dependency issues. + +You can prepare the sample plugin above by running + +``` +dotnet publish MyPlugin1.csproj --output plugins/MyPlugin1/ +``` + +An implementation of a host which finds and loads this plugin might look like this. This sample uses reflection to find +all types in plugins which implement `IPlugin`, and then initializes the types' parameter-less constructors. +This is just one way to implement a host. More examples of how to use plugins can be found in [samples/](./samples/). + +```csharp +using McMaster.NETCore.Plugins; + +var loaders = new List(); + +// create plugin loaders +var pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins"); +foreach (var dir in Directory.GetDirectories(pluginsDir)) +{ + var dirName = Path.GetFileName(dir); + var pluginDll = Path.Combine(dir, dirName + ".dll"); + if (File.Exists(pluginDll)) + { + var loader = PluginLoader.CreateFromAssemblyFile( + pluginDll, + sharedTypes: new [] { typeof(IPlugin) }); + loaders.Add(loader); + } +} + +// Create an instance of plugin types +foreach (var loader in loaders) +{ + foreach (var pluginType in loader + .LoadDefaultAssembly() + .GetTypes() + .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract)) + { + // This assumes the implementation of IPlugin has a parameterless constructor + IPlugin plugin = (IPlugin)Activator.CreateInstance(pluginType); + + Console.WriteLine($"Created plugin instance '{plugin.GetName()}'."); + } +} +``` + + + +### What is a shared type? + +By default, each instance of `PluginLoader` represents a unique collection of assemblies loaded into memory. +This can make it difficult to use the plugin if you want to pass information from plugin to the host and vice versa. +Shared types allow you define the kinds of objects that will be passed between plugin and host. + +For example, let's say you have a simple host app like [samples/hello-world/](./samples/hello-world/), and +two plugins which were compiled with a reference `interface IPlugin`. This interface comes from `Contracts.dll`. +When the application runs, by default, each plugin and the host will have their own version of `Contracts.dll` +which .NET will keep isolated. + +The problem with this isolation is that an object of `IPlugin` created within the "PluginApple" or "PluginBanana" context does not appear to be an instance of `IPlugin` in any of the other plugin contexts. + +![DefaultConfigDiagram](https://i.imgur.com/fHEMBO6.png) + +Configuring a shared type of `IPlugin` allows the .NET to pass objects of this type across the plugin isolation +boundary. It does this by ignoring the version of `Contracts.dll` in each plugin folder, and sharing the version that comes with the Host. + +![SharedTypes](https://i.imgur.com/sTGqPxa.png) + +Read [even more details about shared types here](./docs/what-are-shared-types.md). + +## Support for MVC and Razor + +A common usage for plugins is to load class libraries that contain MVC controllers or Razor Pages. You can +set up an ASP.NET Core to load controllers and views from a plugin using the `McMaster.NETCore.Plugins.Mvc` +package. + +``` +dotnet add package McMaster.NETCore.Plugins.Mvc +``` + +The main API to use is `.AddPluginFromAssemblyFile()`, which can be chained onto the call to `.AddMvc()` +or `.AddRazorPages()` in the `Startup.ConfigureServices` method. + +```c# +public class Startup +{ + public void ConfigureServices(IServiceCollection services) + { + var pluginFile = Path.Combine(AppContext.BaseDirectory, "plugins/MyRazorPlugin/MyRazorPlugin.dll"); + services + .AddMvc() + // The AddPluginFromAssemblyFile method comes from McMaster.NETCore.Plugins.Mvc + .AddPluginFromAssemblyFile(pluginFile); + } +} +``` + +See example projects in [samples/aspnetcore-mvc/](./samples/aspnetcore-mvc/) for more detailed, example usage. + +## Reflection + +Sometimes you may want to use a plugin along with reflection APIs such as `Type.GetType(string typeName)` +or `Assembly.Load(string assemblyString)`. Depending on where these APIs are used, they might fail to +load the assemblies in your plugin. There is an API which you can use to set the _ambient context_ +which .NET's reflection APIs will use to load the correct assemblies from your plugin. + +Example: +```c# +var loader = PluginLoader.CreateFromAssemblyFile("./plugins/MyPlugin/MyPlugin1.dll"); + +using (loader.EnterContextualReflection()) +{ + var myPluginType = Type.GetType("MyPlugin.PluginClass"); + var myPluginAssembly = Assembly.Load("MyPlugin1"); +} + +``` + +Read [this post written by .NET Core engineers](https://github.com/dotnet/coreclr/blob/v3.0.0/Documentation/design-docs/AssemblyLoadContext.ContextualReflection.md) for even more details on contextual reflection. + +## Overriding the Default Load Context + +Under the hood, DotNetCorePlugins is using a .NET API called [ApplicationLoadContext][alc-api]. +This creates a scope for resolving assemblies. By default, `PluginLoader` will create a new context +and fallback to a **default context** if it cannot find an assembly or if type sharing is enabled. +The default fallback context is inferred when `PluginLoader` is instantiated. In certain advanced scenarios, +you may need to manually change the default context, for instance, plugins which then load more plugins, +or when running .NET in a custom native host. + +[alc-api]: https://docs.microsoft.com/dotnet/api/system.runtime.loader.assemblyloadcontext + +To override the default assembly load context, set `PluginConfig.DefaultContext`. Example: + + +```csharp +AssemblyLoadContext myCustomDefaultContext = // (something). +PluginLoader.CreateFromAssemblyFile(dllPath, + config => config.DefaultContext = myCustomDefaultContext); +``` diff --git a/build.ps1 b/build.ps1 new file mode 100644 index 0000000..5690b2b --- /dev/null +++ b/build.ps1 @@ -0,0 +1,30 @@ +#!/usr/bin/env pwsh +[CmdletBinding(PositionalBinding = $false)] +param( + [ValidateSet('Debug', 'Release')] + $Configuration = $null, + [switch] + $ci +) + +Set-StrictMode -Version 1 +$ErrorActionPreference = 'Stop' + +Import-Module -Force -Scope Local "$PSScriptRoot/src/common.psm1" + +if (!$Configuration) { + $Configuration = if ($ci) { 'Release' } else { 'Debug' } +} + +$artifacts = "$PSScriptRoot/artifacts/" + +Remove-Item -Recurse $artifacts -ErrorAction Ignore + +exec dotnet format +exec dotnet build --configuration $Configuration '-warnaserror:CS1591' +exec dotnet pack --no-restore --no-build --configuration $Configuration -o $artifacts +exec dotnet test --no-restore --no-build --configuration $Configuration ` + "$PSScriptRoot/test/Plugins.Tests/McMaster.NETCore.Plugins.Tests.csproj" ` + --collect:"XPlat Code Coverage" + +write-host -f green 'BUILD SUCCEEDED' diff --git a/docs/design-doc.md b/docs/design-doc.md new file mode 100644 index 0000000..267f818 --- /dev/null +++ b/docs/design-doc.md @@ -0,0 +1,94 @@ +.NET Core Plugins - design doc +============================== + +This repository contains a library code to support a .NET Core plugin model. + +## Synopsis + +This library is designed to provide support to .NET Core applications that wish to load assemblies on the fly and execute them as extensions to the main application. +It defines a plugin model that allows these applications to control, to some level, isolation between the extension (plugin) loaded and the +host (main application). + +## Background + +Today, implementing a plugin model requires a high level of knowledge about how assemblies are resolved and loaded. Various features in .NET Core exist to assist in this, but none of them provide an API and experience consistent with how corehost handles and loads assemblies. + +Existing features: + - System.Runtime.Loader.AssemblyLoadContext - users can implement custom assembly loading behaviors + - additionalDeps - users can craft a deps.json file which the host will load into all processes as if the dependencies were in the application's .deps.json file + +Problems with existing features: + - AssemblyLoadContext - users must implement all load behavior themselves. If users want support for deps.json files, additional probing paths, RID graphs, and more, they must implement it themselves. As corehost continually changes, these behaviors must be updated. + - additionalDeps - this feature is currently too inflexible. Applciations can control when or how additional dependencies are loaded, and also, it forces unification to a single version of a dependency. + +## What this library does + +This library provides API for .NET Core app developers to load assemblies as plugins. This API should: + + - reduce the complexity of loading assemblies + - allow plugins to define additional dependencies + - allow controlling the behavior of how types are unified between the host and plugin + - allow the host to customize how and when to load applications + - (stretch goal) allow unloading plugins (depends on unloadable AssemblyLoadContext's) + +## Implementation + +This API implements these features using: + - AssemblyLoadContext to manage assembly loading and assembly version isolation + - Microsoft.Extensions.DependencyModel to use the .deps.json and .runtimeconfig.json files to express additional dependencies +and search paths for dependencies + +## Sample usage + +A host and plugin have a shared abstraction +```c# +public interface IFruit +{ + string GetColor() +} +``` + +A host application could load plugins like this: + +```c# +// (pseudocode) +public class Program +{ + public static void Main(string[] args) + { + foreach (var pluginDirectory in Directory.GetDirectories("plugins/")) + { + var pluginDirName = Path.GetFileName(pluginDir); + var assemblyFile = Path.Combine(pluginDir, pluginDirName + ".dll"); + var loader = PluginLoader.CreateFromAssemblyFile( + assemblyFile: pluginFile, + sharedTypes: new [] { typeof(IFruit) }); + + var plugin = loader.LoadDefaultAssembly(); + foreach (var fruitType in plugin.GetTypes().Where(t => typeof(IFruit).IsAssignableFrom(t) && !t.IsAbstract)) + { + var fruit = (IFruit)Activator.CreateInstance(fruitType, new object[0]); + Console.WriteLine(fruit.GetColor()); + } + + loader.Dispose(); + } + } +} +``` + +A plugin author could implement the shared abstraction and distribute a plugin without knowing how the host will behave: + +```xml + + + netstandard2.0 + + +``` +```c# +internal class MyApple : IFruit +{ + public string GetColor() => "Red"; +} +``` diff --git a/docs/what-are-shared-types.md b/docs/what-are-shared-types.md new file mode 100644 index 0000000..4715bb9 --- /dev/null +++ b/docs/what-are-shared-types.md @@ -0,0 +1,112 @@ +# An Explanation of Shared Types + +The PluginLoader API uses the term "shared types". This document explains what it means. + +```csharp +PluginLoader.CreateFromAssemblyFile("./plugins/MyPlugin/MyPlugin1.dll", + sharedTypes: new [] { typeof(ILogger) }); + +// versus + +PluginLoader.CreateFromAssemblyFile("./plugins/MyPlugin/MyPlugin1.dll", + config => config.PreferSharedTypes = true); +``` + +## Concepts + +First, a quick overview of essential concepts. + +#### Type identity + +Type identity what makes a class/struct/enum unique. It is defined by the combination of type name (which includes its namespace), assembly name, +assembly public key token, and assembly version. You can inspect a type's identity in .NET by looking at System.Type.AssemblyQualifiedName. + +For example, + +```csharp +typeof(ILogger).AssemblyQualifiedName + => "Microsoft.Extensions.Logging.ILogger, Microsoft.Extensions.Logging.Abstractions, Version=2.2.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60" +``` + +Element | Value +---------------------------|------------------ +Type name | Microsoft.Extensions.Logging.ILogger +Assembly name | Microsoft.Extensions.Logging.Abstractions +Assembly version | 2.2.0.0 +Assembly public key token | adb9793829ddae60 + +> Simplification: we're going to ignore the "Culture" part of the fully qualified name for now. + +#### Diamond dependencies + +The [diamond dependency problem][dep-hell] is when a library A depends on libraries B and C, both B and C depend on library D, +but B requires version D.1 and C requires version D.2. + +[dep-hell]: https://en.wikipedia.org/wiki/Dependency_hell + + + +You could solve this problem by + +1. Choosing D.1 +1. Choosing D.2 +1. Choosing both + +#### Type unification (option 2) + +Type unification is .NET's solution for the diamond dependency problem. In the simple example above, +.NET's build system picks the higher version (D.2) and writes this into the application manifest +(the .deps.json or .config file in build output.) Then, when the application is running and encounters usages of D.1, .NET binds +the usage to D.2 instead. + +In other words, .NET will ignore assembly version when evaluating type identity. + +* Type name +* Assembly name +* ~~Assembly version~~ _this part gets ignored_ +* Assembly public key token + +Why is this done? It allows **type exchange** so code can share instances of types even if the code was originally compiled +with different dependency versions. + +```csharp +var instanceOfD = new D(); // D.2 +new B().DoSomethingWith(instanceOfD); // Library B was compiled to expect D.1, but type unification makes it work with D.2 +new C().DoSomethingWith(instanceOfD); +``` + +## But what if... + +There are two common problems with type unification. + +1. Breaking changes: what if library B depends on a behavior of D.1 that changed in D.2 and breaks B? Conversely, what library C uses a new API added in D.2, + but we force the app to use D.1 instead? +2. Static vs dynamic: what if my app has a plugin system with dynamic dependencies? + +## This library's answer... + +By default, this `PluginLoader` does not unify any types. This means you can have **multiple versions of the same assembly** +loaded in separate plugins. + +```csharp +PluginLoader.CreateFromAssemblyFile("./plugins/MyPlugin/MyPlugin1.dll") +``` + +This can make working with a plugin difficult because it breaks **type exchange**, so the `sharedTypes` list API +is provided to allow you to select which types you want to make sure are unified between the plugin and the +application loading the plugin (aka the host). +```csharp +PluginLoader.CreateFromAssemblyFile("./plugins/MyPlugin/MyPlugin1.dll", + sharedTypes: new [] { typeof(ILogger) }); +``` + +Finally, you can invert the default completely to **always attempt to unify** by setting `PreferSharedTypes`. In this mode, +the assembly version provided by the host uses is always used. +```csharp +PluginLoader.CreateFromAssemblyFile("./plugins/MyPlugin/MyPlugin1.dll", + config => config.PreferSharedTypes = true); + +// In older versions of the library, this API was found on PluginLoaderOptions +PluginLoader.CreateFromAssemblyFile("./plugins/MyPlugin/MyPlugin1.dll", + PluginLoaderOptions.PreferSharedTypes); +``` diff --git a/samples/aspnetcore-mvc/MvcApp/Controllers/HomeController.cs b/samples/aspnetcore-mvc/MvcApp/Controllers/HomeController.cs new file mode 100644 index 0000000..8c32c5d --- /dev/null +++ b/samples/aspnetcore-mvc/MvcApp/Controllers/HomeController.cs @@ -0,0 +1,15 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Mvc; + +namespace MvcWebApp.Controllers +{ + public class HomeController : Controller + { + public IActionResult Index() + { + return View(); + } + } +} diff --git a/samples/aspnetcore-mvc/MvcApp/MvcApp.csproj b/samples/aspnetcore-mvc/MvcApp/MvcApp.csproj new file mode 100644 index 0000000..6df2f63 --- /dev/null +++ b/samples/aspnetcore-mvc/MvcApp/MvcApp.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + + + + + + + + + + + + diff --git a/samples/aspnetcore-mvc/MvcApp/Program.cs b/samples/aspnetcore-mvc/MvcApp/Program.cs new file mode 100644 index 0000000..7d71032 --- /dev/null +++ b/samples/aspnetcore-mvc/MvcApp/Program.cs @@ -0,0 +1,20 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace MvcWebApp +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/samples/aspnetcore-mvc/MvcApp/Startup.cs b/samples/aspnetcore-mvc/MvcApp/Startup.cs new file mode 100644 index 0000000..31eb228 --- /dev/null +++ b/samples/aspnetcore-mvc/MvcApp/Startup.cs @@ -0,0 +1,37 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.DependencyInjection; + +namespace MvcWebApp +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + var mvcBuilder = services.AddMvc(); + + foreach (var dir in Directory.GetDirectories(Path.Combine(AppContext.BaseDirectory, "plugins"))) + { + var pluginFile = Path.Combine(dir, Path.GetFileName(dir) + ".dll"); + // The AddPluginFromAssemblyFile method comes from McMaster.NETCore.Plugins.Mvc + mvcBuilder.AddPluginFromAssemblyFile(pluginFile); + } + } + + public void Configure(IApplicationBuilder app) + { + app.UseDeveloperExceptionPage(); + app + .UseRouting() + .UseEndpoints(r => + { + r.MapDefaultControllerRoute(); + }); + } + } +} diff --git a/samples/aspnetcore-mvc/MvcApp/Views/Home/Index.cshtml b/samples/aspnetcore-mvc/MvcApp/Views/Home/Index.cshtml new file mode 100644 index 0000000..7d9c9c2 --- /dev/null +++ b/samples/aspnetcore-mvc/MvcApp/Views/Home/Index.cshtml @@ -0,0 +1,9 @@ +@{ + ViewData["Title"] = "Home Page"; +} + +

Hello from the web app.

+ + diff --git a/samples/aspnetcore-mvc/MvcApp/Views/_ViewImports.cshtml b/samples/aspnetcore-mvc/MvcApp/Views/_ViewImports.cshtml new file mode 100644 index 0000000..7aaace4 --- /dev/null +++ b/samples/aspnetcore-mvc/MvcApp/Views/_ViewImports.cshtml @@ -0,0 +1,2 @@ +@using MvcWebApp +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/samples/aspnetcore-mvc/MvcAppPlugin1/MvcAppPlugin1.csproj b/samples/aspnetcore-mvc/MvcAppPlugin1/MvcAppPlugin1.csproj new file mode 100644 index 0000000..1205947 --- /dev/null +++ b/samples/aspnetcore-mvc/MvcAppPlugin1/MvcAppPlugin1.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + true + + + + + + + diff --git a/samples/aspnetcore-mvc/MvcAppPlugin1/MyPluginController.cs b/samples/aspnetcore-mvc/MvcAppPlugin1/MyPluginController.cs new file mode 100644 index 0000000..9bd61a2 --- /dev/null +++ b/samples/aspnetcore-mvc/MvcAppPlugin1/MyPluginController.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Mvc; + +namespace MvcAppPlugin1 +{ + public class MyPluginController : Controller + { + public IActionResult Index() => View(); + } +} diff --git a/samples/aspnetcore-mvc/MvcAppPlugin1/Views/MyPlugin/Index.cshtml b/samples/aspnetcore-mvc/MvcAppPlugin1/Views/MyPlugin/Index.cshtml new file mode 100644 index 0000000..8f3ce3a --- /dev/null +++ b/samples/aspnetcore-mvc/MvcAppPlugin1/Views/MyPlugin/Index.cshtml @@ -0,0 +1,5 @@ +@{ + ViewData["Title"] = "Plugin1"; +} + +

Hello world from Plugin1!

diff --git a/samples/aspnetcore-mvc/README.md b/samples/aspnetcore-mvc/README.md new file mode 100644 index 0000000..123bd75 --- /dev/null +++ b/samples/aspnetcore-mvc/README.md @@ -0,0 +1,23 @@ +ASP.NET Core MVC Sample +======================= + +This sample contains 2 projects which demonstrate a simple plugin scenario. + +1. 'MvcWebApp' is an ASP.NET Core application which scans for a 'plugins' folder in its base directory and attempts to load any plugins it finds +2. 'MvcAppPlugin1' which implements MVC controllers. + +Normally, an ASP.NET Core MVC application must have a direct dependency on any assemblies +which provide controllers. However, as this sample demonstrates, an MVC application +can load controllers from a list of assemblies which is not known ahead of time when the +host app is built. + +## Running the sample + +Open a command line to this folder and run: + +``` +dotnet restore +dotnet run --project MvcApp/ +``` + +Then open diff --git a/samples/aspnetcore-mvc/aspnetcore-mvc.sln b/samples/aspnetcore-mvc/aspnetcore-mvc.sln new file mode 100644 index 0000000..3f1f511 --- /dev/null +++ b/samples/aspnetcore-mvc/aspnetcore-mvc.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcApp", "MvcApp\MvcApp.csproj", "{4F363469-7783-4A40-B1B5-455187AF1C76}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcAppPlugin1", "MvcAppPlugin1\MvcAppPlugin1.csproj", "{B6316996-A470-470F-9CF1-475096A0510A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4F363469-7783-4A40-B1B5-455187AF1C76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4F363469-7783-4A40-B1B5-455187AF1C76}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4F363469-7783-4A40-B1B5-455187AF1C76}.Debug|x64.ActiveCfg = Debug|Any CPU + {4F363469-7783-4A40-B1B5-455187AF1C76}.Debug|x64.Build.0 = Debug|Any CPU + {4F363469-7783-4A40-B1B5-455187AF1C76}.Debug|x86.ActiveCfg = Debug|Any CPU + {4F363469-7783-4A40-B1B5-455187AF1C76}.Debug|x86.Build.0 = Debug|Any CPU + {4F363469-7783-4A40-B1B5-455187AF1C76}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4F363469-7783-4A40-B1B5-455187AF1C76}.Release|Any CPU.Build.0 = Release|Any CPU + {4F363469-7783-4A40-B1B5-455187AF1C76}.Release|x64.ActiveCfg = Release|Any CPU + {4F363469-7783-4A40-B1B5-455187AF1C76}.Release|x64.Build.0 = Release|Any CPU + {4F363469-7783-4A40-B1B5-455187AF1C76}.Release|x86.ActiveCfg = Release|Any CPU + {4F363469-7783-4A40-B1B5-455187AF1C76}.Release|x86.Build.0 = Release|Any CPU + {B6316996-A470-470F-9CF1-475096A0510A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B6316996-A470-470F-9CF1-475096A0510A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B6316996-A470-470F-9CF1-475096A0510A}.Debug|x64.ActiveCfg = Debug|Any CPU + {B6316996-A470-470F-9CF1-475096A0510A}.Debug|x64.Build.0 = Debug|Any CPU + {B6316996-A470-470F-9CF1-475096A0510A}.Debug|x86.ActiveCfg = Debug|Any CPU + {B6316996-A470-470F-9CF1-475096A0510A}.Debug|x86.Build.0 = Debug|Any CPU + {B6316996-A470-470F-9CF1-475096A0510A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B6316996-A470-470F-9CF1-475096A0510A}.Release|Any CPU.Build.0 = Release|Any CPU + {B6316996-A470-470F-9CF1-475096A0510A}.Release|x64.ActiveCfg = Release|Any CPU + {B6316996-A470-470F-9CF1-475096A0510A}.Release|x64.Build.0 = Release|Any CPU + {B6316996-A470-470F-9CF1-475096A0510A}.Release|x86.ActiveCfg = Release|Any CPU + {B6316996-A470-470F-9CF1-475096A0510A}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/samples/aspnetcore/Abstractions/Abstractions.csproj b/samples/aspnetcore/Abstractions/Abstractions.csproj new file mode 100644 index 0000000..ea8d303 --- /dev/null +++ b/samples/aspnetcore/Abstractions/Abstractions.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/samples/aspnetcore/Abstractions/IPlugin.cs b/samples/aspnetcore/Abstractions/IPlugin.cs new file mode 100644 index 0000000..a9e7744 --- /dev/null +++ b/samples/aspnetcore/Abstractions/IPlugin.cs @@ -0,0 +1,14 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; + +namespace Plugin.Abstractions +{ + public interface IWebPlugin + { + void Configure(IApplicationBuilder appBuilder); + void ConfigureServices(IServiceCollection services); + } +} diff --git a/samples/aspnetcore/Abstractions/IPluginLink.cs b/samples/aspnetcore/Abstractions/IPluginLink.cs new file mode 100644 index 0000000..f9e6ba4 --- /dev/null +++ b/samples/aspnetcore/Abstractions/IPluginLink.cs @@ -0,0 +1,10 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Plugin.Abstractions +{ + public interface IPluginLink + { + string GetHref(); + } +} diff --git a/samples/aspnetcore/MainWebApp/MainWebApp.csproj b/samples/aspnetcore/MainWebApp/MainWebApp.csproj new file mode 100644 index 0000000..55c7e92 --- /dev/null +++ b/samples/aspnetcore/MainWebApp/MainWebApp.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + + + + + + + + + + + + + + + + + + + + diff --git a/samples/aspnetcore/MainWebApp/Pages/Index.cshtml b/samples/aspnetcore/MainWebApp/Pages/Index.cshtml new file mode 100644 index 0000000..d0eaa7b --- /dev/null +++ b/samples/aspnetcore/MainWebApp/Pages/Index.cshtml @@ -0,0 +1,11 @@ +@page +@using MainWebApp +@model IndexModel +
    +@foreach (var link in Model.Links) +{ +
  • + @link +
  • +} +
diff --git a/samples/aspnetcore/MainWebApp/Pages/Index.cshtml.cs b/samples/aspnetcore/MainWebApp/Pages/Index.cshtml.cs new file mode 100644 index 0000000..e0a6dfc --- /dev/null +++ b/samples/aspnetcore/MainWebApp/Pages/Index.cshtml.cs @@ -0,0 +1,24 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using System.Linq; +using Microsoft.AspNetCore.Mvc.RazorPages; +using Plugin.Abstractions; + +namespace MainWebApp +{ + public class IndexModel : PageModel + { + public IndexModel(IEnumerable pluginLinks) + { + Links = pluginLinks.Select(p => p.GetHref()).ToArray(); + } + + public string[] Links { get; } + + public void OnGet() + { + } + } +} diff --git a/samples/aspnetcore/MainWebApp/Program.cs b/samples/aspnetcore/MainWebApp/Program.cs new file mode 100644 index 0000000..300580a --- /dev/null +++ b/samples/aspnetcore/MainWebApp/Program.cs @@ -0,0 +1,20 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; + +namespace MainWebApp +{ + public class Program + { + public static void Main(string[] args) + { + CreateWebHostBuilder(args).Build().Run(); + } + + public static IWebHostBuilder CreateWebHostBuilder(string[] args) => + WebHost.CreateDefaultBuilder(args) + .UseStartup(); + } +} diff --git a/samples/aspnetcore/MainWebApp/Startup.cs b/samples/aspnetcore/MainWebApp/Startup.cs new file mode 100644 index 0000000..7cc94f5 --- /dev/null +++ b/samples/aspnetcore/MainWebApp/Startup.cs @@ -0,0 +1,68 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using McMaster.NETCore.Plugins; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Plugin.Abstractions; + +namespace MainWebApp +{ + public class Startup + { + private readonly List _plugins = new(); + + public Startup() + { + foreach (var pluginDir in Directory.GetDirectories(Path.Combine(AppContext.BaseDirectory, "plugins"))) + { + var dirName = Path.GetFileName(pluginDir); + var pluginFile = Path.Combine(pluginDir, dirName + ".dll"); + var loader = PluginLoader.CreateFromAssemblyFile(pluginFile, + // this ensures that the plugin resolves to the same version of DependencyInjection + // and ASP.NET Core that the current app uses + sharedTypes: new[] + { + typeof(IApplicationBuilder), + typeof(IWebPlugin), + typeof(IServiceCollection), + }); + foreach (var type in loader.LoadDefaultAssembly() + .GetTypes() + .Where(t => typeof(IWebPlugin).IsAssignableFrom(t) && !t.IsAbstract)) + { + Console.WriteLine("Found plugin " + type.Name); + var plugin = (IWebPlugin)Activator.CreateInstance(type)!; + _plugins.Add(plugin); + } + } + } + + public void ConfigureServices(IServiceCollection services) + { + services.AddMvc().AddMvcOptions(o => o.EnableEndpointRouting = false); + + foreach (var plugin in _plugins) + { + plugin.ConfigureServices(services); + } + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + app.UseDeveloperExceptionPage(); + + foreach (var plugin in _plugins) + { + plugin.Configure(app); + } + + app.UseMvcWithDefaultRoute(); + } + } +} diff --git a/samples/aspnetcore/README.md b/samples/aspnetcore/README.md new file mode 100644 index 0000000..c174ac1 --- /dev/null +++ b/samples/aspnetcore/README.md @@ -0,0 +1,42 @@ +ASP.NET Core Sample +=================== + +This sample contains 4 projects which demonstrate a simple plugin scenario. + +1. 'Abstractions' defines common interfaces shared by the web application (host) and plugins +2. 'MainWebApp' is an ASP.NET Core application which scans for a 'plugins' folder in its base directory and attempts to load any plugins it finds +3. 'WebAppPlugin1' references 'Abstractions' and implements `IWebPlugin`. This plugin has a dependency on [AutoMapper](https://www.nuget.org/packages/AutoMapper/) version 6. +4. 'WebAppPlugin2' is the same as plugin1, but it uses AutoMapper version 7. + +Normally, in .NET Core applications you cannot reference two different versions of the same assembly. +However, as this sample demonstrates, using .NET Core plugins you can load and use two different versions. + +* http://localhost:5000/plugin/v1 responds with +``` +This plugin uses AutoMapper, Version=6.2.2.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005 +``` + +* http://localhost:5000/plugin/v2 responds with +``` +This plugin uses AutoMapper, Version=7.0.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005 +``` + +There are some important types, however, which must share the same identity between the plugins and the host. +To ensure type exchange works between the host and the plugins, the MainWebApp project uses the `sharedTypes` +parameter on `PluginLoader.CreateFromAssemblyFile`. + +```csharp + var loader = PluginLoader.CreateFromAssemblyFile( + pluginAssembly, + sharedTypes: new[] + { + typeof(IApplicationBuilder), + typeof(IWebPlugin), + typeof(IServiceCollection), + }); +``` + +This is important because the plugins in this sample are compiled for ASP.NET Core 2.0 interfaces, +but the MainWebApp uses ASP.NET Core 2.1. If not for this parameter, the plugins would also attempt to use +a private copy of the ASP.NET Core implementations and type exchange between the plugin and the web app +would fail to resolve `IApplicationBuilder` and `IServiceCollection` as the same type. diff --git a/samples/aspnetcore/WebAppPlugin1/WebAppPlugin1.csproj b/samples/aspnetcore/WebAppPlugin1/WebAppPlugin1.csproj new file mode 100644 index 0000000..683920f --- /dev/null +++ b/samples/aspnetcore/WebAppPlugin1/WebAppPlugin1.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + + + + + + + + diff --git a/samples/aspnetcore/WebAppPlugin1/WebPlugin1.cs b/samples/aspnetcore/WebAppPlugin1/WebPlugin1.cs new file mode 100644 index 0000000..a3c956e --- /dev/null +++ b/samples/aspnetcore/WebAppPlugin1/WebPlugin1.cs @@ -0,0 +1,32 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Plugin.Abstractions; + +namespace Plugin1 +{ + internal class WebPlugin1 : IWebPlugin, IPluginLink + { + public string GetHref() => "/plugin/v1"; + + public void ConfigureServices(IServiceCollection services) + { + services.AddScoped(); + } + + public void Configure(IApplicationBuilder appBuilder) + { + appBuilder.Map("/plugin/v1", c => + { + var autoMapperType = typeof(AutoMapper.IMapper).Assembly; + c.Run(async (ctx) => + { + await ctx.Response.WriteAsync("This plugin uses " + autoMapperType.GetName().ToString()); + }); + }); + } + } +} diff --git a/samples/aspnetcore/WebAppPlugin2/WebAppPlugin2.csproj b/samples/aspnetcore/WebAppPlugin2/WebAppPlugin2.csproj new file mode 100644 index 0000000..3de01e7 --- /dev/null +++ b/samples/aspnetcore/WebAppPlugin2/WebAppPlugin2.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + + + + + + + + diff --git a/samples/aspnetcore/WebAppPlugin2/WebPlugin2.cs b/samples/aspnetcore/WebAppPlugin2/WebPlugin2.cs new file mode 100644 index 0000000..13d2bf5 --- /dev/null +++ b/samples/aspnetcore/WebAppPlugin2/WebPlugin2.cs @@ -0,0 +1,32 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.DependencyInjection; +using Plugin.Abstractions; + +namespace Plugin2 +{ + internal class WebPlugin2 : IWebPlugin, IPluginLink + { + public string GetHref() => "/plugin/v2"; + + public void ConfigureServices(IServiceCollection services) + { + services.AddScoped(); + } + + public void Configure(IApplicationBuilder appBuilder) + { + appBuilder.Map("/plugin/v2", c => + { + var autoMapperType = typeof(AutoMapper.IMapper).Assembly; + c.Run(async (ctx) => + { + await ctx.Response.WriteAsync("This plugin uses " + autoMapperType.GetName().ToString()); + }); + }); + } + } +} diff --git a/samples/aspnetcore/aspnetcore.sln b/samples/aspnetcore/aspnetcore.sln new file mode 100644 index 0000000..4207936 --- /dev/null +++ b/samples/aspnetcore/aspnetcore.sln @@ -0,0 +1,76 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abstractions", "Abstractions\Abstractions.csproj", "{7CDF7B07-F103-4C22-9BB3-26B7C11716FF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MainWebApp", "MainWebApp\MainWebApp.csproj", "{781A86B1-C278-44CD-997B-1AA26D6BFB38}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAppPlugin1", "WebAppPlugin1\WebAppPlugin1.csproj", "{B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAppPlugin2", "WebAppPlugin2\WebAppPlugin2.csproj", "{ECAB37EC-1E82-4B1F-8C51-983E46A557A4}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Debug|x64.ActiveCfg = Debug|Any CPU + {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Debug|x64.Build.0 = Debug|Any CPU + {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Debug|x86.ActiveCfg = Debug|Any CPU + {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Debug|x86.Build.0 = Debug|Any CPU + {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Release|Any CPU.Build.0 = Release|Any CPU + {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Release|x64.ActiveCfg = Release|Any CPU + {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Release|x64.Build.0 = Release|Any CPU + {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Release|x86.ActiveCfg = Release|Any CPU + {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Release|x86.Build.0 = Release|Any CPU + {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Debug|Any CPU.Build.0 = Debug|Any CPU + {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Debug|x64.ActiveCfg = Debug|Any CPU + {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Debug|x64.Build.0 = Debug|Any CPU + {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Debug|x86.ActiveCfg = Debug|Any CPU + {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Debug|x86.Build.0 = Debug|Any CPU + {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Release|Any CPU.ActiveCfg = Release|Any CPU + {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Release|Any CPU.Build.0 = Release|Any CPU + {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Release|x64.ActiveCfg = Release|Any CPU + {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Release|x64.Build.0 = Release|Any CPU + {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Release|x86.ActiveCfg = Release|Any CPU + {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Release|x86.Build.0 = Release|Any CPU + {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Debug|x64.ActiveCfg = Debug|Any CPU + {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Debug|x64.Build.0 = Debug|Any CPU + {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Debug|x86.ActiveCfg = Debug|Any CPU + {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Debug|x86.Build.0 = Debug|Any CPU + {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Release|Any CPU.Build.0 = Release|Any CPU + {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Release|x64.ActiveCfg = Release|Any CPU + {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Release|x64.Build.0 = Release|Any CPU + {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Release|x86.ActiveCfg = Release|Any CPU + {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Release|x86.Build.0 = Release|Any CPU + {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Debug|x64.ActiveCfg = Debug|Any CPU + {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Debug|x64.Build.0 = Debug|Any CPU + {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Debug|x86.ActiveCfg = Debug|Any CPU + {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Debug|x86.Build.0 = Debug|Any CPU + {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Release|Any CPU.Build.0 = Release|Any CPU + {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Release|x64.ActiveCfg = Release|Any CPU + {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Release|x64.Build.0 = Release|Any CPU + {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Release|x86.ActiveCfg = Release|Any CPU + {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/samples/dependency-injection/DI.HostApp/DI.HostApp.csproj b/samples/dependency-injection/DI.HostApp/DI.HostApp.csproj new file mode 100644 index 0000000..b6ff6ad --- /dev/null +++ b/samples/dependency-injection/DI.HostApp/DI.HostApp.csproj @@ -0,0 +1,26 @@ + + + + Exe + net8.0 + + + + + + + + + + + + + + + + + + diff --git a/samples/dependency-injection/DI.HostApp/Program.cs b/samples/dependency-injection/DI.HostApp/Program.cs new file mode 100644 index 0000000..cc4cbd9 --- /dev/null +++ b/samples/dependency-injection/DI.HostApp/Program.cs @@ -0,0 +1,69 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using McMaster.NETCore.Plugins; +using Microsoft.Extensions.DependencyInjection; + +namespace DependencyInjection +{ + public class Program + { + public static void Main(string[] args) + { + var services = new ServiceCollection(); + var loaders = GetPluginLoaders(); + + ConfigureServices(services, loaders); + + using var serviceProvider = services.BuildServiceProvider(); + + var consumer = serviceProvider.GetRequiredService(); + consumer.Consume(); + } + + private static List GetPluginLoaders() + { + var loaders = new List(); + + // create plugin loaders + var pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins"); + foreach (var dir in Directory.GetDirectories(pluginsDir)) + { + var dirName = Path.GetFileName(dir); + var pluginDll = Path.Combine(dir, dirName + ".dll"); + if (File.Exists(pluginDll)) + { + var loader = PluginLoader.CreateFromAssemblyFile( + pluginDll, + sharedTypes: new[] { typeof(IPluginFactory), typeof(IServiceCollection) }); + loaders.Add(loader); + } + } + + return loaders; + } + + private static void ConfigureServices(ServiceCollection services, List loaders) + { + // Create an instance of plugin types + foreach (var loader in loaders) + { + foreach (var pluginType in loader + .LoadDefaultAssembly() + .GetTypes() + .Where(t => typeof(IPluginFactory).IsAssignableFrom(t) && !t.IsAbstract)) + { + // This assumes the implementation of IPluginFactory has a parameterless constructor + var plugin = Activator.CreateInstance(pluginType) as IPluginFactory; + + plugin?.Configure(services); + } + } + } + + } +} diff --git a/samples/dependency-injection/DI.SharedAbstractions/DI.SharedAbstractions.csproj b/samples/dependency-injection/DI.SharedAbstractions/DI.SharedAbstractions.csproj new file mode 100644 index 0000000..ba25dfa --- /dev/null +++ b/samples/dependency-injection/DI.SharedAbstractions/DI.SharedAbstractions.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/samples/dependency-injection/DI.SharedAbstractions/Fruit.cs b/samples/dependency-injection/DI.SharedAbstractions/Fruit.cs new file mode 100644 index 0000000..d7ad871 --- /dev/null +++ b/samples/dependency-injection/DI.SharedAbstractions/Fruit.cs @@ -0,0 +1,15 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace DependencyInjection +{ + public class Fruit + { + public Fruit(string name) + { + Name = name; + } + + public string Name { get; } + } +} diff --git a/samples/dependency-injection/DI.SharedAbstractions/IFruitConsumer.cs b/samples/dependency-injection/DI.SharedAbstractions/IFruitConsumer.cs new file mode 100644 index 0000000..aec332b --- /dev/null +++ b/samples/dependency-injection/DI.SharedAbstractions/IFruitConsumer.cs @@ -0,0 +1,10 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace DependencyInjection +{ + public interface IFruitConsumer + { + void Consume(); + } +} diff --git a/samples/dependency-injection/DI.SharedAbstractions/IFruitProducer.cs b/samples/dependency-injection/DI.SharedAbstractions/IFruitProducer.cs new file mode 100644 index 0000000..5fa9f44 --- /dev/null +++ b/samples/dependency-injection/DI.SharedAbstractions/IFruitProducer.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace DependencyInjection +{ + public interface IFruitProducer + { + IEnumerable Produce(); + } +} diff --git a/samples/dependency-injection/DI.SharedAbstractions/IPluginFactory.cs b/samples/dependency-injection/DI.SharedAbstractions/IPluginFactory.cs new file mode 100644 index 0000000..bbfbfa7 --- /dev/null +++ b/samples/dependency-injection/DI.SharedAbstractions/IPluginFactory.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.DependencyInjection; + +namespace DependencyInjection +{ + public interface IPluginFactory + { + void Configure(IServiceCollection services); + } +} diff --git a/samples/dependency-injection/MyPlugin1/MyFruitProducer.cs b/samples/dependency-injection/MyPlugin1/MyFruitProducer.cs new file mode 100644 index 0000000..a923d7a --- /dev/null +++ b/samples/dependency-injection/MyPlugin1/MyFruitProducer.cs @@ -0,0 +1,18 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using DependencyInjection; + +namespace MyPlugin1 +{ + internal class MyFruitProducer : IFruitProducer + { + public IEnumerable Produce() + { + yield return new Fruit("banana"); + yield return new Fruit("orange"); + yield return new Fruit("strawberry"); + } + } +} diff --git a/samples/dependency-injection/MyPlugin1/MyPlugin1.csproj b/samples/dependency-injection/MyPlugin1/MyPlugin1.csproj new file mode 100644 index 0000000..9447b64 --- /dev/null +++ b/samples/dependency-injection/MyPlugin1/MyPlugin1.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/samples/dependency-injection/MyPlugin1/PluginConfiguration.cs b/samples/dependency-injection/MyPlugin1/PluginConfiguration.cs new file mode 100644 index 0000000..ae780cd --- /dev/null +++ b/samples/dependency-injection/MyPlugin1/PluginConfiguration.cs @@ -0,0 +1,16 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace MyPlugin1 +{ + public class PluginConfiguration : IPluginFactory + { + public void Configure(IServiceCollection services) + { + services.AddSingleton(); + } + } +} diff --git a/samples/dependency-injection/MyPlugin2/MyFruitConsumer.cs b/samples/dependency-injection/MyPlugin2/MyFruitConsumer.cs new file mode 100644 index 0000000..3ccfc65 --- /dev/null +++ b/samples/dependency-injection/MyPlugin2/MyFruitConsumer.cs @@ -0,0 +1,30 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using DependencyInjection; + +namespace MyPlugin2 +{ + internal class MyFruitConsumer : IFruitConsumer + { + private readonly IEnumerable _producers; + + public MyFruitConsumer(IEnumerable producers) + { + _producers = producers; + } + + public void Consume() + { + foreach (var producer in _producers) + { + foreach (var fruit in producer.Produce()) + { + Console.WriteLine($"Consumed {fruit.Name}"); + } + } + } + } +} diff --git a/samples/dependency-injection/MyPlugin2/MyPlugin2.csproj b/samples/dependency-injection/MyPlugin2/MyPlugin2.csproj new file mode 100644 index 0000000..9447b64 --- /dev/null +++ b/samples/dependency-injection/MyPlugin2/MyPlugin2.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/samples/dependency-injection/MyPlugin2/PluginConfiguration.cs b/samples/dependency-injection/MyPlugin2/PluginConfiguration.cs new file mode 100644 index 0000000..42e29a8 --- /dev/null +++ b/samples/dependency-injection/MyPlugin2/PluginConfiguration.cs @@ -0,0 +1,16 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using DependencyInjection; +using Microsoft.Extensions.DependencyInjection; + +namespace MyPlugin2 +{ + public class PluginConfiguration : IPluginFactory + { + public void Configure(IServiceCollection services) + { + services.AddSingleton(); + } + } +} diff --git a/samples/dependency-injection/README.md b/samples/dependency-injection/README.md new file mode 100644 index 0000000..bef3b56 --- /dev/null +++ b/samples/dependency-injection/README.md @@ -0,0 +1,18 @@ +Dependency Injection Sample +=========================== + +This sample contains 4 projects which demonstrate a plugin scenario which coordinates types between +plugins using a dependency injection container. + +* 'DI.HostApp' is a console application which scans for a 'plugins' folder in its base directory and attempts to load any plugins it finds. It then configures plugins in a dependency injection collection. +* 'DI.SharedAbstractions' which contains an interface shared by plugins and the host. +* 'MyPlugin1' and 'MyPlugin2' implement shared abstractions and register them with the host. + +## Running the sample + +Open a command line to this folder and run: + +``` +dotnet restore +dotnet run --project DI.HostApp/ +``` diff --git a/samples/dynamic-implementation/Contracts/Contracts.csproj b/samples/dynamic-implementation/Contracts/Contracts.csproj new file mode 100644 index 0000000..b68338c --- /dev/null +++ b/samples/dynamic-implementation/Contracts/Contracts.csproj @@ -0,0 +1,12 @@ + + + + Library + net8.0 + + + + + + + diff --git a/samples/dynamic-implementation/Contracts/Fruit.cs b/samples/dynamic-implementation/Contracts/Fruit.cs new file mode 100644 index 0000000..0439f4d --- /dev/null +++ b/samples/dynamic-implementation/Contracts/Fruit.cs @@ -0,0 +1,10 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Contracts +{ + public class Fruit + { + public string? Name { get; set; } + } +} diff --git a/samples/dynamic-implementation/Contracts/IFruitService.cs b/samples/dynamic-implementation/Contracts/IFruitService.cs new file mode 100644 index 0000000..2313606 --- /dev/null +++ b/samples/dynamic-implementation/Contracts/IFruitService.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; + +namespace Contracts +{ + public interface IFruitService + { + List GetFruits(); + } +} diff --git a/samples/dynamic-implementation/Contracts/IMixerService.cs b/samples/dynamic-implementation/Contracts/IMixerService.cs new file mode 100644 index 0000000..44fc8ac --- /dev/null +++ b/samples/dynamic-implementation/Contracts/IMixerService.cs @@ -0,0 +1,10 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Contracts +{ + public interface IMixerService + { + string MixIt(); + } +} diff --git a/samples/dynamic-implementation/Contracts/IPluginFactory.cs b/samples/dynamic-implementation/Contracts/IPluginFactory.cs new file mode 100644 index 0000000..2887a23 --- /dev/null +++ b/samples/dynamic-implementation/Contracts/IPluginFactory.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.Extensions.DependencyInjection; + +namespace Contracts +{ + public interface IPluginFactory + { + void Configure(IServiceCollection services); + } +} diff --git a/samples/dynamic-implementation/DynamicImplementation.sln b/samples/dynamic-implementation/DynamicImplementation.sln new file mode 100644 index 0000000..71ebcb5 --- /dev/null +++ b/samples/dynamic-implementation/DynamicImplementation.sln @@ -0,0 +1,43 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.757 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Host", "Host\Host.csproj", "{52016A46-23E0-48BB-AB79-CA13BECB55DF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "Contracts\Contracts.csproj", "{138FD514-7F88-43A5-A3CD-6084ABA316CD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceImplementation", "ServiceImplementation\ServiceImplementation.csproj", "{A8104C41-1224-4626-A812-ED561BD35432}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mixer", "Mixer\Mixer.csproj", "{DB9CE54F-D2A1-454A-BF58-19F41D4F3E9B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {52016A46-23E0-48BB-AB79-CA13BECB55DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {52016A46-23E0-48BB-AB79-CA13BECB55DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {52016A46-23E0-48BB-AB79-CA13BECB55DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {52016A46-23E0-48BB-AB79-CA13BECB55DF}.Release|Any CPU.Build.0 = Release|Any CPU + {138FD514-7F88-43A5-A3CD-6084ABA316CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {138FD514-7F88-43A5-A3CD-6084ABA316CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {138FD514-7F88-43A5-A3CD-6084ABA316CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {138FD514-7F88-43A5-A3CD-6084ABA316CD}.Release|Any CPU.Build.0 = Release|Any CPU + {A8104C41-1224-4626-A812-ED561BD35432}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A8104C41-1224-4626-A812-ED561BD35432}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A8104C41-1224-4626-A812-ED561BD35432}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A8104C41-1224-4626-A812-ED561BD35432}.Release|Any CPU.Build.0 = Release|Any CPU + {DB9CE54F-D2A1-454A-BF58-19F41D4F3E9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DB9CE54F-D2A1-454A-BF58-19F41D4F3E9B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DB9CE54F-D2A1-454A-BF58-19F41D4F3E9B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DB9CE54F-D2A1-454A-BF58-19F41D4F3E9B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {98A28D5B-F00C-44F0-A5DA-80A16E551E57} + EndGlobalSection +EndGlobal diff --git a/samples/dynamic-implementation/Host/Host.csproj b/samples/dynamic-implementation/Host/Host.csproj new file mode 100644 index 0000000..0aca3d8 --- /dev/null +++ b/samples/dynamic-implementation/Host/Host.csproj @@ -0,0 +1,28 @@ + + + + Exe + net8.0 + + + + + + + + + + + + + + + + + + + + diff --git a/samples/dynamic-implementation/Host/Program.cs b/samples/dynamic-implementation/Host/Program.cs new file mode 100644 index 0000000..9d6dbe9 --- /dev/null +++ b/samples/dynamic-implementation/Host/Program.cs @@ -0,0 +1,69 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Contracts; +using McMaster.NETCore.Plugins; +using Microsoft.Extensions.DependencyInjection; + +namespace Host +{ + internal class Program + { + public static void Main(string[] args) + { + var services = new ServiceCollection(); + var loaders = GetPluginLoaders(); + + ConfigureServices(services, loaders); + + var serviceProvider = services.BuildServiceProvider(); + + var mixer = serviceProvider.GetRequiredService(); + mixer.MixIt(); + } + + private static List GetPluginLoaders() + { + var loaders = new List(); + + // create plugin loaders + var pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins"); + foreach (var dir in Directory.GetDirectories(pluginsDir)) + { + var dirName = Path.GetFileName(dir); + var pluginDll = Path.Combine(dir, dirName + ".dll"); + if (File.Exists(pluginDll)) + { + var loader = PluginLoader.CreateFromAssemblyFile( + pluginDll, + sharedTypes: new[] { typeof(IPluginFactory), typeof(IServiceCollection) }); + loaders.Add(loader); + } + } + + return loaders; + } + + private static void ConfigureServices(ServiceCollection services, List loaders) + { + // Create an instance of plugin types + foreach (var loader in loaders) + { + foreach (var pluginType in loader + .LoadDefaultAssembly() + .GetTypes() + .Where(t => typeof(IPluginFactory).IsAssignableFrom(t) && !t.IsAbstract)) + { + // This assumes the implementation of IPluginFactory has a parameterless constructor + var plugin = Activator.CreateInstance(pluginType) as IPluginFactory; + + plugin?.Configure(services); + } + } + } + } +} diff --git a/samples/dynamic-implementation/Mixer/Mixer.csproj b/samples/dynamic-implementation/Mixer/Mixer.csproj new file mode 100644 index 0000000..485d8bc --- /dev/null +++ b/samples/dynamic-implementation/Mixer/Mixer.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + Library + + + + + + + diff --git a/samples/dynamic-implementation/Mixer/MixerPlugin.cs b/samples/dynamic-implementation/Mixer/MixerPlugin.cs new file mode 100644 index 0000000..2de93df --- /dev/null +++ b/samples/dynamic-implementation/Mixer/MixerPlugin.cs @@ -0,0 +1,17 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Contracts; +using Microsoft.Extensions.DependencyInjection; + +namespace Mixer +{ + public class MixerPluginConfiguration : IPluginFactory + { + public void Configure(IServiceCollection services) + { + services.AddSingleton(); + services.AddSingleton(); + } + } +} diff --git a/samples/dynamic-implementation/Mixer/MixerService.cs b/samples/dynamic-implementation/Mixer/MixerService.cs new file mode 100644 index 0000000..c494703 --- /dev/null +++ b/samples/dynamic-implementation/Mixer/MixerService.cs @@ -0,0 +1,22 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using Contracts; + +namespace Mixer +{ + public class MixerService : IMixerService + { + protected IFruitService fruit; + public MixerService(IFruitService fruit) + { + this.fruit = fruit; + } + + public string MixIt() + { + return string.Join(",", fruit.GetFruits().Select(x => x.Name).ToArray()); + } + } +} diff --git a/samples/dynamic-implementation/Mixer/StandardFruiteService.cs b/samples/dynamic-implementation/Mixer/StandardFruiteService.cs new file mode 100644 index 0000000..cff7fe5 --- /dev/null +++ b/samples/dynamic-implementation/Mixer/StandardFruiteService.cs @@ -0,0 +1,21 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Contracts; + +namespace Mixer +{ + public class StandardFruiteService : IFruitService + { + public List GetFruits() + { + return new List() + { + new Fruit { Name="Banana" }, + new Fruit { Name="Apple" }, + new Fruit { Name="Carrot" }, + }; + } + } +} diff --git a/samples/dynamic-implementation/README.md b/samples/dynamic-implementation/README.md new file mode 100644 index 0000000..a3a2a14 --- /dev/null +++ b/samples/dynamic-implementation/README.md @@ -0,0 +1,21 @@ +Dynamic implementation +=========================== + +This sample contains 4 projects which demonstrate a plugin scenario that coordinates types between +plugins using a dependency injection container. In this scenario, one implements a default service definition (Mixer), but adding a plugin the normal behavior is changed. + +This sample can be useful to create a pluggable application that can be extended or altered just by adding new modules. + +* 'Host': console app that contains the sample +* 'Contracts': which contains an interface shared by plugins and the host. +* 'Mixer': this plugin contains the default mixer implementation. It contains the FruitService, which gives 3 fruits and the Mixer service that returns the fruit shake. +* 'ServiceImplementation': this plugin contains an alternative version of the FruitService. By adding this to the plugin set, the default behavior is altered and the shake is composed by only Banana. + +## Running the sample + +Open a command line to this folder and run, otherwise open the .sln file: + +``` +dotnet restore +dotnet run --project Host/ +``` diff --git a/samples/dynamic-implementation/ServiceImplementation/OverrideFruiteService.cs b/samples/dynamic-implementation/ServiceImplementation/OverrideFruiteService.cs new file mode 100644 index 0000000..0e4e503 --- /dev/null +++ b/samples/dynamic-implementation/ServiceImplementation/OverrideFruiteService.cs @@ -0,0 +1,19 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Collections.Generic; +using Contracts; + +namespace ServiceImplementation +{ + public class OverrideFruiteService : IFruitService + { + public List GetFruits() + { + return new List() + { + new Fruit { Name="Banana" } + }; + } + } +} diff --git a/samples/dynamic-implementation/ServiceImplementation/OverridePlugin.cs b/samples/dynamic-implementation/ServiceImplementation/OverridePlugin.cs new file mode 100644 index 0000000..73f1f18 --- /dev/null +++ b/samples/dynamic-implementation/ServiceImplementation/OverridePlugin.cs @@ -0,0 +1,17 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Contracts; +using Microsoft.Extensions.DependencyInjection; + +namespace ServiceImplementation +{ + public class OverridePluginConfiguration : IPluginFactory + { + public void Configure(IServiceCollection services) + { + //this service override the standard one. unload this plugin or comment this to use the basic service + services.AddSingleton(); + } + } +} diff --git a/samples/dynamic-implementation/ServiceImplementation/ServiceImplementation.csproj b/samples/dynamic-implementation/ServiceImplementation/ServiceImplementation.csproj new file mode 100644 index 0000000..26cd29c --- /dev/null +++ b/samples/dynamic-implementation/ServiceImplementation/ServiceImplementation.csproj @@ -0,0 +1,12 @@ + + + + Library + net8.0 + + + + + + + diff --git a/samples/hello-world/HostApp/HostApp.csproj b/samples/hello-world/HostApp/HostApp.csproj new file mode 100644 index 0000000..efe024a --- /dev/null +++ b/samples/hello-world/HostApp/HostApp.csproj @@ -0,0 +1,21 @@ + + + + Exe + net8.0 + + + + + + + + + + + + + diff --git a/samples/hello-world/HostApp/Program.cs b/samples/hello-world/HostApp/Program.cs new file mode 100644 index 0000000..2ff700d --- /dev/null +++ b/samples/hello-world/HostApp/Program.cs @@ -0,0 +1,41 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using HelloWorld; +using McMaster.NETCore.Plugins; + +var loaders = new List(); + +// create plugin loaders +var pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins"); +foreach (var dir in Directory.GetDirectories(pluginsDir)) +{ + var dirName = Path.GetFileName(dir); + var pluginDll = Path.Combine(dir, dirName + ".dll"); + if (File.Exists(pluginDll)) + { + var loader = PluginLoader.CreateFromAssemblyFile( + pluginDll, + sharedTypes: new[] { typeof(IPlugin) }); + loaders.Add(loader); + } +} + +// Create an instance of plugin types +foreach (var loader in loaders) +{ + foreach (var pluginType in loader + .LoadDefaultAssembly() + .GetTypes() + .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract)) + { + // This assumes the implementation of IPlugin has a parameterless constructor + var plugin = Activator.CreateInstance(pluginType) as IPlugin; + + Console.WriteLine($"Created plugin instance '{plugin?.GetName()}'."); + } +} diff --git a/samples/hello-world/MyPlugin/MyPlugin.csproj b/samples/hello-world/MyPlugin/MyPlugin.csproj new file mode 100644 index 0000000..985bfc0 --- /dev/null +++ b/samples/hello-world/MyPlugin/MyPlugin.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/samples/hello-world/MyPlugin/MyPlugin1.cs b/samples/hello-world/MyPlugin/MyPlugin1.cs new file mode 100644 index 0000000..bd5e461 --- /dev/null +++ b/samples/hello-world/MyPlugin/MyPlugin1.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using HelloWorld; + +namespace MyPlugin +{ + internal class MyPlugin1 : IPlugin + { + public string GetName() => "My plugin v1"; + } +} diff --git a/samples/hello-world/PluginContract/IPlugin.cs b/samples/hello-world/PluginContract/IPlugin.cs new file mode 100644 index 0000000..591d45c --- /dev/null +++ b/samples/hello-world/PluginContract/IPlugin.cs @@ -0,0 +1,16 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace HelloWorld +{ + /// + /// This interface is an example of one way to define the interactions between the host and plugins. + /// There is nothing special about the name "IPlugin"; it's used here to illustrate a concept. + /// Look at https://github.com/natemcmaster/DotNetCorePlugins/tree/main/samples for additional examples + /// of ways you could define the interaction between host and plugins. + /// + public interface IPlugin + { + string GetName(); + } +} diff --git a/samples/hello-world/PluginContract/PluginContract.csproj b/samples/hello-world/PluginContract/PluginContract.csproj new file mode 100644 index 0000000..9f5c4f4 --- /dev/null +++ b/samples/hello-world/PluginContract/PluginContract.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/samples/hello-world/README.md b/samples/hello-world/README.md new file mode 100644 index 0000000..c8a8820 --- /dev/null +++ b/samples/hello-world/README.md @@ -0,0 +1,17 @@ +"Hello World" Sample +==================== + +This sample contains 3 projects which demonstrate a simple plugin scenario. + +1. 'HostApp' is a console application which scans for a 'plugins' folder in its base directory and attempts to load any plugins it finds +2. 'MyPlugin' which implements an implementation of this plugin +3. 'PluginContract' which contains an interface shared by plugins and the host. + +## Running the sample + +Open a command line to this folder and run: + +``` +dotnet restore +dotnet run --project HostApp/ +``` diff --git a/samples/hello-world/hello-world.sln b/samples/hello-world/hello-world.sln new file mode 100644 index 0000000..d27f2c2 --- /dev/null +++ b/samples/hello-world/hello-world.sln @@ -0,0 +1,62 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26124.0 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostApp", "HostApp\HostApp.csproj", "{FAA91115-37CE-4A74-8EC1-C58E15E5D683}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyPlugin", "MyPlugin\MyPlugin.csproj", "{EF895D6F-B174-46AF-936C-F4E3FE2B96E0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginContract", "PluginContract\PluginContract.csproj", "{5F3E90BD-7D9E-4469-A89A-63B0A8F77465}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Debug|x64.ActiveCfg = Debug|Any CPU + {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Debug|x64.Build.0 = Debug|Any CPU + {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Debug|x86.ActiveCfg = Debug|Any CPU + {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Debug|x86.Build.0 = Debug|Any CPU + {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Release|Any CPU.Build.0 = Release|Any CPU + {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Release|x64.ActiveCfg = Release|Any CPU + {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Release|x64.Build.0 = Release|Any CPU + {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Release|x86.ActiveCfg = Release|Any CPU + {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Release|x86.Build.0 = Release|Any CPU + {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Debug|x64.ActiveCfg = Debug|Any CPU + {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Debug|x64.Build.0 = Debug|Any CPU + {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Debug|x86.ActiveCfg = Debug|Any CPU + {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Debug|x86.Build.0 = Debug|Any CPU + {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Release|Any CPU.Build.0 = Release|Any CPU + {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Release|x64.ActiveCfg = Release|Any CPU + {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Release|x64.Build.0 = Release|Any CPU + {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Release|x86.ActiveCfg = Release|Any CPU + {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Release|x86.Build.0 = Release|Any CPU + {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Debug|x64.ActiveCfg = Debug|Any CPU + {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Debug|x64.Build.0 = Debug|Any CPU + {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Debug|x86.ActiveCfg = Debug|Any CPU + {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Debug|x86.Build.0 = Debug|Any CPU + {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Release|Any CPU.Build.0 = Release|Any CPU + {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Release|x64.ActiveCfg = Release|Any CPU + {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Release|x64.Build.0 = Release|Any CPU + {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Release|x86.ActiveCfg = Release|Any CPU + {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/samples/hot-reload/HotReloadApp/HotReloadApp.csproj b/samples/hot-reload/HotReloadApp/HotReloadApp.csproj new file mode 100644 index 0000000..fdf43cd --- /dev/null +++ b/samples/hot-reload/HotReloadApp/HotReloadApp.csproj @@ -0,0 +1,16 @@ + + + + Exe + net8.0 + + + + + + + + diff --git a/samples/hot-reload/HotReloadApp/Program.cs b/samples/hot-reload/HotReloadApp/Program.cs new file mode 100644 index 0000000..24a0982 --- /dev/null +++ b/samples/hot-reload/HotReloadApp/Program.cs @@ -0,0 +1,39 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; +using McMaster.NETCore.Plugins; + +var pluginPath = args[0]; +var loader = PluginLoader.CreateFromAssemblyFile(pluginPath, + config => config.EnableHotReload = true); + +loader.Reloaded += ShowPluginInfo; + +var cts = new CancellationTokenSource(); +Console.CancelKeyPress += (_, __) => cts.Cancel(); + +// Show info on first load +InvokePlugin(loader); + +await Task.Delay(-1, cts.Token); + +static void ShowPluginInfo(object sender, PluginReloadedEventArgs eventArgs) +{ + Console.ForegroundColor = ConsoleColor.Blue; + Console.Write("HotReloadApp: "); + Console.ResetColor(); + Console.WriteLine("plugin was reloaded"); + InvokePlugin(eventArgs.Loader); +} + +static void InvokePlugin(PluginLoader loader) +{ + var assembly = loader.LoadDefaultAssembly(); + assembly + .GetType("TimestampedPlugin.InfoDisplayer", throwOnError: true) + !.GetMethod("Print") + !.Invoke(null, null); +} diff --git a/samples/hot-reload/README.md b/samples/hot-reload/README.md new file mode 100644 index 0000000..986602c --- /dev/null +++ b/samples/hot-reload/README.md @@ -0,0 +1,13 @@ +# Hot Reload Sample + +This is a minimal sample of how to take advantage of hot reloading support. + +To run this sample, execute the `run.sh` script. This will: + +* Compile the projects once +* Start the HotReloadApp console application + * This creates a single loader with hot reloading enabled + * It subscribes to the `PluginLoader.Reloaded` event to be notified when a new version of the assemblies + are availabled. + * It invokes the new version of the assembly +* Rebuilds the TimestampedPlugin every 5 seconds (until you press CTRL+C to exit) diff --git a/samples/hot-reload/TimestampedPlugin/InfoDisplayer.cs b/samples/hot-reload/TimestampedPlugin/InfoDisplayer.cs new file mode 100644 index 0000000..7e0b6a1 --- /dev/null +++ b/samples/hot-reload/TimestampedPlugin/InfoDisplayer.cs @@ -0,0 +1,32 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Linq; +using System.Reflection; +using Microsoft.Data.Sqlite; + +namespace TimestampedPlugin +{ + public class InfoDisplayer + { + public static void Print() + { + // Use something from Microsoft.Data.Sqlite to trigger loading of native dependency + var connectionString = new SqliteConnectionStringBuilder + { + DataSource = "HELLO" + }; + + var compileTimestamp = typeof(InfoDisplayer) + .Assembly + .GetCustomAttributes() + .First(a => a.Key == "CompileTimestamp") + .Value; + Console.ForegroundColor = ConsoleColor.Green; + Console.Write("TimestampedPlugin: "); + Console.ResetColor(); + Console.WriteLine($"this plugin was compiled at {compileTimestamp}. {connectionString.DataSource}!"); + } + } +} diff --git a/samples/hot-reload/TimestampedPlugin/TimestampedPlugin.csproj b/samples/hot-reload/TimestampedPlugin/TimestampedPlugin.csproj new file mode 100644 index 0000000..75e9a11 --- /dev/null +++ b/samples/hot-reload/TimestampedPlugin/TimestampedPlugin.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + + + + + <_Parameter1>CompileTimestamp + <_Parameter2>$([System.DateTime]::Now.ToString('h:mm:ss tt')) + + + + + + + + diff --git a/samples/hot-reload/hot-reload.sln b/samples/hot-reload/hot-reload.sln new file mode 100644 index 0000000..b6f3054 --- /dev/null +++ b/samples/hot-reload/hot-reload.sln @@ -0,0 +1,48 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29209.152 +MinimumVisualStudioVersion = 15.0.26124.0 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotReloadApp", "HotReloadApp\HotReloadApp.csproj", "{421F2562-390A-45C3-B028-32A0027AD8BD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimestampedPlugin", "TimestampedPlugin\TimestampedPlugin.csproj", "{92DCE5FD-1A77-4A77-AE04-2820004A446B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {421F2562-390A-45C3-B028-32A0027AD8BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {421F2562-390A-45C3-B028-32A0027AD8BD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {421F2562-390A-45C3-B028-32A0027AD8BD}.Debug|x64.ActiveCfg = Debug|Any CPU + {421F2562-390A-45C3-B028-32A0027AD8BD}.Debug|x64.Build.0 = Debug|Any CPU + {421F2562-390A-45C3-B028-32A0027AD8BD}.Debug|x86.ActiveCfg = Debug|Any CPU + {421F2562-390A-45C3-B028-32A0027AD8BD}.Debug|x86.Build.0 = Debug|Any CPU + {421F2562-390A-45C3-B028-32A0027AD8BD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {421F2562-390A-45C3-B028-32A0027AD8BD}.Release|Any CPU.Build.0 = Release|Any CPU + {421F2562-390A-45C3-B028-32A0027AD8BD}.Release|x64.ActiveCfg = Release|Any CPU + {421F2562-390A-45C3-B028-32A0027AD8BD}.Release|x64.Build.0 = Release|Any CPU + {421F2562-390A-45C3-B028-32A0027AD8BD}.Release|x86.ActiveCfg = Release|Any CPU + {421F2562-390A-45C3-B028-32A0027AD8BD}.Release|x86.Build.0 = Release|Any CPU + {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Debug|x64.ActiveCfg = Debug|Any CPU + {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Debug|x64.Build.0 = Debug|Any CPU + {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Debug|x86.ActiveCfg = Debug|Any CPU + {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Debug|x86.Build.0 = Debug|Any CPU + {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Release|Any CPU.Build.0 = Release|Any CPU + {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Release|x64.ActiveCfg = Release|Any CPU + {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Release|x64.Build.0 = Release|Any CPU + {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Release|x86.ActiveCfg = Release|Any CPU + {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Release|x86.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal diff --git a/samples/hot-reload/run.ps1 b/samples/hot-reload/run.ps1 new file mode 100644 index 0000000..bdd2dc9 --- /dev/null +++ b/samples/hot-reload/run.ps1 @@ -0,0 +1,42 @@ +#!/usr/bin/env pwsh + +$ErrorActionPreference = 'Stop' + +Push-Location $PSScriptRoot +try { + $publish_dir = "$PSScriptRoot/bin/plugins/TimestampedPlugin/" + + function log { + Write-Host -NoNewline -ForegroundColor Yellow "run.ps1: " + Write-Host $args + } + + function publish { + Write-Host "" + dotnet publish --no-restore TimestampedPlugin/ -o $publish_dir -nologo + Write-Host "" + } + + log "Compiling apps" + + & dotnet build HotReloadApp -nologo -clp:NoSummary + & publish + + log "Use CTRL+C to exit" + + $bg_args = @("run", "--no-build", "--project", "HotReloadApp", "$publish_dir/TimestampedPlugin.dll") + $host_process = Start-Process -NoNewWindow -FilePath dotnet -ArgumentList $bg_args + try { + while ($true) { + Start-Sleep 5 + log "Rebuilding plugin..." + publish + } + } + finally { + $host_process.Kill() + } +} +finally { + Pop-Location +} diff --git a/samples/hot-reload/run.sh b/samples/hot-reload/run.sh new file mode 100644 index 0000000..e991b47 --- /dev/null +++ b/samples/hot-reload/run.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash + +set -e + +RESET="\033[0m" +YELLOW="\033[0;33m" +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +pushd $DIR >/dev/null + +publish_dir="$DIR/bin/plugins/TimestampedPlugin/" + +publish() { + echo "" + dotnet publish --no-restore TimestampedPlugin/ -o $publish_dir -nologo + echo "" +} + +echo -e "${YELLOW}run.sh:${RESET} Compiling apps" +dotnet build HotReloadApp -nologo -clp:NoSummary +publish + +trap "kill 0" EXIT + +echo -e "${YELLOW}run.sh:${RESET} Use CTRL+C to exit" + +dotnet run --no-build --project HotReloadApp -- "$publish_dir/TimestampedPlugin.dll" & + +while true +do + sleep 5 + echo -e "${YELLOW}run.sh:${RESET} Rebuilding plugin..." + publish +done diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets new file mode 100644 index 0000000..33bd792 --- /dev/null +++ b/src/Directory.Build.targets @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/Plugins.Mvc/McMaster.NETCore.Plugins.Mvc.csproj b/src/Plugins.Mvc/McMaster.NETCore.Plugins.Mvc.csproj new file mode 100644 index 0000000..de23cf4 --- /dev/null +++ b/src/Plugins.Mvc/McMaster.NETCore.Plugins.Mvc.csproj @@ -0,0 +1,21 @@ + + + + net8.0 + library + true + true + Provides API for dynamically loading MVC controllers into an ASP.NET Core web application. + +This package should be used by the host application which needs to load plugins. +See https://github.com/natemcmaster/DotNetCorePlugins/blob/main/README.md for more samples and documentation. + + .NET Core;plugins;aspnetcore + + + + + + + + diff --git a/src/Plugins.Mvc/MvcPluginExtensions.cs b/src/Plugins.Mvc/MvcPluginExtensions.cs new file mode 100644 index 0000000..53fe249 --- /dev/null +++ b/src/Plugins.Mvc/MvcPluginExtensions.cs @@ -0,0 +1,74 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Reflection; +using McMaster.NETCore.Plugins; +using Microsoft.AspNetCore.Mvc.ApplicationParts; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extends the MVC builder. + /// + public static class MvcPluginExtensions + { + /// + /// Loads controllers and razor pages from a plugin assembly. + /// + /// This creates a loader with set to true. + /// If you need more control over shared types, use instead. + /// + /// + /// The MVC builder + /// Full path the main .dll file for the plugin. + /// The builder + public static IMvcBuilder AddPluginFromAssemblyFile(this IMvcBuilder mvcBuilder, string assemblyFile) + { + var plugin = PluginLoader.CreateFromAssemblyFile( + assemblyFile, // create a plugin from for the .dll file + config => + // this ensures that the version of MVC is shared between this app and the plugin + config.PreferSharedTypes = true); + + return mvcBuilder.AddPluginLoader(plugin); + } + + /// + /// Loads controllers and razor pages from a plugin loader. + /// + /// In order for this to work, the PluginLoader instance must be configured to share the types + /// and + /// (comes from Microsoft.AspNetCore.Mvc.Core.dll). The easiest way to ensure that is done correctly + /// is to set to true. + /// + /// + /// The MVC builder + /// An instance of PluginLoader. + /// The builder + public static IMvcBuilder AddPluginLoader(this IMvcBuilder mvcBuilder, PluginLoader pluginLoader) + { + var pluginAssembly = pluginLoader.LoadDefaultAssembly(); + + // This loads MVC application parts from plugin assemblies + var partFactory = ApplicationPartFactory.GetApplicationPartFactory(pluginAssembly); + foreach (var part in partFactory.GetApplicationParts(pluginAssembly)) + { + mvcBuilder.PartManager.ApplicationParts.Add(part); + } + + // This piece finds and loads related parts, such as MvcAppPlugin1.Views.dll. + var relatedAssembliesAttrs = pluginAssembly.GetCustomAttributes(); + foreach (var attr in relatedAssembliesAttrs) + { + var assembly = pluginLoader.LoadAssembly(attr.AssemblyFileName); + partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly); + foreach (var part in partFactory.GetApplicationParts(assembly)) + { + mvcBuilder.PartManager.ApplicationParts.Add(part); + } + } + + return mvcBuilder; + } + } +} diff --git a/src/Plugins.Mvc/PublicAPI.Shipped.txt b/src/Plugins.Mvc/PublicAPI.Shipped.txt new file mode 100644 index 0000000..a7593d9 --- /dev/null +++ b/src/Plugins.Mvc/PublicAPI.Shipped.txt @@ -0,0 +1,4 @@ +#nullable enable +Microsoft.Extensions.DependencyInjection.MvcPluginExtensions +static Microsoft.Extensions.DependencyInjection.MvcPluginExtensions.AddPluginFromAssemblyFile(this Microsoft.Extensions.DependencyInjection.IMvcBuilder! mvcBuilder, string! assemblyFile) -> Microsoft.Extensions.DependencyInjection.IMvcBuilder! +static Microsoft.Extensions.DependencyInjection.MvcPluginExtensions.AddPluginLoader(this Microsoft.Extensions.DependencyInjection.IMvcBuilder! mvcBuilder, McMaster.NETCore.Plugins.PluginLoader! pluginLoader) -> Microsoft.Extensions.DependencyInjection.IMvcBuilder! diff --git a/src/Plugins.Mvc/PublicAPI.Unshipped.txt b/src/Plugins.Mvc/PublicAPI.Unshipped.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/Plugins.Mvc/releasenotes.props b/src/Plugins.Mvc/releasenotes.props new file mode 100644 index 0000000..b99b1e3 --- /dev/null +++ b/src/Plugins.Mvc/releasenotes.props @@ -0,0 +1,17 @@ + + + +Breaking change: require .NET >= 8.0 + +See also https://nuget.org/packages/McMaster.NETCore.Plugins/$(VersionPrefix) for its release notes. + + +No changes to this library, but updated to use 1.1.0 of Microsoft.NETCore.Plugins. +See https://nuget.org/packages/McMaster.NETCore.Plugins/$(VersionPrefix) for its release notes. + + +Changes: +* Add new API: IMvcBuilder.AddPluginLoader(PluginLoader pluginLoader) + + + diff --git a/src/Plugins/Internal/Debouncer.cs b/src/Plugins/Internal/Debouncer.cs new file mode 100644 index 0000000..dfc409a --- /dev/null +++ b/src/Plugins/Internal/Debouncer.cs @@ -0,0 +1,42 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace McMaster.NETCore.Plugins.Internal +{ + internal class Debouncer : IDisposable + { + private readonly CancellationTokenSource _cts = new(); + private readonly TimeSpan _waitTime; + private int _counter; + + public Debouncer(TimeSpan waitTime) + { + _waitTime = waitTime; + } + + public void Execute(Action action) + { + var current = Interlocked.Increment(ref _counter); + + Task.Delay(_waitTime).ContinueWith(task => + { + // Is this the last task that was queued? + if (current == _counter && !_cts.IsCancellationRequested) + { + action(); + } + + task.Dispose(); + }, _cts.Token); + } + + public void Dispose() + { + _cts.Cancel(); + } + } +} diff --git a/src/Plugins/Internal/PlatformInformation.cs b/src/Plugins/Internal/PlatformInformation.cs new file mode 100644 index 0000000..c4e2799 --- /dev/null +++ b/src/Plugins/Internal/PlatformInformation.cs @@ -0,0 +1,47 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.Runtime.InteropServices; + +namespace McMaster.NETCore.Plugins +{ + internal class PlatformInformation + { + public static readonly string[] NativeLibraryExtensions; + public static readonly string[] NativeLibraryPrefixes; + public static readonly string[] ManagedAssemblyExtensions = new[] + { + ".dll", + ".ni.dll", + ".exe", + ".ni.exe" + }; + + static PlatformInformation() + { + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + NativeLibraryPrefixes = new[] { "" }; + NativeLibraryExtensions = new[] { ".dll" }; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + NativeLibraryPrefixes = new[] { "", "lib", }; + NativeLibraryExtensions = new[] { ".dylib" }; + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + NativeLibraryPrefixes = new[] { "", "lib" }; + NativeLibraryExtensions = new[] { ".so", ".so.1" }; + } + else + { + Debug.Fail("Unknown OS type"); + NativeLibraryPrefixes = Array.Empty(); + NativeLibraryExtensions = Array.Empty(); + } + } + } +} diff --git a/src/Plugins/Internal/RuntimeConfig.cs b/src/Plugins/Internal/RuntimeConfig.cs new file mode 100644 index 0000000..bd7c90c --- /dev/null +++ b/src/Plugins/Internal/RuntimeConfig.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace McMaster.NETCore.Plugins +{ + internal class RuntimeConfig + { + public RuntimeOptions? RuntimeOptions { get; set; } + } +} diff --git a/src/Plugins/Internal/RuntimeOptions.cs b/src/Plugins/Internal/RuntimeOptions.cs new file mode 100644 index 0000000..748aa38 --- /dev/null +++ b/src/Plugins/Internal/RuntimeOptions.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace McMaster.NETCore.Plugins +{ + internal class RuntimeOptions + { + public string? Tfm { get; set; } + + public string[]? AdditionalProbingPaths { get; set; } + } +} diff --git a/src/Plugins/LibraryModel/ManagedLibrary.cs b/src/Plugins/LibraryModel/ManagedLibrary.cs new file mode 100644 index 0000000..ca45cc5 --- /dev/null +++ b/src/Plugins/LibraryModel/ManagedLibrary.cs @@ -0,0 +1,73 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Reflection; + +namespace McMaster.NETCore.Plugins.LibraryModel +{ + /// + /// Represents a managed, .NET assembly. + /// + [DebuggerDisplay("{Name} = {AdditionalProbingPath}")] + public class ManagedLibrary + { + private ManagedLibrary(AssemblyName name, string additionalProbingPath, string appLocalPath) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + AdditionalProbingPath = additionalProbingPath ?? throw new ArgumentNullException(nameof(additionalProbingPath)); + AppLocalPath = appLocalPath ?? throw new ArgumentNullException(nameof(appLocalPath)); + } + + /// + /// Name of the managed library + /// + public AssemblyName Name { get; private set; } + + /// + /// Contains path to file within an additional probing path root. This is typically a combination + /// of the NuGet package ID (lowercased), version, and path within the package. + /// + /// For example, microsoft.data.sqlite/1.0.0/lib/netstandard1.3/Microsoft.Data.Sqlite.dll + /// + /// + public string AdditionalProbingPath { get; private set; } + + /// + /// Contains path to file within a deployed, framework-dependent application. + /// + /// For most managed libraries, this will be the file name. + /// For example, MyPlugin1.dll. + /// + /// + /// For runtime-specific managed implementations, this may include a sub folder path. + /// For example, runtimes/win/lib/netcoreapp2.0/System.Diagnostics.EventLog.dll + /// + /// + public string AppLocalPath { get; private set; } + + /// + /// Create an instance of from a NuGet package. + /// + /// The name of the package. + /// The version of the package. + /// The path within the NuGet package. + /// + public static ManagedLibrary CreateFromPackage(string packageId, string packageVersion, string assetPath) + { + // When the asset comes from "lib/$tfm/", Microsoft.NET.Sdk will flatten this during publish based on the most compatible TFM. + // The SDK will not flatten managed libraries found under runtimes/ + var appLocalPath = assetPath.StartsWith("lib/") + ? Path.GetFileName(assetPath) + : assetPath; + + return new ManagedLibrary( + new AssemblyName(Path.GetFileNameWithoutExtension(assetPath)), + Path.Combine(packageId.ToLowerInvariant(), packageVersion, assetPath), + appLocalPath + ); + } + } +} diff --git a/src/Plugins/LibraryModel/NativeLibrary.cs b/src/Plugins/LibraryModel/NativeLibrary.cs new file mode 100644 index 0000000..ac5e061 --- /dev/null +++ b/src/Plugins/LibraryModel/NativeLibrary.cs @@ -0,0 +1,69 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.IO; + +namespace McMaster.NETCore.Plugins.LibraryModel +{ + /// + /// Represents an unmanaged library, such as `libsqlite3`, which may need to be loaded + /// for P/Invoke to work. + /// + [DebuggerDisplay("{Name} = {AdditionalProbingPath}")] + public class NativeLibrary + { + private NativeLibrary(string name, string appLocalPath, string additionalProbingPath) + { + Name = name ?? throw new ArgumentNullException(nameof(name)); + AppLocalPath = appLocalPath ?? throw new ArgumentNullException(nameof(appLocalPath)); + AdditionalProbingPath = additionalProbingPath ?? throw new ArgumentNullException(nameof(additionalProbingPath)); + } + + /// + /// Name of the native library. This should match the name of the P/Invoke call. + /// + /// For example, if specifying `[DllImport("sqlite3")]`, should be sqlite3. + /// This may not match the exact file name as loading will attempt variations on the name according + /// to OS convention. On Windows, P/Invoke will attempt to load `sqlite3.dll`. On macOS, it will + /// attempt to find `sqlite3.dylib` and `libsqlite3.dylib`. On Linux, it will attempt to find + /// `sqlite3.so` and `libsqlite3.so`. + /// + /// + public string Name { get; private set; } + + /// + /// Contains path to file within a deployed, framework-dependent application + /// + /// For example, runtimes/linux-x64/native/libsqlite.so + /// + /// + public string AppLocalPath { get; private set; } + + /// + /// Contains path to file within an additional probing path root. This is typically a combination + /// of the NuGet package ID (lowercased), version, and path within the package. + /// + /// For example, sqlite/3.13.3/runtimes/linux-x64/native/libsqlite.so + /// + /// + public string AdditionalProbingPath { get; private set; } + + /// + /// Create an instance of from a NuGet package. + /// + /// The name of the package. + /// The version of the package. + /// The path within the NuGet package. + /// + public static NativeLibrary CreateFromPackage(string packageId, string packageVersion, string assetPath) + { + return new NativeLibrary( + Path.GetFileNameWithoutExtension(assetPath), + assetPath, + Path.Combine(packageId.ToLowerInvariant(), packageVersion, assetPath) + ); + } + } +} diff --git a/src/Plugins/Loader/AssemblyLoadContextBuilder.cs b/src/Plugins/Loader/AssemblyLoadContextBuilder.cs new file mode 100644 index 0000000..bb59d15 --- /dev/null +++ b/src/Plugins/Loader/AssemblyLoadContextBuilder.cs @@ -0,0 +1,369 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using McMaster.NETCore.Plugins.LibraryModel; + +namespace McMaster.NETCore.Plugins.Loader +{ + /// + /// A builder for creating an instance of . + /// + public class AssemblyLoadContextBuilder + { + private readonly List _additionalProbingPaths = new(); + private readonly List _resourceProbingPaths = new(); + private readonly List _resourceProbingSubpaths = new(); + private readonly Dictionary _managedLibraries = new(StringComparer.Ordinal); + private readonly Dictionary _nativeLibraries = new(StringComparer.Ordinal); + private readonly HashSet _privateAssemblies = new(StringComparer.Ordinal); + private readonly HashSet _defaultAssemblies = new(StringComparer.Ordinal); + private AssemblyLoadContext _defaultLoadContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? AssemblyLoadContext.Default; + private string? _mainAssemblyPath; + private bool _preferDefaultLoadContext; + private bool _lazyLoadReferences; + + private bool _isCollectible; + private bool _loadInMemory; + private bool _shadowCopyNativeLibraries; + + /// + /// Creates an assembly load context using settings specified on the builder. + /// + /// A new ManagedLoadContext. + public AssemblyLoadContext Build() + { + var resourceProbingPaths = new List(_resourceProbingPaths); + foreach (var additionalPath in _additionalProbingPaths) + { + foreach (var subPath in _resourceProbingSubpaths) + { + resourceProbingPaths.Add(Path.Combine(additionalPath, subPath)); + } + } + + if (_mainAssemblyPath == null) + { + throw new InvalidOperationException($"Missing required property. You must call '{nameof(SetMainAssemblyPath)}' to configure the default assembly."); + } + + return new ManagedLoadContext( + _mainAssemblyPath, + _managedLibraries, + _nativeLibraries, + _privateAssemblies, + _defaultAssemblies, + _additionalProbingPaths, + resourceProbingPaths, + _defaultLoadContext, + _preferDefaultLoadContext, + _lazyLoadReferences, + _isCollectible, + _loadInMemory, + _shadowCopyNativeLibraries); + } + + /// + /// Set the file path to the main assembly for the context. This is used as the starting point for loading + /// other assemblies. The directory that contains it is also known as the 'app local' directory. + /// + /// The file path. Must not be null or empty. Must be an absolute path. + /// The builder. + public AssemblyLoadContextBuilder SetMainAssemblyPath(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException("Argument must not be null or empty.", nameof(path)); + } + + if (!Path.IsPathRooted(path)) + { + throw new ArgumentException("Argument must be a full path.", nameof(path)); + } + + _mainAssemblyPath = path; + return this; + } + + /// + /// Replaces the default used by the . + /// Use this feature if the of the is not the Runtime's default load context. + /// i.e. (AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly) != + /// + /// The context to set. + /// The builder. + public AssemblyLoadContextBuilder SetDefaultContext(AssemblyLoadContext context) + { + _defaultLoadContext = context ?? throw new ArgumentException($"Bad Argument: AssemblyLoadContext in {nameof(AssemblyLoadContextBuilder)}.{nameof(SetDefaultContext)} is null."); + return this; + } + + /// + /// Instructs the load context to prefer a private version of this assembly, even if that version is + /// different from the version used by the host application. + /// Use this when you do not need to exchange types created from within the load context with other contexts + /// or the default app context. + /// + /// This may mean the types loaded from + /// this assembly will not match the types from an assembly with the same name, but different version, + /// in the host application. + /// + /// + /// For example, if the host application has a type named Foo from assembly Banana, Version=1.0.0.0 + /// and the load context prefers a private version of Banan, Version=2.0.0.0, when comparing two objects, + /// one created by the host (Foo1) and one created from within the load context (Foo2), they will not have the same + /// type. Foo1.GetType() != Foo2.GetType() + /// + /// + /// The name of the assembly. + /// The builder. + public AssemblyLoadContextBuilder PreferLoadContextAssembly(AssemblyName assemblyName) + { + if (assemblyName.Name != null) + { + _privateAssemblies.Add(assemblyName.Name); + } + + return this; + } + + /// + /// Instructs the load context to first attempt to load assemblies by this name from the default app context, even + /// if other assemblies in this load context express a dependency on a higher or lower version. + /// Use this when you need to exchange types created from within the load context with other contexts + /// or the default app context. + /// + /// The name of the assembly. + /// The builder. + public AssemblyLoadContextBuilder PreferDefaultLoadContextAssembly(AssemblyName assemblyName) + { + // Lazy loaded references have dependencies resolved as they are loaded inside the actual Load Context. + if (_lazyLoadReferences) + { + if (assemblyName.Name != null && !_defaultAssemblies.Contains(assemblyName.Name)) + { + _defaultAssemblies.Add(assemblyName.Name); + var assembly = _defaultLoadContext.LoadFromAssemblyName(assemblyName); + foreach (var reference in assembly.GetReferencedAssemblies()) + { + if (reference.Name != null) + { + _defaultAssemblies.Add(reference.Name); + } + } + } + + return this; + } + + var names = new Queue(); + names.Enqueue(assemblyName); + while (names.TryDequeue(out var name)) + { + if (name.Name == null || _defaultAssemblies.Contains(name.Name)) + { + // base cases + continue; + } + + _defaultAssemblies.Add(name.Name); + + // Load and find all dependencies of default assemblies. + // This sacrifices some performance for determinism in how transitive + // dependencies will be shared between host and plugin. + var assembly = _defaultLoadContext.LoadFromAssemblyName(name); + + foreach (var reference in assembly.GetReferencedAssemblies()) + { + names.Enqueue(reference); + } + } + + return this; + } + + /// + /// Instructs the load context to first search for binaries from the default app context, even + /// if other assemblies in this load context express a dependency on a higher or lower version. + /// Use this when you need to exchange types created from within the load context with other contexts + /// or the default app context. + /// + /// This may mean the types loaded from within the context are force-downgraded to the version provided + /// by the host. can be used to selectively identify binaries + /// which should not be loaded from the default load context. + /// + /// + /// When true, first attemp to load binaries from the default load context. + /// The builder. + public AssemblyLoadContextBuilder PreferDefaultLoadContext(bool preferDefaultLoadContext) + { + _preferDefaultLoadContext = preferDefaultLoadContext; + return this; + } + + /// + /// Instructs the load context to lazy load dependencies of all shared assemblies. + /// Reduces plugin load time at the expense of non-determinism in how transitive dependencies are loaded + /// between the plugin and the host. + /// + /// Please be aware of the danger of using this option: + /// + /// https://github.com/natemcmaster/DotNetCorePlugins/pull/164#issuecomment-751557873 + /// + /// + /// True to lazy load, else false. + /// The builder. + public AssemblyLoadContextBuilder IsLazyLoaded(bool isLazyLoaded) + { + _lazyLoadReferences = isLazyLoaded; + return this; + } + + /// + /// Add a managed library to the load context. + /// + /// The managed library. + /// The builder. + public AssemblyLoadContextBuilder AddManagedLibrary(ManagedLibrary library) + { + ValidateRelativePath(library.AdditionalProbingPath); + + if (library.Name.Name != null) + { + _managedLibraries.Add(library.Name.Name, library); + } + + return this; + } + + /// + /// Add a native library to the load context. + /// + /// + /// + public AssemblyLoadContextBuilder AddNativeLibrary(NativeLibrary library) + { + ValidateRelativePath(library.AppLocalPath); + ValidateRelativePath(library.AdditionalProbingPath); + + _nativeLibraries.Add(library.Name, library); + return this; + } + + /// + /// Add a that should be used to search for native and managed libraries. + /// + /// The file path. Must be a full file path. + /// The builder + public AssemblyLoadContextBuilder AddProbingPath(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException("Value must not be null or empty.", nameof(path)); + } + + if (!Path.IsPathRooted(path)) + { + throw new ArgumentException("Argument must be a full path.", nameof(path)); + } + + _additionalProbingPaths.Add(path); + return this; + } + + /// + /// Add a that should be use to search for resource assemblies (aka satellite assemblies). + /// + /// The file path. Must be a full file path. + /// The builder + public AssemblyLoadContextBuilder AddResourceProbingPath(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException("Value must not be null or empty.", nameof(path)); + } + + if (!Path.IsPathRooted(path)) + { + throw new ArgumentException("Argument must be a full path.", nameof(path)); + } + + _resourceProbingPaths.Add(path); + return this; + } + + /// + /// Enable unloading the assembly load context. + /// + /// The builder + public AssemblyLoadContextBuilder EnableUnloading() + { + _isCollectible = true; + return this; + } + + /// + /// Read .dll files into memory to avoid locking the files. + /// This is not as efficient, so is not enabled by default, but is required for scenarios + /// like hot reloading. + /// + /// The builder + public AssemblyLoadContextBuilder PreloadAssembliesIntoMemory() + { + _loadInMemory = true; // required to prevent dotnet from locking loaded files + return this; + } + + /// + /// Shadow copy native libraries (unmanaged DLLs) to avoid locking of these files. + /// This is not as efficient, so is not enabled by default, but is required for scenarios + /// like hot reloading of plugins dependent on native libraries. + /// + /// The builder + public AssemblyLoadContextBuilder ShadowCopyNativeLibraries() + { + _shadowCopyNativeLibraries = true; + return this; + } + + /// + /// Add a that should be use to search for resource assemblies (aka satellite assemblies) + /// relative to any paths specified as + /// + /// The file path. Must not be a full file path since it will be appended to additional probing path roots. + /// The builder + internal AssemblyLoadContextBuilder AddResourceProbingSubpath(string path) + { + if (string.IsNullOrEmpty(path)) + { + throw new ArgumentException("Value must not be null or empty.", nameof(path)); + } + + if (Path.IsPathRooted(path)) + { + throw new ArgumentException("Argument must be not a full path.", nameof(path)); + } + + _resourceProbingSubpaths.Add(path); + return this; + } + + private static void ValidateRelativePath(string probingPath) + { + if (string.IsNullOrEmpty(probingPath)) + { + throw new ArgumentException("Value must not be null or empty.", nameof(probingPath)); + } + + if (Path.IsPathRooted(probingPath)) + { + throw new ArgumentException("Argument must be a relative path.", nameof(probingPath)); + } + } + } +} diff --git a/src/Plugins/Loader/ManagedLoadContext.cs b/src/Plugins/Loader/ManagedLoadContext.cs new file mode 100644 index 0000000..2cded26 --- /dev/null +++ b/src/Plugins/Loader/ManagedLoadContext.cs @@ -0,0 +1,436 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using McMaster.NETCore.Plugins.LibraryModel; +using RuntimeInformation = System.Runtime.InteropServices.RuntimeInformation; + +namespace McMaster.NETCore.Plugins.Loader +{ + /// + /// An implementation of which attempts to load managed and native + /// binaries at runtime immitating some of the behaviors of corehost. + /// + [DebuggerDisplay("'{Name}' ({_mainAssemblyPath})")] + internal class ManagedLoadContext : AssemblyLoadContext + { + private readonly string _basePath; + private readonly string _mainAssemblyPath; + private readonly IReadOnlyDictionary _managedAssemblies; + private readonly IReadOnlyDictionary _nativeLibraries; + private readonly IReadOnlyCollection _privateAssemblies; + private readonly ICollection _defaultAssemblies; + private readonly IReadOnlyCollection _additionalProbingPaths; + private readonly bool _preferDefaultLoadContext; + private readonly string[] _resourceRoots; + private readonly bool _loadInMemory; + private readonly bool _lazyLoadReferences; + private readonly AssemblyLoadContext _defaultLoadContext; + private readonly AssemblyDependencyResolver _dependencyResolver; + private readonly bool _shadowCopyNativeLibraries; + private readonly string _unmanagedDllShadowCopyDirectoryPath; + + public ManagedLoadContext(string mainAssemblyPath, + IReadOnlyDictionary managedAssemblies, + IReadOnlyDictionary nativeLibraries, + IReadOnlyCollection privateAssemblies, + IReadOnlyCollection defaultAssemblies, + IReadOnlyCollection additionalProbingPaths, + IReadOnlyCollection resourceProbingPaths, + AssemblyLoadContext defaultLoadContext, + bool preferDefaultLoadContext, + bool lazyLoadReferences, + bool isCollectible, + bool loadInMemory, + bool shadowCopyNativeLibraries) + : base(Path.GetFileNameWithoutExtension(mainAssemblyPath), isCollectible) + { + if (resourceProbingPaths == null) + { + throw new ArgumentNullException(nameof(resourceProbingPaths)); + } + + _mainAssemblyPath = mainAssemblyPath ?? throw new ArgumentNullException(nameof(mainAssemblyPath)); + _dependencyResolver = new AssemblyDependencyResolver(mainAssemblyPath); + _basePath = Path.GetDirectoryName(mainAssemblyPath) ?? throw new ArgumentException(nameof(mainAssemblyPath)); + _managedAssemblies = managedAssemblies ?? throw new ArgumentNullException(nameof(managedAssemblies)); + _privateAssemblies = privateAssemblies ?? throw new ArgumentNullException(nameof(privateAssemblies)); + _defaultAssemblies = defaultAssemblies != null ? defaultAssemblies.ToList() : throw new ArgumentNullException(nameof(defaultAssemblies)); + _nativeLibraries = nativeLibraries ?? throw new ArgumentNullException(nameof(nativeLibraries)); + _additionalProbingPaths = additionalProbingPaths ?? throw new ArgumentNullException(nameof(additionalProbingPaths)); + _defaultLoadContext = defaultLoadContext; + _preferDefaultLoadContext = preferDefaultLoadContext; + _loadInMemory = loadInMemory; + _lazyLoadReferences = lazyLoadReferences; + + _resourceRoots = new[] { _basePath } + .Concat(resourceProbingPaths) + .ToArray(); + + _shadowCopyNativeLibraries = shadowCopyNativeLibraries; + _unmanagedDllShadowCopyDirectoryPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + + if (shadowCopyNativeLibraries) + { + Unloading += _ => OnUnloaded(); + } + } + + /// + /// Load an assembly. + /// + /// + /// + protected override Assembly? Load(AssemblyName assemblyName) + { + if (assemblyName.Name == null) + { + // not sure how to handle this case. It's technically possible. + return null; + } + + if ((_preferDefaultLoadContext || _defaultAssemblies.Contains(assemblyName.Name)) && !_privateAssemblies.Contains(assemblyName.Name)) + { + // If default context is preferred, check first for types in the default context unless the dependency has been declared as private + try + { + var defaultAssembly = _defaultLoadContext.LoadFromAssemblyName(assemblyName); + if (defaultAssembly != null) + { + // Add referenced assemblies to the list of default assemblies. + // This is basically lazy loading + if (_lazyLoadReferences) + { + foreach (var reference in defaultAssembly.GetReferencedAssemblies()) + { + if (reference.Name != null && !_defaultAssemblies.Contains(reference.Name)) + { + _defaultAssemblies.Add(reference.Name); + } + } + } + + // Older versions used to return null here such that returned assembly would be resolved from the default ALC. + // However, with the addition of custom default ALCs, the Default ALC may not be the user's chosen ALC when + // this context was built. As such, we simply return the Assembly from the user's chosen default load context. + return defaultAssembly; + } + } + catch + { + // Swallow errors in loading from the default context + } + } + + var resolvedPath = _dependencyResolver.ResolveAssemblyToPath(assemblyName); + if (!string.IsNullOrEmpty(resolvedPath) && File.Exists(resolvedPath)) + { + return LoadAssemblyFromFilePath(resolvedPath); + } + + // Resource assembly binding does not use the TPA. Instead, it probes PLATFORM_RESOURCE_ROOTS (a list of folders) + // for $folder/$culture/$assemblyName.dll + // See https://github.com/dotnet/coreclr/blob/3fca50a36e62a7433d7601d805d38de6baee7951/src/binder/assemblybinder.cpp#L1232-L1290 + + if (!string.IsNullOrEmpty(assemblyName.CultureName) && !string.Equals("neutral", assemblyName.CultureName)) + { + foreach (var resourceRoot in _resourceRoots) + { + var resourcePath = Path.Combine(resourceRoot, assemblyName.CultureName, assemblyName.Name + ".dll"); + if (File.Exists(resourcePath)) + { + return LoadAssemblyFromFilePath(resourcePath); + } + } + + return null; + } + + if (_managedAssemblies.TryGetValue(assemblyName.Name, out var library) && library != null) + { + if (SearchForLibrary(library, out var path) && path != null) + { + return LoadAssemblyFromFilePath(path); + } + } + else + { + // if an assembly was not listed in the list of known assemblies, + // fallback to the load context base directory + var dllName = assemblyName.Name + ".dll"; + foreach (var probingPath in _additionalProbingPaths.Prepend(_basePath)) + { + var localFile = Path.Combine(probingPath, dllName); + if (File.Exists(localFile)) + { + return LoadAssemblyFromFilePath(localFile); + } + } + } + + return null; + } + + public Assembly LoadAssemblyFromFilePath(string path) + { + if (!_loadInMemory) + { + return LoadFromAssemblyPath(path); + } + + using var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); + var pdbPath = Path.ChangeExtension(path, ".pdb"); + if (File.Exists(pdbPath)) + { + using var pdbFile = File.Open(pdbPath, FileMode.Open, FileAccess.Read, FileShare.Read); + return LoadFromStream(file, pdbFile); + } + return LoadFromStream(file); + + } + + /// + /// Loads the unmanaged binary using configured list of native libraries. + /// + /// + /// + protected override IntPtr LoadUnmanagedDll(string unmanagedDllName) + { + // Hosihikari fork: a plugin package may ship RID-specific native assets without a deps.json. + var runtimeSpecificPath = GetRuntimeSpecificNativeLibraryPath(_basePath, unmanagedDllName); + if (runtimeSpecificPath != null) + { + return LoadUnmanagedDllFromResolvedPath(runtimeSpecificPath); + } + + var resolvedPath = _dependencyResolver.ResolveUnmanagedDllToPath(unmanagedDllName); + if (!string.IsNullOrEmpty(resolvedPath) && File.Exists(resolvedPath)) + { + return LoadUnmanagedDllFromResolvedPath(resolvedPath, normalizePath: false); + } + + foreach (var prefix in PlatformInformation.NativeLibraryPrefixes) + { + if (_nativeLibraries.TryGetValue(prefix + unmanagedDllName, out var library)) + { + if (SearchForLibrary(library, prefix, out var path) && path != null) + { + return LoadUnmanagedDllFromResolvedPath(path); + } + } + else + { + // coreclr allows code to use [DllImport("sni")] or [DllImport("sni.dll")] + // This library treats the file name without the extension as the lookup name, + // so this loop is necessary to check if the unmanaged name matches a library + // when the file extension has been trimmed. + foreach (var suffix in PlatformInformation.NativeLibraryExtensions) + { + if (!unmanagedDllName.EndsWith(suffix, StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + // check to see if there is a library entry for the library without the file extension + var trimmedName = unmanagedDllName.Substring(0, unmanagedDllName.Length - suffix.Length); + + if (_nativeLibraries.TryGetValue(prefix + trimmedName, out library)) + { + if (SearchForLibrary(library, prefix, out var path) && path != null) + { + return LoadUnmanagedDllFromResolvedPath(path); + } + } + else + { + // fallback to native assets which match the file name in the plugin base directory + var prefixSuffixDllName = prefix + unmanagedDllName + suffix; + var prefixDllName = prefix + unmanagedDllName; + + foreach (var probingPath in _additionalProbingPaths.Prepend(_basePath)) + { + var localFile = Path.Combine(probingPath, prefixSuffixDllName); + if (File.Exists(localFile)) + { + return LoadUnmanagedDllFromResolvedPath(localFile); + } + + var localFileWithoutSuffix = Path.Combine(probingPath, prefixDllName); + if (File.Exists(localFileWithoutSuffix)) + { + return LoadUnmanagedDllFromResolvedPath(localFileWithoutSuffix); + } + } + + } + } + + } + } + + return base.LoadUnmanagedDll(unmanagedDllName); + } + + /// + /// Finds a native library under runtimes/<current RID>/native, if the package ships one. + /// + internal static string? GetRuntimeSpecificNativeLibraryPath(string basePath, string unmanagedDllName) + { + var nativeDirectory = Path.Combine(basePath, "runtimes", RuntimeInformation.RuntimeIdentifier, "native"); + if (!Directory.Exists(nativeDirectory)) + { + return null; + } + + foreach (var prefix in PlatformInformation.NativeLibraryPrefixes) + { + foreach (var extension in PlatformInformation.NativeLibraryExtensions) + { + var candidate = Path.Combine(nativeDirectory, prefix + unmanagedDllName + extension); + if (File.Exists(candidate)) + { + return candidate; + } + + candidate = Path.Combine(nativeDirectory, prefix + unmanagedDllName); + if (File.Exists(candidate)) + { + return candidate; + } + } + } + + return null; + } + + private bool SearchForLibrary(ManagedLibrary library, out string? path) + { + // 1. Check for in _basePath + app local path + var localFile = Path.Combine(_basePath, library.AppLocalPath); + if (File.Exists(localFile)) + { + path = localFile; + return true; + } + + // 2. Search additional probing paths + foreach (var searchPath in _additionalProbingPaths) + { + var candidate = Path.Combine(searchPath, library.AdditionalProbingPath); + if (File.Exists(candidate)) + { + path = candidate; + return true; + } + } + + // 3. Search in base path + foreach (var ext in PlatformInformation.ManagedAssemblyExtensions) + { + var local = Path.Combine(_basePath, library.Name.Name + ext); + if (File.Exists(local)) + { + path = local; + return true; + } + } + + path = null; + return false; + } + + private bool SearchForLibrary(NativeLibrary library, string prefix, out string? path) + { + // 1. Search in base path + foreach (var ext in PlatformInformation.NativeLibraryExtensions) + { + var candidate = Path.Combine(_basePath, $"{prefix}{library.Name}{ext}"); + if (File.Exists(candidate)) + { + path = candidate; + return true; + } + } + + // 2. Search in base path + app local (for portable deployments of netcoreapp) + var local = Path.Combine(_basePath, library.AppLocalPath); + if (File.Exists(local)) + { + path = local; + return true; + } + + // 3. Search additional probing paths + foreach (var searchPath in _additionalProbingPaths) + { + var candidate = Path.Combine(searchPath, library.AdditionalProbingPath); + if (File.Exists(candidate)) + { + path = candidate; + return true; + } + } + + path = null; + return false; + } + + private IntPtr LoadUnmanagedDllFromResolvedPath(string unmanagedDllPath, bool normalizePath = true) + { + if (normalizePath) + { + unmanagedDllPath = Path.GetFullPath(unmanagedDllPath); + } + + return _shadowCopyNativeLibraries + ? LoadUnmanagedDllFromShadowCopy(unmanagedDllPath) + : LoadUnmanagedDllFromPath(unmanagedDllPath); + } + + private IntPtr LoadUnmanagedDllFromShadowCopy(string unmanagedDllPath) + { + var shadowCopyDllPath = CreateShadowCopy(unmanagedDllPath); + + return LoadUnmanagedDllFromPath(shadowCopyDllPath); + } + + private string CreateShadowCopy(string dllPath) + { + Directory.CreateDirectory(_unmanagedDllShadowCopyDirectoryPath); + + var dllFileName = Path.GetFileName(dllPath); + var shadowCopyPath = Path.Combine(_unmanagedDllShadowCopyDirectoryPath, dllFileName); + + if (!File.Exists(shadowCopyPath)) + { + File.Copy(dllPath, shadowCopyPath); + } + + return shadowCopyPath; + } + + private void OnUnloaded() + { + if (!_shadowCopyNativeLibraries || !Directory.Exists(_unmanagedDllShadowCopyDirectoryPath)) + { + return; + } + + // Attempt to delete shadow copies + try + { + Directory.Delete(_unmanagedDllShadowCopyDirectoryPath, recursive: true); + } + catch (Exception) + { + // Files might be locked by host process. Nothing we can do about it, I guess. + } + } + } +} diff --git a/src/Plugins/Loader/RuntimeConfigExtensions.cs b/src/Plugins/Loader/RuntimeConfigExtensions.cs new file mode 100644 index 0000000..53e389d --- /dev/null +++ b/src/Plugins/Loader/RuntimeConfigExtensions.cs @@ -0,0 +1,129 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Text.Json; + +namespace McMaster.NETCore.Plugins.Loader +{ + /// + /// Extensions for creating a load context using settings from a runtimeconfig.json file + /// + public static class RuntimeConfigExtensions + { + private const string JsonExt = ".json"; + private static readonly JsonSerializerOptions s_serializerOptions = new() + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }; + + /// + /// Adds additional probing paths to a managed load context using settings found in the runtimeconfig.json + /// and runtimeconfig.dev.json files. + /// + /// The context builder + /// The path to the runtimeconfig.json file + /// Also read runtimeconfig.dev.json file, if present. + /// The error, if one occurs while parsing runtimeconfig.json + /// The builder. + public static AssemblyLoadContextBuilder TryAddAdditionalProbingPathFromRuntimeConfig( + this AssemblyLoadContextBuilder builder, + string runtimeConfigPath, + bool includeDevConfig, + out Exception? error) + { + error = null; + try + { + var config = TryReadConfig(runtimeConfigPath); + if (config == null) + { + return builder; + } + + RuntimeConfig? devConfig = null; + if (includeDevConfig) + { + var configDevPath = runtimeConfigPath.Substring(0, runtimeConfigPath.Length - JsonExt.Length) + ".dev.json"; + devConfig = TryReadConfig(configDevPath); + } + + var tfm = config.RuntimeOptions?.Tfm ?? devConfig?.RuntimeOptions?.Tfm; + + if (config.RuntimeOptions != null) + { + AddProbingPaths(builder, config.RuntimeOptions, tfm); + } + + if (devConfig?.RuntimeOptions != null) + { + AddProbingPaths(builder, devConfig.RuntimeOptions, tfm); + } + + if (tfm != null) + { + var dotnet = Process.GetCurrentProcess().MainModule?.FileName; + if (dotnet != null && string.Equals(Path.GetFileNameWithoutExtension(dotnet), "dotnet", StringComparison.OrdinalIgnoreCase)) + { + var dotnetHome = Path.GetDirectoryName(dotnet); + if (dotnetHome != null) + { + builder.AddProbingPath(Path.Combine(dotnetHome, "store", RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant(), tfm)); + } + } + } + } + catch (Exception ex) + { + error = ex; + } + return builder; + } + + private static void AddProbingPaths(AssemblyLoadContextBuilder builder, RuntimeOptions options, string? tfm) + { + if (options.AdditionalProbingPaths == null) + { + return; + } + + foreach (var item in options.AdditionalProbingPaths) + { + var path = item; + if (path.Contains("|arch|")) + { + path = path.Replace("|arch|", RuntimeInformation.OSArchitecture.ToString().ToLowerInvariant()); + } + + if (path.Contains("|tfm|")) + { + if (tfm == null) + { + // We don't have enough information to parse this + continue; + } + + path = path.Replace("|tfm|", tfm); + } + + builder.AddProbingPath(path); + } + } + + private static RuntimeConfig? TryReadConfig(string path) + { + try + { + var file = File.ReadAllBytes(path); + return JsonSerializer.Deserialize(file, s_serializerOptions); + } + catch + { + return null; + } + } + } +} diff --git a/src/Plugins/McMaster.NETCore.Plugins.csproj b/src/Plugins/McMaster.NETCore.Plugins.csproj new file mode 100644 index 0000000..cac4198 --- /dev/null +++ b/src/Plugins/McMaster.NETCore.Plugins.csproj @@ -0,0 +1,18 @@ + + + + net8.0 + library + true + true + Hosihikari.McMaster.NETCore.Plugins + 2.1.0 + 2.1.0-hosihikari.1 + Hosihikari fork: probe runtimes/<rid>/native before deps.json-based native resolution. + Nate McMaster;Hosihikari framework + https://github.com/HosihikariFramework/Hosihikari.McMaster.NETCore.Plugins + https://github.com/HosihikariFramework/Hosihikari.McMaster.NETCore.Plugins.git + Hosihikari fork of McMaster.NETCore.Plugins. Provides API for dynamically loading assemblies into a .NET application; this fork probes runtimes/<rid>/native before deps.json-based native resolution. + .NET Core;plugins;hosihikari + + diff --git a/src/Plugins/PluginConfig.cs b/src/Plugins/PluginConfig.cs new file mode 100644 index 0000000..8c42b7b --- /dev/null +++ b/src/Plugins/PluginConfig.cs @@ -0,0 +1,124 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; + +namespace McMaster.NETCore.Plugins +{ + /// + /// Represents the configuration for a .NET Core plugin. + /// + public class PluginConfig + { + /// + /// Initializes a new instance of + /// + /// The full file path to the main assembly for the plugin. + public PluginConfig(string mainAssemblyPath) + { + if (string.IsNullOrEmpty(mainAssemblyPath)) + { + throw new ArgumentException("Value must be null or not empty", nameof(mainAssemblyPath)); + } + + if (!Path.IsPathRooted(mainAssemblyPath)) + { + throw new ArgumentException("Value must be an absolute file path", nameof(mainAssemblyPath)); + } + + MainAssemblyPath = mainAssemblyPath; + } + + /// + /// The file path to the main assembly. + /// + public string MainAssemblyPath { get; } + + /// + /// A list of assemblies which should be treated as private. + /// + public ICollection PrivateAssemblies { get; protected set; } = new List(); + + /// + /// A list of assemblies which should be unified between the host and the plugin. + /// + /// + /// https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md + /// + public ICollection SharedAssemblies { get; protected set; } = new List(); + + /// + /// Attempt to unify all types from a plugin with the host. + /// + /// This does not guarantee types will unify. + /// + /// + /// https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md + /// + /// + public bool PreferSharedTypes { get; set; } + + /// + /// If enabled, will lazy load dependencies of all shared assemblies. + /// Reduces plugin load time at the expense of non-determinism in how transitive dependencies are loaded + /// between the plugin and the host. + /// + /// Please be aware of the danger of using this option: + /// + /// https://github.com/natemcmaster/DotNetCorePlugins/pull/164#issuecomment-751557873 + /// + /// + public bool IsLazyLoaded { get; set; } = false; + + /// + /// If set, replaces the default used by the . + /// Use this feature if the of the is not the Runtime's default load context. + /// i.e. (AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly) != + /// + public AssemblyLoadContext DefaultContext { get; set; } = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()) ?? AssemblyLoadContext.Default; + + private bool _isUnloadable; + + /// + /// The plugin can be unloaded from memory. + /// + public bool IsUnloadable + { + get => _isUnloadable || EnableHotReload; + set => _isUnloadable = value; + } + + private bool _loadInMemory; + + /// + /// Loads assemblies into memory in order to not lock files. + /// As example use case here would be: no hot reloading but able to + /// replace files and reload manually at later time + /// + public bool LoadInMemory + { + get => _loadInMemory || EnableHotReload; + set => _loadInMemory = value; + } + + /// + /// When any of the loaded files changes on disk, the plugin will be reloaded. + /// Use the event to be notified of changes. + /// + /// + /// It will load assemblies into memory in order to not lock files + /// + /// + public bool EnableHotReload { get; set; } + + /// + /// Specifies the delay to reload a plugin, after file changes have been detected. + /// Default value is 200 milliseconds. + /// + public TimeSpan ReloadDelay { get; set; } = TimeSpan.FromMilliseconds(200); + } +} diff --git a/src/Plugins/PluginLoader.cs b/src/Plugins/PluginLoader.cs new file mode 100644 index 0000000..eba4216 --- /dev/null +++ b/src/Plugins/PluginLoader.cs @@ -0,0 +1,400 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; +using McMaster.NETCore.Plugins.Internal; +using McMaster.NETCore.Plugins.Loader; + +namespace McMaster.NETCore.Plugins +{ + /// + /// This loader attempts to load binaries for execution (both managed assemblies and native libraries) + /// in the same way that .NET Core would if they were originally part of the .NET Core application. + /// + /// This loader reads configuration files produced by .NET Core (.deps.json and runtimeconfig.json) + /// as well as a custom file (*.config files). These files describe a list of .dlls and a set of dependencies. + /// The loader searches the plugin path, as well as any additionally specified paths, for binaries + /// which satisfy the plugin's requirements. + /// + /// + public class PluginLoader : IDisposable + { + /// + /// Create a plugin loader for an assembly file. + /// + /// The file path to the main assembly for the plugin. + /// Enable unloading the plugin from memory. + /// + /// + /// A list of types which should be shared between the host and the plugin. + /// + /// + /// + /// https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md + /// + /// + /// + /// A loader. + public static PluginLoader CreateFromAssemblyFile(string assemblyFile, bool isUnloadable, Type[] sharedTypes) + => CreateFromAssemblyFile(assemblyFile, isUnloadable, sharedTypes, _ => { }); + + /// + /// Create a plugin loader for an assembly file. + /// + /// The file path to the main assembly for the plugin. + /// Enable unloading the plugin from memory. + /// + /// + /// A list of types which should be shared between the host and the plugin. + /// + /// + /// + /// https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md + /// + /// + /// + /// A function which can be used to configure advanced options for the plugin loader. + /// A loader. + public static PluginLoader CreateFromAssemblyFile(string assemblyFile, bool isUnloadable, Type[] sharedTypes, Action configure) + { + return CreateFromAssemblyFile(assemblyFile, + sharedTypes, + config => + { + config.IsUnloadable = isUnloadable; + configure(config); + }); + } + + /// + /// Create a plugin loader for an assembly file. + /// + /// The file path to the main assembly for the plugin. + /// + /// + /// A list of types which should be shared between the host and the plugin. + /// + /// + /// + /// https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md + /// + /// + /// + /// A loader. + public static PluginLoader CreateFromAssemblyFile(string assemblyFile, Type[] sharedTypes) + => CreateFromAssemblyFile(assemblyFile, sharedTypes, _ => { }); + + /// + /// Create a plugin loader for an assembly file. + /// + /// The file path to the main assembly for the plugin. + /// + /// + /// A list of types which should be shared between the host and the plugin. + /// + /// + /// + /// https://github.com/natemcmaster/DotNetCorePlugins/blob/main/docs/what-are-shared-types.md + /// + /// + /// + /// A function which can be used to configure advanced options for the plugin loader. + /// A loader. + public static PluginLoader CreateFromAssemblyFile(string assemblyFile, Type[] sharedTypes, Action configure) + { + return CreateFromAssemblyFile(assemblyFile, + config => + { + if (sharedTypes != null) + { + var uniqueAssemblies = new HashSet(); + foreach (var type in sharedTypes) + { + uniqueAssemblies.Add(type.Assembly); + } + + foreach (var assembly in uniqueAssemblies) + { + config.SharedAssemblies.Add(assembly.GetName()); + } + } + configure(config); + }); + } + + /// + /// Create a plugin loader for an assembly file. + /// + /// The file path to the main assembly for the plugin. + /// A loader. + public static PluginLoader CreateFromAssemblyFile(string assemblyFile) + => CreateFromAssemblyFile(assemblyFile, _ => { }); + + /// + /// Create a plugin loader for an assembly file. + /// + /// The file path to the main assembly for the plugin. + /// A function which can be used to configure advanced options for the plugin loader. + /// A loader. + public static PluginLoader CreateFromAssemblyFile(string assemblyFile, Action configure) + { + if (configure == null) + { + throw new ArgumentNullException(nameof(configure)); + } + + var config = new PluginConfig(assemblyFile); + configure(config); + return new PluginLoader(config); + } + + private readonly PluginConfig _config; + private ManagedLoadContext _context; + private readonly AssemblyLoadContextBuilder _contextBuilder; + private volatile bool _disposed; + + private FileSystemWatcher? _fileWatcher; + private Debouncer? _debouncer; + + /// + /// Initialize an instance of + /// + /// The configuration for the plugin. + public PluginLoader(PluginConfig config) + { + _config = config ?? throw new ArgumentNullException(nameof(config)); + _contextBuilder = CreateLoadContextBuilder(config); + _context = (ManagedLoadContext)_contextBuilder.Build(); + if (config.EnableHotReload) + { + StartFileWatcher(); + } + } + + /// + /// True when this plugin is capable of being unloaded. + /// + public bool IsUnloadable => _context.IsCollectible; + + + /// + /// This event is raised when the plugin has been reloaded. + /// If was set to true, + /// the plugin will be reloaded when files on disk are changed. + /// + public event PluginReloadedEventHandler? Reloaded; + + /// + /// The unloads and reloads the plugin assemblies. + /// This method throws if is false. + /// + public void Reload() + { + EnsureNotDisposed(); + + if (!IsUnloadable) + { + throw new InvalidOperationException("Reload cannot be used because IsUnloadable is false"); + } + + _context.Unload(); + _context = (ManagedLoadContext)_contextBuilder.Build(); + GC.Collect(); + GC.WaitForPendingFinalizers(); + Reloaded?.Invoke(this, new PluginReloadedEventArgs(this)); + } + + private void StartFileWatcher() + { + /* + This is a very simple implementation. + Some improvements that could be made in the future: + + * Watch all directories which contain assemblies that could be loaded + * Support a polling file watcher. + * Handle delete/recreate better. + + If you're interested in making improvements, feel free to send a pull request. + */ + + _debouncer = new Debouncer(_config.ReloadDelay); + + var watchedDir = Path.GetDirectoryName(_config.MainAssemblyPath); + if (watchedDir == null) + { + throw new InvalidOperationException("Could not determine which directory to watch. " + + "Please set MainAssemblyPath to an absolute path so its parent directory can be discovered."); + } + + _fileWatcher = new FileSystemWatcher + { + Path = watchedDir + }; + _fileWatcher.Changed += OnFileChanged; + _fileWatcher.Filter = "*.dll"; + _fileWatcher.NotifyFilter = NotifyFilters.LastWrite; + _fileWatcher.EnableRaisingEvents = true; + } + + private void OnFileChanged(object source, FileSystemEventArgs e) + { + if (!_disposed) + { + _debouncer?.Execute(Reload); + } + } + + /// + /// Gets the assembly load context which represents the runtime's concept of a scope for assembly loading. + /// + public AssemblyLoadContext LoadContext => _context; + + /// + /// Load the main assembly for the plugin. + /// + public Assembly LoadDefaultAssembly() + { + EnsureNotDisposed(); + return _context.LoadAssemblyFromFilePath(_config.MainAssemblyPath); + } + + /// + /// Load an assembly by name. + /// + /// The assembly name. + /// The assembly. + public Assembly LoadAssembly(AssemblyName assemblyName) + { + EnsureNotDisposed(); + return _context.LoadFromAssemblyName(assemblyName); + } + + /// + /// Load an assembly from path. + /// + /// The assembly path. + /// The assembly. + public Assembly LoadAssemblyFromPath(string assemblyPath) + => _context.LoadAssemblyFromFilePath(assemblyPath); + + /// + /// Load an assembly by name. + /// + /// The assembly name. + /// The assembly. + public Assembly LoadAssembly(string assemblyName) + { + EnsureNotDisposed(); + return LoadAssembly(new AssemblyName(assemblyName)); + } + + /// + /// Sets the scope used by some System.Reflection APIs which might trigger assembly loading. + /// + /// See https://github.com/dotnet/coreclr/blob/v3.0.0/Documentation/design-docs/AssemblyLoadContext.ContextualReflection.md for more details. + /// + /// + /// + public AssemblyLoadContext.ContextualReflectionScope EnterContextualReflection() + => _context.EnterContextualReflection(); + + /// + /// Disposes the plugin loader. This only does something if is true. + /// When true, this will unload assemblies which which were loaded during the lifetime + /// of the plugin. + /// + public void Dispose() + { + if (_disposed) + { + return; + } + + _disposed = true; + + if (_fileWatcher != null) + { + _fileWatcher.EnableRaisingEvents = false; + _fileWatcher.Changed -= OnFileChanged; + _fileWatcher.Dispose(); + } + + _debouncer?.Dispose(); + + if (_context.IsCollectible) + { + _context.Unload(); + } + } + + private void EnsureNotDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException(nameof(PluginLoader)); + } + } + + private static AssemblyLoadContextBuilder CreateLoadContextBuilder(PluginConfig config) + { + var builder = new AssemblyLoadContextBuilder(); + + builder.SetMainAssemblyPath(config.MainAssemblyPath); + builder.SetDefaultContext(config.DefaultContext); + + foreach (var ext in config.PrivateAssemblies) + { + builder.PreferLoadContextAssembly(ext); + } + + if (config.PreferSharedTypes) + { + builder.PreferDefaultLoadContext(true); + } + + if (config.IsUnloadable || config.EnableHotReload) + { + builder.EnableUnloading(); + } + + if (config.LoadInMemory) + { + builder.PreloadAssembliesIntoMemory(); + builder.ShadowCopyNativeLibraries(); + } + + builder.IsLazyLoaded(config.IsLazyLoaded); + foreach (var assemblyName in config.SharedAssemblies) + { + builder.PreferDefaultLoadContextAssembly(assemblyName); + } + + var baseDir = Path.GetDirectoryName(config.MainAssemblyPath); + var assemblyFileName = Path.GetFileNameWithoutExtension(config.MainAssemblyPath); + + if (baseDir == null) + { + throw new InvalidOperationException("Could not determine which directory to watch. " + + "Please set MainAssemblyPath to an absolute path so its parent directory can be discovered."); + } + + var pluginRuntimeConfigFile = Path.Combine(baseDir, assemblyFileName + ".runtimeconfig.json"); + + builder.TryAddAdditionalProbingPathFromRuntimeConfig(pluginRuntimeConfigFile, includeDevConfig: true, out _); + + // Always include runtimeconfig.json from the host app. + // in some cases, like `dotnet test`, the entry assembly does not actually match with the + // runtime config file which is why we search for all files matching this extensions. + foreach (var runtimeconfig in Directory.GetFiles(AppContext.BaseDirectory, "*.runtimeconfig.json")) + { + builder.TryAddAdditionalProbingPathFromRuntimeConfig(runtimeconfig, includeDevConfig: true, out _); + } + + return builder; + } + } +} diff --git a/src/Plugins/PluginReloadedEventHandler.cs b/src/Plugins/PluginReloadedEventHandler.cs new file mode 100644 index 0000000..b94feed --- /dev/null +++ b/src/Plugins/PluginReloadedEventHandler.cs @@ -0,0 +1,34 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace McMaster.NETCore.Plugins +{ + /// + /// Represents the method that will handle the event. + /// + /// The object sending the event + /// Data about the event. + public delegate void PluginReloadedEventHandler(object sender, PluginReloadedEventArgs eventArgs); + + /// + /// Provides data for the event. + /// + public class PluginReloadedEventArgs : EventArgs + { + /// + /// Initializes . + /// + /// + public PluginReloadedEventArgs(PluginLoader loader) + { + Loader = loader; + } + + /// + /// The plugin loader + /// + public PluginLoader Loader { get; } + } +} diff --git a/src/Plugins/Properties/AssemblyInfo.cs b/src/Plugins/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..12f8e14 --- /dev/null +++ b/src/Plugins/Properties/AssemblyInfo.cs @@ -0,0 +1,7 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.CompilerServices; + +[assembly: InternalsVisibleTo("McMaster.NETCore.Plugins.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001001df0eba4297c8ffdf114a13714ad787744619dfb18e29191703f6f782d6a09e4a4cac35b8c768cbbd9ade8197bc0f66ec66fabc9071a206c8060af8b7a332236968d3ee44b90bd2f30d0edcb6150555c6f8d988e48234debaf2d427a08d7c06ba1343411142dc8ac996f7f7dbe0e93d13f17a7624db5400510e6144b0fd683b9")] +[assembly: InternalsVisibleTo("Hosihikari.McMaster.NETCore.Plugins.Tests, PublicKey=00240000048000009400000006020000002400005253413100040000010001001df0eba4297c8ffdf114a13714ad787744619dfb18e29191703f6f782d6a09e4a4cac35b8c768cbbd9ade8197bc0f66ec66fabc9071a206c8060af8b7a332236968d3ee44b90bd2f30d0edcb6150555c6f8d988e48234debaf2d427a08d7c06ba1343411142dc8ac996f7f7dbe0e93d13f17a7624db5400510e6144b0fd683b9")] diff --git a/src/Plugins/PublicAPI.Shipped.txt b/src/Plugins/PublicAPI.Shipped.txt new file mode 100644 index 0000000..a3b1351 --- /dev/null +++ b/src/Plugins/PublicAPI.Shipped.txt @@ -0,0 +1,72 @@ +#nullable enable +McMaster.NETCore.Plugins.LibraryModel.ManagedLibrary +McMaster.NETCore.Plugins.LibraryModel.ManagedLibrary.AdditionalProbingPath.get -> string! +McMaster.NETCore.Plugins.LibraryModel.ManagedLibrary.AppLocalPath.get -> string! +McMaster.NETCore.Plugins.LibraryModel.ManagedLibrary.Name.get -> System.Reflection.AssemblyName! +McMaster.NETCore.Plugins.LibraryModel.NativeLibrary +McMaster.NETCore.Plugins.LibraryModel.NativeLibrary.AdditionalProbingPath.get -> string! +McMaster.NETCore.Plugins.LibraryModel.NativeLibrary.AppLocalPath.get -> string! +McMaster.NETCore.Plugins.LibraryModel.NativeLibrary.Name.get -> string! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.AddManagedLibrary(McMaster.NETCore.Plugins.LibraryModel.ManagedLibrary! library) -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.AddNativeLibrary(McMaster.NETCore.Plugins.LibraryModel.NativeLibrary! library) -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.AddProbingPath(string! path) -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.AddResourceProbingPath(string! path) -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.AssemblyLoadContextBuilder() -> void +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.Build() -> System.Runtime.Loader.AssemblyLoadContext! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.EnableUnloading() -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.IsLazyLoaded(bool isLazyLoaded) -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.PreferDefaultLoadContext(bool preferDefaultLoadContext) -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.PreferDefaultLoadContextAssembly(System.Reflection.AssemblyName! assemblyName) -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.PreferLoadContextAssembly(System.Reflection.AssemblyName! assemblyName) -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.PreloadAssembliesIntoMemory() -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.SetDefaultContext(System.Runtime.Loader.AssemblyLoadContext! context) -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.SetMainAssemblyPath(string! path) -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder.ShadowCopyNativeLibraries() -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +McMaster.NETCore.Plugins.Loader.RuntimeConfigExtensions +McMaster.NETCore.Plugins.PluginConfig +McMaster.NETCore.Plugins.PluginConfig.DefaultContext.get -> System.Runtime.Loader.AssemblyLoadContext! +McMaster.NETCore.Plugins.PluginConfig.DefaultContext.set -> void +McMaster.NETCore.Plugins.PluginConfig.EnableHotReload.get -> bool +McMaster.NETCore.Plugins.PluginConfig.EnableHotReload.set -> void +McMaster.NETCore.Plugins.PluginConfig.IsLazyLoaded.get -> bool +McMaster.NETCore.Plugins.PluginConfig.IsLazyLoaded.set -> void +McMaster.NETCore.Plugins.PluginConfig.IsUnloadable.get -> bool +McMaster.NETCore.Plugins.PluginConfig.IsUnloadable.set -> void +McMaster.NETCore.Plugins.PluginConfig.LoadInMemory.get -> bool +McMaster.NETCore.Plugins.PluginConfig.LoadInMemory.set -> void +McMaster.NETCore.Plugins.PluginConfig.MainAssemblyPath.get -> string! +McMaster.NETCore.Plugins.PluginConfig.PluginConfig(string! mainAssemblyPath) -> void +McMaster.NETCore.Plugins.PluginConfig.PreferSharedTypes.get -> bool +McMaster.NETCore.Plugins.PluginConfig.PreferSharedTypes.set -> void +McMaster.NETCore.Plugins.PluginConfig.PrivateAssemblies.get -> System.Collections.Generic.ICollection! +McMaster.NETCore.Plugins.PluginConfig.PrivateAssemblies.set -> void +McMaster.NETCore.Plugins.PluginConfig.ReloadDelay.get -> System.TimeSpan +McMaster.NETCore.Plugins.PluginConfig.ReloadDelay.set -> void +McMaster.NETCore.Plugins.PluginConfig.SharedAssemblies.get -> System.Collections.Generic.ICollection! +McMaster.NETCore.Plugins.PluginConfig.SharedAssemblies.set -> void +McMaster.NETCore.Plugins.PluginLoader +McMaster.NETCore.Plugins.PluginLoader.Dispose() -> void +McMaster.NETCore.Plugins.PluginLoader.EnterContextualReflection() -> System.Runtime.Loader.AssemblyLoadContext.ContextualReflectionScope +McMaster.NETCore.Plugins.PluginLoader.IsUnloadable.get -> bool +McMaster.NETCore.Plugins.PluginLoader.LoadAssembly(string! assemblyName) -> System.Reflection.Assembly! +McMaster.NETCore.Plugins.PluginLoader.LoadAssembly(System.Reflection.AssemblyName! assemblyName) -> System.Reflection.Assembly! +McMaster.NETCore.Plugins.PluginLoader.LoadAssemblyFromPath(string! assemblyPath) -> System.Reflection.Assembly! +McMaster.NETCore.Plugins.PluginLoader.LoadContext.get -> System.Runtime.Loader.AssemblyLoadContext! +McMaster.NETCore.Plugins.PluginLoader.LoadDefaultAssembly() -> System.Reflection.Assembly! +McMaster.NETCore.Plugins.PluginLoader.PluginLoader(McMaster.NETCore.Plugins.PluginConfig! config) -> void +McMaster.NETCore.Plugins.PluginLoader.Reload() -> void +McMaster.NETCore.Plugins.PluginLoader.Reloaded -> McMaster.NETCore.Plugins.PluginReloadedEventHandler? +McMaster.NETCore.Plugins.PluginReloadedEventArgs +McMaster.NETCore.Plugins.PluginReloadedEventArgs.Loader.get -> McMaster.NETCore.Plugins.PluginLoader! +McMaster.NETCore.Plugins.PluginReloadedEventArgs.PluginReloadedEventArgs(McMaster.NETCore.Plugins.PluginLoader! loader) -> void +McMaster.NETCore.Plugins.PluginReloadedEventHandler +static McMaster.NETCore.Plugins.LibraryModel.ManagedLibrary.CreateFromPackage(string! packageId, string! packageVersion, string! assetPath) -> McMaster.NETCore.Plugins.LibraryModel.ManagedLibrary! +static McMaster.NETCore.Plugins.LibraryModel.NativeLibrary.CreateFromPackage(string! packageId, string! packageVersion, string! assetPath) -> McMaster.NETCore.Plugins.LibraryModel.NativeLibrary! +static McMaster.NETCore.Plugins.Loader.RuntimeConfigExtensions.TryAddAdditionalProbingPathFromRuntimeConfig(this McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! builder, string! runtimeConfigPath, bool includeDevConfig, out System.Exception? error) -> McMaster.NETCore.Plugins.Loader.AssemblyLoadContextBuilder! +static McMaster.NETCore.Plugins.PluginLoader.CreateFromAssemblyFile(string! assemblyFile) -> McMaster.NETCore.Plugins.PluginLoader! +static McMaster.NETCore.Plugins.PluginLoader.CreateFromAssemblyFile(string! assemblyFile, bool isUnloadable, System.Type![]! sharedTypes) -> McMaster.NETCore.Plugins.PluginLoader! +static McMaster.NETCore.Plugins.PluginLoader.CreateFromAssemblyFile(string! assemblyFile, bool isUnloadable, System.Type![]! sharedTypes, System.Action! configure) -> McMaster.NETCore.Plugins.PluginLoader! +static McMaster.NETCore.Plugins.PluginLoader.CreateFromAssemblyFile(string! assemblyFile, System.Action! configure) -> McMaster.NETCore.Plugins.PluginLoader! +static McMaster.NETCore.Plugins.PluginLoader.CreateFromAssemblyFile(string! assemblyFile, System.Type![]! sharedTypes) -> McMaster.NETCore.Plugins.PluginLoader! +static McMaster.NETCore.Plugins.PluginLoader.CreateFromAssemblyFile(string! assemblyFile, System.Type![]! sharedTypes, System.Action! configure) -> McMaster.NETCore.Plugins.PluginLoader! diff --git a/src/Plugins/PublicAPI.Unshipped.txt b/src/Plugins/PublicAPI.Unshipped.txt new file mode 100644 index 0000000..e69de29 diff --git a/src/Plugins/releasenotes.props b/src/Plugins/releasenotes.props new file mode 100644 index 0000000..f0f6d8e --- /dev/null +++ b/src/Plugins/releasenotes.props @@ -0,0 +1,98 @@ + + + +Breaking changes: +* Require .NET >= 8.0 +* Drop dependencies on .NET Core 2 libraries for manually parsing .deps.json. Use the built-in .NET 8 API for this. + + +Changes: +* @Sewer56 - feature: add option to support lazy loading of transitive dependencies to increase performance (PR #164) +* @Sewer56 - bugfix: search in additional probing paths (PR #172) + + +Changes: +* @KatoStoelen - don't create shadow copy that already exists (PR #147) + + +Changes: +* @bergi9 - add 'LoadInMemory' option so you can manually trigger reloads (alternative to HotReload) (PR #133) + + +Changes: +* @thoemmi - Debounce file system events when using hot reload and add API to control the debounce delay (default = 200 ms) (PR #129) + + +Changes: +* Add an API to enable shadow copying native library dependencies (PR #119) +* Fix issue with native libraries being locked on disk during hot reload (Issue #118) + + +Changes: +* Add an API to enable contextual reflection in .NET Core 3+ (see https://github.com/natemcmaster/DotNetCorePlugins/blob/v1.0.0/README.md#Reflection for details) +* @Sewer56 - Add support for non-default AssemblyLoadContext's (useful for plugins which load more plugins and native .NET Core hosting) (#111) +* Remove API that was made obsolete in 0.3.0 + + +Fixes: +* Fix issue preventing hot reload from working on Windows (#108) + + +Fixes: +* @CopaDataPM: Support CoreCLR in native apps (#80) + + +Changes: +* .NET Core 3.0 support +* Support unloading plugins from memory (only .NET Core >3.0) +* Support hot reloading (only .NET Core >3.0) + +Fixes: +* Fix errors in loading the transitive dependencies of shared types + +Breaking changes: +* Support for loading plugin config from an XML file has been dropped. The APIs for PluginLoader.CreateFromConfigFile were removed + in favor of PluginLoader.CreateFromAssemblyFile +* Merged PluginLoaderOptions with the PluginConfig class + + + +Bug fix: +* Fix the MSBuild targets which generate plugin.config to put it into the correct output directory. + + +Bug fix: +* Fix config file generation when using the SDK package + + + + + + + + + + + + + + + diff --git a/src/StrongName.snk b/src/StrongName.snk new file mode 100644 index 0000000000000000000000000000000000000000..315f8a5f72877cd0f7f353debf2e97165f303dc7 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50096U@av>0e2@L{6rnd1t$24tVV(OJ;*pVX zKW}&~Y6;||%EMcXc8t5(t>_th!1iv&Z>z}%8X#iyy%yo!x@|w*AOw#nEYWZRgnKavjwp4n*B+WqXCk)su?+ zV4l79acCfh3y+Ya5{y`>CBWH3x^uBKD6XHfYDC+>+m8zp%KVy) zXpAH_G}q$R^DV|S_5@F7TdO6zvjydgXA7cME7r(2jiB0Q|2upNBd5SheRVk%yn=aI zr*ZF@mNiPP61kB?v(o}A13G(+I_V!5gykN&t=}baG8=&7VVR1G`Lu(FgyRcrSM{Z$G9RNAE&G^E}R|IR5{;HlcgnCfs>Iz zc?L6fCa z8+4?ObWC?GrkIMreWE?OU<1HAQW#Q7z|M*5@P?pW1_O<>%|bJrjIxp5Xm9^=Ed{nz zAy{;Xu1e=k@+m64)LOXDB_0hDIphDgu>> $_cmd $args" + $ErrorActionPreference = 'Continue' + & $_cmd @args + $ErrorActionPreference = 'Stop' + if ($LASTEXITCODE -ne 0) { + write-error "Failed with exit code $LASTEXITCODE" + exit 1 + } +} diff --git a/test/Hosihikari.Tests/Hosihikari.McMaster.NETCore.Plugins.Tests.csproj b/test/Hosihikari.Tests/Hosihikari.McMaster.NETCore.Plugins.Tests.csproj new file mode 100644 index 0000000..3d51ab6 --- /dev/null +++ b/test/Hosihikari.Tests/Hosihikari.McMaster.NETCore.Plugins.Tests.csproj @@ -0,0 +1,19 @@ + + + + net10.0 + false + true + + + + + + + + + + + + + diff --git a/test/Hosihikari.Tests/NativeRidPathTests.cs b/test/Hosihikari.Tests/NativeRidPathTests.cs new file mode 100644 index 0000000..fd2a820 --- /dev/null +++ b/test/Hosihikari.Tests/NativeRidPathTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Runtime.InteropServices; +using McMaster.NETCore.Plugins.Loader; +using Xunit; + +namespace Hosihikari.McMaster.NETCore.Plugins.Tests +{ + public class NativeRidPathTests + { + [Fact] + public void PrefersRuntimeSpecificNativeDirectory() + { + var basePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + try + { + var nativeDirectory = Path.Combine(basePath, "runtimes", RuntimeInformation.RuntimeIdentifier, "native"); + Directory.CreateDirectory(nativeDirectory); + var fileName = OperatingSystem.IsWindows() ? "probe.dll" + : OperatingSystem.IsMacOS() ? "libprobe.dylib" + : "libprobe.so"; + var expected = Path.Combine(nativeDirectory, fileName); + File.WriteAllText(expected, ""); + + Assert.Equal(expected, ManagedLoadContext.GetRuntimeSpecificNativeLibraryPath(basePath, "probe")); + Assert.Null(ManagedLoadContext.GetRuntimeSpecificNativeLibraryPath(basePath, "missing")); + } + finally + { + if (Directory.Exists(basePath)) + { + Directory.Delete(basePath, recursive: true); + } + } + } + } +} diff --git a/test/Plugins.Tests/BasicAssemblyLoaderTests.cs b/test/Plugins.Tests/BasicAssemblyLoaderTests.cs new file mode 100644 index 0000000..2e1535f --- /dev/null +++ b/test/Plugins.Tests/BasicAssemblyLoaderTests.cs @@ -0,0 +1,147 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using System.Runtime.CompilerServices; +using McMaster.Extensions.Xunit; +using Test.Referenced.Library; +using Xunit; +using Xunit.Abstractions; + +namespace McMaster.NETCore.Plugins.Tests +{ + public class BasicAssemblyLoaderTests + { + private readonly ITestOutputHelper output; + + public BasicAssemblyLoaderTests(ITestOutputHelper output) + { + this.output = output; + } + + [Fact] + public void PluginLoaderCanUnload() + { + var path = TestResources.GetTestProjectAssembly("NetCoreApp2App"); + + // See https://github.com/dotnet/coreclr/pull/22221 + + ExecuteAndUnload(path, out var weakRef); + + // Force a GC collect to ensure unloaded has completed + for (var i = 0; weakRef.IsAlive && (i < 10); i++) + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + Assert.False(weakRef.IsAlive); + } + + [MethodImpl(MethodImplOptions.NoInlining)] // ensure no local vars are create + private void ExecuteAndUnload(string path, out WeakReference weakRef) + { + var loader = PluginLoader.CreateFromAssemblyFile(path, c => { c.IsUnloadable = true; }); + var assembly = loader.LoadDefaultAssembly(); + + var method = assembly + .GetType("NetCoreApp2App.Program", throwOnError: true)! + .GetMethod("GetGreeting", BindingFlags.Static | BindingFlags.Public); + + Assert.True(loader.IsUnloadable); + Assert.NotNull(method); + Assert.Equal("Hello world!", method!.Invoke(null, Array.Empty())); + loader.Dispose(); + Assert.Throws(() => loader.LoadDefaultAssembly()); + + weakRef = new WeakReference(loader.LoadContext, trackResurrection: true); + } + + [Fact] + public void LoadsNetCoreProjectWithNativeDeps() + { + var path = TestResources.GetTestProjectAssembly("PowerShellPlugin"); + var loader = PluginLoader.CreateFromAssemblyFile(path); + var assembly = loader.LoadDefaultAssembly(); + + var method = assembly + .GetType("PowerShellPlugin.Program", throwOnError: true)! + .GetMethod("GetGreeting", BindingFlags.Static | BindingFlags.Public); + Assert.NotNull(method); + Assert.Equal("hello", method!.Invoke(null, Array.Empty())); + } + + [SkippableFact] + [SkipOnOS(OS.Linux | OS.MacOS)] + public void LoadsNativeDependenciesWhenDllImportUsesFilename() + { + // SqlClient has P/invoke that calls "sni.dll" on Windows. This test checks + // that native libraries can still be resolved in this case. + var path = TestResources.GetTestProjectAssembly("SqlClientApp"); + var loader = PluginLoader.CreateFromAssemblyFile(path); + var assembly = loader.LoadDefaultAssembly(); + + var method = assembly + .GetType("SqlClientApp.Program", throwOnError: true)! + .GetMethod("Run", BindingFlags.Static | BindingFlags.Public); + Assert.NotNull(method); + Assert.Equal(true, method!.Invoke(null, Array.Empty())); + } + + [Fact] + public void LoadsNetCoreApp2Project() + { + var path = TestResources.GetTestProjectAssembly("NetCoreApp2App"); + var loader = PluginLoader.CreateFromAssemblyFile(path); + var assembly = loader.LoadDefaultAssembly(); + + var method = assembly + .GetType("NetCoreApp2App.Program", throwOnError: true)! + .GetMethod("GetGreeting", BindingFlags.Static | BindingFlags.Public); + Assert.NotNull(method); + Assert.Equal("Hello world!", method!.Invoke(null, Array.Empty())); + } + + [Fact] + public void LoadsNetStandard20Project() + { + var path = TestResources.GetTestProjectAssembly("NetStandardClassLib"); + var loader = PluginLoader.CreateFromAssemblyFile(path); + var assembly = loader.LoadDefaultAssembly(); + + var type = assembly.GetType("NetStandardClassLib.Class1", throwOnError: true); + var method = type!.GetMethod("GetColor", BindingFlags.Instance | BindingFlags.Public); + Assert.NotNull(method); + Assert.Equal("Red", method!.Invoke(Activator.CreateInstance(type), Array.Empty())); + } + + [Fact] + [UseCulture("es")] + public void ItLoadsSatelliteAssemblies() + { + var fruit = GetPlátano(); + Assert.Equal("Plátano", fruit.GetFlavor()); + } + + [Fact] + [UseCulture("en")] + public void ItLoadsDefaultCultureAssemblies() + { + var fruit = GetPlátano(); + Assert.Equal("Banana", fruit.GetFlavor()); + } + + private IFruit GetPlátano() + { + var path = TestResources.GetTestProjectAssembly("Plátano"); + var loader = PluginLoader.CreateFromAssemblyFile(path, + isUnloadable: true, + sharedTypes: new[] { typeof(IFruit) }); + + var assembly = loader.LoadDefaultAssembly(); + var type = Assert.Single(assembly.GetTypes(), t => typeof(IFruit).IsAssignableFrom(t)); + return (IFruit)Activator.CreateInstance(type)!; + } + } +} diff --git a/test/Plugins.Tests/DebouncerTests.cs b/test/Plugins.Tests/DebouncerTests.cs new file mode 100644 index 0000000..67994f6 --- /dev/null +++ b/test/Plugins.Tests/DebouncerTests.cs @@ -0,0 +1,60 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Threading.Tasks; +using McMaster.NETCore.Plugins.Internal; +using Xunit; + +namespace McMaster.NETCore.Plugins.Tests +{ + public class DebouncerTests + { + [Fact] + public async Task InvocationIsDelayed() + { + var executionCounter = 0; + + var debouncer = new Debouncer(TimeSpan.FromSeconds(.1)); + debouncer.Execute(() => executionCounter++); + + Assert.Equal(0, executionCounter); + + await Task.Delay(TimeSpan.FromSeconds(.5)); + + Assert.Equal(1, executionCounter); + } + + [Fact] + public async Task ActionsAreDebounced() + { + var executionCounter = 0; + + var debouncer = new Debouncer(TimeSpan.FromSeconds(.1)); + debouncer.Execute(() => executionCounter++); + debouncer.Execute(() => executionCounter++); + debouncer.Execute(() => executionCounter++); + + await Task.Delay(TimeSpan.FromSeconds(.5)); + + Assert.Equal(1, executionCounter); + } + + [Fact] + public async Task OnlyLastActionIsInvoked() + { + string? invokedAction = null; + + var debouncer = new Debouncer(TimeSpan.FromSeconds(.1)); + foreach (var action in new[] { "a", "b", "c" }) + { + debouncer.Execute(() => invokedAction = action); + } + + await Task.Delay(TimeSpan.FromSeconds(.5)); + + Assert.NotNull(invokedAction); + Assert.Equal("c", invokedAction); + } + } +} diff --git a/test/Plugins.Tests/ManageLoadContextTests.cs b/test/Plugins.Tests/ManageLoadContextTests.cs new file mode 100644 index 0000000..44f28b0 --- /dev/null +++ b/test/Plugins.Tests/ManageLoadContextTests.cs @@ -0,0 +1,65 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; +using System.Reflection; +using System.Runtime.Loader; +using McMaster.NETCore.Plugins.Loader; +using Xunit; + +namespace McMaster.NETCore.Plugins.Tests +{ + public class ManagedLoadContextTests + { + [Fact] + public void ItUpgradesTypesInContext() + { + var samplePath = TestResources.GetTestProjectAssembly("XunitSample"); + var context = new AssemblyLoadContextBuilder() + .SetMainAssemblyPath(samplePath) + .AddProbingPath(samplePath) + .PreferDefaultLoadContext(true) + .Build(); + + Assert.Same(typeof(TheoryData).Assembly, LoadAssembly(context, "xunit.core")); + } + + [Fact] + public void ContextsHavePrivateVersionsByDefault() + { + var samplePath = TestResources.GetTestProjectAssembly("XunitSample"); + + var context = new AssemblyLoadContextBuilder() + .SetMainAssemblyPath(samplePath) + .AddProbingPath(samplePath) + .Build(); + + Assert.NotSame(typeof(TheoryData).Assembly, LoadAssembly(context, "xunit.core")); + } + + [Fact] + public void ItCanDowngradeUnifiedTypes() + { + var samplePath = TestResources.GetTestProjectAssembly("NetCoreApp2App"); + + var defaultLoader = new AssemblyLoadContextBuilder() + .SetMainAssemblyPath(samplePath) + .AddProbingPath(samplePath) + .PreferDefaultLoadContext(false) + .Build(); + + var unifedLoader = new AssemblyLoadContextBuilder() + .SetMainAssemblyPath(samplePath) + .AddProbingPath(samplePath) + .PreferDefaultLoadContext(true) + .Build(); + + Assert.Equal(new Version("2.0.0.0"), LoadAssembly(defaultLoader, "Test.Referenced.Library").GetName().Version); + Assert.Equal(new Version("1.0.0.0"), LoadAssembly(unifedLoader, "Test.Referenced.Library").GetName().Version); + } + + private Assembly LoadAssembly(AssemblyLoadContext context, string name) + => context.LoadFromAssemblyName(new AssemblyName(name)); + } +} diff --git a/test/Plugins.Tests/McMaster.NETCore.Plugins.Tests.csproj b/test/Plugins.Tests/McMaster.NETCore.Plugins.Tests.csproj new file mode 100644 index 0000000..f295757 --- /dev/null +++ b/test/Plugins.Tests/McMaster.NETCore.Plugins.Tests.csproj @@ -0,0 +1,41 @@ + + + + net8.0 + $(DefaultItemExcludes);TestResults\** + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/Plugins.Tests/PrivateDependencyTests.cs b/test/Plugins.Tests/PrivateDependencyTests.cs new file mode 100644 index 0000000..9148312 --- /dev/null +++ b/test/Plugins.Tests/PrivateDependencyTests.cs @@ -0,0 +1,40 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Reflection; +using Xunit; + +namespace McMaster.NETCore.Plugins.Tests +{ + public class PrivateDependencyTests + { + [Fact] + public void EachContextHasPrivateVersions() + { + var lib1context = PluginLoader.CreateFromAssemblyFile(TestResources.GetTestProjectAssembly("PrivateDepv1")); + var lib2context = PluginLoader.CreateFromAssemblyFile(TestResources.GetTestProjectAssembly("PrivateDepv2")); + var lib3context = PluginLoader.CreateFromAssemblyFile(TestResources.GetTestProjectAssembly("PrivateDepv3")); + + // Load newest first to prove we can load older assemblies later into the same process + var lib3 = GetAssembly(lib3context); + var lib2 = GetAssembly(lib2context); + var lib1 = GetAssembly(lib1context); + + Assert.Equal(new Version("1.0.0.0"), lib1.GetName().Version); + Assert.Equal(new Version("2.0.0.0"), lib2.GetName().Version); + Assert.Equal(new Version("3.0.0.0"), lib3.GetName().Version); + + // types from each context have unique identities + Assert.NotEqual( + lib1.GetType("Mylib.Class1", throwOnError: true), + lib2.GetType("Mylib.Class1", throwOnError: true)); + Assert.NotEqual( + lib2.GetType("Mylib.Class1", throwOnError: true), + lib3.GetType("Mylib.Class1", throwOnError: true)); + } + + private Assembly GetAssembly(PluginLoader loader) + => loader.LoadAssembly(new AssemblyName("Mylib")); + } +} diff --git a/test/Plugins.Tests/ShadowCopyTests.cs b/test/Plugins.Tests/ShadowCopyTests.cs new file mode 100644 index 0000000..af11102 --- /dev/null +++ b/test/Plugins.Tests/ShadowCopyTests.cs @@ -0,0 +1,28 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Xunit; + +namespace McMaster.NETCore.Plugins.Tests +{ + public class ShadowCopyTests + { + [Fact] + public void DoesNotThrowWhenLoadingSameNativeDependecyMoreThanOnce() + { + var samplePath = TestResources.GetTestProjectAssembly("NativeDependency"); + + using var loader = PluginLoader + .CreateFromAssemblyFile(samplePath, config => config.EnableHotReload = true); + + var nativeDependencyLoadMethod = loader.LoadDefaultAssembly() + ?.GetType("NativeDependency.NativeDependencyLoader") + ?.GetMethod("Load"); + + var exception = Record.Exception(() => nativeDependencyLoadMethod?.Invoke(null, null)); + + Assert.Null(exception); + } + } +} + diff --git a/test/Plugins.Tests/SharedTypesTests.cs b/test/Plugins.Tests/SharedTypesTests.cs new file mode 100644 index 0000000..f03f42c --- /dev/null +++ b/test/Plugins.Tests/SharedTypesTests.cs @@ -0,0 +1,107 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using Test.Referenced.Library; +using Test.Shared.Abstraction; +using WithOwnPluginsContract; +using Xunit; + +namespace McMaster.NETCore.Plugins.Tests +{ + public class SharedTypesTests + { + [Fact] + public void PluginsCanForceSharedTypes() + { + var pluginsNames = new[] { "Banana", "Strawberry" }; + var loaders = new List(); + foreach (var name in pluginsNames) + { + var loader = PluginLoader.CreateFromAssemblyFile( + TestResources.GetTestProjectAssembly(name), + sharedTypes: new[] { typeof(IFruit) }); + loaders.Add(loader); + } + + foreach (var plugin in loaders.Select(l => l.LoadDefaultAssembly())) + { + var fruitType = Assert.Single(plugin.GetTypes(), t => typeof(IFruit).IsAssignableFrom(t)); + var fruit = (IFruit)Activator.CreateInstance(fruitType)!; + Assert.NotNull(fruit.GetFlavor()); + } + } + + /// + /// This is a carefully crafted example which tests + /// that the assembly dependencies of shared types are + /// accounted for. Without this, the order in which code loads + /// could cause different assembly versions to be loaded. + /// + [Theory] + [InlineData(true)] + [InlineData(false)] + public void TransitiveAssembliesOfSharedTypesAreResolved(bool isLazyLoaded) + { + using var loader = PluginLoader.CreateFromAssemblyFile(TestResources.GetTestProjectAssembly("TransitivePlugin"), sharedTypes: new[] { typeof(SharedType) }, config => config.IsLazyLoaded = isLazyLoaded); + var assembly = loader.LoadDefaultAssembly(); + var configType = assembly.GetType("TransitivePlugin.PluginConfig", throwOnError: true)!; + var config = Activator.CreateInstance(configType); + var transitiveInstance = configType.GetMethod("GetTransitiveType")?.Invoke(config, null); + Assert.IsType(transitiveInstance); + } + + /// + /// This is a carefully crafted example which tests + /// whether the library can be used outside of the default load context + /// (). + /// + /// It works by loading a plugin (that gets loaded into another ALC) + /// which in turn loads its own plugins using the library. If said plugin + /// can successfully share its own types, the test should work. + /// + [Fact] + public void NonDefaultLoadContextsAreSupported() + { + /* The loaded plugin here will be in its own ALC. + * It will load its own plugins, which are not known to this ALC. + * Then this ALC will ask that ALC if it managed to successfully its own plugins. + */ + + using var loader = PluginLoader.CreateFromAssemblyFile(TestResources.GetTestProjectAssembly("WithOwnPlugins"), new[] { typeof(IWithOwnPlugins) }); + var assembly = loader.LoadDefaultAssembly(); + var configType = assembly.GetType("WithOwnPlugins.WithOwnPlugins", throwOnError: true)!; + var config = (IWithOwnPlugins?)Activator.CreateInstance(configType); + + /* + * Here, we have made sure that neither WithOwnPlugins or its own plugins have any way to be + * accidentally unified with the default (current for our tests) ALC. We did this by ensuring they are + * not loaded in the default ALC in the first place, hence the use of the `IWithOwnPlugins` interface. + * + * We are simulating a real use case scenario where the plugin host is 100% unaware of the + * plugin's own plugins. + * + * An important additional note: + * - Although the assembly of WithOurPlugins is not directly referenced thanks to the + * ReferenceOutputAssembly = false property, its contents will still be copied to the output. + * - This is problematic because the test runner seems to load all of the Assemblies present in the same + * directory as the test assembly, regardless of whether referenced or not. + * - Therefore we store the plugins of `WithOwnPlugins` are output in a `Plugins` directory. + * (see csproj of WithOwnPlugins, Link property) + * + * You can ensure that WithOwnPlugins or its plugins are not loaded by inspecting the following: + * AssemblyLoadContext.Default.Assemblies + * + * Even if it was loaded, there's an extra check on the other side to ensure no unification could happen. + * Nothing wrong with being extra careful ;). + */ + + var callingContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()); + Assert.True(config?.TryLoadPluginsInCustomContext(callingContext)); + } + } +} diff --git a/test/Plugins.Tests/TestProjectRefs.targets b/test/Plugins.Tests/TestProjectRefs.targets new file mode 100644 index 0000000..17e1b82 --- /dev/null +++ b/test/Plugins.Tests/TestProjectRefs.targets @@ -0,0 +1,50 @@ + + + + + false + true + _ResolvedTestProjectReference + + + false + true + _ResolvedTestProjectReference + TargetFramework;TargetFrameworks + + + false + true + _ResolvedPublishedTestProjectReference + TargetFramework;TargetFrameworks + + + + + + + + + + + <_Parameter1>%(_ResolvedTestProjectReference.FileName) + <_Parameter2>%(_ResolvedTestProjectReference.RootDir)%(_ResolvedTestProjectReference.Directory)%(_ResolvedTestProjectReference.FileName).dll + + + + + + + + <_Parameter1>%(PublishedTestProject.FileName) + <_Parameter2>$(TargetDir)%(PublishedTestProject.FileName)/%(PublishedTestProject.FileName).dll + + + + + diff --git a/test/Plugins.Tests/Utilities/TestProjectReferenceAttribute.cs b/test/Plugins.Tests/Utilities/TestProjectReferenceAttribute.cs new file mode 100644 index 0000000..4246596 --- /dev/null +++ b/test/Plugins.Tests/Utilities/TestProjectReferenceAttribute.cs @@ -0,0 +1,20 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace McMaster.NETCore.Plugins.Tests +{ + [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] + public class TestProjectReferenceAttribute : Attribute + { + public TestProjectReferenceAttribute(string name, string path) + { + Name = name; + Path = path; + } + + public string Name { get; } + public string Path { get; } + } +} diff --git a/test/Plugins.Tests/Utilities/TestResources.cs b/test/Plugins.Tests/Utilities/TestResources.cs new file mode 100644 index 0000000..43f17d4 --- /dev/null +++ b/test/Plugins.Tests/Utilities/TestResources.cs @@ -0,0 +1,20 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Linq; +using System.Reflection; + +namespace McMaster.NETCore.Plugins.Tests +{ + public class TestResources + { + public static string GetTestProjectAssembly(string name) + { + return typeof(TestResources) + .Assembly + .GetCustomAttributes() + .First(a => a.Name == name) + .Path; + } + } +} diff --git a/test/TestProjects/Banana/Banana.cs b/test/TestProjects/Banana/Banana.cs new file mode 100644 index 0000000..3f670b7 --- /dev/null +++ b/test/TestProjects/Banana/Banana.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Test.Referenced.Library; + +namespace Test +{ + internal class Banana : IFruit + { + public string GetFlavor() => nameof(Banana); + } +} diff --git a/test/TestProjects/Banana/Banana.csproj b/test/TestProjects/Banana/Banana.csproj new file mode 100644 index 0000000..7e66041 --- /dev/null +++ b/test/TestProjects/Banana/Banana.csproj @@ -0,0 +1,11 @@ + + + + + + + + net8.0 + + + diff --git a/test/TestProjects/Directory.Build.props b/test/TestProjects/Directory.Build.props new file mode 100644 index 0000000..e4594cb --- /dev/null +++ b/test/TestProjects/Directory.Build.props @@ -0,0 +1,7 @@ + + + + + true + + diff --git a/test/TestProjects/DrawingApp/DrawingApp.csproj b/test/TestProjects/DrawingApp/DrawingApp.csproj new file mode 100644 index 0000000..b9c29d8 --- /dev/null +++ b/test/TestProjects/DrawingApp/DrawingApp.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/test/TestProjects/DrawingApp/Finder.cs b/test/TestProjects/DrawingApp/Finder.cs new file mode 100644 index 0000000..5efab3e --- /dev/null +++ b/test/TestProjects/DrawingApp/Finder.cs @@ -0,0 +1,13 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Drawing.Printing; + +public class Finder +{ + public static string FindDrawingAssembly() + { + return typeof(PrinterUnit).Assembly.Location; + } +} + diff --git a/test/TestProjects/Libv1/Class1.cs b/test/TestProjects/Libv1/Class1.cs new file mode 100644 index 0000000..d36396c --- /dev/null +++ b/test/TestProjects/Libv1/Class1.cs @@ -0,0 +1,9 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Mylib; + +public class Class1 +{ + +} diff --git a/test/TestProjects/Libv1/Libv1.csproj b/test/TestProjects/Libv1/Libv1.csproj new file mode 100644 index 0000000..04248e8 --- /dev/null +++ b/test/TestProjects/Libv1/Libv1.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + 1.0.0.0 + Mylib + + + diff --git a/test/TestProjects/Libv2/Class1.cs b/test/TestProjects/Libv2/Class1.cs new file mode 100644 index 0000000..d36396c --- /dev/null +++ b/test/TestProjects/Libv2/Class1.cs @@ -0,0 +1,9 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Mylib; + +public class Class1 +{ + +} diff --git a/test/TestProjects/Libv2/Libv2.csproj b/test/TestProjects/Libv2/Libv2.csproj new file mode 100644 index 0000000..418068e --- /dev/null +++ b/test/TestProjects/Libv2/Libv2.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + 2.0.0.0 + Mylib + + + diff --git a/test/TestProjects/Libv3/Class1.cs b/test/TestProjects/Libv3/Class1.cs new file mode 100644 index 0000000..d36396c --- /dev/null +++ b/test/TestProjects/Libv3/Class1.cs @@ -0,0 +1,9 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Mylib; + +public class Class1 +{ + +} diff --git a/test/TestProjects/Libv3/Libv3.csproj b/test/TestProjects/Libv3/Libv3.csproj new file mode 100644 index 0000000..0bc0ab1 --- /dev/null +++ b/test/TestProjects/Libv3/Libv3.csproj @@ -0,0 +1,9 @@ + + + + net8.0 + 3.0.0.0 + Mylib + + + diff --git a/test/TestProjects/NativeDependency/NativeDependency.csproj b/test/TestProjects/NativeDependency/NativeDependency.csproj new file mode 100644 index 0000000..ba04eb3 --- /dev/null +++ b/test/TestProjects/NativeDependency/NativeDependency.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + + + + + + + diff --git a/test/TestProjects/NativeDependency/NativeDependencyLoader.cs b/test/TestProjects/NativeDependency/NativeDependencyLoader.cs new file mode 100644 index 0000000..9058b50 --- /dev/null +++ b/test/TestProjects/NativeDependency/NativeDependencyLoader.cs @@ -0,0 +1,41 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.IO; + +using Microsoft.Data.Sqlite; + +namespace NativeDependency +{ + public static class NativeDependencyLoader + { + public static void Load() + { + using var tempFile = new TempFile("db.sqlite"); + using var dbConnection = new SqliteConnection($"Data Source={tempFile.FilePath}"); + + dbConnection.Open(); + } + } + + public class TempFile : IDisposable + { + public TempFile(string fileName) + { + FilePath = Path.Combine(Path.GetTempPath(), fileName); + } + + public string FilePath { get; } + + public void Dispose() + { + if (!File.Exists(FilePath)) + { + return; + } + + File.Delete(FilePath); + } + } +} diff --git a/test/TestProjects/NetCoreApp2App/NetCoreApp2App.csproj b/test/TestProjects/NetCoreApp2App/NetCoreApp2App.csproj new file mode 100644 index 0000000..52e8b7d --- /dev/null +++ b/test/TestProjects/NetCoreApp2App/NetCoreApp2App.csproj @@ -0,0 +1,12 @@ + + + + Exe + net8.0 + + + + + + + diff --git a/test/TestProjects/NetCoreApp2App/Program.cs b/test/TestProjects/NetCoreApp2App/Program.cs new file mode 100644 index 0000000..c818479 --- /dev/null +++ b/test/TestProjects/NetCoreApp2App/Program.cs @@ -0,0 +1,17 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; + +namespace NetCoreApp2App +{ + internal class Program + { + public static void Main(string[] args) + { + Console.WriteLine(GetGreeting()); + } + + public static string GetGreeting() => "Hello world!"; + } +} diff --git a/test/TestProjects/NetStandardClassLib/Class1.cs b/test/TestProjects/NetStandardClassLib/Class1.cs new file mode 100644 index 0000000..24bd016 --- /dev/null +++ b/test/TestProjects/NetStandardClassLib/Class1.cs @@ -0,0 +1,10 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace NetStandardClassLib +{ + public class Class1 + { + public string GetColor() => "Red"; + } +} diff --git a/test/TestProjects/NetStandardClassLib/NetStandardClassLib.csproj b/test/TestProjects/NetStandardClassLib/NetStandardClassLib.csproj new file mode 100644 index 0000000..9f5c4f4 --- /dev/null +++ b/test/TestProjects/NetStandardClassLib/NetStandardClassLib.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git "a/test/TestProjects/Pl\303\241tano/Pl\303\241tano.cs" "b/test/TestProjects/Pl\303\241tano/Pl\303\241tano.cs" new file mode 100644 index 0000000..c655f44 --- /dev/null +++ "b/test/TestProjects/Pl\303\241tano/Pl\303\241tano.cs" @@ -0,0 +1,13 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Plátano; +using Test.Referenced.Library; + +namespace Test +{ + internal class Plátano : IFruit + { + public string GetFlavor() => Strings.Flavor; + } +} diff --git "a/test/TestProjects/Pl\303\241tano/Pl\303\241tano.csproj" "b/test/TestProjects/Pl\303\241tano/Pl\303\241tano.csproj" new file mode 100644 index 0000000..7e66041 --- /dev/null +++ "b/test/TestProjects/Pl\303\241tano/Pl\303\241tano.csproj" @@ -0,0 +1,11 @@ + + + + + + + + net8.0 + + + diff --git "a/test/TestProjects/Pl\303\241tano/Strings.Designer.cs" "b/test/TestProjects/Pl\303\241tano/Strings.Designer.cs" new file mode 100644 index 0000000..460c16b --- /dev/null +++ "b/test/TestProjects/Pl\303\241tano/Strings.Designer.cs" @@ -0,0 +1,55 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Plátano { + using System; + + + [System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [System.Diagnostics.DebuggerNonUserCodeAttribute()] + [System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class Strings { + + private static System.Resources.ResourceManager resourceMan; + + private static System.Globalization.CultureInfo resourceCulture; + + [System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + public static System.Resources.ResourceManager ResourceManager { + get { + if (object.Equals(null, resourceMan)) { + System.Resources.ResourceManager temp = new System.Resources.ResourceManager("Plátano.Strings", typeof(Strings).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + [System.ComponentModel.EditorBrowsableAttribute(System.ComponentModel.EditorBrowsableState.Advanced)] + public static System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + public static string Flavor { + get { + return ResourceManager.GetString("Flavor", resourceCulture); + } + } + } +} diff --git "a/test/TestProjects/Pl\303\241tano/Strings.es.Designer.cs" "b/test/TestProjects/Pl\303\241tano/Strings.es.Designer.cs" new file mode 100644 index 0000000..8b13789 --- /dev/null +++ "b/test/TestProjects/Pl\303\241tano/Strings.es.Designer.cs" @@ -0,0 +1 @@ + diff --git "a/test/TestProjects/Pl\303\241tano/Strings.es.resx" "b/test/TestProjects/Pl\303\241tano/Strings.es.resx" new file mode 100644 index 0000000..532a76d --- /dev/null +++ "b/test/TestProjects/Pl\303\241tano/Strings.es.resx" @@ -0,0 +1,24 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Plátano + + diff --git "a/test/TestProjects/Pl\303\241tano/Strings.resx" "b/test/TestProjects/Pl\303\241tano/Strings.resx" new file mode 100644 index 0000000..1ee8873 --- /dev/null +++ "b/test/TestProjects/Pl\303\241tano/Strings.resx" @@ -0,0 +1,24 @@ + + + + + + + + + + text/microsoft-resx + + + 1.3 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Banana + + diff --git a/test/TestProjects/PowerShellPlugin/PowerShellPlugin.csproj b/test/TestProjects/PowerShellPlugin/PowerShellPlugin.csproj new file mode 100644 index 0000000..ddd5d2d --- /dev/null +++ b/test/TestProjects/PowerShellPlugin/PowerShellPlugin.csproj @@ -0,0 +1,11 @@ + + + + net8.0 + + + + + + + diff --git a/test/TestProjects/PowerShellPlugin/Program.cs b/test/TestProjects/PowerShellPlugin/Program.cs new file mode 100644 index 0000000..eec7c42 --- /dev/null +++ b/test/TestProjects/PowerShellPlugin/Program.cs @@ -0,0 +1,25 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Management.Automation; + +namespace PowerShellPlugin +{ + internal class Program + { + public static void Main(string[] args) + { + Console.WriteLine(GetGreeting()); + } + + public static string GetGreeting() + { + using var ps = PowerShell.Create(); + var type = typeof(AliasAttribute); + // Console.WriteLine(type.Assembly.Location); + var results = ps.AddScript("Write-Output hello").Invoke(); + return results[0].ToString(); + } + } +} diff --git a/test/TestProjects/PrivateDepv1/Class1.cs b/test/TestProjects/PrivateDepv1/Class1.cs new file mode 100644 index 0000000..9619d13 --- /dev/null +++ b/test/TestProjects/PrivateDepv1/Class1.cs @@ -0,0 +1,8 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace PrivateDepv1; + +public class Class1 +{ +} diff --git a/test/TestProjects/PrivateDepv1/PrivateDepv1.csproj b/test/TestProjects/PrivateDepv1/PrivateDepv1.csproj new file mode 100644 index 0000000..130eac0 --- /dev/null +++ b/test/TestProjects/PrivateDepv1/PrivateDepv1.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + + + + + + + + diff --git a/test/TestProjects/PrivateDepv2/Class1.cs b/test/TestProjects/PrivateDepv2/Class1.cs new file mode 100644 index 0000000..6115d7d --- /dev/null +++ b/test/TestProjects/PrivateDepv2/Class1.cs @@ -0,0 +1,8 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace PrivateDepv2; + +public class Class1 +{ +} diff --git a/test/TestProjects/PrivateDepv2/PrivateDepv2.csproj b/test/TestProjects/PrivateDepv2/PrivateDepv2.csproj new file mode 100644 index 0000000..4f75de4 --- /dev/null +++ b/test/TestProjects/PrivateDepv2/PrivateDepv2.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + + + + + + + + diff --git a/test/TestProjects/PrivateDepv3/Class1.cs b/test/TestProjects/PrivateDepv3/Class1.cs new file mode 100644 index 0000000..8bc9e84 --- /dev/null +++ b/test/TestProjects/PrivateDepv3/Class1.cs @@ -0,0 +1,8 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace PrivateDepv3; + +public class Class1 +{ +} diff --git a/test/TestProjects/PrivateDepv3/PrivateDepv3.csproj b/test/TestProjects/PrivateDepv3/PrivateDepv3.csproj new file mode 100644 index 0000000..be6be20 --- /dev/null +++ b/test/TestProjects/PrivateDepv3/PrivateDepv3.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + + + + + + + + diff --git a/test/TestProjects/ReferencedLibv1/Class1.cs b/test/TestProjects/ReferencedLibv1/Class1.cs new file mode 100644 index 0000000..3ad31b6 --- /dev/null +++ b/test/TestProjects/ReferencedLibv1/Class1.cs @@ -0,0 +1,10 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Test.Referenced.Library +{ + public class Class1 + { + public static string GetVersion() => "v1"; + } +} diff --git a/test/TestProjects/ReferencedLibv1/IFruit.cs b/test/TestProjects/ReferencedLibv1/IFruit.cs new file mode 100644 index 0000000..5aee614 --- /dev/null +++ b/test/TestProjects/ReferencedLibv1/IFruit.cs @@ -0,0 +1,10 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Test.Referenced.Library +{ + public interface IFruit + { + string GetFlavor(); + } +} diff --git a/test/TestProjects/ReferencedLibv1/ReferencedLibv1.csproj b/test/TestProjects/ReferencedLibv1/ReferencedLibv1.csproj new file mode 100644 index 0000000..e6fb2b2 --- /dev/null +++ b/test/TestProjects/ReferencedLibv1/ReferencedLibv1.csproj @@ -0,0 +1,9 @@ + + + + netstandard2.0 + Test.Referenced.Library + 1.0.0.0 + + + diff --git a/test/TestProjects/ReferencedLibv2/Class1.cs b/test/TestProjects/ReferencedLibv2/Class1.cs new file mode 100644 index 0000000..af12c3e --- /dev/null +++ b/test/TestProjects/ReferencedLibv2/Class1.cs @@ -0,0 +1,10 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Test.Referenced.Library +{ + public class Class1 + { + public static string GetVersion() => "v2"; + } +} diff --git a/test/TestProjects/ReferencedLibv2/ReferencedLibv2.csproj b/test/TestProjects/ReferencedLibv2/ReferencedLibv2.csproj new file mode 100644 index 0000000..5622b28 --- /dev/null +++ b/test/TestProjects/ReferencedLibv2/ReferencedLibv2.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + Test.Referenced.Library + 2.0.0.0 + + + + + + + diff --git a/test/TestProjects/SharedAbstraction.v1/SharedAbstraction.v1.csproj b/test/TestProjects/SharedAbstraction.v1/SharedAbstraction.v1.csproj new file mode 100644 index 0000000..1b9b58e --- /dev/null +++ b/test/TestProjects/SharedAbstraction.v1/SharedAbstraction.v1.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + Test.Shared.Abstraction + 1.0.0.0 + + + + + + + diff --git a/test/TestProjects/SharedAbstraction.v1/SharedType.cs b/test/TestProjects/SharedAbstraction.v1/SharedType.cs new file mode 100644 index 0000000..869345d --- /dev/null +++ b/test/TestProjects/SharedAbstraction.v1/SharedType.cs @@ -0,0 +1,13 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using Test.Transitive; + +namespace Test.Shared.Abstraction +{ + public class SharedType + { + public Type GetTransitive() => typeof(TransitiveSharedType); + } +} diff --git a/test/TestProjects/SharedAbstraction.v2/SharedAbstraction.v2.csproj b/test/TestProjects/SharedAbstraction.v2/SharedAbstraction.v2.csproj new file mode 100644 index 0000000..1eddcc9 --- /dev/null +++ b/test/TestProjects/SharedAbstraction.v2/SharedAbstraction.v2.csproj @@ -0,0 +1,14 @@ + + + + netstandard2.0 + Test.Shared.Abstraction + 2.0.0.0 + + + + + + + + diff --git a/test/TestProjects/SqlClientApp/Program.cs b/test/TestProjects/SqlClientApp/Program.cs new file mode 100644 index 0000000..79fc076 --- /dev/null +++ b/test/TestProjects/SqlClientApp/Program.cs @@ -0,0 +1,31 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Data.SqlClient; + +namespace SqlClientApp +{ + public class Program + { + // required to make the C# compiler happy + public static void Main(string[] args) + { + } + + public static bool Run() + { + try + { + using var client = new SqlConnection(@"Data Source=(localdb)\mssqllocaldb;Integrated Security=True"); + client.Open(); + return !string.IsNullOrEmpty(client.ServerVersion); + } + catch (SqlException ex) when (ex.Number == -2) // -2 means SQL timeout + { + // When running the test in Azure DevOps build pipeline, we'll get a SqlException with "Connection Timeout Expired". + // We can ignore this safely in unit tests. + return true; + } + } + } +} diff --git a/test/TestProjects/SqlClientApp/SqlClientApp.csproj b/test/TestProjects/SqlClientApp/SqlClientApp.csproj new file mode 100644 index 0000000..57f8df1 --- /dev/null +++ b/test/TestProjects/SqlClientApp/SqlClientApp.csproj @@ -0,0 +1,12 @@ + + + + net8.0 + Exe + + + + + + + diff --git a/test/TestProjects/Strawberry/Strawberry.cs b/test/TestProjects/Strawberry/Strawberry.cs new file mode 100644 index 0000000..47a3c87 --- /dev/null +++ b/test/TestProjects/Strawberry/Strawberry.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Test.Referenced.Library; + +namespace Test +{ + internal class Strawberry : IFruit + { + public string GetFlavor() => nameof(Strawberry); + } +} diff --git a/test/TestProjects/Strawberry/Strawberry.csproj b/test/TestProjects/Strawberry/Strawberry.csproj new file mode 100644 index 0000000..cbbb6d6 --- /dev/null +++ b/test/TestProjects/Strawberry/Strawberry.csproj @@ -0,0 +1,11 @@ + + + + + + + + netstandard2.0 + + + diff --git a/test/TestProjects/TransitiveDep.v1/TransitiveDep.v1.csproj b/test/TestProjects/TransitiveDep.v1/TransitiveDep.v1.csproj new file mode 100644 index 0000000..a133a0c --- /dev/null +++ b/test/TestProjects/TransitiveDep.v1/TransitiveDep.v1.csproj @@ -0,0 +1,9 @@ + + + + netstandard2.0 + Test.Transitive + 1.0.0.0 + + + diff --git a/test/TestProjects/TransitiveDep.v1/TransitiveSharedType.cs b/test/TestProjects/TransitiveDep.v1/TransitiveSharedType.cs new file mode 100644 index 0000000..5f4ca47 --- /dev/null +++ b/test/TestProjects/TransitiveDep.v1/TransitiveSharedType.cs @@ -0,0 +1,7 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Test.Transitive +{ + public class TransitiveSharedType { } +} diff --git a/test/TestProjects/TransitiveDep.v2/TransitiveDep.v2.csproj b/test/TestProjects/TransitiveDep.v2/TransitiveDep.v2.csproj new file mode 100644 index 0000000..ff98f18 --- /dev/null +++ b/test/TestProjects/TransitiveDep.v2/TransitiveDep.v2.csproj @@ -0,0 +1,13 @@ + + + + netstandard2.0 + Test.Transitive + 2.0.0.0 + + + + + + + diff --git a/test/TestProjects/TransitivePlugin/PluginConfig.cs b/test/TestProjects/TransitivePlugin/PluginConfig.cs new file mode 100644 index 0000000..7e4b420 --- /dev/null +++ b/test/TestProjects/TransitivePlugin/PluginConfig.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Test.Transitive; + +namespace TransitivePlugin +{ + public class PluginConfig + { + public TransitiveSharedType GetTransitiveType() => new(); + } +} diff --git a/test/TestProjects/TransitivePlugin/TransitivePlugin.csproj b/test/TestProjects/TransitivePlugin/TransitivePlugin.csproj new file mode 100644 index 0000000..4a917c2 --- /dev/null +++ b/test/TestProjects/TransitivePlugin/TransitivePlugin.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.0 + + + + + + + diff --git a/test/TestProjects/WithOurPluginsPluginA/Class1.cs b/test/TestProjects/WithOurPluginsPluginA/Class1.cs new file mode 100644 index 0000000..cf15f88 --- /dev/null +++ b/test/TestProjects/WithOurPluginsPluginA/Class1.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using WithOurPluginsPluginContract; + +namespace WithOurPluginsPluginA +{ + public class Class1 : ISayHello + { + public string SayHello() => $"Hello from {nameof(WithOurPluginsPluginA)}"; + } +} diff --git a/test/TestProjects/WithOurPluginsPluginA/WithOurPluginsPluginA.csproj b/test/TestProjects/WithOurPluginsPluginA/WithOurPluginsPluginA.csproj new file mode 100644 index 0000000..d20507a --- /dev/null +++ b/test/TestProjects/WithOurPluginsPluginA/WithOurPluginsPluginA.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + false + + + + + + + diff --git a/test/TestProjects/WithOurPluginsPluginB/Class1.cs b/test/TestProjects/WithOurPluginsPluginB/Class1.cs new file mode 100644 index 0000000..837f0e7 --- /dev/null +++ b/test/TestProjects/WithOurPluginsPluginB/Class1.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using WithOurPluginsPluginContract; + +namespace WithOurPluginsPluginB +{ + public class Class1 : ISayHello + { + public string SayHello() => $"Hello from {nameof(WithOurPluginsPluginB)}"; + } +} diff --git a/test/TestProjects/WithOurPluginsPluginB/WithOurPluginsPluginB.csproj b/test/TestProjects/WithOurPluginsPluginB/WithOurPluginsPluginB.csproj new file mode 100644 index 0000000..d20507a --- /dev/null +++ b/test/TestProjects/WithOurPluginsPluginB/WithOurPluginsPluginB.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + false + + + + + + + diff --git a/test/TestProjects/WithOurPluginsPluginContract/ISayHello.cs b/test/TestProjects/WithOurPluginsPluginContract/ISayHello.cs new file mode 100644 index 0000000..cc52587 --- /dev/null +++ b/test/TestProjects/WithOurPluginsPluginContract/ISayHello.cs @@ -0,0 +1,10 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace WithOurPluginsPluginContract +{ + public interface ISayHello + { + string SayHello(); + } +} diff --git a/test/TestProjects/WithOurPluginsPluginContract/WithOurPluginsPluginContract.csproj b/test/TestProjects/WithOurPluginsPluginContract/WithOurPluginsPluginContract.csproj new file mode 100644 index 0000000..9f5c4f4 --- /dev/null +++ b/test/TestProjects/WithOurPluginsPluginContract/WithOurPluginsPluginContract.csproj @@ -0,0 +1,7 @@ + + + + netstandard2.0 + + + diff --git a/test/TestProjects/WithOwnPlugins/WithOwnPlugins.cs b/test/TestProjects/WithOwnPlugins/WithOwnPlugins.cs new file mode 100644 index 0000000..b16cf7a --- /dev/null +++ b/test/TestProjects/WithOwnPlugins/WithOwnPlugins.cs @@ -0,0 +1,70 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.Loader; +using McMaster.NETCore.Plugins; +using WithOurPluginsPluginContract; +using WithOwnPluginsContract; + +namespace WithOwnPlugins +{ + public class WithOwnPlugins : IWithOwnPlugins + { + public bool TryLoadPluginsInCustomContext(AssemblyLoadContext? callingContext) + { + var currentContext = AssemblyLoadContext.GetLoadContext(Assembly.GetExecutingAssembly()); + if (currentContext == callingContext) + { + throw new ArgumentException("The context of the caller is the context of this assembly. This invalidates the test."); + } + + /* + Ensure the source calling context does not have our plugin's interfaces loaded. + This guarantees that the Assembly cannot possibly unify with the default load context. + + Note: + The code below this check would fail anyway if the assembly would unify with the default context. + This is more of a safety check to ensure "correctness" as opposed to anything else. + */ + var sayHelloAssembly = typeof(ISayHello).Assembly; + if (callingContext?.Assemblies.Contains(sayHelloAssembly) == true) // .Assemblies API not available in Core 2.X + { + throw new ArgumentException("The context of the caller has this plugin's interface to interact with its own plugins loaded. Test is void."); + } + + // Load our own plugins: Remember, we are in an isolated, non-default ALC. + var plugins = new List(); + string[] assemblyNames = { "Plugins/WithOurPluginsPluginA.dll", "Plugins/WithOurPluginsPluginB.dll" }; + + foreach (var assemblyName in assemblyNames) + { + var currentAssemblyFolderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location) ?? throw new Exception("Unable to get folder path for currently executing assembly."); + var pluginPath = Path.Combine(currentAssemblyFolderPath, assemblyName); + + using var loader = PluginLoader.CreateFromAssemblyFile(pluginPath, new[] { typeof(ISayHello) }); + var assembly = loader.LoadDefaultAssembly(); + var configType = assembly.GetTypes().First(x => typeof(ISayHello).IsAssignableFrom(x) && !x.IsAbstract); + var plugin = (ISayHello?)Activator.CreateInstance(configType); + if (plugin == null) + { + throw new Exception($"Failed to load instance of {nameof(ISayHello)} from plugin."); + } + + plugins.Add(plugin); + } + + // Shouldn't need to check for this but just in case to absolutely make sure. + if (plugins.Any(plugin => String.IsNullOrEmpty(plugin?.SayHello()))) + { + throw new Exception("No value returned from plugin."); + } + + return true; + } + } +} diff --git a/test/TestProjects/WithOwnPlugins/WithOwnPlugins.csproj b/test/TestProjects/WithOwnPlugins/WithOwnPlugins.csproj new file mode 100644 index 0000000..c46293d --- /dev/null +++ b/test/TestProjects/WithOwnPlugins/WithOwnPlugins.csproj @@ -0,0 +1,15 @@ + + + + net8.0 + + + + + + + + + + + diff --git a/test/TestProjects/WithOwnPluginsContract/IWithOwnPlugins.cs b/test/TestProjects/WithOwnPluginsContract/IWithOwnPlugins.cs new file mode 100644 index 0000000..ae7563f --- /dev/null +++ b/test/TestProjects/WithOwnPluginsContract/IWithOwnPlugins.cs @@ -0,0 +1,12 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System.Runtime.Loader; + +namespace WithOwnPluginsContract +{ + public interface IWithOwnPlugins + { + bool TryLoadPluginsInCustomContext(AssemblyLoadContext? callingContext); + } +} diff --git a/test/TestProjects/WithOwnPluginsContract/WithOwnPluginsContract.csproj b/test/TestProjects/WithOwnPluginsContract/WithOwnPluginsContract.csproj new file mode 100644 index 0000000..58990cd --- /dev/null +++ b/test/TestProjects/WithOwnPluginsContract/WithOwnPluginsContract.csproj @@ -0,0 +1,7 @@ + + + + net8.0 + + + diff --git a/test/TestProjects/XunitSample/Class1.cs b/test/TestProjects/XunitSample/Class1.cs new file mode 100644 index 0000000..b124389 --- /dev/null +++ b/test/TestProjects/XunitSample/Class1.cs @@ -0,0 +1,9 @@ +// Copyright (c) Nate McMaster. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace XunitSample +{ + public class Class1 + { + } +} diff --git a/test/TestProjects/XunitSample/XunitSample.csproj b/test/TestProjects/XunitSample/XunitSample.csproj new file mode 100644 index 0000000..810e4ce --- /dev/null +++ b/test/TestProjects/XunitSample/XunitSample.csproj @@ -0,0 +1,12 @@ + + + + netstandard2.0 + true + + + + + + + From 54958df5ff2d6413635529ac32c5a627791a4c97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E7=BA=BE?= Date: Mon, 19 Oct 2026 10:36:49 +0800 Subject: [PATCH 02/12] =?UTF-8?q?docs:=20=E5=88=97=E5=87=BA=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E7=9A=84=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FORK.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/FORK.md b/FORK.md index 5059fd8..f7282c6 100644 --- a/FORK.md +++ b/FORK.md @@ -14,5 +14,8 @@ This repository is a fork of ## Licence -Apache-2.0, the same as upstream. The original copyright and licence notices are retained; -modified files carry a notice. See [LICENSE.txt](LICENSE.txt). +Apache-2.0, the same as upstream. The original copyright and licence notices are retained. +Modified files: `src/Plugins/Loader/ManagedLoadContext.cs`, +`src/Plugins/McMaster.NETCore.Plugins.csproj`, `src/Plugins/Properties/AssemblyInfo.cs`, +`README.md`, `.github/workflows/ci.yml`; added `FORK.md` and `test/Hosihikari.Tests`. See +[LICENSE.txt](LICENSE.txt). From 6708091048b181ca66b956db90e356f989c0ce8d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E7=BA=BE?= Date: Mon, 19 Oct 2026 11:24:50 +0800 Subject: [PATCH 03/12] =?UTF-8?q?chore:=20=E7=A7=BB=E9=99=A4=E4=B8=8A?= =?UTF-8?q?=E6=B8=B8=20samples=EF=BC=8C=E6=94=B6=E7=B4=A7=E8=A7=A3?= =?UTF-8?q?=E5=86=B3=E6=96=B9=E6=A1=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- DotNetCorePlugins.sln | 827 +----------------- FORK.md | 6 +- README.md | 2 +- .../MvcApp/Controllers/HomeController.cs | 15 - samples/aspnetcore-mvc/MvcApp/MvcApp.csproj | 21 - samples/aspnetcore-mvc/MvcApp/Program.cs | 20 - samples/aspnetcore-mvc/MvcApp/Startup.cs | 37 - .../MvcApp/Views/Home/Index.cshtml | 9 - .../MvcApp/Views/_ViewImports.cshtml | 2 - .../MvcAppPlugin1/MvcAppPlugin1.csproj | 12 - .../MvcAppPlugin1/MyPluginController.cs | 12 - .../MvcAppPlugin1/Views/MyPlugin/Index.cshtml | 5 - samples/aspnetcore-mvc/README.md | 23 - samples/aspnetcore-mvc/aspnetcore-mvc.sln | 48 - .../Abstractions/Abstractions.csproj | 11 - samples/aspnetcore/Abstractions/IPlugin.cs | 14 - .../aspnetcore/Abstractions/IPluginLink.cs | 10 - .../aspnetcore/MainWebApp/MainWebApp.csproj | 30 - .../aspnetcore/MainWebApp/Pages/Index.cshtml | 11 - .../MainWebApp/Pages/Index.cshtml.cs | 24 - samples/aspnetcore/MainWebApp/Program.cs | 20 - samples/aspnetcore/MainWebApp/Startup.cs | 68 -- samples/aspnetcore/README.md | 42 - .../WebAppPlugin1/WebAppPlugin1.csproj | 12 - .../aspnetcore/WebAppPlugin1/WebPlugin1.cs | 32 - .../WebAppPlugin2/WebAppPlugin2.csproj | 12 - .../aspnetcore/WebAppPlugin2/WebPlugin2.cs | 32 - samples/aspnetcore/aspnetcore.sln | 76 -- .../DI.HostApp/DI.HostApp.csproj | 26 - .../DI.HostApp/Program.cs | 69 -- .../DI.SharedAbstractions.csproj | 11 - .../DI.SharedAbstractions/Fruit.cs | 15 - .../DI.SharedAbstractions/IFruitConsumer.cs | 10 - .../DI.SharedAbstractions/IFruitProducer.cs | 12 - .../DI.SharedAbstractions/IPluginFactory.cs | 12 - .../MyPlugin1/MyFruitProducer.cs | 18 - .../MyPlugin1/MyPlugin1.csproj | 11 - .../MyPlugin1/PluginConfiguration.cs | 16 - .../MyPlugin2/MyFruitConsumer.cs | 30 - .../MyPlugin2/MyPlugin2.csproj | 11 - .../MyPlugin2/PluginConfiguration.cs | 16 - samples/dependency-injection/README.md | 18 - .../Contracts/Contracts.csproj | 12 - .../dynamic-implementation/Contracts/Fruit.cs | 10 - .../Contracts/IFruitService.cs | 12 - .../Contracts/IMixerService.cs | 10 - .../Contracts/IPluginFactory.cs | 12 - .../DynamicImplementation.sln | 43 - .../dynamic-implementation/Host/Host.csproj | 28 - .../dynamic-implementation/Host/Program.cs | 69 -- .../dynamic-implementation/Mixer/Mixer.csproj | 12 - .../Mixer/MixerPlugin.cs | 17 - .../Mixer/MixerService.cs | 22 - .../Mixer/StandardFruiteService.cs | 21 - samples/dynamic-implementation/README.md | 21 - .../OverrideFruiteService.cs | 19 - .../ServiceImplementation/OverridePlugin.cs | 17 - .../ServiceImplementation.csproj | 12 - samples/hello-world/HostApp/HostApp.csproj | 21 - samples/hello-world/HostApp/Program.cs | 41 - samples/hello-world/MyPlugin/MyPlugin.csproj | 11 - samples/hello-world/MyPlugin/MyPlugin1.cs | 12 - samples/hello-world/PluginContract/IPlugin.cs | 16 - .../PluginContract/PluginContract.csproj | 7 - samples/hello-world/README.md | 17 - samples/hello-world/hello-world.sln | 62 -- .../HotReloadApp/HotReloadApp.csproj | 16 - samples/hot-reload/HotReloadApp/Program.cs | 39 - samples/hot-reload/README.md | 13 - .../TimestampedPlugin/InfoDisplayer.cs | 32 - .../TimestampedPlugin.csproj | 18 - samples/hot-reload/hot-reload.sln | 48 - samples/hot-reload/run.ps1 | 42 - samples/hot-reload/run.sh | 33 - 74 files changed, 41 insertions(+), 2432 deletions(-) delete mode 100644 samples/aspnetcore-mvc/MvcApp/Controllers/HomeController.cs delete mode 100644 samples/aspnetcore-mvc/MvcApp/MvcApp.csproj delete mode 100644 samples/aspnetcore-mvc/MvcApp/Program.cs delete mode 100644 samples/aspnetcore-mvc/MvcApp/Startup.cs delete mode 100644 samples/aspnetcore-mvc/MvcApp/Views/Home/Index.cshtml delete mode 100644 samples/aspnetcore-mvc/MvcApp/Views/_ViewImports.cshtml delete mode 100644 samples/aspnetcore-mvc/MvcAppPlugin1/MvcAppPlugin1.csproj delete mode 100644 samples/aspnetcore-mvc/MvcAppPlugin1/MyPluginController.cs delete mode 100644 samples/aspnetcore-mvc/MvcAppPlugin1/Views/MyPlugin/Index.cshtml delete mode 100644 samples/aspnetcore-mvc/README.md delete mode 100644 samples/aspnetcore-mvc/aspnetcore-mvc.sln delete mode 100644 samples/aspnetcore/Abstractions/Abstractions.csproj delete mode 100644 samples/aspnetcore/Abstractions/IPlugin.cs delete mode 100644 samples/aspnetcore/Abstractions/IPluginLink.cs delete mode 100644 samples/aspnetcore/MainWebApp/MainWebApp.csproj delete mode 100644 samples/aspnetcore/MainWebApp/Pages/Index.cshtml delete mode 100644 samples/aspnetcore/MainWebApp/Pages/Index.cshtml.cs delete mode 100644 samples/aspnetcore/MainWebApp/Program.cs delete mode 100644 samples/aspnetcore/MainWebApp/Startup.cs delete mode 100644 samples/aspnetcore/README.md delete mode 100644 samples/aspnetcore/WebAppPlugin1/WebAppPlugin1.csproj delete mode 100644 samples/aspnetcore/WebAppPlugin1/WebPlugin1.cs delete mode 100644 samples/aspnetcore/WebAppPlugin2/WebAppPlugin2.csproj delete mode 100644 samples/aspnetcore/WebAppPlugin2/WebPlugin2.cs delete mode 100644 samples/aspnetcore/aspnetcore.sln delete mode 100644 samples/dependency-injection/DI.HostApp/DI.HostApp.csproj delete mode 100644 samples/dependency-injection/DI.HostApp/Program.cs delete mode 100644 samples/dependency-injection/DI.SharedAbstractions/DI.SharedAbstractions.csproj delete mode 100644 samples/dependency-injection/DI.SharedAbstractions/Fruit.cs delete mode 100644 samples/dependency-injection/DI.SharedAbstractions/IFruitConsumer.cs delete mode 100644 samples/dependency-injection/DI.SharedAbstractions/IFruitProducer.cs delete mode 100644 samples/dependency-injection/DI.SharedAbstractions/IPluginFactory.cs delete mode 100644 samples/dependency-injection/MyPlugin1/MyFruitProducer.cs delete mode 100644 samples/dependency-injection/MyPlugin1/MyPlugin1.csproj delete mode 100644 samples/dependency-injection/MyPlugin1/PluginConfiguration.cs delete mode 100644 samples/dependency-injection/MyPlugin2/MyFruitConsumer.cs delete mode 100644 samples/dependency-injection/MyPlugin2/MyPlugin2.csproj delete mode 100644 samples/dependency-injection/MyPlugin2/PluginConfiguration.cs delete mode 100644 samples/dependency-injection/README.md delete mode 100644 samples/dynamic-implementation/Contracts/Contracts.csproj delete mode 100644 samples/dynamic-implementation/Contracts/Fruit.cs delete mode 100644 samples/dynamic-implementation/Contracts/IFruitService.cs delete mode 100644 samples/dynamic-implementation/Contracts/IMixerService.cs delete mode 100644 samples/dynamic-implementation/Contracts/IPluginFactory.cs delete mode 100644 samples/dynamic-implementation/DynamicImplementation.sln delete mode 100644 samples/dynamic-implementation/Host/Host.csproj delete mode 100644 samples/dynamic-implementation/Host/Program.cs delete mode 100644 samples/dynamic-implementation/Mixer/Mixer.csproj delete mode 100644 samples/dynamic-implementation/Mixer/MixerPlugin.cs delete mode 100644 samples/dynamic-implementation/Mixer/MixerService.cs delete mode 100644 samples/dynamic-implementation/Mixer/StandardFruiteService.cs delete mode 100644 samples/dynamic-implementation/README.md delete mode 100644 samples/dynamic-implementation/ServiceImplementation/OverrideFruiteService.cs delete mode 100644 samples/dynamic-implementation/ServiceImplementation/OverridePlugin.cs delete mode 100644 samples/dynamic-implementation/ServiceImplementation/ServiceImplementation.csproj delete mode 100644 samples/hello-world/HostApp/HostApp.csproj delete mode 100644 samples/hello-world/HostApp/Program.cs delete mode 100644 samples/hello-world/MyPlugin/MyPlugin.csproj delete mode 100644 samples/hello-world/MyPlugin/MyPlugin1.cs delete mode 100644 samples/hello-world/PluginContract/IPlugin.cs delete mode 100644 samples/hello-world/PluginContract/PluginContract.csproj delete mode 100644 samples/hello-world/README.md delete mode 100644 samples/hello-world/hello-world.sln delete mode 100644 samples/hot-reload/HotReloadApp/HotReloadApp.csproj delete mode 100644 samples/hot-reload/HotReloadApp/Program.cs delete mode 100644 samples/hot-reload/README.md delete mode 100644 samples/hot-reload/TimestampedPlugin/InfoDisplayer.cs delete mode 100644 samples/hot-reload/TimestampedPlugin/TimestampedPlugin.csproj delete mode 100644 samples/hot-reload/hot-reload.sln delete mode 100644 samples/hot-reload/run.ps1 delete mode 100644 samples/hot-reload/run.sh diff --git a/DotNetCorePlugins.sln b/DotNetCorePlugins.sln index e9bd38d..ed1921d 100644 --- a/DotNetCorePlugins.sln +++ b/DotNetCorePlugins.sln @@ -1,142 +1,19 @@  Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 -VisualStudioVersion = 17.9.34622.214 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{471CA3C0-0BC9-4C0C-A013-D9356DBD0F38}" +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{827E0CD3-B72D-47B6-A68D-7590B98EB39B}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "McMaster.NETCore.Plugins", "src\Plugins\McMaster.NETCore.Plugins.csproj", "{2D251CFD-A9DA-421F-B516-B25873348C08}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Plugins", "Plugins", "{58D3A2C3-F96F-5E57-2C6B-ECE59D6A18FC}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{69958BAA-5D54-4F3F-82D6-F622F9491057}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "McMaster.NETCore.Plugins", "src\Plugins\McMaster.NETCore.Plugins.csproj", "{2B65624B-6194-450C-970D-92E0F7CB1236}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "McMaster.NETCore.Plugins.Tests", "test\Plugins.Tests\McMaster.NETCore.Plugins.Tests.csproj", "{00D7FBF0-801B-4B0E-9FDD-5C367E71444B}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0C88DD14-F956-CE84-757C-A364CCF449FC}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "TestProjects", "TestProjects", "{98E964A2-55DA-4740-9F2E-B64FDF6715DB}" - ProjectSection(SolutionItems) = preProject - test\TestProjects\Directory.Build.props = test\TestProjects\Directory.Build.props - EndProjectSection +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Hosihikari.Tests", "Hosihikari.Tests", "{34DD7E53-B439-98E4-B5A2-4A9A2CFAE897}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetCoreApp2App", "test\TestProjects\NetCoreApp2App\NetCoreApp2App.csproj", "{B9C61145-C010-4FAA-B9EA-4D1AEE20305F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetStandardClassLib", "test\TestProjects\NetStandardClassLib\NetStandardClassLib.csproj", "{4FDB7710-F4CA-45FB-9514-A4D288EDA44A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReferencedLibv1", "test\TestProjects\ReferencedLibv1\ReferencedLibv1.csproj", "{CC37B369-BA9E-4B82-A14D-68B58924885D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReferencedLibv2", "test\TestProjects\ReferencedLibv2\ReferencedLibv2.csproj", "{50F86C8F-E052-448C-84CE-08763945FCC7}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XunitSample", "test\TestProjects\XunitSample\XunitSample.csproj", "{3CEF9F32-5887-4977-AB8A-1BBC48875A21}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Banana", "test\TestProjects\Banana\Banana.csproj", "{8797AFCA-1EE8-4270-8FC4-F93A85F9C825}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Strawberry", "test\TestProjects\Strawberry\Strawberry.csproj", "{78D2B20D-B173-4CF2-A763-F2AA5347947E}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PowerShellPlugin", "test\TestProjects\PowerShellPlugin\PowerShellPlugin.csproj", "{C0BA460C-C1AC-4C54-9C88-60E4B8681E94}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{9E47E94A-3F50-4B3B-8937-1FCF832ABEB6}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - .gitignore = .gitignore - build.ps1 = build.ps1 - src\common.psm1 = src\common.psm1 - Directory.Build.props = Directory.Build.props - src\Directory.Build.targets = src\Directory.Build.targets - LICENSE.txt = LICENSE.txt - README.md = README.md - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Plátano", "test\TestProjects\Plátano\Plátano.csproj", "{5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DrawingApp", "test\TestProjects\DrawingApp\DrawingApp.csproj", "{8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SqlClientApp", "test\TestProjects\SqlClientApp\SqlClientApp.csproj", "{0CDA2765-C9DF-45EE-A099-0E2D4A980E59}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcApp", "samples\aspnetcore-mvc\MvcApp\MvcApp.csproj", "{F934B760-4847-4BA8-9F6A-7F750ECF2328}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MvcAppPlugin1", "samples\aspnetcore-mvc\MvcAppPlugin1\MvcAppPlugin1.csproj", "{2DC38785-B7B6-49E9-84D4-103A56D3604A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{F49FFE3B-E1AE-483E-951F-D498F6ABA08E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "aspnetcoremvc", "aspnetcoremvc", "{610E8065-1F29-41E8-A3FC-07ABDF16C8BB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "McMaster.NETCore.Plugins.Mvc", "src\Plugins.Mvc\McMaster.NETCore.Plugins.Mvc.csproj", "{263CD320-FAC5-4F0C-8FFB-788DD3C455D4}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "aspnetcore", "aspnetcore", "{DD87D202-998B-4C20-8D68-C769797D7761}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Abstractions", "samples\aspnetcore\Abstractions\Abstractions.csproj", "{D1E1AEDC-4870-4863-A8D3-B6A184E651C1}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MainWebApp", "samples\aspnetcore\MainWebApp\MainWebApp.csproj", "{7DD1722B-B971-4322-BA74-C622E808B84D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebAppPlugin1", "samples\aspnetcore\WebAppPlugin1\WebAppPlugin1.csproj", "{A4C90265-C922-4145-8CDC-82EC5EBBC3A4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WebAppPlugin2", "samples\aspnetcore\WebAppPlugin2\WebAppPlugin2.csproj", "{ADF569DC-E916-4F39-8ABF-8AA42F69266E}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "hello-world", "hello-world", "{3B0C6936-E777-4413-B68A-1686A768F928}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HostApp", "samples\hello-world\HostApp\HostApp.csproj", "{DFC13FA1-509C-42AF-870F-543C7A3F231B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyPlugin", "samples\hello-world\MyPlugin\MyPlugin.csproj", "{2573526D-A764-4507-B2F6-1A2BF7A3FA93}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PluginContract", "samples\hello-world\PluginContract\PluginContract.csproj", "{6842C9FD-6720-43EA-83AD-DE315531DDA9}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dependency-injection", "dependency-injection", "{D17C19AC-EC11-4661-BD23-DC8308CF71C0}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DI.HostApp", "samples\dependency-injection\DI.HostApp\DI.HostApp.csproj", "{5DBEE6E0-8744-46E4-B711-8FEC98D41A50}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyPlugin1", "samples\dependency-injection\MyPlugin1\MyPlugin1.csproj", "{BF78F36A-D072-4F1C-89B1-73D9C208ABB3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MyPlugin2", "samples\dependency-injection\MyPlugin2\MyPlugin2.csproj", "{4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DI.SharedAbstractions", "samples\dependency-injection\DI.SharedAbstractions\DI.SharedAbstractions.csproj", "{40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransitiveDep.v2", "test\TestProjects\TransitiveDep.v2\TransitiveDep.v2.csproj", "{CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedAbstraction.v1", "test\TestProjects\SharedAbstraction.v1\SharedAbstraction.v1.csproj", "{D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransitiveDep.v1", "test\TestProjects\TransitiveDep.v1\TransitiveDep.v1.csproj", "{704EA4BF-71D9-442B-96D9-9B3E81068CC2}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SharedAbstraction.v2", "test\TestProjects\SharedAbstraction.v2\SharedAbstraction.v2.csproj", "{3549E707-E4B0-428F-9CF4-5B5B86219B6D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TransitivePlugin", "test\TestProjects\TransitivePlugin\TransitivePlugin.csproj", "{1EA2BACF-3D44-4B05-A0E7-727AE13E693D}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "hot-reload", "hot-reload", "{07E0093F-F05C-4AED-A489-1254C9854E21}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "HotReloadApp", "samples\hot-reload\HotReloadApp\HotReloadApp.csproj", "{D815817F-CC0D-42CC-9C9C-8E229B9C03A8}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TimestampedPlugin", "samples\hot-reload\TimestampedPlugin\TimestampedPlugin.csproj", "{FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WithOwnPlugins", "test\TestProjects\WithOwnPlugins\WithOwnPlugins.csproj", "{F47ED576-F347-44C4-BEC7-6C00A500555A}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WithOwnPluginsContract", "test\TestProjects\WithOwnPluginsContract\WithOwnPluginsContract.csproj", "{F55E52BD-A846-4808-9482-E4AB0921328D}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WithOurPluginsPluginContract", "test\TestProjects\WithOurPluginsPluginContract\WithOurPluginsPluginContract.csproj", "{7C7B19F2-5822-4DA2-80D2-E077CE705039}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WithOurPluginsPluginA", "test\TestProjects\WithOurPluginsPluginA\WithOurPluginsPluginA.csproj", "{3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WithOurPluginsPluginB", "test\TestProjects\WithOurPluginsPluginB\WithOurPluginsPluginB.csproj", "{D5B3DA06-E60D-4C85-8835-6A26003365AA}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NativeDependency", "test\TestProjects\NativeDependency\NativeDependency.csproj", "{2837A894-11D6-4DBF-B140-84C97C385A15}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dynamic-implementation", "dynamic-implementation", "{10109B62-DACC-42BA-AD8C-73DE4B93841B}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Contracts", "samples\dynamic-implementation\Contracts\Contracts.csproj", "{39B9F0B5-3C53-4140-B158-96C67C03E876}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Host", "samples\dynamic-implementation\Host\Host.csproj", "{703ECD55-EF0E-4734-A28E-371EDD213EEF}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Mixer", "samples\dynamic-implementation\Mixer\Mixer.csproj", "{9E2178A0-5915-4308-B6C3-5C8758A47788}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ServiceImplementation", "samples\dynamic-implementation\ServiceImplementation\ServiceImplementation.csproj", "{39BCFE6F-50C4-45AE-8D13-38FB423AB619}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libv1", "test\TestProjects\Libv1\Libv1.csproj", "{3761F08D-06AE-4E07-8B88-F19C465DC169}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libv2", "test\TestProjects\Libv2\Libv2.csproj", "{DB02518A-49A1-4836-80B7-D69A70DE68BA}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Libv3", "test\TestProjects\Libv3\Libv3.csproj", "{47C888A8-6887-4FA2-A202-8653684F3651}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrivateDepv1", "test\TestProjects\PrivateDepv1\PrivateDepv1.csproj", "{88691B89-0167-4E90-86F6-5FD6C974F7A3}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrivateDepv2", "test\TestProjects\PrivateDepv2\PrivateDepv2.csproj", "{63631241-3CA1-40E2-9545-01E019414E4F}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PrivateDepv3", "test\TestProjects\PrivateDepv3\PrivateDepv3.csproj", "{0C5BEA9A-9F07-49F5-8E84-80F3250E6516}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Hosihikari.McMaster.NETCore.Plugins.Tests", "test\Hosihikari.Tests\Hosihikari.McMaster.NETCore.Plugins.Tests.csproj", "{96826AA6-ADBF-4835-978D-9735EC150E47}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -148,670 +25,38 @@ Global Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {2D251CFD-A9DA-421F-B516-B25873348C08}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2D251CFD-A9DA-421F-B516-B25873348C08}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2D251CFD-A9DA-421F-B516-B25873348C08}.Debug|x64.ActiveCfg = Debug|Any CPU - {2D251CFD-A9DA-421F-B516-B25873348C08}.Debug|x64.Build.0 = Debug|Any CPU - {2D251CFD-A9DA-421F-B516-B25873348C08}.Debug|x86.ActiveCfg = Debug|Any CPU - {2D251CFD-A9DA-421F-B516-B25873348C08}.Debug|x86.Build.0 = Debug|Any CPU - {2D251CFD-A9DA-421F-B516-B25873348C08}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2D251CFD-A9DA-421F-B516-B25873348C08}.Release|Any CPU.Build.0 = Release|Any CPU - {2D251CFD-A9DA-421F-B516-B25873348C08}.Release|x64.ActiveCfg = Release|Any CPU - {2D251CFD-A9DA-421F-B516-B25873348C08}.Release|x64.Build.0 = Release|Any CPU - {2D251CFD-A9DA-421F-B516-B25873348C08}.Release|x86.ActiveCfg = Release|Any CPU - {2D251CFD-A9DA-421F-B516-B25873348C08}.Release|x86.Build.0 = Release|Any CPU - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Debug|x64.ActiveCfg = Debug|Any CPU - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Debug|x64.Build.0 = Debug|Any CPU - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Debug|x86.ActiveCfg = Debug|Any CPU - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Debug|x86.Build.0 = Debug|Any CPU - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Release|Any CPU.Build.0 = Release|Any CPU - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Release|x64.ActiveCfg = Release|Any CPU - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Release|x64.Build.0 = Release|Any CPU - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Release|x86.ActiveCfg = Release|Any CPU - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B}.Release|x86.Build.0 = Release|Any CPU - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Debug|x64.ActiveCfg = Debug|Any CPU - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Debug|x64.Build.0 = Debug|Any CPU - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Debug|x86.ActiveCfg = Debug|Any CPU - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Debug|x86.Build.0 = Debug|Any CPU - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Release|Any CPU.Build.0 = Release|Any CPU - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Release|x64.ActiveCfg = Release|Any CPU - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Release|x64.Build.0 = Release|Any CPU - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Release|x86.ActiveCfg = Release|Any CPU - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F}.Release|x86.Build.0 = Release|Any CPU - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Debug|x64.ActiveCfg = Debug|Any CPU - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Debug|x64.Build.0 = Debug|Any CPU - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Debug|x86.ActiveCfg = Debug|Any CPU - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Debug|x86.Build.0 = Debug|Any CPU - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Release|Any CPU.Build.0 = Release|Any CPU - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Release|x64.ActiveCfg = Release|Any CPU - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Release|x64.Build.0 = Release|Any CPU - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Release|x86.ActiveCfg = Release|Any CPU - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A}.Release|x86.Build.0 = Release|Any CPU - {CC37B369-BA9E-4B82-A14D-68B58924885D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CC37B369-BA9E-4B82-A14D-68B58924885D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CC37B369-BA9E-4B82-A14D-68B58924885D}.Debug|x64.ActiveCfg = Debug|Any CPU - {CC37B369-BA9E-4B82-A14D-68B58924885D}.Debug|x64.Build.0 = Debug|Any CPU - {CC37B369-BA9E-4B82-A14D-68B58924885D}.Debug|x86.ActiveCfg = Debug|Any CPU - {CC37B369-BA9E-4B82-A14D-68B58924885D}.Debug|x86.Build.0 = Debug|Any CPU - {CC37B369-BA9E-4B82-A14D-68B58924885D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CC37B369-BA9E-4B82-A14D-68B58924885D}.Release|Any CPU.Build.0 = Release|Any CPU - {CC37B369-BA9E-4B82-A14D-68B58924885D}.Release|x64.ActiveCfg = Release|Any CPU - {CC37B369-BA9E-4B82-A14D-68B58924885D}.Release|x64.Build.0 = Release|Any CPU - {CC37B369-BA9E-4B82-A14D-68B58924885D}.Release|x86.ActiveCfg = Release|Any CPU - {CC37B369-BA9E-4B82-A14D-68B58924885D}.Release|x86.Build.0 = Release|Any CPU - {50F86C8F-E052-448C-84CE-08763945FCC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {50F86C8F-E052-448C-84CE-08763945FCC7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {50F86C8F-E052-448C-84CE-08763945FCC7}.Debug|x64.ActiveCfg = Debug|Any CPU - {50F86C8F-E052-448C-84CE-08763945FCC7}.Debug|x64.Build.0 = Debug|Any CPU - {50F86C8F-E052-448C-84CE-08763945FCC7}.Debug|x86.ActiveCfg = Debug|Any CPU - {50F86C8F-E052-448C-84CE-08763945FCC7}.Debug|x86.Build.0 = Debug|Any CPU - {50F86C8F-E052-448C-84CE-08763945FCC7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {50F86C8F-E052-448C-84CE-08763945FCC7}.Release|Any CPU.Build.0 = Release|Any CPU - {50F86C8F-E052-448C-84CE-08763945FCC7}.Release|x64.ActiveCfg = Release|Any CPU - {50F86C8F-E052-448C-84CE-08763945FCC7}.Release|x64.Build.0 = Release|Any CPU - {50F86C8F-E052-448C-84CE-08763945FCC7}.Release|x86.ActiveCfg = Release|Any CPU - {50F86C8F-E052-448C-84CE-08763945FCC7}.Release|x86.Build.0 = Release|Any CPU - {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Debug|x64.ActiveCfg = Debug|Any CPU - {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Debug|x64.Build.0 = Debug|Any CPU - {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Debug|x86.ActiveCfg = Debug|Any CPU - {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Debug|x86.Build.0 = Debug|Any CPU - {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Release|Any CPU.Build.0 = Release|Any CPU - {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Release|x64.ActiveCfg = Release|Any CPU - {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Release|x64.Build.0 = Release|Any CPU - {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Release|x86.ActiveCfg = Release|Any CPU - {3CEF9F32-5887-4977-AB8A-1BBC48875A21}.Release|x86.Build.0 = Release|Any CPU - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Debug|x64.ActiveCfg = Debug|Any CPU - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Debug|x64.Build.0 = Debug|Any CPU - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Debug|x86.ActiveCfg = Debug|Any CPU - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Debug|x86.Build.0 = Debug|Any CPU - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Release|Any CPU.Build.0 = Release|Any CPU - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Release|x64.ActiveCfg = Release|Any CPU - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Release|x64.Build.0 = Release|Any CPU - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Release|x86.ActiveCfg = Release|Any CPU - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825}.Release|x86.Build.0 = Release|Any CPU - {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Debug|x64.ActiveCfg = Debug|Any CPU - {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Debug|x64.Build.0 = Debug|Any CPU - {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Debug|x86.ActiveCfg = Debug|Any CPU - {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Debug|x86.Build.0 = Debug|Any CPU - {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Release|Any CPU.Build.0 = Release|Any CPU - {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Release|x64.ActiveCfg = Release|Any CPU - {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Release|x64.Build.0 = Release|Any CPU - {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Release|x86.ActiveCfg = Release|Any CPU - {78D2B20D-B173-4CF2-A763-F2AA5347947E}.Release|x86.Build.0 = Release|Any CPU - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Debug|x64.ActiveCfg = Debug|Any CPU - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Debug|x64.Build.0 = Debug|Any CPU - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Debug|x86.ActiveCfg = Debug|Any CPU - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Debug|x86.Build.0 = Debug|Any CPU - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Release|Any CPU.Build.0 = Release|Any CPU - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Release|x64.ActiveCfg = Release|Any CPU - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Release|x64.Build.0 = Release|Any CPU - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Release|x86.ActiveCfg = Release|Any CPU - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94}.Release|x86.Build.0 = Release|Any CPU - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Debug|x64.ActiveCfg = Debug|Any CPU - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Debug|x64.Build.0 = Debug|Any CPU - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Debug|x86.ActiveCfg = Debug|Any CPU - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Debug|x86.Build.0 = Debug|Any CPU - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Release|Any CPU.Build.0 = Release|Any CPU - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Release|x64.ActiveCfg = Release|Any CPU - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Release|x64.Build.0 = Release|Any CPU - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Release|x86.ActiveCfg = Release|Any CPU - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4}.Release|x86.Build.0 = Release|Any CPU - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Debug|x64.ActiveCfg = Debug|Any CPU - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Debug|x64.Build.0 = Debug|Any CPU - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Debug|x86.ActiveCfg = Debug|Any CPU - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Debug|x86.Build.0 = Debug|Any CPU - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Release|Any CPU.Build.0 = Release|Any CPU - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Release|x64.ActiveCfg = Release|Any CPU - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Release|x64.Build.0 = Release|Any CPU - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Release|x86.ActiveCfg = Release|Any CPU - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB}.Release|x86.Build.0 = Release|Any CPU - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Debug|x64.ActiveCfg = Debug|Any CPU - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Debug|x64.Build.0 = Debug|Any CPU - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Debug|x86.ActiveCfg = Debug|Any CPU - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Debug|x86.Build.0 = Debug|Any CPU - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Release|Any CPU.Build.0 = Release|Any CPU - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Release|x64.ActiveCfg = Release|Any CPU - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Release|x64.Build.0 = Release|Any CPU - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Release|x86.ActiveCfg = Release|Any CPU - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59}.Release|x86.Build.0 = Release|Any CPU - {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Debug|x64.ActiveCfg = Debug|Any CPU - {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Debug|x64.Build.0 = Debug|Any CPU - {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Debug|x86.ActiveCfg = Debug|Any CPU - {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Debug|x86.Build.0 = Debug|Any CPU - {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Release|Any CPU.Build.0 = Release|Any CPU - {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Release|x64.ActiveCfg = Release|Any CPU - {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Release|x64.Build.0 = Release|Any CPU - {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Release|x86.ActiveCfg = Release|Any CPU - {F934B760-4847-4BA8-9F6A-7F750ECF2328}.Release|x86.Build.0 = Release|Any CPU - {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Debug|x64.ActiveCfg = Debug|Any CPU - {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Debug|x64.Build.0 = Debug|Any CPU - {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Debug|x86.ActiveCfg = Debug|Any CPU - {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Debug|x86.Build.0 = Debug|Any CPU - {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Release|Any CPU.Build.0 = Release|Any CPU - {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Release|x64.ActiveCfg = Release|Any CPU - {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Release|x64.Build.0 = Release|Any CPU - {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Release|x86.ActiveCfg = Release|Any CPU - {2DC38785-B7B6-49E9-84D4-103A56D3604A}.Release|x86.Build.0 = Release|Any CPU - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Debug|x64.ActiveCfg = Debug|Any CPU - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Debug|x64.Build.0 = Debug|Any CPU - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Debug|x86.ActiveCfg = Debug|Any CPU - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Debug|x86.Build.0 = Debug|Any CPU - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Release|Any CPU.Build.0 = Release|Any CPU - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Release|x64.ActiveCfg = Release|Any CPU - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Release|x64.Build.0 = Release|Any CPU - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Release|x86.ActiveCfg = Release|Any CPU - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4}.Release|x86.Build.0 = Release|Any CPU - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Debug|x64.ActiveCfg = Debug|Any CPU - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Debug|x64.Build.0 = Debug|Any CPU - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Debug|x86.ActiveCfg = Debug|Any CPU - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Debug|x86.Build.0 = Debug|Any CPU - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Release|Any CPU.Build.0 = Release|Any CPU - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Release|x64.ActiveCfg = Release|Any CPU - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Release|x64.Build.0 = Release|Any CPU - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Release|x86.ActiveCfg = Release|Any CPU - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1}.Release|x86.Build.0 = Release|Any CPU - {7DD1722B-B971-4322-BA74-C622E808B84D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7DD1722B-B971-4322-BA74-C622E808B84D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7DD1722B-B971-4322-BA74-C622E808B84D}.Debug|x64.ActiveCfg = Debug|Any CPU - {7DD1722B-B971-4322-BA74-C622E808B84D}.Debug|x64.Build.0 = Debug|Any CPU - {7DD1722B-B971-4322-BA74-C622E808B84D}.Debug|x86.ActiveCfg = Debug|Any CPU - {7DD1722B-B971-4322-BA74-C622E808B84D}.Debug|x86.Build.0 = Debug|Any CPU - {7DD1722B-B971-4322-BA74-C622E808B84D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7DD1722B-B971-4322-BA74-C622E808B84D}.Release|Any CPU.Build.0 = Release|Any CPU - {7DD1722B-B971-4322-BA74-C622E808B84D}.Release|x64.ActiveCfg = Release|Any CPU - {7DD1722B-B971-4322-BA74-C622E808B84D}.Release|x64.Build.0 = Release|Any CPU - {7DD1722B-B971-4322-BA74-C622E808B84D}.Release|x86.ActiveCfg = Release|Any CPU - {7DD1722B-B971-4322-BA74-C622E808B84D}.Release|x86.Build.0 = Release|Any CPU - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Debug|x64.ActiveCfg = Debug|Any CPU - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Debug|x64.Build.0 = Debug|Any CPU - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Debug|x86.ActiveCfg = Debug|Any CPU - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Debug|x86.Build.0 = Debug|Any CPU - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Release|Any CPU.Build.0 = Release|Any CPU - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Release|x64.ActiveCfg = Release|Any CPU - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Release|x64.Build.0 = Release|Any CPU - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Release|x86.ActiveCfg = Release|Any CPU - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4}.Release|x86.Build.0 = Release|Any CPU - {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Debug|x64.ActiveCfg = Debug|Any CPU - {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Debug|x64.Build.0 = Debug|Any CPU - {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Debug|x86.ActiveCfg = Debug|Any CPU - {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Debug|x86.Build.0 = Debug|Any CPU - {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Release|Any CPU.Build.0 = Release|Any CPU - {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Release|x64.ActiveCfg = Release|Any CPU - {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Release|x64.Build.0 = Release|Any CPU - {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Release|x86.ActiveCfg = Release|Any CPU - {ADF569DC-E916-4F39-8ABF-8AA42F69266E}.Release|x86.Build.0 = Release|Any CPU - {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Debug|x64.ActiveCfg = Debug|Any CPU - {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Debug|x64.Build.0 = Debug|Any CPU - {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Debug|x86.ActiveCfg = Debug|Any CPU - {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Debug|x86.Build.0 = Debug|Any CPU - {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Release|Any CPU.Build.0 = Release|Any CPU - {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Release|x64.ActiveCfg = Release|Any CPU - {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Release|x64.Build.0 = Release|Any CPU - {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Release|x86.ActiveCfg = Release|Any CPU - {DFC13FA1-509C-42AF-870F-543C7A3F231B}.Release|x86.Build.0 = Release|Any CPU - {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Debug|x64.ActiveCfg = Debug|Any CPU - {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Debug|x64.Build.0 = Debug|Any CPU - {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Debug|x86.ActiveCfg = Debug|Any CPU - {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Debug|x86.Build.0 = Debug|Any CPU - {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Release|Any CPU.Build.0 = Release|Any CPU - {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Release|x64.ActiveCfg = Release|Any CPU - {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Release|x64.Build.0 = Release|Any CPU - {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Release|x86.ActiveCfg = Release|Any CPU - {2573526D-A764-4507-B2F6-1A2BF7A3FA93}.Release|x86.Build.0 = Release|Any CPU - {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Debug|x64.ActiveCfg = Debug|Any CPU - {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Debug|x64.Build.0 = Debug|Any CPU - {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Debug|x86.ActiveCfg = Debug|Any CPU - {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Debug|x86.Build.0 = Debug|Any CPU - {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Release|Any CPU.Build.0 = Release|Any CPU - {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Release|x64.ActiveCfg = Release|Any CPU - {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Release|x64.Build.0 = Release|Any CPU - {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Release|x86.ActiveCfg = Release|Any CPU - {6842C9FD-6720-43EA-83AD-DE315531DDA9}.Release|x86.Build.0 = Release|Any CPU - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Debug|x64.ActiveCfg = Debug|Any CPU - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Debug|x64.Build.0 = Debug|Any CPU - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Debug|x86.ActiveCfg = Debug|Any CPU - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Debug|x86.Build.0 = Debug|Any CPU - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Release|Any CPU.Build.0 = Release|Any CPU - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Release|x64.ActiveCfg = Release|Any CPU - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Release|x64.Build.0 = Release|Any CPU - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Release|x86.ActiveCfg = Release|Any CPU - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50}.Release|x86.Build.0 = Release|Any CPU - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Debug|x64.ActiveCfg = Debug|Any CPU - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Debug|x64.Build.0 = Debug|Any CPU - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Debug|x86.ActiveCfg = Debug|Any CPU - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Debug|x86.Build.0 = Debug|Any CPU - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Release|Any CPU.Build.0 = Release|Any CPU - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Release|x64.ActiveCfg = Release|Any CPU - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Release|x64.Build.0 = Release|Any CPU - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Release|x86.ActiveCfg = Release|Any CPU - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3}.Release|x86.Build.0 = Release|Any CPU - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Debug|x64.ActiveCfg = Debug|Any CPU - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Debug|x64.Build.0 = Debug|Any CPU - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Debug|x86.ActiveCfg = Debug|Any CPU - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Debug|x86.Build.0 = Debug|Any CPU - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Release|Any CPU.Build.0 = Release|Any CPU - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Release|x64.ActiveCfg = Release|Any CPU - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Release|x64.Build.0 = Release|Any CPU - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Release|x86.ActiveCfg = Release|Any CPU - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9}.Release|x86.Build.0 = Release|Any CPU - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Debug|Any CPU.Build.0 = Debug|Any CPU - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Debug|x64.ActiveCfg = Debug|Any CPU - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Debug|x64.Build.0 = Debug|Any CPU - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Debug|x86.ActiveCfg = Debug|Any CPU - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Debug|x86.Build.0 = Debug|Any CPU - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Release|Any CPU.ActiveCfg = Release|Any CPU - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Release|Any CPU.Build.0 = Release|Any CPU - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Release|x64.ActiveCfg = Release|Any CPU - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Release|x64.Build.0 = Release|Any CPU - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Release|x86.ActiveCfg = Release|Any CPU - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65}.Release|x86.Build.0 = Release|Any CPU - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Debug|x64.ActiveCfg = Debug|Any CPU - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Debug|x64.Build.0 = Debug|Any CPU - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Debug|x86.ActiveCfg = Debug|Any CPU - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Debug|x86.Build.0 = Debug|Any CPU - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Release|Any CPU.Build.0 = Release|Any CPU - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Release|x64.ActiveCfg = Release|Any CPU - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Release|x64.Build.0 = Release|Any CPU - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Release|x86.ActiveCfg = Release|Any CPU - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6}.Release|x86.Build.0 = Release|Any CPU - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Debug|x64.ActiveCfg = Debug|Any CPU - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Debug|x64.Build.0 = Debug|Any CPU - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Debug|x86.ActiveCfg = Debug|Any CPU - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Debug|x86.Build.0 = Debug|Any CPU - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Release|Any CPU.Build.0 = Release|Any CPU - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Release|x64.ActiveCfg = Release|Any CPU - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Release|x64.Build.0 = Release|Any CPU - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Release|x86.ActiveCfg = Release|Any CPU - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D}.Release|x86.Build.0 = Release|Any CPU - {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Debug|x64.ActiveCfg = Debug|Any CPU - {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Debug|x64.Build.0 = Debug|Any CPU - {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Debug|x86.ActiveCfg = Debug|Any CPU - {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Debug|x86.Build.0 = Debug|Any CPU - {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Release|Any CPU.Build.0 = Release|Any CPU - {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Release|x64.ActiveCfg = Release|Any CPU - {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Release|x64.Build.0 = Release|Any CPU - {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Release|x86.ActiveCfg = Release|Any CPU - {704EA4BF-71D9-442B-96D9-9B3E81068CC2}.Release|x86.Build.0 = Release|Any CPU - {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Debug|x64.ActiveCfg = Debug|Any CPU - {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Debug|x64.Build.0 = Debug|Any CPU - {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Debug|x86.ActiveCfg = Debug|Any CPU - {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Debug|x86.Build.0 = Debug|Any CPU - {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Release|Any CPU.Build.0 = Release|Any CPU - {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Release|x64.ActiveCfg = Release|Any CPU - {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Release|x64.Build.0 = Release|Any CPU - {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Release|x86.ActiveCfg = Release|Any CPU - {3549E707-E4B0-428F-9CF4-5B5B86219B6D}.Release|x86.Build.0 = Release|Any CPU - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Debug|x64.ActiveCfg = Debug|Any CPU - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Debug|x64.Build.0 = Debug|Any CPU - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Debug|x86.ActiveCfg = Debug|Any CPU - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Debug|x86.Build.0 = Debug|Any CPU - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Release|Any CPU.Build.0 = Release|Any CPU - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Release|x64.ActiveCfg = Release|Any CPU - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Release|x64.Build.0 = Release|Any CPU - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Release|x86.ActiveCfg = Release|Any CPU - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D}.Release|x86.Build.0 = Release|Any CPU - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Debug|x64.ActiveCfg = Debug|Any CPU - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Debug|x64.Build.0 = Debug|Any CPU - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Debug|x86.ActiveCfg = Debug|Any CPU - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Debug|x86.Build.0 = Debug|Any CPU - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Release|Any CPU.Build.0 = Release|Any CPU - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Release|x64.ActiveCfg = Release|Any CPU - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Release|x64.Build.0 = Release|Any CPU - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Release|x86.ActiveCfg = Release|Any CPU - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8}.Release|x86.Build.0 = Release|Any CPU - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Debug|x64.ActiveCfg = Debug|Any CPU - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Debug|x64.Build.0 = Debug|Any CPU - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Debug|x86.ActiveCfg = Debug|Any CPU - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Debug|x86.Build.0 = Debug|Any CPU - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Release|Any CPU.Build.0 = Release|Any CPU - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Release|x64.ActiveCfg = Release|Any CPU - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Release|x64.Build.0 = Release|Any CPU - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Release|x86.ActiveCfg = Release|Any CPU - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4}.Release|x86.Build.0 = Release|Any CPU - {F47ED576-F347-44C4-BEC7-6C00A500555A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F47ED576-F347-44C4-BEC7-6C00A500555A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F47ED576-F347-44C4-BEC7-6C00A500555A}.Debug|x64.ActiveCfg = Debug|Any CPU - {F47ED576-F347-44C4-BEC7-6C00A500555A}.Debug|x64.Build.0 = Debug|Any CPU - {F47ED576-F347-44C4-BEC7-6C00A500555A}.Debug|x86.ActiveCfg = Debug|Any CPU - {F47ED576-F347-44C4-BEC7-6C00A500555A}.Debug|x86.Build.0 = Debug|Any CPU - {F47ED576-F347-44C4-BEC7-6C00A500555A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F47ED576-F347-44C4-BEC7-6C00A500555A}.Release|Any CPU.Build.0 = Release|Any CPU - {F47ED576-F347-44C4-BEC7-6C00A500555A}.Release|x64.ActiveCfg = Release|Any CPU - {F47ED576-F347-44C4-BEC7-6C00A500555A}.Release|x64.Build.0 = Release|Any CPU - {F47ED576-F347-44C4-BEC7-6C00A500555A}.Release|x86.ActiveCfg = Release|Any CPU - {F47ED576-F347-44C4-BEC7-6C00A500555A}.Release|x86.Build.0 = Release|Any CPU - {F55E52BD-A846-4808-9482-E4AB0921328D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F55E52BD-A846-4808-9482-E4AB0921328D}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F55E52BD-A846-4808-9482-E4AB0921328D}.Debug|x64.ActiveCfg = Debug|Any CPU - {F55E52BD-A846-4808-9482-E4AB0921328D}.Debug|x64.Build.0 = Debug|Any CPU - {F55E52BD-A846-4808-9482-E4AB0921328D}.Debug|x86.ActiveCfg = Debug|Any CPU - {F55E52BD-A846-4808-9482-E4AB0921328D}.Debug|x86.Build.0 = Debug|Any CPU - {F55E52BD-A846-4808-9482-E4AB0921328D}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F55E52BD-A846-4808-9482-E4AB0921328D}.Release|Any CPU.Build.0 = Release|Any CPU - {F55E52BD-A846-4808-9482-E4AB0921328D}.Release|x64.ActiveCfg = Release|Any CPU - {F55E52BD-A846-4808-9482-E4AB0921328D}.Release|x64.Build.0 = Release|Any CPU - {F55E52BD-A846-4808-9482-E4AB0921328D}.Release|x86.ActiveCfg = Release|Any CPU - {F55E52BD-A846-4808-9482-E4AB0921328D}.Release|x86.Build.0 = Release|Any CPU - {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Debug|x64.ActiveCfg = Debug|Any CPU - {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Debug|x64.Build.0 = Debug|Any CPU - {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Debug|x86.ActiveCfg = Debug|Any CPU - {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Debug|x86.Build.0 = Debug|Any CPU - {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Release|Any CPU.Build.0 = Release|Any CPU - {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Release|x64.ActiveCfg = Release|Any CPU - {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Release|x64.Build.0 = Release|Any CPU - {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Release|x86.ActiveCfg = Release|Any CPU - {7C7B19F2-5822-4DA2-80D2-E077CE705039}.Release|x86.Build.0 = Release|Any CPU - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Debug|x64.ActiveCfg = Debug|Any CPU - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Debug|x64.Build.0 = Debug|Any CPU - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Debug|x86.ActiveCfg = Debug|Any CPU - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Debug|x86.Build.0 = Debug|Any CPU - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Release|Any CPU.Build.0 = Release|Any CPU - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Release|x64.ActiveCfg = Release|Any CPU - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Release|x64.Build.0 = Release|Any CPU - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Release|x86.ActiveCfg = Release|Any CPU - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845}.Release|x86.Build.0 = Release|Any CPU - {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Debug|x64.ActiveCfg = Debug|Any CPU - {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Debug|x64.Build.0 = Debug|Any CPU - {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Debug|x86.ActiveCfg = Debug|Any CPU - {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Debug|x86.Build.0 = Debug|Any CPU - {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Release|Any CPU.Build.0 = Release|Any CPU - {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Release|x64.ActiveCfg = Release|Any CPU - {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Release|x64.Build.0 = Release|Any CPU - {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Release|x86.ActiveCfg = Release|Any CPU - {D5B3DA06-E60D-4C85-8835-6A26003365AA}.Release|x86.Build.0 = Release|Any CPU - {2837A894-11D6-4DBF-B140-84C97C385A15}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {2837A894-11D6-4DBF-B140-84C97C385A15}.Debug|Any CPU.Build.0 = Debug|Any CPU - {2837A894-11D6-4DBF-B140-84C97C385A15}.Debug|x64.ActiveCfg = Debug|Any CPU - {2837A894-11D6-4DBF-B140-84C97C385A15}.Debug|x64.Build.0 = Debug|Any CPU - {2837A894-11D6-4DBF-B140-84C97C385A15}.Debug|x86.ActiveCfg = Debug|Any CPU - {2837A894-11D6-4DBF-B140-84C97C385A15}.Debug|x86.Build.0 = Debug|Any CPU - {2837A894-11D6-4DBF-B140-84C97C385A15}.Release|Any CPU.ActiveCfg = Release|Any CPU - {2837A894-11D6-4DBF-B140-84C97C385A15}.Release|Any CPU.Build.0 = Release|Any CPU - {2837A894-11D6-4DBF-B140-84C97C385A15}.Release|x64.ActiveCfg = Release|Any CPU - {2837A894-11D6-4DBF-B140-84C97C385A15}.Release|x64.Build.0 = Release|Any CPU - {2837A894-11D6-4DBF-B140-84C97C385A15}.Release|x86.ActiveCfg = Release|Any CPU - {2837A894-11D6-4DBF-B140-84C97C385A15}.Release|x86.Build.0 = Release|Any CPU - {39B9F0B5-3C53-4140-B158-96C67C03E876}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39B9F0B5-3C53-4140-B158-96C67C03E876}.Debug|Any CPU.Build.0 = Debug|Any CPU - {39B9F0B5-3C53-4140-B158-96C67C03E876}.Debug|x64.ActiveCfg = Debug|Any CPU - {39B9F0B5-3C53-4140-B158-96C67C03E876}.Debug|x64.Build.0 = Debug|Any CPU - {39B9F0B5-3C53-4140-B158-96C67C03E876}.Debug|x86.ActiveCfg = Debug|Any CPU - {39B9F0B5-3C53-4140-B158-96C67C03E876}.Debug|x86.Build.0 = Debug|Any CPU - {39B9F0B5-3C53-4140-B158-96C67C03E876}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39B9F0B5-3C53-4140-B158-96C67C03E876}.Release|Any CPU.Build.0 = Release|Any CPU - {39B9F0B5-3C53-4140-B158-96C67C03E876}.Release|x64.ActiveCfg = Release|Any CPU - {39B9F0B5-3C53-4140-B158-96C67C03E876}.Release|x64.Build.0 = Release|Any CPU - {39B9F0B5-3C53-4140-B158-96C67C03E876}.Release|x86.ActiveCfg = Release|Any CPU - {39B9F0B5-3C53-4140-B158-96C67C03E876}.Release|x86.Build.0 = Release|Any CPU - {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Debug|x64.ActiveCfg = Debug|Any CPU - {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Debug|x64.Build.0 = Debug|Any CPU - {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Debug|x86.ActiveCfg = Debug|Any CPU - {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Debug|x86.Build.0 = Debug|Any CPU - {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Release|Any CPU.Build.0 = Release|Any CPU - {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Release|x64.ActiveCfg = Release|Any CPU - {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Release|x64.Build.0 = Release|Any CPU - {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Release|x86.ActiveCfg = Release|Any CPU - {703ECD55-EF0E-4734-A28E-371EDD213EEF}.Release|x86.Build.0 = Release|Any CPU - {9E2178A0-5915-4308-B6C3-5C8758A47788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {9E2178A0-5915-4308-B6C3-5C8758A47788}.Debug|Any CPU.Build.0 = Debug|Any CPU - {9E2178A0-5915-4308-B6C3-5C8758A47788}.Debug|x64.ActiveCfg = Debug|Any CPU - {9E2178A0-5915-4308-B6C3-5C8758A47788}.Debug|x64.Build.0 = Debug|Any CPU - {9E2178A0-5915-4308-B6C3-5C8758A47788}.Debug|x86.ActiveCfg = Debug|Any CPU - {9E2178A0-5915-4308-B6C3-5C8758A47788}.Debug|x86.Build.0 = Debug|Any CPU - {9E2178A0-5915-4308-B6C3-5C8758A47788}.Release|Any CPU.ActiveCfg = Release|Any CPU - {9E2178A0-5915-4308-B6C3-5C8758A47788}.Release|Any CPU.Build.0 = Release|Any CPU - {9E2178A0-5915-4308-B6C3-5C8758A47788}.Release|x64.ActiveCfg = Release|Any CPU - {9E2178A0-5915-4308-B6C3-5C8758A47788}.Release|x64.Build.0 = Release|Any CPU - {9E2178A0-5915-4308-B6C3-5C8758A47788}.Release|x86.ActiveCfg = Release|Any CPU - {9E2178A0-5915-4308-B6C3-5C8758A47788}.Release|x86.Build.0 = Release|Any CPU - {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Debug|Any CPU.Build.0 = Debug|Any CPU - {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Debug|x64.ActiveCfg = Debug|Any CPU - {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Debug|x64.Build.0 = Debug|Any CPU - {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Debug|x86.ActiveCfg = Debug|Any CPU - {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Debug|x86.Build.0 = Debug|Any CPU - {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Release|Any CPU.Build.0 = Release|Any CPU - {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Release|x64.ActiveCfg = Release|Any CPU - {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Release|x64.Build.0 = Release|Any CPU - {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Release|x86.ActiveCfg = Release|Any CPU - {39BCFE6F-50C4-45AE-8D13-38FB423AB619}.Release|x86.Build.0 = Release|Any CPU - {3761F08D-06AE-4E07-8B88-F19C465DC169}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3761F08D-06AE-4E07-8B88-F19C465DC169}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3761F08D-06AE-4E07-8B88-F19C465DC169}.Debug|x64.ActiveCfg = Debug|Any CPU - {3761F08D-06AE-4E07-8B88-F19C465DC169}.Debug|x64.Build.0 = Debug|Any CPU - {3761F08D-06AE-4E07-8B88-F19C465DC169}.Debug|x86.ActiveCfg = Debug|Any CPU - {3761F08D-06AE-4E07-8B88-F19C465DC169}.Debug|x86.Build.0 = Debug|Any CPU - {3761F08D-06AE-4E07-8B88-F19C465DC169}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3761F08D-06AE-4E07-8B88-F19C465DC169}.Release|Any CPU.Build.0 = Release|Any CPU - {3761F08D-06AE-4E07-8B88-F19C465DC169}.Release|x64.ActiveCfg = Release|Any CPU - {3761F08D-06AE-4E07-8B88-F19C465DC169}.Release|x64.Build.0 = Release|Any CPU - {3761F08D-06AE-4E07-8B88-F19C465DC169}.Release|x86.ActiveCfg = Release|Any CPU - {3761F08D-06AE-4E07-8B88-F19C465DC169}.Release|x86.Build.0 = Release|Any CPU - {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Debug|x64.ActiveCfg = Debug|Any CPU - {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Debug|x64.Build.0 = Debug|Any CPU - {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Debug|x86.ActiveCfg = Debug|Any CPU - {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Debug|x86.Build.0 = Debug|Any CPU - {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Release|Any CPU.Build.0 = Release|Any CPU - {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Release|x64.ActiveCfg = Release|Any CPU - {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Release|x64.Build.0 = Release|Any CPU - {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Release|x86.ActiveCfg = Release|Any CPU - {DB02518A-49A1-4836-80B7-D69A70DE68BA}.Release|x86.Build.0 = Release|Any CPU - {47C888A8-6887-4FA2-A202-8653684F3651}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {47C888A8-6887-4FA2-A202-8653684F3651}.Debug|Any CPU.Build.0 = Debug|Any CPU - {47C888A8-6887-4FA2-A202-8653684F3651}.Debug|x64.ActiveCfg = Debug|Any CPU - {47C888A8-6887-4FA2-A202-8653684F3651}.Debug|x64.Build.0 = Debug|Any CPU - {47C888A8-6887-4FA2-A202-8653684F3651}.Debug|x86.ActiveCfg = Debug|Any CPU - {47C888A8-6887-4FA2-A202-8653684F3651}.Debug|x86.Build.0 = Debug|Any CPU - {47C888A8-6887-4FA2-A202-8653684F3651}.Release|Any CPU.ActiveCfg = Release|Any CPU - {47C888A8-6887-4FA2-A202-8653684F3651}.Release|Any CPU.Build.0 = Release|Any CPU - {47C888A8-6887-4FA2-A202-8653684F3651}.Release|x64.ActiveCfg = Release|Any CPU - {47C888A8-6887-4FA2-A202-8653684F3651}.Release|x64.Build.0 = Release|Any CPU - {47C888A8-6887-4FA2-A202-8653684F3651}.Release|x86.ActiveCfg = Release|Any CPU - {47C888A8-6887-4FA2-A202-8653684F3651}.Release|x86.Build.0 = Release|Any CPU - {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Debug|x64.ActiveCfg = Debug|Any CPU - {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Debug|x64.Build.0 = Debug|Any CPU - {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Debug|x86.ActiveCfg = Debug|Any CPU - {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Debug|x86.Build.0 = Debug|Any CPU - {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Release|Any CPU.Build.0 = Release|Any CPU - {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Release|x64.ActiveCfg = Release|Any CPU - {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Release|x64.Build.0 = Release|Any CPU - {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Release|x86.ActiveCfg = Release|Any CPU - {88691B89-0167-4E90-86F6-5FD6C974F7A3}.Release|x86.Build.0 = Release|Any CPU - {63631241-3CA1-40E2-9545-01E019414E4F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {63631241-3CA1-40E2-9545-01E019414E4F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {63631241-3CA1-40E2-9545-01E019414E4F}.Debug|x64.ActiveCfg = Debug|Any CPU - {63631241-3CA1-40E2-9545-01E019414E4F}.Debug|x64.Build.0 = Debug|Any CPU - {63631241-3CA1-40E2-9545-01E019414E4F}.Debug|x86.ActiveCfg = Debug|Any CPU - {63631241-3CA1-40E2-9545-01E019414E4F}.Debug|x86.Build.0 = Debug|Any CPU - {63631241-3CA1-40E2-9545-01E019414E4F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {63631241-3CA1-40E2-9545-01E019414E4F}.Release|Any CPU.Build.0 = Release|Any CPU - {63631241-3CA1-40E2-9545-01E019414E4F}.Release|x64.ActiveCfg = Release|Any CPU - {63631241-3CA1-40E2-9545-01E019414E4F}.Release|x64.Build.0 = Release|Any CPU - {63631241-3CA1-40E2-9545-01E019414E4F}.Release|x86.ActiveCfg = Release|Any CPU - {63631241-3CA1-40E2-9545-01E019414E4F}.Release|x86.Build.0 = Release|Any CPU - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Debug|x64.ActiveCfg = Debug|Any CPU - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Debug|x64.Build.0 = Debug|Any CPU - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Debug|x86.ActiveCfg = Debug|Any CPU - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Debug|x86.Build.0 = Debug|Any CPU - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Release|Any CPU.Build.0 = Release|Any CPU - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Release|x64.ActiveCfg = Release|Any CPU - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Release|x64.Build.0 = Release|Any CPU - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Release|x86.ActiveCfg = Release|Any CPU - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516}.Release|x86.Build.0 = Release|Any CPU + {2B65624B-6194-450C-970D-92E0F7CB1236}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2B65624B-6194-450C-970D-92E0F7CB1236}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2B65624B-6194-450C-970D-92E0F7CB1236}.Debug|x64.ActiveCfg = Debug|Any CPU + {2B65624B-6194-450C-970D-92E0F7CB1236}.Debug|x64.Build.0 = Debug|Any CPU + {2B65624B-6194-450C-970D-92E0F7CB1236}.Debug|x86.ActiveCfg = Debug|Any CPU + {2B65624B-6194-450C-970D-92E0F7CB1236}.Debug|x86.Build.0 = Debug|Any CPU + {2B65624B-6194-450C-970D-92E0F7CB1236}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2B65624B-6194-450C-970D-92E0F7CB1236}.Release|Any CPU.Build.0 = Release|Any CPU + {2B65624B-6194-450C-970D-92E0F7CB1236}.Release|x64.ActiveCfg = Release|Any CPU + {2B65624B-6194-450C-970D-92E0F7CB1236}.Release|x64.Build.0 = Release|Any CPU + {2B65624B-6194-450C-970D-92E0F7CB1236}.Release|x86.ActiveCfg = Release|Any CPU + {2B65624B-6194-450C-970D-92E0F7CB1236}.Release|x86.Build.0 = Release|Any CPU + {96826AA6-ADBF-4835-978D-9735EC150E47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {96826AA6-ADBF-4835-978D-9735EC150E47}.Debug|Any CPU.Build.0 = Debug|Any CPU + {96826AA6-ADBF-4835-978D-9735EC150E47}.Debug|x64.ActiveCfg = Debug|Any CPU + {96826AA6-ADBF-4835-978D-9735EC150E47}.Debug|x64.Build.0 = Debug|Any CPU + {96826AA6-ADBF-4835-978D-9735EC150E47}.Debug|x86.ActiveCfg = Debug|Any CPU + {96826AA6-ADBF-4835-978D-9735EC150E47}.Debug|x86.Build.0 = Debug|Any CPU + {96826AA6-ADBF-4835-978D-9735EC150E47}.Release|Any CPU.ActiveCfg = Release|Any CPU + {96826AA6-ADBF-4835-978D-9735EC150E47}.Release|Any CPU.Build.0 = Release|Any CPU + {96826AA6-ADBF-4835-978D-9735EC150E47}.Release|x64.ActiveCfg = Release|Any CPU + {96826AA6-ADBF-4835-978D-9735EC150E47}.Release|x64.Build.0 = Release|Any CPU + {96826AA6-ADBF-4835-978D-9735EC150E47}.Release|x86.ActiveCfg = Release|Any CPU + {96826AA6-ADBF-4835-978D-9735EC150E47}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {2D251CFD-A9DA-421F-B516-B25873348C08} = {471CA3C0-0BC9-4C0C-A013-D9356DBD0F38} - {00D7FBF0-801B-4B0E-9FDD-5C367E71444B} = {69958BAA-5D54-4F3F-82D6-F622F9491057} - {98E964A2-55DA-4740-9F2E-B64FDF6715DB} = {69958BAA-5D54-4F3F-82D6-F622F9491057} - {B9C61145-C010-4FAA-B9EA-4D1AEE20305F} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {4FDB7710-F4CA-45FB-9514-A4D288EDA44A} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {CC37B369-BA9E-4B82-A14D-68B58924885D} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {50F86C8F-E052-448C-84CE-08763945FCC7} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {3CEF9F32-5887-4977-AB8A-1BBC48875A21} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {8797AFCA-1EE8-4270-8FC4-F93A85F9C825} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {78D2B20D-B173-4CF2-A763-F2AA5347947E} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {C0BA460C-C1AC-4C54-9C88-60E4B8681E94} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {5889B0C9-50E5-4FDC-933B-8684B9FAB8E4} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {8BE0F92F-F353-4D63-9C6F-50D4A0BB7FAB} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {0CDA2765-C9DF-45EE-A099-0E2D4A980E59} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {F934B760-4847-4BA8-9F6A-7F750ECF2328} = {610E8065-1F29-41E8-A3FC-07ABDF16C8BB} - {2DC38785-B7B6-49E9-84D4-103A56D3604A} = {610E8065-1F29-41E8-A3FC-07ABDF16C8BB} - {610E8065-1F29-41E8-A3FC-07ABDF16C8BB} = {F49FFE3B-E1AE-483E-951F-D498F6ABA08E} - {263CD320-FAC5-4F0C-8FFB-788DD3C455D4} = {471CA3C0-0BC9-4C0C-A013-D9356DBD0F38} - {DD87D202-998B-4C20-8D68-C769797D7761} = {F49FFE3B-E1AE-483E-951F-D498F6ABA08E} - {D1E1AEDC-4870-4863-A8D3-B6A184E651C1} = {DD87D202-998B-4C20-8D68-C769797D7761} - {7DD1722B-B971-4322-BA74-C622E808B84D} = {DD87D202-998B-4C20-8D68-C769797D7761} - {A4C90265-C922-4145-8CDC-82EC5EBBC3A4} = {DD87D202-998B-4C20-8D68-C769797D7761} - {ADF569DC-E916-4F39-8ABF-8AA42F69266E} = {DD87D202-998B-4C20-8D68-C769797D7761} - {3B0C6936-E777-4413-B68A-1686A768F928} = {F49FFE3B-E1AE-483E-951F-D498F6ABA08E} - {DFC13FA1-509C-42AF-870F-543C7A3F231B} = {3B0C6936-E777-4413-B68A-1686A768F928} - {2573526D-A764-4507-B2F6-1A2BF7A3FA93} = {3B0C6936-E777-4413-B68A-1686A768F928} - {6842C9FD-6720-43EA-83AD-DE315531DDA9} = {3B0C6936-E777-4413-B68A-1686A768F928} - {D17C19AC-EC11-4661-BD23-DC8308CF71C0} = {F49FFE3B-E1AE-483E-951F-D498F6ABA08E} - {5DBEE6E0-8744-46E4-B711-8FEC98D41A50} = {D17C19AC-EC11-4661-BD23-DC8308CF71C0} - {BF78F36A-D072-4F1C-89B1-73D9C208ABB3} = {D17C19AC-EC11-4661-BD23-DC8308CF71C0} - {4BE54BD4-E561-4951-88F1-4DF09C3C3AB9} = {D17C19AC-EC11-4661-BD23-DC8308CF71C0} - {40E1EB3E-0CAE-45B1-B146-C7C8B3865C65} = {D17C19AC-EC11-4661-BD23-DC8308CF71C0} - {CE0C0288-E0A3-4B06-B05F-CCBFFC4E7BF6} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {D5519A45-AD22-41DB-ADDA-589C3E7D6C0D} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {704EA4BF-71D9-442B-96D9-9B3E81068CC2} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {3549E707-E4B0-428F-9CF4-5B5B86219B6D} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {1EA2BACF-3D44-4B05-A0E7-727AE13E693D} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {07E0093F-F05C-4AED-A489-1254C9854E21} = {F49FFE3B-E1AE-483E-951F-D498F6ABA08E} - {D815817F-CC0D-42CC-9C9C-8E229B9C03A8} = {07E0093F-F05C-4AED-A489-1254C9854E21} - {FAB78955-5E1F-4E6D-A79B-F85D8D18BDF4} = {07E0093F-F05C-4AED-A489-1254C9854E21} - {F47ED576-F347-44C4-BEC7-6C00A500555A} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {F55E52BD-A846-4808-9482-E4AB0921328D} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {7C7B19F2-5822-4DA2-80D2-E077CE705039} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {3A7712FC-9A91-4322-AF7E-4FEC6EB0D845} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {D5B3DA06-E60D-4C85-8835-6A26003365AA} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {2837A894-11D6-4DBF-B140-84C97C385A15} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {10109B62-DACC-42BA-AD8C-73DE4B93841B} = {F49FFE3B-E1AE-483E-951F-D498F6ABA08E} - {39B9F0B5-3C53-4140-B158-96C67C03E876} = {10109B62-DACC-42BA-AD8C-73DE4B93841B} - {703ECD55-EF0E-4734-A28E-371EDD213EEF} = {10109B62-DACC-42BA-AD8C-73DE4B93841B} - {9E2178A0-5915-4308-B6C3-5C8758A47788} = {10109B62-DACC-42BA-AD8C-73DE4B93841B} - {39BCFE6F-50C4-45AE-8D13-38FB423AB619} = {10109B62-DACC-42BA-AD8C-73DE4B93841B} - {3761F08D-06AE-4E07-8B88-F19C465DC169} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {DB02518A-49A1-4836-80B7-D69A70DE68BA} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {47C888A8-6887-4FA2-A202-8653684F3651} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {88691B89-0167-4E90-86F6-5FD6C974F7A3} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {63631241-3CA1-40E2-9545-01E019414E4F} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - {0C5BEA9A-9F07-49F5-8E84-80F3250E6516} = {98E964A2-55DA-4740-9F2E-B64FDF6715DB} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {B1AF41DC-A03E-47B1-BBDB-3DC27ABD9F74} + {58D3A2C3-F96F-5E57-2C6B-ECE59D6A18FC} = {827E0CD3-B72D-47B6-A68D-7590B98EB39B} + {2B65624B-6194-450C-970D-92E0F7CB1236} = {58D3A2C3-F96F-5E57-2C6B-ECE59D6A18FC} + {34DD7E53-B439-98E4-B5A2-4A9A2CFAE897} = {0C88DD14-F956-CE84-757C-A364CCF449FC} + {96826AA6-ADBF-4835-978D-9735EC150E47} = {34DD7E53-B439-98E4-B5A2-4A9A2CFAE897} EndGlobalSection EndGlobal diff --git a/FORK.md b/FORK.md index f7282c6..f5c1322 100644 --- a/FORK.md +++ b/FORK.md @@ -11,11 +11,13 @@ This repository is a fork of resolution, so a plugin package can ship RID-specific native assets without a deps file. - Package ID is `Hosihikari.McMaster.NETCore.Plugins`. - `test/Hosihikari.Tests` covers the RID-specific path probe. +- Upstream `samples/` removed; `DotNetCorePlugins.sln` contains only the library and the + Hosihikari test project. ## Licence Apache-2.0, the same as upstream. The original copyright and licence notices are retained. Modified files: `src/Plugins/Loader/ManagedLoadContext.cs`, `src/Plugins/McMaster.NETCore.Plugins.csproj`, `src/Plugins/Properties/AssemblyInfo.cs`, -`README.md`, `.github/workflows/ci.yml`; added `FORK.md` and `test/Hosihikari.Tests`. See -[LICENSE.txt](LICENSE.txt). +`README.md`, `DotNetCorePlugins.sln`, `.github/workflows/ci.yml`; added `FORK.md` and +`test/Hosihikari.Tests`; removed `samples/`. See [LICENSE.txt](LICENSE.txt). diff --git a/README.md b/README.md index b2b700e..648144e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ============ > **Hosihikari fork.** This fork probes `runtimes//native` before deps.json-based native -> resolution (ADR-0034). See [FORK.md](FORK.md). +> resolution (ADR-0034). Upstream samples are omitted. See [FORK.md](FORK.md). [![Build Status][ci-badge]][ci] [![Code Coverage][codecov-badge]][codecov] [![NuGet][nuget-badge] ![NuGet Downloads][nuget-download-badge]][nuget] diff --git a/samples/aspnetcore-mvc/MvcApp/Controllers/HomeController.cs b/samples/aspnetcore-mvc/MvcApp/Controllers/HomeController.cs deleted file mode 100644 index 8c32c5d..0000000 --- a/samples/aspnetcore-mvc/MvcApp/Controllers/HomeController.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace MvcWebApp.Controllers -{ - public class HomeController : Controller - { - public IActionResult Index() - { - return View(); - } - } -} diff --git a/samples/aspnetcore-mvc/MvcApp/MvcApp.csproj b/samples/aspnetcore-mvc/MvcApp/MvcApp.csproj deleted file mode 100644 index 6df2f63..0000000 --- a/samples/aspnetcore-mvc/MvcApp/MvcApp.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - net8.0 - - - - - - - - - - - - diff --git a/samples/aspnetcore-mvc/MvcApp/Program.cs b/samples/aspnetcore-mvc/MvcApp/Program.cs deleted file mode 100644 index 7d71032..0000000 --- a/samples/aspnetcore-mvc/MvcApp/Program.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; - -namespace MvcWebApp -{ - public class Program - { - public static void Main(string[] args) - { - CreateWebHostBuilder(args).Build().Run(); - } - - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup(); - } -} diff --git a/samples/aspnetcore-mvc/MvcApp/Startup.cs b/samples/aspnetcore-mvc/MvcApp/Startup.cs deleted file mode 100644 index 31eb228..0000000 --- a/samples/aspnetcore-mvc/MvcApp/Startup.cs +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.IO; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.DependencyInjection; - -namespace MvcWebApp -{ - public class Startup - { - public void ConfigureServices(IServiceCollection services) - { - var mvcBuilder = services.AddMvc(); - - foreach (var dir in Directory.GetDirectories(Path.Combine(AppContext.BaseDirectory, "plugins"))) - { - var pluginFile = Path.Combine(dir, Path.GetFileName(dir) + ".dll"); - // The AddPluginFromAssemblyFile method comes from McMaster.NETCore.Plugins.Mvc - mvcBuilder.AddPluginFromAssemblyFile(pluginFile); - } - } - - public void Configure(IApplicationBuilder app) - { - app.UseDeveloperExceptionPage(); - app - .UseRouting() - .UseEndpoints(r => - { - r.MapDefaultControllerRoute(); - }); - } - } -} diff --git a/samples/aspnetcore-mvc/MvcApp/Views/Home/Index.cshtml b/samples/aspnetcore-mvc/MvcApp/Views/Home/Index.cshtml deleted file mode 100644 index 7d9c9c2..0000000 --- a/samples/aspnetcore-mvc/MvcApp/Views/Home/Index.cshtml +++ /dev/null @@ -1,9 +0,0 @@ -@{ - ViewData["Title"] = "Home Page"; -} - -

Hello from the web app.

- - diff --git a/samples/aspnetcore-mvc/MvcApp/Views/_ViewImports.cshtml b/samples/aspnetcore-mvc/MvcApp/Views/_ViewImports.cshtml deleted file mode 100644 index 7aaace4..0000000 --- a/samples/aspnetcore-mvc/MvcApp/Views/_ViewImports.cshtml +++ /dev/null @@ -1,2 +0,0 @@ -@using MvcWebApp -@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/samples/aspnetcore-mvc/MvcAppPlugin1/MvcAppPlugin1.csproj b/samples/aspnetcore-mvc/MvcAppPlugin1/MvcAppPlugin1.csproj deleted file mode 100644 index 1205947..0000000 --- a/samples/aspnetcore-mvc/MvcAppPlugin1/MvcAppPlugin1.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - net8.0 - true - - - - - - - diff --git a/samples/aspnetcore-mvc/MvcAppPlugin1/MyPluginController.cs b/samples/aspnetcore-mvc/MvcAppPlugin1/MyPluginController.cs deleted file mode 100644 index 9bd61a2..0000000 --- a/samples/aspnetcore-mvc/MvcAppPlugin1/MyPluginController.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Mvc; - -namespace MvcAppPlugin1 -{ - public class MyPluginController : Controller - { - public IActionResult Index() => View(); - } -} diff --git a/samples/aspnetcore-mvc/MvcAppPlugin1/Views/MyPlugin/Index.cshtml b/samples/aspnetcore-mvc/MvcAppPlugin1/Views/MyPlugin/Index.cshtml deleted file mode 100644 index 8f3ce3a..0000000 --- a/samples/aspnetcore-mvc/MvcAppPlugin1/Views/MyPlugin/Index.cshtml +++ /dev/null @@ -1,5 +0,0 @@ -@{ - ViewData["Title"] = "Plugin1"; -} - -

Hello world from Plugin1!

diff --git a/samples/aspnetcore-mvc/README.md b/samples/aspnetcore-mvc/README.md deleted file mode 100644 index 123bd75..0000000 --- a/samples/aspnetcore-mvc/README.md +++ /dev/null @@ -1,23 +0,0 @@ -ASP.NET Core MVC Sample -======================= - -This sample contains 2 projects which demonstrate a simple plugin scenario. - -1. 'MvcWebApp' is an ASP.NET Core application which scans for a 'plugins' folder in its base directory and attempts to load any plugins it finds -2. 'MvcAppPlugin1' which implements MVC controllers. - -Normally, an ASP.NET Core MVC application must have a direct dependency on any assemblies -which provide controllers. However, as this sample demonstrates, an MVC application -can load controllers from a list of assemblies which is not known ahead of time when the -host app is built. - -## Running the sample - -Open a command line to this folder and run: - -``` -dotnet restore -dotnet run --project MvcApp/ -``` - -Then open diff --git a/samples/aspnetcore-mvc/aspnetcore-mvc.sln b/samples/aspnetcore-mvc/aspnetcore-mvc.sln deleted file mode 100644 index 3f1f511..0000000 --- a/samples/aspnetcore-mvc/aspnetcore-mvc.sln +++ /dev/null @@ -1,48 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcApp", "MvcApp\MvcApp.csproj", "{4F363469-7783-4A40-B1B5-455187AF1C76}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MvcAppPlugin1", "MvcAppPlugin1\MvcAppPlugin1.csproj", "{B6316996-A470-470F-9CF1-475096A0510A}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {4F363469-7783-4A40-B1B5-455187AF1C76}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4F363469-7783-4A40-B1B5-455187AF1C76}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4F363469-7783-4A40-B1B5-455187AF1C76}.Debug|x64.ActiveCfg = Debug|Any CPU - {4F363469-7783-4A40-B1B5-455187AF1C76}.Debug|x64.Build.0 = Debug|Any CPU - {4F363469-7783-4A40-B1B5-455187AF1C76}.Debug|x86.ActiveCfg = Debug|Any CPU - {4F363469-7783-4A40-B1B5-455187AF1C76}.Debug|x86.Build.0 = Debug|Any CPU - {4F363469-7783-4A40-B1B5-455187AF1C76}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4F363469-7783-4A40-B1B5-455187AF1C76}.Release|Any CPU.Build.0 = Release|Any CPU - {4F363469-7783-4A40-B1B5-455187AF1C76}.Release|x64.ActiveCfg = Release|Any CPU - {4F363469-7783-4A40-B1B5-455187AF1C76}.Release|x64.Build.0 = Release|Any CPU - {4F363469-7783-4A40-B1B5-455187AF1C76}.Release|x86.ActiveCfg = Release|Any CPU - {4F363469-7783-4A40-B1B5-455187AF1C76}.Release|x86.Build.0 = Release|Any CPU - {B6316996-A470-470F-9CF1-475096A0510A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B6316996-A470-470F-9CF1-475096A0510A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B6316996-A470-470F-9CF1-475096A0510A}.Debug|x64.ActiveCfg = Debug|Any CPU - {B6316996-A470-470F-9CF1-475096A0510A}.Debug|x64.Build.0 = Debug|Any CPU - {B6316996-A470-470F-9CF1-475096A0510A}.Debug|x86.ActiveCfg = Debug|Any CPU - {B6316996-A470-470F-9CF1-475096A0510A}.Debug|x86.Build.0 = Debug|Any CPU - {B6316996-A470-470F-9CF1-475096A0510A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B6316996-A470-470F-9CF1-475096A0510A}.Release|Any CPU.Build.0 = Release|Any CPU - {B6316996-A470-470F-9CF1-475096A0510A}.Release|x64.ActiveCfg = Release|Any CPU - {B6316996-A470-470F-9CF1-475096A0510A}.Release|x64.Build.0 = Release|Any CPU - {B6316996-A470-470F-9CF1-475096A0510A}.Release|x86.ActiveCfg = Release|Any CPU - {B6316996-A470-470F-9CF1-475096A0510A}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/samples/aspnetcore/Abstractions/Abstractions.csproj b/samples/aspnetcore/Abstractions/Abstractions.csproj deleted file mode 100644 index ea8d303..0000000 --- a/samples/aspnetcore/Abstractions/Abstractions.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - netstandard2.0 - - - - - - - diff --git a/samples/aspnetcore/Abstractions/IPlugin.cs b/samples/aspnetcore/Abstractions/IPlugin.cs deleted file mode 100644 index a9e7744..0000000 --- a/samples/aspnetcore/Abstractions/IPlugin.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; - -namespace Plugin.Abstractions -{ - public interface IWebPlugin - { - void Configure(IApplicationBuilder appBuilder); - void ConfigureServices(IServiceCollection services); - } -} diff --git a/samples/aspnetcore/Abstractions/IPluginLink.cs b/samples/aspnetcore/Abstractions/IPluginLink.cs deleted file mode 100644 index f9e6ba4..0000000 --- a/samples/aspnetcore/Abstractions/IPluginLink.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Plugin.Abstractions -{ - public interface IPluginLink - { - string GetHref(); - } -} diff --git a/samples/aspnetcore/MainWebApp/MainWebApp.csproj b/samples/aspnetcore/MainWebApp/MainWebApp.csproj deleted file mode 100644 index 55c7e92..0000000 --- a/samples/aspnetcore/MainWebApp/MainWebApp.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - net8.0 - - - - - - - - - - - - - - - - - - - - diff --git a/samples/aspnetcore/MainWebApp/Pages/Index.cshtml b/samples/aspnetcore/MainWebApp/Pages/Index.cshtml deleted file mode 100644 index d0eaa7b..0000000 --- a/samples/aspnetcore/MainWebApp/Pages/Index.cshtml +++ /dev/null @@ -1,11 +0,0 @@ -@page -@using MainWebApp -@model IndexModel -
    -@foreach (var link in Model.Links) -{ -
  • - @link -
  • -} -
diff --git a/samples/aspnetcore/MainWebApp/Pages/Index.cshtml.cs b/samples/aspnetcore/MainWebApp/Pages/Index.cshtml.cs deleted file mode 100644 index e0a6dfc..0000000 --- a/samples/aspnetcore/MainWebApp/Pages/Index.cshtml.cs +++ /dev/null @@ -1,24 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using System.Linq; -using Microsoft.AspNetCore.Mvc.RazorPages; -using Plugin.Abstractions; - -namespace MainWebApp -{ - public class IndexModel : PageModel - { - public IndexModel(IEnumerable pluginLinks) - { - Links = pluginLinks.Select(p => p.GetHref()).ToArray(); - } - - public string[] Links { get; } - - public void OnGet() - { - } - } -} diff --git a/samples/aspnetcore/MainWebApp/Program.cs b/samples/aspnetcore/MainWebApp/Program.cs deleted file mode 100644 index 300580a..0000000 --- a/samples/aspnetcore/MainWebApp/Program.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; - -namespace MainWebApp -{ - public class Program - { - public static void Main(string[] args) - { - CreateWebHostBuilder(args).Build().Run(); - } - - public static IWebHostBuilder CreateWebHostBuilder(string[] args) => - WebHost.CreateDefaultBuilder(args) - .UseStartup(); - } -} diff --git a/samples/aspnetcore/MainWebApp/Startup.cs b/samples/aspnetcore/MainWebApp/Startup.cs deleted file mode 100644 index 7cc94f5..0000000 --- a/samples/aspnetcore/MainWebApp/Startup.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using McMaster.NETCore.Plugins; -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.DependencyInjection; -using Plugin.Abstractions; - -namespace MainWebApp -{ - public class Startup - { - private readonly List _plugins = new(); - - public Startup() - { - foreach (var pluginDir in Directory.GetDirectories(Path.Combine(AppContext.BaseDirectory, "plugins"))) - { - var dirName = Path.GetFileName(pluginDir); - var pluginFile = Path.Combine(pluginDir, dirName + ".dll"); - var loader = PluginLoader.CreateFromAssemblyFile(pluginFile, - // this ensures that the plugin resolves to the same version of DependencyInjection - // and ASP.NET Core that the current app uses - sharedTypes: new[] - { - typeof(IApplicationBuilder), - typeof(IWebPlugin), - typeof(IServiceCollection), - }); - foreach (var type in loader.LoadDefaultAssembly() - .GetTypes() - .Where(t => typeof(IWebPlugin).IsAssignableFrom(t) && !t.IsAbstract)) - { - Console.WriteLine("Found plugin " + type.Name); - var plugin = (IWebPlugin)Activator.CreateInstance(type)!; - _plugins.Add(plugin); - } - } - } - - public void ConfigureServices(IServiceCollection services) - { - services.AddMvc().AddMvcOptions(o => o.EnableEndpointRouting = false); - - foreach (var plugin in _plugins) - { - plugin.ConfigureServices(services); - } - } - - public void Configure(IApplicationBuilder app, IWebHostEnvironment env) - { - app.UseDeveloperExceptionPage(); - - foreach (var plugin in _plugins) - { - plugin.Configure(app); - } - - app.UseMvcWithDefaultRoute(); - } - } -} diff --git a/samples/aspnetcore/README.md b/samples/aspnetcore/README.md deleted file mode 100644 index c174ac1..0000000 --- a/samples/aspnetcore/README.md +++ /dev/null @@ -1,42 +0,0 @@ -ASP.NET Core Sample -=================== - -This sample contains 4 projects which demonstrate a simple plugin scenario. - -1. 'Abstractions' defines common interfaces shared by the web application (host) and plugins -2. 'MainWebApp' is an ASP.NET Core application which scans for a 'plugins' folder in its base directory and attempts to load any plugins it finds -3. 'WebAppPlugin1' references 'Abstractions' and implements `IWebPlugin`. This plugin has a dependency on [AutoMapper](https://www.nuget.org/packages/AutoMapper/) version 6. -4. 'WebAppPlugin2' is the same as plugin1, but it uses AutoMapper version 7. - -Normally, in .NET Core applications you cannot reference two different versions of the same assembly. -However, as this sample demonstrates, using .NET Core plugins you can load and use two different versions. - -* http://localhost:5000/plugin/v1 responds with -``` -This plugin uses AutoMapper, Version=6.2.2.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005 -``` - -* http://localhost:5000/plugin/v2 responds with -``` -This plugin uses AutoMapper, Version=7.0.1.0, Culture=neutral, PublicKeyToken=be96cd2c38ef1005 -``` - -There are some important types, however, which must share the same identity between the plugins and the host. -To ensure type exchange works between the host and the plugins, the MainWebApp project uses the `sharedTypes` -parameter on `PluginLoader.CreateFromAssemblyFile`. - -```csharp - var loader = PluginLoader.CreateFromAssemblyFile( - pluginAssembly, - sharedTypes: new[] - { - typeof(IApplicationBuilder), - typeof(IWebPlugin), - typeof(IServiceCollection), - }); -``` - -This is important because the plugins in this sample are compiled for ASP.NET Core 2.0 interfaces, -but the MainWebApp uses ASP.NET Core 2.1. If not for this parameter, the plugins would also attempt to use -a private copy of the ASP.NET Core implementations and type exchange between the plugin and the web app -would fail to resolve `IApplicationBuilder` and `IServiceCollection` as the same type. diff --git a/samples/aspnetcore/WebAppPlugin1/WebAppPlugin1.csproj b/samples/aspnetcore/WebAppPlugin1/WebAppPlugin1.csproj deleted file mode 100644 index 683920f..0000000 --- a/samples/aspnetcore/WebAppPlugin1/WebAppPlugin1.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - netstandard2.0 - - - - - - - - diff --git a/samples/aspnetcore/WebAppPlugin1/WebPlugin1.cs b/samples/aspnetcore/WebAppPlugin1/WebPlugin1.cs deleted file mode 100644 index a3c956e..0000000 --- a/samples/aspnetcore/WebAppPlugin1/WebPlugin1.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Plugin.Abstractions; - -namespace Plugin1 -{ - internal class WebPlugin1 : IWebPlugin, IPluginLink - { - public string GetHref() => "/plugin/v1"; - - public void ConfigureServices(IServiceCollection services) - { - services.AddScoped(); - } - - public void Configure(IApplicationBuilder appBuilder) - { - appBuilder.Map("/plugin/v1", c => - { - var autoMapperType = typeof(AutoMapper.IMapper).Assembly; - c.Run(async (ctx) => - { - await ctx.Response.WriteAsync("This plugin uses " + autoMapperType.GetName().ToString()); - }); - }); - } - } -} diff --git a/samples/aspnetcore/WebAppPlugin2/WebAppPlugin2.csproj b/samples/aspnetcore/WebAppPlugin2/WebAppPlugin2.csproj deleted file mode 100644 index 3de01e7..0000000 --- a/samples/aspnetcore/WebAppPlugin2/WebAppPlugin2.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - netstandard2.0 - - - - - - - - diff --git a/samples/aspnetcore/WebAppPlugin2/WebPlugin2.cs b/samples/aspnetcore/WebAppPlugin2/WebPlugin2.cs deleted file mode 100644 index 13d2bf5..0000000 --- a/samples/aspnetcore/WebAppPlugin2/WebPlugin2.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.Extensions.DependencyInjection; -using Plugin.Abstractions; - -namespace Plugin2 -{ - internal class WebPlugin2 : IWebPlugin, IPluginLink - { - public string GetHref() => "/plugin/v2"; - - public void ConfigureServices(IServiceCollection services) - { - services.AddScoped(); - } - - public void Configure(IApplicationBuilder appBuilder) - { - appBuilder.Map("/plugin/v2", c => - { - var autoMapperType = typeof(AutoMapper.IMapper).Assembly; - c.Run(async (ctx) => - { - await ctx.Response.WriteAsync("This plugin uses " + autoMapperType.GetName().ToString()); - }); - }); - } - } -} diff --git a/samples/aspnetcore/aspnetcore.sln b/samples/aspnetcore/aspnetcore.sln deleted file mode 100644 index 4207936..0000000 --- a/samples/aspnetcore/aspnetcore.sln +++ /dev/null @@ -1,76 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Abstractions", "Abstractions\Abstractions.csproj", "{7CDF7B07-F103-4C22-9BB3-26B7C11716FF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MainWebApp", "MainWebApp\MainWebApp.csproj", "{781A86B1-C278-44CD-997B-1AA26D6BFB38}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAppPlugin1", "WebAppPlugin1\WebAppPlugin1.csproj", "{B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WebAppPlugin2", "WebAppPlugin2\WebAppPlugin2.csproj", "{ECAB37EC-1E82-4B1F-8C51-983E46A557A4}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Debug|x64.ActiveCfg = Debug|Any CPU - {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Debug|x64.Build.0 = Debug|Any CPU - {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Debug|x86.ActiveCfg = Debug|Any CPU - {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Debug|x86.Build.0 = Debug|Any CPU - {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Release|Any CPU.Build.0 = Release|Any CPU - {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Release|x64.ActiveCfg = Release|Any CPU - {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Release|x64.Build.0 = Release|Any CPU - {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Release|x86.ActiveCfg = Release|Any CPU - {7CDF7B07-F103-4C22-9BB3-26B7C11716FF}.Release|x86.Build.0 = Release|Any CPU - {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Debug|Any CPU.Build.0 = Debug|Any CPU - {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Debug|x64.ActiveCfg = Debug|Any CPU - {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Debug|x64.Build.0 = Debug|Any CPU - {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Debug|x86.ActiveCfg = Debug|Any CPU - {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Debug|x86.Build.0 = Debug|Any CPU - {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Release|Any CPU.ActiveCfg = Release|Any CPU - {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Release|Any CPU.Build.0 = Release|Any CPU - {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Release|x64.ActiveCfg = Release|Any CPU - {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Release|x64.Build.0 = Release|Any CPU - {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Release|x86.ActiveCfg = Release|Any CPU - {781A86B1-C278-44CD-997B-1AA26D6BFB38}.Release|x86.Build.0 = Release|Any CPU - {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Debug|x64.ActiveCfg = Debug|Any CPU - {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Debug|x64.Build.0 = Debug|Any CPU - {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Debug|x86.ActiveCfg = Debug|Any CPU - {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Debug|x86.Build.0 = Debug|Any CPU - {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Release|Any CPU.Build.0 = Release|Any CPU - {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Release|x64.ActiveCfg = Release|Any CPU - {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Release|x64.Build.0 = Release|Any CPU - {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Release|x86.ActiveCfg = Release|Any CPU - {B17BE4CB-C382-4C7D-8B11-FD21E0E1A7CB}.Release|x86.Build.0 = Release|Any CPU - {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Debug|x64.ActiveCfg = Debug|Any CPU - {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Debug|x64.Build.0 = Debug|Any CPU - {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Debug|x86.ActiveCfg = Debug|Any CPU - {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Debug|x86.Build.0 = Debug|Any CPU - {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Release|Any CPU.Build.0 = Release|Any CPU - {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Release|x64.ActiveCfg = Release|Any CPU - {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Release|x64.Build.0 = Release|Any CPU - {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Release|x86.ActiveCfg = Release|Any CPU - {ECAB37EC-1E82-4B1F-8C51-983E46A557A4}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/samples/dependency-injection/DI.HostApp/DI.HostApp.csproj b/samples/dependency-injection/DI.HostApp/DI.HostApp.csproj deleted file mode 100644 index b6ff6ad..0000000 --- a/samples/dependency-injection/DI.HostApp/DI.HostApp.csproj +++ /dev/null @@ -1,26 +0,0 @@ - - - - Exe - net8.0 - - - - - - - - - - - - - - - - - - diff --git a/samples/dependency-injection/DI.HostApp/Program.cs b/samples/dependency-injection/DI.HostApp/Program.cs deleted file mode 100644 index cc4cbd9..0000000 --- a/samples/dependency-injection/DI.HostApp/Program.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using McMaster.NETCore.Plugins; -using Microsoft.Extensions.DependencyInjection; - -namespace DependencyInjection -{ - public class Program - { - public static void Main(string[] args) - { - var services = new ServiceCollection(); - var loaders = GetPluginLoaders(); - - ConfigureServices(services, loaders); - - using var serviceProvider = services.BuildServiceProvider(); - - var consumer = serviceProvider.GetRequiredService(); - consumer.Consume(); - } - - private static List GetPluginLoaders() - { - var loaders = new List(); - - // create plugin loaders - var pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins"); - foreach (var dir in Directory.GetDirectories(pluginsDir)) - { - var dirName = Path.GetFileName(dir); - var pluginDll = Path.Combine(dir, dirName + ".dll"); - if (File.Exists(pluginDll)) - { - var loader = PluginLoader.CreateFromAssemblyFile( - pluginDll, - sharedTypes: new[] { typeof(IPluginFactory), typeof(IServiceCollection) }); - loaders.Add(loader); - } - } - - return loaders; - } - - private static void ConfigureServices(ServiceCollection services, List loaders) - { - // Create an instance of plugin types - foreach (var loader in loaders) - { - foreach (var pluginType in loader - .LoadDefaultAssembly() - .GetTypes() - .Where(t => typeof(IPluginFactory).IsAssignableFrom(t) && !t.IsAbstract)) - { - // This assumes the implementation of IPluginFactory has a parameterless constructor - var plugin = Activator.CreateInstance(pluginType) as IPluginFactory; - - plugin?.Configure(services); - } - } - } - - } -} diff --git a/samples/dependency-injection/DI.SharedAbstractions/DI.SharedAbstractions.csproj b/samples/dependency-injection/DI.SharedAbstractions/DI.SharedAbstractions.csproj deleted file mode 100644 index ba25dfa..0000000 --- a/samples/dependency-injection/DI.SharedAbstractions/DI.SharedAbstractions.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - netstandard2.0 - - - - - - - diff --git a/samples/dependency-injection/DI.SharedAbstractions/Fruit.cs b/samples/dependency-injection/DI.SharedAbstractions/Fruit.cs deleted file mode 100644 index d7ad871..0000000 --- a/samples/dependency-injection/DI.SharedAbstractions/Fruit.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace DependencyInjection -{ - public class Fruit - { - public Fruit(string name) - { - Name = name; - } - - public string Name { get; } - } -} diff --git a/samples/dependency-injection/DI.SharedAbstractions/IFruitConsumer.cs b/samples/dependency-injection/DI.SharedAbstractions/IFruitConsumer.cs deleted file mode 100644 index aec332b..0000000 --- a/samples/dependency-injection/DI.SharedAbstractions/IFruitConsumer.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace DependencyInjection -{ - public interface IFruitConsumer - { - void Consume(); - } -} diff --git a/samples/dependency-injection/DI.SharedAbstractions/IFruitProducer.cs b/samples/dependency-injection/DI.SharedAbstractions/IFruitProducer.cs deleted file mode 100644 index 5fa9f44..0000000 --- a/samples/dependency-injection/DI.SharedAbstractions/IFruitProducer.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace DependencyInjection -{ - public interface IFruitProducer - { - IEnumerable Produce(); - } -} diff --git a/samples/dependency-injection/DI.SharedAbstractions/IPluginFactory.cs b/samples/dependency-injection/DI.SharedAbstractions/IPluginFactory.cs deleted file mode 100644 index bbfbfa7..0000000 --- a/samples/dependency-injection/DI.SharedAbstractions/IPluginFactory.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.Extensions.DependencyInjection; - -namespace DependencyInjection -{ - public interface IPluginFactory - { - void Configure(IServiceCollection services); - } -} diff --git a/samples/dependency-injection/MyPlugin1/MyFruitProducer.cs b/samples/dependency-injection/MyPlugin1/MyFruitProducer.cs deleted file mode 100644 index a923d7a..0000000 --- a/samples/dependency-injection/MyPlugin1/MyFruitProducer.cs +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using DependencyInjection; - -namespace MyPlugin1 -{ - internal class MyFruitProducer : IFruitProducer - { - public IEnumerable Produce() - { - yield return new Fruit("banana"); - yield return new Fruit("orange"); - yield return new Fruit("strawberry"); - } - } -} diff --git a/samples/dependency-injection/MyPlugin1/MyPlugin1.csproj b/samples/dependency-injection/MyPlugin1/MyPlugin1.csproj deleted file mode 100644 index 9447b64..0000000 --- a/samples/dependency-injection/MyPlugin1/MyPlugin1.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - netstandard2.0 - - - - - - - diff --git a/samples/dependency-injection/MyPlugin1/PluginConfiguration.cs b/samples/dependency-injection/MyPlugin1/PluginConfiguration.cs deleted file mode 100644 index ae780cd..0000000 --- a/samples/dependency-injection/MyPlugin1/PluginConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using DependencyInjection; -using Microsoft.Extensions.DependencyInjection; - -namespace MyPlugin1 -{ - public class PluginConfiguration : IPluginFactory - { - public void Configure(IServiceCollection services) - { - services.AddSingleton(); - } - } -} diff --git a/samples/dependency-injection/MyPlugin2/MyFruitConsumer.cs b/samples/dependency-injection/MyPlugin2/MyFruitConsumer.cs deleted file mode 100644 index 3ccfc65..0000000 --- a/samples/dependency-injection/MyPlugin2/MyFruitConsumer.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using DependencyInjection; - -namespace MyPlugin2 -{ - internal class MyFruitConsumer : IFruitConsumer - { - private readonly IEnumerable _producers; - - public MyFruitConsumer(IEnumerable producers) - { - _producers = producers; - } - - public void Consume() - { - foreach (var producer in _producers) - { - foreach (var fruit in producer.Produce()) - { - Console.WriteLine($"Consumed {fruit.Name}"); - } - } - } - } -} diff --git a/samples/dependency-injection/MyPlugin2/MyPlugin2.csproj b/samples/dependency-injection/MyPlugin2/MyPlugin2.csproj deleted file mode 100644 index 9447b64..0000000 --- a/samples/dependency-injection/MyPlugin2/MyPlugin2.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - netstandard2.0 - - - - - - - diff --git a/samples/dependency-injection/MyPlugin2/PluginConfiguration.cs b/samples/dependency-injection/MyPlugin2/PluginConfiguration.cs deleted file mode 100644 index 42e29a8..0000000 --- a/samples/dependency-injection/MyPlugin2/PluginConfiguration.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using DependencyInjection; -using Microsoft.Extensions.DependencyInjection; - -namespace MyPlugin2 -{ - public class PluginConfiguration : IPluginFactory - { - public void Configure(IServiceCollection services) - { - services.AddSingleton(); - } - } -} diff --git a/samples/dependency-injection/README.md b/samples/dependency-injection/README.md deleted file mode 100644 index bef3b56..0000000 --- a/samples/dependency-injection/README.md +++ /dev/null @@ -1,18 +0,0 @@ -Dependency Injection Sample -=========================== - -This sample contains 4 projects which demonstrate a plugin scenario which coordinates types between -plugins using a dependency injection container. - -* 'DI.HostApp' is a console application which scans for a 'plugins' folder in its base directory and attempts to load any plugins it finds. It then configures plugins in a dependency injection collection. -* 'DI.SharedAbstractions' which contains an interface shared by plugins and the host. -* 'MyPlugin1' and 'MyPlugin2' implement shared abstractions and register them with the host. - -## Running the sample - -Open a command line to this folder and run: - -``` -dotnet restore -dotnet run --project DI.HostApp/ -``` diff --git a/samples/dynamic-implementation/Contracts/Contracts.csproj b/samples/dynamic-implementation/Contracts/Contracts.csproj deleted file mode 100644 index b68338c..0000000 --- a/samples/dynamic-implementation/Contracts/Contracts.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Library - net8.0 - - - - - - - diff --git a/samples/dynamic-implementation/Contracts/Fruit.cs b/samples/dynamic-implementation/Contracts/Fruit.cs deleted file mode 100644 index 0439f4d..0000000 --- a/samples/dynamic-implementation/Contracts/Fruit.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Contracts -{ - public class Fruit - { - public string? Name { get; set; } - } -} diff --git a/samples/dynamic-implementation/Contracts/IFruitService.cs b/samples/dynamic-implementation/Contracts/IFruitService.cs deleted file mode 100644 index 2313606..0000000 --- a/samples/dynamic-implementation/Contracts/IFruitService.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; - -namespace Contracts -{ - public interface IFruitService - { - List GetFruits(); - } -} diff --git a/samples/dynamic-implementation/Contracts/IMixerService.cs b/samples/dynamic-implementation/Contracts/IMixerService.cs deleted file mode 100644 index 44fc8ac..0000000 --- a/samples/dynamic-implementation/Contracts/IMixerService.cs +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace Contracts -{ - public interface IMixerService - { - string MixIt(); - } -} diff --git a/samples/dynamic-implementation/Contracts/IPluginFactory.cs b/samples/dynamic-implementation/Contracts/IPluginFactory.cs deleted file mode 100644 index 2887a23..0000000 --- a/samples/dynamic-implementation/Contracts/IPluginFactory.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Microsoft.Extensions.DependencyInjection; - -namespace Contracts -{ - public interface IPluginFactory - { - void Configure(IServiceCollection services); - } -} diff --git a/samples/dynamic-implementation/DynamicImplementation.sln b/samples/dynamic-implementation/DynamicImplementation.sln deleted file mode 100644 index 71ebcb5..0000000 --- a/samples/dynamic-implementation/DynamicImplementation.sln +++ /dev/null @@ -1,43 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.28307.757 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Host", "Host\Host.csproj", "{52016A46-23E0-48BB-AB79-CA13BECB55DF}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Contracts", "Contracts\Contracts.csproj", "{138FD514-7F88-43A5-A3CD-6084ABA316CD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ServiceImplementation", "ServiceImplementation\ServiceImplementation.csproj", "{A8104C41-1224-4626-A812-ED561BD35432}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mixer", "Mixer\Mixer.csproj", "{DB9CE54F-D2A1-454A-BF58-19F41D4F3E9B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {52016A46-23E0-48BB-AB79-CA13BECB55DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {52016A46-23E0-48BB-AB79-CA13BECB55DF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {52016A46-23E0-48BB-AB79-CA13BECB55DF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {52016A46-23E0-48BB-AB79-CA13BECB55DF}.Release|Any CPU.Build.0 = Release|Any CPU - {138FD514-7F88-43A5-A3CD-6084ABA316CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {138FD514-7F88-43A5-A3CD-6084ABA316CD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {138FD514-7F88-43A5-A3CD-6084ABA316CD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {138FD514-7F88-43A5-A3CD-6084ABA316CD}.Release|Any CPU.Build.0 = Release|Any CPU - {A8104C41-1224-4626-A812-ED561BD35432}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A8104C41-1224-4626-A812-ED561BD35432}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A8104C41-1224-4626-A812-ED561BD35432}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A8104C41-1224-4626-A812-ED561BD35432}.Release|Any CPU.Build.0 = Release|Any CPU - {DB9CE54F-D2A1-454A-BF58-19F41D4F3E9B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB9CE54F-D2A1-454A-BF58-19F41D4F3E9B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB9CE54F-D2A1-454A-BF58-19F41D4F3E9B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB9CE54F-D2A1-454A-BF58-19F41D4F3E9B}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {98A28D5B-F00C-44F0-A5DA-80A16E551E57} - EndGlobalSection -EndGlobal diff --git a/samples/dynamic-implementation/Host/Host.csproj b/samples/dynamic-implementation/Host/Host.csproj deleted file mode 100644 index 0aca3d8..0000000 --- a/samples/dynamic-implementation/Host/Host.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - Exe - net8.0 - - - - - - - - - - - - - - - - - - - - diff --git a/samples/dynamic-implementation/Host/Program.cs b/samples/dynamic-implementation/Host/Program.cs deleted file mode 100644 index 9d6dbe9..0000000 --- a/samples/dynamic-implementation/Host/Program.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using Contracts; -using McMaster.NETCore.Plugins; -using Microsoft.Extensions.DependencyInjection; - -namespace Host -{ - internal class Program - { - public static void Main(string[] args) - { - var services = new ServiceCollection(); - var loaders = GetPluginLoaders(); - - ConfigureServices(services, loaders); - - var serviceProvider = services.BuildServiceProvider(); - - var mixer = serviceProvider.GetRequiredService(); - mixer.MixIt(); - } - - private static List GetPluginLoaders() - { - var loaders = new List(); - - // create plugin loaders - var pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins"); - foreach (var dir in Directory.GetDirectories(pluginsDir)) - { - var dirName = Path.GetFileName(dir); - var pluginDll = Path.Combine(dir, dirName + ".dll"); - if (File.Exists(pluginDll)) - { - var loader = PluginLoader.CreateFromAssemblyFile( - pluginDll, - sharedTypes: new[] { typeof(IPluginFactory), typeof(IServiceCollection) }); - loaders.Add(loader); - } - } - - return loaders; - } - - private static void ConfigureServices(ServiceCollection services, List loaders) - { - // Create an instance of plugin types - foreach (var loader in loaders) - { - foreach (var pluginType in loader - .LoadDefaultAssembly() - .GetTypes() - .Where(t => typeof(IPluginFactory).IsAssignableFrom(t) && !t.IsAbstract)) - { - // This assumes the implementation of IPluginFactory has a parameterless constructor - var plugin = Activator.CreateInstance(pluginType) as IPluginFactory; - - plugin?.Configure(services); - } - } - } - } -} diff --git a/samples/dynamic-implementation/Mixer/Mixer.csproj b/samples/dynamic-implementation/Mixer/Mixer.csproj deleted file mode 100644 index 485d8bc..0000000 --- a/samples/dynamic-implementation/Mixer/Mixer.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - net8.0 - Library - - - - - - - diff --git a/samples/dynamic-implementation/Mixer/MixerPlugin.cs b/samples/dynamic-implementation/Mixer/MixerPlugin.cs deleted file mode 100644 index 2de93df..0000000 --- a/samples/dynamic-implementation/Mixer/MixerPlugin.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Contracts; -using Microsoft.Extensions.DependencyInjection; - -namespace Mixer -{ - public class MixerPluginConfiguration : IPluginFactory - { - public void Configure(IServiceCollection services) - { - services.AddSingleton(); - services.AddSingleton(); - } - } -} diff --git a/samples/dynamic-implementation/Mixer/MixerService.cs b/samples/dynamic-implementation/Mixer/MixerService.cs deleted file mode 100644 index c494703..0000000 --- a/samples/dynamic-implementation/Mixer/MixerService.cs +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Linq; -using Contracts; - -namespace Mixer -{ - public class MixerService : IMixerService - { - protected IFruitService fruit; - public MixerService(IFruitService fruit) - { - this.fruit = fruit; - } - - public string MixIt() - { - return string.Join(",", fruit.GetFruits().Select(x => x.Name).ToArray()); - } - } -} diff --git a/samples/dynamic-implementation/Mixer/StandardFruiteService.cs b/samples/dynamic-implementation/Mixer/StandardFruiteService.cs deleted file mode 100644 index cff7fe5..0000000 --- a/samples/dynamic-implementation/Mixer/StandardFruiteService.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using Contracts; - -namespace Mixer -{ - public class StandardFruiteService : IFruitService - { - public List GetFruits() - { - return new List() - { - new Fruit { Name="Banana" }, - new Fruit { Name="Apple" }, - new Fruit { Name="Carrot" }, - }; - } - } -} diff --git a/samples/dynamic-implementation/README.md b/samples/dynamic-implementation/README.md deleted file mode 100644 index a3a2a14..0000000 --- a/samples/dynamic-implementation/README.md +++ /dev/null @@ -1,21 +0,0 @@ -Dynamic implementation -=========================== - -This sample contains 4 projects which demonstrate a plugin scenario that coordinates types between -plugins using a dependency injection container. In this scenario, one implements a default service definition (Mixer), but adding a plugin the normal behavior is changed. - -This sample can be useful to create a pluggable application that can be extended or altered just by adding new modules. - -* 'Host': console app that contains the sample -* 'Contracts': which contains an interface shared by plugins and the host. -* 'Mixer': this plugin contains the default mixer implementation. It contains the FruitService, which gives 3 fruits and the Mixer service that returns the fruit shake. -* 'ServiceImplementation': this plugin contains an alternative version of the FruitService. By adding this to the plugin set, the default behavior is altered and the shake is composed by only Banana. - -## Running the sample - -Open a command line to this folder and run, otherwise open the .sln file: - -``` -dotnet restore -dotnet run --project Host/ -``` diff --git a/samples/dynamic-implementation/ServiceImplementation/OverrideFruiteService.cs b/samples/dynamic-implementation/ServiceImplementation/OverrideFruiteService.cs deleted file mode 100644 index 0e4e503..0000000 --- a/samples/dynamic-implementation/ServiceImplementation/OverrideFruiteService.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System.Collections.Generic; -using Contracts; - -namespace ServiceImplementation -{ - public class OverrideFruiteService : IFruitService - { - public List GetFruits() - { - return new List() - { - new Fruit { Name="Banana" } - }; - } - } -} diff --git a/samples/dynamic-implementation/ServiceImplementation/OverridePlugin.cs b/samples/dynamic-implementation/ServiceImplementation/OverridePlugin.cs deleted file mode 100644 index 73f1f18..0000000 --- a/samples/dynamic-implementation/ServiceImplementation/OverridePlugin.cs +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using Contracts; -using Microsoft.Extensions.DependencyInjection; - -namespace ServiceImplementation -{ - public class OverridePluginConfiguration : IPluginFactory - { - public void Configure(IServiceCollection services) - { - //this service override the standard one. unload this plugin or comment this to use the basic service - services.AddSingleton(); - } - } -} diff --git a/samples/dynamic-implementation/ServiceImplementation/ServiceImplementation.csproj b/samples/dynamic-implementation/ServiceImplementation/ServiceImplementation.csproj deleted file mode 100644 index 26cd29c..0000000 --- a/samples/dynamic-implementation/ServiceImplementation/ServiceImplementation.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - Library - net8.0 - - - - - - - diff --git a/samples/hello-world/HostApp/HostApp.csproj b/samples/hello-world/HostApp/HostApp.csproj deleted file mode 100644 index efe024a..0000000 --- a/samples/hello-world/HostApp/HostApp.csproj +++ /dev/null @@ -1,21 +0,0 @@ - - - - Exe - net8.0 - - - - - - - - - - - - - diff --git a/samples/hello-world/HostApp/Program.cs b/samples/hello-world/HostApp/Program.cs deleted file mode 100644 index 2ff700d..0000000 --- a/samples/hello-world/HostApp/Program.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using HelloWorld; -using McMaster.NETCore.Plugins; - -var loaders = new List(); - -// create plugin loaders -var pluginsDir = Path.Combine(AppContext.BaseDirectory, "plugins"); -foreach (var dir in Directory.GetDirectories(pluginsDir)) -{ - var dirName = Path.GetFileName(dir); - var pluginDll = Path.Combine(dir, dirName + ".dll"); - if (File.Exists(pluginDll)) - { - var loader = PluginLoader.CreateFromAssemblyFile( - pluginDll, - sharedTypes: new[] { typeof(IPlugin) }); - loaders.Add(loader); - } -} - -// Create an instance of plugin types -foreach (var loader in loaders) -{ - foreach (var pluginType in loader - .LoadDefaultAssembly() - .GetTypes() - .Where(t => typeof(IPlugin).IsAssignableFrom(t) && !t.IsAbstract)) - { - // This assumes the implementation of IPlugin has a parameterless constructor - var plugin = Activator.CreateInstance(pluginType) as IPlugin; - - Console.WriteLine($"Created plugin instance '{plugin?.GetName()}'."); - } -} diff --git a/samples/hello-world/MyPlugin/MyPlugin.csproj b/samples/hello-world/MyPlugin/MyPlugin.csproj deleted file mode 100644 index 985bfc0..0000000 --- a/samples/hello-world/MyPlugin/MyPlugin.csproj +++ /dev/null @@ -1,11 +0,0 @@ - - - - netstandard2.0 - - - - - - - diff --git a/samples/hello-world/MyPlugin/MyPlugin1.cs b/samples/hello-world/MyPlugin/MyPlugin1.cs deleted file mode 100644 index bd5e461..0000000 --- a/samples/hello-world/MyPlugin/MyPlugin1.cs +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using HelloWorld; - -namespace MyPlugin -{ - internal class MyPlugin1 : IPlugin - { - public string GetName() => "My plugin v1"; - } -} diff --git a/samples/hello-world/PluginContract/IPlugin.cs b/samples/hello-world/PluginContract/IPlugin.cs deleted file mode 100644 index 591d45c..0000000 --- a/samples/hello-world/PluginContract/IPlugin.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -namespace HelloWorld -{ - /// - /// This interface is an example of one way to define the interactions between the host and plugins. - /// There is nothing special about the name "IPlugin"; it's used here to illustrate a concept. - /// Look at https://github.com/natemcmaster/DotNetCorePlugins/tree/main/samples for additional examples - /// of ways you could define the interaction between host and plugins. - /// - public interface IPlugin - { - string GetName(); - } -} diff --git a/samples/hello-world/PluginContract/PluginContract.csproj b/samples/hello-world/PluginContract/PluginContract.csproj deleted file mode 100644 index 9f5c4f4..0000000 --- a/samples/hello-world/PluginContract/PluginContract.csproj +++ /dev/null @@ -1,7 +0,0 @@ - - - - netstandard2.0 - - - diff --git a/samples/hello-world/README.md b/samples/hello-world/README.md deleted file mode 100644 index c8a8820..0000000 --- a/samples/hello-world/README.md +++ /dev/null @@ -1,17 +0,0 @@ -"Hello World" Sample -==================== - -This sample contains 3 projects which demonstrate a simple plugin scenario. - -1. 'HostApp' is a console application which scans for a 'plugins' folder in its base directory and attempts to load any plugins it finds -2. 'MyPlugin' which implements an implementation of this plugin -3. 'PluginContract' which contains an interface shared by plugins and the host. - -## Running the sample - -Open a command line to this folder and run: - -``` -dotnet restore -dotnet run --project HostApp/ -``` diff --git a/samples/hello-world/hello-world.sln b/samples/hello-world/hello-world.sln deleted file mode 100644 index d27f2c2..0000000 --- a/samples/hello-world/hello-world.sln +++ /dev/null @@ -1,62 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26124.0 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HostApp", "HostApp\HostApp.csproj", "{FAA91115-37CE-4A74-8EC1-C58E15E5D683}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MyPlugin", "MyPlugin\MyPlugin.csproj", "{EF895D6F-B174-46AF-936C-F4E3FE2B96E0}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PluginContract", "PluginContract\PluginContract.csproj", "{5F3E90BD-7D9E-4469-A89A-63B0A8F77465}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Debug|x64.ActiveCfg = Debug|Any CPU - {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Debug|x64.Build.0 = Debug|Any CPU - {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Debug|x86.ActiveCfg = Debug|Any CPU - {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Debug|x86.Build.0 = Debug|Any CPU - {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Release|Any CPU.Build.0 = Release|Any CPU - {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Release|x64.ActiveCfg = Release|Any CPU - {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Release|x64.Build.0 = Release|Any CPU - {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Release|x86.ActiveCfg = Release|Any CPU - {FAA91115-37CE-4A74-8EC1-C58E15E5D683}.Release|x86.Build.0 = Release|Any CPU - {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Debug|x64.ActiveCfg = Debug|Any CPU - {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Debug|x64.Build.0 = Debug|Any CPU - {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Debug|x86.ActiveCfg = Debug|Any CPU - {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Debug|x86.Build.0 = Debug|Any CPU - {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Release|Any CPU.Build.0 = Release|Any CPU - {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Release|x64.ActiveCfg = Release|Any CPU - {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Release|x64.Build.0 = Release|Any CPU - {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Release|x86.ActiveCfg = Release|Any CPU - {EF895D6F-B174-46AF-936C-F4E3FE2B96E0}.Release|x86.Build.0 = Release|Any CPU - {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Debug|Any CPU.Build.0 = Debug|Any CPU - {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Debug|x64.ActiveCfg = Debug|Any CPU - {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Debug|x64.Build.0 = Debug|Any CPU - {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Debug|x86.ActiveCfg = Debug|Any CPU - {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Debug|x86.Build.0 = Debug|Any CPU - {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Release|Any CPU.ActiveCfg = Release|Any CPU - {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Release|Any CPU.Build.0 = Release|Any CPU - {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Release|x64.ActiveCfg = Release|Any CPU - {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Release|x64.Build.0 = Release|Any CPU - {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Release|x86.ActiveCfg = Release|Any CPU - {5F3E90BD-7D9E-4469-A89A-63B0A8F77465}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/samples/hot-reload/HotReloadApp/HotReloadApp.csproj b/samples/hot-reload/HotReloadApp/HotReloadApp.csproj deleted file mode 100644 index fdf43cd..0000000 --- a/samples/hot-reload/HotReloadApp/HotReloadApp.csproj +++ /dev/null @@ -1,16 +0,0 @@ - - - - Exe - net8.0 - - - - - - - - diff --git a/samples/hot-reload/HotReloadApp/Program.cs b/samples/hot-reload/HotReloadApp/Program.cs deleted file mode 100644 index 24a0982..0000000 --- a/samples/hot-reload/HotReloadApp/Program.cs +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Threading; -using System.Threading.Tasks; -using McMaster.NETCore.Plugins; - -var pluginPath = args[0]; -var loader = PluginLoader.CreateFromAssemblyFile(pluginPath, - config => config.EnableHotReload = true); - -loader.Reloaded += ShowPluginInfo; - -var cts = new CancellationTokenSource(); -Console.CancelKeyPress += (_, __) => cts.Cancel(); - -// Show info on first load -InvokePlugin(loader); - -await Task.Delay(-1, cts.Token); - -static void ShowPluginInfo(object sender, PluginReloadedEventArgs eventArgs) -{ - Console.ForegroundColor = ConsoleColor.Blue; - Console.Write("HotReloadApp: "); - Console.ResetColor(); - Console.WriteLine("plugin was reloaded"); - InvokePlugin(eventArgs.Loader); -} - -static void InvokePlugin(PluginLoader loader) -{ - var assembly = loader.LoadDefaultAssembly(); - assembly - .GetType("TimestampedPlugin.InfoDisplayer", throwOnError: true) - !.GetMethod("Print") - !.Invoke(null, null); -} diff --git a/samples/hot-reload/README.md b/samples/hot-reload/README.md deleted file mode 100644 index 986602c..0000000 --- a/samples/hot-reload/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Hot Reload Sample - -This is a minimal sample of how to take advantage of hot reloading support. - -To run this sample, execute the `run.sh` script. This will: - -* Compile the projects once -* Start the HotReloadApp console application - * This creates a single loader with hot reloading enabled - * It subscribes to the `PluginLoader.Reloaded` event to be notified when a new version of the assemblies - are availabled. - * It invokes the new version of the assembly -* Rebuilds the TimestampedPlugin every 5 seconds (until you press CTRL+C to exit) diff --git a/samples/hot-reload/TimestampedPlugin/InfoDisplayer.cs b/samples/hot-reload/TimestampedPlugin/InfoDisplayer.cs deleted file mode 100644 index 7e0b6a1..0000000 --- a/samples/hot-reload/TimestampedPlugin/InfoDisplayer.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) Nate McMaster. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Linq; -using System.Reflection; -using Microsoft.Data.Sqlite; - -namespace TimestampedPlugin -{ - public class InfoDisplayer - { - public static void Print() - { - // Use something from Microsoft.Data.Sqlite to trigger loading of native dependency - var connectionString = new SqliteConnectionStringBuilder - { - DataSource = "HELLO" - }; - - var compileTimestamp = typeof(InfoDisplayer) - .Assembly - .GetCustomAttributes() - .First(a => a.Key == "CompileTimestamp") - .Value; - Console.ForegroundColor = ConsoleColor.Green; - Console.Write("TimestampedPlugin: "); - Console.ResetColor(); - Console.WriteLine($"this plugin was compiled at {compileTimestamp}. {connectionString.DataSource}!"); - } - } -} diff --git a/samples/hot-reload/TimestampedPlugin/TimestampedPlugin.csproj b/samples/hot-reload/TimestampedPlugin/TimestampedPlugin.csproj deleted file mode 100644 index 75e9a11..0000000 --- a/samples/hot-reload/TimestampedPlugin/TimestampedPlugin.csproj +++ /dev/null @@ -1,18 +0,0 @@ - - - - net8.0 - - - - - <_Parameter1>CompileTimestamp - <_Parameter2>$([System.DateTime]::Now.ToString('h:mm:ss tt')) - - - - - - - - diff --git a/samples/hot-reload/hot-reload.sln b/samples/hot-reload/hot-reload.sln deleted file mode 100644 index b6f3054..0000000 --- a/samples/hot-reload/hot-reload.sln +++ /dev/null @@ -1,48 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.29209.152 -MinimumVisualStudioVersion = 15.0.26124.0 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "HotReloadApp", "HotReloadApp\HotReloadApp.csproj", "{421F2562-390A-45C3-B028-32A0027AD8BD}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TimestampedPlugin", "TimestampedPlugin\TimestampedPlugin.csproj", "{92DCE5FD-1A77-4A77-AE04-2820004A446B}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Debug|x64 = Debug|x64 - Debug|x86 = Debug|x86 - Release|Any CPU = Release|Any CPU - Release|x64 = Release|x64 - Release|x86 = Release|x86 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {421F2562-390A-45C3-B028-32A0027AD8BD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {421F2562-390A-45C3-B028-32A0027AD8BD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {421F2562-390A-45C3-B028-32A0027AD8BD}.Debug|x64.ActiveCfg = Debug|Any CPU - {421F2562-390A-45C3-B028-32A0027AD8BD}.Debug|x64.Build.0 = Debug|Any CPU - {421F2562-390A-45C3-B028-32A0027AD8BD}.Debug|x86.ActiveCfg = Debug|Any CPU - {421F2562-390A-45C3-B028-32A0027AD8BD}.Debug|x86.Build.0 = Debug|Any CPU - {421F2562-390A-45C3-B028-32A0027AD8BD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {421F2562-390A-45C3-B028-32A0027AD8BD}.Release|Any CPU.Build.0 = Release|Any CPU - {421F2562-390A-45C3-B028-32A0027AD8BD}.Release|x64.ActiveCfg = Release|Any CPU - {421F2562-390A-45C3-B028-32A0027AD8BD}.Release|x64.Build.0 = Release|Any CPU - {421F2562-390A-45C3-B028-32A0027AD8BD}.Release|x86.ActiveCfg = Release|Any CPU - {421F2562-390A-45C3-B028-32A0027AD8BD}.Release|x86.Build.0 = Release|Any CPU - {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Debug|x64.ActiveCfg = Debug|Any CPU - {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Debug|x64.Build.0 = Debug|Any CPU - {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Debug|x86.ActiveCfg = Debug|Any CPU - {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Debug|x86.Build.0 = Debug|Any CPU - {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Release|Any CPU.Build.0 = Release|Any CPU - {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Release|x64.ActiveCfg = Release|Any CPU - {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Release|x64.Build.0 = Release|Any CPU - {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Release|x86.ActiveCfg = Release|Any CPU - {92DCE5FD-1A77-4A77-AE04-2820004A446B}.Release|x86.Build.0 = Release|Any CPU - EndGlobalSection -EndGlobal diff --git a/samples/hot-reload/run.ps1 b/samples/hot-reload/run.ps1 deleted file mode 100644 index bdd2dc9..0000000 --- a/samples/hot-reload/run.ps1 +++ /dev/null @@ -1,42 +0,0 @@ -#!/usr/bin/env pwsh - -$ErrorActionPreference = 'Stop' - -Push-Location $PSScriptRoot -try { - $publish_dir = "$PSScriptRoot/bin/plugins/TimestampedPlugin/" - - function log { - Write-Host -NoNewline -ForegroundColor Yellow "run.ps1: " - Write-Host $args - } - - function publish { - Write-Host "" - dotnet publish --no-restore TimestampedPlugin/ -o $publish_dir -nologo - Write-Host "" - } - - log "Compiling apps" - - & dotnet build HotReloadApp -nologo -clp:NoSummary - & publish - - log "Use CTRL+C to exit" - - $bg_args = @("run", "--no-build", "--project", "HotReloadApp", "$publish_dir/TimestampedPlugin.dll") - $host_process = Start-Process -NoNewWindow -FilePath dotnet -ArgumentList $bg_args - try { - while ($true) { - Start-Sleep 5 - log "Rebuilding plugin..." - publish - } - } - finally { - $host_process.Kill() - } -} -finally { - Pop-Location -} diff --git a/samples/hot-reload/run.sh b/samples/hot-reload/run.sh deleted file mode 100644 index e991b47..0000000 --- a/samples/hot-reload/run.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash - -set -e - -RESET="\033[0m" -YELLOW="\033[0;33m" -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -pushd $DIR >/dev/null - -publish_dir="$DIR/bin/plugins/TimestampedPlugin/" - -publish() { - echo "" - dotnet publish --no-restore TimestampedPlugin/ -o $publish_dir -nologo - echo "" -} - -echo -e "${YELLOW}run.sh:${RESET} Compiling apps" -dotnet build HotReloadApp -nologo -clp:NoSummary -publish - -trap "kill 0" EXIT - -echo -e "${YELLOW}run.sh:${RESET} Use CTRL+C to exit" - -dotnet run --no-build --project HotReloadApp -- "$publish_dir/TimestampedPlugin.dll" & - -while true -do - sleep 5 - echo -e "${YELLOW}run.sh:${RESET} Rebuilding plugin..." - publish -done From 5352ddae977a397226188e7b4e20f0d7dc86438e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E7=BA=BE?= Date: Mon, 19 Oct 2026 16:51:24 +0800 Subject: [PATCH 04/12] =?UTF-8?q?ci:=20=E6=9B=B4=E6=96=B0=E5=B7=A5?= =?UTF-8?q?=E4=BD=9C=E6=B5=81=E5=8A=A8=E4=BD=9C=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/ci.yml | 4 ++-- .github/workflows/stale.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2485306..e93b9ee 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,8 +8,8 @@ jobs: test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: actions/setup-dotnet@v4 + - uses: actions/checkout@v7 + - uses: actions/setup-dotnet@v6 with: dotnet-version: '10.0.x' - name: 跑断言 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 9237e4d..9d4a5ac 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -13,7 +13,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v9 + - uses: actions/stale@v11 with: debug-only: ${{ github.event.inputs.debug-only == 'true' }} days-before-stale: 365 From 8e918af1b6fcb2c05d7c03dcf6dff58fbb5bc564 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E7=BA=BE?= Date: Mon, 19 Oct 2026 17:06:47 +0800 Subject: [PATCH 05/12] =?UTF-8?q?ci:=20=E5=8F=91=E5=B8=83=20fork=20?= =?UTF-8?q?=E5=8C=85=E5=88=B0=20NuGet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/publish.yml | 56 +++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/publish.yml diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..57b8dec --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,56 @@ +name: publish + +on: + push: + tags: ['v*'] + workflow_dispatch: + +jobs: + nuget: + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # 换 OIDC 令牌(Trusted Publishing) + + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-dotnet@v6 + with: + dotnet-version: '10.0.x' + + # 标签与包版本漂了就在构建前停下。 + - name: 版本一致 + if: startsWith(github.ref, 'refs/tags/') + shell: pwsh + run: | + $tag = '${{ github.ref_name }}' -replace '^v', '' + $version = dotnet msbuild src/Plugins/McMaster.NETCore.Plugins.csproj -getProperty:PackageVersion + if ($version -ne $tag) { throw "PackageVersion 是 $version,标签是 $tag" } + + # 版本号的事实源是 csproj 里的 ,标签只是触发器。 + - name: 打包 + run: dotnet pack src/Plugins/McMaster.NETCore.Plugins.csproj -c Release -o out + + # 用 OIDC 令牌换一把临时 API key —— 仓库里不存任何长期密钥。 + # 换来的 key 只有一小时有效期、且一个令牌只能换一把,所以这一步紧贴推送。 + - name: 换临时 API key + uses: NuGet/login@v1 + id: login + with: + # nuget.org 的用户名(profile name),不是邮箱。 + user: ${{ secrets.NUGET_USER }} + + - name: 推送 + run: > + dotnet nuget push out/*.nupkg + --api-key ${{ steps.login.outputs.NUGET_API_KEY }} + --source https://api.nuget.org/v3/index.json + --skip-duplicate + + - name: 推送符号 + run: > + dotnet nuget push out/*.snupkg + --api-key ${{ steps.login.outputs.NUGET_API_KEY }} + --source https://api.nuget.org/v3/index.json + --skip-duplicate From bc25e435cb83abe71dcf8fcc37900623167b98cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E7=BA=BE?= Date: Mon, 19 Oct 2026 17:22:46 +0800 Subject: [PATCH 06/12] =?UTF-8?q?docs:=20=E8=AF=B4=E6=98=8E=20NuGet=20?= =?UTF-8?q?=E5=8F=91=E5=B8=83=E6=B5=81=E7=A8=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FORK.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/FORK.md b/FORK.md index f5c1322..3ddd297 100644 --- a/FORK.md +++ b/FORK.md @@ -14,6 +14,13 @@ This repository is a fork of - Upstream `samples/` removed; `DotNetCorePlugins.sln` contains only the library and the Hosihikari test project. +## Publishing + +`.github/workflows/publish.yml` publishes `Hosihikari.McMaster.NETCore.Plugins` to nuget.org +through Trusted Publishing when a `v*` tag is pushed. The tag must equal `` in +`src/Plugins/McMaster.NETCore.Plugins.csproj`; the workflow checks this before packing and pushes +both the package and its symbols. + ## Licence Apache-2.0, the same as upstream. The original copyright and licence notices are retained. From 8e6d0421414d6c8cff829da3e8ce3168ecf18d1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E7=BA=BE?= Date: Mon, 19 Oct 2026 17:36:32 +0800 Subject: [PATCH 07/12] =?UTF-8?q?chore:=20=E6=9B=B4=E6=96=B0=E4=BE=9D?= =?UTF-8?q?=E8=B5=96=E9=A1=B9=E7=89=88=E6=9C=AC=E5=92=8C=E7=9B=AE=E6=A0=87?= =?UTF-8?q?=E6=A1=86=E6=9E=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Directory.Build.targets | 4 ++-- src/Plugins/McMaster.NETCore.Plugins.csproj | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 33bd792..dda0ebe 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,8 +1,8 @@ - - + + diff --git a/src/Plugins/McMaster.NETCore.Plugins.csproj b/src/Plugins/McMaster.NETCore.Plugins.csproj index cac4198..8c9276f 100644 --- a/src/Plugins/McMaster.NETCore.Plugins.csproj +++ b/src/Plugins/McMaster.NETCore.Plugins.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 library true true From 964f675c6ff7c6d9b95de188030e071d70a877ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E7=BA=BE?= Date: Mon, 19 Oct 2026 17:42:22 +0800 Subject: [PATCH 08/12] =?UTF-8?q?fix:=20=E5=85=AC=E5=85=B1=20API=20?= =?UTF-8?q?=E5=A3=B0=E6=98=8E=E4=B8=8E=20.NET=2010=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PublicApiAnalyzers 5.6.0 要求委托的 Invoke 进入公共 API 清单;xunit.v3 的 MTP 在 .NET 10 上用 global.json 选择测试运行器。 --- global.json | 9 +++++++++ src/Plugins/PublicAPI.Unshipped.txt | 1 + .../Hosihikari.McMaster.NETCore.Plugins.Tests.csproj | 9 ++++++--- 3 files changed, 16 insertions(+), 3 deletions(-) create mode 100644 global.json diff --git a/global.json b/global.json new file mode 100644 index 0000000..9e9a685 --- /dev/null +++ b/global.json @@ -0,0 +1,9 @@ +{ + "sdk": { + "version": "10.0.402", + "rollForward": "latestFeature" + }, + "test": { + "runner": "Microsoft.Testing.Platform" + } +} diff --git a/src/Plugins/PublicAPI.Unshipped.txt b/src/Plugins/PublicAPI.Unshipped.txt index e69de29..de2ce0e 100644 --- a/src/Plugins/PublicAPI.Unshipped.txt +++ b/src/Plugins/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +virtual McMaster.NETCore.Plugins.PluginReloadedEventHandler.Invoke(object! sender, McMaster.NETCore.Plugins.PluginReloadedEventArgs! eventArgs) -> void diff --git a/test/Hosihikari.Tests/Hosihikari.McMaster.NETCore.Plugins.Tests.csproj b/test/Hosihikari.Tests/Hosihikari.McMaster.NETCore.Plugins.Tests.csproj index 3d51ab6..cac7a72 100644 --- a/test/Hosihikari.Tests/Hosihikari.McMaster.NETCore.Plugins.Tests.csproj +++ b/test/Hosihikari.Tests/Hosihikari.McMaster.NETCore.Plugins.Tests.csproj @@ -7,9 +7,12 @@ - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + From a9099b79b7ddab1a6d87f0e0bc9b1bbccc87c7a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E7=BA=BE?= Date: Tue, 20 Oct 2026 10:11:24 +0800 Subject: [PATCH 09/12] chore: 2.1.0-hosihikari.2 --- src/Plugins/McMaster.NETCore.Plugins.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Plugins/McMaster.NETCore.Plugins.csproj b/src/Plugins/McMaster.NETCore.Plugins.csproj index 8c9276f..a530851 100644 --- a/src/Plugins/McMaster.NETCore.Plugins.csproj +++ b/src/Plugins/McMaster.NETCore.Plugins.csproj @@ -7,7 +7,7 @@ true Hosihikari.McMaster.NETCore.Plugins 2.1.0 - 2.1.0-hosihikari.1 + 2.1.0-hosihikari.2 Hosihikari fork: probe runtimes/<rid>/native before deps.json-based native resolution. Nate McMaster;Hosihikari framework https://github.com/HosihikariFramework/Hosihikari.McMaster.NETCore.Plugins From 16f397a4b75036eea640b4d999dbe7fbce1ecc94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E7=BA=BE?= Date: Wed, 4 Nov 2026 11:38:41 +0800 Subject: [PATCH 10/12] docs: point repository metadata at DotNetCorePlugins The package keeps its ID; the project URL, repository URL, fork notes and README now name the repository that carries it. Upstream samples stay linked from their original home. --- CLAUDE.md | 7 +++++-- FORK.md | 3 ++- README.md | 7 ++++--- src/Plugins/McMaster.NETCore.Plugins.csproj | 4 ++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 8f44d8f..0001f40 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,7 +4,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co ## Project Overview -DotNetCorePlugins is a .NET library for dynamically loading .NET assemblies as plugins with fine-grained control over assembly isolation and type sharing. Version 2.0+ targets .NET 8.0. +DotNetCorePlugins is a .NET library for dynamically loading .NET assemblies as plugins with fine-grained control over assembly isolation and type sharing. This is the Hosihikari fork (see [FORK.md](FORK.md)); it targets .NET 10. ## Build Commands @@ -18,7 +18,7 @@ pwsh ./build.ps1 -ci # Individual commands dotnet format # Code formatting dotnet build # Build all projects -dotnet test test/Plugins.Tests # Run tests +dotnet test test/Hosihikari.Tests -c Release # RID probe tests dotnet pack -o artifacts/ # Create NuGet packages ``` @@ -48,6 +48,9 @@ Include `[ci skip]` in a commit message to skip CI builds. - `test/Plugins.Tests/`: xUnit tests - `test/TestProjects/`: 30+ test assemblies for various plugin scenarios +- `test/Hosihikari.Tests/`: the RID-specific native probe + +Upstream `samples/` is not carried in this fork. ## Code Conventions diff --git a/FORK.md b/FORK.md index 3ddd297..d6e34e5 100644 --- a/FORK.md +++ b/FORK.md @@ -2,7 +2,8 @@ This repository is a fork of [natemcmaster/DotNetCorePlugins](https://github.com/natemcmaster/DotNetCorePlugins) at commit -`bcf9c8bd6d50097cfea1c3eeba132b571410fabe`, used by the Hosihikari framework. +`bcf9c8bd6d50097cfea1c3eeba132b571410fabe`, used by the Hosihikari framework. It replaces the +earlier `Hosihikari.McMaster.NETCore.Plugins` repository; the package ID is unchanged. ## Changes diff --git a/README.md b/README.md index 648144e..51f10f0 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,8 @@ ============ > **Hosihikari fork.** This fork probes `runtimes//native` before deps.json-based native -> resolution (ADR-0034). Upstream samples are omitted. See [FORK.md](FORK.md). +> resolution (ADR-0034). Upstream samples are omitted; read them +> [upstream](https://github.com/natemcmaster/DotNetCorePlugins/tree/main/samples). See [FORK.md](FORK.md). [![Build Status][ci-badge]][ci] [![Code Coverage][codecov-badge]][codecov] [![NuGet][nuget-badge] ![NuGet Downloads][nuget-download-badge]][nuget] @@ -59,7 +60,7 @@ Using plugins requires at least two projects: (1) the 'host' app which loads plu but typically also uses a third, (3) an contracts project which defines the interaction between the plugin and the host. -For a fully functional sample of this, see [samples/hello-world/](./samples/hello-world/) . +For a fully functional sample of this, see [samples/hello-world/](https://github.com/natemcmaster/DotNetCorePlugins/tree/main/samples/hello-world) upstream. ### The plugin contract @@ -72,7 +73,7 @@ public interface IPlugin } ``` -There is nothing special about the name "IPlugin" or the fact that it's an interface. This is just here to illustrate a concept. Look at [samples/](./samples/) for additional examples of ways you could define the interaction between host and plugins. +There is nothing special about the name "IPlugin" or the fact that it's an interface. This is just here to illustrate a concept. Look at the upstream [samples/](https://github.com/natemcmaster/DotNetCorePlugins/tree/main/samples) for additional examples of ways you could define the interaction between host and plugins. ### The plugins diff --git a/src/Plugins/McMaster.NETCore.Plugins.csproj b/src/Plugins/McMaster.NETCore.Plugins.csproj index a530851..fc8e1b0 100644 --- a/src/Plugins/McMaster.NETCore.Plugins.csproj +++ b/src/Plugins/McMaster.NETCore.Plugins.csproj @@ -10,8 +10,8 @@ 2.1.0-hosihikari.2 Hosihikari fork: probe runtimes/<rid>/native before deps.json-based native resolution. Nate McMaster;Hosihikari framework - https://github.com/HosihikariFramework/Hosihikari.McMaster.NETCore.Plugins - https://github.com/HosihikariFramework/Hosihikari.McMaster.NETCore.Plugins.git + https://github.com/HosihikariFramework/DotNetCorePlugins + https://github.com/HosihikariFramework/DotNetCorePlugins.git Hosihikari fork of McMaster.NETCore.Plugins. Provides API for dynamically loading assemblies into a .NET application; this fork probes runtimes/<rid>/native before deps.json-based native resolution. .NET Core;plugins;hosihikari From fa41f7a2b9a227e59106c9a3c8335a388363ee77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E7=BA=BE?= Date: Wed, 4 Nov 2026 11:48:17 +0800 Subject: [PATCH 11/12] chore: ignore the custom build output directory --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index bbe7087..4f5ca64 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ bin/ obj/ +.build/ .vs/ artifacts/ .idea/ From 005d6ea7043d62214f0937a6b3960a081ca34339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=88=E7=BA=BE?= Date: Thu, 12 Nov 2026 13:58:04 +0800 Subject: [PATCH 12/12] =?UTF-8?q?chore:=20SDK=20=E4=B8=8B=E9=99=90=2010.0.?= =?UTF-8?q?404?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index 9e9a685..590a3b5 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.402", + "version": "10.0.404", "rollForward": "latestFeature" }, "test": {