Доступ к данным

Создаем и используем данные OData в формате JSON

Джули Лерман

Исходный код можно скачать по ссылке

Джули Лерман

В прошлой статье «Data Bind OData in Web Apps with Knockout.js» (msdn.microsoft.com/magazine/jj133816) я немного поиграла с Knockout.js и OData. Изучив, как связывать Knockout с результатами от OData-сервиса, я также узнала больше об OData и JSON, чем мне было известно раньше. В этой статье я переключу свое внимание на использование JSON от OData и создание WCF-сервисов данных, дружественных к JSON.

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

OData — это спецификация, гарантирующая, что потребители сервисов данных смогут единообразно использовать различные сервисы. Одно из правил в этой спецификации — OData по умолчанию выводит результаты в формате ATOM (это специфический вариант форматирования XML), но может выводить их и в формате JSON.

По мере развития спецификации OData в нее закладывается новая функциональность, и вы, возможно, захотите, чтобы ее могли задействовать пользователи вашего сервиса. После того как мы обсудим различные способы использования и создания JSON в OData-каналах, я расскажу о том, как предстоящие изменения в спецификации отразятся на OData и что вы можете сделать уже сегодня для подготовки к этим изменениям. Я начну с предоставления доступа к Customer с помощью следующей схемы в своем сервисе:

public class Customer
{
  public int Id { get; set; }
  public string FirstName { get; set; }
  public string LastName { get; set; }
  public string AccountNumber { get; set; }
}

По умолчанию результат Customer от моего сервиса был бы в виде данных ATOM и выглядел бы (после форматирования браузером), как показано на рис. 1.

Результаты в формате ATOM

Рис. 1. Результаты в формате ATOM

Тот же результат в формате JSON будет таким:

{"d":[
{"__metadata":{"id":"http://localhost:43447/DataService.svc/Customers(1)",
               "uri":"http://localhost:43447/DataService.svc/Customers(1)",
               "type":"DataAccessMSDNJuly2012.Customer"},
  "Id":1,
  "FirstName":"Julie",
  "LastName":"Lerman",
  "AccountNumber":"A123"}
]}

Если вы работаете с исходными результатами ATOM или JSON, формат вывода может оказаться очень важным с точки зрения удобства кодирования. Например, если вы используете канал в JavaScript, гораздо проще работать непосредственно с JSON-объектом, чем разбирать XML.

Есть два способа гарантировать, что ваши результаты будут возвращаться в формате JSON: вы можете либо указать application/json в заголовке запроса, либо добавить параметр в запрос OData. При формировании запросов в Fiddler для целей тестирования довольно легко добавить заголовок, как показано на рис. 2.

Задание JSON в заголовке запроса

Рис. 2. Задание JSON в заголовке запроса

Вы также можете добавить заголовок в JQuery при создании запроса, но ввиду наличия пары других вариантов вам незачем заходить так далеко. Один из этих вариантов — использование datajs, библиотеки JavaScript для OData, которая вдобавок дает массу других преимуществ при кодировании с применением OData. Второй вариант — применение параметра $format в запросе OData.

Получение JSON по умолчанию с помощью datajs

Библиотеку datajs можно скачать с datajs.codeplex.com. На момент написания этой статьи текущей версией является 1.0.3.

Если вы используете Visual Studio, то можете включить datajs в проект с помощью диспетчера пакетов NuGet. NuGet добавит в ваш проект папку scripts и поместит в нее текущую версию datajs (datajs-1.0.3.js) и связанные с ней минимально необходимые файлы скриптов.

Эта библиотека скриптов предоставляет класс OData, который упрощает чтение и запись в каналах, совместимых с OData, а также содержит другие средства. Хотя заголовок в запрос можно легко вставить вызовом функции OData.read, это не обязательно. По умолчанию datajs будет автоматически добавлять заголовок, так как, если вы используете эту библиотеку, вам скорее всего нужен JSON.

Ниже приведен JavaScript-код, который вызывает OData.read, передает Uri, представляющий запрос моего сервиса данных, и указывает, что делать с результатами (в данном случае я сохраняю результаты в переменной customer):

<script src="Scripts/datajs-1.0.2.js" type="text/javascript"></script>
<script type="text/javascript" charset="utf-8">
  var customer;
  OData.read({ requestUri:"http://localhost:43447/DataService.svc/Customers?$top=1"
             },
             function (data, response) {
               customer = data.results[0];
             },
             function (err) {
               alert("Error occurred: " + err.message);
             });
</script>

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

Просмотр JSON-результата в режиме отладки

Рис. 3. Просмотр JSON-результата в режиме отладки

Запустив Fiddler для захвата HTTP-трафика можно увидеть, что заголовок Accept, указывающий application/json, был частью этого запроса.

Если вы программируете на PHP, то должны проверить соответствующую библиотеку — OData SDK for PHP (odataphp.codeplex.com). Как и библиотека datajs для разработчиков на JavaScript, PHP SDK гарантирует вам релевантность результатов. Но в таком случае это достигается запросом формата ATOM и последующей материализацией полученных ATOM-данных в PHP-объекты.

Запрос JSON с помощью параметра $format

Не все, кому нужен JSON, будут использовать библиотеку datajs. Но это не означает, что вы должны обязательно использовать заголовок в запросе, чтобы получать JSON. В спецификации OData также предусмотрен параметр $format, который принимает json как одно из своих значений.

Вот модифицированный Uri, напрямую запрашивающий JSON:

http://localhost:43447/DataService.svc/Customers(1)?$format=json

Вы можете добавлять параметр $format в любой запрос. Ниже я комбинирую запрос формата с фильтром:

http://localhost:43447/DataService.svc/Customers?$filter=FirstName eq 'Julie'&$format=json

Как заставить сервис данных корректно поддерживать параметр $format

Хотя использование параметра формата для запроса JSON является частью спецификации OData, не каждому сервису данных известно, как обрабатывать этот параметр. По сути, когда вы создаете WCF-сервис данных, он по умолчанию возвращает ATOM. Поэтому, хотя сервис примет параметр $format=json, в ответ он все равно будет возвращать ATOM. Но некоторые разработчики из группы OData поделились простым решением (доступно по ссылке archive.msdn.microsoft.com/DataServicesJSONP), которое вы можете добавить в свое приложение, и это позволит WCF Data Services обрабатывать команду $format и возвращать JSON.

Данное расширение предоставляется в виде класса JSONPSupportInspector и атрибута JSONP¬SupportBehavior. Оба класса используют логику из System.Component¬Model. Вы можете добавить эти классы напрямую в свой проект или создать сборку из них и ссылаться на нее. Как только ваш WCF-сервис данных получит доступ к этим классам, вам потребуется лишь добавить атрибут JSONPSupportBehavior к классу сервиса, например:

[JSONPSupportBehavior]
public class DataService : DataService<SalesContext>
{
  public static void InitializeService(DataServiceConfiguration config)
  {
    config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
    config.SetEntitySetAccessRule("Orders", EntitySetRights.All);
  }

После включения этого атрибута мой сервис будет реагировать на параметр $format=json, когда я добавлю его в запрос, и сервис будет возвращать JSON.

Я пишу эту статью по следам апрельского выпуска WCF Data Services 5, который все еще требует от вас явного добавления поддержки JSONP в ваши сервисы данных. Если вы хотите, чтобы эту поддержку в будущем включили в WCF Data Services API, голосуйте за нее на странице UserVoice Feature Suggestions группы (bit.ly/ImeTQt).

Поддержка JSON и версии OData

Спецификация OData в настоящее время находится на стадии версии 2 и продолжает развиваться. Сейчас идут работы над версией 3, и бета-версия документации на нее уже доступна на OData.org. По умолчанию ваш WCF-сервис данных будет ориентироваться на версию 1. К сожалению, этот вариант по умолчанию не годится, если вы хотите использовать функциональность OData версии 2, например разбиение на страницы на серверной стороне, доступную в WCF Data Services. Для демонстрации я добавила в свой сервис конфигурацию SetEntitySetPageSize:

public static void InitializeService(DataServiceConfiguration config)
  {
    config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
    config.SetEntitySetAccessRule("Orders", EntitySetRights.All);
    config.SetEntitySetPageSize("Customers", 3);
  }

Из-за использования функциональности версии 2 сервис сгенерирует исключение и сообщит, что вы должны указать значение MaxProtocolVersion выше версии 1. Вот ошибка, связанная с конфигурацией SetEntitySetPageSize:

«Разбиение на страницы на серверной стороне нельзя использовать, если MaxProtocol¬Version сервиса данных установлен в DataServiceProtocolVersion.V1».

Чтобы исправить эту проблему, достаточно задать версию в коде сервиса:

public static void InitializeService(DataSeviceConfiguration config)
  {
    config.SetEntitySetAccessRule("Customers", EntitySetRights.All);
    config.SetEntitySetAccessRule("Orders", EntitySetRights.All);
    config.SetEntitySetPageSize("Customers", 3);
    config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2;
  }

И даже в этом случае все равно можно запрашивать JSON-вывод либо в заголовке запроса, либо через параметр $format в Uri.

Однако вы, возможно, предпочтете проектировать свой сервис с расчетом на будущую совместимость, задав в MaxProtocolVersion значение «DataServiceProtocol¬Version.V3». Но в версии 3 формат JSON будет изменен на более простой — JSON Light (bit.ly/JrM6RQ). Это может стать крупной проблемой, если ваше клиентское приложение не ожидает такого варианта формата JSON. В этом случае приложение-потребитель обнаружит, что его запрос JSON проигнорирован и что оно получило данные в формате ATOM, а не JSON.

Вся штука в том, что ваш клиент должен либо явно запрашивать более детализированную версию формата, либо указывать целевую версию OData. В ином случае сервис будет возвращать ATOM. Поэтому вам придется обязательно выбрать один из двух вариантов, чтобы получать JSON-результаты, если сервис настроен на версию 3.

Вот заголовок, модифицированный так, чтобы специально запрашивать детальный формат JSON:

Accept: application/json;odata=verbose

А вот пример заголовка с запросом на то, чтобы в ответе OData использовалось форматирование версии 2:

Accept: application/json
MaxDataServiceVersion: 2.0

Что будет, если в запросе используется параметр $format=json вместо запроса JSON в заголовке? И вновь сервис не поймет запрос, и вы получите ошибку с сообщением о том, что данный MIME-тип не поддерживается. В этом случае вы должны включить MaxDataServiceVersion в заголовок своего запроса.

Однако, если вы применяете datajs, вам не о чем беспокоиться. Начиная с версии 1.0.3, этой библиотеке известно о необходимости информации о версии, и она предоставляет ее в своих запросах. При ее наличии ваш сервис готов к OData версии 3, а потребители получат гибкие возможности в запросе тех версий формата, которые им нужны.

JSON в конвейере OData

Благодаря упрощенному формату JSON становится все более важным для разработчиков. Возможно, вы помните, что некоторые новые базы данных документов, о которых я писала в статье «What the Heck are Document Databases?» (msdn.microsoft.com/magazine/hh547103) за ноябрь 2011 г., хранят данные с использованием JSON или некоей его разновидности.

Если вы вернетесь к прошлой статье, то найдете примеры чтения и записи в сервисах данных с применением JSON и Knockout.js.

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


Джули ЛерманMicrosoft MVP, преподаватель и консультант по .NET, живет в Вермонте. Часто выступает на конференциях по всему миру и в группах пользователей по тематике, связанной с доступом к данным и другими технологиями Microsoft .NET. Ведет блог thedatafarm.com/blog и является автором книг «Programming Entity Framework» (O’Reilly Media, 2010) и «Programming Entity Framework: Code First» (O’Reilly Media, 2011). Вы также можете читать ее заметки в twitter.com/julielerman.

Выражаю благодарность за рецензирование статьи эксперту Алехандро Триго (Alejandro Trigo).