Управління WEB-формою з користувальницького компонента

Ivanov_SV

Багаторазове використання одного разу написаного і налагодженого коду – одна з найважливіших завдань, вирішення якої в тій чи іншій мірі забезпечують практично всі сучасні системи програмування. Не виняток тут і технології ASP.Net, надають розробникові можливість не тільки використовувати поставляються в стандартній комплектації WEB-компоненти, але і створювати свої власні.

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

Є, однак, у користувальницьких компонентів один недолік, який може ускладнювати їх повноцінне використання в контексті складних інтерактивних форм введення, – ці компоненти працюють практично повністю під керуванням виконуючого ядра ASP.Net, що не дозволяє працювати з ними як з повноцінними об'єктами. У документації описана можливість управління компонентом з сторінки, але це управління одностороннє. Термін "одностороннє управління" в даному контексті означає, що ініціатором яких-небудь дій виступає WEB-форма, на якій встановлений компонент, тобто тільки з методів форми можна встановити значення властивостей компонента і / або викликати які-небудь його цільові методи, але ніяк не навпаки.

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

HttpRequest request = Page.Request;

Тільки ось чи багато користі від доступу до стандартних властивостей і методів сторінки? Набагато важливіше було б мати можливість викликати будь-які спеціалізовані методи сторінки, на якій встановлений компонент, наприклад, змінити доступність елементів управління або виконати комплексну перевірку даних. Але тут виникає протиріччя – компонент, який (як мінімум потенційно) може використовуватися на різних сторінках, не може знати про те, які конкретно сторінки його будуть використовувати. (Одвічна проблема: що первинно – курка чи яйце?) Тобто при додаванні в додаток нової форми, на якій ми хочемо використовувати компонент, доведеться переписувати сам компонент?! ..

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

Нехай потрібно зробити форму, в яку вводиться ПІБ працівника. Після введення ПІБ користувач може натиснути кнопку пошуку – в результаті на сервері буде запущений пошук в базі даних. Результатом пошуку може бути:

У залежності від результату пошуку:

  1. працівник в базі не знайдено – інформацію про людину необхідно додати в базу даних, тобто потрібно відобразити на формі компонент введення додаткових даних про людину (наприклад, рік і місце народження, адресу проживання тощо) для запису в БД

  2. працівник у базі знайдено – на формі потрібно відобразити компонент для виведення детальної інформації про людину;

  3. знайдено кілька людей – на формі потрібно відобразити компонент для вибору конкретної людини.

У першому і третьому варіанті будуть потрібні деякі додаткові дії з боку користувача й системи (додавання до БД або ідентифікація), у разі їх успішного завершення форма автоматично повинна перейти в стан 2.

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

Ключовий момент тут – деякі компоненти повинні виконати на сервері якісь дії, і потім містить їх сторінка повинна перекомпонувати.

Отже, можливі рішення:

У коді це виглядає приблизно так:

Спадкування форми від свого класу

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; } }

Цей варіант має такі недоліки:

Визначення цільових інтерфейсів

Вище за приклад був наведений код, який ілюструє виклик методу 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; } }

Деякі пояснення:

Цікава можливість, притаманна делегатам, – підключати кілька делегатів:

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; } }

Відмінності від використання делегатів:

Як недолік, – при використанні стандартної схеми обробки подій може виникати необхідність у написанні спеціалізованих класів-спадкоємців від 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; } } }

Висновки

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

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

Використання механізму наслідування для реалізації взаємодії компонентів і форм не рекомендується.

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*