Не удалось разрешить контроллер, который был загружен из внешней dll

Я создаю веб-API с использованием веб-API MVC4 с контейнером IoC (в данном случае Simple Injector, но я не думаю, что эта проблема связана с этим контейнером), который должен предоставлять различные операции CRUD и запросов. Причина использования IOC в моем случае заключается в том, что мы являемся магазином разработчиков, и мне нужно позволить клиентам создавать свои собственные контроллеры веб-API, чтобы предоставлять данные, которые им нужны, чтобы выявлять потребность в нашей системе. Следовательно, я надеялся спроектировать свое решение таким образом, чтобы я мог протестировать свой собственный продукт, сделав все контроллеры, как наши, так и наших клиентов, внешними и загружаемыми через IOC.

На веб-сайте нет ссылки на библиотеку, но библиотека содержит контроллеры, которые я хочу использовать на веб-сайте. Типы регистрируются в контейнере, а для DependencyResolver устанавливается настраиваемый сопоставитель зависимостей. У меня есть код, который находит плагин dll и загружает тип контроллера, но когда я пытаюсь перейти к маршруту, который он будет представлять, он говорит, что не может его найти.

то есть, если я попытаюсь перейти к /api/Test1Api, я должен увидеть текст hello world

Моя проблема здесь в том, что, хотя я загрузил свой тип контроллера, я не могу преобразовать его в маршрут, который есть на веб-сайте.

я получаю ошибку

Не найден ресурс HTTP, соответствующий URI запроса.

Не найдено ни одного типа, соответствующего контроллеру с именем «Test1Api».

Вот как я регистрирую контейнер

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);
        }
    }
}

Любая помощь приветствуется.


person abraganza    schedule 31.05.2013    source источник
comment
Здесь вы зарегистрировали эти типы контроллеров в контейнере DI. Однако веб-API имеет собственный процесс поиска (и кэширования) типов контроллеров. Регистрация их в вашем контейнере не означает, что веб-API может автоматически разрешать их.   -  person Steven    schedule 01.06.2013
comment
Однако, если я не ошибаюсь, поведение веб-API по умолчанию заключается в поиске контроллера во всех загруженных сборках (с использованием IAssembliesResolver/DefaultAssembliesResolver). Поскольку вы загружаете эти сборки плагинов, я ожидал, что Web API подберет их автоматически.   -  person Steven    schedule 01.06.2013
comment
Зачем изобретать велосипед и писать собственный контейнер? Другие прошли через значительную разработку и тестирование, не лучше ли использовать один из них?   -  person Richard    schedule 01.06.2013


Ответы (2)


Одно большое предупреждение о вашем решении. Крайне важно также зарегистрировать контроллер в контейнере (этот совет справедлив для всех DI-фреймворков, хотя некоторые фреймворки по умолчанию заставляют вас также регистрировать конкретные типы). В противном случае вы обязательно столкнетесь с той же проблемой, что и у разработчика.

Поскольку IHttpControllerTypeResolver использует IAssembliesResolver, самое простое (и самое безопасное) решение — просто попросить IHttpControllerTypeResolver зарегистрировать все элементы управления. Ваш SimpleInjectorInitializer в этом случае будет выглядеть так:

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);
        }        
    }
}

Также обратите внимание, что ваш CustomAssembliesResolver можно значительно упростить:

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();        
    }
}
person Steven    schedule 01.06.2013
comment
Сладкий! Спасибо за помощь :) - person abraganza; 01.06.2013
comment
Я изменил строку плагинов на следующую, так как я думаю, что отсутствует выбор. private readonly Assembly[] plugins = (from file in Directory.GetFiles( AppDomain.CurrentDomain.BaseDirectory + "\\bin\\Plugins", "*.dll", SearchOption.AllDirectories) let assembly = Assembly.LoadFile(file) select assembly).ToArray(); - person abraganza; 01.06.2013
comment
@abraganza: я думаю, что был :) - person Steven; 01.06.2013

Я понял!

Поэтому я создал новый класс в своем веб-API с вызовом CustomAssembliesResolver, который наследуется от DefaultAssembliesResolver. По сути, я добавляю свою сборку в список сборок, которые анализируются при поиске контроллеров. У меня все еще есть код, который использует Simple Injector для части решения DI.

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;
    }
}

Я также добавил следующую строку в начало App_Start в файле Global.asax.cs.

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

Надеюсь, это поможет кому-то!

Огромное, огромное спасибо Стивену за его помощь и понимание! Я очень ценю это!!

person abraganza    schedule 01.06.2013
comment
Просто примечание: Assembly.Load заблокирует файл, но вы можете использовать Assembly.Load(File.ReadAllBytes(path)) чтобы избежать блокировки. - person Sergey Barskiy; 07.11.2013