Razor Generator: как использовать представление, скомпилированное в библиотеке, как частичное представление для мастера, определенного в основном проекте MVC
У нас есть приложение ASP.NET MVC 4, содержащее около 3000 представлений. Мы решили разделить этот набор представлений на отдельные библиотеки DLL и скомпилировать его с помощью RazorGenerator. Мы сохраняем только основной _Layout.cshtml и связанные файлы в основном проекте MVC.
Мы не можем загрузить частичные представления из DLL вместе с главным представлением в основном проекте MVC. Подробное описание ниже.
Что уже сделано:
Представления успешно компилируются в библиотеки DLL (я подтвердил, что они находятся в двоичном файле)
Объект PrecompiledMvcEngine создается и регистрируется для каждой библиотеки DLL, содержащей представления, с использованием приведенного ниже кода в Application_Start в Global.asax.cs:
,
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
// ...
// some code determining whether we've got an assembly with views
// ...
var engine = new PrecompiledMvcEngine(assembly);
engine.UsePhysicalViewsIfNewer = true;
ViewEngines.Engines.Insert(0, engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}
Что не работает:
Я не могу загрузить представление, определенное в основном проекте MVC (скажем, _Layout.cshtml) с частичным представлением, определенным в одной из библиотек (например, Partial.cshtml). Я использую следующий код в действии контроллера, чтобы сообщить платформе MVC, какое представление я запросил:
var view = "~/Views/" + partialName + ".cshtml";
return View(view, "~/Views/Shared/_Layout.cshtml", model);
В сообщениях об ошибках говорится: "~/Views/Partial.cshtml" или его мастер не найдены, или никакой движок представления не поддерживает найденные местоположения. Были найдены следующие местоположения: ~/Views/Partial.cshtml ~/Views/Shared/_Layout.cshtml
Когда я пытаюсь загрузить представления отдельно, указав либо:
return View("~/Views/Shared/_Layout.cshtml", model);
или же
return View(view, model);
, правильный вид найден. Однако мне нужно, чтобы они были загружены вместе. Код работает, когда у меня есть все необходимые файлы.cshtml в основном проекте MVC.
Обратите внимание, что представления в скомпилированных DLL имеют PageVirtualPathAttribute с тем же путем, который указан в действии контроллера, например:
namespace SomeBaseNamespace.Views
{
[GeneratedCode("RazorGenerator", "1.5.0.0"), PageVirtualPath("~/Views/Partial.cshtml")]
public class Partial : WebViewPage<PartialModel>
{
[CompilerGenerated]
private static class <Execute>o__SiteContainer3
{
// logic
}
public override void Execute()
{
// logic
}
}
}
Подводя итог, возникает вопрос: как вызвать главное представление, хранящееся в основном проекте MVC, с частично скомпилированным представлением, определенным в другом проекте?
3 ответа
При запуске приложения, когда ваше приложение вызывает эту строку...
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
Сборки, содержащие ваши внешние представления, вероятно, еще не были загружены и поэтому не включены в качестве механизмов просмотра. Я бы на самом деле рекомендовал против использования AppDomain.CurrentDomain.GetAssemblies()
во всяком случае, так как это будет включать в себя все сборки, загруженные при запуске.
Решение заключается в добавлении пакета RazorGenerator.Mvc NuGet в каждый проект, который содержит скомпилированные представления. Это добавит следующий код запуска приложения аналогично вашему...
[assembly: WebActivatorEx.PostApplicationStartMethod(typeof(SomeBaseNamespace.Views.RazorGeneratorMvcStart), "Start")]
namespace SomeBaseNamespace.Views
{
public static class RazorGeneratorMvcStart
{
public static void Start()
{
var engine = new PrecompiledMvcEngine(typeof(RazorGeneratorMvcStart).Assembly)
{
UsePhysicalViewsIfNewer = HttpContext.Current.Request.IsLocal
};
ViewEngines.Engines.Insert(0, engine);
}
}
}
Обратите внимание, как это создает механизм представления, использующий текущую сборку (сборку ваших представлений), и добавляет его к статической ViewEngines
коллекция (содержится в основном проекте MVC).
После запуска я бы также рекомендовал отключить UsePhysicalViewsIfNewer
настройка, которая добавляет значительные потери производительности.
терминология
BaseMvc - с созданными Razor представлениями, контроллерами и т. Д.
ConsumerMvc - имеет макет для этого проекта и ссылки на BaseMvc
Резюме
Создайте доставку представления в базовом контроллере. Представление использует макет, который присутствует в ConsumerMvc через _ViewStart.cshtml в BaseMvc. Для моей ситуации у меня были проекты с разными макетами, отсюда и вид "указатель". Я подумал, что это полезный пример.
Пример BaseMvc
Я создал AREA
чтобы я мог установить макет по умолчанию.
/Areas/Components/Controllers/ShoppingController.cs
public ActionResult Basket()
{
return View();
}
/Areas/Components/Views/Shopping/Basket.cshtml
Welcome to the Basket!
/Areas/Components/Views/_ViewStart.cshtml
@{
//-- NOTE: "Layout_Component.cshtml" do not exist in the BaseMVC project. I did not
// experiment with having it in both projects. A tip if you do is to ensure both
// the base and consumer _Layout_Component.cshtml files are both razor
// generated to allow the razor generator to handle the overrride. See
// my other SO answer linked below.
Layout = "~/Views/Shared/_Layout_Component.cshtml";
}
Ссылка, указанная в комментарии к коду: переопределить представление на сайте ASP.NET MVC не работает
Пример ConsumerMvc
/Views/Shared/_Layout_Component.cshtml
@{
Layout = "~/Views/Shared/_Layout_ConsumerMvc.cshtml";
}
@RenderBody()
Мой URL
http://www.consumermvc.example.com/Components/Shopping/Basket
Не все сборки загружаются при Application_Start
называется. Добавьте дополнительный обработчик:
AppDomain.CurrentDomain.AssemblyLoad += (sender, args) =>
{
// ...
// some code determining whether we've got an assembly with views
// ...
var engine = new PrecompiledMvcEngine(args.LoadedAssembly);
engine.UsePhysicalViewsIfNewer = true;
ViewEngines.Engines.Insert(0, engine);
// StartPage lookups are done by WebPages.
VirtualPathFactoryManager.RegisterVirtualPathFactory(engine);
}