Razor Generator: как использовать представление, скомпилированное в библиотеке, как частичное представление для мастера, определенного в основном проекте MVC

У нас есть приложение ASP.NET MVC 4, содержащее около 3000 представлений. Мы решили разделить этот набор представлений на отдельные библиотеки DLL и скомпилировать его с помощью RazorGenerator. Мы сохраняем только основной _Layout.cshtml и связанные файлы в основном проекте MVC.

Мы не можем загрузить частичные представления из DLL вместе с главным представлением в основном проекте MVC. Подробное описание ниже.

Что уже сделано:

  1. Представления успешно компилируются в библиотеки DLL (я подтвердил, что они находятся в двоичном файле)

  2. Объект 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);
}
Другие вопросы по тегам