Создание интерфейса для выбора одной учетной записи пользователя из многих (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 . Назовите страницы:

  • ManageUsers.aspx
  • UserInformation.aspx

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

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

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

Мы хотим отобразить разметку эталонной страницы по умолчанию для 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. Карта сайта включает узел с названием "Администрирование пользователей" (Нажмите, чтобы просмотреть изображение в полном размере)

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

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

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

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

Добавьте три BoundFields и три CheckBoxFields в GridView

Рис. 3. Добавление трех ограничивающих полей и трех 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, UserAccounts GridView перечисляет имя пользователя, адрес электронной почты и другие соответствующие сведения об учетной записи для всех пользователей в системе.

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

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

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

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

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

Начните с добавления элемента управления Repeater на страницу над UserAccounts GridView. Задайте для свойства Repeater ID значение FilteringUI. Настройте шаблоны Repeater таким образом, чтобы он ItemTemplate отображал LinkButton, свойства которого TextCommandName привязаны к текущему элементу массива. Как мы видели в руководстве по назначению ролей пользователям, это можно сделать с помощью синтаксиса 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>

Чтобы заполнить этот повторитель нужными параметрами фильтрации, создайте метод с именем 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 страница при просмотре через браузер.

Репитер c 27 фильтрующими кнопками-ссылками

Рис. 5. Список повторяющихся списков 27 фильтрующих ссылок (щелкните, чтобы просмотреть изображение полного размера)

Замечание

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

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

Начните с обновления ManageUser.aspx класса code-behind страницы, чтобы он включает свойство с именем 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("%"), тем самым возвращая все учетные записи пользователей.

Создайте обработчик событий для события Повторителя ItemCommand . Это событие поднимается, когда происходит щелчок на одной из кнопок фильтра LinkButton; она передает значение нажатой кнопки LinkButton через объект 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'.

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

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

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

GridView, показанный на рисунке 5 и 6, содержит список всех записей, возвращаемых из FindUsersByName метода. Если есть сотни или тысячи учетных записей пользователей, это может привести к перегрузке информации при просмотре всех учетных записей (как это происходит при нажатии кнопки All LinkButton или при первоначальном посещении страницы). Чтобы предоставить учетные записи пользователей в более управляемых блоках, давайте настроим 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 LinkButtons. При нажатии кнопки First LinkButton пользователь перейдет на первую страницу данных, в то время как кнопка Previous LinkButton возвратит его на предыдущую страницу. Аналогичным образом, next and 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 показаны четыре LinkButtons при просмотре с помощью представления дизайна Visual Web Developer.

Добавление кнопок-ссылок

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

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

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

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. Кроме того, необходимо включить или отключить интерфейс разбиения на страницы в зависимости от отображаемой страницы. При просмотре первой страницы данных необходимо отключить ссылки first и Previous; При просмотре последней страницы необходимо отключить следующую и последнюю страницу.

Обновите метод 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 для последней кнопки-ссылки немного сложнее, так как нам нужно определить количество отображаемых записей, чтобы установить индекс последней страницы.

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. Нажатие следующей ссылки отображает вторую страницу учетных записей пользователей (щелкните, чтобы просмотреть изображение полного размера)

Сводка

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

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

Дальнейшее чтение

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

Сведения о авторе

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

Особое спасибо кому

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