пятница, 11 января 2008 г.

Строго типизированная привязка данных для ASP.NET

Easier refactoring and exploration. ... Both lend themselves to having a more manageable code base over time.


Аннотация
Предлагается механизм строго типизированной привязки данных с помощью C#(VB) кода. Новый синтаксические конструкции языка C# 3.0 (такие как вывод типа, лямбда выражения, методы расширения) сделали данный подход более элегантным. Механизм привязки данных не накладывает ни каких ограничений на framework, который используется для работы с данными.


Ведение

Место положение привязки данных показано на рисунке.

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

  • перенос данных из объектов на страницу (чаще всего в контролы)
  • перенос данных со страницы в объекты данных

Ключевыми особенностями любого механизма привязки данных являются:

P1. Как декларируется привязка данных? (Место, где пишутся выражения привязки данных. Язык выражений.).
P2. Кто и на каком этапе жизненного цикла страницы выполняет код привязки?

В предлагаемом подходе для пункта P1 используются лямбда выражения или реже классы. Для выполнения пункта P2 предлагаются два метода FromModel и ToModel. Данный подход не определяет, когда будет выполнятся код привязки данных, тем самым оставляя возможность разработки для пункта P2 специализированных классов.

Схема привязки данных

На странице определяется одно или несколько полей типа, который реализует интерфейс IGroupBinder<Tdata>. Параметр TData указывает на тип объекта данных.
private GroupBinder<Customer> customerBinder;
Эти поля обычно инициализируются в конструкторе формы или в методе Page_Init.

Привязка контролов

Если контрол расположены непосредственно на форме, то Visual Studio генерирует поле класса формы, которое можно использовать в code behind. Привязку данных можно декларировать в методе Page_Init.

protected void Page_Init(object sender, EventArgs e)
{
customerBinder.Add(TextBox1).Text(x => x.Name);

Если контрол расположен внутри template-а, то привязка данных осуществляется через обработчик события Control.Init. В markup-е подписываемся на событие

<asp:TextBox runat="server" OnInit="TextBox3_OnInit" />

В code behind-е декларируем привязку данных

protected void TextBox3_OnInit(object sender, EventArgs e)
{
customerBinder.Add((TextBox)sender).Text(x => x. Name);
}

Декларирование привязки в markup-е


Для контролов таких как Repeater внутри template-а можно указывать приязку данных, используя ASP.NET синтаксис

<asp:Label runat="server" Text="<%# customerBinder.Data.Address %>" />

Реализацию и примеры использования можно посмотреть здесь.

4 комментария:

Krasin комментирует...

Посмотрел на пример использования. Не крутые вещи:

1. aBinder.ToModel(DataFactory.Instance.GetById{A}(1));

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

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

iderBinder.Add((Literal)sender).Add(x => x.Text, x => x.Id);

Из примера я не увидел, зачем надо использовать такую привязку. Усложнение есть, а видимого эффекта - нет. Правда, возможно, дело не в идее, а в неудачном примере применения.

Alexander комментирует...

>Мы не достигли того, чего хотели от типизированности - возможности проверки во время компиляции, что все привязки осуществлены верно. Причина - нетипизированная привязка событий к контролам:
iderBinder.Add((Literal)sender).Add(x => x.Text, x => x.Id);

Возможное решение этой проблемы отнаследоваться от каждого контрола и в вести дополнительное событие AdvInit с типизированным аргументом:

public class TextBox: System.Web.UI.WebControls.TextBox
{
public event Action{TextBox} AdvInit;

public TextBox()
{
Init += delegate { if (AdvInit != null) AdvInit(this); };
}
}

Такие короткие классы можно поместить в отдельный неймспейс. Прописать префикс к контролам в конфиг файле. Aspx-страницы будут отличаться от обычных только префиксом перед контролами.

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

Недостаток:
генерация пачки классов.

Вопрос в том, можно ли мириться с этим недостатком? а почему нет?

В наследниках событие AdvInit можно перекрывать с помощью ключевого слова "new".

Alexander комментирует...

Надо еще вспомнить про UserControl-ы. В каждом юзерконтроле тоже придется делать событие AdvInitю.

Alexander комментирует...

Для UserControl-ов можно написать класс

namespace TypedDataBinding
public class UserControl{T}: System.Web.UserControl
{
public event Action{T} AdvInit;
public UserControl()
{
Init += delegate(object sender) { if (AdvInit != null) AdvInit((T)sender); };
}
}

Юзерконтролы редко наследуются друг от друга, поэтому кастомный контрол просто наследуем от TypedDataBinding.UserControl{T}

public class CustomUserControl: UserControl{CustomUserControl}
{
}