<% 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








