Ronald Meijboom
Ronald Meijboom

Reputation: 1574

Resolving controllers using Castle Windsor with dynamically loaded DLLs

After loading the web application, Castle Windsor cannot find the controllers. The controller for path '' was not found or does not implement IController. When I look into the kernel (in the CustomControllerFactory) I see that all the controllers are correctly registered.

The main MVC application loads 3 other DLL's. When we directly reference the DLL's in Visual studio and load the Plugin types it is working. But when dynamically loading it, it says fails. When I request a URL the context being passed into the GetControllerInstance is correct but the Type parameter is null.

I am loading an assembly using Assembload.LoadFrom.Then I retrieve the Types foreach module, which is a subclass of Plugin. Which results in the 3 types I have.

Assembly assembly = Assembly.LoadFrom(module);

Type pluginType = assembly.GetTypes()
                    .Single(x => x.IsSubclassOf(typeof(Plugin)));

Then I create an instance of the Plugin, which I use to register the routes.

(IPlugin)Activator.CreateInstance(type))

RegisterRoutes:

    public static void RegisterRoutes(RouteCollection routes, IEnumerable<IPlugin> plugins)
    {
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}");

        var pluginRouteDefaults = new {action = "Index", id = UrlParameter.Optional};
        foreach (var plugin in plugins)
        {
            var context = new AreaRegistrationContext(plugin.Area, routes);
            context.MapRoute(plugin.Area, $"{plugin.Area}/{{controller}}/{{action}}/{{id}}", pluginRouteDefaults, plugin.GetControllerNamespaces().ToArray());
        }

        routes.MapRoute(
              name: "Default",
              url: "{area}/{controller}/{action}/{id}",
              defaults: new { area = "Framework", controller = "Home", action = "Index", id = UrlParameter.Optional },
              namespaces: new string[] { "Web.Framework.Controllers" }
              );
    }

CustomControllerFactory:

public class CustomControllerFactory : DefaultControllerFactory
{
    private readonly IKernel _kernel;

    public VlcControllerFactory(IKernel kernel)
    {
        this._kernel = kernel;
    }

    public override void ReleaseController(IController controller)
    {
        _kernel.ReleaseComponent(controller);
    }

    protected override IController GetControllerInstance(RequestContext context, Type controllerType)
    {
        if (controllerType == null)
        {
            return base.GetControllerInstance(context, controllerType);
        }
        try
        {
            return (IController)_kernel.Resolve(controllerType);
        }
        catch
        {
            return base.GetControllerInstance(context, controllerType);
        }
    }
}

Registering the controllers. After doing this I can see that in the Modules window in Visual Studio the DLL's are loaded. Also the AppDomain.CurrentDomain.GetAssemblies() says that the DLL's are loaded.

container.Register(
            Classes.FromThisAssembly().BasedOn<IController>().LifestyleTransient());

MvcApplication class where I locate the Dll's.

public class MvcApplication : System.Web.HttpApplication
{
    protected void Application_Start()
    {
        var directories = Directory.GetDirectories("C:\Projects\main\modules").Where(x => !x.EndsWith("Framework"));
        string subPath = GetSubPath();

        List<Type> pluginTypes = GetPluginTypes(directories, subPath);
        var plugins = GetIPlugins(pluginTypes);
        Launcher.CreateWindsorContainer(plugins.ToArray());
    }

    private static List<IPlugin> GetIPlugins(List<Type> pluginTypes)
    {
        List<IPlugin> plugins = new List<IPlugin>{new MvcInstaller()};

        pluginTypes.ForEach(type => plugins.Add((IPlugin) Activator.CreateInstance(type)));
        return plugins;
    }

    private static List<Type> GetPluginTypes(IEnumerable<string> directories, string subPath)
    {
        List<Type> pluginTypes = new List<Type>();

        foreach (string directory in directories)
        {
            string module = Directory.GetFiles(directory + subPath).SingleOrDefault(x => x.EndsWith("Plugin.dll"));

            if (!string.IsNullOrEmpty(module))
            {
                Assembly assembly = Assembly.LoadFrom(module);
                Type pluginType = assembly.GetTypes()
                    .Single(x => x.IsSubclassOf(typeof(Plugin)));
                pluginTypes.Add(pluginType);
            }
        }
        return pluginTypes;
    }

    private static string GetSubPath()
    {
        #if DEBUG
        var subPath = @"\bin\Debug\";
        #else
        subPath = @"\bin\Release\";
        #endif

        return subPath;
    }
}

When I omit this code and directly reference the other Dll's and do the following:

Launcher.CreateWindsorContainer(new PluginA(), new PluginB(), new MVCPlugin());

Then it works perfect, but with my loading of Dlls, the resolving of the controllers is failing. Why can Castle Windsor not find the Types when requesting a Controller?

Upvotes: 3

Views: 880

Answers (1)

mycroes
mycroes

Reputation: 675

The problem here is not the Windsor resolution of your controllers. The DefaultControllerType.GetControllerType() method is probably returning null, because you didn't add the assemblies to the BuildManager (with BuildManager.AddReferencedAssembly(assembly)). Keep in mind you can only call this before application start, so you need to use [assembly:PreApplicationStartupMethod(typeof(...SomeType), "PublicStaticVoidMethodOnSomeType").

Upvotes: 3

Related Questions