Типізований повторювач - все про IT і програмування
Written on 05 Липня 2013 . Posted in ASP.NET
Злам ASP.NET для створення повторювача з підтримкою узагальнень
Обмеження виразів прив'язки даних
Вирази прив'язки даних ASP.NET ( ) чудові. Вони прості, потужні, підтримують інтелектуальне сприйняття і перевіряються під час компіляції. Вони скорочують щоденні трудовитрати на програмування, але не засмічують уявлення тяжкою логікою.
Найпоширеніше використання виразів – визначення вмісту шаблону. Repeater і GridView - керуючі елементи, де найчастіше зустрічаються.
Але є кілька проблем із використанням цих виразів у шаблонах. Найцікавіша деталь у прив'язці даних шаблоні – створюється екземпляр елемента даних шаблону ряду у ньому.
Цей елемент даних доступний через інтерфейс IDataItemContainer, реалізований кожним RepeaterItem та GridViewRow. Всередині виразів шаблону контейнер доступний як змінна Container або через псевдофункцію Eval/Bind.
Цей інтерфейс успадкував основну проблему. Розглянемо приклад для керуючого елемента Repeater: уявіть, що є клас бізнес-сутності Person:
І ви хочете показати список Person в Repeater. Є два способи зробити це: використовувати Eval або Cast DataItem. Розберемо обидва.
1. Eval:
Це найшвидший та невірний спосіб. Невелика проблема: імена властивостей не підтримують інтелектуальне сприйняття. Велика проблема: уявіть, що хтось вирішить, що Email тепер має називатися Mail. Ви змінюєте його, компілюєте все, але якщо ви маєте багато сторінок і ліниву команду тестувальників, або команди тестувальників взагалі немає, то може пройти багаточасу, перш ніж хтось зауважить, що ця сторінка більше не працює.
2. Cast:
Звичайно, це має працювати так:
На щастя, є спосіб змусити це працювати.
Час зламати ASP.NET
Як зламати ASP.NET (за 4 кроки): 1. Завантажити та встановити Reflector 2. З'ясувати, який код робить те, що потрібно змінити 3. Знайти найменший прийом програмування, необхідний, щоб змусити його працювати потрібним вам способом. Написати прийом програмування та насолоджуватися результатами
Шуканий код - TemplateContainerAttribute і клас ControlBuilder. ControlBuilder перевіряє атрибут, щоб дізнатися тип згенерованої змінної Container. Атрибут застосовується до властивості шаблону:
Найменша зміна була дещо складною в даному випадку, але все ж таки маленькою. Треба було змінювати тип контейнера в TemplateContainerAttribute залежно від DataItemTypeName, заданого розміткою Repeater. Це означає, що не можна задати це за допомогою атрибуту – TemplateContainerAttribute ізольований, тому не можна вставити до нього жодну динамічну логіку. Натомість перехоплюємо виклик GetCustomAttributes() для властивості ItemTemplate і повертаємо новий атрибут з правильним типом.
Але перш ніж перейти до перехоплення, створимо узагальнені класи, які будуть служити як Container і новий Repeater. Є три класи:
1. Generic RepeaterItem
2. Generic Repeater
3. Subclassed Repeater
Підклас Повторювач потрібен, тому що розмітка ASP.NET не розуміє узагальнення. Але досить легко хитрістю змусити ASP.NET використовувати ControlBuilder із Repeater для складання Repeater. Це буде розглянуто під час обговорення перехоплення.
І перехоплення не таке важке, як здається. Вінробиться за три кроки:
Крок 1. Створити користувальницький тип, що обгортає typeof(Repeater
Це дуже просто – Microsoft надає клас TypeDelegator для обгортання будь-якого Type.
Код дуже простий та самодокументований. Він отримує DataItemType і потім представляє себе як правильний тип Repeater<> з методом MakeGenericType.
Крок 2. Створити користувальницький PropertyInfo для перевизначення методу GetCustomAttributes. Це було трохи важче, оскільки немає готового PropertyInfoDelegator. Тому ми зібрали і потім успадкували його:
Цей код також дуже простий.
Крок 3. Створити RepeaterControlBuilder, який замінює RepeaterFakeType замість typeof(Repeater). Це була найлегша частина, лише перевизначення Init:
З урахуванням усього вищенаведеного, тепер може написати
Також можна отримати «інтелектуальне сприйняття» та перевірки під час компіляції. Залишається лише інтелектуальне сприйняття в атрибуті DataItemTypeName, але це дрібна проблема, яку розглянемо пізніше. Поки що скачайте код і пограйте з ним.