Книга: C# для профессионалов. Том II

Поддержание состояния в специальном элементе управления

Поддержание состояния в специальном элементе управления

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

Чтобы проиллюстрировать это, добавим дополнительное свойство в элемент управления RainbowLabel. Мы добавив метод с именем Cycle(), который циклически перебирает доступные цвета и использует хранимое поле offset для определения цвета первой буквы выводимой строки.

Это поле должно использовать ViewState элемента управления, чтобы быть устойчивым между запросами. Если это не сделано и поле инициализируется в элементе управления, то все будет работать неправильно.

Здесь будет показан код для обоих случаев, чтобы увидеть ловушку, в которую очень легко попасть. Сначала мы посмотрим на код, который не может воспользоваться ViewState:

public class RainbowLabel : System.Web.UI.WebControls.Label {
 private Color[] colors = new Color[] {
  Color.Red, Color.Orange, Color.Yellow,
  Color.GreenYellow, Color.Blue, Color.Indigo, Color.Violet
 };
 private int offset = 0;
 protected override void Render(HtmlTextWriter writer) {
  string text = Text;
  for (int pos = 0; pos < text.Length; pos++ ) {
   int rgb = colors[(pos + offset) % 7].ToArgb() & 0xFFFFFF;
   output.Write("<font color= '#" + rgb.ToString("X6") + "'>" + text[pos] + "</font>");
  }
 }
 public void Cycle() {
  offset = ++offset % 7;
 }
}

Здесь мы инициализируем поле offset нулем, а затем позволяем методу Cycle() увеличивать его. Использование оператора % гарантируем, что оно уменьшится до 0, если достигнет 7.

Чтобы протестировать это, требуется способ вызова метода Cycle() и добавление кнопки к форме:

<form method="post" runat="server">
 <PCS:RainbowLabel Runat="server" Text="Multicolored label!"
  />
 <asp:Button Runat="server"
  Text="Cycle colors" />
</form>

Co следующим обработчиком событий:

protected void cycleButton_Click(object sender, System.EventArgs e) {
 this.rainbowLabel1.Cycle();
}

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

Если этот элемент управления сохраняет себя на сервере между запросами, то он работает правильно, так как поле offset поддерживает свое состояние, не требуя специального внимания. Однако эта техника не имеет смысла для приложения Web, когда потенциально тысячи пользователей используют его одновременно. Создание отдельного экземпляра для каждого пользователя только ухудшает ситуацию.

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

Чтобы поместить поле offset в ViewState, мы используем следующий код:

ViewState["_offset"] = offset;
ViewState
состоит из пар имя-значение, и в данном случае используется имя _offset. Нам не нужно объявлять его где-либо, оно будет создано при первом использовании этого кода.

Аналогично для извлечения состояния используется код:

offset = (inc)ViewState["_offset"];

Если мы сделаем это, когда ничего не хранится в ViewState под этим именем, то будет получено значение null. Простейший способ справиться с такой ситуацией — использовать вызов в блоке try.

Собирая все вместе, сделаем следующие изменения в коде:

public class RainbowLabel : System.Web.UI.WebControls.Label {
 private Color[] colors = new Color[] {
  Color.Red, Color.Orange, Color.Yellow,
  Color.GreenYellow, Color.Blue, Color.Indigo, Color.Violet
 };
 private int Offset;
 protected override void Render(HtmlTextWriter writer) {
  string text = Text;
  GetOffset();
  for (int pos = 0; pos < text.Length; pos++) {
   int rgb = colors[(post + offset) % 7].ToArgb() & 0xFFFFFF;
   writer.Write("<font color='#" + rgb.ToString("X6") + "'>" + text[pos] + "</font>");
  }
 }
 private void GetOffset() {
  try {
   offset = (int)ViewState["_offset"],
  } catch {
   offset = 0;
  }
 }
 public void Cycle() {
  GetOffset();
  offset = ++offset % 7;
  ViewState["_offset"] = offset;
 }
}

Теперь элемент управления позволит методу Cycle() работать каждый раз. Обычно ViewState используется для простых свойств, таких как свойства string:

public string Name {
 get {
  return (string)ViewState["_name"];
 }
 set {
  ViewState["_name"] = value;
 }
}

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

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

Оглавление книги


Генерация: 2.101. Запросов К БД/Cache: 3 / 1
поделиться
Вверх Вниз