ASP.NET Core RC2: встроенная поддержка модульности (application parts)

Будучи исторически погруженным в вопросы разработки модульных приложения на ASP.NET, первое что я сделал, когда вышел ASP.NET Core RC2 — постарался перевести на него свой модульный фреймворк ExtCore. И вот тут оказалось, что в новой версии все изменилось и старые подходы из RC1 больше не работают, зато появились новые интересные возможности, о которых я и хочу рассказать.

Если коротко, то разработка модульных приложений в RC2 очень упрощена. Благодаря новой возможности «части приложения» (application parts), вы легко можете разделить свой большой проект на несколько более мелких и затем свободно компоновать их. Особенно это удобно при работе с областями (areas), которые и так изолируют набор контроллеров, представлений и прочих ресурсов — каждую область теперь можно выделить в отдельный проект. Насколько я понял (в частности, из aspnet/Mvc#4089), реализация ориентирована именно на разделение большого проекта на маленькие и только в части MVC. Остальное все-таки придется писать самому.

Реализация


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

3340667e08e345378e4531e54fa1a45d.png

На следующем шаге выбираем «Веб-приложение», чтобы Visual Studio создала для нас готовое к тестированию приложение:

51b089fb2df04fc1819385a1d979e2f7.png

Вот и все. Теперь запустим наше новое приложение:

9856852cbb8e44ae9e903a2b914c13f0.png

Я не буду останавливаться на структуре проекта и на отличиях структуры от того, к чему мы привыкли в RC1. При желании, можно посмотреть вот это.

Теперь добавим еще один проект в наше решение, на этот раз библиотеку классов:

ab97ff85915143879e6007d83f47a4e5.png

Т. к. мы хотим посмотреть на работу контроллера и представления из нашей сборки, добавим в файл project.json ссылку на MVC. Также нам необходимо, чтобы представления в этом проекте были добавлены в сборку в виде ресурсов. Это делается при помощи соответствующей настройки в разделе buildOptions файла project.json. В результате получим такой файл:

{
  "buildOptions": { "embed": [ "Views/**" ] },
  "dependencies": {
    "Microsoft.AspNetCore.Mvc": "1.0.0-rc2-final",
    "NETStandard.Library": "1.5.0-rc2-24027"
  },
  "frameworks": {
    "netstandard1.5": {
      "imports": "dnxcore50"
    }
  },
  "version": "1.0.0-*"
}

Теперь создадим в нашем проекте новый контроллер с единственным методом (для единообразия файл с классом контроллера желательно поместить в папку Controllers, хотя это и необязательно):

public class ModuleAController : Controller
{
  public ActionResult Index()
  {
    return this.View();
  }
}

Теперь в папке \Views\ModuleA создадим представление Index.cshtml с таким содержимым, которое вам нравится.

Проект готов. Соберем его. В папке с проектом появится папка bin (как в прошлых версиях ASP.NET), а в ней — наша сборка. Осталось только рассказать о ней основному приложения.

Откроем класс Startup проекта нашего приложения и перейдем к методу ConfigureServices. Первым делом загрузим нашу сборку с контроллером и представлением:

Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(@"абсолютный путь к сборке");

Далее, добавим загруженную сборку в качестве части приложения в MVC:

services.AddMvc().AddApplicationPart(assembly);

Практически все. Если сейчас запустить приложение и перейти по адресу /modulea, мы получим исключение: InvalidOperationException: The view 'Index' was not found. Чтобы объяснить MVC, что искать представления необходимо и внутри нашей сборки, добавим соответствующий провайдер файлов в настройках Razor. Модифицируем предыдущую строчку кода, чтобы получилось вот так:

services.AddMvc().AddApplicationPart(assembly).AddRazorOptions(
  o =>
  {
    o.FileProviders.Add(new EmbeddedFileProvider(assembly, assembly.GetName().Name));
  }
);

Наше приложение, состоящее из двух частей, готово. Запустим его, перейдем по адресу /modulea:

bd999865fd7a44f3b005715aaf040b21.png

Очень здорово. Еще в RC1 для этого потребовалось бы больше кода. Но этого достаточно только до тех пор, пока вы не захотите использовать строго типизированные представления. Если добавить класс модели вида в наш проект, и затем указать его в качестве модели для нашего представления, во время выполнения мы получим исключение: The type or namespace name 'ModuleA' does not exist in the namespace 'AspNetCoreApplicationParts'. Связано это с тем, что наша сборка не входит в набор сборок, в котором Razor ищет типы при компиляции представлений. К счастью, есть достаточно простой способ это исправить. Кроме того, в ближайшем будущем в этом шаге не будет необходимости, т. к. сборки, добавленные как части приложения, будут участвовать в Razor-компиляции автоматически.

Модифицируем вызов функции AddRazorOptions, которую мы использовали на предыдущем шаге, таким образом:

.AddRazorOptions(
  o =>
  {
    o.FileProviders.Add(new EmbeddedFileProvider(assembly, assembly.GetName().Name));

    Action previous = o.CompilationCallback;

    o.CompilationCallback = c =>
    {
      if (previous != null)
      {
        previous(c);
      }

      c.Compilation = c.Compilation.AddReferences(reference);
    };
  }
);

Осталось объявить переменную reference где-то перед загрузкой сборки:

PortableExecutableReference reference = MetadataReference.CreateFromFile(@"абсолютный путь к сборке");

Вот и все. Теперь мы можем использовать нашу модель вида. Запустим приложение и перейдем по адресу /modulea:

047ca8a438c7410e86e2fabbe53630e2.png

Кстати, еще в RC1 можно было использовать предварительную компиляцию представлений и не иметь проблем с разрешением типов моделей видов во время выполнения. К сожалению, в RC2 предварительная компиляция не поддерживается (насколько я понял, из-за сложности реализации), но в будущем будет возвращена.

Результат


Пожалуй, application parts — именно то, чего давно не хватало ASP.NET. Я потратил много времени, чтобы добиться аналогичного результата в предыдущих версиях (еще до ASP.NET Core). Надеюсь, приведенного примера вполне достаточно, чтобы начать использовать эту возможность. И спасибо ребятам из нашего чата в Gitter, вместе с которыми мы разбирались с RC2.

Весь проект целиком (в немного упрощенном виде) доступен на GitHub.

© Habrahabr.ru