На переднем крае

Cвязывание с обновляемыми данными в ASP.NET AJAX 4.0

Дино Эспозито

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

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

Одна книга особенно привлекла мое внимание. Она была посвящена передовым (для той эпохи, конечно) методикам программирования для Internet Explorer 4. Я не смог удержаться от искушения ее пролистать.

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

Связывание с данными было просто одним из многих популярных механизмов, которые активно исследовались и развивались. Хотя суть связывания с данными за прошедшие годы существенно не изменилась, его применение в мире Web серьезно трансформировалось. Подход к реализации связывания с данными в Web в наши дни радикально отличается от того, что было в конце 90-х, и большая часть различий связана с AJAX (Asynchronous JavaScript and XML).

В этой статье мы обсудим различные формы связывания с данными на клиентской стороне, которые появились в ASP.NET AJAX 4.0. В частности, я уделю внимание некоторым дополнительным функциям связывания с данными и отслеживаемым объектам (observable objects).

Базовые принципы

В целом, связывание с данными — это программная возможность связывать некоторые члены целевого компонента с членами источника данных. Средства быстрой разработки (Rapid Application Development, RAD) вроде Microsoft Access и Microsoft Visual Basic сделали связывание с данными по-настоящему удобным.

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

В этом отношении связывание с данными отлично подходило при использовании смарт-клиента, но создавало дополнительные проблемы в случае применения веб-клиента. Например, как передавать записи с сервера исходной базы данных браузеру с поддержкой JavaScript?

Среди многих других вещей, которые обсуждались в той старой книге, открытой мной после стольких лет забвения, были средства связывания с данными в IE4, основанные на паре специальных ActiveX-элементов: Tabular Data Control (TDC) и Remote Data Services (RDS). Общая схема архитектуры связывания с данными на клиентской стороне в том виде, в каком она представлялась архитекторам IE4 в самом начале эпохи динамичной Web, показана на рис. 1.

Рис. 1 Связывание с данным на клиентской стороне в Internet Explorer 4

Специально созданный ActiveX-элемент управляет соединением с удаленным источником данных и берет на себя загрузку (а при необходимости и кеширование) данных. Источником данных является любой ODBC-совместимый источник с RDS; в данном случае это текстовый файл с TDC на серверной стороне.

Реальное связывание источника и целевых элементов реализуется с помощью специфичных для браузера HTML-атрибутов вроде datasrc, datafld и dataformatas. Вот небольшой пример:

<span id="Label1" datasrc="#rdsCustomers" datafld="CompanyName" />

Связанному полю — CompanyName в данном примере — отводится пространство, зарезервированное для тега SPAN в конечном DOM (Document Object Model).

Что происходит на практике при вставке данных в DOM? Как уже упоминалось, в этом случае все делает браузер. Когда браузер встречает любой атрибут dataXXX, он запрашивает данные у элемента управления — источника данных, используя внутренний API на основе контракта. Любые полученные данные затем вставляются в конечный DOM и отображаются пользователю.

Как видите, это решение сильно зависит от конкретного браузера, а потому оно так и не увлекло разработчиков.

После этого связывание с данными на клиентской стороне было отложено на годы, пока не получили широкое распространение ASP.NET и ее модель программирования на серверной стороне.

В последние годы появление AJAX вновь пробудило широкий интерес к связыванию с данными на клиентской стороне, и инженеры вернулись к поиску эффективного способа его реализации (на этот раз общего для всех браузеров).

Эволюция ASP.NET AJAX

Общая схема архитектуры связывания с данными на клиентской стороне в ASP.NET AJAX 4.0 показана на рис. 2.

Рис. 2 Связывание с данными на клиентской стороне в ASP.NET AJAX 4.0

Хотя архитектуры, приведенные на рис. 1 и 2, на первый взгляд могут показаться очень похожими, на самом деле они сильно отличаются по нескольким ключевым аспектам.

Во-первых (и это самое главное), ASP.NET AJAX 4.0 позволяет создавать решения в области связывания с данными на клиентской стороне, работающие с любым современным браузером. Связующий код, необходимый для реальной привязки источника данных к целевым элементам, теперь является частью библиотеки Microsoft Ajax JavaScript Library. А она может быть размещена в любом браузере.

Более того, связывание больше не требует использования нестандартных HTML-атрибутов наподобие datasrc и datafld, которые должен обрабатывать браузер. Вместо этого информация о связывании указывается с применением XHTML-совместимых настраиваемых атрибутов, распределенных по соответствующим пространствам имен. И эти атрибуты обрабатываются кодом в JavaScript-библиотеке. То же самое можно делать императивно.

Старый и новый стили

Другое значимое отличие заключается в структуре объекта источника данных. При связывании с данными в старом стиле вы использовали прокси — своего рода черный ящик, который управлял выборкой нужных вам данных. В ASP.NET AJAX 4.0 ничего подобного не требуется — достаточно JavaScript-объекта с исходными данными.

Встроенная инфраструктура привязки связывает поля этого JavaScript-объекта и DOM-элементы.

В ASP.NET AJAX 4.0 такая инфраструктура заключена в компонент Sys.UI.DataView. С функциональной точки зрения, объект DataView работает во многом аналогично элементу управления RDS в IE4. Он напрямую подключается к удаленной конечной точке для получения и предоставления данных.

Но и здесь есть ряд различий. Объект DataView является клиентским элементом управления, который написан исключительно на JavaScript и не требует специальной поддержки от браузера. Как видите, ничего общего с ActiveX-элементом RDS.

Более того, элемент управления DataView не взаимодействует напрямую с источником реляционных данных. Вместо этого он подключается к любому сервису с поддержкой JSON (JavaScript Object Notation) или JSONP (JSON With Padding), например к конечной точке Windows Communication Foundation, и обменивается данными, используя JSON. Сервис может обертывать любое физическое хранилище данных (в том числе реляционную базу данных) или даже быть простой оболочкой модели Entity Framework. Как показано на рис. 2, в ASP.NET AJAX 4.0 вы можете реализовать связывание с данными без исходящего соединения с сервером. Если при загрузке страница помещает какие-то данные на клиентский компьютер, механизм связывания с данными может работать с локально кешированными данными без дальнейшего обмена информацией с сервером.

Краткий обзор

В своей рубрике за октябрь 2009 г. (msdn.microsoft.com/magazine/ee309508.aspx) я рассказывал об основах связывания с данными в ASP.NET AJAX 4.0 с точки зрения разработчика. Хотя вы можете по-прежнему опираться на ту статью, я решил дать здесь краткий сводный обзор наиболее важных моментов в программировании связывания с данными на клиентской стороне.

В ASP.NET AJAX 4.0 связывание с данными на клиентской стороне может происходить в рамках подходящего HTML-шаблона. Такой шаблон является DOM-деревом, дополненным CSS-атрибутом sys-template:

<div sys:attach="dataview1" class="sys-template">  
   ...
</div>

Атрибут sys-template — это принятое название пользовательского CSS-стиля, который, как минимум, должен включать следующее:

<style type="text/css">
   .sys-template { display:none; }
</style>

Однако простого дополнения данного HTML-тега атрибутом sys-template недостаточно. Вы также должны добавить к тегу какое-то поведение, позволяющее ему обрабатывать любые помещенные в него выражения привязки. В этом контексте поведение — это просто экземпляр адаптированного JavaScript-компонента или элемента управления.

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

Вот пример присваивания открытого имени экземпляру компонента DataView:

<body xmlns:sys="javascript:Sys" 
      xmlns:dataview1="javascript:Sys.UI.DataView">
...
</body>

Атрибут sys:attach получает открытое имя и создает экземпляр объекта поведения. В первом примере кода в этом разделе тег DIV включает поведение в виде объекта dataview1. Как вы, вероятно, догадываетесь, dataview1 — это открытое имя JavaScript-объекта Sys.UI.DataView.

После подключения к шаблону ASP.NET AJAX 4.0 экземпляр DataView может успешно обрабатывать любые выражения привязки, содержащиеся в шаблонах. Вот пример шаблона с простейшим синтаксисом связывания:

<ul id="imageListView" class="sys-template" 
     sys:attach="dataview1" 
    dataview1:data="{{ imageArray }}">
    <li>
        <span>{{ Name }}</span>
        <span>{{ Description }}</span>
    </li>
</ul>

Маркер {{выражение}} сообщает DataView обработать встроенное JavaScript-выражение и присвоить результат DOM или указанному свойству DataView. Например, предыдущий код присваивает содержимое JavaScript-переменной imageArray свойству data объекта DataView с именем dataview1. Тем самым вы определяете источник данных для операции связывания. Эта информация используется для раскрытия любых других выражений привязки в шаблоне вроде указанных в теле двух тегов SPAN. Предполагается, что Name и Description являются открытыми свойствами элемента (или элементов) данных, присвоенных свойству data объекта DataView.

Оценка подставляемого выражения

Синтаксис {{выражение}} указывает простейший тип связывания, поддерживаемого ASP.NET AJAX 4.0. Любые привязки, выраженные таким способом, оцениваются только при рендеринге шаблона. И обновляются лишь при программном обновлении шаблона.

Подставляемое выражение оценивается, исходя из текущего (на момент рендеринга) состояния источника данных, но в дальнейшем никакие изменения в источнике данных автоматически не отслеживаются.

В качестве альтернативы также поддерживается более функциональная модель связывания, которая предоставляет вам те же возможности в программировании, что и XAML-привязка к данным в приложениях Windows Presentation Foundation и Silverlight. Этот "продвинутый" механизм часто называют активной привязкой, или связыванием с обновляемыми данными (live binding). Рассмотрим его подробнее.

Активная привязка

Активная привязка гарантирует, что связанное значение, показываемое в UI, будет автоматически обновляться всякий раз, когда оно изменяется в источнике данных. Допустим, вы устанавливаете активную привязку между тегом SPAN и свойством CompanyName в источнике данных. Тег SPAN отображает текущее значение свойства CompanyName на момент рендеринга. Однако содержимое тега SPAN будет автоматически меняться всякий раз, когда обновляется значение связанного свойства в источнике данных. Активную привязку можно применить к любым двум объектам, будь то DOM-элементы или JavaScript-объекты.

Выражения активной привязки требуют другой синтаксис. Вот пример:

<span>{binding CompanyName}</span>

Вы заключаете выражение в одну пару фигурных скобок и ставите перед ним ключевое слово binding. Как видите, в целом, этот синтаксис имеет много общего с эквивалентным XAML-синтаксисом, и это не случайность.

Заметьте, что содержимое тега SPAN обновляется при каждом изменении, обнаруживаемом в связанном элементе данных; в обратную сторону механизм не работает. Если изменяется содержимое SPAN, это изменение не распространяется на объект-источник.

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

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

Двухсторонняя привязка данных

Двухсторонняя привязка — особая форма активной привязки, при которой используются два канала для обнаружения изменений на обеих сторонах привязки. Когда между двумя объектами установлена двухсторонняя привязка, происходит следующее:

  • если изменяется источник данных, автоматически обновляется целевой объект;
  • если изменяется целевой объект, новое состояние распространяется на нижележащий источник данных.

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

Пересечение границ уровней недопустимо

Может быть, кому-то это кажется и так понятным, но позвольте мне все равно разъяснить этот вопрос. Представьте двухстороннюю привязку между какой-то частью UI и некими данными, получаемыми сервисом из базы данных.

Данные, загружаемые с сервера и связываемые с визуальными элементами UI, представляют снимок модели предметной области (domain model). Возьмем, к примеру, объект Customer. Если отображаемый объект Customer модифицируется на двухсторонне связанной странице, тогда все обнаруженные изменения отражаются на объекте Customer, который находится на клиентской стороне, но эти изменения ни в коем случае не распространяются на сервер. Двухсторонняя привязка не пересекает никакие уровни.

То есть, с точки зрения синтаксиса, двухсторонняя привязка — почти то же самое, что и односторонняя активная привязка.

Привязка ASP.NET AJAX выражается через JavaScript-объект Sys.Binding. Вы можете управлять направлением потока данных через атрибут mode объекта Sys.Binding, который инфраструктура создает за вас каждый раз, когда вы используете синтаксис активной привязки. (Заметьте, что объект Sys.Binding не создается при использовании простых подставляемых выражений {{...}}.)

Следующий фрагмент кода показывает, как настроить двухстороннюю привязку с помощью атрибута mode:

<span id="Label1">{binding CompanyName, mode=twoWay}></span>

Возможные значения атрибута mode приведены в табл. 1.

Член Значение Описание
Auto 0 Поток данных работает в режиме twoWay, если цель является элементом ввода, выбора, textArea или компонентом, реализующим интерфейс Sys.INotifyPropertyChange. В ином случае поток данных работает в режиме oneWay.
onetime 1 Данные передаются от объекта-источника целевому объекту только раз — при рендеринге шаблона
oneWay 2 Данные передаются от объекта-источника целевому объекту всякий раз, когда обновляется источник
twoWay 3 Данные передаются от объекта-источника целевому объекту всякий раз, когда обновляется источник, и от целевого объекта источнику всякий раз, когда изменяется целевой объект
oneWaytoSource 4 Данные передаются от целевого объекта источнику при каждом изменении целевого объекта

Табл. 1 Перечисление Sys.BindingMode

Активная привязка в действии

На рис. 3 приведен пример кода, демонстрирующий активную двухстороннюю привязку. Страница содержит один шаблон, рендеринг которого осуществляется с использованием элемента управления DataView. Объект источника данных является JavaScript-массивом с именем theCustomers. Пусть вас не вводит в заблуждение тот факт, что theCustomers — локальный объект, статически определяемый в клиентском браузере. По-настоящему важно то, что theCustomers в конечном счете присваивается свойству data объекта DataView. Объект DataView предоставляет богатый программный интерфейс, позволяющий помещать в свойство data любой контент, в том числе получаемый от веб-сервиса.

<asp:Content ID="Content2" runat="server" ContentPlaceHolderID="PH_Head">
  
    <link rel="stylesheet" type="text/css" 
      href="../../Css/lessantique.css" />
    <style type="text/css">
        .sys-template { display:none; }
    </style>
    
    <script type="text/javascript">
        var theCustomers = [
           { ID: "ALFKI", CompanyName: 
            "Alfred Futterkiste" },
           { ID: "CONTS", CompanyName: 
           "Contoso" }
        ];
    </script>
</asp:Content>    

<asp:Content ID="Content5" 
  ContentPlaceHolderID="PH_Body" 
  runat="server">

    <asp:ScriptManagerProxy  
    runat="server">
        <Scripts>
          <asp:ScriptReference Path=
"http://ajax.microsoft.com/ajax/beta/0910/MicrosoftAjaxTemplates.js" />
        </Scripts>
    </asp:ScriptManagerProxy>

    <div id="customerList">
       <ul class="sys-template" 
         sys:attach="dataview" dataview:data="{{ theCustomers }}">
         <li>
           <span><b>{binding ID}</b></span>
           <input type="text" id="TextBox1" 
             value="{binding CompanyName}" /> 
           <br />  
           <span>Currently displaying... 
<b>{binding CompanyName}</b></span>  
         </li>
       </ul>
    </div>
</asp:Content>

Рис. 3 Пример активной двухсторонней привязки

Для каждого связанного элемента данных шаблон генерирует тег LI, включающий текстовое поле. Это поле связывается со свойством CompanyName источника. В том же шаблоне тег SPAN тоже связан со свойством CompanyName источника.

Заметьте, что активные привязки не ограничены шаблоном, к которому они относятся. Одно и то же выражение, например {binding CompanyName}, может находиться в двух разных шаблонах. Если к обоим шаблонам подключен один и тот же источник данных (или совместимый объект), привязка всегда будет разрешаться корректно. На рис. 4 показана страница в действии.

Рис. 4 Активная привязка в действии

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

Этап редактирования считается законченным только после того, как текстовое поле теряет фокус ввода. В этот момент срабатывает механизм двухсторонней привязки и обновляет нижележащий объект — источник данных. Поскольку тег SPAN связан с тем же свойством data через активную привязку, любые изменения распространяются дальше.

Чтобы предотвратить автоматическое обновление источника данных при использовании элемента INPUT, задайте свойство mode явным образом:

<input type="text" value="{binding CompanyName, mode=oneWay}" />

Внесите это изменение в код на рис. 3 и вы увидите разницу.

Обнаружение изменений

Если свойство mode привязки не задано явным образом, оно получает значение auto, которое действует, как описано в табл. 1. Так что, когда вы подключаете привязку к любым HTML-элементам, связанным с вводом (например, INPUT, SELECT или TEXTAREA), свойство mode по умолчанию устанавливается в twoWay. В итоге все изменения в целевом объекте, внесенные через UI браузера, автоматически передаются в источник.

Заметьте, что существует две вариации привязки oneWay. Стандартная привязка oneWay обнаруживает изменения в источнике и отражает их в UI. Альтернативная oneWayToSource делает обратное: она обнаруживает изменения в целевом объекте и отражает их в объекте-источнике. Попробуйте вставить такой код:

<input type="text" value="{binding CompanyName, mode=oneWayToSource}" />

Изначально отображаемая страница будет содержать два пустых текстовых поля. Однако по мере ввода будет распознаваться новый текст и соответствующим образом обрабатываться.

Двухсторонняя привязка также является вариантом по умолчанию для любого связанного JavaScript-объекта, реализующего интерфейс Sys.INotifyPropertyChange. (Этот интерфейс — часть библиотеки Microsoft Ajax JavaScript Library.)

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

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

Для отражения обновления одной стороны привязки на другой изменение должно происходить так, чтобы его можно было обнаружить. Допустим, у вас есть привязка, где источник — JavaScript-массив:

<ul class="sys-template" sys:attach="dataview" 
    dataview:data="{{ theCustomers }}">
  <li>
     <span ><b>{binding CompanyName}</b></span>
  </li>
</ul>

Попробуйте обновить свойство CompanyName объекта внутри массива. В разметке появится кнопка, щелчок которой запустит JavaScript-функцию enterChanges:

<span>{binding CompanyName}</span>
<input type="button" value="Enter changes" 
onclick="enterChanges()" />
...
<script type="text/javascript">
    function enterChanges() {
        theCustomers[0].CompanyName = 
       "This is a new name";
    }
</script>

Функция enterChanges обновляет свойство CompanyName первого объекта в массиве. Как видите, это явно операция, изменяющая состояние связанного объекта.

В случае активной привязки следовало бы ожидать, что новое значение покажет тег SPAN. Но если вы попробуете так сделать, у вас ничего не выйдет.

Дело в том, что универсального для разных браузеров способа уведомления об обновлении в таком старом JavaScript-объекте нет. Поэтому изменения происходят, а привязка остается в неведении о них, и UI не обновляется.

Жизнеспособен ли вариант с опросом состояния обычного JavaScript-объекта? Скорее всего нет, и разработчики резонно исключили этот вариант — главным образом по соображениям масштабируемости.

В конце концов, разве использование HTML-элементов ввода, связанных с данными, является единственной возможностью внесения изменений в данные? Не совсем. В библиотеке Microsoft Ajax JavaScript Library имеется статический API, через который вы можете "наблюдать" за изменениями в любом JavaScript-объекте. Этот API также доступен в варианте, где обычный JavaScript-объект преобразуется в отслеживаемый, чтобы механизм привязки мог сам обнаруживать изменения.

Отслеживаемые JavaScript-объекты

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

Отслеживаемые объекты отлично подходят в ситуации, где нужно установить активные привязки между визуальными элементами и JavaScript-объектами, например такими, которые вы можете получать от удаленного веб-сервиса.

С отслеживаемыми объектами можно работать двумя способами. В первом случае вы преобразуете конкретный объект в отслеживаемый, добавляя к нему кое-какой динамический код; его объем не столь велик, чтобы обычный JavaScript-объект превратился в нечто сложное, но достаточен для введения новых возможностей. Вот пример:

<script type="text/javascript">
    var theCustomers = [
            { ID: "ALFKI", CompanyName: 
            "Alfred Futterkiste" },
            { ID: "CONTS", CompanyName: 
            "Contoso" }
        ];    
    function pageLoad() {
        Sys.Observer.makeObservable(theCustomers);
    }
    
    function onInsert() {
        var newCustomer = { ID: "ANYNA", 
        CompanyName: "AnyNameThatWorks Inc" };
        theCustomers.add(newCustomer);
    }
</script>

Метод Sys.Observer.makeObservable принимает JavaScript-объект (в том числе массивы) и добавляет методы, с помощью которых вы можете вносить в объект обнаруживаемые привязками изменения. Заметьте, что отслеживаемый массив предоставляет методы для изменения этого массива обнаруживаемым инфраструктурой образом, а это позволяет распознавать вставки и удаления. Но он не предоставляет автоматически соответствующие методы для корректной модификации свойств индивидуальных элементов в массиве. Для этого вам придется отдельно вызвать makeObservable для каждого элемента, и тогда нужные методы будут добавлены и к этим элементам.

Как я упоминал, следующий код, сопоставленный с событием click, не вызовет срабатывания привязки:

<script type="text/javascript">
    function enterChanges() {
        theCustomers[0].CompanyName = 
        "This is a new name";
    }
</script>

А этот код — вызовет:

<script type="text/javascript">
    function enterChanges() {
       System.Observer.setValue(theCustomers[0], 
       "CompanyName", "New name");
    }
</script>

А как быть, если у отслеживаемого объекта есть дочерние объекты? Не волнуйтесь: методу setValue известно, как обрабатывать "точечный" синтаксис:

System.Observer.setValue(theCustomers[0], "Company.Address.City", "Rome");

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

Статическая и динамическая привязка

Используя в приложении связывание с данными, вы чаще всего предпочтете активную привязку и, как минимум, одностороннюю, если не двухстороннюю. В ASP.NET AJAX 4.0 привязка может быть статической (простая оценка подставляемых значений в момент рендеринга) и динамической (в том плане, что она позволяет распознавать изменения в источнике или целевом объекте и применять их). Не все изменения можно обнаруживать и использовать для обновления привязок. ASP.NET AJAX 4.0 позволяет легко распознавать изменения, вносимые в связанные объекты через визуальные элементы. Но в случае программных изменений в JavaScript-объектах и массивах надежного и универсального для всех браузеров способа активного распознавания нет. Трюк, применимый в ASP.NET AJAX, заключается в том, чтобы сделать изменения отслеживаемыми и поэтому обнаруживаемыми активными привязками. Для этого к объекту добавляются некоторые отслеживаемые операции или — в качестве альтернативы — используются статические методы Sys.Observer.

Дино Эспозито (Dino Esposito) — автор книги "Programming ASP.NET MVC 2" (Microsoft Press, 2010). Проживает в Италии и часто выступает на отраслевых мероприятиях по всему миру. С ним можно связаться через его блог weblogs.asp.net/despos.

Выражаю благодарность за рецензирование этой статьи экспертам Дейву Риду (Dave Reed) и Борису Риверс-Муру (Boris Rivers-Moore).