Unable to resolve a controller that was loaded fro

2019-03-22 04:52发布

问题:

I am building a Web API using MVC4 Web API with an IoC container (Simple Injector in this case, but I don't think this problem is related to that container) that should expose a variety of CRUD and query operations. The reason for using IOC in my case is that we are a dev shop and I need to be able to let customers build their own web API controllers to expose the data they need to expose need from our system. Consequently, I was hoping to design my solution in a way that allowed me to dogfood my own product by making all the controllers, both ours and our customers', external and loadable through IOC.

The website does not have any reference to the library but the library contains controllers that I want to use in the website. The types are registered in the container and the DependencyResolver is set to the custom dependency resolver. I have the code finding the dll plugin and loading the controller type but when I try to navigate to the route that it would represent it says it can't find it.

i.e. if I try to navigate to /api/Test1Api I should see the text "hello world"

My problem here is that although I have loaded my controller type, I am unable to translate that into a route that the website says is there.

I get the error

No HTTP resource was found that matches the request URI

No type was found that matches the controller named 'Test1Api'.

Here is how I register the container

public static class SimpleInjectorInitializer
{
    /// <summary>Initialize the container and register it as MVC3 Dependency Resolver.</summary>
    public static void Initialize()
    {
        //// Did you know the container can diagnose your configuration? Go to: http://bit.ly/YE8OJj.

        // Create the IOC container.
        var container = new Container();

        InitializeContainer(container);

        container.RegisterMvcAttributeFilterProvider();
        // Verify the container configuration
        container.Verify();

        // Register the dependency resolver.
        GlobalConfiguration.Configuration.DependencyResolver =
                    new SimpleInjectorWebApiDependencyResolver(container);
    }

    private static void InitializeContainer(Container container)
    {
        var appPath = AppDomain.CurrentDomain.BaseDirectory;

        string[] files = Directory.GetFiles(appPath + "\\bin\\Plugins", "*.dll",
            SearchOption.AllDirectories);
        var assemblies = files.Select(Assembly.LoadFile);

        // register Web API controllers
        var apiControllerTypes =
            from assembly in assemblies
            where !assembly.IsDynamic
            from type in assembly.GetExportedTypes()
            where typeof(IHttpController).IsAssignableFrom(type)
            where !type.IsAbstract
            where !type.IsGenericTypeDefinition
            where type.Name.EndsWith("Controller", StringComparison.Ordinal)
            select type;

        // register MVC controllers
        var mvcControllerTypes =
            from assembly in assemblies
            where !assembly.IsDynamic
            from type in assembly.GetExportedTypes()
            where typeof(IController).IsAssignableFrom(type)
            where !type.IsAbstract
            where !type.IsGenericTypeDefinition
            where type.Name.EndsWith("Controller", StringComparison.Ordinal)
            select type;

        foreach (var controllerType in apiControllerTypes)
        {
            container.Register(controllerType);
        }

        foreach (var controllerType in mvcControllerTypes)
        {
            container.Register(controllerType);
        }
    }
}

Any help is appreciated.

回答1:

One big warning about your solution. It is quite crucial to register your controller into your container as well (this advice holds for all DI frameworks, although some frameworks by default force you to register concrete types as well). Otherwise you will certainly get bitten by the same problem as this developer had.

Since the IHttpControllerTypeResolver makes use of the IAssembliesResolver, the simplest (and safest) solution is to simply ask the IHttpControllerTypeResolver for all controls to register. Your SimpleInjectorInitializer in that case will look like this:

public static class SimpleInjectorInitializer
{
    public static void Initialize()
    {
        // Create the IOC container.
        var container = new Container();

        InitializeContainer(container);

        container.RegisterMvcAttributeFilterProvider();

        // Verify the container configuration
        container.Verify();

        // Register the dependency resolver.
        GlobalConfiguration.Configuration.DependencyResolver =
            new SimpleInjectorWebApiDependencyResolver(container);
    }

    private static void InitializeContainer(Container container)
    {
        GlobalConfiguration.Configuration.Services
            .Replace(typeof(IAssembliesResolver),
                new CustomAssembliesResolver());

        var services = GlobalConfiguration.Configuration.Services;
        var controllerTypes = services.GetHttpControllerTypeResolver()
            .GetControllerTypes(services.GetAssembliesResolver());

        // register Web API controllers (important!)
        foreach (var controllerType in controllerTypes)
        {
            container.Register(controllerType);
        }        
    }
}

Also note that your CustomAssembliesResolver can be made considerably easier:

public class CustomAssembliesResolver
    : DefaultAssembliesResolver
{
    private Assembly[] plugins = (
        from file in Directory.GetFiles(
            appPath + "\\bin\\Plugins", "*.dll",
            SearchOption.AllDirectories)
        let assembly = Assembly.LoadFile(file)
        select assembly)
        .ToArray();

    public override ICollection<Assembly> GetAssemblies()
    {
        var appPath =
            AppDomain.CurrentDomain.BaseDirectory;

        return base.GetAssemblies()
            .Union(this.plugins).ToArray();        
    }
}


回答2:

I figured it out!

So I created a new class in my Web Api call CustomAssembliesResolver that inherits from DefaultAssembliesResolver. Essentially I add my assembly to the list of assemblies that are parsed when looking for controllers. I still have the code that uses Simple Injector for the DI portion of the solution.

public class CustomAssembliesResolver : DefaultAssembliesResolver
{
    public override ICollection<Assembly> GetAssemblies()
    {
        var appPath = AppDomain.CurrentDomain.BaseDirectory;
        var baseAssemblies = base.GetAssemblies();
        var assemblies = new List<Assembly>(baseAssemblies);
        var files = Directory.GetFiles(appPath + "\\bin\\Plugins", "*.dll",
                        SearchOption.AllDirectories);
        var customAssemblies = files.Select(Assembly.LoadFile);

        // register Web API controllers
        var apiControllerAssemblies =
            from assembly in customAssemblies
            where !assembly.IsDynamic
            from type in assembly.GetExportedTypes()
            where typeof(IHttpController).IsAssignableFrom(type)
            where !type.IsAbstract
            where !type.IsGenericTypeDefinition
            where type.Name.EndsWith("Controller", StringComparison.Ordinal)
            select assembly;

        foreach (var assembly in apiControllerAssemblies)
        {
            baseAssemblies.Add(assembly);
        }

        return assemblies;
    }
}

I also added the following line to the beginning of the App_Start in the Global.asax.cs

GlobalConfiguration.Configuration.Services.Replace(typeof(IAssembliesResolver), new CustomAssembliesResolver());

Hope this helps someone!

A huge, huge thank you to Steven for his help and insight! I really appreciate it!!