Готовим ASP.NET Core: создаем свой кроссплатформенный модульный фреймворк

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

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

57b2c39241ed46259315ba7d0d5177b1.jpg

Основные возможности


ExtCore умеет обнаруживать и использовать типы (а также, представления и статический контент), определенные как в проектах (в виде исходного кода или NuGet-пакетов), на которые есть явные ссылки в зависимостях, так и в проектах, которые размещены в определенной папке в виде скомпилированных DLL-сборок.

Для удобства, опционально, эти проекты могут быть условно объединены в расширения (т. е. каждое расширение может состоять из одного или многих проектов). Также, каждое расширение может иметь класс (в одном из проектов), реализующий интерфейс IExtension, а ExtCore, в свою очередь, позволяет в любой момент получить доступный набор экземпляров всех классов, реализующих этот интерфейс. Эти классы можно использовать для предоставления метаданных, описывающих расширения, инициализации расширений (например, регистрации маршрутов) и т. д.

Хотя непосредственно сам фреймворк и не содержит какого-либо функционала, связанного с работой с данными, но все, что для этого необходимо (в т. ч. единый контекст хранилища для всех расширений), реализовано в расширении ExtCore.Data, о котором ниже.

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


Как это работает


Во время запуска приложения ExtCore обнаруживает все сборки (те, на которые есть явные ссылки, и те, на которые явных ссылок нет, но которые размещены в определенной папке). Отбираются и загружаются лишь сборки, которые содержат ссылку на ExtCore.Infrastructure.

Далее, все обнаруженные сборки, соответствующие критериям отбора, помещаются в класс-контейнер ExtensionManager, откуда они могут быть получены в любой момент откуда угодно (т. е. как из основного проекта приложения, так и из любого другого проекта). Эти сборки «регистрируются» как источники для поиска контроллеров (все контроллеры из сборок, на которые есть явные ссылки, обнаруживаются ASP.NET автоматически, а вот об остальных нужно сообщить дополнительно, «вручную»), представлений (и в виде ресурсов, и предварительно скомпилированных) и статического контента.

После этого производится выполнение ряда методов IExtension среди экземпляров доступных реализаций этого интерфейса. Так расширения получают возможность выполнить свой код во время выполнения методов ConfigureServices, Configure и т. д.

Рекомендуемая структура расширения


Для упрощения понимания и сохранения единообразия, удобно придерживаться следующей структуры при разработке собственных расширений (где X — название вашего расширения):

  • YourApplication.X;
  • YourApplication.X.Frontend;
  • YourApplication.X.Backend;
  • YourApplication.X.Data.Models;
  • YourApplication.X.Data.Abstractions;
  • YourApplication.X.Data.SpecificStorageA;
  • YourApplication.X.Data.SpecificStorageB;
  • YourApplication.X.Data.SpecificStorageC;
  • и так далее.


YourApplication.X

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

YourApplication.X.Frontend и YourApplication.X.Backend

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

ExtCore поддерживает два варианта для работы с представлениями (одновременно): представления в виде ресурсов и предварительно скомпилированные представления. Если кратко, я рекомендую использовать второй вариант. Более подробно о методах работы с представлениями, примененными в ExtCore, можно почитать в статье «Готовим ASP.NET Core: поговорим про нестандартные подходы при работе с представлениями» (https://habrahabr.ru/company/microsoft/blog/276061/).

Работа со статическим контентом также не вызывает затруднений. В статье «Готовим ASP.NET Core: как представить статический контент в виде ресурсов» (https://habrahabr.ru/company/microsoft/blog/277235/) подробно описан используемый в ExtCore метод. Если резюмировать статью, то необходимый статический контент помещается в сборки в виде ресурсов и затем эти сборки «регистрируются» как поставщики файлов (file providers), что делает возможным использование ресурсов практически как обычных физических файлов (в т. ч. с прямым доступом по HTTP).

Интересно, что областью видимости как представлений, так и статического контента является все приложение. Это означает, что одни расширения могут использовать содержимое других расширений и наоборот. Более того, расширения могут использовать представления и статический контент из основного веб-приложения. Таким образом можно, например, описать логику в проектах расширения, но дать возможность пользователю определить то, как все будет выглядеть, прямо в основном приложении.

YourApplication.X.Data.*

Проекты, описывающие работу с данными. Более подробно об этом ниже.

Работа с данными

ExtCore дополнительно имеет опциональное расширение ExtCore.Data, реализующее весь необходимый базовый функционал для работы с данными. Центральным элементом расширения является интерфейс IStorage (единица работы). Этот интерфейс описывает всего 2 метода: GetRepository и Save. Реализация GetRepository обнаруживает и возвращает доступный экземпляр репозитория, реализующего запрошенный интерфейс (а также, IRepository) и следит за тем, чтобы все репозитории имели единый контекст хранилища (интерфейс IStorageContext).

Если требуется какая-то регистрация типов сущностей из различных расширений (как, например, в случае с EntityFramework), это может быть легко достигнуто при помощи чего-то вроде интерфейса IModelRegistrar, который применяется в ExtCore.Data.EntityFramework.Sqlite и ExtCore.Data.EntityFramework.SqlServer. Тестовый проект (ссылка на него в конце статьи) иллюстрирует этот подход.

Структура расширения следующая:

  • ExtCore.Data;
  • ExtCore.Data.Models.Abstractions;
  • ExtCore.Data.Abstractions;
  • ExtCore.Data.EntityFramework.Sqlite;
  • ExtCore.Data.EntityFramework.SqlServer.


ExtCore.Data

Проект содержит класс, реализующий интерфейс IExtension. Этот класс при запуске приложения выполняет код, который обнаруживает доступную реализацию интерфейса IStorage (см. ниже) и регистрирует ее во встроенном в ASP.NET Core DI, чтобы любой контроллер затем смог получить ее экземпляр при необходимости. Удобно, что можно изменять тип поддерживаемого хранилища путем простого изменения ссылки в зависимостях или копирования DLL-файла.

ExtCore.Data.Models.Abstractions

Проект описывает интерфейс IEntity, который должны реализовать все сущности во всех расширениях.

ExtCore.Data.Abstractions

Проект описывает ключевые интерфейсы IStorageContext, IStorage и IRepository, о которых я рассказал выше. В собственных расширениях в проекты с именами вроде YourApplication.X.Data.Abstractions я помещаю интерфейсы репозиториев, чтобы затем работать через них без привязки к реализации конкретного хранилища.

ExtCore.Data.EntityFramework.Sqlite и ExtCore.Data.EntityFramework.SqlServer

Проект содержит классы, реализующие интерфейсы из ExtCore.Data.Abstractions для конкретных хранилищ (в данном случае это Sqlite и SqlServer).

Выводы


В настоящий момент — это лишь альфа-версия. У меня есть много планов по развитию проекта (подключение и отключение расширений в любой момент и общая оптимизация; также хочу поработать над классом ExtensionManager, добавить удобную возможность получения доступных реализаций заданных интерфейсов, а не только IExtension). Я использую этот фреймворк в своем проекте (CMS). Насколько могу судить — получилось удобно и гибко. Буду рад услышать замечания и критику. Спасибо!

Вот ссылка на исходники: https://github.com/ExtCore/ExtCore.

А вот ссылка на готовый тестовый проект: https://github.com/ExtCore/ExtCore-Sample.

Авторам


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

afcbc1126db747c8977cf60207b9c6fa.jpg

Об авторе


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

© Habrahabr.ru