404 на контроллерах во внешних сборках

У меня возникли проблемы с разрешением 404 ответов в моем проекте Asp.Net MVC 4. Он построен в VS2012, ориентированном на 4.5.

У меня есть предварительно скомпилированные представления и контроллеры, встроенные в автономные библиотеки DLL. Я могу динамически загружать библиотеки DLL и проверять их из моего основного проекта, даже вызывать методы на них; однако, похоже, что MVC Framework не знает контроллеров. Я здесь близко, но чего-то не хватает.

Фон на контроллерах и представлениях

Контроллеры построены в автономном проекте MVC и наследуются от Controller. Ничего интересного не происходит. Представления используют RazorGenerator и становятся классами, которые живут в проекте.

Результатом проекта является DLL, которая правильно содержит контроллеры и представления.

DLL реализуют определенный интерфейс, мы будем называть его IPlugin в отдельном классе (не являющемся частью контроллера) в библиотеке.

Загрузка DLL

Запуск в качестве администратора в Visual Studio Я скомпилирую свое приложение, которое размещено под IIS. С построенным проектом я бросаю DLL плагина в каталог "Плагины". Без отладки (это становится важным позже) я открываю IE и перехожу на сайт. Обратите внимание, что на данный момент приложение создано, но никогда не запускается, поэтому запускаются события запуска. Все здесь по-прежнему непротиворечиво, если я повторно использую пул приложений.

У меня есть класс Startup с двумя методами, PreStart и PostStart и вызывать методы, используя WebActivator.PreApplicationStartMethod и WebActivator.PostApplicationStartMethod соответственно.

PreStart, где я делаю следующее:

  • Получить список всех DLL-модулей плагина в моем каталоге "Плагины"
  • Скопируйте все плагины на AppDomain.CurrentDomain.DynamicDirectory
  • Загрузите тип... если он содержит IPlugin I, тогда
    • Добавить сборку в BuildManager
    • Вызвать некоторые из методов класса, реализующего IPlugin

В 'PostStart' я делаю этот бит кода (на основе кода 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 в этом контексте является парой ключ/значение, где значения представляют собой загруженные сборки. Цель этого кода - убедиться, что MVC осведомлен о представлениях, добавив механизм просмотра для каждой сборки, который знает, как разрешить представления (это часть RazorGenerator).

Как я знаю, что я закрыт (но явно не хватает сигары)

IPlugin определяет метод под названием RegisterRoutes, где, как вы догадались, маршруты должны быть зарегистрированы для тех, кто реализует интерфейс. Я вызываю этот метод в PreStart и добавляются маршруты - я проверил, что они существуют в моей таблице маршрутов. Например, на маршруте, определенном в моем плагине, создаваемом динамическим вызовом метода во время PreStart, я вижу что-то вроде этого как DataToken при рассмотрении моих маршрутов:

Namespaces = Plugin.Name.Controllers

Итак, маршрут зарегистрирован, сборка загружена, я проверил, что DLL правильно скопирована в DynamicDirectory AppDomain. Я могу вызвать участников классов, которые динамически загружаются во время выполнения. Но когда я перехожу к URL-адресу, который соответствует маршруту , я получаю 404. Это не "не удалось найти вид" YSOD, он более сродни не обнаружению контроллера вообще.

Вот часть, которая смущает меня из-за меня: если на данный момент, ничего не делая, я возвращаюсь в Visual Studio и нажимаю F5... все работает.

Это похоже на то, что Visual Studio осознает контроллер каким-то образом, который я не могу определить, и MVC Framework подбирает его.

Наконец, вопрос

Что мне не хватает, и как мне получить MVC Framework о моем контроллере?

И в этот момент, если вы все еще читаете это, спасибо.:)

Ответ 1

Оказывается, что это ошибка в самой Asp.Net.

Обсудив проблему с Эйлоном Липтоном из команды Asp.Net и думая, что это было что-то не так, в MVC Framework, Эйлон и члены нескольких команд врывались в вещи и обнаружили, что ошибка была на более низком уровне за этот разговор: http://aspnetwebstack.codeplex.com/discussions/403529

Они также предложили обходное решение, которое включало другой вызов BuildManager после вызова AddReferencedAssembly, который я реализовал с помощью следующего кода:

    // Add the plugin as a reference to the application
    BuildManager.AddReferencedAssembly(assembly);
    BuildManager.AddCompilationDependency(assembly.FullName);

Это позволяет добавлять дополнительные контроллеры/скомпилированные представления при запуске на этапе инициализации перед приложением. Теперь я просматриваю список DLL в моем каталоге плагинов и нажимаю их на BuildManager, как указано выше.

Единственное ограничение здесь заключается в том, что вы не можете удалить сборки или очистить кеш динамически. Единственный способ, который я нашел для этого, - добавить ранее неизвестную сборку к ссылочным сборкам и зависимостям компиляции. Я экспериментирую с динамическим испусканием новой сборки во время инициализации перед приложением, чтобы я мог, эффективно, очистить кеш и удалить ранее включенные плагины, создав новую сборку.

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

Приветствия.

Ответ 2

Похоже на эту проблему:

MVC использует имя типа сборки для сборки, соответствующее disambiguate просматривать записи кэша из разных механизмов просмотра. Итак, это невозможно иметь более одного объекта PrecompiledMvcEngine (as когда у вас есть предварительно скомпилированные представления в более чем одной сборке). проблема может быть решена путем создания другого производного класса из PrecompiledMvcEngine для каждой сборки. Или, создав сингл родовой производный класс, параметризованный каким-то типом из сборки.

Статья здесь.