I am having trouble resolving 404 responses in my Asp.Net MVC 4 project. It's built in VS2012 targeting 4.5.
I have pre-compiled views and controllers built into stand-alone DLLs. I am able to dynamically load the DLLs and inspect them from my core project, even invoke methods on them; however, it seems that the MVC Framework is not aware of the controllers. I am close here, but there is something missing.
Background on the Controllers and Views
Controllers are built in a stand-alone MVC project and inherit from Controller
. Nothing too interesting going on there. The views use RazorGenerator and become classes that live in the project.
The output of the project is a DLL which correctly contains the controllers and views.
The DLLs implement a specific interface, we'll call it IPlugin
, in a separate class (not part of a controller) in the library.
Loading the DLLs
Running as admin in Visual Studio I compile my app, which is hosted under IIS. With the project built, I drop a plugin DLL into my "Plugins" directory. Without debugging (this becomes important later), I open IE and navigate to the site. Note that at this point the App has been built, but never run, so startup events will fire. Everything here is still consistent if I recycle the app pool.
I have a Startup
class with two methods, PreStart
and PostStart
and invoke the methods using WebActivator.PreApplicationStartMethod
and WebActivator.PostApplicationStartMethod
respectively.
PreStart
is where I do the following:
- Get a list of all the plugin DLLs in my "Plugins" directory
- Copy all plugins to
AppDomain.CurrentDomain.DynamicDirectory
- Load the type...if it contains an
IPlugin
I then- Add the assembly to the BuildManager
- Call some of the methods on the class that implements IPlugin
In 'PostStart' I do this bit of code (based on code from RazorGenerator.Mvc):
foreach (var assembly in Modules.Select(m=>m.Value))
{
var engine = new PrecompiledMvcEngine(assembly)
{
UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal
};
ViewEngines.Engines.Insert(0, engine);
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
Modules
in this context is a key/value pair where the values are the loaded assemblies. The purpose of this code is to make sure that MVC is aware of the views by adding a view engine for each assembly that knows how to resolve the views (this is part of RazorGenerator).
How I Know I'm Close (but Clearly Lacking the Cigar)
IPlugin
defines a method called RegisterRoutes
where, you guessed it, routes are to be registered for those who implement the interface. I call this method in PreStart
and routes are added - I have verified that these exist in my route table. For instance, on a route defined in my plugin, created through dynamic invocation of the method during the PreStart
, I see something like this as a DataToken when examining my routes:
Namespaces = Plugin.Name.Controllers
So, the route is registered, the assembly is loaded, I have verified that the DLL is correctly copied to the DynamicDirectory of the AppDomain. I am able to invoke members of classes that are loaded dynamically at runtime. But when I navigate to the URL that is matched by the route I get a 404. This is not a "could not locate view" YSOD, it's more akin to not finding the controller at all.
Here is the part that confuses the heck out of me: if, at this point, without doing anything, I return to Visual Studio and hit F5...everything works.
It's like Visual Studio is becoming aware of the controller in some way that I can't identify, and the MVC Framework is picking up on it.
Finally, A Question
What am I missing, and how do I get the MVC Framework to be aware of my controller?
And hey, at this point, if you're still reading this, thanks. :)