Разработка расширенных веб-частей

148

Реализация веб-частей через пользовательские элементы управления является довольно простым способом создания веб-частей. Однако пользовательские элементы управления также обладают и рядом недостатков:

Таким образом, иногда реализация расширенных веб-частей в виде серверных элементов управления, унаследованных от WebPart, бывает полезной. Обладая основными знаниями относительно создания специальных серверных элементов управления ASP.NET, вы готовы к построению веб-частей такого рода. Все, что нужно иметь в виду при создании специальной веб-части подобным образом - это то, что страницы ASP.NET и элементы управления ASP.NET обрабатываются исполняющей средой (которая определяет порядок элементов управления и событий страницы, а также действия для каждого из этих событий). В результате вы всегда знаете, какие шаги необходимо предпринять для реализации.

Ниже перечислены шаги по созданию пользовательской веб-части. (Они покажутся знакомыми, если вы вспомните жизненный цикл страницы ASP.NET и элемента управления)

  1. Наследование от WebPart. Первым делом должен быть создан простой класс, унаследованный от System.Web.UI.WebControls.WebParts.WebPart.

  2. Добавление специальных свойств. Затем следует добавить специальные свойства веб-части и указать с помощью атрибутов, какие из свойств могут редактироваться пользователем, какие из свойств сохраняются отдельно для каждого пользователя в хранилище персонализации, а какие - разделяются между всеми пользователями.

  3. Написание кода инициализации и загрузки. Теперь необходимо переопределить любую необходимую процедуру инициализации. Обычно для создания составного элемента управления или веб-части переопределяются методы OnInit() и CreateChildControls().

    В большинстве случаев должны создаваться составные элементы управления, потому что это позволяет сэкономить на ручном написании кода генерации HTML-разметки. Во время фазы инициализации также можно загружать информацию из баз данных, а на фазе загрузки (перехватывая событие Load или переопределяя метод OnLoad} можно инициализировать другие свойства веб-части (или серверного элемента управления).

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

  5. Подготовка к генерации. Прежде чем стартует фаза генерации, необходимо выполнить последние задачи, такие как установка свойств элементов управления и построение структуры элементов управления на основе источников данных, к которым они привязаны (например, вызов метода DataBind(), если не используется новая программная модель с источниками данных).

  6. Генерация HTML-разметки. Наконец, потребуется написать код для генерации веб-части. На этот раз не нужно переопределять метод RenderControl() (как в случае серверных элементов управления). Понадобится переопределить метод RenderContents, который вызывается из базового класса между генерацией рамки, панели заголовка и меню заголовка с соответствующими пунктами.

Если помнить об этих шагах, создание специальной веб-части не составит особого труда (хотя это и не настолько просто, как создание веб-частей на основе пользовательских элементов управления). Давайте создадим простую веб-часть с использованием описанного подхода. Эта веб-часть должна позволить заказчикам добавлять свои замечания в таблицу CustomerNotes, которую нужно будет создать в тестовой базе данных Northwind:

ALTER TABLE Customers 
ADD CONSTRAINT [FK_Customers] FOREIGN KEY  (CustomerID) REFERENCES [dbo].[Customers ] ([CustomerID]);

CREATE TABLE CustomerNotes(
    CustomerID NCHAR (5) NOT NULL,
    NoteID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
	NoteDate DATETIME,
	NoteContent NTEXT,
    CONSTRAINT [FK_Notes_Customers] FOREIGN KEY ([CustomerID]) REFERENCES [dbo].[Customers] ([CustomerID])
)

Создание типизированных DataSet

Прежде чем погружаться в детали разработки веб-части, необходимо добавить специальные компоненты для упрощения доступа к данным, хранящимся в базе. (Эти компоненты также понадобятся для завершения примеров кода.)

В веб-частях, разрабатываемых в этой статье, нужен доступ к данным из таблиц Customer и CustomerNotes. Необходимо создать простой типизированный DataSet для доступа к обеим таблицам, который будет добавлен в проект приложения, как показано на рисунке ниже:

Типизированные DataSet, необходимые для решения

Для этого щелкните правой кнопкой мыши на проекте, выберите в контекстном меню пункт Add New Item (Добавить новый элемент) и в открывшемся диалоговом окне укажите DataSet. Назовите его CustomerSet. После добавления DataSet к проекту (вспомните, что Visual Studio добавит его в каталог App_Code в случае разработки беспроектного веб-сайта) можно перетащить таблицы Customer и CustomerNotes из окна Server Explorer на поверхность DataSet.

Для этих целей должно быть сконфигурировано соединение в Server Explorer. Обратите внимание, что если используется файловая база данных SQL Server Express, это соединение появляется в Server Explorer автоматически.

Типизированные DataSet расширяют класс DataSet и предоставляют типизированные адаптеры таблиц, которые используются для разработки остальных частей веб-приложения. Как видно на рисунке, для таблицы CustomerNotes мы добавили второй запрос к типизированному DataSet (щелкните правой кнопкой мыши на DataTable в конструкторе и выберите в контекстном меню пункт Add --> Query (Добавить --> Запрос)).

SELECT NoteID, CustomerID, NoteDate, NoteContent FROM dbo.CustomerNotes
	WHERE CustomerID=@custID

Этот запрос будет применяться позже для добавления замечаний, относящихся к выделенному заказчику. Поэтому запрос имеет параметр по имени CustomerId, который позволяет передавать идентификатор заказчика, замечания которого нужно извлечь. И последнее: в общем случае, прежде чем приступать к разработке компонентов пользовательского интерфейса, всегда должен создаваться уровень бизнес-логики и уровень доступа к данным, а веб-части - это определенно компоненты пользовательского интерфейса. Компоненты уровня бизнес-логики и уровня доступа к данным являются повторно используемыми между разными приложениями, как эти два типизированных DataSet.

Структура специальной веб-части

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

using System;
using System.Data;
using System.Drawing;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Collections.Generic;

// Пространство имен сгенерированное типизированным DataSet
using CustomerSetTableAdapters;

namespace ProfessorWeb.WebParts
{
	public class CustomerNotesPart : WebPart
    {
	    // ...
	}
}

Теперь добавьте к веб-части некоторые свойства. Для каждой процедуры свойства в классе можно указать, что свойство должно быть персонализованным на уровне пользователя либо допускать разделение, а также доступность свойства пользователям. Например, в классе CustomerNotesPart можно предусмотреть свойство, устанавливающее заказчика по умолчанию, для которого должны отображаться замечания:

[WebBrowsable(true)]
[Personalizable(PersonalizationScope.User)]
public string Customer
{
    get
    {
        return _Customer;
    }
    set
    {
        _Customer = value;
    }
}

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

Инициализация веб-части

Чтобы написать код инициализации, можно дополнительно создать дочерние элементы управления; это похоже на создание составной веб-части. Если вы не хотите использовать предварительно построенные готовые элементы управления в методе RenderContents, то можете визуализировать веб-часть самостоятельно: однако применение составных элементов управления значительно облегчает жизнь, поскольку в этом случае не придется беспокоиться о деталях HTML-разметки.

Для создания элементов управления потребуется переопределить метод CreateChildControls(), как показано ниже. Не забудьте объявить переменные-члены для каждого элемента управления, который собираетесь создавать в своем классе WebPart:

private TextBox NewNoteText;
private Button InsertNewNote;
private GridView CustomerNotesGrid;

protected override void CreateChildControls()
{
    // Создать текстовое поле для добавления нового замечания
    NewNoteText = new TextBox();

    // Создать кнопку для отправки замечаний
    InsertNewNote = new Button();
    InsertNewNote.Text = "Вставить...";
    InsertNewNote.Click += new EventHandler(InsertNewNote_Click);

    // Создать экранную таблицу для отображения замечаний заказчиков
    CustomerNotesGrid = new GridView();
    CustomerNotesGrid.HeaderStyle.BackColor = Color.LightBlue;
    CustomerNotesGrid.RowStyle.BackColor = Color.LightGreen;
    CustomerNotesGrid.AlternatingRowStyle.BackColor = Color.LightGray;
    CustomerNotesGrid.SelectedRowStyle.Font.Bold = true;
    CustomerNotesGrid.SelectedRowStyle.BackColor = Color.Red;
    CustomerNotesGrid.AllowPaging = true;
    CustomerNotesGrid.PageSize = 5;
    CustomerNotesGrid.DataKeyNames = new string[] { "NoteID" };
    CustomerNotesGrid.AutoGenerateSelectButton = true;
    CustomerNotesGrid.PageIndexChanging += new GridViewPageEventHandler(CustomerNotesGrid_PageIndexChanging);
    CustomerNotesGrid.SelectedIndexChanging += new GridViewSelectEventHandler(CustomerNotesGrid_SelectedIndexChanging);

    // Добавить все элементы управления в коллекцию элементов
    Controls.Add(NewNoteText);
    Controls.Add(InsertNewNote);
    Controls.Add(CustomerNotesGrid);
}

Внутри метода CreateChildControls() создаются все элементы управления, используемые специальной веб-частью. Не забудьте добавить их в коллекцию Controls веб-части, чтобы исполняющая среда ASP.NET знала об их существовании и могла управлять состоянием представления, а также всеми прочими деталями, относящимися к жизненному циклу страницы. Более того, в этом методе устанавливаются процедуры обработки событий, как показано на примере кнопки InsertNewNote элемента управления GridView по имени CustomerNotesGrid.

Загрузка данных и обработка событий

Следующей фазой жизненного цикла элемента управления (веб-части) является фаза загрузки. Здесь можно подключиться к базе и загрузить данные в элемент управления. Для этого потребуется переопределить методы OnInit и OnLoad либо перехватить события Init и Load веб-части. Оба способа дают один и тот же эффект. Но при переопределении метода OnLoad, например, нужно не забыть вызвать base.OnLoad(), чтобы выполнилась также и функциональность загрузки базового класса. Таким образом, имеет смысл установить обработчики событий однажды и перехватывать события специального элемента управления следующим образом:

public CustomerNotesPart()
{
    this.Init += new EventHandler(CustomerNotesPart_Init);
    this.Load += new EventHandler(CustomerNotesPart_Load);
    this.PreRender += new EventHandler(CustomerNotesPart_PreRender);
}

void CustomerNotesPart_Load(object sender, EventArgs e)
{
    // Инициализировать другие свойства...
}

void CustomerNotesPart_Init(object sender, EventArgs e)
{
    //  Загрузить данные из базы...
}

Реализация обработчика события PreRender рассматривается позже. Теперь можно написать функциональность загрузки данных из базы. Предположим, что уже создан типизированный DataSet для таблицы CustomerNotes. Теперь можно создать вспомогательный метод для привязки ранее созданного элемента управления GridView к данным из базы и затем вызвать этот метод в событии Load, как показано ниже. Для простоты метод привязывает информацию непосредственно к GridView и не использует кэширование для оптимизации доступа к данным, поскольку сейчас нужно сосредоточиться на создании самой веб-части. Обратите внимание на необходимость импортирования пространства имен CustomerSetTableAdapters с ранее построенным типизированным DataSet:

void CustomerNotesPart_Load(object sender, EventArgs e)
{
    // Инициализировать свойства веб-части
    this.Title = "Пользовательские записи";
    this.TitleIconImageUrl = "~/NotesImage.jpg";
}

void CustomerNotesPart_Init(object sender, EventArgs e)
{
    // He пытаться загружать данные в режиме проектирования
    if (!this.DesignMode)
    {
        BindGrid();
    }
}

void BindGrid()
{
    EnsureChildControls();

    CustomerNotesTableAdapter adapter =
        new CustomerNotesTableAdapter();

    if (Customer.Equals(string.Empty))
        CustomerNotesGrid.DataSource = adapter.GetData();
    else
        CustomerNotesGrid.DataSource = adapter.GetDataByCustomer(Customer);
}

He забудьте вызвать EnsureChildControls(). Поскольку не известно, когда среда ASP.NET действительно вызывает CreateChildControls() и таким образом создает дочерние элементы управления (потому что она создает их по мере необходимости), необходимо удостовериться, что элементы управления доступны внутри этого метода, вызвав EnsureChildControls().

Теперь данные загружены в GridView. Во время следующей фазы жизненного цикла события обрабатываются исполняющей средой ASP.NET. Специальная веб-часть должна перехватывать событие щелчка на ранее добавленной кнопке InsertNewNote, которая отправляет новое замечание в базу данных, и событие элемента управления CustomerNotesGrid, который изменяет страницу:

void CustomerNotesGrid_SelectedIndexChanging(object sender, GridViewSelectEventArgs e)
{
    CustomerNotesGrid.SelectedIndex = e.NewSelectedIndex;
}

void CustomerNotesGrid_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
    // Вставить логику изменения страницы
    CustomerNotesGrid.PageIndex = e.NewPageIndex;
}

void InsertNewNote_Click(object sender, EventArgs e)
{
    // Вставить новое замечание
    CustomerNotesTableAdapter adapter =
    new CustomerNotesTableAdapter();

    // Обратите внимание, что NoteID - столбец идентичности в 
    // базе данных, поэтому он не должен передаваться в качестве 
    // параметра методу, сгенерированному DataSet!
    adapter.Insert(Customer, DateTime.Now, NewNoteText.Text);

    // Перерисовать сетку с новой строкой
    BindGrid();
}

Наконец, загрузить данные в GridView необходимо еще в одном месте кода. Если кто-то изменит значение свойства Customer, нужно, чтобы веб-часть отобразила информацию, ассоциированную с вновь выбранным заказчиком. Код свойства должен быть модифицирован следующим образом:

[WebBrowsable(true)]
[Personalizable(PersonalizationScope.User)]
public string Customer
{
    get
    {
        return _Customer;
    }
    set
    {
        _Customer = value;

        // He пытаться загрузить данные в режиме проектирования
        if (!this.DesignMode)
        {
            EnsureChildControls();
            CustomerNotesGrid.PageIndex = 0;
            CustomerNotesGrid.SelectedIndex = -1;
            BindGrid();
        }
    }
}

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

Финальная генерация

К этому моменту инициализирована веб-часть, созданы элементы управления, написан код загрузки данных и перехвачены события элементов управления. Наступило время генерации разметки для веб-части. Непосредственно перед генерацией можно установить финальные значения свойств элементов управления, которые влияют на генерацию. Например, кнопку InsertNewNote необходимо сделать недоступной, если пользователь не инициализировал свойство Customer. И, конечно, GridView теперь может создать необходимые элементы управления HTML для отображения данных, к которым он привязан. Для этого понадобится вызвать метод DataBind(), как показано ниже:

void CustomerNotesPart_PreRender(object sender, EventArgs e)
{
    if (Customer.Equals(string.Empty))
        InsertNewNote.Enabled = false;
    else
        InsertNewNote.Enabled = true;

    CustomerNotesGrid.DataBind();
}

В методе RenderContents() можно создать HTML-код компоновки веб-части. Если не переопределить этот метод, то веб-часть автоматически сгенерирует ранее добавленные элементы управления в том порядке, в котором они вставлялись в коллекцию Controls веб-части внутри метода CreateChildControls(). Поскольку эта компоновка проста (это всего лишь последовательность элементов управления), метод RenderContents() переопределяется для построения лучшей компоновки, основанной на таблице:

protected override void RenderContents(HtmlTextWriter writer)
{
    writer.Write("<table>");

    writer.Write("<tr>");
    writer.Write("<td>");
    NewNoteText.RenderControl(writer);
    InsertNewNote.RenderControl(writer);
    writer.Write("</td>");
    writer.Write("</tr>");

    writer.Write("<tr>");
    writer.Write("<td>");
    CustomerNotesGrid.RenderControl(writer);
    writer.Write("</td>");
    writer.Write("</tr>");

    writer.Write("</table>");
}

Этот код генерирует HTML-таблицу с двумя строками и одним столбцом через HtmlTextWriter. Первая строка содержит текстовое поле и кнопку, а вторая - GridView с замечаниями. В конце этот метод использует метод RenderControl() дочерних элементов управления для генерации текстового поля, кнопки и сетки в заданной позиции внутри таблицы. Как видите, переопределить генерацию разметки по умолчанию базового класса WebPart довольно легко.

Дополнительные шаги настройки

Как было показано в предыдущей статье, с помощью интерфейса IWebPart, который реализует специальная веб-часть, можно переопределять такие свойства, как заголовок или описание. Более того, можно определить значения по умолчанию для других свойств веб-части (это лучше всего делать в методе Load). Можно даже переопределить реализацию стандартных свойств и методов веб-части. В следующем примере показано, как инициализировать веб-часть и переопределить ее свойства:

void CustomerNotesPart_Load(object sender, EventArgs e)
{
    // Инициализировать другие свойства...
    // Инициализировать свойства веб-части
    this.Title = "Пользовательские записи";
    this.TitleIconImageUrl = "~/NotesImage.jpg";
}

public override bool AllowClose
{
    get
    {
        return false;
    }
    set
    {
        // Это свойство не должно устанавливаться
    }
}

Этот код инициализирует в событии Load некоторые свойства веб-части значениями по умолчанию. Вдобавок он переопределяет свойство AllowClose, чтобы оно всегда возвращало false и игнорировало любые операции установки. В результате получается веб-часть, поведение которой вызывающий код не может переопределить простым присваиванием свойства. Имеется возможность полной настройки и управления тем, что может, а что не может делать специальная веб-часть. Это не доступно при работе с пользовательскими элементами управления.

Использование веб-части

Давайте посмотрим, как использовать специальную веб-часть на странице. Для начала зарегистрируйте веб-часть на странице с веб-частями, добавив в начало страницы директиву <%@ Register %>:

<%@ Register TagPrefix="professorweb" Namespace="ProfessorWeb.WebParts" %>

Вспомните, что пространство имен ProfessorWeb.WebParts используется в файле класса специальной веб-части. Директива <%@ Register %> назначает префикс "professorweb" этому пространству имен. Это позволяет применять веб-часть в одном из ранее созданных элементов управления WebPartZone следующим образом:

<asp:WebPartZone ID="MainZone" runat="server">
    <ZoneTemplate>
        <control:MyDataControl ID="MyDataControl1" runat="server" OnLoad="MyDataControl1_Load" />
        <professorweb:CustomerNotesPart ID="MyCustomNotesParts" runat="server" />
    </ZoneTemplate>
</asp:WebPartZone>

Теперь можете протестировать вновь созданную веб-часть, запустив веб-приложение. На рисунке ниже показан результат проделанной работы:

Специальная веб-часть во взаимодействии с другими веб-частями

Редакторы веб-частей

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

Платформа ASP.NET Web Part Framework предоставляет функциональность для редактирования свойств веб-частей. Как было показано при создании элемента управления Menu для переключения свойства DisplayMode страницы, помимо прочих, поддерживается режим Edit. Однако если вы попытаетесь активизировать его, то получите исключение, связанное с отсутствием элементов управления на странице. Недостающие порции режима Edit - это EditorZone и ряд соответствующих частей редактора. Все они встроены; WebPartZone содержит части редактора. Их можно использовать за счет добавления к своей странице EditorZone и одной из предварительно построенных частей редактора, как показано ниже:

<asp:CatalogZone ...>
    ...
</asp:CatalogZone>
<asp:EditorZone runat="server" ID="SimpleEditor">
    <ZoneTemplate>
        <asp:AppearanceEditorPart ID="MyMainEditor" runat="server" />
    </ZoneTemplate>
</asp:EditorZone>

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

Редактирование свойств веб-частей

Доступные редакторы веб-частей перечислены в таблице ниже:

Веб-части области редактирования, входящие в состав ASP.NET
Веб-часть редактора Описание
AppearanceEditorPart

Позволяет конфигурировать базовые свойства веб-части, в том числе заголовок и ChromeStyle

BehaviorEditorPart

Включает редакторы для модификации свойств, влияющих на поведение веб-части. Типичными примерами этих свойств являются AllowClose и AllowMinimize, а также такие свойства, как TitleUrl, HelpMode и HelpUrl. Каждое свойство модифицирует поведение, такое как способность веб-части к сворачиванию

LayoutEditorPart

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

PropertyGridEditorPart

Отображает текстовое поле для каждого общедоступного свойства специальной веб-части, включающего атрибут [WebBrowsable(true)]

Часть редактора PropertyGridEditorPart - подходящий способ позволить пользователю модифицировать ранее реализованное свойство Customer веб-части. Просто добавьте часть редактора к странице; как показано ниже, после чего можно будет редактировать специальную веб-часть:

<asp:EditorZone runat="server" ID="SimpleEditor">
    <ZoneTemplate>
        <asp:PropertyGridEditorPart runat="server" ID="MyGridEditor" />
        <asp:AppearanceEditorPart ID="MyMainEditor" runat="server" />
    </ZoneTemplate>
</asp:EditorZone>

На рисунке ниже показан результат. После перехода в режим Edit во время редактирования специальной веб-части есть возможность изменить значение свойства Customer:

Часть редактора PropertyGridEditorPart в действии

Поскольку ранее в set-методе свойства был вызван метод BindGrid(), внешний вид веб-части изменится, как только будет выполнен щелчок на кнопке Apply (Применить) в EditorZone. Кроме того, если к специальному свойству добавить атрибут [WebDisplayName] в дополнение к [WebBrowsable], можно контролировать имя свойства, которое будет отображать редактор.

Пройди тесты
Лучший чат для C# программистов