Модульное Приложение На Asp.net 5

Довольно долгое время при разработке сайтов для своих клиентов я использовал собственную простую CMS (Platformus).

Он был написан на ASP.NET+MVC и имел закрытый исходный код. С появлением первой бета-версии нового ASP.NET 5 я решил переписать свою систему на этой технологии, чтобы сделать ее кроссплатформенной и в конечном итоге опубликовать на GitHub. Поскольку технология очень новая, информации по этому вопросу практически не было, поэтому решение некоторых проблем было найдено либо случайно, либо в процессе изучения исходных кодов самой ASP.NET 5. Чтобы упростить задачу, я подготовил и разместил на GitHub специальное тестовое решение — AspNet5ModularApp .

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



Модульное приложение на ASP.NET 5



Решение основных проблем

Если опустить вопросы, специфичные для разработки CMS, то главной задачей для меня была (и остается, кстати, до сих пор) грамотная модульная архитектура проекта.

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

В этом случае его единственная задача — найти, загрузить и инициализировать все расширения.

Расширения, в свою очередь, должны реализовывать некий общий интерфейс и могут содержать, по сути, что угодно.

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

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

подробно опишу это ниже.

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

из сборки, на которую нет явных ссылок в файле project.json основного веб-приложения).

и который загружался из каталога с расширениями после запуска).

С контроллерами проблем не было.

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

AspNet5ModularApp.ExtensionAssemblyProvider ), а затем просто зарегистрируйте новую реализацию в ConfigurationServices (см.

AspNet5ModularApp.Startup ).

Единственное, что по умолчанию MVC ищет контроллеры в сборках, которые ссылаются на что-то вроде Microsoft.AspNet.Mvc. Так как в моем случае почти все проекты ссылаются на эту сборку (это плохо, но ниже я описал почему пока так), то поиск происходит во всех них, что безусловно негативно влияет на производительность, поэтому в любом случае это необходимо исправить (хотя, конечно, в нашем тестовом решении проблем с производительностью не наблюдается).

Пришлось немного повозиться с представлениями.

Насколько я сейчас знаю, вы можете либо создавать ресурсы представлений (добавив, например, строку "resource": "Views/**" в project.json), либо использовать предварительную компиляцию представлений (добавив RazorPreCompilation класс, который наследуется от RazorPreCompileModule).

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

Ну и все ошибки в представлениях тоже становятся очевидными уже на этапе компиляции.

Основное веб-приложение нашего тестового решения — AspNet5ModularApp — одновременно поддерживает оба этих варианта, соответствующее поведение задается с помощью функций AddPrecompiledRazorViews и AddRazorOptions (см.

Стартап.

cs ).

Если для работы предкомпилированных представлений достаточно просто установить содержащие их сборки, то с представлениями ресурсов необходимо реализовать интерфейс IFileProvider (см.

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

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

Как только содержащие их сборки будут добавлены в CompositeFileProvider, они будут доступны по путям, по которым они расположены в своих сборках.

ExtensionA иллюстрирует первый вариант (с представлениями ресурсов), а ExtensionB иллюстрирует второй (с предварительно скомпилированными представлениями).

Здесь также необходимо рассказать о паре проблем, решение которых я пока не нашел.

Во-первых, разные части ASP.NET 5 используют разные версии сборок System.*, поэтому если вы подключите, например, Microsoft.AspNet.Mvc в одном проекте, а попытаетесь подключить System.Runtime в другом, то вы можете получить ошибку.

что разные версии одной и той же библиотеки.

На данный момент AspNet5ModularApp построен на 8-й бета-версии ASP.NET 5, поэтому я думаю, что это будет исправлено к выпуску.

Пока я просто прописал Microsoft.AspNet.Mvc во всех проектах (кроме тех, которые работают с Entity Framework — там достаточно самого Entity Framework), чтобы получить одинаковый набор зависимостей.

Согласен, это очень плохо, но это позволило мне не мелочиться.

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

Например, мне пришлось поместить System.Reflection.dll и System.Reflection.TypeExtensions.dll в каталог с расширениями (иначе получаю исключение при попытке загрузить сборки, имеющие указанные выше зависимости).

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

dnx в GAC, но мне кажется, что это неправильно.

) Затем я начал понимать данные и их хранение.

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

В проекте AspNet5ModularApp.Models.Abstractions Я определил базовый интерфейс для модели — IEntity. В проекте AspNet5ModularApp.Data.Abstractions Я определил 3 основных интерфейса — IStorageContext, IStorage и IRepository. Их цель лучше всего иллюстрирует проект AspNet5ModularApp.Data.EF.Sqlite , который содержит реализации этих интерфейсов для работы с базой данных Sqlite с использованием Entity Framework 7. Также в этом проекте определен интерфейс IModelRegistrar, который позволяет расширениям регистрировать свои модели в едином контексте (см.

AspNet5ModularApp.Data.EF.Sqlite.StorageContext.OnModelCreating ).

Общий принцип работы следующий.

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

Проект с контроллерами и представлениями знает о моделях репозитория и абстракциях, но не знает об их реализации для конкретных источников данных.

Таким образом, в конструкторе контроллера можно попросить встроенный DI ASP.NET 5 предоставить доступный экземпляр IStorage и в дальнейшем запросить у него доступную реализацию определенного репозитория, используя его интерфейс.

(Разумеется, чтобы встроенный DI нашел доступную реализацию IStorage, ему нужно об этом сообщить, что и делается в AspNet5ModularApp.ExtensionB.ExtensionB.ConfigureServices .

Чтобы не делать это в каждом расширении, в Платформусе я вынес общий функционал в отдельное расширение — Barebone.)

Результат

Что произошло в результате.

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

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

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

Вы также можете значительно повысить производительность, добавив кеширование в механизмы поиска типов.

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



Ссылки и спасибо

Связь в приложении AspNet5ModularApp. Я хотел бы поблагодарить пользователя GitHub github.com/leo9223 , который мне очень помог, показав мне это проект .

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

Теперь я уже знаю почему.

При создании EmbeddedFileProvider из сборок с расширениями базовые пространства имен не были указаны, поэтому представления не удалось найти.

Я нашел подсказку здесь .

Еще мне помогли ответы на мои вопросы на GitHub от разработчиков ASP.NET, спасибо им за терпение и внимание.

Теги: #asp.net 5 #mvc 6 #модульная система #Разработка веб-сайтов #.

NET #ASP #ASP

Вместе с данным постом часто просматривают:

Автор Статьи


Зарегистрирован: 2019-12-10 15:07:06
Баллов опыта: 0
Всего постов на сайте: 0
Всего комментарий на сайте: 0
Dima Manisha

Dima Manisha

Эксперт Wmlog. Профессиональный веб-мастер, SEO-специалист, дизайнер, маркетолог и интернет-предприниматель.