ASP.NET Core RC2: встроенная поддержка модульности (application parts)
Будучи исторически погруженным в вопросы разработки модульных приложения на ASP.NET, первое что я сделал, когда вышел ASP.NET Core RC2 — постарался перевести на него свой модульный фреймворк ExtCore. И вот тут оказалось, что в новой версии все изменилось и старые подходы из RC1 больше не работают, зато появились новые интересные возможности, о которых я и хочу рассказать.
Если коротко, то разработка модульных приложений в RC2 очень упрощена. Благодаря новой возможности «части приложения» (application parts), вы легко можете разделить свой большой проект на несколько более мелких и затем свободно компоновать их. Особенно это удобно при работе с областями (areas), которые и так изолируют набор контроллеров, представлений и прочих ресурсов — каждую область теперь можно выделить в отдельный проект. Насколько я понял (в частности, из aspnet/Mvc#4089), реализация ориентирована именно на разделение большого проекта на маленькие и только в части MVC. Остальное все-таки придется писать самому.
Реализация
Для примера, создадим небольшое приложение и посмотрим, как все работает (предполагается, что вы уже зашли сюда и установили все, что нужно). Итак, создаем проект:
На следующем шаге выбираем «Веб-приложение», чтобы Visual Studio создала для нас готовое к тестированию приложение:
Вот и все. Теперь запустим наше новое приложение:
Я не буду останавливаться на структуре проекта и на отличиях структуры от того, к чему мы привыкли в RC1. При желании, можно посмотреть вот это.
Теперь добавим еще один проект в наше решение, на этот раз библиотеку классов:
Т. к. мы хотим посмотреть на работу контроллера и представления из нашей сборки, добавим в файл 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:
Очень здорово. Еще в 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:
Кстати, еще в RC1 можно было использовать предварительную компиляцию представлений и не иметь проблем с разрешением типов моделей видов во время выполнения. К сожалению, в RC2 предварительная компиляция не поддерживается (насколько я понял, из-за сложности реализации), но в будущем будет возвращена.
Результат
Пожалуй, application parts — именно то, чего давно не хватало ASP.NET. Я потратил много времени, чтобы добиться аналогичного результата в предыдущих версиях (еще до ASP.NET Core). Надеюсь, приведенного примера вполне достаточно, чтобы начать использовать эту возможность. И спасибо ребятам из нашего чата в Gitter, вместе с которыми мы разбирались с RC2.
Весь проект целиком (в немного упрощенном виде) доступен на GitHub.