Готовим ASP.NET Core: поговорим про нестандартные подходы при работе с представлениями

в 8:24, , рубрики: .net, #aspnetcolumn, ASP, ASP.NET, asp.net core, razor, views, Блог компании Microsoft, представления

Мы продолжаем нашу колонку по теме ASP.NET Core публикацией от Дмитрия Сикорского ( DmitrySikorsky) — руководителя компании «Юбрейнианс» из Украины. В своей очередной статье Дмитрий рассказывает об опыте нестандартной работы с представлениями в ASP.NET Core. Предыдущие статьи из колонки всегда можно прочитать по ссылке #aspnetcolumn — Владимир Юнев

В последнее время я много работал над своим модульным фреймворком для ASP.NET 5 (теперь уже ASP.NET Core 1.0). В рамках этого проекта пришлось решать различные задачи, и одной из них была работа с представлениями, которые находятся либо просто в нестандартных местах, либо вообще вне основной сборки веб-приложения. В этой статье я попробую рассказать, какие у вас есть варианты, если вам необходимо нечто подобное.

Готовим ASP.NET Core: поговорим про нестандартные подходы при работе с представлениями - 1

Представления в нестандартных местах внутри основной сборки веб-приложения

Если вдруг по какой-то причине ваши представления оказались вне положенной им папки Views (но, при этом, все-таки остались внутри основного проекта приложения), вам потребуется сообщить об этом Razor’y. Если раньше для этого пришлось бы написать класс, производный от RazorViewEngine, то сейчас сделать это немного проще.

aspnetcolumngithubСовет! Вы можете попробовать все самостоятельно или загрузив исходный код из GitHub https://github.com/DmitrySikorsky/AspNet5Views.

Во-первых, реализуем интерфейс IViewLocationExpander (напомню, что «{1}» в строковой константе будет заменено на имя контроллера, а «{0}» – на имя действия):

public class CustomViewLocationExpander : IViewLocationExpander
  {
    public IEnumerable<string> ExpandViewLocations(ViewLocationExpanderContext context, IEnumerable<string> viewLocations)
    {
      List<string> expandedViewLocations = new List<string>();

      expandedViewLocations.AddRange(viewLocations);
      expandedViewLocations.Add("/Views/SomeExtraFolder/{1}/{0}.cshtml");
      return expandedViewLocations;
    }

    public void PopulateValues(ViewLocationExpanderContext context)
    {
    }
  }

Затем «зарегистрируем» экземпляр полученного класса в методе ConfigureServices:

services.Configure<RazorViewEngineOptions>(options =>
        {
          options.ViewLocationExpanders.Add(new CustomViewLocationExpander());
        }
);

Вот и все, теперь Razor примет к сведению наше новое расположение представлений.

Представления в виде ресурсов в других сборках

На самом деле, я не знаю о преимуществах (но знаю о недостатках!) этого подхода перед следующим, но все-равно считаю необходимым о нем рассказать.

Чтобы представления были помещены в сборку в качестве ресурсов, необходимо добавить в project.json соответствующего проекта следующую строку (на самом деле, таким образом в ресурсы превращаются не только представления):

«resource»: «Views/**»

Чтобы такие представления были затем обнаружены Razor’ом, необходимо реализовать интерфейс IFileProvider и присвоить экземпляр полученного класса соответствующему свойству в методе ConfigureServices в основном проекте:

services.Configure<RazorViewEngineOptions>(options =>
        {
          options.FileProvider = this.GetFileProvider(this.applicationBasePath);
        }
);

Метод GetFileProvider создает экземпляр нашего класса CompositeFileProvider, который, по сути, просто объединяет несколько различных провайдеров (в нашем случае это физические файлы в папке с основным проектом и ресурсы из указанных сборок).

Самый главный недостаток данного подхода в том, что мы не имеем возможности использовать для типизации представлений типы, определенные в сборках, на которые нет явных ссылок в основном проекте. Например, нельзя использовать типы, которые определены в проекте, в котором находятся сами представления. Это происходит из-за того, что представления в виде ресурсов компилируются уже во время исполнения и, соответственно, типы их моделей не могут быть найдены. Это не проблема, если есть возможность добавить зависимости явно, но если требуется подгружать сборки динамически, во время исполнения, то сделать этого нельзя. Следующий подход решает эту проблему.

(На самом деле, насколько я понял, это все-таки можно решить. Я обсуждал этот вопрос тут: https://github.com/aspnet/Mvc/issues/3413, но далее этого обсуждения дело пока не зашло.)

Предварительно скомпилированные представления в других сборках

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

Как и в предыдущем случае, при использовании предварительно скомпилированных представлений вам не потребуется копировать их на сервер при публикации, что значительно ускоряет этот процесс (вместо множества cshtml-файлов копируется лишь один dll-файл). Но, в отличии от представлений в виде ресурсов, такие представления, как понятно из названия, компилируются на этапе сборки проекта, а не во время исполнения, что, во-первых, позволяет заранее выявить все ошибки в них, а во-вторых, заметно экономит время при запуске приложения при обращении к конкретным страницам.

Чтобы заставить компилятор компилировать представления, достаточно в соответствующем проекте создать класс RazorPreCompilation, унаследовать его от RazorPreCompileModule, переопределить в нем метод EnablePreCompilation так, чтобы он возвращал true, и в конце концов поместить его в папку CompilerPreProcess:

public class RazorPreCompilation : RazorPreCompileModule
{
    protected override bool EnablePreCompilation(BeforeCompileContext context) => true;
}

В основном проекте приложения, чтобы сообщить Razor’у, что он должен использовать предварительно скомпилированные представления из других сборок, необходимо просто передать набор этих сборок в соответствующий метод:

services.AddMvc()
        .AddPrecompiledRazorViews(
          new Assembly[] { Assembly.Load(new AssemblyName("AspNet5Views.PrecompiledViews")) }
);

Этого достаточно, чтобы все заработало.

Как я писал выше, благодаря тому, что представления компилируются на этапе сборки всего проекта, мы имеем возможность типизировать их типами, на которые в основном проекте приложения ссылок нет.

Выводы

В общем, все достаточно просто. У двух последних подходов есть существенный недостаток, а именно, необходимость перезапуска всего приложения для изменения, например, даже самой незначительной детали в верстке. Но, с другой стороны, это все-равно гораздо удобнее, чем копировать представления из всех частей приложения в основной проект, если уж вам необходимо разделить приложение на части.

Я подготовил небольшой тестовый проект, чтобы можно было самостоятельно попробовать все, что описано в этой статье: https://github.com/DmitrySikorsky/AspNet5Views.

Авторам

Друзья, если вам интересно поддержать колонку своим собственным материалом, то прошу написать мне на vyunev@microsoft.com для того чтобы обсудить все детали. Мы разыскиваем авторов, которые могут интересно рассказать про ASP.NET и другие темы.

Готовим ASP.NET Core: поговорим про нестандартные подходы при работе с представлениями - 3

Об авторе

Сикорский Дмитрий Александрович
Компания «Юбрейнианс» (http://ubrainians.com/)
Владелец, руководитель
DmitrySikorsky

Автор: Microsoft

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js