| « Поставить закладку » « Сделать стартовой » | |||
|
|||
|
Создание форума в ASP.NET
ВведениеФорумы – они же ньюс-группы и эхо-конференции, – пожалуй, один из наиболее привлекательных сервисов, которые может предложить веб-ресурс своим посетителям. Огромная популярность некоторых сайтов рунета (думаю, читатель и сам догадается, о чем речь) не в последнюю очередь объясняется тем, что на этих сайтах можно найти интересные и профессионально сделанные форумы с высокой посещаемостью, где всегда смогут помочь дельным советом. Ясно, что популярность того или иного форума не в последнюю очередь объясняется качеством его реализации – если форум сделан слишком примитивно, это может отпугнуть посетителей, но, с другой стороны, если переборщить с различными «наворотами», то работа форума может всерьез перегрузить сервер, что тоже не очень хорошо скажется на посещаемости вашего веб-ресурса. Существует множество различных вариантов создания форума. Наиболее простые из них могут целиком избавить вас от больших объемов кодирования – например, если вы захотите довольствоваться только плоским режимом вывода сообщений, без разделения на темы. Конечно же, выбор той или иной реализации – это дело вкуса, но посвящать целую статью созданию плоского списка сообщений явно не стоит. Поэтому здесь мы рассмотрим более интересный вариант. Настоящая статья описывает основные стадии создания иерархического форума с использованием ASP.NET. Из сообщений будет выстраиваться дерево, по которому удобно прослеживать развитие темы собеседниками:
Рисунок 1. Пример форума Число сообщений в каждой ветке теоретически бесконечно. Сообщения форума выводятся постранично, причем количество тем на странице можно указывать вручную – выбрав нужное число из комбинированного списка, расположенного в заголовке форума. Для рендеринга такой страницы достаточно одного SQL-запроса, о котором мы поговорим чуть позже. Форум тестировался и прекрасно работает в браузерах MSIE версии 5 и выше, Mozilla 1.4, Opera версии 7 и выше. Форум был написан по принципу «раздельного кода» – все серверные сценарии отделены от aspx-кода. В качестве сервера SQL использовался MS SQL Server. В настоящей статье описываются основные этапы создания серверных и клиентских сценариев данного форума. В приложении к статье можно найти готовую реализацию форума – проект для Visual Studio .NET 2003. Создание шаблонов DHTML-кодаОбщие принципыДля начала скажем несколько слов об основной идеологии описываемой в настоящей статье реализации. Хотя мы и будем рассматривать здесь создание ASP.NET-проекта на языке C#, наш подход будет, скажем так, не совсем типичен для разработки веб-ресурсов с использованием ASP.NET. Естественно, когда речь заходит о преимуществах ASP.NET, то первое и, пожалуй, самое броское преимущество, которое хочется упомянуть – это удобные северные элементы управления, позволяющие скрыть от разработчика «сложные» детали DHTML. Благодаря таким control-ам, как DataGrid, можно сильно сократить время разработки – свести решение некоторых задач к «работе мышкой» в дизайнере веб-форм, причем никакого знания DHTML не потребуется. В этом смысле серверные элементы управления представляют собой готовые решения для наиболее часто используемой функциональности, но если даже в ASP.NET вам потребуется нечто большее, то об удобствах визуального дизайнера придется забыть. То же самое справедливо и для настольных приложений – прежде всего для тех, которые уже строятся на готовом каркасе (framework), инкапсулирующем системные вызовы для упрощения работы по созданию пользовательского интерфейса. Так, чтобы разработать интерфейс с базовой функциональностью, достаточно провести лишь несколько минут в дизайнере форм или даже просто воспользоваться соответствующим мастером. Но если требуется нечто большее, чем стандартные элементы управления Windows, то без больших объемов ручного кодирования не обойтись. Даже в FCL для решения подобных задач, как правило, придется напрямую работать с системными функциями, хотя, бесспорно, одно из основных преимуществ данной библиотеки классов заключается в довольном сильном покрытии Windows API и возможности создавать пользовательские интерфейсы, «абстрагируясь» от API-функций. В отношении веб-приложений своего рода «аналогом» Windows API может выступать DHTML-код – т.е. это, скажем так, тот самый «низкий уровень», на котором можно создавать веб-приложение. Описываемая здесь реализация форума как раз и является такой «низкоуровневой» реализацией. Значит ли это, что мы не будет использовать никаких преимуществ ASP.NET? Отвечая на этот вопрос, не стоит забывать о том, что, помимо удобных серверных control-ов, в числе преимуществ ASP.NET находится также и возможность писать серверную часть на таких современных объектно-ориентированных языках, как C#, и использовать богатые возможности библиотеки классов FCL. И надо сказать, что без этого преимущества создание самого «сердца» нашего проекта – класса ForumGenerator – стало было невозможным. Принцип работы генератора форума достаточно прост. Вся работа на сервере будет ограничиваться лишь собственно созданием DHTML-кода форума – хотя это основной и наиболее сложный этап в реализации, – после чего «управление» перейдет к клиентским скриптам, а вся нагрузка с сервера будет снята. Результирующая страница форума складывается из готовых шаблонов HTML-кода, хранящихся в ресурсах сборки. Такой подход используется в прилагаемой к статье реализации форума и у него, разумеется, есть ряд существенных недостатков. Например, для изменения шаблонов DHTML придется производить перекомпиляцию всего проекта. Если же в разрабатываемом проекте придется менять шаблоны HTML достаточно часто, и необходимость перекомпиляции для совершения каждого изменения серьезно затруднит поддержку веб-ресурса, то лучше перенести шаблоны HTML из ресурсов сборки во внешний XML-файл – общие принципы реализации форума от этого ничуть не изменятся. Так как практически весь код страницы будет формироваться динамически, то основная стартовая страница демонстрационного проекта – default.aspx – будет содержать лишь пустой контейнер (DIV), обрабатываемый на стороне сервера, и несколько скрытых полей (INPUT type=”hidden”), предназначенных для обмена данными между клиентскими и серверными скриптами.
Для удобства все клиентские сценарии, которые используются в описываемой здесь реализации, размещены в отдельном файле forum.js. Поэтому на странице также должны содержаться ссылки на этот файл и на файл с таблицей стилей, которые будут определять внешний вид нашего форума – forum.css.
Не забудьте также правильно указать кодовую страницу – windows-1251 – как в теле самой aspx-страницы, так и в конфигурационном файле веб-приложения (web.config). Например:
Помимо основного файла default.aspx, в приложении будут использоваться файлы post.aspx, содержащий код для отправки сообщения в форум, и postbody.aspx, в который будет выводиться текст выбранного пользователем сообщения. Основные шаблоны HTMLНачнем с написания DHTML-кода, который будет использоваться при рендеринге форума. Два основных элемента форума – это сообщение без ответов и сообщение с ответами (щелкнув по иконке которого, можно раскрыть все дерево дочерних сообщений). Наиболее удобно будет сделать данные элементы в виде таблиц. Так как все «события» форума (как, например, раскрытие ветки сообщений, создание ответа на сообщение и пр.) после его рендеринга будут обрабатываться только на клиенте, то необходимо добавить к телу каждого элемента специальные скрытые поля, в которых будет храниться неотображаемая на странице, но необходимая для создания новых сообщений информация – уникальный идентификатор сообщений, идентификатор темы и пр. Итак, код сообщения без ответов будет выглядеть так:
Символ “|” будет заменяться в процессе генерации на индексы. Метод showPost будет выводить текст сообщения в кадр IFrame – его реализация будет рассмотрена ниже. Методы overPost и outPost динамически изменяют класс стиля, когда над сообщением проходит курсор мышки. Для создания отступов между сообщениями разного уровня используется элемент стиля padding. Слово Change, указанное в данном элементе, будет динамически заменяться на нужный отступ в процессе генерации страницы. Можно было бы, конечно, задать элементам относительное позиционирование (position:relative) и установить поля в некоторую постоянную величину – например, margin-left:10px. Однако такой способ в нашем случае неприемлем, так как необходимо, чтобы сдвигалось лишь содержимое поля с темой сообщения, а остальные оставались неподвижными. HTML-код сообщения, которое содержит ответы, отличается лишь двумя моментами. Прежде всего, в тег IMG добавлен обработчик события onclick, куда помещен указатель на функцию TreeExpand, раскрывающую список дочерних сообщений. Кроме того, каждый шаблон сообщения, содержащего ответы, содержит незакрытый тег DIV (этот тег закрывается программно), выступающий в качестве контейнера для дочерних сообщений:
Соответственно, при генерации дерева форума надо будет закрывать этот тег уже в коде самого генератора для каждого иерархического уровня. Помимо этого, как уже говорилось, при отрисовке форума к каждому сообщению будут добавлены скрытые поля для хранения дополнительных данных, связанных с этим сообщением. Также на странице форума будет размещаться заголовок сообщений и колонтитул самого форума – их HTML-код не представляет никакого интереса. Навигация по форуму (переход между страницами) будет осуществляться с помощью специальной панельки, для рендеринга которой мы также будем использовать элемент HTML table:
Слова pagenumber и pagecount будут динамически заменяться на номер текущей страницы и общее количество страниц, соответственно, на этапе рендеринга страницы. Также вы наверняка заметили, что в атрибут style ячеек таблицы вставлены слова disptype и displaytype. Они будут заменяться на указания display:none или display:inline, в зависимости от ряда причин. Например, если форум состоит только из одной страницы, кнопки вообще не будут выводиться, и т.п. Кнопки, с помощью которых, собственно, и осуществляется навигация по форуму, имеют тип submit. После их нажатия данные будут автоматически отсылаться на сервер. Можно также использовать javascript-указание document.[formName].submit(), где formName – это название вашей формы (не забывайте всегда указывать объект document – в противном случае этот код будут правильно интерпретировать не все браузеры). Кодовые символы < и > будут отображаться на странице HTML как треугольные скобки. С помощью функций goPrevious и goNext, собственно, и будет осуществляться навигация между страницами. Помимо панели для навигации на странице будет также размещаться и панелька с кнопками, позволяющими отправить новое сообщение в форум.
Эти кнопки уже не обязательно должны иметь тип submit, так при их нажатии данные на сервер отсылаться не будут, но просто откроется страница для создания нового сообщения. Код для открытия данных страниц будет размещен в javascript-функциях newPost и reply, соответственно. Использование IFrameСам текст сообщения будет выводиться в окно IFrame, размещенное между панелью для навигации по форуму и панелью для создания нового сообщения. Данный подход удобен тем, что он позволяет не размещать в результирующей HTML-странице весь текст выводимых на нее сообщений – как это делается, к примеру, в форумах codeproject.com, где в процессе рендеринга дерева сообщений само содержимое последних также выводится в специальные скрытые поля, которые автоматически раскрываются, когда пользователь щелкает на теме сообщения. Такая методика несколько упрощает общую структуру элементов управления HTML, которые используются для представления форума. Например, отпадает необходимость в специальном окне, в которое будет выводиться текст сообщений. Однако размер результирующих HTML-страниц значительно увеличивается, что может негативно сказаться на производительности форума. По этой причине в настоящей реализации мы и будем использовать окно IFrame. Фактически в IFrame будет загружаться специальная ASPX-страница. В качестве параметра этой странице будет передаваться уникальный идентификатор сообщения, содержимое которого мы хотим просмотреть, а в серверном коде, размещенном на данной странице, будет осуществляться выборка тела сообщения из базы данных. Этот код мы рассмотрим чуть позднее, когда обратимся к созданию серверных сценариев. Написание клиентских сценариевПри прохождении мышки по заголовку сообщения, заголовок будет выделяться с помощью специальной рамки засветки. При щелчке по данному сообщению оно будет отмечаться как выбранное, что позволит пользователю легко определить, какое из сообщений он в настоящий момент просматривает. Для осуществления этих эффектов мы используем такие Javascript-функции:
Как мы видим, класс стиля будет меняться только в том случае, если он является классом выбранного пользователем сообщения – в таком случае внешний вид заголовка сообщения никак не будет реагировать на прохождение над ним курсора мышки. Чтобы отмечать сообщение как выбранное, будет использоваться такая функция:
Переменная selPost будет хранить ссылку на последнее выбранное пользователем сообщение. Topic – это ссылка на сообщение, по которому пользователь щелкнул мышкой. Благодаря использованию переменной selPost можно гарантировать, что в каждый момент времени только одно сообщение будет помечено как выбранное
Помимо этого, при щелчке по иконке сообщения (принимающей вид «плюса» или «минуса» в зависимости от ситуации) будет раскрываться ветка данной темы. Для этого будет использоваться такая клиентская функция:
С помощью данной функции будут не только раскрываться темы сообщений, но и производиться динамическая замена иконок, обозначающих, раскрыта данная тема или свернута. Для навигации между страницами используются функции goNext и goPrevious. В этих функциях производится инкремент номера текущей страницы. Полученное значение записывается в скрытое поле CountBox. В функциях reply и newPost содержится код, формирующий все необходимые для создания сообщения параметры, которые будут передаваться в функцию performPost. Эти параметры состоят из идентификационных данных выбранного пользователем сообщения. В итоге на их основе сформируется запрос для открытия страницы post.aspx, в которую будут в качестве параметров передаваться:
(Если сообщение открывает новую тему, то вместо некоторых из этих параметров будет передаваться значение “-1”). Функция performPost будет открывать в отдельном окне страницу для создания сообщения (с помощью javascript-функции window.open), передавая в нее эти параметры в виде запроса как часть адресной строки. Вряд ли тут требуются какие-то дополнительные пояснения. Для динамического изменения количества тем на странице будет использоваться функция refresh, вызов которой происходит при нажатии кнопки типа submit, в результате чего данные отправляются на сервер. Вот код этой функции:
Элемент PerPageBox – это скрытое поле, обрабатываемое на сервере. В этом поле хранится количество тем, отображаемых на странице. PerPage – это элемент HTML типа Select, позволяющий пользователю выбрать количество тем на странице. Данные, передаваемые в скрытое поле PerPageBox, будут интерпретироваться уже серверным сценарием. Проектирование базы данныхДля начала следует сказать, что предлагаемое здесь решение позволяет размещать на Web-ресурсе сколько угодно форумов. При этом для добавления нового форума понадобится лишь внести соответствующую запись в одну из таблиц БД и поместить ссылку на этот форум в главном меню сайта. Разумеется, подобная функциональность накладывает свой отпечаток на структуру БД и, хотя в демонстрационном проекте приведен пример лишь одного форума, сама реализация построена так, что при желании можно добавить сколько угодно новых форумов. Тем не менее, в данном случае проектирование базы – далеко не самый сложный этап в разработке. База данных – назовем ее forumdb – будет состоять всего из двух таблиц, связанных по ключевому полю forum_id. Так они должны выглядеть:
В таблице forum будет храниться список всех форумов. Форумы идентифицируются по ключевому полю forum_id, которое также выступает в качестве foreign key в основной таблице БД forum_post. С помощью данного поля производится выборка сообщений из конкретного форума. В поле name будет храниться название форума. Куда больший интерес представляет таблица forum_post. Структура данной таблицы специально оптимизирована под хранение данных с иерархической структурой, какими и являются сообщения нашего форума. Ниже я перечислю все поля данной таблицы с краткими пояснениями:
Предназначение этих полей станет более понятным, когда мы обратимся непосредственно к рассмотрению алгоритма генерации HTML-кода форума. Как вы уже заметили, в рассматриваемой здесь реализации в качестве SQL-сервера использовался MS SQL Server, однако в структуре базы данных и коде SQL-запросов нет ничего такого, что нельзя с минимумом сложностей перевести на SyBase или Oracle. Создание SQL-запросовПрежде всего следует определиться с задачами. Итак, сообщения форума будут выводиться постранично, и на каждой странице будет размещаться не более десяти тем. Так как текст самих сообщений не будет включаться в результирующий код страницы, то, соответственно, он не должен включаться и в выборку данных таблицы forum_posts. К тому же, как мы уже определились, данная таблица может содержать сообщения для произвольного количества форумов, соответственно, выборка сообщений будет совершаться по полю forum_id. Наконец, на основе данных, полученных в результате запроса, будет заполняться dataset, и основной цикл генерации форума будет происходить при разъединенном подключении к базе данных. Поэтому основной запрос должен совершать выборку сразу всех сообщений для текущей страницы – то есть, например, десяти корневых сообщений с первым уровнем вложенности и всех сообщений, которые являются ответами на них. Итак, для этих целей мы можем использовать такой запрос.
Переменная @forum будет определять, из какого форума производится в настоящий момент выборка сообщений. X будет динамически заменяться на количество тем на странице (вычисление которого на стороне клиента было показано выше), Y – на число, вычисляемое по формуле [номер текущей страницы] * [количество тем на странице]. Таким образом, если текущей является первая страница (т.е. фактически номер страницы равен нулю), то вместо X будет подставлен 0 – и так далее. Сортировка в обратном порядке по ключевому полю необходима для того, чтобы сообщения выстраивались в том порядке, в котором они были записаны в таблицу форума. Сортировка в обратном порядке по полю level необходима для работы генератора форума, который будет рассмотрен ниже. Он не использует T-SQL, не блокирует таблицу на момент выборки данных, не создает временных таблиц. Необходимость двух вложенных SELECT’ов объясняется в данном случае тем, что нужно возвращать некоторое заданное количество тем с произвольным количеством сообщений – т.е. данный запрос может в принципе вернуть любое количество строк таблицы. Именно в этом и заключается его коренное отличие от классической реализации постраничной выборки без использования специфичного T-SQL. У этого два запроса есть два немаловажных недостатка. Первый недостаток заключается в том, что последний вложенный SELECT с переходом на каждую новую страницу будет производить выборку все большего числа сообщений – так, при показе первой страницы количество сообщений будет равно нулю, при показе второй страницы – уже десяти, при показе третьей – тридцати и пр. В принципе, в данном случае потеря производительности будет не столь критична, потому что вышеупомянутый SELECT производит выборку только одного поля и только корневых сообщений одного форума. Второй недостаток – это отсутствие кэширования планов запроса при использовании выборки с помощью ключевого слова TOP. Дело в том, что хотя в вышеозначенном примере номер текущей страницы и количество тем на странице мы обозначили как X и Y, в действительности эти величины нельзя параметризировать (как это делается, например, для идентификатора форума), и запрос придется динамически генерировать в коде. Достоинство этого запроса заключается в том, что он будет работать на большинстве SQL-серверов. При желании можно, конечно, оптимизировать данный запрос, однако тема постраничного вывода данных из таблицы – а особенно применительно к различным SQL-серверам – заслуживает отдельной большой статьи, поэтому здесь мы не будем касаться ее подробно. Приведем лишь пример того, как можно переписать этот запрос, используя специфичный T-SQL и временную таблицу:
Помимо этого, в основном классе генератора форума используется еще два простых запроса. Один из них будет возвращать общее количество тем в указанном форуме. Другой будет совершать выборку из таблицы forum и возвращать название текущего форума по его идентификатору. Эти два последних запроса будут выполняться в одном пакете с первым, но только один раз, при первом открытии форума. Впоследствии данные, полученные с помощью этих запросов, запишутся в скрытые поля на странице, и, когда пользователь начнет путешествовать по страницам нашего форума, эти запросы повторно исполняться уже не будут. В остальных классах нашего проекта мы будем использовать еще несколько запросов. Однако они более чем просты, поэтому не требуют подробного рассмотрения. Для простоты и наглядности мы будет прописывать все запросы непосредственно в коде, хотя такой подход вряд ли стоит брать на вооружение. В реальном проекте наиболее предпочтительным способом является использование хранимых процедур. Класс ForumGeneratorОбщие сведения о классеРеализация класса ForumGenerator – основной этап разработки форума. Все параметры, необходимые для работы этого класса, будут передаваться в его конструктор при создании экземпляра. Данный класс будет содержать всего один открытый метод – string Generate() – не принимающий никаких параметров и возвращающий полный DHTML-код форума в виде строки. В теле класса объявляются следующие поля:
В полях slider и sliderDiv будут храниться блоки DHTML-кода, полученные из ресурсов сборки через менеджер ресурсов (res). Предназначение остальных полей станет ясно из описания конструктора класса, который выглядит так:
В качестве параметра forumName будет передаваться содержимое скрытого поля StateBox, которое при первом создании экземпляра класса будет пустым, а соответственно, в качестве данного параметра будет передаваться пустая строка (String.Empty). Аналогично и с параметрами pageSize и current – они представляют собой содержимое полей PerPageBox и CountBox, которые будут заполняться через клиентский скрипт, что уже рассматривалось нами ранее. В качестве параметра forum будет передаваться идентификационный номер форума по таблице forum. Помимо публичного метода Generate класс будет также содержать несколько закрытых вспомогательных методов и два класса, которые понадобятся при построении дерева сообщений форума. Объектная модель сообщенийНеобходимость построения дерева сообщений в данном случае может показаться не столь очевидной, однако в действительности она может сильно помочь нам в процессе генерации форума. Все дело в том, что с одной стороны, мы должны уже формировать DHTML-код всех сообщений, чтобы генерация не разбивалась на два этапа – создание дерева сообщений и собственно генерацию DHTML на основе этого дерева. Но с другой стороны, у нас по-прежнему должна остаться возможность доступа к некоторым данным, ассоциированным с каждым сообщением, а так как после итогового формирования DHTML-кода для сообщения это последнее будет представлять собой обычную строку, то выполнение такого рода задачи может стать несколько затруднительным. Поэтому для того чтобы упростить себе жизнь, мы создадим специальный класс, представляющий сообщение, на которое нет ответов. Данный класс будет включен в тело основного класса ForumGenerator, так как не будет использоваться нигде за пределами генератора форума:
Как вы видите, в классе перегружен метод string ToString() – так, чтобы с его помощью можно было получить DHTML-содержимое данного сообщения. Удобство такого подхода станет более ясным далее, когда мы рассмотрим реализацию второго класса, используемого при построении дерева сообщений. Вышеприведенная реализация класса Post, как несложно догадаться, может содержать лишь данные для одиночного сообщения, на которое нет ответов. Если же сообщение является началом целой темы или отдельного обсуждения внутри темы, то оно должно содержать также и ссылки на дочерние сообщения. Для такого рода сообщений нам придется создать отдельный класс – наследник Post:
Главное новшество в классе TopicPost по сравнению с родительским классом – это свойство ChildNodes, инкапсулирующее поле, в котором будут храниться ссылки на все дочерние сообщения. Дочерние сообщения могут быть как одиночными, так и содержащими ответы, а следовательно, будут представлены в нашем дереве различными объектами. Использование в данном случае типизированной коллекции нецелесообразно, поэтому данное свойство было объявлено как ArrayList. Еще одно важное новшество – это более сложная по сравнению с предыдущим классом реализация метода ToString. Дело в том, что если аналогичный метод класса одиночного сообщения должен возвращать DHTML-код только одного сообщения, то в данном случае нам нужно получить DHTML-код всех дочерних сообщений. К тому же, как уже говорилось при описании шаблона, используемого при рендеринге сообщения с ответами, все дочерние сообщения должны быть обрамлены тегом DIV. Проще всего сделать это, используя предлагаемую здесь модель. Теперь если вы, к примеру, вызовете метод ToString у корневого сообщения длинной и разветвленной темы, то получите правильно скомпонованный DHTML-код всех сообщений данной темы (думаю, вряд ли у кого возникнут сомнения, что это действительно так). Вызов метода ToString мы будем производить только один раз, на самом последнем этапе генерации форума. Однако это еще не все. Текущая модель позволяет создать иерархический дерево, наиболее, казалось бы, удобное для наших целей, однако обладающее одним серьёзным недостатком – чтобы перебрать все элементы дерева, придется совершать перебор элементов всех коллекций в многократно вложенных циклах, что не только понизит производительность генератора, но и сделает длину ветки ограниченной. Однако учитывая, что в данном случае мы не будем использовать некую изначально заданную иерархическую структуру, но самостоятельно формировать собственную, эта проблема является вполне решаемой. Все, что нужно сделать, – это написать метод, представляющий все вложенные классы коллекции в виде линейного списка, элементы которого можно перебрать в одном цикле. Реализация данного метода включена в класс TopicPost и выглядит так:
GetEnumeration является рекурсивной функцией, возвращающей список всех дочерних веток. Таким образом, мы получаем простой механизм для перебора всех членов коллекции. Основной алгоритм генерации форумаТеперь мы уже можем перейти к написанию главного метода класса ForumGenerator – метода Generate. В нем будут использоваться несколько вспомогательных методов:
Реализация этих методов вполне очевидна, и многие ее аспекты уже затрагивалась выше. Фактически в них содержится вспомогательная логика генератора форума, не представляющая особого интереса. Здесь же мы рассмотрим главный алгоритм генератора. Ниже я приведу весь код метода Generate – так, как он реализован в прилагающемся к статье демонстрационном проекте:
Здесь не обойтись без нескольких комментариев. На основе приведенных ранее запросов заполняется датасет. Если форум открывается в первый раз, то датасет состоит из трех таблиц: первая – это, собственно, сами сообщения форума, а вторая и третья содержат общее количество корневых сообщений форума и название форума, взятое из таблицы forum. Если форум просто пролистывается пользователем, то датасет состоит всего из одной таблицы с сообщениями. Основная часть кода метода занимается построением дерева сообщений на основе данных, которые содержатся в первой таблице датасета. При построении дерева главную роль играют два поля – topics типа ArrayList и topicNumbers типа Hashtable. Поле topics является коллекцией с длиной, равной количеству тем, отображаемых на странице одновременно. В данной коллекции будут храниться объекты типов Post и TopicPost. Смысл коллекции topics заключается в том, что, благодаря ей, сохраняется порядок следования сообщений. Поскольку (и об этом уже говорилось) список сообщений в таблице датасета отсортирован по возрастанию поля level, то в числе самых первых сообщений окажутся корневые сообщения, т.е. те, ответами на которые являются все остальные сообщения данной страницы форума, а все остальные будут расположены в порядке убывания их индекса, чем мы и воспользуемся при генерации. Поэтому на первоначальном этапе построения дерева вся коллекция topics будут целиком (или почти целиком, если, к примеру, во всем форуме менее десяти тем) заполнена корневыми сообщениями тем. Если на корневое сообщение нет ответов, и для его представления в коллекции использовался экземпляр типа Post, то весь его рендеринг будет завершен еще на этом первом этапе и, можно сказать, что в дальнейшей генерации оно участвовать не будет. Если же на сообщение имеются ответы (что определяется содержимым поля answers типа Bit), то для его представления будет использоваться экземпляр типа TopicPost, который, как мы помним, сам по себе может содержать коллекцию дочерних элементов. И именно в данную коллекцию будут добавляться сообщения второго и более глубоких уровней вложенности. Тут, конечно же, не все так просто. Когда начинается второй этап генерации (его код содержится в блоке else), то прежде всего нужно определить, частью какой из тем является рассматриваемое в настоящий момент сообщение (или, говоря другими словами, каков индекс данной темы в коллекции topics). Использовать для этих целей поле answer, содержащее ссылку на forum_post_id того сообщения, ответом на которое является рассматриваемое, нельзя, так как если первый этап генерации совершается только для сообщений первого уровня, то второй этап – для сообщений всех остальных уровней, и нам пришлось бы использовать многократно вложенные циклы для этого. Тут нам приходит на помощь заранее определенное поле topic_id, которое есть у каждого сообщения, и которое позволяет ассоциировать его с той или иной темой. Чтобы оптимизировать поиск нужной темы в коллекции topics, используется хэш-таблица topicNumbers. Еще на первом этапе генерации в эту хэш-таблицу заносятся индексы всех тем, добавляемых в коллекцию topics, причем в качестве ключа выступает, конечно же, topic_id этой темы. Поэтому все, что нам нужно на втором этапе – это просто прочитать нужное значение из хэш-таблицы. Это делается с помощью строчки кода: int value = (Int32)topicNumbers[r["topic_id"]]. В результате в поле value будет находиться индекс темы в коллекции topics – т.е. то, что нам и нужно было получить. Но, опять-таки, трудности на этом не кончаются. Теперь мы точно знаем, к какой из тем принадлежит рассматриваемое сообщение, и что искать его нужно, скажем, в той коллекции, ссылка на которую содержится, например, в topics[0]. Область поиска, конечно, сужается, однако все равно остается не такой узкой, как хотелось бы. К сожалению, знание уровня вложенности рассматриваемого сообщения нам уже никак не поможет. Ведь на одном и том же уровне может быть множество разных сообщений, каждое из которых может начинать отдельную подтему. Но тут на помощь придет метод GetEnumeration, реализация которого рассматривалась выше. Благодаря ему можно представить все иерархические коллекции сообщений данной темы в виде одной плоской коллекции, итерацию по которой совершать уже совсем несложно. Так заканчивается последний этап генерации дерева сообщений. Теперь остается лишь вызвать метод ToString у всех корневых тематических сообщений – и мы получим полностью сформированный DHTML-код страницы форума. Просмотр и отправка сообщенияСтраница для просмотра сообщенийВ принципе, весь код этой страницы чрезвычайно прост. При ее разработке даже не потребуется визуальный редактор Web-форм. Нужно всего лишь создать контейнер (DIV), обрабатываемый на сервере, или любой другой элемент, который вы сочтете наиболее удобным, и написать метод, который будет вызываться при загрузке страницы:
ForumText – это элемент HTML, в который будет выводиться текст сообщения. Весь код, содержащийся в данном методе, будет выполняться только тогда, когда в страницу через строку запроса будет передан параметр forum_id. В противном случае страница будет оставаться пустой (как это происходит когда, например, просто загружается само дерево сообщений форума). Все остальное, в принципе, и так очевидно – с помощью простейшего запроса будет извлекаться содержимое поля body из таблицы forum_post – из той строки, которая соответствует заданному forum_id. Страница для отправки сообщенийЭта страница будет состоять из нескольких элементов управления, которые удобнее всего создать в визуальном дизайнере. Внешний вид этих элементов управления можно определить непосредственно в редакторе свойств, но лучше все-таки создать соответствующие CSS-классы.
Итак, на форме должны быть размещены три текстовых поля – для ввода имени, темы сообщения и текста сообщения. Кроме этого, следует добавить кнопки OK и Cancel. Также будет производиться проверка содержимого всех трех текстовых полей, расположенных на форме, с помощью компонентов типа RequiredFieldValidator. Для вывода суммарных сообщений валидаторов будет использоваться компонент ValidationSummary. В итоге форма будет выглядеть примерно так:
При загрузке формы будет вызываться метод Page_OnLoad. Код данного метода будет считывать запросы из адресной строки, с помощью которой была вызвана данная форма, а также высчитывать общее количестве тем в указанном форуме (в том случае, если создаваемое сообщение не является ответом на какое-либо другое сообщение и, соответственно, открывает новую тему). Также в том случае, когда сообщение является ответом, будет динамически формироваться его заголовок на основе заголовка сообщения, на которое производится ответ, с добавленным к нему префиксом «Re:». Так будет выглядеть код данного метода:
При нажатии на кнопку OK будет вызываться метод OK_Click и, соответственно, данная кнопка должна представлять собой серверный элемент управления. При нажатии на кнопку Отмена будет просто закрываться окно, т.е. выполняться javascript-код window.close() и, соответственно, данная кнопка должна представлять собой обычный элемент HTML и обрабатываться только на клиенте. Вот код, который будет исполняться при нажатии на кнопку OK:
Как видно, в том случае, если создаваемое сообщение является ответом, код будет вносить изменения в то сообщение, на которое отвечает пользователь, чтобы при последующем считывании данного сообщения из датасета можно было сразу определить, что на него есть ответы. В методе parse, возвращающем значение типа string, происходит проверка текста сообщения. В частности, символы «<» и «>» заменяются кодами этих символов («<» и «&062»), что необходимо, так как автор сообщения может при желании ввести в текст сообщения клиентский сценарий или любой другой запрещенный DHTML-код. ЗаключениеРазумеется, в этой статье я описал лишь реализацию базовой функциональности форума, однако при желании можно без особых сложностей добавить к ней функции модерирования, сложное форматирование сообщений и пр. К тому же – несмотря на то, что мне бы этого очень хотелось – описанная здесь реализация не претендует на безупречность; по большому счету, это только пример, который вы можете самостоятельно улучшить и обогатить функциональностью. Моей целью было лишь показать некоторые преимущества, которые дает использование объектно-ориентированных возможностей языка C# и библиотеки классов FCL при создании форума. Приведенная здесь реализация была бы в принципе невозможна, если бы мы, к примеру, использовали обычный ASP и писали серверную часть на примитивных скриптовых языках вроде VBScript и javascript. Эта статья опубликована в журнале RSDN Magazine #5-2003 Автор: Воронков
Василий Рубрика: Учебник по ASP.NET
HTML 5: пять вещей вызывающих особый интер....
HTML 5 — это грядущее обновление гипертекстового языка разметки, основного способа создания контента для размещения его во всемирной паутине. Разработка HTML остановилась в 1999 году, на версии HTML 4.01 и с тех пор web-содержимое изменилось так, что текущие спецификации HTML перестали соответствовать сегодняшним требованиям. HTML 5 нацелен на то, чтобы увеличить функциональную совместимость HTML и соответствовать растущим требованиям разнообразного и смешанного web-контента. HTML 5 так же нацелен на устранение недостатков четвертой версии. В этой статье мы взглянем на 5 новых интересных вещей в HTML 5.
Подробнее... |
Рубрика: Html
| Добавлено: 22.12.2008
asp.net: ListView с разных сторон.
Элемент управления ListView был представлен в .Net Framework 3.5 как замена устаревшему GridView. Новый элемент имеет более расширенный функционал, чем его предшественник, но в тоже время лишен некоторых внутренних механизмов, что впрочем целиком следствие из расширенной универсальности ListView. Среди отличий ListView и GridView можно назвать и гибкую настройку разметки, что позволяет выводить данные не только в табличном виде, но и вообще в любом каком пожелает программист. Благодаря шаблонам ItemTemplate, EditItemTemplate, InsertItemTeplate можно настроить внешний вид при любом из состояний ListView: редактировании или выборе элемента.
Подробнее... |
Рубрика: .NET компоненты
| Добавлено: 22.12.2008
Создание кросс-таб отчета в Stimulsoft Rep....
Компания Стимулсофт предоставляет для разработчиков мощный набор инструментов для создания отчетов для Microsoft Visual Studio .Net 2005 и 2008; эти инструменты доступны как для Windows Forms, так и для Web Forms. Это генератор отчетов Stimulsoft Reports.Net. Генератор отчетов Stimulsoft Reports.Net имеет ряд особенностей: простая работа с дизайнером отчетов, полная поддержка экспорта в PDF, Word, Excel и многие другие форматы. Crystal Report и Microsoft Reporting Service – очень хорошие программные продукты для повседневной работы, но, если Вам необходимо создать отчеты с поддержкой кросс-табов, drill down, Ajax, штрих-кодов и возможностью подключения одновременно более одного источника данных, то Stimulsoft Reports.Net поможет Вам сэкономить массу времени. Также, данный генератор отчетов позволяет пользователям создавать свои собственные отчеты любой сложности. И все эти особенности делают Stimulsoft Reports.Net хорошим выбором в сфере программных продуктов для Business Intelligence.
Подробнее... |
Рубрика: .NET компоненты
| Добавлено: 22.12.2008
Остальные статьи: |
Цитата дня (все,добавить):
|
Realcoding.NET
© 2003-2008 |
Контакты |
Реклама на сайте
|