natemcmaster/DotNetCorePlugins

Loading dependencies from Microsoft.AspNetCore.App fails

per-samuelsson opened this issue · 12 comments

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

FWIW, this is a regression - it did in fact work using AspNetCore 2.0, targeting netcoreapp 2.0.

Made a repro of that too: checkout branch Issue19Asp2.0 and then Run2.bat there to see it pass.

Did you try using the sharedTypes parameter on PluginLoader.CreateFromAssemblyFile? By default, each plugin gets a private version of its dependencies, which can break type exchange.

See this sample for example:

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),
});

Did you try using the sharedTypes parameter on PluginLoader.CreateFromAssemblyFile? By default, each plugin gets a private version of its dependencies, which can break type exchange.

Yes, I know, and I do understand and appreciate that concept. But I don't really get how that feature applies to this particular issue?

The hosting application in the repro here is a simple console application (dotnet new console), loading and hosting a web application (dotnet new web). You mean the host should have another reference of Microsoft.AspNetCore.Hosting.Abstractions then, and even if so, why wouldn't the ALC of the hosted application just load the one it needs, given that no types are specified as shared?

By studying and comparing .deps.json files of both versions of the hosted app - 2.0 version that succeed to load, and 2.1 version that fail - I have assured both have the proper reference to Microsoft.AspNetCore.Hosting.Abstractions, but there is a difference: in the later, compileOnly is applied:

"Microsoft.AspNetCore.Hosting.Abstractions/2.1.1": {
        "dependencies": {
          "Microsoft.AspNetCore.Hosting.Server.Abstractions": "2.1.1",
          "Microsoft.AspNetCore.Http.Abstractions": "2.1.1",
          "Microsoft.Extensions.Hosting.Abstractions": "2.1.1"
        },
        "compile": {
          "lib/netstandard2.0/Microsoft.AspNetCore.Hosting.Abstractions.dll": {}
        },
        "compileOnly": true
}

Could it be so that the plugin fail to resolve such dependencies?

Sorry for delayed responses. Been swamped at work. I'll investigate more and see if there is a good solution here. I should have more time now that it is holiday season.

I read the repro closer. The problem is this: https://github.com/per-samuelsson/ReproPluginsIssue/blob/948cc5f31b898e2f5eff19dadd2285c194cef0a6/src/AspNetCoreWebApp/AspNetCoreWebApp.csproj#L12. Your project is using the ASP.NET Core shared framework. This means assemblies need to be resolved against C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App\2.1.*.

This is something I haven't added yet to this library, and it would be difficult to implement correctly. I spent some time hunting through .NET Core's code to see if I could find a reliable way to do this, but couldn't find something that works well. In .NET Core 3.0, there is a plan to add APIs to resolve assemblies (and I was hoping to use them #15), but it looks like the latest iteration of this plan does not support resolving assemblies from shared frameworks.

As a result, for now, you will need to add this to the host application in order to use plugins which reference asp.net core.

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.App" />
  </ItemGroup>

Thanks for getting back on this @natemcmaster. Appreciated!

I'll read up on your responses and the links and will get back on it once/if I have anything to add.

Read up more now on upcoming plans, and also deeper on the current design (i.e. 2.x-wise, and how it have evolved by introducing shared frameworks and runtime package stores). It's really not as simple as in 1.x any more, and options seem to grow all the time.

At this point, I have two questions:

  1. Using shared framework is optional, correct? If so, and if I'm willing to pay the price of not using one, couldn't I just drop the reference to the shared framework, and instead reference those same bits as NuGet packages? (As an alternative to your proposal of having my host/loading application also include a reference to the shared framework).
  2. How does shared frameworks relate to self-contained deployments? If I would instead tweak my host to load the a SCD-version of the same app, shouldn't that mean there were no references to the shared framework remaining?

Using shared framework is optional, correct?

Not exactly a yes-no question since there are multiple shared frameworks (three, to be exact). You have 3 options: use zero shared frameworks (aka self-contained deployment), use some shared frameworks (mixed mode), or use all shared frameworks.

FWIW, in .NET Core 3.0 I'm working with aspnet to simplify by eliminating the mixed mode.

How does shared frameworks relate to self-contained deployments?

Give this a read and let me know if it answers this question: https://natemcmaster.com/blog/2018/08/29/netcore-primitives-2/#the-basics

Fair warning, SCD + plugins suffers from this bug right now: #14

Using shared framework is optional, correct?

Not exactly a yes-no question since there are multiple shared frameworks (three, to be exact). You have 3 options: use zero shared frameworks (aka self-contained deployment), use some shared frameworks (mixed mode), or use all shared frameworks.

How does shared frameworks relate to self-contained deployments?

Give this a read and let me know if it answers this question: https://natemcmaster.com/blog/2018/08/29/netcore-primitives-2/#the-basics

I did already. The whole series actually, and even multiple times (coming back to it). I highly appreciate it! And regarding that particular question, I think I understand it better now.

Fair warning, SCD + plugins suffers from this bug right now: #14

Ah, that's problematic then. Think it would rule out my idea of using SCD deployments as a workaround then. Thanks for pointing that out.

Using shared framework is optional, correct?

Not exactly a yes-no question since there are multiple shared frameworks (three, to be exact). You have 3 options: use zero shared frameworks (aka self-contained deployment), use some shared frameworks (mixed mode), or use all shared frameworks.

Hm, I think I didn't formulate the question specific enough then. I think I need to consider some more. Thanks for the feedback anyway.

I had a conversation with the .NET Core team. At the moment, there is no plan to support dynamically loading shared frameworks. There isn't anything my library can do to workaround this, so I'm going to close this as 'wontfix'. If .NET changes in the future and adds this capability, we can revisit.

The workaround is to have the host application have a reference to Microsoft.AspNetCore.App, even if the host app isn't using aspnetcore directly. Plugins can't use AspNetCore unless the app loading the plugin does, too.