Git Product home page Git Product logo

dotnetcoreplugins's Introduction

.NET Core Plugins

Build Status Code Coverage NuGet NuGet Downloads

This project provides API for loading .NET Core assemblies dynamically, executing them as extensions to the main application, and finding and isolating the dependencies of the plugin from the main application. This library supports .NET Core 2, but works best in .NET Core 3 and up. It allows fine-grained control over assembly isolation and type sharing. Read more details about type sharing below.

Blog post introducing this project, July 25, 2018: .NET Core Plugins: Introducing an API for loading .dll files (and their dependencies) as '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" 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.

Getting started

Install the McMaster.NETCore.Plugins NuGet package.

dotnet add package McMaster.NETCore.Plugins

The main API to use is PluginLoader.CreateFromAssemblyFile.

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?)
  • isUnloadable = (.NET Core 3+ only). 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/ 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/ .

The plugin contract

You can define your own plugin contract. A minimal contract might look like this.

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/ for additional examples of ways you could define the interaction between host and plugins.

The plugins

Typically, it is best to implement plugins by targeting net5.0 or higher. They can target netstandard2.0 as well, but using net5.0 is better because it reduces the number of redundant System.* assemblies in the plugin output.

A minimal implementation of the plugin could be as simple as this.

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/.

using McMaster.NETCore.Plugins;

var loaders = new List<PluginLoader>();

// 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/, 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 Core 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

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

Read even more details about shared types here.

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.

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/ 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. In .NET Core 3+, 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:

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 for even more details on contextual reflection.

Overriding the Default Load Context

Under the hood, DotNetCorePlugins is using a .NET Core API called ApplicationLoadContext. 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.

To override the default assembly load context, set PluginConfig.DefaultContext. Example:

AssemblyLoadContext myCustomDefaultContext = // (something).
PluginLoader.CreateFromAssemblyFile(dllPath,
     config => config.DefaultContext = myCustomDefaultContext);

dotnetcoreplugins's People

Contributors

bergi9 avatar copadatapm avatar davidpendraykalibrate avatar dazinator avatar dependabot[bot] avatar gasparnagy avatar katostoelen avatar natemcmaster avatar per-samuelsson avatar sewer56 avatar sf8321 avatar thoemmi avatar vdurante avatar zeppaman avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

dotnetcoreplugins's Issues

Antiforgery token validation failed - Form post

I am using your library similar to your sample "aspnetcore-mvc" but with Razor Pages. This works beautifully until I set up a simple from like this:

<form method="post">
    <button type="submit">Save</button>
   โ€ฆ some other unrelated fields..
</form>

If I hit the button then the request does not work. Because it is intercepted from the .net core framework with the following message:

Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.AutoValidateAntiforgeryTokenAuthorizationFilter: Information: Antiforgery token validation failed. The required antiforgery cookie ".AspNetCore.Antiforgery.Fda_KFWU8eY" is not present.

Microsoft.AspNetCore.Antiforgery.AntiforgeryValidationException: The required antiforgery cookie ".AspNetCore.Antiforgery.Fda_KFWU8eY" is not present.
at Microsoft.AspNetCore.Antiforgery.DefaultAntiforgery.ValidateRequestAsync(HttpContext httpContext)
at Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.ValidateAntiforgeryTokenAuthorizationFilter.OnAuthorizationAsync(AuthorizationFilterContext context)
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker: Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.ViewFeatures.Filters.AutoValidateAntiforgeryTokenAuthorizationFilter'.
Microsoft.AspNetCore.Mvc.StatusCodeResult: Information: Executing HttpStatusCodeResult, setting HTTP status code 400
Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure.PageActionInvoker: Information: Executed page /Plugin in 106.3939ms

Well, as you know Razor Pages are designed to be automatically protected from cross-site request forgery (CSRF/XSRF) attacks. Anti-forgery token generation and validation is automatically included in Razor Pages.

I can get this to work by disabling this security feature like shown below, but this would be bad practice...
.AddRazorPagesOptions(o => { o.Conventions.ConfigureFilter(new IgnoreAntiforgeryTokenAttribute()); })

How could I use your library without having to disable the Antiforgery feature?

Thank you very much for your help!

Assembly loading doesn't work as expected

Describe the bug
Loading the assembly from its path creates types differently from expected

To Reproduce
Steps to reproduce the behavior:

  1. Using this version of the library '0.2.4'
  2. Run tests from https://github.com/godrose/netcore-load
  3. See the error in the test which uses the package as opposed to Assembly.LoadFrom approach

Expected behavior
Tests should be green in both cases

Screenshots
Irrelevant

Additional context
.NET Core pre-3.0, VS 2017, Test Explorer

System.InvalidCastException when trying to create instance of loaded plugin type

Hi,
I'm experiencing problems when trying to activate instance of plugin type that inherits interface type.

For example I have "Context" type that implements "IContext" interface which I am trying to load using PluginLoader.

Here is the part where the type is loaded:

        private static Type GetContextType()
        {          
            var pluginPath = Path.GetFullPath(Const.PersistencePath);
            var loader = PluginLoader.CreateFromAssemblyFile(pluginPath);
            var assembly = loader.LoadDefaultAssembly();
            var types = assembly.GetExportedTypes();
            var type = types.Single(t => t.Name == "Context");

            return type;
        }

And here is the part where the exception occurs:
public static IContext LoadContext(string connectionString) => (IContext)Activator.CreateInstance(GetContextType(), connectionString);

System.InvalidCastException: 'Unable to cast object of type 'Vigab.Persistence.Context' to type 'Vigab.Application.Interfaces.IContext'.'

Context class:

public sealed class Context : DbContext, IContext
{        
        public Context(string connectionString) : base(new DbContextOptionsBuilder()
            .UseSqlServer(connectionString)
            .Options) { }
}

Additional info:
Presentation layer references application layer which only has interface IContext.
Persistence layer is the plugin which implements IContext interface and should be loaded dynamically without having references included into presentation or application layer. It also depends on Entity framework and System.Data.SqlClient, which seems to be unmanaged assembly, because of which I have tried to use this library as probable solution.

How to add controllers from plugin?

Hi

I'm trying to load controllers from a plugin but it's not working. I just used PluginLoader class to load plugin dll and then called AddApplicationPart by passing plugin assembly on IMvcBuilder but it does not load controllers and I can not access the route I have given to controller. But when I add plugin reference to my host application it works.
I think it because asp net core loads controller from default assembly context loader and my plugins are in another context.

Am I doing something wrong or this is default behavior?
And if not, Is there any way to solve this problem?

Error while loading unmanaged dlls

"CreateFromPackage" method defined in "NativeLibrary" class sets the native library name as a library's dll name without extension (

Name = Path.GetFileNameWithoutExtension(assetPath),
).

This name is used by "AssemblyLoadContextBuilder" in "AddNativeLibrary" method as a key for a "_nativeLibraries" dictionary (

_nativeLibraries.Add(library.Name, library);
) wich later passed to the "ManagedLoadContext" constructor.

"ManagedLoadContext" class defines "LoadUnmanagedDll(string unmanagedDllName)" method to proccess unmanaged dll requests (

protected override IntPtr LoadUnmanagedDll(string unmanagedDllName)
). This method trying to get "NativeLibrary" instance from "_nativeLibraries" dictionary, using platform related prefixes + "unmanagedDllName".

"unmanagedDllName" argument of "LoadUnmanagedDll" may contain both library name and library file path (absolute or relative). In second case (for example, https://github.com/dotnet/corefx/blob/8f7b490ca874ee2a9f11f0163412f7c95811298b/src/System.Data.SqlClient/src/Interop/SNINativeMethodWrapper.Windows.cs#L183

private const string SNI = "sni.dll";
...
[DllImport(SNI, CallingConvention = CallingConvention.Cdecl, EntryPoint = "SNIAddProviderWrapper")]
internal static extern uint SNIAddProvider(SNIHandle pConn, ProviderEnum ProvNum, [In] ref uint pInfo);

) "LoadUnmanagedDll" method will fail.

It seems that trimming path and extensions from "PlatformInformation.NativeLibraryExtensions" in "unmanagedDllName" is appropriate here. Or better "Path.GetFileNameWithoutExtension" call as it works in "NativeLibrary.CreateFromPackage".

Hope my description is understoodable.

Please update nuget

Hi @natemcmaster

Please update nuget with the latest commit for loading shared types by default.

The current version 0.1.1 does not contain it yet.

Many thanks

PluginLoader.CreateFromAssemblyFile fail when dependency is in two packages

Describe the bug
Having two packages where one package contain a library that is also in the other package, PluginLoader.CreateFromAssemblyFile when iterating deps file, adding managed libraries.

To Reproduce

EDIT 1/11: Reproduction repo provided in later comment.

Steps to reproduce the behavior:

  1. Create a library Lib1 (e.g dotnet new classlib). Package it.
  2. Create another library, Lib2, and package it, but include Lib1.dll in that package too.
  3. Create an application referencing both packages (e.g dotnet new console). (I used a local feed and a NuGet.config)
  4. Load the application with PluginLoader.CreateFromAssemblyFile.

Expected behavior
The application load fine and loader resolves what library to use.

Additional context
Failure is when AssemblyLoadContextBuilder.AddManagedLibrary tries to add the contained library the second time, causing _managedLibraries.Add(library.Name.Name, library); to raise exception.

Regarding packages, here's what they would look like if following instructions:

Lib1
  lib\
    lib1.dll

Lib2
  lib\
    lib1.dll
    lib2.dll

And yes, it's a pretty exotic case. But since NuGet will support multiple libraries in one lib\ hive, these packages exist out there, and you'll see them fail.

PluginLoader - No option to load from existing PluginConfig

Is your feature request related to a problem? Please describe.
PluginLoader has a load from config file method that takes a string which is a file path.
However that implementation is coupled to the physical file system (system.io.file, system.io.directory etc) - I'd like to be able to load PluginLoader from a config file provided as a stream.

Describe the solution you'd like
PluginConfig itself can already be loaded from a stream, I think PluginLoader just needs a factory method that is capable of taking an existing PluginConfig instance rather than always creating a new one based on file path.

Describe alternatives you've considered
Tried to construct my own instance of Plugin Loader - but the constructor is internal forcing use of the static factory methods available.

Additional context
I'm approaching plugin loading trying to limit all file system interactions to go via an IFileProvider abstraction. Which means any methods which go directly via System.IO.File or Directory are no good to me - so I am looking for alternatives that allow me to provide streams etc.

Add Controller from Plugin in .NET Core 3 doesn`t work as expected

Describe the bug
I use your code with net core 3 preview 6, and the plugin is loaded.
But no controller from the plugin is loaded or not visible in swagger. Only the controller from
the api are loaded and accessable.

To Reproduce
Steps to reproduce the behavior:

  1. Using your example of net core 3 webapi.
  2. Using this version of the library '0.3.0-beta.26'
  3. Add this code to your startup function:
var assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(pluginDll);
foreach(var type in assembly.GetTypes())
{
    if (typeof(Controller).IsAssignableFrom(assembly.GetType(type.FullName)))
    {
        this._logger.LogDebug(" + Controller: " + type.FullName);
        services.AddMvc().AddApplicationPart(assembly).AddControllersAsServices();
    }
}
  1. See what happen

Expected behavior
The Controller is visible in swagger and the endpoint is usable.

Additional context
.NET Core pre-3.0 preview 6, VS 2019, Chrome

ReflectionTypeLoadException saying a method is not implemented when it is

I don't believe this is a bug in DotNetCorePlugins, but I could really use some help understanding why I can't get it working.

First the exception I am seeing:

{System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types.
Method 'ListAsync' in type 'TestPlugin' from assembly 'TestPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
   at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)
   at System.Reflection.RuntimeModule.GetTypes()
   at System.Reflection.Assembly.GetTypes()
   at PluginManager.LoadPlugins(String pluginsDir) in C:\Users\me\source\repos\TestSol\TestFramework\PluginManager.cs:line 29
   at TestConsole.Program.TestDataCollection() in C:\Users\me\source\repos\TestSol\TestConsole\Program.cs:line 33
   at TestConsole.Program.<>c.<<Main>b__0_0>d.MoveNext() in C:\Users\me\source\repos\TestSol\TestConsole\Program.cs:line 19
System.TypeLoadException: Method 'ListAsync' in type 'TestPlugin' from assembly 'TestPlugin, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.}	System.Exception {System.Reflection.ReflectionTypeLoadException}

I have three projects (all netcoreapp2.1) in my solution (TestSol):

  1. A base framework for the plugin called TestFramework that exposes an interface called IDataCollectorPlugin. It also has the class to load the plugins called PluginManager.
  2. A sample plugin implementing this interface called TestPlugin
  3. A console app called TestConsole that is used to load everything up.

So here is the code for TestPlugin:

   public class TestPlugin : IDataCollectorPlugin
    {
        public string PluginName
        {
            get => "Test";
        }

        public async Task<List<ResourceItemSummary>> ListAsync(ServiceClientCredentials creds, string subscriptionId)
        {
            // ACTUAL WORK HERE
        }
    }

Here is the interface:

public interface IDataCollectorPlugin
    {
        string PluginName { get; }

        Task<List<ResourceItemSummary>> ListAsync(ServiceClientCredentials creds, string subscriptionId);
    }

I can't understand why it says ListAsync() has not been implemented when it clearly has. This exception is thrown when I call .GetTypes() from your sample in the foreach loop:

foreach (var pluginType in loader
                     .LoadDefaultAssembly()
                     .GetTypes()
                     .Where(t => typeof(IDataCollectorPlugin).IsAssignableFrom(t) && !t.IsAbstract))
                {
                    IDataCollectorPlugin plugin = (IDataCollectorPlugin)Activator.CreateInstance(pluginType);
                    Console.WriteLine($"Created plugin instance '{plugin.PluginName}'.");
                }

Any ideas what I am doing wrong in trying to scaffold this plugin?

Dependabot couldn't find a <anything>.(cs|vb|fs)proj for this project

Dependabot couldn't find a .(cs|vb|fs)proj for this project.

Dependabot requires a .(cs|vb|fs)proj to evaluate your project's current .NET dependencies. It had expected to find one at the path: /src, /test/Plugins.Tests/<anything>.(cs|vb|fs)proj.

If this isn't a .NET project, or if it is a library, you may wish to disable updates for it from within Dependabot.

You can mention @dependabot in the comments below to contact the Dependabot team.

Consider adopting IFileProvider abstraction for all file system interactions

Is your feature request related to a problem? Please describe.
In various places, system.io namespace is used to check check for folders / files and read files into memory. These means there is no level of indirection for file access.

Describe the solution you'd like
I would like to provide an IFileProvider to be used for all file system access (Microsoft.Extensions.FileProviders). The benefit of this, to me, is that there are many different implementations of IFileProvider that could be swapped out as needed. For example, for testing, I could use an InMemory IFileProvider (stage in memory streams for plugins and config files). At application run time, I could use the PhysicalFileProvider from IHostingEnvironment or perhaps provide an AzureBlobStorageFileProvider or 7ZipArchiveFileProvider (there are some IFileProviders that allow you to effectively mount zip archives as a file system which could be useful if you want to download plugins as zip files, and want to keep them compressed).

Describe alternatives you've considered
Sticking with system.io.

Additional context
Add any other context or screenshots about the feature request here.

Unable to load related assemblies

Describe the bug
I'm trying to reproduce the mvc sample app, but it fails to load the related assembly attribute and so cannot load the MvcAppPlugin1.Views.dll

To Reproduce
Steps to reproduce the behavior:

  1. dotnet new mvc -o MvcApp -n MvcApp
  2. dotnet new razorclasslib -o MvcAppPlugin1 -n MvcAppPlugin1
  3. dotnet build MvcApp\MvcApp.csproj
  4. dotnet build MvcAppPlugin1\MvcAppPlugin1.csproj
  5. xcopy MvcAppPlugin1\bin\Debug\netstandard2.0\* MvcApp\bin\Debug\netcoreapp2.1\plugins\MvcAppPlugin1\
  6. cd MvcApp
  7. dotnet add package McMaster.NETCore.Plugins
  8. Use Startup.cs attached here Startup.cs.txt
  9. dotnet build
  10. dotnet run

The RelatedAssembly does not load, I think it's because the GetCustomAttributes cannot match the RelatedAssemblyAttribute from another version.
To show this I have added some debug info in the Startup.cs, and when I cast the RelatedAssemblyAttribute I get this exception :

System.InvalidCastException: [A]Microsoft.AspNetCore.Mvc.ApplicationParts.RelatedAssemblyAttribute cannot be cast to [B]Microsoft.AspNetCore.Mvc.ApplicationParts.RelatedAssemblyAttribute. Type A originates from 'Microsoft.AspNetCore.Mvc.Core, Version=2.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' in the context 'Default' at location 'C:\Users\NCO3367\.nuget\packages\microsoft.aspnetcore.mvc.core\2.1.3\lib/netstandard2.0/Microsoft.AspNetCore.Mvc.Core.dll'. Type B originates from 'Microsoft.AspNetCore.Mvc.Core, Version=2.1.3.0, Culture=neutral, PublicKeyToken=adb9793829ddae60' in the context 'Default' at location 'C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\2.1.6\Microsoft.AspNetCore.Mvc.Core.dll'.
   at MvcApp.Startup.ConfigureServices(IServiceCollection services) in C:\Users\NCO3367\Source\Repos\MvcApp\MvcApp\Startup.cs:line 66

Expected behavior
MvcAppPlugin1.dll should load with its related MvcAppPlugin1.Views.dll

Additional context

SDK .NET Core (reflรฉtant tous les global.json)ย :
 Version:   2.1.500
 Commit:    b68b931422

Environnement d'exรฉcutionย :
 OS Name:     Windows
 OS Version:  10.0.17763
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\2.1.500\

Host (useful for support):
  Version: 2.1.6
  Commit:  3f4f8eebd8

.NET Core SDKs installed:
  1.1.11 [C:\Program Files\dotnet\sdk]
  2.1.202 [C:\Program Files\dotnet\sdk]
  2.1.403 [C:\Program Files\dotnet\sdk]
  2.1.500 [C:\Program Files\dotnet\sdk]

.NET Core runtimes installed:
  Microsoft.AspNetCore.All 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.All 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
  Microsoft.AspNetCore.App 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.AspNetCore.App 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
  Microsoft.NETCore.App 1.0.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 1.1.10 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.5 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
  Microsoft.NETCore.App 2.1.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]

To install additional .NET Core runtimes or SDKs:
  https://aka.ms/dotnet-download

Figure out why the LoadsNetCoreProjectWithNativeDeps test fails when collectible assemblies are used

Follow-up on #31

Something about this test doesn't work when load contexts are collectible.

[Fact]
public void LoadsNetCoreProjectWithNativeDeps()
{
var path = TestResources.GetTestProjectAssembly("PowerShellPlugin");
var loader = PluginLoader.CreateFromConfigFile(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<object>()));
}

Dependent assemblies fail to load from within Razor Views

Describe the bug
Modifying the MVC sample to reference an assembly like AutoMapper, interacting with the assembly from a controller works, while interacting with the assembly from a View fails to load the dependent assembly.

To Reproduce
Steps to reproduce the behavior:

  1. Start with the MVC sample here: https://github.com/natemcmaster/DotNetCorePlugins/tree/abf20cdaacebb1d691df3ed5aa18d22892f5a48a/samples/aspnetcore-mvc
  2. Aside: I had to modify MvcApp.csproj's publish in order for the plugin to successfully publish.
  3. From a controller, interact with typeof(IMapper).Assembly. Expect success.
  4. From a view, interact with typeof(IMapper).Assembly. Fails to load the assembly that was successfully loaded in step 3.

Additional context

Modification to MvcApp.csproj:

  <Target Name="PublishPlugins" BeforeTargets="Build">

    <ItemGroup>
      <PluginProject Include="..\MvcAppPlugin1\MvcAppPlugin1.csproj" />
    </ItemGroup>

    <MSBuild Projects="@(PluginProject)" Targets="Publish" Properties="Configuration=$(Configuration);PublishDir=$(TargetDir)plugins\%(FileName)\" />
  </Target>

Modified MyPluginController action:

        public IActionResult Index()
        {
            ViewData["AutoMapperVersion"] = typeof(IMapper).Assembly.GetName().ToString();

            return View();
        }

Modified MvcAppPlugin1\Views\MyPlugin1\Index.cshtml

@using AutoMapper
@{
    ViewData["Title"] = "Plugin1";
}

<p>Plugin 1</p>
<div>Referencing AutoMapper from controller action: @ViewData["AutoMapperVersion"]</div>
<div>Referencing AutoMapper from a view: @(typeof(IMapper).Assembly.GetName().ToString())</div>

If I remove the final line of the view, the app successfully shows the assembly name determined by the controller. If I leave it in place, we fail to load AutoMapper:

image

Does this look like a misuse of the plugin library? Is there an additional step I should take so that work performed in views can successfully work with a plugin's own dependencies?

.NET Standard Support?

Is your feature request related to a problem? Please describe.
I'd like to use the library from a shared .net standard project

Describe the solution you'd like
Use the library from a .NET Standard project

Describe alternatives you've considered
In a complex project, we have more than one .net core applications that load plugins. I'd like to share the plugin loader code in a .NET standard project. Right now the only solution possible is to duplicated plugin loading in all core apps?

BTW great project!

Add support for resolving a plugin from NuGetv3

Is your feature request related to a problem? Please describe.
My scenario is a platform-styled server application that uses "Connectors" to act on a workflow. As of now, the server has to be recompiled with the appropriate connectors. This library enables me to use the plugin model which is fantastic, but still requires manually deploying the plugins to the server. It's certainly workable.

Describe the solution you'd like
I'd like a method that would, given a nuget packageId and optionally version, download & install the plugin and do the same for any unresolved dependencies, so that it could be loaded by this library.

Describe alternatives you've considered
The alternative is to do all the heavy lifting with NuGet in a separate library specialized for that, and then load the plugin as a step 2.

Additional Context
The NuGet feed I would resolve the plugin from would be secured & trusted for code execution security purposes. The dependent packages could resolve from either feed.

Plugin config not generating

Describe the bug
Using the plugins.sdk, when publishing or building, the plugin.config file is not generated. Not sure whether this is a documentation issue or a bug.

To Reproduce
Steps to reproduce the behavior based on the readme file:

  1. Create a new .net standard class library in VS Studio
  2. Change csproj file to
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netcoreapp2.0</TargetFramework>
    <IsPlugin>true</IsPlugin>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="McMaster.NETCore.Plugins.Sdk" Version="*" />
  </ItemGroup>
</Project>
  1. Publish via
dotnet publish MyPlugin1.csproj --output plugins/MyPlugin1/

Expected behavior
A plugin.config file should be generated in the output directory.

Passing path with forward-slashes to LoadUnmanagedDllFromPath cause load to fail

Describe the bug
When a native binary in a package is defined in the .nuspec using forward-slashes, AssemblyLoadContext.LoadUnmanagedDllFromPath() will fail loading it.

Sample <file> causing this failure:

Foo.nuspec

<files>
  <file src="nativeassets\*.dll" target="runtimes/win-x64/native" />
</files>

To Reproduce
Steps to reproduce the behavior:

  1. Define a NuGet package that include a native binary using the above pattern.
  2. Load an assembly in an ALC (new ManagedLoadContext(...)) that depend on the native binary and execute some code in it, e.g. using P/Invoke.

Expected behavior
The native binary is found and successfully loaded by DotNetCorePlugins.

Actual behavior
The native binary is found, but fail to load.

Screenshots
image

Additional context
Spent quite some time figuring out what this problem was about, and what especially fooled me was that File.Exist(unmanagedPath) return true, because that API clearly deal with mixed slashes. Then when you pass the path on to LoadContext.LoadUnmanagedDllFromPath(path) here, here, here or here, it will raise the above exception.

Considered making a PR, but wasn't sure in what spot you want to deal with this - SeachForLibrary maybe - or if this even should be reported as a flaw in AssemblyLoadContext.LoadUnmanagedDllFromPath. If the later, just let me know and I'll file an issue there instead.

Tested only on

.NET Core SDK (reflecting any global.json):
 Version:   2.2.101
 Commit:    236713b0b7

Runtime Environment:
 OS Name:     Windows
 OS Version:  10.0.17134
 OS Platform: Windows
 RID:         win10-x64
 Base Path:   C:\Program Files\dotnet\sdk\2.2.101\

Host (useful for support):
  Version: 2.2.0
  Commit:  1249f08fed

Dependabot couldn't find a <anything>.(cs|vb|fs)proj for this project

Dependabot couldn't find a .(cs|vb|fs)proj for this project.

Dependabot requires a .(cs|vb|fs)proj to evaluate your project's current .NET dependencies. It had expected to find one at the path: /src/<anything>.(cs|vb|fs)proj.

If this isn't a .NET project, or if it is a library, you may wish to disable updates for it from within Dependabot.

You can mention @dependabot in the comments below to contact the Dependabot team.

CreateFromAssemblyFile obsolete

Hi,

I'm currently successfully using this method overload which seems to go obsolete from this version.

PluginLoader.CreateFromAssemblyFile(Path.GetFullPath(path), null, PluginLoaderOptions.PreferSharedTypes)

I have tried to use without loader options, but unsuccessfully. What do you recommend?

Unable to resolve services using depenency injection for plugin dependency on another plugin

Is your feature request related to a problem? Please describe.
I want to be able to have plugins that provide services to be registered with dependency injection and can be resolved and used by other plugins. This means that Plugin1 might register IMyService with the DI and Plugin2 wants to resolve this and use it.

The issue I have is that once all plugins are loaded and each one has registered the services they want to register, I can only resolve services in a plugin that the same plugin registered. I believe this is because each plugin when published to the plugins folder has their own copy of the Plugin1.Abstractions dll...when plugin 1 registered IMyService it's type refers to the assembly loaded from that plugin folder, whereas plugin2 tries to resolve IMyService that's type refers to the assembly under it's plugin folder.

This is an issue because one plugin may be providing a service with caching or something else for argument's sake, if the same service is registered twice because it's type came from different assembly file locations then you have duplicated caches in the application using up more memory. This is just a simple example, I only need the service instance once!

Describe the solution you'd like
Is there any way to have plugins depend on other plugins etc or libraries from nuget where the DI can resolve the types even if they are from different copies of the same DLL? Is this just something that just cannot be done? Perhaps it is a limitation of the MS DI?

Describe alternatives you've considered
I tried several differnet ways of setting up the plugins and looked at all examples and docs provided, I cannot seem to find a solution for this issue.

Load w/o isolation

Hi Nate

I faced an issue with loading plugin in isolation.

The host is AspNetCore 2.1 WebApi

I have modules, which implement some database logic using EF Core.
Also, they can have controllers.

I have existing code, which loads up these modules dynamically into default app context.
The only issue I face there is that I can't resolve System.Data.SqlClient. As workaround, I included the package into WebApi project, so can load it from existing domain assemblies.

Using your code, when I load the modules in isolation, controllers are not recognised (I use services.AddMvc().AddApplicationPart) - on a sample call it returns 404.
When I use PluginLoader.CreateFromAssemblyFile and pass all current AppDomain assemblies into shared types, the controllers start working. But there are numerous issues with EF, it seems to load own version of System.Linq.something. I tried to filter which assemblies to share, but it always ends up with some error with EF or controller.

The solution as I see is to allow loading the plugin into current context, without isolation.
Please share your thoughts.

Plugin depending on System.Drawing.Common cannot be used properly

Describe the bug
When using the PluginLoader to load a library that references System.Drawing.Common, anytime any code executes from your plugin that uses a component from that library (PrintDocument in our case), the code blows up with an error claiming System.Drawing is not supported on this platform.. Obviously that is cross platform now (and I'm running this on Windows to boot).

To Reproduce
Steps to reproduce the behavior:

  1. Add a reference to System.Drawing.Common to your plugin project.
  2. Write some basic functionality that uses that library (instantiating a PrintDocument is sufficient).
  3. Use McMaster.NETCore.Plugins 0.2.0 in you main project
  4. Load primary DLL of your plugin using PluginLoader
  5. Use reflection (or whatever is easiest) to call the offending System.Drawing code
  6. See the exception generated

Expected behavior
I would expect this code to work properly as if it weren't being loaded by a plugin

Additional context
As a temporary workaround, if I use the PluginLoaderOptions.PreferSharedTypes flag and reference System.Drawing.Common directly in my host project, things work properly if that helps at all.

Remove PluginLoaderOptions

Starting in 0.3.0, PluginLoaderOptions will be marked as obsolete. The recommended replacement is to use PluginConfig instead.

Justification: as new features like unloading (#16) and hot reloading (#37) were added, an enum was no longer sufficient to represent the various ways to configure the plugin loader. Instead of adding more flags to an enum, configuration is now a class with a set of bool options to turn on/off each feature.

Example, obsolete usage

PluginLoader.CreateFromAssemblyFile(assemblyFile, PluginLoaderOptions.PreferSharedTypes);

Example replacement

PluginLoader.CreateFromAssemblyFile(assemblyFile, config => config.PreferSharedTypes = true);

Unrelated question

I've got a question for you unrelated to this repo (sorry!) but about something I think you mentioned some time ago. It was in some other dotnet or aspnet repo middle to late of last year and I can't seem to find it, otherwise I would just respond there. And if I'm misremembering or way off base here then feel free to delete hah.

Whichever project it was, was just beginning to move to the Arcade system, and you (I think it was you) were expressing uneasiness about having to keep these generated and automatically managed files, specifically related to the build process, inside the git repo. I am starting to feel the same apprehension at my work - has there been any discussion or changes to this?

Specifically, I'm looking to automate generating the pipeline yaml as part of the build process itself. For example, I have a dotnet solution with X projects in it which each require a set of build phases and steps. Right now I can run a tool locally that will generate the correct yaml pipeline, but then it must save it to the repo before it can run in AzDO. Even with putting as much as possible into remote yaml template git repositories, I still need that entrypoint yaml to call out to that template X times, and this file must be regenerated each time more projects are added to the solution.

I'd like to be able to have an HTTP endpoint as a remote template source rather than a git repo - so that endpoint could analyze the solution and respond with a dynamic yaml template with the desired number of phases.
Or maybe the build pipeline only contains one phase, but that single phase performs the analysis (or reaches out to a separate system that manages our pipelines) and adds more phases to the currently running build somehow.

I'm asking you because from what I recall (or maybe I've made this whole memory up) you were trying to suggest something similar for darc/maestro. Was that a battle that was lost? Or is there something in the works along those lines? Is there anywhere more appropriate I can ask this? I've tried over at azure-pipelines-yaml but haven't found anything just yet.

Thanks!

Load assembly from project.assets.json file alone

I am writing a tool that operates on a target project and needs to load one of the project dependencies even before the project is built. Let say we have ProjectA that depends either on DllX or DllY (usually not both): my tool scans ProjectA.csproj and determines the dependencies then need to load and use the correct dll. Right now the tool knows its own location into the NuGet cache and tries to load the dll from there but this is a bit fragile (for example, project references won't work).

I'd like to be able to tell DotNetCorePlugins "please, use the project.assets.json from that obj directory (after a restore) and load me the assembly named DllX".

A different approach is to build the target project then use DotNetCorePlugins to load the main assembly and eventually load the correct dll but this is a kind of chicken-and-egg problem because the project may depend on the code generated by my tool to build.

Api call in controller not found after loading the assembly at runtime.

Hi Nate,

Thanks for sharing this project, I'd really like to get a plugin architecture working in our projects. Also, congrats on your newborn! (yes I read the other issues ;-)).

I have tried using your library to perform a really simple task: Load an assembly at runtime containing a controller to include the calls from that controller, without having to reference that assembly at compile time.

I have reproduced the issue in a simple sample project here: https://github.com/Chris197/applicationparttest

Any idea what's wrong?

Regards,

Chris

UPDATE:

I have just found that replacing this code:

PluginLoader.CreateFromAssemblyFile(path).LoadDefaultAssembly();

with this:

AssemblyLoadContext.Default.LoadFromAssemblyPath(path);

does work. So the problem does seem to be related to the PluginLoader.

Loading the assembly requires having reference to its interface-containing assembly as well

Describe the bug
The assembly containing the implementation for the contract (interface) can't be done without explicitly referencing
the assembly which contains the definition of the contract (interface)

To Reproduce
Steps to reproduce the behavior:

  1. Using this version of the library '0.3.0'
  2. Add breakpoint inside ``
  3. Run Facade project from
    https://github.com/godrose/netcore-load/tree/master
  4. Hit localhost:32064/api/warehouse

Expected behavior
Call is handled successfully

Actual behavior
IWarehouseRepository implementation can't be located

Notes
Observe how reverting the changes from the commit godrose/netcore-load@d5e945c would fail the call.
This functionality also works in one of the old pre-release packages, 0.3.0-beta.36, for example.

Additional context
.NET Core 3.0, VS 2019, Test Explorer

Code isolation: Plugins with no shared types vs AppDomain

I'm using plugins successfully in my app. Everything works great. Currently the host and the single plugin are being built at the same time. One would not expect any conflict given that all the shared dependencies are the same version. I want to separate the host from the plugin with regard to building. The shared dependencies (numerous) will now be different. The host would be updated much less frequently then the plugins. So now my question: I had previously been using AppDomains for isolation and of course never experienced any dependency clash. If I do not share any types (no unification) can I expect the same level of isolation with DotNetCorePlugins?

Thanks for you efforts.
Jim

Loading dependencies from Microsoft.AspNetCore.App fails

Describe the bug
Loading and executing a minimal web app as a plugin fail to resolve reference to Microsoft.AspNetCore.Hosting.Abstractions

To Reproduce
(-- Repro code provided below further down --)

  1. Create a new web app, i.e. dotnet new web and build it.
  2. Load the web app using PluginLoader.CreateFromAssemblyFile(pathToWebApp).LoadDefaultAssembly();
  3. Execute the web app entrypoint: app.EntryPoint.Invoke(null, new object[] { new string[] { } });

Expected behavior
The application load and execute properly.

Additional context
Repro provided:

  1. Clone https://github.com/per-samuelsson/ReproPluginsIssue
  2. CD src
  3. Run Run2.bat

Use built-in APIs for library loading

.NET Core 3.0 is working on adding API which may mean this library doesn't have to do deps.json parsing on its own. See dotnet/designs#47

If this goes in, this library may be able to directly invoke corehost to load components and their dependencies correctly.

Update plugins functionality

When project will go live then it will require to update its plugins at run time like WordPress.
So I think at your library you can provide that functionality for loading new version of plugins at runtime.

Make plugins unloadable

This will require .NET Core 3.0 and up, which has added libraries to make assembly load contexts unloadable.

See https://github.com/dotnet/coreclr/issues/552, dotnet/coreclr#18476, https://github.com/dotnet/corefx/issues/14724, and https://github.com/dotnet/coreclr/projects/9.


Update

Merged #31 which adds API when using .NET Core 3.0 to allow unloading.

Usage

Unloadable plugins is supported in .NET Core 3.0 Preview 2 and newer. To unload a plugin, make sure isUnloadable = true and dispose the loader to trigger the unload.

var loader = PluginLoader.CreateFromAssemblyFile(
                    pluginDll,
                    sharedTypes: new [] { typeof(IPlugin) },
                    isUnloadable: true);
// do stuff

loader.Dispose(); // this triggers an unload

In order for this to work, you must make sure you have not left behind any variables (static, local, etc) or manually allocated memory. Once GC has collected everything, the assembly will be unloaded. See dotnet/coreclr#22221

Hot reloading plugins in .NET Core 3 on run-time (without restarting the app)

Is your feature request related to a problem? Please describe.
More than a year ago I wrote a library for loading plugins. To prevent plugins from being inaccessible by the system, I loaded all the plugins directories in memory. I was monitoring the directory changes (PhysicalFileProvider Watch()), and whenever the plugin was added, I loaded it automatically.

You can find it here

Is it possible to do like this with your plugin system? Replace an assembly (plugin) and reload it automatically? (even publish a message to subscribers to update\refresh the UI and ...!)

Describe the solution you'd like
Unfortunately, I am not familiar with the new way on .NET Core 3 so I asked you about hot reloading the plugins!

Additional context
In my solution, we can copy/paste any plugin on plugin directory and LoadEngine and reload it instantly and automatically it helps us to have a very dynamic plugin system without restarting the web servers or applications.
I hope it is possible in .NET Core 3 and your awesome plugin system.

Self contained apps do not have a runtime graph

Describe the bug
The .NET Core SDK trims the RID graph for self-contained apps. This means self contained apps cannot correctly interpret deps.json and RIDs.

To Reproduce

  1. Create a host app that is self contained.
  2. Create a plugin which uses a dependency that has rid-specific assets, such as System.Drawing.Common

Expected behavior
Plugins do not need to know about the RID used by the host, and vice versa.

Additional context

The .NET Core SDK makes the assumption that self-contained applications know about their dependencies at build-time, and it does some trimming to remove information it thinks won't be needed at runtime. Some of the information trimmed is the runtime identifier fallback graph. This means when this section of code runs, it cannot find a runtime graph so fallbackGraph = { "any" }.

var ridGraph = dependencyContext.RuntimeGraph.Any()
? dependencyContext.RuntimeGraph
: DependencyContext.Default.RuntimeGraph;
var rid = Microsoft.DotNet.PlatformAbstractions.RuntimeEnvironment.GetRuntimeIdentifier();
var fallbackRid = GetFallbackRid();
var fallbackGraph = ridGraph.FirstOrDefault(g => g.Runtime == rid)
?? ridGraph.FirstOrDefault(g => g.Runtime == fallbackRid)
?? new RuntimeFallbacks("any");

For example: when it is reading the deps.json file, the runtime does not know how to handle the "win" runtime identifer.

          "runtimes/win/lib/netcoreapp2.0/System.Drawing.Common.dll": {
            "rid": "win",
            "assetType": "runtime",
            "assemblyVersion": "4.0.0.1",
            "fileVersion": "4.6.26919.2"
          }

Unable to debug code loaded in more than one ALC

Consider this setup:

  • Library1
  • Plugin1 (depend on Library1, via project reference)
  • Plugin2 (depend on Library1, via project reference)
  • Host (console app)

Host is using PluginLoader.CreateFromAssemblyFile(pluginPath) to load both plugin assemblies, and calls a "startup" method in them. Plugins call a common method in Library1.

No shared types and no custom options in use.

If I debug the host, I can step Host -> Plugin -> Library1 when the first plugin is loaded. But when the second one is, I can not step into method of Library1. Also, I see a message (in Debug Output) that Library1 from path of Plugin can't load. (Using VS Code to debug, but tested VS 17 too). That part is also confusing, since why shouldn't it load in the second ALC?

Let me know if you want me to craft a repro.

Resolve native assemblies from NuGet package

Is your feature request related to a problem? Please describe.
I have a plugin with a dependency on Microsoft.CognitiveServices.Speech, which has native dependencies in the runtimes folder that are resolved per platform. When the P/Invoke call occurs from inside Microsoft.CognitiveServices.Speech after resolving the plugin with PluginLoader.CreateFromAssemblyFile, I get a DllNotFoundException.

Describe the solution you'd like
I'd like the PluginLoader to also search the runtimes folder in the base path for the AssemblyLoadContext so that I do not get the DllNotFoundException for the native dependency.

Describe alternatives you've considered
Haven't started thinking about alternatives.

Additional context
No additional context.

Could this work with .net 4.6+?

Hey, I really enjoyed using this lib with dotnet core, but I really need to make something similar for .net 4.6. Would this work if I built it for 4.6? Thanks

MVC Sample - FrameworkReference & .NET Core 3 questions

Hi,

I've been checking the mvc sample you've provided and I was interested as to why and how you knew about the Microsoft.NET.Sdk.Razor, the FrameworkReference (here)?

After some research, I now understand what they mean. Using Microsoft.AspNetCore.App means you're including the full ASP.NET Core 3. What I don't get is that the Web application (.NET Core 3) doesn't have a FrameworkReference nor Nuget packages so how does dotnet know?

Thank you!

External Type / extend API

Hi man,

I just discovered your POC.
I'm huge fan of the concept but one question,
would it be possible in the main app to use external Type class from plugin ?
If so how ?

Because the main app would not normally recongize type at compile ...
Or an other case could plugin add route to the ones registered in the main app ?

thanks in advance,
Best regards

.deps.json as Intermediary format for assembly loading

@natemcmaster Here is draft number one of my proposal based on discussion in #41 - I think it needs a bit more refining (fewer words, clearer user stories), but let me know your thoughts. I also think this might be two "RFCs" in one at the moment, so we may want to break this issue into two in the future:

  1. analysis of dependencies
  2. run-time construction of dependencies.

Is your feature request related to a problem? Please describe.

  • As a developer, I want to load assemblies from my executable's .deps.json file, so that I have a well-defined manifest for what assemblies can be loaded, as well as what transitive dependencies are required to be loaded.
  • As a developer, I want to create additional .deps.json files that mirror DotNetCorePlugins contexts, so that I have a human readable data structure similar to the old bindingRedirect xml syntax.

Describe the solution you'd like

  1. Human readable (e.g., json or xml) data structure that defines, per plugin:
    • Where to locate assemblies
    • What acceptable assembly versions
    • Transitive dependencies required by the plugin
    • 32-bit/64-bit/AnyCPU scenario handling
  2. Bonus if that data structure matches exactly the .deps.json format used by the CLR to load an executable's dependencies, accessible via: System.AppContext.GetData("APP_CONTEXT_DEPS_FILES")
  3. Bonus if solution can expose nicer API than System.AppContext.GetData("APP_CONTEXT_DEPS_FILES")
  4. Bonus if there is a command line tool to generate a .deps.json file for a given DLL.
    • This is effectively feature parity with Java's Maven tool and it's dependency:analyze plugin goal
    • Think of this as a tool that performs analysis of possible AssemblyLoadContext "participants". This would work similar to ReSharper's "Did you forget to include a reference to this assembly?" tooltip suggestion in Visual Studio, except for v1, it is probably best to make it a batch-mode .NET CLI Tool.
    • Bonus would be to have a test-run command which would validate, using Roslyn, that all transitive dependencies can be loaded.

Describe alternatives you've considered
Use Microsoft.DotNet.PlatformAbstractions package or Microsoft.Extensions.FileSystemGlobbing to round up all *.dll files matching a certain prefix. The problem with this approach is it doesn't specify sequential ordering requirements for loading dll's.

Additional context
Seems similar to request #11 which was closed due to inactivity. Except my request has a bit more "meat on the bone".

Getting ReflectionTypeLoadException when loading a dll from a different directory

Describe the bug
Code:

var loader = PluginLoader.CreateFromAssemblyFile(
                    assemblyFile: @"D:\Sources\MyTaskDlls\Task1.dll",
                    sharedTypes: new[] {typeof(ITaskHandler)});
                var taskHandlersInAssembly = loader.LoadDefaultAssembly().GetTypes().Where(t => typeof(ITaskHandler).IsAssignableFrom(t));
                var taskHandlerType = taskHandlersInAssembly.First();

                var taskHandlerInstance = (ITaskHandler) Activator.CreateInstance(taskHandlerType);
                var response = taskHandlerInstance.HandleTask("blablbalba");

loader.LoadDefaultAssembly() throws the following exception:
"System.Reflection.ReflectionTypeLoadException: Unable to load one or more of the requested types. Retrieve the LoaderExceptions property for more information.\r\n at System.Reflection.RuntimeModule.GetTypes(RuntimeModule module)\r\n at System.Reflection.Assembly.GetTypes()\r\n at ...

core 2.2 loading mvc fails at runtime with Cannot find compilation library location for package xxx

Describe the bug
Code repo: https://github.com/kukks/btctransmuter-vnext

I've configured everything as per sample however when I publish a plugin(that has mvc views, partials, etc) and attempt to load it, I get Cannot find compilation library location for package xxx

I've tried publishing the plugin with a number of options in the csproj such as:

<PreserveCompilationContext>true</PreserveCompilationContext>
<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
<MvcRazorCompileOnPublish>true</MvcRazorCompileOnPublish>

To Reproduce
Steps to reproduce the behavior:

  1. Using this version of the library '0.2.4'
  2. Run this code '....'
  3. With these arguments '....'
  4. See error

Additional context
Full stack:

System.InvalidOperationException: Cannot find compilation library location for package 'BtcTransmuter.Extension.Email'
   at Microsoft.Extensions.DependencyModel.CompilationLibrary.ResolveReferencePaths(ICompilationAssemblyResolver resolver, List`1 assemblies)
   at Microsoft.Extensions.DependencyModel.CompilationLibrary.ResolveReferencePaths()
   at Microsoft.AspNetCore.Mvc.ApplicationParts.AssemblyPart.<>c.<GetReferencePaths>b__8_0(CompilationLibrary library)
   at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()
   at Microsoft.AspNetCore.Mvc.Razor.Compilation.MetadataReferenceFeatureProvider.PopulateFeature(IEnumerable`1 parts, MetadataReferenceFeature feature)
   at Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager.PopulateFeature[TFeature](TFeature feature)
   at Microsoft.AspNetCore.Mvc.Razor.Internal.DefaultRazorReferenceManager.GetCompilationReferences()
   at System.Threading.LazyInitializer.EnsureInitializedCore[T](T& target, Boolean& initialized, Object& syncLock, Func`1 valueFactory)
   at System.Threading.LazyInitializer.EnsureInitialized[T](T& target, Boolean& initialized, Object& syncLock, Func`1 valueFactory)
   at Microsoft.AspNetCore.Mvc.Razor.Internal.DefaultRazorReferenceManager.get_CompilationReferences()
   at Microsoft.AspNetCore.Mvc.Razor.Internal.LazyMetadataReferenceFeature.get_References()
   at Microsoft.CodeAnalysis.Razor.CompilationTagHelperFeature.GetDescriptors()
   at Microsoft.AspNetCore.Razor.Language.DefaultRazorTagHelperBinderPhase.ExecuteCore(RazorCodeDocument codeDocument)
   at Microsoft.AspNetCore.Razor.Language.RazorEnginePhaseBase.Execute(RazorCodeDocument codeDocument)
   at Microsoft.AspNetCore.Razor.Language.DefaultRazorEngine.Process(RazorCodeDocument document)
   at Microsoft.AspNetCore.Razor.Language.DefaultRazorProjectEngine.ProcessCore(RazorCodeDocument codeDocument)
   at Microsoft.AspNetCore.Razor.Language.RazorProjectEngine.Process(RazorProjectItem projectItem)
   at Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler.CompileAndEmit(String relativePath)
   at Microsoft.AspNetCore.Mvc.Razor.Internal.RazorViewCompiler.OnCacheMiss(String normalizedPath)
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Razor.Internal.DefaultRazorPageFactoryProvider.CreateFactory(String relativePath)
   at Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine.CreateCacheResult(HashSet`1 expirationTokens, String relativePath, Boolean isMainPage)
   at Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine.OnCacheMiss(ViewLocationExpanderContext expanderContext, ViewLocationCacheKey cacheKey)
   at Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine.LocatePageFromViewLocations(ActionContext actionContext, String pageName, Boolean isMainPage)
   at Microsoft.AspNetCore.Mvc.Razor.RazorViewEngine.FindView(ActionContext context, String viewName, Boolean isMainPage)
   at Microsoft.AspNetCore.Mvc.ViewEngines.CompositeViewEngine.FindView(ActionContext context, String viewName, Boolean isMainPage)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.RenderPartialCoreAsync(String partialViewName, Object model, ViewDataDictionary viewData, TextWriter writer)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.HtmlHelper.PartialAsync(String partialViewName, Object model, ViewDataDictionary viewData)
   at AspNetCore.Views_Shared_Components_ExtensionMenus_Default.ExecuteAsync() in C:\Git\BtcTransmuterVNext\BtcTransmuter\Views\Shared\Components\ExtensionMenus\Default.cshtml:line 7
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewComponents.ViewViewComponentResult.ExecuteAsync(ViewComponentContext context)
   at Microsoft.AspNetCore.Mvc.ViewComponents.DefaultViewComponentInvoker.InvokeAsync(ViewComponentContext context)
   at Microsoft.AspNetCore.Mvc.ViewComponents.DefaultViewComponentHelper.InvokeCoreAsync(ViewComponentDescriptor descriptor, Object arguments)
   at AspNetCore.Views_Shared__Layout.<ExecuteAsync>b__21_1() in C:\Git\BtcTransmuterVNext\BtcTransmuter\Views\Shared\_Layout.cshtml:line 32
   at Microsoft.AspNetCore.Razor.Runtime.TagHelpers.TagHelperExecutionContext.SetOutputContentAsync()
   at AspNetCore.Views_Shared__Layout.ExecuteAsync()
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageCoreAsync(IRazorPage page, ViewContext context)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderPageAsync(IRazorPage page, ViewContext context, Boolean invokeViewStarts)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderLayoutAsync(ViewContext context, ViewBufferTextWriter bodyWriter)
   at Microsoft.AspNetCore.Mvc.Razor.RazorView.RenderAsync(ViewContext context)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ViewContext viewContext, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewExecutor.ExecuteAsync(ActionContext actionContext, IView view, ViewDataDictionary viewData, ITempDataDictionary tempData, String contentType, Nullable`1 statusCode)
   at Microsoft.AspNetCore.Mvc.ViewFeatures.ViewResultExecutor.ExecuteAsync(ActionContext context, ViewResult result)
   at Microsoft.AspNetCore.Mvc.ViewResult.ExecuteResultAsync(ActionContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultAsync(IActionResult result)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResultFilterAsync[TFilter,TFilterAsync]()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResultExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.ResultNext[TFilter,TFilterAsync](State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeResultFilters()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextResourceFilter()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Rethrow(ResourceExecutedContext context)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeFilterPipelineAsync()
   at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeAsync()
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Routing.EndpointRoutingMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.StaticFiles.StaticFileMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.MigrationsEndPointMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore.DatabaseErrorPageMiddleware.Invoke(HttpContext httpContext)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Allow building compile dependencies for the plugin loader (optionally)

Is your feature request related to a problem? Please describe.

When using the plugin loader for testing tools, it might be required to load the (ASP.NET Core) compile time dependencies as well. (In my case it was missing Microsoft.AspNetCore.Hosting.Abstractions, see specsolutions/deveroom-visualstudio#5.)

Describe the solution you'd like

I have made a solution based on v0.2.4, that allows to control this behavior through PluginLoaderOptions and add the necessary libs in the AddDependencyContext.

The code can be found in this commit: gasparnagy@b03be18
It is currently not applicable to master because the plugin loading conf has been changed since then. Any suggestion is welcome, how this could be applied to the master and I will send a PR.

Describe alternatives you've considered

I could not figure out any other.

Additional context

Please note that #45 is required for this to work.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.