<% ASP+ на блюдечке %>. Часть 1
Система доступа к базам данных
Система доступа к базам данных, по сути, представляет собой Web-версию популярной программы ISQL, входящей в состав Microsoft SQL-сервера, а фактически позволяет подключаться и выполнять запросы к удаленному или локальному SQL-серверу. При этом указываются сервер, база данных, учетная запись (login) и пароль доступа. Исходя из введенных значений система пытается осуществить подключение и выполнить введенный запрос. Результат по желанию может быть сформирован или в табличном, или в XML-представлении.
Итак, приступим к созданию нашего Web-интерфейса. Для начала изготовим форму диалога с пользователем:
<%@Page Language="C#" Inherits="Aquarius. AquariusWI " Src="WebSql.cs" Trace="False" %>
Для читателей почти все должно быть очевидным. Во-первых, первая директива указывает на то, что в качестве языка описания сценариев используется язык C#. Во-вторых, создаваемая форма будет наследовать свойства и методы класса Aquarius, описанного в исходном файле «WebSql.cs». В-третьих, директива Trace=«False» указывает на то, что в настоящий момент режим отладки приложения выключен и ASP+ не будет выдавать в качестве результата работы страницы подробный отчет обо всех последовательно произведенных действиях и событиях.
Однако возможен и следующий вариант:
<%@Page Language="C#" Inherits="Aquarius. AquariusWI " Codebehind=" WebSql.dll"" Trace="False" %>
В данном случае ссылка «исходный файл» заменена ссылкой на результат его компиляции. По умолчанию система будет производить поиск последнего в подкаталоге bin корневого каталога нашего Web-приложения. Разумеется, заранее необходимо позаботиться о наличии этого файла именно там, прекомпилировав его с помощью процедуры nmake.exe (для того чтобы узнать, как это сделать, достаточно запустить ее без параметров). Такая технология позволит скрыть исходные тексты ваших приложений даже от лиц, имеющих доступ к серверным файлам.
Далее страница-интерфейс мало чем отличается от обычной HTML-страницы, разве что несколькими типичными для ASP+ компонентами. Давайте рассмотрим их подробнее:
<asp:TextBox id="txtServer" size=20 maxLength=20 runat="server"/>
Таким способом можно создать текстовое поле размером и числом максимально вводимых символов, равными 20, с названием «txtServer». Давайте разберемся с последней директивой: Runat= «server». Этот атрибут указывает компилятору на то, что к данному элементу будет осуществлен программный доступ, то есть данный объект интерфейса может быть обработан программно. Однако необходимость в этом возникает далеко не всегда, и именно поэтому следует прибавлять данную директиву не ко всем компонентам интерфейса страницы, а лишь применительно к тем, обработка событий которых нам потребуется в дальнейшем.
Так, по сути, компонент <asp:TextBox> представляет собой ASP+ вариант обычного HTML -компонента <input type="text">. Аналогом же компонента <select> является компонент
<asp:DropDownList id=lstDatabases size=1 AutoPostBack="True" OnSelectedIndexChanged="lstDatabases_Change" MaintainState="True" runat="server" />
Директива AutoPostBack= «True» указывает компилятору ASP+-страницы создать клиентский механизм отсылки страницы обратно на сервер при возникновении события, требующего программной обработки. К примеру, в вышеприведенном случае компилятор автоматически вернет серверу страницу при возникновении события OnChange и вызовет его обработчик lstDatabases_Change.
Необходимый для возврата код будет сгенерирован компилятором автоматически. В данном случае будет сгенерирован следующий код:
<select name="lstDatabases" id="lstDatabases" size="1" onchange="javascript:__doPostBack('lstDatabases', '')">
где __doPostBack функция вида:
function __doPostBack (eventTarget, eventArgument)
Как видите, __doPostBack() — это JavaScript-код, выполняющийся на стороне клиента и попросту извлекающий идентификатор обрабатываемого компонента интерфейса и необходимую дополнительную информацию (аргументы), которую он посылает обратно на сервер, где она обрабатывается соответствующим обработчиком. С точки зрения управления результирующая HTML-страница в браузере выглядит следующим образом:
<script language="javascript"> function __doPostBack(eventTarget, eventArgument) { var theform = document.ctrl1 theform.__EVENTTARGET.value = eventTarget theform.__EVENTARGUMENT.value = eventArgument theform.submit() } </script> … <select name="lstDatabases" id="lstDatabases" size="1" onchange="javascript: __doPostBack('lstDatabases', '')"> … <input type="checkbox" id="chkSavePassword" value="chkSavePassword" name="chkSavePassword"> … <select name="lstSave" id="lstSave" onchange="javascript:__doPostBack('lstSave', '')"> …
Давайте посмотрим, как выглядит вышеупомянутый обработчик события OnChange:
protected void lstDatabases_Change (object sender, EventArgs e) { if (lstDatabases.Items[lstDatabases.SelectedIndex].Value == TEXT_REFRESH) { string sql_conn_str = "server=" + _ServerName + ";uid=" + _UserName + ";password=" + _Password + ";database=Master"; SQLConnection myConnection = new SQLConnection (sql_conn_str); SQLDataSetCommand myCommand = new SQLDataSetCommand(COMMAND_DATABASES, myConnection); try { DataSet ds = new DataSet(); myCommand.FillDataSet(ds,"Databases"); lstDatabases.DataTextField = "Name"; lstDatabases.DataValueField = "Name"; lstDatabases.DataSource = ds.Tables["Databases"].DefaultView; lstDatabases.DataBind(); } catch (Exception eo) { HandleException(eo); } finally { if (myConnection.State == DBObjectState.Open) myConnection.Close(); } } }
Обработчик события OnChange компонента lstDatabases (lstDatabases_Change) проверяет выбранное значение в выпадающем списке выбора на предмет его совпадения с константой TEXT_REFRESH. Если выбранное значение не совпадает с константой, то метод завершает свою работу. В противном случае (когда известно, что пользователь пытается обновить базу данных) строится строка соединения с базой данных исходя из введенных пользователем значений:
string sql_conn_str = "server=" + _ServerName + ";uid=" + _UserName + _ ";password=" + _Password + ";database=Master";
Переменные _ServerName, _UserName, и _Password являются переменными членами класса AquariusWI, определяемыми в классе в ходе выполнения процедуры загрузки страницы и принимающими значения, введенные пользователем в компонентах txtServer, txtUser и txtPassword соответственно: public class AquariusWI : Page { private string _DatabaseName; private string _ServerName; private string _UserName; private string _Password; protected void Page_Load (object sender, EventArgs e) { ... _ServerName = txtServer.Text; _UserName = txtUser.Text; _Password = txtPassword.Text; ... } ... }
Теперь, когда у нас уже есть строка подключения, нам необходимо создать экземпляр объекта типа SQLConnection (соединения с базой данных) следующим образом:
SQLConnection myConnection = new SQLConnection (sql_conn_str);
Далее следует создать экземпляр ADO+ объекта типа SQLDataSetCommand, который является аналогом объекта Command ADO. Для этого воспользуемся константой
COMMAND_DATABASES. SQLDataSetCommand myCommand = new SQLDataSetCommand(COMMAND_DATABASES, myConnection);
После этого необходимо создать ADO+-объект доступа к данным — DataSet. Из предыдущей статьи известно, что объект DataSet — это представление в памяти, содержащее наборы таблиц, их отношений и связей. Объект DataTable является аналогом ADO-объекта RecordSet и служит для выполнения операций над наборами данных.
Объект DataTable состоит из единственной таблицы, которая может представлять собой результат выполнения определенной выборки, хранимой процедуры, вида и т.д. Иначе говоря, объект DataTable содержит данные, а объект DataSet содержит объект(ы) DataTable.
Далее следует блок try — catch — finally, служащий для обработки исключений. Здесь важно отметить, что метод FillDataSet объекта SQLCommand заполняет объект DataSet в зависимости от команды, заданной в SQLDataSetCommand. Второй параметр — «Databases», по сути, позволит нам обозначить результаты работы запроса определенным именем, используя которое можно будет в дальнейшем ссылаться на данные из результата этого запроса.
Далее необходимо осуществить две привязки данных в только что полученной таблице «Databases» типа DataTable с выпадающим списком выбора lstDatabases типа <asp: DropDownList>: привязку текста, показываемого пользователю с полем «Name» нашего объекта типа DataTable при помощи метода DataTextField, и привязку значения каждого пункта выпадающего списка выбора с помощью метода DataValueField.
Установка свойства DataSource нашего выпадающего списка выбора (lstDatabases) позволяет окончательно задать таблицу для связывания со списком выбора:
lstDatabases.DataSource = ds.Tables["Databases"].DefaultView;
После этого достаточно будет одного вызова метода DataBind(), который свяжет все объекты нашей страницы, для которых заданы параметры связей:
lstDatabases.DataBind();
В случае возникновения ошибки можно попросту выставить свойство «text» объекта типа Label (метка) — <asp:Label id="lblMessage"> равным значению текста, соответствующего коду возникшей ошибки:
Catch (Exception eo) { lblMessage.Text = e.Message.ToString(); }
После выполнения всех действий необходимо проверить и закрыть соединение с базой данных, если оно открыто:
finally { if(myConnection.State == DBObjectState.Open) myConnection.Close(); }
Теперь стоит разобраться с функцией загрузки страницы Page_Load, которая, заметьте, вызывается после вызова любого конструктора компонентов класса:
protected void Page_Load (object sender, EventArgs e) { if ( lstDatabases.SelectedIndex != -1 ) _DatabaseName = lstDatabases.Items [lstDatabases.SelectedIndex]. Value; _ServerName = txtServer.Text; _UserName = txtUser.Text; _Password = txtPassword.Text; if (!Page.IsPostBack) { BordersOn.Checked = true; rdoHTML.Checked = true; ListItem item = new ListItem (TEXT_BLANK, ""); lstSave.Items.Add (item); lstDatabases.Items.Add (""); lstDatabases.Items.Add (TEXT_REFRESH); } if ( ! chkSavePassword.Checked) { txtPassword.Text = ""; } }
Помимо оговоренных инициализаций переменных _ServerName, _UserName, и _Password здесь с самого начала выполняется инициализация переменной _DatabaseName, причем делается это только в том случае, если из соответствующего выпадающего списка выбора выбрано значение, то есть когда индекс выбранного значения из списка не равен -1.
Далее необходимо выяснить, является ли данная загрузка страницы первой, с тем чтобы выполнить ряд действий по инициализации компонентов нашего Web-интерфейса. Для этого лучше всего воспользоваться переменной — членом класса Page (объекта страница) IsPostBack, которая принимает значение «False», если страница загружается в первый раз и «True», если нет.
Если загрузка страницы является первой, то необходимо выставить значения «радиокнопок» BordersOn и HTML, подготовить и добавить пункт в список выбора только что выполненных запросов lstSave. Константа TEXT_BLANK служит для отображения в списке выбора, а константа "" соответствует ее значению. И наконец, необходимо проверить значение флажка запоминания пароля и обнулить поле «пароль», в случае если флажок сброшен:
txtPassword.Text = "";
Теперь разберемся с другими обработчиками событий, возникающих при действиях пользователей. Для начала рассмотрим обработчик нажатия на кнопку «Save Query», посредством которой пользователь может сохранить введенные ранее запросы и добавить их в выпадающий список выбора lstSave:
protected void btnSave_Click (object sender, EventArgs e) { String QueryValue = txtQuery.Text; int length = QUERY_LIST_MAX; if (QueryValue.Length < QUERY_LIST_MAX) length = QueryValue.Length; ListItem item = new ListItem (QueryValue.Substring(0,length), QueryValue); lstSave.Items.Add (item); }
По сути, этот компонент интерфейса должен сохранять содержимое компонента, в который вводится текст запроса всякий раз, когда пользователь потребует этого. Для начала присвоим это значение внутренней переменной QueryValue. Далее создадим локальную переменную Length и присвоим ей значение константы QUERY_LIST_MAX, которая содержит значение максимально допустимой строки запроса, показываемой в списке выбора lstSave. Если длина строки меньше константы, то необходимо сосчитать длину первой.
И наконец, необходимо добавить в список запросов строку и ее значение. Для этого, во-первых, необходимо извлечь из исходной строки ровно length символов. Воспользуемся функцией SubString, с помощью которой укажем, что строку необходимо извлечь начиная с позиции 0 и длиной в length символов от исходной:
QueryValue.Substring(0,length)
Таким образом, мы используем «обрезанную» строку для ее показа в списке и исходную, — для хранения ее в качестве значения.
Далее создадим и добавим новый элемент к списку выбора:
ListItem item = new ListItem(QueryValue.Substring(0,length), QueryValue); lstSave.Items.Add(item);
Теперь настала пора разобраться и с обработчиком нажатия на кнопку выполнения запроса — «Execute Query»:
protected void btnExecute_Click(object sender, EventArgs e) { string sql_conn_str = "server=" + _ServerName + ";uid=" + _UserName + ";password=" + _Password + ";database=" + _DatabaseName; string sql_command = txtQuery.Text; SQLConnection myConnection = new SQLConnection(sql_conn_str); SQLDataSetCommand myCommand = new SQLDataSetCommand(sql_command, myConnection); DataSet ds = new DataSet(); Try { myCommand.FillDataSet(ds,"Query"); switch (rdoXML.Checked) { case true: txtXML.Visible = true; txtXML.Text = ds.Xml; break; case false: grdResults.BorderWidth = BordersOn.Checked.ToInt32(); grdResults.DataSource = ds.Tables["Query"].DefaultView; grdResults.DataBind(); break; } } catch (Exception eo) { HandleException (eo); } finally { if (myConnection.State == DBObjectState.Open) myConnection.Close(); } btnClearResults.Visible = true; }
Здесь, как и в обработчике Change объекта lstDatabases, строится строка подключения к базе данных — sql_conn_str и также создается объект SQLDataSetCommand. Обратите внимание, что в качестве переменной текста SQL-запроса используется текст, введенный пользователем и хранящийся в переменной sql_command. Таким образом, метод SQLDataSetCommand выполнит любой запрос, находящийся в переменной sql_command, а следовательно, введенный пользователем в соответствующее текстовое поле (поле Text компонента txtQuery).
Далее попробуем выполнить метод FillDataSet только что созданного экземпляра объекта SQLDataSetCommand — myCommand и проверим значение флажка показа результатов выполнения запроса в виде таблицы (или в виде XML).
Если пользователь выбрал режим просмотра результатов в виде XML, выставим флажок видимости (Visible) соответствующего текстового поля (txtXML <asp:TextBox>) в true. После этого от нас потребуется попросту установить свойство XML заполненного объекта DataSet (ds) равным значению поля Text текстового поля txtXML <asp:TextBox>.
Если же пользователь выбрал режим просмотра результатов в виде таблицы, присвоим переменной ширины разделительных линий таблицы BorderWidth объекта типа DataList (переменная grdResults) значение, равное целочисленному значению логической переменной Checked радиокнопки-переключателя включения/выключения показа разграничительных линий BordersOn. Вот и все, осталось только насладиться простотой и гибкостью связывания данных с помощью ASP+ и ADO+, выставив значение свойства DataSource объекта типа DataList равным значению по умолчанию (DefaultView). После этого можно вызывать метод DataBind и любоваться результатом.
Рассмотренная система состоит всего из двух файлов: WebSql.aspx — страницы с представлением интерфейса и WebSql.cs — описания класса AquariusWI с обработчиками событий и методами работы приложения.
Объект DataGrid — расширение возможностей!
В заключение хотелось бы привести еще один пример, ярко иллюстрирующий преимущества ASP+ перед ASP. Для этого давайте рассмотрим, пожалуй, наиболее простую задачу, которую тем не менее наверняка приходится решать каждому Web-программисту чаще других: отображение содержимого таблицы базы данных в виде HTML-таблицы. Задача довольно простая и, в принципе, легко осуществимая с помощью ASP. Давайте оценим, насколько облегчается ее решение и насколько расширяются возможности при использовании ASP+ — компонента DataGrid. Начнем с элементарного — формирования данных в виде обычной таблицы:
<%@ language="vb" %> <%@ import namespace="system.data" %> <%@ import namespace="system.data.ado" %> <script language="vb" runat="server"> dim cnn as adoconnection dim cmd as adodatasetcommand dim ds as new dataset public sub page_load(sender as object,e as eventargs) if page.ispostback=false then cnn=new adoconnection("dsn=sample") cmd=new adodatasetcommand("select * from authors",cnn) cmd.filldataset(ds,"authors") grid1.datasource=ds.tables("authors").defaultview grid1.databind() end if end sub </script> <form id=form1 runat="server"> <asp:datagrid id="grid1" runat="server" /> </form>
Как видите, осуществляется элементарная привязка объекта типа DataGrid с источником данных. В данном случае для этого используется ODBC алиас sample, база данных Pubs и таблица Authors.
Попытаемся теперь добиться от этого компонента большей управляемости. Оставим первоначальный код без изменений, но добавим несколько штрихов к нашей таблице. Изменим лишь объявление компонента таким образом, чтобы заголовок полученной таблицы был синего цвета, а строки поочередно светло- и темно-серого.
<asp:datagrid id="grid1" runat="server" headerstyle-backcolor="blue" headerstyle-forecolor="white" alternatingitemstyle-backcolor="gray" itemstyle-backcolor="silver"/>
Отображение таблиц или данных в табличном представлении зачастую не обходится без разбиения на части. Ведь одна таблица может содержать огромное количество записей. В таком случае проблему ее «листания» приходится решать вручную, причем использования для этого традиционного способа — далеко не самое приятное занятие. В объекте DataGrid предусмотрено и это. Смотрите сами:
<%@ language="VB" %> <%@ Import Namespace="System.Data" %> <%@ Import Namespace="System.Data.ADO" %> <script language="VB" runat="server"> Sub Page_Load(sender As Object, e As EventArgs) BindGrid End Sub Sub changePage(sender As Object, e As DataGridPageChangedEventArgs) BindGrid End Sub Sub BindGrid() Dim ds As DataSet Dim cnn As ADOConnection Dim cmd As ADODataSetCommand cnn = New ADOConnection("dsn=sample") cmd = New ADODataSetCommand("select * from Authors", cnn) ds = new DataSet() cmd.FillDataSet(ds, "Authors") Grid1.DataSource=ds.Tables("Authors").DefaultView Grid1.DataBind() End Sub </script> <form runat="server"> <ASP:DataGrid id="Grid1" runat="server" AllowPaging="True" PageSize="10" PageCount="1" PagerStyle-Mode="NumericPages" PagerStyle-HorizontalAlign="Right" OnPageIndexChanged="changePage" /> </form>
Попробуем отобразить таблицу, но так, чтобы при этом можно было ее редактировать. Для обычного ASP задача далеко не тривиальная, требующая обработки не одной формы. Посмотрите и оцените сами, как просто эта задача решается с помощью ASP+:
<%@ language="vb" %> <%@ import namespace="system.data" %> <%@ import namespace="system.data.ado" %> <script language="vb" runat="server"> dim cnn as adoconnection dim cmd as adodatasetcommand dim ds as new dataset public sub page_load(sender as object,e as eventargs) if page.ispostback=false then BindGrid() end if end sub public sub BindGrid() cnn=new adoconnection("dsn=sample") cmd=new adodatasetcommand("select * from authors",cnn) cmd.filldataset(ds,"authors") grid1.datasource=ds.tables("authors").defaultview grid1.databind() end sub public sub EditRow(sender as Object, e as DataGridCommandEventArgs) grid1.EditItemIndex = e.Item.ItemIndex BindGrid() end sub public sub CancelUpdate(sender as Object, e as DataGridCommandEventArgs) grid1.EditItemIndex = -1 BindGrid() end sub public sub DeleteRow(sender as Object,e as DataGridCommandEventArgs) end sub public sub UpdateRow(sender as Object,e as DataGridCommandEventArgs) dim txt1 as textbox dim txt2 as textbox txt1=e.Item.FindControl("txtAuthor") txt2=e.Item.FindControl("txtYear") end sub </script> <form runat=server> <asp:datagrid id="grid1" runat="server" OnEditCommand="EditRow" OnCancelCommand="CancelUpdate" OnUpdateCommand="UpdateRow" OnDeleteCommand="DeleteRow" DataKeyField="au_id" AutoGenerateColumns="false" > <property name="Columns"> <asp:EditCommandColumn EditText="Edit" CancelText="Cancel" UpdateText="Update" /> <asp:BoundColumn Headertext="ID" DataField="au_id" ReadOnly="true"/> <asp:TemplateColumn headertext="Name"> <template name="ItemTemplate"> <asp:Label Text='<%# Container.DataItem("au_lname") %>' runat="server"/> </template> <template name="EditItemTemplate"> <asp:TextBox id="txtAuthor" Text='<%# Container.DataItem("au_lname") %>' runat="server"/> </template> </asp:TemplateColumn> <asp:TemplateColumn headertext="Year Born"> <template name="ItemTemplate"> <asp:Label Text='<%# Container.DataItem("phone") %>' runat="server"/> </template> <template name="EditItemTemplate"> <asp:TextBox id="txtYear" Text='<%# Container.DataItem("phone") %>' runat="server"/> </template> </asp:TemplateColumn> </property> </asp:datagrid> </form>
Объяснения излишни: код говорит сам за себя. Пожалуй, это основное достоинство ASP+ — «красноречивость» кода. Попробуйте скопировать его в .aspx-файл и выполнить. Поверьте, результат и то, какими небольшими усилиями он может быть достигнут, вас приятно удивит.
Как видите, все довольно просто, однако эта простота обусловлена наличием в ASP+ богатой палитры визуальных компонентов, позволяющих заменять целые группы обычных HTML-компонентов, использовавшихся в обычном ASP. Достоинство этих компонентов (в данном случае, например, компонентов DataList или DataGrid) заключается не только в визуализации разнообразных данных и процессов, но и в богатых возможностях обработки событий, наличия огромного количества типовых решений и всевозможных шаблонов для выполнения типовых операций и т.д. И это еще одно преимущество ASP+.
КомпьютерПресс 3'2001