Создание интерфейса для выбора одной учетной записи пользователя из многих (C#)

Скотт Митчелл

В этом руководстве мы создадим пользовательский интерфейс со страничной фильтруемой сеткой. В частности, наш пользовательский интерфейс будет состоять из серии LinkButtons для фильтрации результатов на основе начальной буквы имени пользователя и элемента управления GridView для отображения соответствующих пользователей. Начнем со списка всех учетных записей пользователей в GridView. Затем на шаге 3 мы добавим фильтр LinkButtons. На шаге 4 рассматривается разбиение отфильтрованные результаты по страницам. Интерфейс, созданный в шагах 2–4, будет использоваться в последующих руководствах для выполнения административных задач для конкретной учетной записи пользователя.

Введение

В руководстве Назначение ролей пользователям мы создали элементарный интерфейс для администратора, чтобы выбрать пользователя и управлять его ролями. В частности, интерфейс предоставил администратору раскрывающийся список всех пользователей. Такой интерфейс подходит, когда есть только дюжина или около того учетных записей пользователей, но является громоздким для сайтов с сотнями или тысячами учетных записей. Выстраивная фильтруемая сетка больше подходит для веб-сайтов с большими пользовательскими базами.

В этом руководстве мы создадим такой пользовательский интерфейс. В частности, наш пользовательский интерфейс будет состоять из серии LinkButtons для фильтрации результатов на основе начальной буквы имени пользователя и элемента управления GridView для отображения соответствующих пользователей. Начнем со списка всех учетных записей пользователей в GridView. Затем на шаге 3 мы добавим фильтр LinkButtons. На шаге 4 рассматривается разбиение отфильтрованные результаты по страницам. Интерфейс, созданный в шагах 2–4, будет использоваться в последующих руководствах для выполнения административных задач для конкретной учетной записи пользователя.

Приступим к работе!

Шаг 1. Добавление новых страниц ASP.NET

В этом руководстве и в следующих двух мы рассмотрим различные функции и возможности, связанные с администрированием. Нам потребуется ряд ASP.NET страниц для реализации тем, рассмотренных в этих руководствах. Давайте создадим эти страницы и обновим карту сайта.

Для начала создайте в проекте папку с именем Administration. Затем добавьте две новые страницы ASP.NET в папку, связав каждую страницу со страницей Site.master master. Назовите страницы:

  • ManageUsers.aspx
  • UserInformation.aspx

Также добавьте две страницы в корневой каталог веб-сайта: ChangePassword.aspx и RecoverPassword.aspx.

На этом этапе эти четыре страницы должны иметь два элемента управления Содержимое, по одному для каждого из master страницы ContentPlaceHolders: MainContent и LoginContent.

<asp:Content ID="Content1" ContentPlaceHolderID="MainContent" Runat="Server">
</asp:Content>
<asp:Content ID="Content2" ContentPlaceHolderID="LoginContent" Runat="Server">
</asp:Content>

Мы хотим показать разметку страницы master по умолчанию для LoginContent ContentPlaceHolder для этих страниц. Поэтому удалите декларативную разметку для элемента управления Содержимое Content2 . После этого разметка страниц должна содержать только один элемент управления Содержимое.

Страницы ASP.NET в папке Administration предназначены исключительно для пользователей с правами администратора. Мы добавили в систему роль Администраторы в руководстве по созданию ролей и управлению ими. Ограничьте доступ к этим двум страницам этой ролью. Для этого добавьте Web.config файл в папку Administration и настройте его <authorization> элемент, чтобы разрешить пользователям роль администраторов и запретить все остальные.

<?xml version="1.0"?>
<configuration>
 <system.web>
 <authorization>
 <allow roles="Administrators" />
 <deny users="*"/>
 </authorization>
 </system.web>
</configuration>

На этом этапе Обозреватель решений проекта должны выглядеть примерно так, как на снимке экрана, показанном на рисунке 1.

На веб-сайт добавлены четыре новые страницы и файл Web.config

Рис. 1. На веб-сайт добавлены четыре новые страницы и Web.config файл (щелкните для просмотра полноразмерного изображения)

Наконец, обновите карту сайта (Web.sitemap), чтобы включить запись на страницу ManageUsers.aspx . Добавьте следующий XML-код после того, <siteMapNode> как мы добавили для руководств по ролям.

<siteMapNode title="User Administration" url="~/Administration/ManageUsers.aspx"/>

После обновления карты сайта посетите сайт через браузер. Как показано на рисунке 2, навигация слева теперь содержит элементы для учебников по администрированию.

Схема сайта включает узел с заголовком администрирование пользователей

Рис. 2. Схема сайта включает узел с заголовком User Administration (Щелкните, чтобы просмотреть полноразмерное изображение)

Шаг 2. Перечисление всех учетных записей пользователей в GridView

Наша конечная цель в этом руководстве — создать фильтруемую сетку со страницами, с помощью которой администратор может выбрать учетную запись пользователя для управления. Начнем с перечисления всех пользователей в GridView. После этого мы добавим интерфейсы и функции фильтрации и разбиения по страницам.

Откройте страницу ManageUsers.aspx в папке Administration и добавьте GridView, задав для нее ID значение UserAccounts. Через некоторое время мы напишем код для привязки набора учетных записей пользователей к GridView с помощью Membership метода класса GetAllUsers . Как обсуждалось в предыдущих руководствах, метод GetAllUsers возвращает MembershipUserCollection объект , который представляет собой коллекцию MembershipUser объектов . Каждая MembershipUser коллекция содержит такие свойства, как UserName, Email, IsApprovedи т. д.

Чтобы отобразить нужные сведения об учетной записи пользователя в GridView, задайте для свойства GridView AutoGenerateColumns значение False и добавьте BoundFields для UserNameсвойств , Emailи Comment и CheckBoxFields для IsApprovedсвойств , IsLockedOutи IsOnline . Эту конфигурацию можно применить с помощью декларативной разметки элемента управления или диалогового окна Поля. На рисунке 3 показан снимок экрана диалогового окна Поля после того, как флажок Автоматическое создание полей был снят, а также добавлены и настроены BoundFields и CheckBoxFields.

Добавление three BoundFields и Three CheckBoxFields в GridView

Рис. 3. Добавление трех полей BoundField и трех CheckBoxFields в GridView (щелкните для просмотра полноразмерного изображения)

После настройки GridView убедитесь, что его декларативная разметка выглядит примерно так:

<asp:GridView ID="UserAccounts" runat="server" AutoGenerateColumns="False">
 <Columns>
 <asp:BoundField DataField="UserName" HeaderText="UserName"/>
 <asp:BoundField DataField="Email" HeaderText="Email" />
 <asp:CheckBoxField DataField="IsApproved" HeaderText="Approved?"/>
 <asp:CheckBoxField DataField="IsLockedOut" HeaderText="Locked Out?" />
 <asp:CheckBoxField DataField="IsOnline" HeaderText="Online?"/>
 <asp:BoundField DataField="Comment" HeaderText="Comment"/>
 </Columns>
</asp:GridView>

Далее необходимо написать код, который привязывает учетные записи пользователей к GridView. Создайте метод с именем BindUserAccounts для выполнения этой задачи, а затем вызовите его из обработчика Page_Load событий при первом посещении страницы.

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    BindUserAccounts();
}

private void BindUserAccounts()
{
    UserAccounts.DataSource = Membership.GetAllUsers();
    UserAccounts.DataBind();
}

Проверьте страницу в браузере. Как показано на рисунке 4, GridView UserAccounts перечисляет имя пользователя, адрес электронной почты и другие соответствующие сведения об учетной записи для всех пользователей в системе.

Учетные записи пользователей перечислены в GridView

Рис. 4. Учетные записи пользователей перечислены в GridView (щелкните, чтобы просмотреть полноразмерное изображение)

Шаг 3. Фильтрация результатов по первой букве имени пользователя

В настоящее время в UserAccounts GridView отображаются все учетные записи пользователей. Для веб-сайтов с сотнями или тысячами учетных записей пользователей крайне важно, чтобы пользователь мог быстро проанализировать отображаемые учетные записи. Это можно сделать, добавив фильтрацию LinkButtons на страницу. Давайте добавим на страницу 27 linkButtons: один с заголовком Все вместе с одним LinkButton для каждой буквы алфавита. Если посетитель щелкает кнопку Все ссылки, gridView отобразит всех пользователей. Если щелкнуть определенную букву, будут отображаться только те пользователи, имя пользователя которых начинается с выбранной буквы.

Наша первая задача — добавить 27 элементов управления LinkButton. Один из вариантов — создать 27 LinkButtons декларативно, по одному за раз. Более гибкий подход заключается в использовании элемента управления Repeater с элементом ItemTemplate управления , который отрисовывает LinkButton, а затем привязывает параметры фильтрации к repeater в виде массива string .

Начните с добавления элемента управления Repeater на страницу над UserAccounts GridView. Задайте для свойства Repeater ID значение FilteringUI. Настройте шаблоны Repeater таким образом, чтобы он ItemTemplate отображал элемент LinkButton, свойства и CommandName которого Text привязаны к текущему элементу массива. Как мы видели в руководстве Назначение ролей пользователям , это можно сделать с помощью синтаксиса Container.DataItem привязки данных. Используйте повторитель SeparatorTemplate для отображения вертикальной линии между каждой ссылкой.

<asp:Repeater ID="FilteringUI" runat="server">
 <ItemTemplate>
 <asp:LinkButton runat="server" ID="lnkFilter"
 Text='<%# Container.DataItem %>'
 CommandName='<%# Container.DataItem %>'></asp:LinkButton>
 </ItemTemplate>
 <SeparatorTemplate>|</SeparatorTemplate>
</asp:Repeater>

Чтобы заполнить этот repeater нужными параметрами фильтрации, создайте метод с именем BindFilteringUI. Обязательно вызовите этот метод из обработчика Page_Load событий при первой загрузке страницы.

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        BindUserAccounts();
        BindFilteringUI();
    }
}

private void BindFilteringUI()
{
    string[] filterOptions = { "All", "A", "B", "C","D", "E", "F", "G", "H", "I","J", "K", "L", "M", "N", "O","P", "Q", "R", "S", "T", "U","V", "W", "X", "Y", "Z" };
    FilteringUI.DataSource = filterOptions;
    FilteringUI.DataBind();
}

Этот метод задает параметры фильтрации в виде элементов в массиве stringfilterOptions. Для каждого элемента в массиве Repeater отрисовывает элемент LinkButton со свойствами Text и CommandName , назначенными значению элемента массива.

На рисунке 5 показана ManageUsers.aspx страница при просмотре в браузере.

Элемент repeater Списки 27 Filtering LinkButtons

Рис. 5. Элемент Repeater Списки 27 Filtering LinkButtons (Щелкните для просмотра полноразмерного изображения)

Примечание

Имена пользователей могут начинаться с любого символа, включая цифры и знаки препинания. Чтобы просмотреть эти учетные записи, администратор должен использовать параметр All LinkButton . Кроме того, можно добавить LinkButton, чтобы вернуть все учетные записи пользователей, начинающиеся с числа. Я оставляю это как упражнение для читателя.

Если щелкнуть любой из фильтров linkButtons, возникает обратная связь и возникает событие Repeater ItemCommand , но в сетке нет изменений, так как нам еще предстоит написать код для фильтрации результатов. Класс Membership включает FindUsersByName метод , который возвращает учетные записи пользователей, имя пользователя которых соответствует указанному шаблону поиска. Этот метод можно использовать для получения только тех учетных записей пользователей, имена пользователей которых начинаются с буквы, указанной CommandName в отфильтрованном элементе LinkButton, который был нажат.

Начните с обновления ManageUser.aspx класса кода программной части страницы таким образом, чтобы он был включен в свойство с именем UsernameToMatch. Это свойство сохраняет строку фильтра имени пользователя в обратных передачах:

private string UsernameToMatch
{
 get
 {
 object o = ViewState["UsernameToMatch"];
 if (o == null)
 return string.Empty;
 else
 return (string)o;
 }
 set
 {
 ViewState["UsernameToMatch"] = value;
 }
}

Свойство UsernameToMatch сохраняет значение, присвоенное ViewState коллекции с помощью ключа UsernameToMatch. Когда значение этого свойства считывается, оно проверяет, существует ли значение в ViewState коллекции. В противном случае возвращается значение по умолчанию — пустая строка. Свойство UsernameToMatch демонстрирует общий шаблон, а именно сохранение значения для просмотра состояния, чтобы любые изменения свойства сохранялись в обратных данных. Дополнительные сведения об этом шаблоне см. в статье Основные сведения о состоянии представления ASP.NET.

Затем обновите BindUserAccounts метод таким образом, чтобы вместо вызова Membership.GetAllUsersон вызывает Membership.FindUsersByName, передав значение свойства с подстановочным UsernameToMatch знаком SQL , %.

private void BindUserAccounts()
{
    UserAccounts.DataSource = Membership.FindUsersByName(this.UsernameToMatch + "%");
    UserAccounts.DataBind();
}

Чтобы отобразить только тех пользователей, имя пользователя которых начинается с буквы A, задайте UsernameToMatch для свойства значение A, а затем вызовите BindUserAccounts. Это приведет к Membership.FindUsersByName("A%")вызову , который вернет всех пользователей, имя пользователя которых начинается с A. Аналогичным образом, чтобы вернуть всех пользователей, назначьте свойству UsernameToMatch пустую строку, чтобы BindUserAccounts метод вызывал Membership.FindUsersByName("%"), тем самым возвращая все учетные записи пользователей.

Создайте обработчик событий для события Repeater ItemCommand . Это событие возникает каждый раз, когда щелкается один из фильтров LinkButtons; значение linkButton CommandName передается через RepeaterCommandEventArgs объект . Необходимо назначить соответствующее значение свойству UsernameToMatch , а затем вызвать BindUserAccounts метод . CommandName Если имеет значение All, назначьте пустую строку , UsernameToMatch чтобы отображались все учетные записи пользователей. В противном случае назначьте CommandName значение UsernameToMatch.

protected void FilteringUI_ItemCommand(object source, RepeaterCommandEventArgs e)
{
    if (e.CommandName == "All")
        this.UsernameToMatch = string.Empty;
    else
        this.UsernameToMatch e.CommandName;
    BindUserAccounts();
}

Используя этот код, протестируйте функцию фильтрации. При первом посещении страницы отображаются все учетные записи пользователей (см. рис. 5). Щелчок элемента A LinkButton вызывает обратную передачу и фильтрует результаты, отображая только те учетные записи пользователей, которые начинаются с A .

Используйте фильтр linkButtons для отображения пользователей, имя пользователя которых начинается с определенной буквы

Рис. 6. Использование ссылок фильтрации для отображения пользователей, имя пользователя которых начинается с определенной буквы (щелкните для просмотра полноразмерного изображения)

Шаг 4. Обновление GridView для использования разбиения по страницам

GridView, показанный на рис. 5 и 6, перечисляет все записи, возвращенные методом FindUsersByName . Если есть сотни или тысячи учетных записей пользователей, это может привести к перегрузке информации при просмотре всех учетных записей (как в случае при нажатии кнопки Все ссылки или при первоначальном посещении страницы). Чтобы упростить представление учетных записей пользователей в виде более управляемых блоков, давайте настроим GridView для отображения 10 учетных записей пользователей за раз.

Элемент управления GridView предлагает два типа разбиения по страницам:

  • Разбиение на разбиение по умолчанию — простое в реализации, но неэффективное. В двух словах, при разбиении по страницам по умолчанию GridView ожидает все записи из своего источника данных. Затем отображается только соответствующая страница записей.
  • Настраиваемое разбиение на разбиение по страницам — требует больше работы, но это более эффективно, чем разбиение по страницам по умолчанию, так как при использовании пользовательского разбиения по страницам источник данных возвращает только точный набор записей для отображения.

Разница в производительности между разбиением по умолчанию и пользовательским разбиением по страницам может быть весьма существенной при разбиении по страницам по тысячам записей. Так как мы строим этот интерфейс, предполагая, что учетных записей пользователей могут быть сотни или тысячи, давайте будем использовать пользовательское разбиение по страницам.

Примечание

Более подробное обсуждение различий между разбиением по умолчанию и пользовательскими разбиения по страницам, а также о проблемах, связанных с реализацией пользовательского разбиения по страницам, см. в статье Эффективное разбиение по страницам с помощью больших объемов данных.

Для реализации пользовательского разбиения по страницам сначала требуется механизм, с помощью которого можно получить точное подмножество записей, отображаемых GridView. Хорошей новостью Membership является то, что метод класса FindUsersByName имеет перегрузку, которая позволяет указать индекс страницы и размер страницы и возвращает только те учетные записи пользователей, которые попадают в этот диапазон записей.

В частности, эта перегрузка имеет следующую сигнатуру: FindUsersByName(usernameToMatch, pageIndex, pageSize, totalRecords).

Параметр pageIndex указывает страницу учетных записей пользователей для возврата; pageSize указывает, сколько записей будет отображаться на странице. Параметр totalRecords — это параметр, возвращающий out общее количество учетных записей пользователей в хранилище пользователей.

Примечание

Данные, возвращаемые параметром , FindUsersByName сортируются по имени пользователя; условия сортировки не могут быть настроены.

GridView можно настроить для использования пользовательского разбиения по страницам, но только при привязке к элементу управления ObjectDataSource. Для реализации пользовательского разбиения по страницам элементу управления ObjectDataSource требуются два метода: один передает индекс начальной строки и максимальное количество отображаемых записей и возвращает точное подмножество записей, которые входят в этот диапазон; и метод, возвращающий общее количество записей, которые выстраиваются на страницы. Перегрузка FindUsersByName принимает индекс страницы и размер страницы и возвращает общее количество записей с помощью out параметра . Таким образом, здесь есть несоответствие интерфейса.

Одним из вариантов является создание прокси-класса, который предоставляет интерфейс, ожидаемый ObjectDataSource, а затем внутренний FindUsersByName вызов метода . Другой вариант , который мы будем использовать для этой статьи, заключается в создании собственного интерфейса разбиения по страницам и использовании его вместо встроенного интерфейса GridView для разбиения по страницам.

Создание интерфейса first, previous, next, last paging interface

Давайте создадим интерфейс разбиения на страницы с атрибутами First, Previous, Next и Last LinkButtons. При нажатии кнопки First LinkButton пользователь перейдет на первую страницу данных, в то время как Назад возвращает его на предыдущую страницу. Аналогичным образом, Next и Last переместят пользователя на следующую и последнюю страницы соответственно. Добавьте четыре элемента управления LinkButton под UserAccounts GridView.

<p>
 <asp:LinkButton ID="lnkFirst" runat="server"> First</asp:LinkButton> |
 <asp:LinkButton ID="lnkPrev" runat="server">  Prev</asp:LinkButton>|
 <asp:LinkButton ID="lnkNext" runat="server">Next  </asp:LinkButton>|
 <asp:LinkButton ID="lnkLast" runat="server">Last  </asp:LinkButton>
</p>

Затем создайте обработчик событий для каждого события LinkButton Click .

На рисунке 7 показаны четыре элемента LinkButton в представлении Visual Web Developer Design.

Добавление параметров First, Previous, Next и Last LinkButtons под GridView

Рис. 7. Добавление первой, предыдущей, следующей и последней ссылок под GridView (щелкните для просмотра полноразмерного изображения)

Отслеживание текущего индекса страницы

Когда пользователь впервые посещает страницу ManageUsers.aspx или нажимает одну из кнопок фильтрации, мы хотим отобразить первую страницу данных в GridView. Однако когда пользователь щелкает один из ссылок навигации LinkButtons, необходимо обновить индекс страницы. Чтобы сохранить индекс страницы и количество записей, отображаемых на странице, добавьте следующие два свойства в класс кода программной части страницы:

private int PageIndex
{
 get
 {
 object o = ViewState["PageIndex"];
 if (o == null)
 return 0;
 else
 return (int)o;
 }
 set
 {
 ViewState["PageIndex"] = value;
 }
}

private int PageSize
{
 get
 {
 return 10;
 }
}

Как и свойство UsernameToMatch , PageIndex свойство сохраняет свое значение для просмотра состояния. Свойство только PageSize для чтения возвращает жестко закодированное значение 10. Я приглашаю заинтересованного читателя обновить это свойство, чтобы использовать тот же шаблон PageIndex, что и , а затем расширить ManageUsers.aspx страницу таким образом, чтобы пользователь, посещающий страницу, смог указать, сколько учетных записей пользователей будет отображаться на странице.

Получение только записей текущей страницы, обновление индекса страницы, а также включение и отключение ссылок интерфейса подкачки

При наличии интерфейса подкачки и добавлении PageIndex свойств и PageSize мы готовы обновить BindUserAccounts метод таким образом, чтобы он использовал соответствующую FindUsersByName перегрузку. Кроме того, необходимо включить или отключить интерфейс разбиения по страницам в зависимости от отображаемой страницы. При просмотре первой страницы данных ссылки Первая и Предыдущая должны быть отключены; Next и Last должны быть отключены при просмотре последней страницы.

Обновите метод BindUserAccounts, используя следующий код:

private void BindUserAccounts()
{
 int totalRecords;
 UserAccounts.DataSource = Membership.FindUsersByName(this.UsernameToMatch + "%",this.PageIndex, this.PageSize, out totalRecords);
 UserAccounts.DataBind();

 // Enable/disable the paging interface
 bool visitingFirstPage = (this.PageIndex == 0);
 lnkFirst.Enabled = !visitingFirstPage;
 lnkPrev.Enabled = !visitingFirstPage;

 int lastPageIndex = (totalRecords - 1) / this.PageSize;
 bool visitingLastPage = (this.PageIndex >= lastPageIndex);
 lnkNext.Enabled = !visitingLastPage;
 lnkLast.Enabled = !visitingLastPage;
}

Обратите внимание, что общее количество записей, которые выстраиваются на страницы, определяется последним параметром FindUsersByName метода . Это out параметр, поэтому необходимо сначала объявить переменную для хранения этого значения (totalRecords), а затем добавить в нее out префикс ключевое слово.

После возврата указанной страницы учетных записей пользователей четыре элемента LinkButton либо включаются, либо отключаются в зависимости от того, просматривается ли первая или последняя страница данных.

Последним шагом является написание кода для четырех обработчиков событий LinkButtons Click . Эти обработчики событий должны обновить PageIndex свойство , а затем повторно привязать данные к GridView с помощью вызова BindUserAccounts. Обработчики событий First, Previous и Next очень просты. Обработчик Click событий для Last LinkButton, однако, немного сложнее, так как нам нужно определить, сколько записей отображается, чтобы определить индекс последней страницы.

protected void lnkFirst_Click(object sender, EventArgs e)
{
 this.PageIndex = 0;
 BindUserAccounts();
}

protected void lnkPrev_Click(object sender, EventArgs e)
{
 this.PageIndex -= 1;
 BindUserAccounts();
}

protected void lnkNext_Click(object sender, EventArgs e)
{
 this.PageIndex += 1;
 BindUserAccounts();
}

protected void lnkLast_Click(object sender, EventArgs e)
{
 // Determine the total number of records
 int totalRecords;
 Membership.FindUsersByName(this.UsernameToMatch + "%", this.PageIndex,this.PageSize, out totalRecords);
 // Navigate to the last page index
 this.PageIndex = (totalRecords - 1) / this.PageSize;
 BindUserAccounts();
}

На рисунках 8 и 9 показан пользовательский интерфейс подкачки в действии. На рисунке 8 показана ManageUsers.aspx страница при просмотре первой страницы данных для всех учетных записей пользователей. Обратите внимание, что отображаются только 10 из 13 учетных записей. При нажатии на ссылку Далее или Последняя происходит обратная связь, обновляется PageIndex до 1 и привязывается вторая страница учетных записей пользователей к сетке (см. рис. 9).

Отображаются первые 10 учетных записей пользователей

Рис. 8. Отображаются первые 10 учетных записей пользователей (щелкните для просмотра полноразмерного изображения)

При нажатии следующей ссылки отображается вторая страница учетных записей пользователей

Рис. 9. При щелчке по следующей ссылке отображается вторая страница учетных записей пользователей (щелкните для просмотра полноразмерного изображения)

Сводка

Администраторам часто требуется выбрать пользователя из списка учетных записей. В предыдущих руководствах мы рассмотрели использование раскрывающегося списка, заполненного пользователями, но этот подход не очень хорошо масштабируется. В этом руководстве мы рассмотрели лучшую альтернативу: фильтруемый интерфейс, результаты которого отображаются в выстраивном GridView. С помощью этого пользовательского интерфейса администраторы могут быстро и эффективно находить и выбирать одну учетную запись пользователя из тысяч.

Счастливого программирования!

Дополнительные материалы

Дополнительные сведения о темах, рассмотренных в этом руководстве, см. в следующих ресурсах:

Об авторе

Скотт Митчелл(Scott Mitchell), автор нескольких книг ASP/ASP.NET и основатель 4GuysFromRolla.com, работает с веб-технологиями Майкрософт с 1998 года. Скотт работает независимым консультантом, тренером и писателем. Его последняя книга Sams Teach Yourself ASP.NET 2.0 в 24 часах. Скотт может быть доступен в mitchell@4guysfromrolla.com или через его блог по адресу http://ScottOnWriting.NET.

Особая благодарность

Эта серия учебников была рассмотрена многими полезными рецензентами. Ведущим рецензентом этого руководства была Алиця Мазиарц. Хотите просмотреть предстоящие статьи MSDN? Если да, бросить мне строку на mitchell@4GuysFromRolla.com