DotNetCorePlugins
DotNetCorePlugins copied to clipboard
Add feature to allow MVC application parts & other resources
Duplicated assemblies with MVC and other resources When using PluginLoader.CreateFromAssemblyFile and mvcBuilder.AddPluginFromAssemblyFile, you end up loading the assemblies twice, and they'll end up in their own LoadContexts.
I may be doing things in the wrong spot, but I had two calls:
- PluginLoader.CreateFromAssemblyFile inside Startup.Configure, to add RequestPath entries for static content, and add Endpoint Routes.
- mvcBuilder.AddPluginFromAssemblyFile inside Startup.ConfigureServices, to MVC parts.
I was trying to use the IServiceCollection dependency injection to easily access to plugin data/state from an MVC controller, but was going crazy when GetService and any direct references to Singletons and static classes were always null. Well, it turns out I was making two different types of plugin loads, and that mean the assemblies loaded twice. The MVC controller had its own un-instantiated set of classes with everything null or default.
Describe the solution you'd like Within the IMvcBuilder extensions, add a method or mode to reference an assembly that has MVC parts, and call ApplicationParts.Add for those. This should be done without reloading the assembly - the use case is the assembly has already been loaded by the plugin manager.
Alternatively, there may be a way to add MVC parts elsewhere, but I recall reading it had to be in the right spot.
I ended up taking some of the code from mvcBuilder AddPluginLoader and grabbing just the bits I needed inside ConfigureServices. Inside my call to PluginLoader.CreateFromAssemblyFile I added a list of plugins that tell me they want to use MVC (part of my plugin interface).
var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assemblyWithMVC);
foreach (var part in partFactory.GetApplicationParts(assemblyWithMVC))
{
mvcBuilder.PartManager.ApplicationParts.Add(part);
}
Interestingly, I didn't need the GetCustomAttributes<RelatedAssemblyAttribute>()
to find the views. I haven't looked into why, but I'm assuming they are being found or are already loaded. If I work out why I'll post again.
Describe alternatives you've considered Well, I guess we could do it ourselves, like I did, but I REALLY like this library. I found it small enough to get going with easily, but with a good feature set. A little more documentation would be nice, but hey I know it's probably a hobby project :-)
Additional context This is my first large project in ASP.NET Core. It started with NET 5.0 earlier this year and I just moved it to NET 6.0 RC2 as we're only a day or two before launch! I know Startup.cs & Program.cs can now be merged in NET 6.0, but haven't looked into that as yet.
Did you try using mvcBuilder.AddPluginLoader
instead to avoid the double load?
https://github.com/natemcmaster/DotNetCorePlugins/blob/452f8d306ef17c84c8b02b948e93eb95ef182be3/src/Plugins.Mvc/MvcPluginExtensions.cs#L48
Hi Nate,
I haven't tried that yet, but I could probably do a more thorough review of where I am calling things.
Inside ASP.NET Core Startup.cs Startup(IWebHostEnvironment env)
method, I call a helper class that finds the plugins and calls PluginLoader.CreateFromAssemblyFile for each.
Then in Startup.cs Configure(IApplicationBuilder app, IWebHostEnvironment env)
I find any static content for each plugin, and add a app.UseStaticFiles call with a new PhysicalFileProvider, RequestPath etc. Further down here, I also inject the ASP.NET IServiceProvider back into the plugin... this was a way to get access to the SignalR HubContext inside a plugin that needs it.
Finally in Startup.cs ConfigureServices(IServiceCollection services)
, I do a mvcBuilder.PartManager.ApplicationParts.Add(part)
for each plugin with MVC - basically the same as what the AppPluginLoader does, but without the additional assembly load.
When I started on this, I was pretty blurry on how ASP.NET builds... but that's a little clearer now. Although, I don't know I could say with any certainty that I know what order it all happens in. I guess this is where they were going with condensing the ASP.NET Core startup templates in NET 6.0.
So, end result is what I have is working great... but perhaps could be cleaned up a little to do more of the plugin load in one place.
Hope that helps with a bit more background!
Thanks for the additional context. I think what you are describing has the potential to be its own class library and could probably be enhanced to have even deeper integration with ASP.NET Core. If this is something you are interested in developing on your own, I could link to your project from this project's README.
I've done something similar, but determined that we were limited by the less than stellar DI Container, so started using SimpleInjector instead. It was difficult at first, as the .net container allows you to do things that SimpleInjector does not. Once we got over the hump though, we've been able to do exactly what you are trying to do above.
Basically I register a collection of IPluginConfigs and use them to find the wwwroot from the plugin. I've recently stumbled upon the embedded fileprovider, which makes a ton more sense for most things, but doesn't allow you to edit your static files after deployment.
Let me know if you want more information. The actual implementation is proprietary, but I'm happy to help with concepts and practices that we have that work, I just can't give you the code :)
That said, I also agree with @natemcmaster that this is beyond the intention of this library.
This issue has been automatically marked as stale because it has no recent activity. It will be closed if no further activity occurs. Please comment if you believe this should remain open, otherwise it will be closed in 14 days. Thank you for your contributions to this project.
Closing due to inactivity. If you are looking at this issue in the future and think it should be reopened, please make a commented here and mention natemcmaster so he sees the notification.