Разработка модуля системы: основные понятия
Совершенно недавно было высказано мнение о том, что неплохо бы было остановиться и осмотреть, что было сделано и чем уже готовым можно пользоваться, дабы не изобресть велосипед. По этому поводу пишу я эту статью.
URL - метод навигации по сайту и способ вызова модулей
Основной идеей разработки системы является её модульность. Основная проблема при таком подходе - узнать, когда какой модуль надо запускать. Ядро системы заложено в классе Imin.Web.IminPage и ряде других классов. Класс IminPage занимается разбором строки запроса пришедшей от пользователя и, согласно ей, включением тех или иных пунктов меню, загрузкой определенного модуля.
Для каждого конкретного URL может быть загружен один единственный модуль. Изменение URL как все вы, милостивые государи, понимаете, происходит через обновление страницы. Таким образом, загружаясь, класс IminPage каждый раз загружает только один единственный модуль. Других модулей он даже не создает, не вызывает их методов-конструкторов.
Меню
Строка URL обыкновенно составляется пользователем через использование ссылок. Уделом создавать набор ссылок, объединяющих всю систему наделен модуль TemplateMenu. В этот модуль передаются данные из таблицы MENUS. Таким образом выходит, что за структуру приложения отвечает именно эта таблица. Наибольший интерес для нас в ней представляет теперь 3 поля:
- TYPE - название класса, который необходимо загружать для того, чтобы отобразить пункт меню.
- PARAMS - строка форматированная XML, в каковой указываются всевозможные параметры для класса указанного в TYPE, класс этот, впрочем, может их и не принимать, но в любом случае у него должны быть собственные значения по умолчанию. Список поддерживаемых параметров следует искать в описании класса (как и указывать их там).
- URI - часть строки URL обозначающая пункт меню или, если желаете, конкретный модуль.
За счет таблицы MENUS_TREE пункты меню находятся во взаимном подчинении таким образом, что до конечной ветви дерева подчинения возможно составить путь. Этот путь и станет строкой URL. Промежуточными звеньями в этой цепочке всегда должны быть пункты меню, обозначающие под собой модуль TemplateMenu. Конечным пунктом цепочки будет какой-либо рабочий модуль.
Значения поля TYPE
Всего поле TYPE может принимать 3 вида значения:
- Название класса каковой является модулем;
- Путь к файлу .ascx, который лежит в каталоге сайта. Такой файл будет загружен наподобие модуля;
- строка URL, начинающаяся в HTTP://
Названия классов в поле TYPE, любезные судари и сударыни, надобно писать в полном виде. Вернее сказать необходимо указывать полное имя класса - вместе с именем пространства имен, в котором оный находится. Требование это проистекает из необходимости динамически загружать модули, для такой загрузки и нужно полное имя класса. Впрочем, для удобства было сделано одно исключение. Название классов находящихся в пространстве имен Imin.Web.CoreModules можно писать в сокращенном виде.
Фактически нет разницы в каком месте находится описание класса - это может быть каталог App_Code или одна из загруженных сборок. Загрузчик перед созданием класса проверяет список всех доступных классов из указанных мест и создает первый попавшийся класс с указанным полным именем.
Файл .ASCX, как уже было указано, должен находиться в каталоге сайта. Каталог этот ищется по правилу "/sites/"+доменное имя сайта. Если в этом каталоге файл не найден, то он будет искаться в каталоге "/sites/default".
Логика работы загрузчика такова, что встретив в имени класса HTTP:// загрузчик просто создаст пункт меню указывающий на данный URL, более никаких действий производиться не будет. Такое поведение очень удобно для создания ссылочной целостности сайта.
WebModule - родительский класс
Итак, милостивые государи, модулем информационной системы будем считать класс явно или опосредованно наследующий класс Imin.Web.CoreModules.WebModule. Возможно для очень сложных модулей модель поведения, заложенная в WebModule, окажется излишней, мешающей разработке, в таком случае следует реализовать интерфейс IWebModule. Интерфейс, как таковой, вас ни к чему не принуждает, однако ядро системы будет способно распознать такой класс и правильно его инициализировать.
IWebModule будет особенно полезен разработчикам создающим .ascx-файлы. Поскольку эти файлы должны наследовать класс WebControl, и не могут наследовать WebModule, так как последний WebControl не наследует. Для таких файлов рекомендуется код помещать в отдельный файл. В файле кода для класса можно указать интерфейс IWebModule, таким образом разработчик получит в свое распоряжение Params и ModuleRoot, о которых ниже.
WebModule сам реализовывает IWebModule. В последнем определено 3 члена:
- свойство (XmlParams)Params - для чтения и записи в нем хранится объект дающий доступ к XML-параметрам, записанным в базе данных меню для конкретного пути, по которому инициализируется модуль;
- свойство (String)ModuleRoot - для чтения и записи, в это свойство записывается часть строки URL-запроса к по которому инициализируется модуль;
- метод (void)Install() - этот метод по, моему разумению, должен проводить всевозможные мероприятия по воссозданию рабочей среды для модуля, однако на данный момент его поддержка не реализована и ядро системы его не запускает. На будущее, что называется.
Общая цепочка наследования у WebModule достаточно большая и в некоторый момент приводит к классу Control, что позволяет обращаться с модулем как с обычным контролом, а именно помещать его в коллекцию Controls (например страницы) если понадобится ручное управление модулем.
WebModule и URL
Для ускорения разработки была введена следующая модель модуля. Модуль, как сложная программа, обладает набором состояний. В каждом состоянии модуль предоставляет пользователю определенный интерфейс - то есть определенный набор функций. Такими функциями могут быть, например:
- просмотр списка записей;
- редактирование одной записи;
- добавление записи;
- детальный просмотр записи и т.п.
Переключение между состояниями производится за счет изменения строки URL. Да, милостивые государи, для этого необходимо перегрузить страницу. Такой метод переключения оказывается удобным при разработке, достаточно узнать какой URL, чтобы показать ту или иную функциональность. Не нужно хранить никаких переменных в сесии или гонять данные в скрытых полях.
Однако, обо всем по порядку. Как мы уже отмечали URL уже используется для переключения модулей, куда же боле, подумаете вы. Но все просто, для отделения, что называется, зерен от плевел было введено свойство ModuleRoot модуля. Когда модуль загружается этому свойству задается начало URL, которое послужило поводом для его загрузки, вся же оставшаяся строка остается на попечении самого модуля. Ежели вычесть из всей строки URL ту часть, что относится непосредственно к модулю ModuleRoot, то мы получим строку, которая является основанием для выбора состояния модуля.
Приведем небольшой пример.
Допустим у нас есть строка http://mineralogy.ru/articles/article/43
В таблице меню у нас прописано, что:
- для / (корень) - нужно загружать модуль TemplateMenu
- для articles загружать модуль ArticleChanel и поскольку это не TemplateMenu то всякое дальнейшее перебирание модулей прекращается.
Таким образом составляется строка http://mineralogy.ru/articles - это и есть ModuleRoot, остаток, а именно /article/43 - передается в модуль как параметр.
Следить за строкой URL! Разбирать какие-то параметры!!! Так возможно подумать после осмысление написанного. Однако, милостивым государям следует внимательно прочитать следующее, чтобы понять насколько всё просто и удобно.
Собственно следить за строками URL необходимости нет. За ними следит ядро системы. А вот за остатком от строки, передаваемой в модуль, следит сам модуль. Но не все так сложно как кажется. Если вы наследуюте от WebModule, то в нем уже все за вас продумано. В момент цикла CreateChildControls WebModule разбирает остаток строки. Результатом разбора является выделение первого слова из остатка (если остаток пуст, т.е. наличествует ситуация "корня модуля", то таковой строкой всегда будет слово "SHOW"). Это первое слово используется для динамического (через рефлексию) вызова метода с именем составленным по правилу "Mode_" + найденное слово. Для корня модуля или, если пожелаете, метод по умолчанию Mode_Show().
Вернемся к нашему примеру. Мы имеем остаток /article/43, следуя описанной логике будет вызван метод Mode_Article(). Уже метод Mode_Article() анализирует остаток строки и приходит к выводу, что цифра 43 - это ID статьи, которую необходимо отобразить. Вот мы собственно и пришли к одному из вариантов функциональности - детальный просмотр записи.
Подобных состояний может быть неограниченное количество, каждое состояние может ссылаться на другие состояния посредством простых ссылок, составленных с учетом ModuleRoot. А параметры в остаточной строке могут быть весьма разнообразными.
Строка URL - что еще нужно знать
Разрабатывая модуль важно помнить о нескольких моментах касающихся URL.
- Составляя систему параметров и состояний, которые влияют на строку URL, как описано выше, важно не переусердствовать. Помните, строка запроса в браузере ограничена определенной, конечной длиной.
- Строка URL приходит от пользователя, с пользовательской стороны. Поэтому категорически не доверяйте тем параметрам которые в ней приходят. Не надо считать, что злоумышленник не поймет что там в ней закодировано. Имейте в виду данные полученные из URL могут содержать SQL запросы (это называется SQL-инъекция). Всегда проверяйте права пользователя.
- Url - в общем случае (для модулей всегда) заканчивается на default.aspx или page#.aspx (где # - номер страницы, используется при постраничном выводе). Если ни одного из этих окончаний нет, то подразумевается default.aspx. Прочие "имена файлов" приписывать нельзя.
Это что касалось безопасности. А теперь о приятном. Разбирать строку-остаток вручную вовсе не нужно. Достаточно в методе Mode_*() указать параметр типа WebModuleParams. Если этот параметр указан, то WebModule обязательно передаст его в вашу функцию. Задача класса WebModuleParams - это разбор строки-остатка.
Действует он очень просто. В нашем примере в модуль ArticleChanel пришла строка-остаток /article/43, был вызван метод Mode_Article().
Давайте определим в коде метод:
void Mode_Article(WebModuleParams UrlParams) { }
Тогда будет возможно сделать так:
void Mode_Article(WebModuleParams UrlParams) { String article_id = UrlParams["Article"]; Int32 id = Int32.Parse(article_id); вот мы и получили нужное нам значение }
Логика разбора строки URL проста. Принимается, что строка url имеет следующий формат: /ключ/значение/ключ2/значение2 и т.д.. Если рассматривать эту строку, то UrlParams["ключ2"] вернет нам "занчение2". Удобно, правда? Но не забывайте о двух вещах:
- Злоумышленники не дремлют и полученные значения нельзя вставлять в sql-запрос кроме как через параметры.
- Выражение UrlParams["значение"] вернет нам "ключ2". Имейте это в виду, когда будете составлять имена параметров.
Вместо заключения
Это далеко не все возможности. Есть еще что описывать. Есть еще что рассматривать, поэтому ждите следующий статей. В них, милоствые государи, мы рассмотрим процесс создания простенького модуля на примерах. Отдельной статьей осветим вопросы организации поиска.