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 для каждой сборки. Или, создав сингл родовой производный класс, параметризованный каким-то типом из сборки.
Статья здесь.
Ответ 3
@MisterJames, посмотрите на это:
Приложение Asp.Net Mvc Pluggable
Надеюсь, это полезно.