9

We're currently developping a dynamic system which need to load some extensions at runtime.

An extension is build on the same architecture that MVC app, which means a controller folder, with classes that ends with Controller, associated Views and ViewComponents that are located in /View/ControllerName, and related models.

It is not possible to add this project as reference to the project, so I created a middleware that load them at runtime :

foreach (var item in extensions)
{
   Assembly.LoadFrom($@"extensions\{item.Name}.dll");
}

So far so good, they are loaded on runtime. But, when I try to access a route that is created in one of extension's controller, WebSite gives me a 404 response.

I tried to add the extension as reference and it works well, so this is not an issue inside my extension.

How can I manage to register my dll's controller into the MVC main site ?

This is NOT ASP MVC 4, this is ASP Core, therefore it seems that this answer is not valid : asp.net mvc put controllers into a separate project

Although Dependency Injection could be a solution, I don't found any solution to make my extension register services for itself (and it's complicated for extensions creators).

My routing for extensions is defined Controller side :

[Route("[controller]/[action]")]
public class LotteryController : Controller { ... }

On my Startup.cs, I've kept the default route actually :

 app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

The fact is, I want my extension to enable route : http://localhost/Lottery/Index

And it gives me a blank page. My current Index action, for testing purpose, is

 // GET Index
 public IActionResult Index()
{
   return Content("From extension");
   //return View();
}

And here's my extension project hierarchy

enter image description here

Community
  • 1
  • 1
cdie
  • 4,014
  • 4
  • 34
  • 57
  • Is it possible the problem is routing related rather than the code responsible for loading the extensions? Perhaps you could provide your controller code and/or MVC config in Startup.cs? – lawst Apr 05 '16 at 12:25
  • Added them to my question – cdie Apr 05 '16 at 13:08
  • Ok so it looks like you're using the default routing. Can you also provide the controller method for "Index"? You also mentioned "View/ControllerName" in your question, however the default view directory structure should be "Views/ControllerName" so can you also confirm the view is located in the correct place? – lawst Apr 05 '16 at 13:24
  • For testing purpose, I made it really basic. I edited my question according to it – cdie Apr 05 '16 at 13:30
  • `so I created a middleware that load them at runtime :` - At what point are the assemblies loaded in? – Igor Apr 05 '16 at 13:40
  • In my Startup.Configure Method, at the begining. The only this I have Before is UseIISPlatformHandler, AddConsole/Debug, UseDevelopperExceptionPage, UseRuntimeInfoPage and UseFileServer. Then, my Middleware load. – cdie Apr 05 '16 at 13:42
  • Have you tried https://www.nuget.org/packages/EmbeddedResourceVirtualPathProvider/ ? – usr-local-ΕΨΗΕΛΩΝ Apr 06 '16 at 15:35

2 Answers2

7

MVC will automatically find controllers that inherit from Controller on startup and include them. That will work for external assemblies but will more than likely only work for those that are statically linked.

If you are dynamically loading them you may want to either try and delay the startup of MVC or register the assemblies separately.

This code snippet shows how to search other assemblies for controllers. The full article explaining this can be found here. You may be able to use this once you have loaded your assemblies to force MVC to search them for controllers.

services.AddMvc().AddControllersAsServices(new[]
{ 
    typeof(MyController).Assembly,
    typeof(ExternalPocoController).Assembly 
}); 

This will search those assemblies for controllers. The only caveat is that it may not be able to find the associated views. I'm honestly not sure on this as I've only ever done it with WebAPI controllers. Edit: This looks like it might be a way to get the views working

As a side note, I'd probably suggest either using Route decorators OR mapping routes via the old way. Using both seems like a recipe for trouble.

Community
  • 1
  • 1
Chris
  • 26,744
  • 48
  • 193
  • 345
  • Responding to the side note - sometimes you want to use Attribute Routing but have a few routes which you want to load in after all of the others have been loaded. I've 4 routes (of hundreds) which I load in after all of the attribute routing has been taken care of, simply because routing them via Area Registration gives me much more control than Attribute Routing. See http://stackoverflow.com/questions/33155259/is-it-possible-to-change-order-of-routes-in-routing-table-when-using-attribute-r – David T. Macknet Apr 08 '16 at 16:54
1

When you loaded assembly you need add ActionDescriptor instances for your actions manually. Actually MVC6 using 'Composite root' and creating immutable routes table. You can implement your custom IActionDescriptorCollectionProvider and replace the default in DI:

services.Replace(ServiceDescriptor.Describe(typeof(IActionDescriptorCollectionProvider), typeof(CustomActionDescriptorCollectionProvider), ServiceLifetime.Singleton));

and in getter of property:

public ActionDescriptorCollection ActionDescriptors

return default (build at startup) and your own ActionDescriptor's. In my solution i create intermediate store where i put my own ActionDescriptor's when i load dynamic assembly and in IActionDescriptorCollectionProvider i'm just check this store

Den Kozlov
  • 11
  • 1