diff --git a/Directory.Build.props b/Directory.Build.props
index aa7a424..68bae61 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -8,6 +8,7 @@
Copyright © Nate McMaster
en-US
false
+ $(MSBuildThisFileDirectory)
https://www.apache.org/licenses/LICENSE-2.0
https://github.com/natemcmaster/DotNetCorePlugins
https://github.com/natemcmaster/DotNetCorePlugins.git
diff --git a/README.md b/README.md
index 8df4eb5..1178f43 100644
--- a/README.md
+++ b/README.md
@@ -7,3 +7,8 @@ simplify the usage of those lower-level APIs.
## Getting started
This project requires [.NET Core 2.0](https://aka.ms/dotnet-download) or higher.
+
+
+## Usage
+
+See example projects in [samples/](./samples/) for more detailed, example usage.
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..fc8d7d7
--- /dev/null
+++ b/samples/aspnetcore/Abstractions/IPlugin.cs
@@ -0,0 +1,12 @@
+using System;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.AspNetCore.Builder;
+
+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..048b490
--- /dev/null
+++ b/samples/aspnetcore/Abstractions/IPluginLink.cs
@@ -0,0 +1,7 @@
+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..6fd04a0
--- /dev/null
+++ b/samples/aspnetcore/MainWebApp/MainWebApp.csproj
@@ -0,0 +1,31 @@
+
+
+
+ netcoreapp2.1
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..6122919
--- /dev/null
+++ b/samples/aspnetcore/MainWebApp/Pages/Index.cshtml.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Mvc;
+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..24741d4
--- /dev/null
+++ b/samples/aspnetcore/MainWebApp/Program.cs
@@ -0,0 +1,24 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+
+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/Properties/launchSettings.json b/samples/aspnetcore/MainWebApp/Properties/launchSettings.json
new file mode 100644
index 0000000..1b32514
--- /dev/null
+++ b/samples/aspnetcore/MainWebApp/Properties/launchSettings.json
@@ -0,0 +1,27 @@
+{
+ "iisSettings": {
+ "windowsAuthentication": false,
+ "anonymousAuthentication": true,
+ "iisExpress": {
+ "applicationUrl": "http://localhost:35566",
+ "sslPort": 44362
+ }
+ },
+ "profiles": {
+ "IIS Express": {
+ "commandName": "IISExpress",
+ "launchBrowser": true,
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ },
+ "WebAppWithPlugins": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:5001;http://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/samples/aspnetcore/MainWebApp/Startup.cs b/samples/aspnetcore/MainWebApp/Startup.cs
new file mode 100644
index 0000000..32191f1
--- /dev/null
+++ b/samples/aspnetcore/MainWebApp/Startup.cs
@@ -0,0 +1,68 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.IO;
+using System.Threading.Tasks;
+using McMaster.Extensions.Plugins;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Http;
+using Microsoft.Extensions.DependencyInjection;
+using Plugin.Abstractions;
+
+namespace MainWebApp
+{
+ public class Startup
+ {
+ private List _plugins = new List();
+
+ public Startup()
+ {
+ foreach (var pluginFile in Directory.GetFiles(AppContext.BaseDirectory, "plugin.config", SearchOption.AllDirectories))
+ {
+ var loader = PluginLoader.CreateFromConfigFile(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();
+
+ foreach (var plugin in _plugins)
+ {
+ plugin.ConfigureServices(services);
+ }
+ }
+
+ public void Configure(IApplicationBuilder app, IHostingEnvironment env)
+ {
+ if (env.IsDevelopment())
+ {
+ 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..b9d73e7
--- /dev/null
+++ b/samples/aspnetcore/README.md
@@ -0,0 +1,41 @@
+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 much share the same identity between the plugins and the host.
+To ensure type exchnage works between the host and the plugins, the MainWebApp project uses the `sharedTypes`
+parameter on `PluginLoader.CreateFromConfigFile`.
+
+```csharp
+ var loader = PluginLoader.CreateFromConfigFile(pluginFile,
+ 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/Directory.Build.targets b/samples/aspnetcore/WebAppPlugin1/Directory.Build.targets
new file mode 100644
index 0000000..23032f7
--- /dev/null
+++ b/samples/aspnetcore/WebAppPlugin1/Directory.Build.targets
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/samples/aspnetcore/WebAppPlugin1/WebAppPlugin1.csproj b/samples/aspnetcore/WebAppPlugin1/WebAppPlugin1.csproj
new file mode 100644
index 0000000..89e0482
--- /dev/null
+++ b/samples/aspnetcore/WebAppPlugin1/WebAppPlugin1.csproj
@@ -0,0 +1,13 @@
+
+
+
+ netstandard2.0
+ true
+
+
+
+
+
+
+
+
diff --git a/samples/aspnetcore/WebAppPlugin1/WebPlugin1.cs b/samples/aspnetcore/WebAppPlugin1/WebPlugin1.cs
new file mode 100644
index 0000000..db3b54f
--- /dev/null
+++ b/samples/aspnetcore/WebAppPlugin1/WebPlugin1.cs
@@ -0,0 +1,30 @@
+using System;
+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/Directory.Build.targets b/samples/aspnetcore/WebAppPlugin2/Directory.Build.targets
new file mode 100644
index 0000000..23032f7
--- /dev/null
+++ b/samples/aspnetcore/WebAppPlugin2/Directory.Build.targets
@@ -0,0 +1,9 @@
+
+
+
+
+
+
diff --git a/samples/aspnetcore/WebAppPlugin2/WebAppPlugin2.csproj b/samples/aspnetcore/WebAppPlugin2/WebAppPlugin2.csproj
new file mode 100644
index 0000000..c1e6abf
--- /dev/null
+++ b/samples/aspnetcore/WebAppPlugin2/WebAppPlugin2.csproj
@@ -0,0 +1,13 @@
+
+
+
+ netstandard2.0
+ true
+
+
+
+
+
+
+
+
diff --git a/samples/aspnetcore/WebAppPlugin2/WebPlugin2.cs b/samples/aspnetcore/WebAppPlugin2/WebPlugin2.cs
new file mode 100644
index 0000000..2cf6886
--- /dev/null
+++ b/samples/aspnetcore/WebAppPlugin2/WebPlugin2.cs
@@ -0,0 +1,30 @@
+using System;
+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/src/Plugins.Sdk/sdk/Sdk.targets b/src/Plugins.Sdk/sdk/Sdk.targets
index 8072598..866f1bd 100644
--- a/src/Plugins.Sdk/sdk/Sdk.targets
+++ b/src/Plugins.Sdk/sdk/Sdk.targets
@@ -1,9 +1,17 @@
- $(OutputPath)plugin.config
+ $(TargetDir)plugin.config
$(MSBuildAllProjects);$(MSBuildThisFileFullPath)
+
+
+ false
+ PreserveNewest
+ PreserveNewest
+
+
+
+
+
+
+