Исходники.Ру - Программирование
Исходники
Статьи
Книги и учебники
Скрипты
Новости RSS
Магазин программиста

Главная » Статьи по программированию » .NET - Все статьи »

Обсудить на форуме Обсудить на форуме

Управление WEB-формой из пользовательского компонента
Рассматривается возможность управления содержимым WEB-формы из пользовательского компонента (Web User Control).
Многократное использование единожды написанного и отлаженного кода - одна из важнейших задач, решение которой в той или иной мере обеспечивают практически все современные системы программирования. Не исключение здесь и технологии ASP.Net, предоставляющие разработчику возможность не только использовать поставляемые в стандартной комплектации WEB-компоненты, но и создавать свои собственные.

В распоряжении разработчика ASP.Net имеются две технологии компонентного программирования: "заказные" компоненты (Web Custom Controls) и пользовательские компоненты (Web User Controls). В документации достаточно подробно описаны преимущества и недостатки тех и других. Стоит отметить, что по некоторым позициям пользовательские компоненты уступают "заказным" компонентам, однако простота их создания во многих случаях позволяет закрыть глаза на мелкие недостатки, а возможность визуальной компоновки делает разработку пользовательских компонентов быстрой и приятной.

Есть, однако, у пользовательских компонентов один недостаток, который может затруднять их полноценное использование в контексте сложных интерактивных форм ввода, - эти компоненты работают практически полностью под управлением исполняющего ядра ASP.Net, что не позволяет работать с ними как с полноценными объектами. В документации описана возможность управления компонентом из страницы, но это управление одностороннее. Термин "одностороннее управление" в данном контексте означает, что инициатором каких-либо действий выступает WEB-форма, на которой установлен компонент, т.е. только из методов формы можно установить значения свойств компонента и/или вызвать какие-либо его целевые методы, но никак не наоборот.

Формально класс System.UI.WebControls.Control, от которого наследуются пользовательские компоненты, имеет свойство Page, которое можно было бы использовать для доступа к странице-владельцу, например, так:

	HttpRequest request = Page.Request;
Только вот много ли пользы от доступа к стандартным свойствам и методам страницы? Гораздо важнее было бы иметь возможность вызвать какие-либо специализированные методы страницы, на которой установлен компонент, например, изменить доступность элементов управления или выполнить комплексную проверку данных. Но здесь возникает противоречие - компонент, который (как минимум потенциально) может использоваться на разных страницах, не может знать о том, какие конкретно страницы его будут использовать. (Извечная проблема: что первично - курица или яйцо?) То есть при добавлении в приложение новой формы, на которой мы хотим использовать компонент, придется переписывать сам компонент?!..

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

Пусть требуется сделать форму, в которую вводится ФИО работника. После ввода ФИО пользователь может нажать кнопку поиска - в результате на сервере будет запущен поиск в базе данных. Результатом поиска может быть:

  • 0 записей - работник в базе не найден;
  • 1 запись - работник в базе найден;
  • несколько записей - найдено несколько человек, например, однофамильцы.
В зависимости от результата поиска:
  1. работник в базе не найден - информацию о человеке необходимо добавить в базу данных, т.е. нужно отобразить на форме компонент ввода дополнительных данных о человеке (например, год и место рождения, адрес жительства и пр.) для записи в БД
  2. работник в базе найден - на форме нужно отобразить компонент для вывода детальной информации о человеке;
  3. найдено несколько человек - на форме нужно отобразить компонент для выбора конкретного человека.
В первом и третьем варианте потребуются некоторые дополнительные действия со стороны пользователя и системы (добавление в БД или идентификация), в случае их успешного завершения форма автоматически должна перейти в состояние 2.

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

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

Итак, возможные решения:

  • наследовать форму-владельца не от System.UI.WebControls.Page, а от своего класса;
  • определить интерфейсы взаимодействия главной страницы со своими компонентами и, соответственно, реализовать их в форме-владельце;
  • использовать делегаты, подключаемые к компоненту в форме-владельце;
  • генерировать в компонентах специальные события, а в форме-владельце подключать соответствующие обработчики.
В коде это выглядит примерно так:

 

Наследование формы от своего класса

public abstract class BasePage : System.UI.WebControls.Page {
  public virtual abstract void DoSearchModuleAction(int recordCount);
}
public class MyPage : BasePage {
  public override void DoSearchModuleAction(int recordCount) { 
    // здесь выполняется реальная работа
  }
}
public class SeachModule : System.UI.WebControls.Control {
  private void Button1_Click() {
    if (Page is BasePage) 
      ((BasePage) Page).DoSearchModuleAction(Search());
  }
  private int Search() {
    return 0;
  }
} 

Этот вариант имеет следующие недостатки:

  • если потребуется, чтобы некоторая форма содержала компонент, но не предоставляла ему "обратной связи", в классе этой формы все равно придется прописывать все методы управления, хотя бы и пустые;
  • если в ходе разработки появится новый компонент, придется переписывать базовый класс и, соответственно, ВСЕХ его наследников;
  • невозможно разместить компонент в компоненте, т.к. компонент-владелец наследуется от System.UI.WebControls.Control, а не от System.UI.WebControls.Page и, соответственно, не может быть наследован от BasePage.

Определение целевых интерфейсов

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

    if (Page is BasePage) ...
Оператор is выполняет проверку того, что Page является экземпляром класса BasePage (или его наследником), или - ВНИМАНИЕ - что объект Page реализует интерфейс BasePage. Естественным образом напрашивается решение использовать не наследование, а реализацию интерфейсов:
public interface IBasePage {
  void DoSearchModuleAction(int recordCount);
}
public class MyPage : System.UI.WebControls.Page, IbasePage {
  public void IBasePage.DoSearchModuleAction(int recordCount) { 
    // здесь выполняется реальная работа
  }
}
public class SeachModule : System.UI.WebControls.Control {
  private void Button1_Click() {
    if (Page is IBasePage) 
      ((IBasePage) Page).DoSearchModuleAction(Search());
  }
  private int Search() {
    return 0;
  }
} 

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

Тем не менее, и в этом варианте можно найти некоторые недостатки:

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

Использование делегатов

Передать управление форме-владельцу можно следующим образом:

public delegate void SearchModuleDelegate(int recordCount);
public class MyPage : System.UI.WebControls.Page {
  protected SeachModule SeachModule1;
  public void PageLoad() {
    // подключение делегата к компоненту
    SeachModule1.Action = new SearchModuleDelegate(DoSearchModuleAction);
  }
  private void DoSearchModuleAction(int recordCount) {
    // здесь выполняется реальная работа
  }
}
public class SeachModule : System.UI.WebControls.Control {
  public SearchModuleDelegate Action = null;
  private void Button1_Click() {
    if (Action != null) Action(Search()); 
  }
  private int Search() {
    return 0;
  }
} 

Некоторые пояснения:

  • в качестве делегата выступает приватный метод DoSearchModuleAction; в общем случае может использоваться не только метод экземпляра класса формы, но и статический метод формы или даже метод другого класса;
  • подключение делегата к компоненту выполняется в методе PageLoad формы-владельца.

Интересная возможность, присущая делегатам, - подключать несколько делегатов:

delegate void D(int x);
class C {
  public static void M1(int i) { /* ... */ }
  public static void M2(int i) { /* ... */ }
}
class Test {
  static void Main() { 
    D cd1 = new D(C.M1);  // M1
    D cd2 = new D(C.M2);  // M2
    D cd3 = cd1 + cd2;    // M1 + M2
    D cd4 = cd3 + cd1;    // M1 + M2 + M1
    D cd5 = cd4 + cd3;    // M1 + M2 + M1 + M1 + M2
  }
}

Примечание: Пример взят из раздела 15.1 спецификации языка C#.

 

Генерация событий

Использование событий мало чем отличается от рассмотренного выше варианта, т.к. обработчики событий представляют собой не что иное, как стандартизованные (по типу возвращаемого значения и набору параметров) делегаты:

public delegate void SearchEventHandler(object sender, SearchEventArgs e);
public class SearchEventArgs : System.EventArgs {
  public readonly int RecordCount;
  public SearchEventArgs(int recordCount) : base() {
    RecordCount = recordCount;
  }
}
public class MyPage : System.UI.WebControls.Page {
  protected SeachModule SeachModule1;
  public void PageLoad() {
    // подключение обработчика события к компоненту
    SeachModule1.Action += new SearchEventHandler(DoSearchModuleAction);
  }
  private void DoSearchModuleAction(object sender, SearchEventArgs e) {
    // здесь выполняется реальная работа
  }
}
public class SeachModule : System.UI.WebControls.Control {
  public event SearchEventHandler Action;
  private void Button1_Click() {
    if (Action != null) Action(this, new SearchEventArgs(Search())); 
  }
  private int Search() {
    return 0;
  }
} 
Отличия от использования делегатов:
  • обработчик события представляет собой делегат SearchEventHandler; для обработчиков событий: тип возвращаемого значения void, параметр sender - объект, сгенерировавший событие, параметр e - аргументы события;
  • для передачи в событии специфических данных создается класс аргументов события SearchEventArgs, наследованный от System.EventArgs;
  • подключение делегата к компоненту выполняется в методе PageLoad формы-владельца; при этом используется специальный синтаксис подключения обработчика - оператор +=
  • точка подключения обработчика события Action в компоненте описывается с использованием ключевого слова event; в результате вне класса SeachModule можно использовать только операторы += и -=, что исключает, например, случайное обнуление.
Как недостаток, - при использовании стандартной схемы обработки событий может возникать необходимость в написании специализированных классов-наследников от System.EventArgs. Впрочем, написания специализированных аргументов события можно избежать, если использовать, например, свойства компонента (ниже приводится модифицированный пример обработчика события):
public class MyPage : System.UI.WebControls.Page {
  protected SeachModule SeachModule1;
  public void PageLoad() {
    // подключение обработчика события к компоненту
    SeachModule1.Action += new EventHandler(DoSearchModuleAction);
  }
  private void DoSearchModuleAction(object sender, EventArgs e) {
    int recordCount = (sender is SeachModule)
 ? ((SeachModule) sender).RecordCount : -1;
    // TODO: add code to interpret recordCount
  }
}
public class SeachModule : System.UI.WebControls.Control {
  public event EventHandler Action;
  private int recordCount = -1;
  private void Button1_Click() {
    if (Action != null) {
      recordCount = Search(); 
      Action(this, null);
    }
  }
  private int Search() {
    // TODO: add code to search person
    return 0;
  }
  public int RecordCount {
    get {
      return recordCount;
    }
  }
} 

Выводы

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

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

Использование механизма наследования для реализации взаимодействия компонентов и форм не рекомендуется.


Может пригодится:


Автор: Ivanov_SV
Прочитано: 4019
Рейтинг:
Оценить: 1 2 3 4 5

Комментарии: (0)

Добавить комментарий
Ваше имя*:
Ваш email:
URL Вашего сайта:
Ваш комментарий*:
Код безопастности*:

Рассылка новостей
Рейтинги
© 2007, Программирование Исходники.Ру