Довольно долгое время при разработке сайтов для своих клиентов я использовал собственную простую CMS (Platformus).
Он был написан на ASP.NET+MVC и имел закрытый исходный код. С появлением первой бета-версии нового ASP.NET 5 я решил переписать свою систему на этой технологии, чтобы сделать ее кроссплатформенной и в конечном итоге опубликовать на GitHub. Поскольку технология очень новая, информации по этому вопросу практически не было, поэтому решение некоторых проблем было найдено либо случайно, либо в процессе изучения исходных кодов самой ASP.NET 5. Чтобы упростить задачу, я подготовил и разместил на GitHub специальное тестовое решение — AspNet5ModularApp .
В этой статье я в основном буду опираться на него, но также коснусь некоторых методов и идей, которые использовал в Platformus (частично в надежде получить о них отзывы).
Решение основных проблем
Если опустить вопросы, специфичные для разработки CMS, то главной задачей для меня была (и остается, кстати, до сих пор) грамотная модульная архитектура проекта.Я решил, что основное веб-приложение не должно иметь никаких контроллеров, представлений или ресурсов (скриптов, стилей, изображений и т. д.), не должно знать о данных или способах их хранения, но должно знать о каталоге с расширениями (набором сборки).
В этом случае его единственная задача — найти, загрузить и инициализировать все расширения.
Расширения, в свою очередь, должны реализовывать некий общий интерфейс и могут содержать, по сути, что угодно.
(Но, поскольку практически каждое расширение в моем случае будет работать с данными, я ввел дополнительный уровень абстракции, описывающий этот механизм.
Благодаря этому все расширения могут работать с данными единообразно и в едином контексте, что весьма важно.
подробно опишу это ниже.
) В большинстве случаев расширения должны содержать контроллеры и представления, поэтому первое, что я попробовал, — заставить контроллер и представление работать из динамически загружаемой сборки (т. е.
из сборки, на которую нет явных ссылок в файле project.json основного веб-приложения).
и который загружался из каталога с расширениями после запуска).
С контроллерами проблем не было.
Достаточно просто реализовать интерфейс IAssemblyProvider, скопировать сборки из DefaultAssemblyProvider и добавить в них те сборки, которые загружаются динамически из каталога расширений (см.
AspNet5ModularApp.ExtensionAssemblyProvider ), а затем просто зарегистрируйте новую реализацию в ConfigurationServices (см.
Единственное, что по умолчанию 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
-
Задача Программирования О Розничной Торговле
19 Oct, 24 -
Как Добавить Функциональность Firefox В Ie7
19 Oct, 24 -
Космические Сверхзвуковые Парашюты
19 Oct, 24 -
Как Запустить Интернет-Радио Прямо На Сайте?
19 Oct, 24