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

Наш канал на Youtube

1999 1 2 3 4 5 6 7 8 9 10 11 12
2000 1 2 3 4 5 6 7 8 9 10 11 12
2001 1 2 3 4 5 6 7 8 9 10 11 12
2002 1 2 3 4 5 6 7 8 9 10 11 12
2003 1 2 3 4 5 6 7 8 9 10 11 12
2004 1 2 3 4 5 6 7 8 9 10 11 12
2005 1 2 3 4 5 6 7 8 9 10 11 12
2006 1 2 3 4 5 6 7 8 9 10 11 12
2007 1 2 3 4 5 6 7 8 9 10 11 12
2008 1 2 3 4 5 6 7 8 9 10 11 12
2009 1 2 3 4 5 6 7 8 9 10 11 12
2010 1 2 3 4 5 6 7 8 9 10 11 12
2011 1 2 3 4 5 6 7 8 9 10 11 12
2012 1 2 3 4 5 6 7 8 9 10 11 12
2013 1 2 3 4 5 6 7 8 9 10 11 12
Популярные статьи
КомпьютерПресс использует