Программная логика приложений для Windows 8 и их взаимодействие с системой

Сетевое взаимодействие

Разбить на страницы
Показывать лекцию целиком

Введение

Материалы к лекциям 7-9 Вы можете скачать здесь.

В прошлом году я с семьей два раза переезжал, и я был поражен сходством между этим процессом и тем, как по нашим сетям перемещаются данные. Укладка вещей в грузовик для первого переезда – из Орегона в Калифорнию, конечно, была упражнением на сжатие! Порой я думаю, что мы не смогли уместить все в наш фургон и в арендованный 20-футовый грузовик, но как-то это все вместилось. Затем у нас было долгое путешествие к югу, прежде чем пришло время декомпрессии данных, то есть, в дом, где мы жили некоторое время, пока строители доделывали наше постоянное жилище.

Переезд в новый дом – это кое-что другое, в основном потому, что он был метрах в 200-х, и мы могли переносить все по частям в разное время. Когда дом был принят, все бумаги подписаны, и мы могли (легально) перенести всю мебель и коробки, эта задача была решена с помощью многих друзей и множество разных транспортных средств, с очень невысокой степенью сжатия наших вещей, пока они были в пути!

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

В том же духе, поразмыслите, как вы можете нанять грузчиков для того, чтобы сделать все это – вы показываете им ваши вещи, даете им адрес назначения, выписываете им немаленький чек, и как по волшебству все ваши вещи оказываются в новом месте. Подобный процесс напоминает API фоновой передачи данных в WinRT, одна из первых тем этой лекции. При домашнем переезде существует так же концепция использования наемных транспортных средств и упаковки "коробков", в таком случае вы упаковываете все сами, но перевозка, (и, возможно, хранение) обрабатывается отдельно. Но я не так уж уверен в том, поступает ли так кто-нибудь при написании приложений для Windows 8!

Чтобы не углубляться в подобные аналогии, давайте просто скажем, что сетевое взаимодействие – это богатая и обширная тема, где все основана на необходимости переместить данные из одного пункта в другой различными способами. Цель этой лекции, таким образом, представить, по крайней мере, обзор сетевых возможностей Windows 8. Материалы этой лекции варьируются от XmlHttpRequests, фоновой передачи данных, проверки подлинности и учетных данных и синдикации, до сетевых подключений и сетевой информации, функциональности при нахождении вне сети (оффлайновой), и сокетов. Мы сосредоточимся на большинстве тем, представляющих интерес для большинства приложений, кратко затронув другие вопросы, более специфичных для подобных сценариев и поговорим о множестве дополнительных ресурсах полезной информации. Один из таких ресурсов – материал "Разработка подключенных приложений" (http://msdn.microsoft.com/library/windows/apps/hh465399.aspx), который служит хорошим общим обзором сетевых возможностей.

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

Информация о сети и возможности подключения

В предыдущей лекции, в сносках, я у поминал о том, что тогда, когда я писал о динамических плитках, все соединения с Интернетом, которые могли быть у приложений для Windows 8, мой дом и много тысяч других в Северной Калифорнии, были полностью отключены из-за аварии на оптоволоконном канале. Сбой длился, казалось, целую вечность по современным стандартам – 36 часов! Хотя я нашел, чем себя занять, был момент, когда я открыл один из моих ноутбуков, увидел, что связи все еще нет, и на мгновение задался вопросом о том, на что годен компьютер без подключения к сети! Очевидно, я привык, как, подозреваю, и вы тоже, воспринимать постоянное подключение к сети как нечто само собой разумеющееся.

Как разработчики отличных приложений, однако, мы не может позволить себе быть столь самодовольными. Всегда важно обрабатывать ошибки при попытке установить соединение и получить данные из сетевых источников, так как даже при проведении единственной операции может возникнуть любое количество проблем. Но нам нужно мыслить глубже. Это наша работа – сделать наши приложения такими полезными, как только возможно, при потере сетевого соединения, что, возможно, произошло лишь потому, что наши пользователи находятся в самолете и включили на устройстве режим работы в самолете. Таким образом, не давайте пользователям причины задумываться о полезности их устройств в подобной ситуации! Отличное приложение докажет свою полезность с помощью отличного опыта взаимодействия с ним пользователя даже в отсутствии сетевого подключения.

Сетевое подключение, кроме того, может меняться в течение сеанса работы приложения, когда приложение может часто приостанавливаться и восстанавливаться, или быть в приостановленном состоянии долгое время. В особенности это касается мобильных устройств, когда кто-то может переключаться между множеством сетей без необходимости даже знать об этом. Windows 8, на самом деле, пытается сделать переход между сетями настолько прозрачным, насколько это возможно, за исключением ситуаций, когда важно оповестить пользователя о том, что работа в новой сети может стоить некоторую сумму. Это требуется политикой Магазина Windows, чтобы приложения знали о стоимости передачи данных в лимитированных сетях и предотвращали "шоковые счета" от не всегда щедрых провайдеров мобильной широкополосной связи. Так же, как то, что приложение не может выполнять некоторые действия, когда устройство не подключено к сети, характеристики текущей сети могут привести к тому, что приложение отложит или отменит некоторые операции.

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

Типы сетей в манифесте

Почти каждый пример, с которыми мы до сих пор работали, имеет возможность Интернет (Клиент) (Internet (Client)), объявленную в манифесте, благодаря Visual Studio, который включает ее по умолчанию. Ранее я упоминал, что так было не всегда: первые разработчики приложений в Microsoft время от времени чесали голову, пытаясь понять, почему что-то вполне очевидное, вроде простого запроса XmlHttpRequest к блогу, отказывается работать. Без этой возможности у приложения попросту нет доступа к Интернету!

Тем не менее, Интернет (Клиент) (Internet (Client)) – не единственный участник игры возможностей. Некоторые сетевые приложения так же планируют выступать в роли сервера для получения входящих данных из Интернета, а не только делать запросы к другим серверам. В подобных случаях – в таких, как предоставление общего доступа к файлу, работа медиа-серверов, VoIP-связь, чат, многопользовательские игры, и в других подобых сценариев двунаправленного обмена данными, включающими в себя входящий сетевой трафик, как в случас с сокетами – приложение должно объявить возможность Интернет (клиент и сервер) Internet (Client Server), как показано на рис 7.1. Это позволяет подобному трафику проходить через локальный брандмауэр, хотя критически важные порты всегда заблокированы.

Есть, кроме того, сетевой трафик, который циркулирует по частным сетям, в домах или офисах, причем, данные из Интернета в этом не участвуют. Для подобных ситуаций существует возможность Частные сети (клиент и сервер) (Private Networks (Client Server)), так же показанная на рис 7.1, которая подходит для организации общего доступа к файлам или мультимедийным данным, для бизнес-приложений, HTTP-клиентов, многопользовательских игр для локальных сетей и так далее. Что делает каждый конкретный IP-адрес принадлежащим к конкретной частной сети, зависит от многих факторов, все из которых описаны в материале "Настройка сетевых характеристик" (http://msdn.microsoft.com/library/windows/apps/Hh770532.aspx). Например, IPv4-адреса в диапазонах 10.0.0.0–10.255.255.255, 172.16.0.0–172.31.255.255, и 192.168.0.0–192.168.255.255 считаются частными. Пользователь может отметить сеть как доверенную, так же, присутствие контроллера домена делает сеть частной. В любом случае, если пункт назначения сетевых данных попадает в эту категорию, поведение приложения на данном устройстве зависит от этой возможности, а не от тех, которые имеют отношение к Интернету.

(рис 7.1) Дополнительные сетевые возможности в манифесте

Врезка: Локальная обратная петля

Вне зависимости от возможностей, объявленных в манифесте, локальная обратная петля, (то есть, использование URI http://localhost) заблокирована для приложений Магазина Windows. Исключение сделано лишь для компьютеров, на которых установлена лицензия разработчика, как описано в Главе 2. Это исключение существует лишь для упрощения отладки совместной работы приложений и сервисов, так как при разработке они могут исполняться на одном и том же компьютере.

Информация о сети (Реестр сетевых объектов)

Не зависимо от того, о какой сети идет речь, все, что вам может понадобиться узнать об этой сети, доступно с помощью объекта ), за исключением единственного события networkstatuschanged, которое мы рассмотрим в разделе "События подключения", немного позже. Интерфейс этого объекта состоит из методов для получения более подробных сведений в других объектах.

Выполняя ранее данное обещание о том, что мы лишь затронем некоторые специальные темы, ниже привожу реестр методов из NetworkInformation и содержимого объектов, получаемых с их помощью. Вы можете испытать большинство из этих API в отмеченных сценариях примера "Информация о сети" (http://code.msdn.microsoft.com/windowsapps/Network-Information-Sample-63aaa201):

  • ), один для каждого подключения, который содержит строки с различными именами (), со значениями domainName, ipv4, ipv6, и bluetooth), и свойство ipinformation (типа IPInformation (http://msdn.microsoft.com/library/windows/apps/windows.networking.connectivity.ipinformation.aspx)) содержащее свойства prefixLength и networkAdapter для хостов IPV4 и IPV6. (Последнее – это объект ) с различными низкоуровневыми сведениями.) Класс HostName используется в различных API работы с сетью для идентификации сервера или какой-то другой конечной точки.
  • ), по одному для каждого соединения, среди которых будут присутствовать активное интернет-соединение, возвращенное getInternetConnectionProfile. Кроме того, включает в себя любые беспроводные соединения, которые вы установили ранее и для которых указали Подключать автоматически (Connect Automatically). (В подобной ситуации пример покажет вам некоторые подробности о том, где вы недавно были!). Смотрите следующий раздел для того, чтобы узнать подробности о ConnectionProfile.
  • ) для Интернет-соединения, активного в настоящий момент. Если присутствует больше одного соединения, этот метод возвратит профиль предпочитаемого соединения, которое, вероятнее всего, будет использовано для передачи Интернет-трафика.
  • ), каждый из которых содержит свойство infrastructureId (значение типа LanIdentifierData, содержащее type и value), свойство networkAdapterId (GUID), и свойство portId (LanIdentifierData).
  • ) для заданного URI и текущего пользователя. У этого объекта есть свойства: canConnectDirectly (логического типа) и proxyUris (вектор объектов Windows.Foundation.Uri для конфигурации).
  • x) массив объектов ). Объект EndpointPair содержит имя хоста и сервиса для локальных и удаленных конечных точек, обычно получаемых при установке конкретных соединений, наподобие сокетов. Два параметра сортировки – это none и optimizeForLongConnections, который различает поведение соединений на основе того, устанавливает ли приложения короткие или длительные соединений. Для того, чтобы узнать подробности, обратитесь к домументации по EndpointPair и HostNameSortOptions.
  • Объект ConnectionProfile

    Среди всей информации, доступной с помощью объекта ), наиболее часто данный объект получают с помощью getInternetConnectionProfile, так как именно по этому соединению передается Интернет-трафик приложения. Профиль – это то, что содержит всю информацию, которая необходима для того, чтобы принять решение о том, как использовать сеть, в особенности для того, чтобы знать о стоимости передачи данных. Это, так же, то, что обычно здесь проверяют при изменениях в состоянии сети. Сценарии 1 и 3 примера "Информация о сети" получают и отображают большинство из этих сведений.

    У каждого профиля есть свойство ), описывающий проверку подлинности и типы шифрования

    В общем случае более интересен метод ): none (нет соединения), localAccess (уровень соединения, который вы возненавидите, если вам нужно хорошее соединение!), constrainedInternetAccess (привязаное портальное подключение, обычно требующее дополнительных учетных данных, как часто бывает в отелях, аэропортах и так далее), и internetAccess (состояние, к которому вы практически всегда будуте стремиться). Уровень подключения часто влияет на логику приложения, обычно так же отслеживают изменение сетевого статуса.

    Для отслеживания входящего и исходящего сетевого трафика соединения, метод ), который содержит свойства bytesReceived и bytesSent, каждое – либо содержит сведения за все время существования подключения, либо за определенный период. Похожим образом getConnectionCost и getDataPlanStatus предоставляют информацию, необходимую приложению для знания того, сколько потрачено сетевого трафика и сколько это может стоить пользователю. Мы скоро вернемся к этому в разделе "Сведения о стоимости передачи данных", в том числе, поговорим о том, как увидеть использование трафика конкретным приложением в Диспетчере задач (Task Manager).

    События подключения

    Обычно запущенное приложение интересуется изменениями сетевого подключения. Таким образом оно может предпринять необходимые шаги для отключения или включения определенной функциональности, для оповещения пользователя, синхронизации данных после нахождения в состоянии отключения от сети и так далее. Для этого приложению нужно лишь прослушивать событие ), которые вызывается, когда происходит серьезное изменение в иерархии объектов, которые мы только что видели (и не забудьте, это событие исходит из объекта WinRT). Например, событие вызывается, если меняется уровень подключения в профиле. Оно так же вызывается, если меняется профиль Интернета, как когда устройство перемещается между разными сетями, или когда, при применении лимитированного плана передачи данных, приближается или исчерпывается его ограничение, то есть, когда пользователь начинает беспокоиться о каждом мегабайте переданных данных. Коротко говоря, это событие обычно прослушивают для обновления любого внутреннего состояния приложения, зависящего от характеристик сети и установки любых флагов, используемых для настройки сетевого поведения приложения. Это особенно важно при переходах между режимом работы в сети и автономной работы и при переходах между безлимитными и лимитированными сетями. Windows, в свою очередь, так же отслеживает это событие для того, чтобы настроить собственное поведение, например, в реализации функций API фоновой передачи данных.

    Примечание. Приложения для Магазина Windows, написанные на JavaScript так же могут использовать простые события window.nagivator.ononline и window.navigator.onoffline для отслеживания состояния подключений. Так же, свойство window.navigator.onLine property может принимать значения true или false в соответствии с состоянием подключения. Эти события, однако, не оповестят об изменении профилей соединений, стоимости или других аспектов, которые не связаны с базовой доступностью интернет-соединения.

    Вы можете поэкспериментировать с событием networkstatuschanged в Сценарии 5 примера "Информация о сети". При подключении или отключении от сети, или при выполнении других изменений, пример обновит выводимые сведения для текущего Интернет-профиля, если он доступен (вот код, взятый из js/network-status-change.js):

    var networkInfo = Windows.Networking.Connectivity.NetworkInformation;
    // Напоминаю, что для этого WinRT-события может понадобиться removeEventListener
    networkInfo.addEventListener("networkstatuschanged", onNetworkStatusChange);
    
    function onNetworkStatusChange(sender) {
    internetProfileInfo = "Network Status Changed: \n\r";
    var internetProfile = networkInfo.getInternetConnectionProfile();
    
    if (internetProfile === null) {
    // Сообщение об ошибке
    } else {
    internetProfileInfo += getConnectionProfileInfo(internetProfile) + "\n\r";
    // Отображение информации
    }
    
    internetProfileInfo = "";
    }
            

    Конечно, прослушивание этого события полезно только если приложение исполняется, но что, если это не так? В подобном случае приложению нужно зарегистрировать фоновую задачу, как обсуждалось в конце Главы 2, для триггера ), обычно, при необходимости, с применением условий internetAvailable или internetNotAvailable. Пример "Фоновая задача для определения состояния сети" ( http://code.msdn.microsoft.com/windowsapps/Network-status-background-957eb3eb) предоставляет демонстрацию этого, объявляя фоновую задачу в манифесте, с C#-точкой входа для NetworkStatusTask.NetworkStatusBackgroundTask. Задача зарегистрирована в js/network-status-with-internet-present.js (с использованием вспомогательных функций из js/global.js, как обычно делается в примерах использования фоновых задач):

    BackgroundTaskSample.registerBackgroundTask(BackgroundTaskSample.sampleBackgroundTaskEntryPoint,
    BackgroundTaskSample.sampleBackgroundTaskWithConditionName,
    new Windows.ApplicationModel.Background.SystemTrigger(
    Windows.ApplicationModel.Background.SystemTriggerType.networkStateChange, false),
    new Windows.ApplicationModel.Background.SystemCondition(
    Windows.ApplicationModel.Background.SystemConditionType.internetAvailable));
            

    Фоновая задача в BackgroundTask.cs просто записывает имя Интернет-профиля и id сетевого адаптера в область локальных данных приложения в ответ на триггер. Эти значения выводятся на экран в completeHandler в js/global.js. Реальное приложение, конечно, будет делать что-то более полезное, например, активировать фоновую передачу данных для синхронизации данных при восстановлении соединения. Несмотя на это, пример представляет необходимую для реализации подобных механизмов базовую структуру.

    Кроме того, очень важно помнить о том, что состояние сети может измениться, когда приложение приостановлено. Приложениям, которые отслеживают событие networkstatuschanged, следует так же обновлять свое состояние, связанное с соединением в обработчике возобновления работы.

    В качестве последнего замечания, хочу посоветовать вам обратиться к материалу "Диагностика и отладка ошибок сетевых подключений" (http://msdn.microsoft.com/library/windows/apps/hh770534.aspx), который содержит руководство по реализации реакции приложения на изменения состояния сети и по обработке сетевых ошибок.

    Сведения о стоимости передачи данных

    Если вы когда-нибудь оказывались в 3G-роуминге со смартфоном, который настроен на автоматическую загрузку электронной почты, вы, возможно, научились на собственном горьком опыте отключать синхронизацию в подобных обстоятельствах. Я однажды ехал из штата Вашингтон в Канаду, не представляя, что я плачу $15 за мегабайт за возможность загружать большие почтовые вложения. Конечно, я, как законопослушный гражданин, не смотрел на телефон, пока вел машину (пишу это и подмигиваю!) для того, чтобы заметить, что я в роуминге. А через несколько недель я узнал, что такое "шоковый счет"!

    Дело в том, что если пользователь решит, что ваше приложение в ответе за подобное поведение, независимо от того, прав ли он, вряд ли в Магазине Windows у вашего приложения появятся хорошие оценки и отзывы! Таким образом, очень важно обращаться внимание на изменения в стоимости профилей подключения, которыми вы пользуетесь, обычно это касается Интернет-профиля. Всегда проверяйте это при старте, в обработчике события networkstatuschanged и в обработчике возобновления работы.

    Вы, и, хочу добавить, все ваши пользователи, могут отслеживать использование приложением сети на закладке App History (Журнал приложений) в Task Manager (Диспетчер задач), как показано ниже. Убедитесь, что вы развернули область просмотра, нажав на More Details (Подробнее) в нижнем левом углу, если вы не видите того, что приведено на рисунке. Вы можете видеть здесь использование трафика по показателям Network (Сеть) и Metered Network (Сеть с учетом трафика), а так же – сведения о сетевом трафике, потребленном при обновлении плиток:

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

  • ), может принимать значения: unknown, unrestricted (дополнительная плата не взимается), fixed (неограниченное, до достижения лимита), и variable (взимается плата на основе учета количества переданных байтов или мегабайтов).
  • roaming Значение логического типа, которое указывает на то, подключены ли вы к сети, которая находится за пределами нормальной зоны покрытия вашего оператора, что подразумевает возможность дополнительной платы. Приложению следует весьма консервативно пользоваться сетью, если это значение установлено в true.
  • approachingDataLimit Логическое значение, показывает, что использование данных в сети фиксированного (fixed) типа (смотрите networkCostType) приближается к лимиту тарифного плана.
  • overDataLimit Логическое значение, которое показывает, что лимит фиксированного тарифного плана превышен и за передачу данных взимается дополнительная плата. Если это значение установлено в true, приложению следует весьма консервативно пользоваться сетью, как и в случае, когда свойство roaming равно true.
  • Второй метод, ) со следующими свойствами:

  • dataPlanLimitInMegabytes Максимальный объем передачи данный, который разрешен для подключения в каждом тарифном цикле.
  • ) с одинаково важными свойствами megabytesUsed и lastSyncTime (UTC), которое показывает, когда megabytesUsed было обновлено в последний раз.
  • maxTransferSizeInMegabytes Максимальный рекомендованный объем данных, переданных в одной сетевой операции. Это свойство отражает скорее не возможности самого лимитированного подключения (как следует из документации), а скорее соответствующий верхний предел для передачи данных в этой сети.
  • nextBillingCycle Дата и время в формате UTC, указывающие на то, когда закончится тарифный цикл, что приведет к сбрасыванию dataPlanUsage в ноль.
  • inboundBitsPerSecond и outboundBitsPerSecond указывают на номинальную скорость передачи данных по данному соединению.
  • Пользуясь всеми этими свойствами мы можем принять обоснованное решение о сетевой активности приложения, и/или предупредить пользователя о возможных дополнительных затратах. Очевидно, когда параметр networkCostType имеет значение unrestricted, вы можете делать все, что хотите. С другой стороны, когда тип соединения имеет значение variable и пользователь платить за каждый байт, особенно, когда значение roaming установлено в true, вам понадобится уведомить пользователя о таком состоянии дел и предоставить параметры, с помощью которых пользователь сможет ограничить сетевую деятельность приложения, если не вовсе остановить такую деятельность. В конце концов, пользователь может решить, что определенные виды данных ему нужны. Например, он должен иметь возможность установки качества потокового видео, указывать, нужно ли загружать сообщения электронной почты, или лишь их заголовки, указать нужно ли загружать изображения, указать, нужно ли производить кэширование сетевых данных, иметь возможность выключить фоновое потоковое аудио и так далее.

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

    Приложение, конечно, спросит у пользователя разрешения для выполнения каждой сетевой операции. Здесь решают вы и ваш дизайнер – когда задавать подобные вопросы и как часть. В свою очередь, "Сертификационные требования к приложениям для Windows 8" (http://msdn.microsoft.com/library/windows/apps/hh694083.aspx) (раздел 4.5.) требуют, чтобы вы задавали пользователю вопрос о любой передаче данных, объем которой превышает один мегабайт, когда свойства roaming и overDataLimit установлены в true, и когда производите любую передачу данных в условиях превышания maxTransferSizeInMegabytes.

    Для сетей фиксированного (fixed) типа, когда объем передачи данных не ограничен параметром ). Вызовите getLocalUsage с заданным периодом между lastSyncTime и DateTime.now(). Затем добавьте полученное значение к megabytesUsed и вычтите результат из dataPlanLimitInMegabytes. Это скажет вам, сколько еще данных вы можете передать, не приведя к дополнительным расходам и сможете получить данные для того, чтобы иметь основания задать пользователю вопрос: "Загрузка этого файла приведет к превышению ограничений вашего тарифного плана. Желаете продолжить?"

    В целях упрощения, вы можете рассматривать знание сведений о стоимости подключения в виде трех схем поведени: обычной, консервативной и требующей согласия пользователя, что описано в материале "Краткое руководство: управление тарифными ограничениями в сети с лимитным тарифным планом" (http://msdn.microsoft.com/library/windows/apps/hh750310.asp x). Более общие сведения об управлении соединением в сетях с лимитными тарифными планами можно найти в материале "Разработка подключенных приложений" (http://msdn.microsoft.com/library/windows/apps/hh465399.aspx). Оба материала предоставляют дополнительные сведения о том, как принимать решения, о которых мы здесь говорили. В итоге, предотвращение "шоковых счетов" - и создание отличных приложений, учитывающих стоимость сетевого подключения – это достойное вложение усилий.

    Врезка: Имитация сетей с лимитным тарифным планом

    Вы можете подумать: "Хорошо, я реализую в своем приложении правильное поведение в сетях с лимитным тарифным планам, но как мне все это проверить, не тратя кучу денег на оплату счетов операторов (включая плату за роуминг)?". Простой ответ заключается в том, что вы можете имитировать поведение сети с лимитным танифным планом с помощью любого Wi-Fi-соединения. Во-первых, откройте интерфейс чудо-кнопки Параметры и щелкните по значку вашего сетевого соединения, расположенного около ее нижней части (смотрите верхний левый рисунок, в особенности – верхний левый значок, который имеет подпись "Nuthatch"). В панели Сети (Networks), которая после этого откроется (нижнее правое изображение), щелкните правой кнопкой мыши по беспроводному соединению и выберите параметр Задать как лимитное подключение (Set As Metered Connection):

    Хотя включение этого параметра не установит свойства DataUsage и все те сведения, которые можно получить в реальной лимитированной сети, но благодаря ему networkCostType будет установлено в значение fixed, что позволяет вам увидеть, как отреагирует на это ваше приложение. Так же вы можете воспользоваться элементом меню Показать оценочные сведения об использовании данных (Show Estimated Data Usage) для того, чтобы увидеть, какой трафик генерирует ваше приложение при нормальной работе, и вы можете сбросить счетчик для того, чтобы получить более точные показания:

    Работа без подключения к сети

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

  • Что происходит, если ваше приложение запускается при отсутствии сетевого соединения, и с плитки (основной и дополнительной) и посредством контракта, такого, как контракт Поиск, Общий доступ, контракт средства выбора файлов?
  • Что происходит, если ваше приложение запускается в первый раз, а сетевого соединения нет?
  • Что происходит при потере сетевого соединения во время выполнения вашего приложения?
  • Что происходит, когда соединение восстанавливается?
  • Как описно выше, в разделе "Сведения о стоимости передачи данных", вы можете использовать событие networkstatuschanged для обработки таких ситуаций при исполнении приложения и обработчик resuming для проверки изменения состояния соединения, которое могло произойти пока приложение было приостановлено. Если у вас есть фоновая задача, связанная с триггером networkStateChange, вам сначала нужно сохранить сведения о состоянии, которые сможет проверить ваш обработчик resuming

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

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

    Как и когда кэшировать данные из онлайновых источников, это один из вопросов, ответ на который требует большого искусства от разработчика программного обеспечения. Когда загружать данные? Сколько данных загружать? Где их хранить? Следует ли задать верхнее ограничение на размер кэша? Позволить ли изменять кэшированные данные, что вызовет необходимость синхронизации с сервером, когда соединение будет восстановлено? Это отличные вопросы, но есть, конечно, и другие. Позвольте мне, по крайней мере, предложить несколько идей и предложений.

    Во-первых, вы можете использовать любой сетевой транспорт для того, чтобы загрузить данные для кэширования, такой, как WinJS.xhr, API фоновой передачи данных, а так же механизмом HTML5 AppCache (http://msdn.microsoft.com/library/ie/hh673545.aspx), который хорошо подходит для веб-содержимого, которое вы загружаете в элементы iframe. Обратите внимание на то, что использование AppCache требует, чтобы URI, имеющие дело к вопросу, были объявлены в манифесте, как ApplicationContentUri (URI содержимого) (смотрите Главу 3 курса "Введение в разработку приложений для Windows 8 с использованием HTML, CSS и JavaScript"). В свою очереть, другие данные, полученные из удаленных источников, такие, как изображения, кэшируются автоматически, как временные файлы Интернета. Даже удаленный скрипт, загруженный в iframe, работающий в веб-контексте, кэшируется подобным образом. И механизмы кэширования, и вопрос об ограничениях размера кэша задает Internet Explorer.

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

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

    Лучшее место для хранения кэшированных данных, это папки данных вашего приложения, в особенности, LocalFolder и TemporaryFolder. Избегайте использования RoamingFolder для кэширования данных, полученных из онлайновых источников: помимо риска превысить ограничение на объем перемещаемых данных (смотрите Главу 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"), это еще и не имеет особого смысла. Так как система будет, в любом случае, перемещать эти данные по сети, лучше позволить приложению снова их загрузить, когда ему это будет нужно. То же самое касается и покупок внутри приложения: так как пользователь может легко загрузить эти покупки из Магазина Windows на другое устройство (и приложение на этом устройстве обнаружит, что данные покупки уже оплачены), перемещать их между устройствами не нужно.

    Использовать ли LocalFolder или TemporaryFolder, зависит от того, насколько важны данные в работе приложения. Если приложение не может запуститься без кэша – как, например, приложение с рецептами, которое я раньше упоминал – используйте папку локальных данных приложения. Если кэш – это лишь оптимизация и пользователь может очистить занятое им пространство с помощью средства очистки диска, сохраните его в TemporaryFolder и снова восстановите позже. (Хочу еще раз напомнить, что IndexedDB, как описано в Главе 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript", имеет ограничение на объем данных, который может хранить приложение и общий системный лимит. Если это – потенциальный источник проблем, вы можете воспользоваться другим механизмом хранения данных.

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

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

    Врезка: Сетевое соединение и изображения из удаленных источниках на динамических плитках и всплывающих уведомлениях

    В Главе 2 мы говорили о том, как приложение может выглядеть динамичным, выполняющим какие-то действия, посредством таких возможностей, как динамические плитки и уведомления. Очевидно, периодические уведомления и push-уведомления полностью зависят от состояния соединения и без него работать не будут. Исполняющееся приложение, с другой стороны, может отправлять обновление при отсутствии подключения. В подобных обстоятельствах приложению следует избегать ссылаться на удаленные изображения в обновления, так как они не смогут быть загружены при отсутствии соединения, а системы поддержки плиток и всплывающих уведомлений сейчас не поддерживают использование локальных резервных копий изображений. Таким образом, приложению следует проверить состояние соединения перед отправкой обновления и при его отсутствии использовать локальные (ms-appx:/// или ms-appdata:///) изображения вместо удаленных или использовать шаблоны плиток и уведомлений, которые содержат только текст.

    XmlHttpRequest

    Как мы уже много раз видели, передача данных на веб-сервис и с него с использованием XmlHttpRequest, это вполне обычное дело для приложений для Магазина Windows, особенно для тех, которые написаны на JavaScript, для которых обработка XML и/или JSON весьма проста. Это особенно справедливо при использовании оболочки WinJS.xhr, которая превращает весь процесс в обычный promise-вызов.

    Для того, чтобы пользоваться тем, что мы уже рассматривали в Главе 3 курса "Введение в разработку приложений для Windows 8 с использованием HTML, CSS и JavaScript", нужно учесть еще несколько особенностей, связанных с подобными запросами, большинство из которых описаны в материале "Подключение к веб-службам" (http://msdn.microsoft.com/library/windows/apps/hh761502.aspx).

    Во-первых, материал "Загрузка различных типов содержимого" (http://msdn.microsoft.com/library/windows/apps/hh868280.aspx) содержит подробности о различных типах содержимого, которые поддерживает XHR для приложений Магазина. Основные сведения о них приведены здесь:

    Тип Использование responseText responseXML
    arraybuffer Двоичное содержимое, массив Int8 или Int64, или других целочисленных типов и типов с плавающей запятой. Неопределено Неопределено
    Blob Двоичное содержимое, представленное в виде единого объекта. Неопределено Неопределено
    document Объект XML DOM представленный XML-содержимым (MIME –тип text/XML). Неопределено XML -содержимое
    json JSON-строки. Строка JSON Неопределено
    ms-stream Потоковые данные;смотрите материал "Улучшения объекта XMLHttpRequest" (http://msdn.microsoft.com/library/windows/apps/hh673569.aspx) Неопределено Неопределено
    Text Текст (по умолчанию). Текстовая строка Неопределено

    Вот-вторых, знайте, что XHR-ответы могут быть автоматически кэшированы, что подразумевает то, что более поздние запросы по тому же URI могут привести к возврату устаревших данных. Для того, чтобы повторно отправить запрос без учета кэша, добавьте HTTP-заголовок If-Modified-Since так, как показано в материале "Проверка отправки повторных запросов с помощью WinJS.xhr" (http://msdn.microsoft.com/library/windows/apps/hh868281.aspx).

    Рассуждая в том же русле, вы можете заключить операцию WinJS.xhr в другой promise-объект для того, чтобы реализовать автоматические повторы при возникновении ошибки в любом заданном запросе. Таким образом, построить логику повторных запросов вокруг операции XHR с сохранением результата в некоторой переменной. Потом поместите весь код в WinJS.Promise.wrap (или в новый WinJS.Promise) и используйте это везде, где нужно, в приложении.

    В каждой попытке выполнения запроса с помощью XHR помните, что вы так же можете использовать WinJS.Promise.timeout вместе с WinJS.Xhr , как описано в материале "Установка значений времени ожидания с помощью WinJS.xhr" (http://msdn.microsoft.com/library/windows/apps/hh868283.aspx), так как в WinJS.xhr нет прямого описания тайм-аутов. Вы можете, конечно, установить время ожидания в необработанном запросе, но это означает создание зонного всего того, что уже есть в WinJS.xhr.

    Вообще говоря, XHR-заголовки доступны приложению, за исключением куки (заголовки set-cookie и set-cookie2), так как они отфильтрованы при использовании XHR в локальном контексте. Они не отфильтрованы для XHR, который работает в веб-контексте, поэтому, если вам нужны куки, попытайтесь получить их элементе iframe, который работает в веб-контексте, и передать в локальный контекст с использованием postMessage.

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

    И на этой ноте давайте посмотрим на то, как устроен API фоновой передачи данных.

    Врезка: отладка передачи данных по сети с помощью Fiddler

    Если вы заинтересованы в просмотре HTTP(S)-трафика между вашим компьютером и интернетом, что прямо-таки неоценимо при работе с XmlHttpRequests—взгляните на бесплатный инструмента, известный как Fiddler (http://www.fiddler2.com/fiddler2/). В дополнение к проверке трафика, вы так же можете устанавливать точки останова на различные события и "рыться" во входящих и исходящих данных (то есть, изменять их). Этот сервис поддерживает трафик от любого приложения или браузера, в том числе – и от приложений для Магазина Windows.

    Фоновая передача данных

    Одна из операций, которая касается пользовательских данных, заключается в передаче потенциально больших файлов в онлайновые хранилища и в загрузке этих данных оттуда. Даже для файлов умеренного размера, это являет собой некоторую проблему: немногие пользователи хотели бы смотреть на дисплей, наблюдая за процессом передачи файла, поэтому весьма вероятно, что они переключатся на другое приложение, чтобы заняться чем-нибудь поинтереснее пока завершится передача данных. При этом приложение, выполняющее передачу данных будет приостановлено и, возможно, даже остановлено. Это не сулит ничего хорошего при попытке выполнить подобные действия с использованием механизмов наподобие WinJS.xhr!

    Одно из решений – предоставить для этой цели фоновую задачу, что было обычным пожеланием к ранним предварительным версиям Windows 8 до тех пор, пока API не было готово. Это означает запуск некоторого кода, выполняющего эту задачу. WinRT, в итоге, предоставляет специальный API для фоновой передачи данных, ), которое поддерживает до 500 запланированных передач данных во всей системе. Он предлагает встроенный механизм учета стоимости соединения и механизм, позволяющий ему устойчиво работать при изменениях в сетевом соединении, что освобождает приложения от необходимости беспокоиться обо всем этом самостоятельно. Передача данных продолжается, когда приложение приостанавливается и приостанавливается, когда приложение останавливается. Когда приложение снова начинает работу, оно может проверить состояние фоновых передач данных, которые оно ранее инициировало и предпринять, при необходимости, дальшейшие действия – обработать загруженные данные, оповестить пользователя об успешной отправке данных в своем интерфейсе, перечислить неоконченные передачи, перезапустить те из них, что были приостановлены или прерваны. С другой стороны, если пользователь явным образом закрывает приложение (с помощью жеста, сочетания клавиш Alt+ F4 или из Диспетчера задач), все запрошенные приложением сеансы передачи данных отменяются. Это так же справедливо при остановке отладки приложения в Visual Studio.

    Вообще говоря, рекомендовано использовать API фоновой передачи данных всегда, когда вы ожидаете, что операция по передаче данных может превысить возможность пользователя по ожиданию ее окончания. Это, очевидно, зависит от скорости сетевого соединения и от того, предполагаете ли вы, что пользователь переключится на другое приложение в то время, как осуществляется передача данных в вашем приложении. Например, если вы начали передачу данных, но пользователь может продолжать работать (или развлекаться) в вашем приложении в то время, пока это происходит, использование WinJS.xhr c HTTP GET и POST/PUT вполне возможно, хотя вам все еще нужно отслеживать параметры стоимости соединения и обрабатывать изменения в подключении. Если, с другой стороны, пользователь ничего не может делать в вашем приложении до завершения передачи данных, вы можете решить использовать фоновую передачу, видимо, для любых данных, объем которых превышает 10 килобайт или какой-то другой размер, зависящий от текущей скорости сетевого соединения.

    В любом случае, когда вы готовые использовать фоновую передачу данных в приложении, объекты ) и ) в пространстве имен Windows.Networking.BackgroundTransfer станут вашими верными друзьями. У обоих объектов есть методы и свойства, посредством которых вы можете перечислять отложенные передачи, выполнять общие настройки учетных данных, HTTP-заголовков запросов, методов передачи, политики тарификации (для лимитированных сетей), и групповые операции. Каждая отдельная операций, таким образом, представлена объектом DownloadOperation или UploadOperation, с помощью которых вы можете управлять операцией (приостанавливать, отменять ) и получать сведения о ее состоянии. Вместе с каждой операцией вы можете задать учетные данные, политику тарификации и так далее, переназначая общие настройки в классах BackgroundDownloader и BackgroundUploader.

    Примечение. И для случая передачи данных, и при загрузке, запрос на подключение будет отменен, если новое соединение не было установлено в течение пяти минут. Далее, срок любых других HTTP-запросов, которые использованы при передаче, истекает через две минуты. Фоновая передача данных попытается повторить операцию, до трех раз, при наличии подключения.

    Для того, чтобы увидеть основы этого API в действии, начнем с примера "Фоновая передача данных" (http://code.msdn.microsoft.com/windowsapps/Background-Transfer-Sample-d7833f61). Для того, чтобы запустить этот пример, сначала вам нужно настроить сервер на локальном хосте, а так же файл данных и целевую страницу загрузки. Убедитесь, что у вас установлен Internet Information Services, как описано в Главе 2. Затем, в командной строке администратора, перейдите к папке примера Server и выполните команду powershell –file serversetup.ps1. Эта команда произведет установку необходимых серверных файлов для примера на локальном хосте и позволит вам запускать дополнительные упражнения к этой лекции, которые находятся в дополнительных материалах к ней.

    Основы загрузки данных

    Сценарий 1 примера "Фоновая передача данных" , (js/downloadFile.js) позволяет загружать файл изображения с сервера, расположеного на локальном хосте, и сохранять это изображение в Библиотеке изображений. По умолчанию поле для ввода URI установлено на конкретное URI локального хоста и элемент управления не активен. Это так, потому что пример не производит проверку правильности ввода URI, что всегда следует выполнять в ваших приложениях. Если вы хотите ввести другие URI, просто удалите disabled="disabled" из элемента serverAddressField в html/downloadFile.html. Для того, чтобы увидеть загрузчик в действии, так же полезно подготовить какой-нибудь большой файл, передача которого займет некоторое время. В этом вам может помочь любимая поисковая машина, или вы можете скопировать какой-нибудь из своих файлов на сервер локального хоста.

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

    Начало фоновой загрузки данных выглядит так. Сначала создают ) для передачи данных, используя BackgroundDownloader.createDownload, в данный момент можно усатновить свойства объекта method, costPolicy, и group для переопределения параметров, заданных объектом BackgroundDownloader. Метод передачи данных (method) – это строка, которая идентифицирует тип используемой передачи данных (обычно GET для HTTP или RETR для FTP). К двум другим параметрам мы вернемся в разделах "Задание стоимостной политики" и "Группировка нескольких запросов"

    Как только операция соответствующим образом сконфигурирована, последний шаг заключается в вызове ее метода startAsync с вашими обработчиками завершения, ошибки и прогресса операции :

    // Асинхронно создает файл в папке изображений (требуется объявление возможностей).
    Windows.Storage.KnownFolders.picturesLibrary.createFileAsync(fileName,
    Windows.Storage.CreationCollisionOption.generateUniqueName)
    .done(function (newFile) {
    // Предполагается, что uriString это текстовое URI файла для загрузки
    var uri = Windows.Foundation.Uri(uriString);
    var downloader = new Windows.Networking.BackgroundTransfer.BackgroundDownloader();
    
    // Создание новой операции загрузки.
    var download = downloader.createDownload(uri, newFile);
    
    // Запуск загрузки
    download.startAsync().then(complete, error, progress);
    }
            

    Когда операция выполняется, следующие свойства предоставляют дополнительные сведения о передаче данных:

  • requestedUri и resultFile Те же, которые были переданы createDownload.
  • guid Уникальный идентификатор, присвоенный операции.
  • ), содержащая следующие члены: ): idle, running, pausedByApplication, pausedCostedNetwork, pausedNoNetwork, canceled, error, и completed).
  • Вот несколько методов DownloadOperation, которые так же можно использовать при передаче данных:

  • pause и resume Управляют производимой загрузкой. Больше о них – в разделе "Приостановка, возобновление, перезапуск фоновых передач данных" ниже.
  • ) со свойствами headers (коллекция заголовков ответа сервера), actualUri, isResumable, и statusCode (с сервера). Повторяющиеся вызовые этого метода возвращают те же самые данные до тех пор, пока свойство hasResponseChanged не будет установлено в true.
  • getResultStreamAt Возвращает IInputStream для загруженного содержимого, с тем, что уже загружено, или, когда операция завершена, со всеми загруженными данными.
  • В Сценарии 1 примера, функция прогресса – переданная promise-объекту, возвращенному startAsync, использует getResponseInformation и getResultStreamAt для показа частично загруженного изображения:

    var currentProgress = download.progress;
    
    // ...
    
    // Получает заголовок ответа Content-Type.
    var contentType = download.getResponseInformation().headers.lookup("Content-Type");
    
    // Проверяет, является ли поток изображением.
    if (contentType.indexOf("image/") === 0) {
    .
    // Получает поток, с позиции 0
    imageStream = download.getResultStreamAt(0);
    
    // Конвертирует поток в тип WinRT
    var msStream = MSApp.createStreamFromInputStream(contentType, imageStream);
    var imageUrl = URL.createObjectURL(msStream);
    
    // Передает URL потока в тег HTML image.
    id("imageHolder").src = imageUrl;
    
    // Закрывает поток, когда изображение отображено.
    id("imageHolder").onload = function () {
    if (imageStream) {
    imageStream.close();
    imageStream = null;
    }
    };
    }
            

    Все это работает, потому что API фоновой передачи данных сохраняет загруженные данные во временный файл и предоставляет поток для него, а функция наподобие URL.createObjectURL делает то же самое, что мы уже делали с ее помощью напрямую с объектом StorageFile. Как только объект DownloadOperation выйдет из области видимости и будет произведена сборка мусора, однако, данный временный файл будет удален.

    Данный временный файл существует еще и потому, что, как отмечено выше, нет необходимости предоставлять объект StorageFile для размещения в нем загруженных данных. Таким образом, вы можете передать в качестве второго аргумента null в createDownload и работать с данными посредством DownloadOperation.getResultStreamAt. Это вполне подходит, если конечной целью не является отдельный файл.

    Существуют и вариант createDownload, который принимает второй аргумент StorageFile , содержимого которого представляет собой тело запросов HTTP GET или FTP RETR, которое будет отправлено на URI сервера перед началом загрузке. Это подходит для некоторых веб-сайтов, которые требуют, чтобы вы, перед началом загрузки, заполнили форму.

    Врезка: Где команда отмены?

    Вы уже могли заметить, что ни у DownloadOperation ни у UploadOperation нет метода для отмены операции. Как же это сделать? Вы отменяете передачу, отменяя операцию startAsync — то есть, вызывая метод cancel promise-объекта, возвращенного startAsync. Это означает, что вам нужно пологаться на promise-объекты каждой вызванной вами операции передачи данных.

    Основы отправки данных

    Сценарий 2 примера "Фоновая передача данных" (js/uploadFile.js) использует возможность фоновой отправки данных, в частности, он отправляет некоторые файлы (выбранные с помощью средства выбора файлов) по URI, который может их принять. По умолчанию URI указывает на http://localhost/BackgroundTransferSample/upload.aspx, на страницу, установленная с помощью скрипта PowerShell, который использовался для настройки сервера. Как и в случае со Сценарием 1, элемент управления для ввода URI заблокирован, так как пример не проверяет его, как, снова повторю, всегда нужно делать в приложениях, которые принимают любые URI из недоверенных источников (от пользователя, в данном случае). Для тестовых целей, конечно, вы можете убрать disabled="disabled" из элемента serverAddressField element вhtml/uploadFile.html и ввести другие URI, которые позволят испытать ваши собственные сервисы по приему отправленных файлов. Это особенно удобно, если вы исполняете серверную часть примера в Visual Studio 2012 Express для Web, когда URI нуждается в номере порта локального хоста, присвоенного отладчиком.

    В дополнении к кнопкам для начала отправки и отмены, пример предоставляет еще одну кнопку, для начала составной, состоящей из нескольких частей, операции отправки данных. Больше об этом – в разделе "Составная отправка данных" ниже.

    В коде отправка выглядит очень похожей на загрузку. Предполагая, что у вас имеется ) для передачи данных с помощью ). Это так же можно использовать для того, чтобы разбить большие файлы на отдельные части, если сервер может их принять. Смотрите "Разбивка больших файлов" ниже.

    Когда есть объект операции, можно настроить некоторые параметры передачи, переопределив свойства по умолчанию, заданные BackgroundUploader. Среди них – method (HTTP POST или PUT, или FTP STOR), costPolicy, и group. По поводу последних, смотрите разделы "Задание стоимостной политики" и "Группировка нескольких запросов" ниже.

    Как только все готово, вызов метода операции startAsync приведет к началу отправки данных :

    // Предполагается, что uri это объект Windows.Foundation.Uri и file это StorageFile для отправки
    var uploader = new Windows.Networking.BackgroundTransfer.BackgroundUploader();
    var upload = uploader.createUpload(uri, file);
    promise = upload.startAsync().then(complete, error, progress);
            

    Когда операция выполняется, следующие свойства предоставляют дополнительные сведения о передаче данных:

  • requestedUri и sourceFile То же самое, что передано createUpload (операция, созданная с помощью createUploadFromStreamAsync поддерживает лишь requestedUri).
  • guid Уникальный идентификатор, присвоенный операции.
  • progress Структура BackgroundUploadProgress (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.backgrounduploadprogress.aspx) со следующими членами: ), с возможными значениями idle, running, pausedByApplication, pausedCostedNetwork, pausedNoNetwork, canceled, error, и completed).

    В отличие от операции загрузки, UploadOperation не имеет методов для приостановки или возобновления, но имеет те же методы getResponseInformation и getResultStreamAt. В случае с отправкой данных, ответ от сервера менее интересен, так как он не содержит переданых данных, лишь заголовки, сведения о состояниях и содержимое, которое возвращает страница, осуществляющая прием передаваемых данных. Если данная страница возвращает какой-нибудь интересный HTML-код, однако, вы можете использовать эти данные как часть справочных данных, выводимых приложением при отправке данных.

    Как было упомянуто ранее, для отмены операции UploadOperation, вызовите метод cancel promise-объекта, возвращенного startAsync.

    Разбивка больших файлов

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

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

    С точки зрения клиента, каждая часть передается в отдельной UploadOperation; это более, чем очевидно. Сложная часть этой задачи заключается в разбиении большого файла на клиенте. Вся эта непростая задача, весьма вероятно, может разрешиться в виде сложной цепочки вложенных асинхронных операций, возможно, с созданием множества временных файлов из единственного источника. Если вы заняты этим вопросом, я предлагая вам написать программу для этого и куда-нибудь ее отправить, чтобы все мы могли это увидеть!

    Но есть и более простой подход, с использованием ). С помощью его метода getInputStreamAt затем вы IInputStream для каждой из начальных точек в потоке (то есть, задавая смещения в зависимости от размеров фрагмента). Затем вы создаете UploadOperation с каждым из входных потоков, используя createUploadFromStreamAsync. Последнее условие – указать, что операция обслуживает лишь некоторую часть этого потока. Это делается с помощью вызова ее метода setRequestHeader("content-length", <length> ), где <length> - это размер сегмента, плюс – размер других данных в запросе; так же вам понадобится доавить заголовок для идентификации сегмента для данной операции отправки информации. После всего этого можно вызывать методы startAsync операций для начала передачи данных.

    Составная отправка данных

    В дополнение к методам createUpload и createUploadFromStreamAsync, объект BackgroundUploader предоставляет другой метод, который называется createUploadAsync (в трех вариантах). Он обрабатывает то, что называется составной отправкой данных (multipart upload).

    С точки зрения сервера, составная отправка данных – это один HTTP-запрос, который содержит разные фрагменты данных (части), такие. как идентификаторы приложений, маркеры авторизации, и так далее, вместе с содержимым, где каждая часть может быть отделена с помощью специальной строки. Подобная отправка используется сетевыми сервисами, наподобие Flickr и YouTube, каждый из которых принимает запросы с multipart Content-Type. (Смотрите материал "Content-type: multipart" (http://msdn.microsoft.com/library/ms527355.aspx)). Например, как показано в примере "Отправка фото – POST" ( http://www.flickr.com/services/api/upload.example.html), Flickr ожидает запросов с типом содержимого multipart/form-data, с частями api_key, auth_token, api_sig, photo, и, наконец, с содержимым файла. В случае с YouTube, как описано в "YouTube API v2.0 – Прямая отправка данных" (https:xmlns//developers.google.com/youtube/2.0/developers_guide_protocol_direct_uploading), сервис ожидает типа содержимого multipart/related с частями, которые содержат XML-данные запроса, тип видео, и, затем двоичный файл данных.

    Средство фоновой отправки данных поддерживает все это посредством метода ), каждый из которых представляет одну из частей отправляемых данных. Результирующая операция отправит запрос с типом содержимого multipart/form-data со случайным GUID для строк-разделителей. Второй вариант createUploadAsync позволяет задавать тип содержимого напрямую (посредством подтипов, таких, как related), и третий вариант добавляет разделительную строку.

    То есть, учитывая, что parts - это как массив частей, методы выглядят так:

    var uploadOpPromise1 = uploader.createUploadAsync(uri, parts);
    var uploadOpPromise2 = uploader.createUploadAsync(uri, parts, "related");
    var uploadOpPromise3 = uploader.createUploadAsync(uri, parts, "form-data", "-------123456");
            

    Для создания каждой из частей, сначала создайте ):

  • new BackgroundContentPart() Создает часть по умолчанию.
  • new BackgroundContentPart(<name> ) Создает часть с заданным именем.
  • new BackgroundContentPart(<name> , <file>) Создает часть с заданным именем и локальным именем файла.
  • В каждом случае выполняют дальнейшую инициализацию частей, вызывая их методы setText, setHeader, и setFile. Первый, setText, присваивает части значение. Второй, setHeader, можно вызывать несколько раз для предоставления части значений заголовков. Третий, setFile, это способ предоставления StorageFile части, созданной с помощью третьего из вышеописанных вариантов.

    Сценарий 2 исходного примера "Фоновая передача данных" показывает последний вариант с использованием массива из выбранных файлов, но, возможно, немногие сервисы смогут принять подобный запрос. Вместо этого давайте посмотрим, как можно создать составной запрос на отправку данных, на примере "Отправка фото – POST" (http://www.flickr.com/services/api/upload.example.html). Для этой цели я создал упражнение "Multipart Upload", которое можно найти в дополнительных материалах к этой лекции. Вот код из js/uploadMultipart.js, который создает все необходимые части с использованием файла tinyimage.jpg из пакета приложения:

    // К этому моменту переменные file и uri уже установлены. bt – это короткое имя для пространства имен
    var bt = Windows.Networking.BackgroundTransfer;
    var uploader = new bt.BackgroundUploader();
    var contentParts = [];
    
    // Вместо отправки нескольких файлов (как в исходном примере), мы создаем эти части, которые
    // совпадают с примером POST дляFlickr on http://www.flickr.com/services/api/upload.example.html
    var part;
    
    part = new bt.BackgroundTransferContentPart();
    part.setHeader("Content-Disposition", "form-data; name=\"api_key\"");
    part.setText("3632623532453245");
    contentParts.push(part);
    
    part = new bt.BackgroundTransferContentPart();
    part.setHeader("Content-Disposition", "form-data; name=\"auth_token\"");
    part.setText("436436545");
    contentParts.push(part);
    
    part = new bt.BackgroundTransferContentPart();
    part.setHeader("Content-Disposition", "form-data; name=\"api_sig\"");
    part.setText("43732850932746573245");
    contentParts.push(part);
    
    part = new bt.BackgroundTransferContentPart();
    part.setHeader("Content-Disposition", "form-data; name=\"photo\"; filename=\"" + file.name + "\"");
    part.setHeader("Content-Type", "image/jpeg");
    part.setFile(file);
    contentParts.push(part);
    
    // Создаем новую операцию отправки, задаем разделительную строку.
    uploader.createUploadAsync(uri, contentParts,
    "form-data", "-----------------------------7d44e178b0434")
    .then(function (uploadOperation) {
    // Start the upload and persist the promise
    upload = uploadOperation;
    promise = uploadOperation.startAsync().then(complete, error, progress);
    }
    );
            

    Итоговый запрос будет выглядеть так, как показано ниже, что очень похоже на то, что показано на странице Flickr (лишь с несколькими дополнительными заголовками):

    POST /website/multipartupload.aspx HTTP/1.1
    Cache-Control=no-cache Connection=Keep-Alive Content-Length=1328
    Content-Type=multipart/form-data; boundary="-----------------------------7d44e178b0434" Accept=*/*
    Accept-Encoding=gzip, deflate
    Host=localhost:60355
    User-Agent=Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0; Touch) UA-CPU=AMD64
    -------------------------------7d44e178b0434
    Content-Disposition: form-data; name="api_key"
    
    3632623532453245
    -------------------------------7d44e178b0434
    Content-Disposition: form-data; name="auth_token"
    
    436436545
    -------------------------------7d44e178b0434
    Content-Disposition: form-data; name="api_sig"
    
    43732850932746573245
    -------------------------------7d44e178b0434
    Content-Disposition: form-data; name="photo"; filename="tinysquare.jpg" Content-Type: image/jpeg
    
    {RAW JFIF DATA}
    -------------------------------7d44e178b0434--
            

    Для того, чтобы запустить пример и увидеть, как принимается этот запрос, перейдите в папку MultipartUploadServer в дополнительных материалах к этой лекции. Загрузите website.sln в Visual Studio 2012 Express для Web, откройте MultipartUploadServer.aspx, и установите точку останова на первое выражение if внутри метода Page_Load. Затем запустите сайт в Internet Explorer для открытия данной страницы с помощью отладочного порта локального хоста. Скопируйте URI страницы для выполнения следующего шага.

    В упражнении "Multipart Upload" вставьте данный URI в поле URI и нажмите на кнопку Start Multipart Transfer (Начать составную передачу данных). Когда будет вызван метод startAsync операции, должна сработать точка останова серверной страницы в Visual Studio для Web. Вы можете пошагово исполнить данный код, если нужно, и изучить объект Request; в конце код скопирует запрос в файл на сервере, который называется multipart-request.txt. Он будет включать в себя содержимое запроса, как показано выше, где вы можете увидеть взаимосвязь между тем, как вы настраиваете части на клиенте и как они принимаются сервером.

    Предоставление заголовков и учетных данных

    При работе с объектами BackgroundDownloader и BackgroundUploader есть возможность устанавливать значения для отдельных HTTP-заголовков, используя их методы setRequestHeader. Оба принимают имя заголовка и значение, и их вызывают несколько раз, если нужно установить больше одного заголовка.

    Похожим образом, и тот и другой объекты имеют два свойства для учетных данных: serverCredential и proxyCredential, которые используются в зависимости от нужд URI сервера. Оба свойства это объекты ). Так как цель операции фоновой передачи данных – предоставление учетных данных для сервера, обычно PasswordCredential создают так:

    var cred = new Windows.Security.Credentials.PasswordCredential
              (resource, userName, password);

    В данном случае resource это обычная строка, которая идентифицирует ресурс, к которому применяются учетные данные. Это используется для управления учетными данными в хранилище учетных данных, как мы увидим в разделе "Проверка подлинности, учетные данные и профиль пользователя" ниже. Сейчас создание учетных данных таким способом – это все, что нужно для того, чтобы пройти проверку подлинности сервером при выполнении передачи данных.

    Примечание В настоящий момент установка свойства serverCredential не работает с URI, которые описывают FTP-сервер. Чтобы решить эту проблему, включайте учетные данные прямо в URI, в форме ftp://<user> : <password>@server.com/file.ext (например, ftp://admin:password1@server.com/file.bin).

    Задание стоимостной политики

    Как упомянуто выше, в разделе "Сведения о стоимости передачи данных", политика Магазина Windows требует, чтобы приложения осторожно относились к передаче больших объемов данных в лимитированных сетях. API фоновой передачи данных учитывает это, основываясь на значениях из перечисления BackgroundTransferCostPolicy:

  • default Разрешает передачу данных в сетях с оплатой.
  • unrestrictedOnly Не разрешает передачу данных в сетях с оплатой.
  • always Всегда разрешает загрузку вне зависимости от стоимости передачи данных.
  • Для того, чтобы применить правила к последующим передачам данных, установите значение BackgroundDownloader.costPolicy и/или BackgroundUploader.costPolicy. Правила для отдельной операции можно установить с помощью свойств DownloadOperation.costPolicy и UploadOperation.costPolicy.

    Обычно политику изменяют после того, как запрашивают согласие пользователя, или позволяют пользователю задать поведение программы посредством параметров. Например, если у пользователь запретил загрузку или отправку данных в лимитированной сети, приложению следует устанавливать общий параметр costPolicy в unrestrictedOnly. Если устройство подключено к лимитированной сети, где применяется плата за роуминт и пользователь разрешил передачу данных, параметр costPolicy каждой отдельной операции устанавливается в always. В противном случае API не выполнит передачу данных, так как по умолчанию в лимитированных сетях это не разрешено.

    Когда передача данных заблокирована политикой, свойство операции progress.status будет содержать BackgroundTransferStatus.pausedCostedNetwork.

    Группировка нескольких запросов

    Свойство group, которое есть у объектов BackgroundDownloader, BackgroundUploader, DownloadOperation, и UploadOperation это простая строка, которая указывает на то, что операция передачи данных принадлежит некоторой группе. Свойство можно установить только посредством BackgroundDownloader и BackgroundUploader. Его задают до создания последовательностей отдельных операций. В этих операциях свойство group доступно, но только для чтения.

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

    Свойство group не имеет отношения к самой передаче, она не связана с серверной страницей, которая принимает данные.

    Приостановка, возобновление, перезапуск фоновых передач данных

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

    Когда приложение восстанавливается из состояния приостановки, оно может проверить состояние неоконченных передач данных с помощью использования методов BackgroundDownloader.getCurrentDownloadsAsync и BackgroundUploader.getCurrentUploadsAsync. В обоих случаях существуют два варианта этих методов: один, который перечисляет все передачи данных, и другой, который перечисляет лишь те операции, которые относятся к определенной группе (к той, которая совпадает со свойством group операций).

    Список, который возвращают эти методы, это вектор объектов DownloadOperation и UploadOperation, и, как всегда, к вектору можно обращаться как к массиву. Вот как может выглядеть код для обхода списка:

    Windows.Networking.BackgroundTransfer.BackgroundDownloader.getCurrentDownloadsAsync()
    .done(function (downloads) {
    for (var i = 0; i<downloads.size; i++) {	
    var download = downloads[i];	
    }	
    });	
    
    Windows.Networking.BackgroundTransfer.BackgroundUploader.getCurrentUploadsAsync()
    .done(function (uploads) {	
    for (var i = 0; i<uploads.size; i++) {	
    var upload = uploads[i];	
    }	
    });
            

    В каждом случае, свойство progress каждой операции сообщает о том, насколько продвинулась операция передачи данных. Свойство ), которое может принимать одно из следующих значений: idle, running, pausedByApplication, pausedCostedNetwork, pausedNoNetwork, canceled, error, и completed. Очевидно, это необходимо для предоставления соответствующих оповещений пользователю и для того, чтобы дать ему возможность повторно запустить операция, которая приостановлена или завершилась с ошибкой, для приостановки выполняющейся операции и для того, чтобы выполнить что-либо при завершении передачи данных.

    Кстати, при использовании API фоновой передачи данных, приложению всегда следует давать пользователю возможность управления начатыми операциями. Загрузку можно приостановить с помощью метода DownloadOperation.pause и снова начать с помощью метода DownloadOperation.resume. (Для отправки данных эквивалентов нет.) Операции загрузки и отправки отменяют, отменяя promise-вызовы, которые возвращены startAsync.

    Возникает интересная ситуация: если ваше приложение закрыто и позже запущено снова, как заново снова запустить операцию передачи данных, которая была приостановлена? Ответ довольно прост. При перечислении операций с помощью getCurrentDownloadsAsync и getCurrentUploadsAsync, незавершенные передачи данных автоматически запускаются. Но как получить promise-объекты, которые изначально были возвращены методами startAsync? Они не являются значениями, которые можно сохранить в составе состояния сеанса приложения и загрузить при запуске, и, все же, они нужны для того, чтобы можно было отменить операции, если нужно, и так же для того, чтобы присоединить к ним обработчики завершения, ошибок и прогресса.

    По этой причине и DownloadOperation и UploadOperation предоставляют метод, который называется attachAsync, который возвращает promise-объект для операции, так же, как изначально это делает startAsync. Затем вы можете вызвать методы promise-объекта then или done для предоставления собственных обработчиков:

      promise = download.attachAsync().then(complete, error, progress);

    А так же, если нужно, вызвать promise.cancel(). Короче говоря, когда Windows снова запускает фоновую передачу данных и обычным образом вызывает startAsync от имени вашего приложения, эти promise-объекты хранятся внутри. Методы attachAsync просто возвращают эти новые promise-объекты.

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

    Страницы:

    Введение

    Материалы к лекциям 7-9 Вы можете скачать здесь.

    В прошлом году я с семьей два раза переезжал, и я был поражен сходством между этим процессом и тем, как по нашим сетям перемещаются данные. Укладка вещей в грузовик для первого переезда – из Орегона в Калифорнию, конечно, была упражнением на сжатие! Порой я думаю, что мы не смогли уместить все в наш фургон и в арендованный 20-футовый грузовик, но как-то это все вместилось. Затем у нас было долгое путешествие к югу, прежде чем пришло время декомпрессии данных, то есть, в дом, где мы жили некоторое время, пока строители доделывали наше постоянное жилище.

    Переезд в новый дом – это кое-что другое, в основном потому, что он был метрах в 200-х, и мы могли переносить все по частям в разное время. Когда дом был принят, все бумаги подписаны, и мы могли (легально) перенести всю мебель и коробки, эта задача была решена с помощью многих друзей и множество разных транспортных средств, с очень невысокой степенью сжатия наших вещей, пока они были в пути!

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

    В том же духе, поразмыслите, как вы можете нанять грузчиков для того, чтобы сделать все это – вы показываете им ваши вещи, даете им адрес назначения, выписываете им немаленький чек, и как по волшебству все ваши вещи оказываются в новом месте. Подобный процесс напоминает API фоновой передачи данных в WinRT, одна из первых тем этой лекции. При домашнем переезде существует так же концепция использования наемных транспортных средств и упаковки "коробков", в таком случае вы упаковываете все сами, но перевозка, (и, возможно, хранение) обрабатывается отдельно. Но я не так уж уверен в том, поступает ли так кто-нибудь при написании приложений для Windows 8!

    Чтобы не углубляться в подобные аналогии, давайте просто скажем, что сетевое взаимодействие – это богатая и обширная тема, где все основана на необходимости переместить данные из одного пункта в другой различными способами. Цель этой лекции, таким образом, представить, по крайней мере, обзор сетевых возможностей Windows 8. Материалы этой лекции варьируются от XmlHttpRequests, фоновой передачи данных, проверки подлинности и учетных данных и синдикации, до сетевых подключений и сетевой информации, функциональности при нахождении вне сети (оффлайновой), и сокетов. Мы сосредоточимся на большинстве тем, представляющих интерес для большинства приложений, кратко затронув другие вопросы, более специфичных для подобных сценариев и поговорим о множестве дополнительных ресурсах полезной информации. Один из таких ресурсов – материал "Разработка подключенных приложений" (http://msdn.microsoft.com/library/windows/apps/hh465399.aspx), который служит хорошим общим обзором сетевых возможностей.

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

    Информация о сети и возможности подключения

    В предыдущей лекции, в сносках, я у поминал о том, что тогда, когда я писал о динамических плитках, все соединения с Интернетом, которые могли быть у приложений для Windows 8, мой дом и много тысяч других в Северной Калифорнии, были полностью отключены из-за аварии на оптоволоконном канале. Сбой длился, казалось, целую вечность по современным стандартам – 36 часов! Хотя я нашел, чем себя занять, был момент, когда я открыл один из моих ноутбуков, увидел, что связи все еще нет, и на мгновение задался вопросом о том, на что годен компьютер без подключения к сети! Очевидно, я привык, как, подозреваю, и вы тоже, воспринимать постоянное подключение к сети как нечто само собой разумеющееся.

    Как разработчики отличных приложений, однако, мы не может позволить себе быть столь самодовольными. Всегда важно обрабатывать ошибки при попытке установить соединение и получить данные из сетевых источников, так как даже при проведении единственной операции может возникнуть любое количество проблем. Но нам нужно мыслить глубже. Это наша работа – сделать наши приложения такими полезными, как только возможно, при потере сетевого соединения, что, возможно, произошло лишь потому, что наши пользователи находятся в самолете и включили на устройстве режим работы в самолете. Таким образом, не давайте пользователям причины задумываться о полезности их устройств в подобной ситуации! Отличное приложение докажет свою полезность с помощью отличного опыта взаимодействия с ним пользователя даже в отсутствии сетевого подключения.

    Сетевое подключение, кроме того, может меняться в течение сеанса работы приложения, когда приложение может часто приостанавливаться и восстанавливаться, или быть в приостановленном состоянии долгое время. В особенности это касается мобильных устройств, когда кто-то может переключаться между множеством сетей без необходимости даже знать об этом. Windows 8, на самом деле, пытается сделать переход между сетями настолько прозрачным, насколько это возможно, за исключением ситуаций, когда важно оповестить пользователя о том, что работа в новой сети может стоить некоторую сумму. Это требуется политикой Магазина Windows, чтобы приложения знали о стоимости передачи данных в лимитированных сетях и предотвращали "шоковые счета" от не всегда щедрых провайдеров мобильной широкополосной связи. Так же, как то, что приложение не может выполнять некоторые действия, когда устройство не подключено к сети, характеристики текущей сети могут привести к тому, что приложение отложит или отменит некоторые операции.

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

    Типы сетей в манифесте

    Почти каждый пример, с которыми мы до сих пор работали, имеет возможность Интернет (Клиент) (Internet (Client)), объявленную в манифесте, благодаря Visual Studio, который включает ее по умолчанию. Ранее я упоминал, что так было не всегда: первые разработчики приложений в Microsoft время от времени чесали голову, пытаясь понять, почему что-то вполне очевидное, вроде простого запроса XmlHttpRequest к блогу, отказывается работать. Без этой возможности у приложения попросту нет доступа к Интернету!

    Тем не менее, Интернет (Клиент) (Internet (Client)) – не единственный участник игры возможностей. Некоторые сетевые приложения так же планируют выступать в роли сервера для получения входящих данных из Интернета, а не только делать запросы к другим серверам. В подобных случаях – в таких, как предоставление общего доступа к файлу, работа медиа-серверов, VoIP-связь, чат, многопользовательские игры, и в других подобых сценариев двунаправленного обмена данными, включающими в себя входящий сетевой трафик, как в случас с сокетами – приложение должно объявить возможность Интернет (клиент и сервер) Internet (Client Server), как показано на рис 7.1. Это позволяет подобному трафику проходить через локальный брандмауэр, хотя критически важные порты всегда заблокированы.

    Есть, кроме того, сетевой трафик, который циркулирует по частным сетям, в домах или офисах, причем, данные из Интернета в этом не участвуют. Для подобных ситуаций существует возможность Частные сети (клиент и сервер) (Private Networks (Client Server)), так же показанная на рис 7.1, которая подходит для организации общего доступа к файлам или мультимедийным данным, для бизнес-приложений, HTTP-клиентов, многопользовательских игр для локальных сетей и так далее. Что делает каждый конкретный IP-адрес принадлежащим к конкретной частной сети, зависит от многих факторов, все из которых описаны в материале "Настройка сетевых характеристик" (http://msdn.microsoft.com/library/windows/apps/Hh770532.aspx). Например, IPv4-адреса в диапазонах 10.0.0.0–10.255.255.255, 172.16.0.0–172.31.255.255, и 192.168.0.0–192.168.255.255 считаются частными. Пользователь может отметить сеть как доверенную, так же, присутствие контроллера домена делает сеть частной. В любом случае, если пункт назначения сетевых данных попадает в эту категорию, поведение приложения на данном устройстве зависит от этой возможности, а не от тех, которые имеют отношение к Интернету.

    (рис 7.1) Дополнительные сетевые возможности в манифесте

    Врезка: Локальная обратная петля

    Вне зависимости от возможностей, объявленных в манифесте, локальная обратная петля, (то есть, использование URI http://localhost) заблокирована для приложений Магазина Windows. Исключение сделано лишь для компьютеров, на которых установлена лицензия разработчика, как описано в Главе 2. Это исключение существует лишь для упрощения отладки совместной работы приложений и сервисов, так как при разработке они могут исполняться на одном и том же компьютере.

    Информация о сети (Реестр сетевых объектов)

    Не зависимо от того, о какой сети идет речь, все, что вам может понадобиться узнать об этой сети, доступно с помощью объекта ), за исключением единственного события networkstatuschanged, которое мы рассмотрим в разделе "События подключения", немного позже. Интерфейс этого объекта состоит из методов для получения более подробных сведений в других объектах.

    Выполняя ранее данное обещание о том, что мы лишь затронем некоторые специальные темы, ниже привожу реестр методов из NetworkInformation и содержимого объектов, получаемых с их помощью. Вы можете испытать большинство из этих API в отмеченных сценариях примера "Информация о сети" (http://code.msdn.microsoft.com/windowsapps/Network-Information-Sample-63aaa201):

  • ), один для каждого подключения, который содержит строки с различными именами (), со значениями domainName, ipv4, ipv6, и bluetooth), и свойство ipinformation (типа IPInformation (http://msdn.microsoft.com/library/windows/apps/windows.networking.connectivity.ipinformation.aspx)) содержащее свойства prefixLength и networkAdapter для хостов IPV4 и IPV6. (Последнее – это объект ) с различными низкоуровневыми сведениями.) Класс HostName используется в различных API работы с сетью для идентификации сервера или какой-то другой конечной точки.
  • ), по одному для каждого соединения, среди которых будут присутствовать активное интернет-соединение, возвращенное getInternetConnectionProfile. Кроме того, включает в себя любые беспроводные соединения, которые вы установили ранее и для которых указали Подключать автоматически (Connect Automatically). (В подобной ситуации пример покажет вам некоторые подробности о том, где вы недавно были!). Смотрите следующий раздел для того, чтобы узнать подробности о ConnectionProfile.
  • ) для Интернет-соединения, активного в настоящий момент. Если присутствует больше одного соединения, этот метод возвратит профиль предпочитаемого соединения, которое, вероятнее всего, будет использовано для передачи Интернет-трафика.
  • ), каждый из которых содержит свойство infrastructureId (значение типа LanIdentifierData, содержащее type и value), свойство networkAdapterId (GUID), и свойство portId (LanIdentifierData).
  • ) для заданного URI и текущего пользователя. У этого объекта есть свойства: canConnectDirectly (логического типа) и proxyUris (вектор объектов Windows.Foundation.Uri для конфигурации).
  • x) массив объектов ). Объект EndpointPair содержит имя хоста и сервиса для локальных и удаленных конечных точек, обычно получаемых при установке конкретных соединений, наподобие сокетов. Два параметра сортировки – это none и optimizeForLongConnections, который различает поведение соединений на основе того, устанавливает ли приложения короткие или длительные соединений. Для того, чтобы узнать подробности, обратитесь к домументации по EndpointPair и HostNameSortOptions.
  • Объект ConnectionProfile

    Среди всей информации, доступной с помощью объекта ), наиболее часто данный объект получают с помощью getInternetConnectionProfile, так как именно по этому соединению передается Интернет-трафик приложения. Профиль – это то, что содержит всю информацию, которая необходима для того, чтобы принять решение о том, как использовать сеть, в особенности для того, чтобы знать о стоимости передачи данных. Это, так же, то, что обычно здесь проверяют при изменениях в состоянии сети. Сценарии 1 и 3 примера "Информация о сети" получают и отображают большинство из этих сведений.

    У каждого профиля есть свойство ), описывающий проверку подлинности и типы шифрования

    В общем случае более интересен метод ): none (нет соединения), localAccess (уровень соединения, который вы возненавидите, если вам нужно хорошее соединение!), constrainedInternetAccess (привязаное портальное подключение, обычно требующее дополнительных учетных данных, как часто бывает в отелях, аэропортах и так далее), и internetAccess (состояние, к которому вы практически всегда будуте стремиться). Уровень подключения часто влияет на логику приложения, обычно так же отслеживают изменение сетевого статуса.

    Для отслеживания входящего и исходящего сетевого трафика соединения, метод ), который содержит свойства bytesReceived и bytesSent, каждое – либо содержит сведения за все время существования подключения, либо за определенный период. Похожим образом getConnectionCost и getDataPlanStatus предоставляют информацию, необходимую приложению для знания того, сколько потрачено сетевого трафика и сколько это может стоить пользователю. Мы скоро вернемся к этому в разделе "Сведения о стоимости передачи данных", в том числе, поговорим о том, как увидеть использование трафика конкретным приложением в Диспетчере задач (Task Manager).

    События подключения

    Обычно запущенное приложение интересуется изменениями сетевого подключения. Таким образом оно может предпринять необходимые шаги для отключения или включения определенной функциональности, для оповещения пользователя, синхронизации данных после нахождения в состоянии отключения от сети и так далее. Для этого приложению нужно лишь прослушивать событие ), которые вызывается, когда происходит серьезное изменение в иерархии объектов, которые мы только что видели (и не забудьте, это событие исходит из объекта WinRT). Например, событие вызывается, если меняется уровень подключения в профиле. Оно так же вызывается, если меняется профиль Интернета, как когда устройство перемещается между разными сетями, или когда, при применении лимитированного плана передачи данных, приближается или исчерпывается его ограничение, то есть, когда пользователь начинает беспокоиться о каждом мегабайте переданных данных. Коротко говоря, это событие обычно прослушивают для обновления любого внутреннего состояния приложения, зависящего от характеристик сети и установки любых флагов, используемых для настройки сетевого поведения приложения. Это особенно важно при переходах между режимом работы в сети и автономной работы и при переходах между безлимитными и лимитированными сетями. Windows, в свою очередь, так же отслеживает это событие для того, чтобы настроить собственное поведение, например, в реализации функций API фоновой передачи данных.

    Примечание. Приложения для Магазина Windows, написанные на JavaScript так же могут использовать простые события window.nagivator.ononline и window.navigator.onoffline для отслеживания состояния подключений. Так же, свойство window.navigator.onLine property может принимать значения true или false в соответствии с состоянием подключения. Эти события, однако, не оповестят об изменении профилей соединений, стоимости или других аспектов, которые не связаны с базовой доступностью интернет-соединения.

    Вы можете поэкспериментировать с событием networkstatuschanged в Сценарии 5 примера "Информация о сети". При подключении или отключении от сети, или при выполнении других изменений, пример обновит выводимые сведения для текущего Интернет-профиля, если он доступен (вот код, взятый из js/network-status-change.js):

    var networkInfo = Windows.Networking.Connectivity.NetworkInformation;
    // Напоминаю, что для этого WinRT-события может понадобиться removeEventListener
    networkInfo.addEventListener("networkstatuschanged", onNetworkStatusChange);
    
    function onNetworkStatusChange(sender) {
    internetProfileInfo = "Network Status Changed: \n\r";
    var internetProfile = networkInfo.getInternetConnectionProfile();
    
    if (internetProfile === null) {
    // Сообщение об ошибке
    } else {
    internetProfileInfo += getConnectionProfileInfo(internetProfile) + "\n\r";
    // Отображение информации
    }
    
    internetProfileInfo = "";
    }
            

    Конечно, прослушивание этого события полезно только если приложение исполняется, но что, если это не так? В подобном случае приложению нужно зарегистрировать фоновую задачу, как обсуждалось в конце Главы 2, для триггера ), обычно, при необходимости, с применением условий internetAvailable или internetNotAvailable. Пример "Фоновая задача для определения состояния сети" ( http://code.msdn.microsoft.com/windowsapps/Network-status-background-957eb3eb) предоставляет демонстрацию этого, объявляя фоновую задачу в манифесте, с C#-точкой входа для NetworkStatusTask.NetworkStatusBackgroundTask. Задача зарегистрирована в js/network-status-with-internet-present.js (с использованием вспомогательных функций из js/global.js, как обычно делается в примерах использования фоновых задач):

    BackgroundTaskSample.registerBackgroundTask(BackgroundTaskSample.sampleBackgroundTaskEntryPoint,
    BackgroundTaskSample.sampleBackgroundTaskWithConditionName,
    new Windows.ApplicationModel.Background.SystemTrigger(
    Windows.ApplicationModel.Background.SystemTriggerType.networkStateChange, false),
    new Windows.ApplicationModel.Background.SystemCondition(
    Windows.ApplicationModel.Background.SystemConditionType.internetAvailable));
            

    Фоновая задача в BackgroundTask.cs просто записывает имя Интернет-профиля и id сетевого адаптера в область локальных данных приложения в ответ на триггер. Эти значения выводятся на экран в completeHandler в js/global.js. Реальное приложение, конечно, будет делать что-то более полезное, например, активировать фоновую передачу данных для синхронизации данных при восстановлении соединения. Несмотя на это, пример представляет необходимую для реализации подобных механизмов базовую структуру.

    Кроме того, очень важно помнить о том, что состояние сети может измениться, когда приложение приостановлено. Приложениям, которые отслеживают событие networkstatuschanged, следует так же обновлять свое состояние, связанное с соединением в обработчике возобновления работы.

    В качестве последнего замечания, хочу посоветовать вам обратиться к материалу "Диагностика и отладка ошибок сетевых подключений" (http://msdn.microsoft.com/library/windows/apps/hh770534.aspx), который содержит руководство по реализации реакции приложения на изменения состояния сети и по обработке сетевых ошибок.

    Сведения о стоимости передачи данных

    Если вы когда-нибудь оказывались в 3G-роуминге со смартфоном, который настроен на автоматическую загрузку электронной почты, вы, возможно, научились на собственном горьком опыте отключать синхронизацию в подобных обстоятельствах. Я однажды ехал из штата Вашингтон в Канаду, не представляя, что я плачу $15 за мегабайт за возможность загружать большие почтовые вложения. Конечно, я, как законопослушный гражданин, не смотрел на телефон, пока вел машину (пишу это и подмигиваю!) для того, чтобы заметить, что я в роуминге. А через несколько недель я узнал, что такое "шоковый счет"!

    Дело в том, что если пользователь решит, что ваше приложение в ответе за подобное поведение, независимо от того, прав ли он, вряд ли в Магазине Windows у вашего приложения появятся хорошие оценки и отзывы! Таким образом, очень важно обращаться внимание на изменения в стоимости профилей подключения, которыми вы пользуетесь, обычно это касается Интернет-профиля. Всегда проверяйте это при старте, в обработчике события networkstatuschanged и в обработчике возобновления работы.

    Вы, и, хочу добавить, все ваши пользователи, могут отслеживать использование приложением сети на закладке App History (Журнал приложений) в Task Manager (Диспетчер задач), как показано ниже. Убедитесь, что вы развернули область просмотра, нажав на More Details (Подробнее) в нижнем левом углу, если вы не видите того, что приведено на рисунке. Вы можете видеть здесь использование трафика по показателям Network (Сеть) и Metered Network (Сеть с учетом трафика), а так же – сведения о сетевом трафике, потребленном при обновлении плиток:

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

  • ), может принимать значения: unknown, unrestricted (дополнительная плата не взимается), fixed (неограниченное, до достижения лимита), и variable (взимается плата на основе учета количества переданных байтов или мегабайтов).
  • roaming Значение логического типа, которое указывает на то, подключены ли вы к сети, которая находится за пределами нормальной зоны покрытия вашего оператора, что подразумевает возможность дополнительной платы. Приложению следует весьма консервативно пользоваться сетью, если это значение установлено в true.
  • approachingDataLimit Логическое значение, показывает, что использование данных в сети фиксированного (fixed) типа (смотрите networkCostType) приближается к лимиту тарифного плана.
  • overDataLimit Логическое значение, которое показывает, что лимит фиксированного тарифного плана превышен и за передачу данных взимается дополнительная плата. Если это значение установлено в true, приложению следует весьма консервативно пользоваться сетью, как и в случае, когда свойство roaming равно true.
  • Второй метод, ) со следующими свойствами:

  • dataPlanLimitInMegabytes Максимальный объем передачи данный, который разрешен для подключения в каждом тарифном цикле.
  • ) с одинаково важными свойствами megabytesUsed и lastSyncTime (UTC), которое показывает, когда megabytesUsed было обновлено в последний раз.
  • maxTransferSizeInMegabytes Максимальный рекомендованный объем данных, переданных в одной сетевой операции. Это свойство отражает скорее не возможности самого лимитированного подключения (как следует из документации), а скорее соответствующий верхний предел для передачи данных в этой сети.
  • nextBillingCycle Дата и время в формате UTC, указывающие на то, когда закончится тарифный цикл, что приведет к сбрасыванию dataPlanUsage в ноль.
  • inboundBitsPerSecond и outboundBitsPerSecond указывают на номинальную скорость передачи данных по данному соединению.
  • Пользуясь всеми этими свойствами мы можем принять обоснованное решение о сетевой активности приложения, и/или предупредить пользователя о возможных дополнительных затратах. Очевидно, когда параметр networkCostType имеет значение unrestricted, вы можете делать все, что хотите. С другой стороны, когда тип соединения имеет значение variable и пользователь платить за каждый байт, особенно, когда значение roaming установлено в true, вам понадобится уведомить пользователя о таком состоянии дел и предоставить параметры, с помощью которых пользователь сможет ограничить сетевую деятельность приложения, если не вовсе остановить такую деятельность. В конце концов, пользователь может решить, что определенные виды данных ему нужны. Например, он должен иметь возможность установки качества потокового видео, указывать, нужно ли загружать сообщения электронной почты, или лишь их заголовки, указать нужно ли загружать изображения, указать, нужно ли производить кэширование сетевых данных, иметь возможность выключить фоновое потоковое аудио и так далее.

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

    Приложение, конечно, спросит у пользователя разрешения для выполнения каждой сетевой операции. Здесь решают вы и ваш дизайнер – когда задавать подобные вопросы и как часть. В свою очередь, "Сертификационные требования к приложениям для Windows 8" (http://msdn.microsoft.com/library/windows/apps/hh694083.aspx) (раздел 4.5.) требуют, чтобы вы задавали пользователю вопрос о любой передаче данных, объем которой превышает один мегабайт, когда свойства roaming и overDataLimit установлены в true, и когда производите любую передачу данных в условиях превышания maxTransferSizeInMegabytes.

    Для сетей фиксированного (fixed) типа, когда объем передачи данных не ограничен параметром ). Вызовите getLocalUsage с заданным периодом между lastSyncTime и DateTime.now(). Затем добавьте полученное значение к megabytesUsed и вычтите результат из dataPlanLimitInMegabytes. Это скажет вам, сколько еще данных вы можете передать, не приведя к дополнительным расходам и сможете получить данные для того, чтобы иметь основания задать пользователю вопрос: "Загрузка этого файла приведет к превышению ограничений вашего тарифного плана. Желаете продолжить?"

    В целях упрощения, вы можете рассматривать знание сведений о стоимости подключения в виде трех схем поведени: обычной, консервативной и требующей согласия пользователя, что описано в материале "Краткое руководство: управление тарифными ограничениями в сети с лимитным тарифным планом" (http://msdn.microsoft.com/library/windows/apps/hh750310.asp x). Более общие сведения об управлении соединением в сетях с лимитными тарифными планами можно найти в материале "Разработка подключенных приложений" (http://msdn.microsoft.com/library/windows/apps/hh465399.aspx). Оба материала предоставляют дополнительные сведения о том, как принимать решения, о которых мы здесь говорили. В итоге, предотвращение "шоковых счетов" - и создание отличных приложений, учитывающих стоимость сетевого подключения – это достойное вложение усилий.

    Врезка: Имитация сетей с лимитным тарифным планом

    Вы можете подумать: "Хорошо, я реализую в своем приложении правильное поведение в сетях с лимитным тарифным планам, но как мне все это проверить, не тратя кучу денег на оплату счетов операторов (включая плату за роуминг)?". Простой ответ заключается в том, что вы можете имитировать поведение сети с лимитным танифным планом с помощью любого Wi-Fi-соединения. Во-первых, откройте интерфейс чудо-кнопки Параметры и щелкните по значку вашего сетевого соединения, расположенного около ее нижней части (смотрите верхний левый рисунок, в особенности – верхний левый значок, который имеет подпись "Nuthatch"). В панели Сети (Networks), которая после этого откроется (нижнее правое изображение), щелкните правой кнопкой мыши по беспроводному соединению и выберите параметр Задать как лимитное подключение (Set As Metered Connection):

    Хотя включение этого параметра не установит свойства DataUsage и все те сведения, которые можно получить в реальной лимитированной сети, но благодаря ему networkCostType будет установлено в значение fixed, что позволяет вам увидеть, как отреагирует на это ваше приложение. Так же вы можете воспользоваться элементом меню Показать оценочные сведения об использовании данных (Show Estimated Data Usage) для того, чтобы увидеть, какой трафик генерирует ваше приложение при нормальной работе, и вы можете сбросить счетчик для того, чтобы получить более точные показания:

    Работа без подключения к сети

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

  • Что происходит, если ваше приложение запускается при отсутствии сетевого соединения, и с плитки (основной и дополнительной) и посредством контракта, такого, как контракт Поиск, Общий доступ, контракт средства выбора файлов?
  • Что происходит, если ваше приложение запускается в первый раз, а сетевого соединения нет?
  • Что происходит при потере сетевого соединения во время выполнения вашего приложения?
  • Что происходит, когда соединение восстанавливается?
  • Как описно выше, в разделе "Сведения о стоимости передачи данных", вы можете использовать событие networkstatuschanged для обработки таких ситуаций при исполнении приложения и обработчик resuming для проверки изменения состояния соединения, которое могло произойти пока приложение было приостановлено. Если у вас есть фоновая задача, связанная с триггером networkStateChange, вам сначала нужно сохранить сведения о состоянии, которые сможет проверить ваш обработчик resuming

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

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

    Как и когда кэшировать данные из онлайновых источников, это один из вопросов, ответ на который требует большого искусства от разработчика программного обеспечения. Когда загружать данные? Сколько данных загружать? Где их хранить? Следует ли задать верхнее ограничение на размер кэша? Позволить ли изменять кэшированные данные, что вызовет необходимость синхронизации с сервером, когда соединение будет восстановлено? Это отличные вопросы, но есть, конечно, и другие. Позвольте мне, по крайней мере, предложить несколько идей и предложений.

    Во-первых, вы можете использовать любой сетевой транспорт для того, чтобы загрузить данные для кэширования, такой, как WinJS.xhr, API фоновой передачи данных, а так же механизмом HTML5 AppCache (http://msdn.microsoft.com/library/ie/hh673545.aspx), который хорошо подходит для веб-содержимого, которое вы загружаете в элементы iframe. Обратите внимание на то, что использование AppCache требует, чтобы URI, имеющие дело к вопросу, были объявлены в манифесте, как ApplicationContentUri (URI содержимого) (смотрите Главу 3 курса "Введение в разработку приложений для Windows 8 с использованием HTML, CSS и JavaScript"). В свою очереть, другие данные, полученные из удаленных источников, такие, как изображения, кэшируются автоматически, как временные файлы Интернета. Даже удаленный скрипт, загруженный в iframe, работающий в веб-контексте, кэшируется подобным образом. И механизмы кэширования, и вопрос об ограничениях размера кэша задает Internet Explorer.

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

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

    Лучшее место для хранения кэшированных данных, это папки данных вашего приложения, в особенности, LocalFolder и TemporaryFolder. Избегайте использования RoamingFolder для кэширования данных, полученных из онлайновых источников: помимо риска превысить ограничение на объем перемещаемых данных (смотрите Главу 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript"), это еще и не имеет особого смысла. Так как система будет, в любом случае, перемещать эти данные по сети, лучше позволить приложению снова их загрузить, когда ему это будет нужно. То же самое касается и покупок внутри приложения: так как пользователь может легко загрузить эти покупки из Магазина Windows на другое устройство (и приложение на этом устройстве обнаружит, что данные покупки уже оплачены), перемещать их между устройствами не нужно.

    Использовать ли LocalFolder или TemporaryFolder, зависит от того, насколько важны данные в работе приложения. Если приложение не может запуститься без кэша – как, например, приложение с рецептами, которое я раньше упоминал – используйте папку локальных данных приложения. Если кэш – это лишь оптимизация и пользователь может очистить занятое им пространство с помощью средства очистки диска, сохраните его в TemporaryFolder и снова восстановите позже. (Хочу еще раз напомнить, что IndexedDB, как описано в Главе 2 курса "Пользовательский интерфейс приложений для Windows 8, созданных с использованием HTML, CSS и JavaScript", имеет ограничение на объем данных, который может хранить приложение и общий системный лимит. Если это – потенциальный источник проблем, вы можете воспользоваться другим механизмом хранения данных.

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

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

    Врезка: Сетевое соединение и изображения из удаленных источниках на динамических плитках и всплывающих уведомлениях

    В Главе 2 мы говорили о том, как приложение может выглядеть динамичным, выполняющим какие-то действия, посредством таких возможностей, как динамические плитки и уведомления. Очевидно, периодические уведомления и push-уведомления полностью зависят от состояния соединения и без него работать не будут. Исполняющееся приложение, с другой стороны, может отправлять обновление при отсутствии подключения. В подобных обстоятельствах приложению следует избегать ссылаться на удаленные изображения в обновления, так как они не смогут быть загружены при отсутствии соединения, а системы поддержки плиток и всплывающих уведомлений сейчас не поддерживают использование локальных резервных копий изображений. Таким образом, приложению следует проверить состояние соединения перед отправкой обновления и при его отсутствии использовать локальные (ms-appx:/// или ms-appdata:///) изображения вместо удаленных или использовать шаблоны плиток и уведомлений, которые содержат только текст.

    XmlHttpRequest

    Как мы уже много раз видели, передача данных на веб-сервис и с него с использованием XmlHttpRequest, это вполне обычное дело для приложений для Магазина Windows, особенно для тех, которые написаны на JavaScript, для которых обработка XML и/или JSON весьма проста. Это особенно справедливо при использовании оболочки WinJS.xhr, которая превращает весь процесс в обычный promise-вызов.

    Для того, чтобы пользоваться тем, что мы уже рассматривали в Главе 3 курса "Введение в разработку приложений для Windows 8 с использованием HTML, CSS и JavaScript", нужно учесть еще несколько особенностей, связанных с подобными запросами, большинство из которых описаны в материале "Подключение к веб-службам" (http://msdn.microsoft.com/library/windows/apps/hh761502.aspx).

    Во-первых, материал "Загрузка различных типов содержимого" (http://msdn.microsoft.com/library/windows/apps/hh868280.aspx) содержит подробности о различных типах содержимого, которые поддерживает XHR для приложений Магазина. Основные сведения о них приведены здесь:

    Тип Использование responseText responseXML
    arraybuffer Двоичное содержимое, массив Int8 или Int64, или других целочисленных типов и типов с плавающей запятой. Неопределено Неопределено
    Blob Двоичное содержимое, представленное в виде единого объекта. Неопределено Неопределено
    document Объект XML DOM представленный XML-содержимым (MIME –тип text/XML). Неопределено XML -содержимое
    json JSON-строки. Строка JSON Неопределено
    ms-stream Потоковые данные;смотрите материал "Улучшения объекта XMLHttpRequest" (http://msdn.microsoft.com/library/windows/apps/hh673569.aspx) Неопределено Неопределено
    Text Текст (по умолчанию). Текстовая строка Неопределено

    Вот-вторых, знайте, что XHR-ответы могут быть автоматически кэшированы, что подразумевает то, что более поздние запросы по тому же URI могут привести к возврату устаревших данных. Для того, чтобы повторно отправить запрос без учета кэша, добавьте HTTP-заголовок If-Modified-Since так, как показано в материале "Проверка отправки повторных запросов с помощью WinJS.xhr" (http://msdn.microsoft.com/library/windows/apps/hh868281.aspx).

    Рассуждая в том же русле, вы можете заключить операцию WinJS.xhr в другой promise-объект для того, чтобы реализовать автоматические повторы при возникновении ошибки в любом заданном запросе. Таким образом, построить логику повторных запросов вокруг операции XHR с сохранением результата в некоторой переменной. Потом поместите весь код в WinJS.Promise.wrap (или в новый WinJS.Promise) и используйте это везде, где нужно, в приложении.

    В каждой попытке выполнения запроса с помощью XHR помните, что вы так же можете использовать WinJS.Promise.timeout вместе с WinJS.Xhr , как описано в материале "Установка значений времени ожидания с помощью WinJS.xhr" (http://msdn.microsoft.com/library/windows/apps/hh868283.aspx), так как в WinJS.xhr нет прямого описания тайм-аутов. Вы можете, конечно, установить время ожидания в необработанном запросе, но это означает создание зонного всего того, что уже есть в WinJS.xhr.

    Вообще говоря, XHR-заголовки доступны приложению, за исключением куки (заголовки set-cookie и set-cookie2), так как они отфильтрованы при использовании XHR в локальном контексте. Они не отфильтрованы для XHR, который работает в веб-контексте, поэтому, если вам нужны куки, попытайтесь получить их элементе iframe, который работает в веб-контексте, и передать в локальный контекст с использованием postMessage.

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

    И на этой ноте давайте посмотрим на то, как устроен API фоновой передачи данных.

    Врезка: отладка передачи данных по сети с помощью Fiddler

    Если вы заинтересованы в просмотре HTTP(S)-трафика между вашим компьютером и интернетом, что прямо-таки неоценимо при работе с XmlHttpRequests—взгляните на бесплатный инструмента, известный как Fiddler (http://www.fiddler2.com/fiddler2/). В дополнение к проверке трафика, вы так же можете устанавливать точки останова на различные события и "рыться" во входящих и исходящих данных (то есть, изменять их). Этот сервис поддерживает трафик от любого приложения или браузера, в том числе – и от приложений для Магазина Windows.

    Фоновая передача данных

    Одна из операций, которая касается пользовательских данных, заключается в передаче потенциально больших файлов в онлайновые хранилища и в загрузке этих данных оттуда. Даже для файлов умеренного размера, это являет собой некоторую проблему: немногие пользователи хотели бы смотреть на дисплей, наблюдая за процессом передачи файла, поэтому весьма вероятно, что они переключатся на другое приложение, чтобы заняться чем-нибудь поинтереснее пока завершится передача данных. При этом приложение, выполняющее передачу данных будет приостановлено и, возможно, даже остановлено. Это не сулит ничего хорошего при попытке выполнить подобные действия с использованием механизмов наподобие WinJS.xhr!

    Одно из решений – предоставить для этой цели фоновую задачу, что было обычным пожеланием к ранним предварительным версиям Windows 8 до тех пор, пока API не было готово. Это означает запуск некоторого кода, выполняющего эту задачу. WinRT, в итоге, предоставляет специальный API для фоновой передачи данных, ), которое поддерживает до 500 запланированных передач данных во всей системе. Он предлагает встроенный механизм учета стоимости соединения и механизм, позволяющий ему устойчиво работать при изменениях в сетевом соединении, что освобождает приложения от необходимости беспокоиться обо всем этом самостоятельно. Передача данных продолжается, когда приложение приостанавливается и приостанавливается, когда приложение останавливается. Когда приложение снова начинает работу, оно может проверить состояние фоновых передач данных, которые оно ранее инициировало и предпринять, при необходимости, дальшейшие действия – обработать загруженные данные, оповестить пользователя об успешной отправке данных в своем интерфейсе, перечислить неоконченные передачи, перезапустить те из них, что были приостановлены или прерваны. С другой стороны, если пользователь явным образом закрывает приложение (с помощью жеста, сочетания клавиш Alt+ F4 или из Диспетчера задач), все запрошенные приложением сеансы передачи данных отменяются. Это так же справедливо при остановке отладки приложения в Visual Studio.

    Вообще говоря, рекомендовано использовать API фоновой передачи данных всегда, когда вы ожидаете, что операция по передаче данных может превысить возможность пользователя по ожиданию ее окончания. Это, очевидно, зависит от скорости сетевого соединения и от того, предполагаете ли вы, что пользователь переключится на другое приложение в то время, как осуществляется передача данных в вашем приложении. Например, если вы начали передачу данных, но пользователь может продолжать работать (или развлекаться) в вашем приложении в то время, пока это происходит, использование WinJS.xhr c HTTP GET и POST/PUT вполне возможно, хотя вам все еще нужно отслеживать параметры стоимости соединения и обрабатывать изменения в подключении. Если, с другой стороны, пользователь ничего не может делать в вашем приложении до завершения передачи данных, вы можете решить использовать фоновую передачу, видимо, для любых данных, объем которых превышает 10 килобайт или какой-то другой размер, зависящий от текущей скорости сетевого соединения.

    В любом случае, когда вы готовые использовать фоновую передачу данных в приложении, объекты ) и ) в пространстве имен Windows.Networking.BackgroundTransfer станут вашими верными друзьями. У обоих объектов есть методы и свойства, посредством которых вы можете перечислять отложенные передачи, выполнять общие настройки учетных данных, HTTP-заголовков запросов, методов передачи, политики тарификации (для лимитированных сетей), и групповые операции. Каждая отдельная операций, таким образом, представлена объектом DownloadOperation или UploadOperation, с помощью которых вы можете управлять операцией (приостанавливать, отменять ) и получать сведения о ее состоянии. Вместе с каждой операцией вы можете задать учетные данные, политику тарификации и так далее, переназначая общие настройки в классах BackgroundDownloader и BackgroundUploader.

    Примечение. И для случая передачи данных, и при загрузке, запрос на подключение будет отменен, если новое соединение не было установлено в течение пяти минут. Далее, срок любых других HTTP-запросов, которые использованы при передаче, истекает через две минуты. Фоновая передача данных попытается повторить операцию, до трех раз, при наличии подключения.

    Для того, чтобы увидеть основы этого API в действии, начнем с примера "Фоновая передача данных" (http://code.msdn.microsoft.com/windowsapps/Background-Transfer-Sample-d7833f61). Для того, чтобы запустить этот пример, сначала вам нужно настроить сервер на локальном хосте, а так же файл данных и целевую страницу загрузки. Убедитесь, что у вас установлен Internet Information Services, как описано в Главе 2. Затем, в командной строке администратора, перейдите к папке примера Server и выполните команду powershell –file serversetup.ps1. Эта команда произведет установку необходимых серверных файлов для примера на локальном хосте и позволит вам запускать дополнительные упражнения к этой лекции, которые находятся в дополнительных материалах к ней.

    Основы загрузки данных

    Сценарий 1 примера "Фоновая передача данных" , (js/downloadFile.js) позволяет загружать файл изображения с сервера, расположеного на локальном хосте, и сохранять это изображение в Библиотеке изображений. По умолчанию поле для ввода URI установлено на конкретное URI локального хоста и элемент управления не активен. Это так, потому что пример не производит проверку правильности ввода URI, что всегда следует выполнять в ваших приложениях. Если вы хотите ввести другие URI, просто удалите disabled="disabled" из элемента serverAddressField в html/downloadFile.html. Для того, чтобы увидеть загрузчик в действии, так же полезно подготовить какой-нибудь большой файл, передача которого займет некоторое время. В этом вам может помочь любимая поисковая машина, или вы можете скопировать какой-нибудь из своих файлов на сервер локального хоста.

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

    Начало фоновой загрузки данных выглядит так. Сначала создают ) для передачи данных, используя BackgroundDownloader.createDownload, в данный момент можно усатновить свойства объекта method, costPolicy, и group для переопределения параметров, заданных объектом BackgroundDownloader. Метод передачи данных (method) – это строка, которая идентифицирует тип используемой передачи данных (обычно GET для HTTP или RETR для FTP). К двум другим параметрам мы вернемся в разделах "Задание стоимостной политики" и "Группировка нескольких запросов"

    Как только операция соответствующим образом сконфигурирована, последний шаг заключается в вызове ее метода startAsync с вашими обработчиками завершения, ошибки и прогресса операции :

    // Асинхронно создает файл в папке изображений (требуется объявление возможностей).
    Windows.Storage.KnownFolders.picturesLibrary.createFileAsync(fileName,
    Windows.Storage.CreationCollisionOption.generateUniqueName)
    .done(function (newFile) {
    // Предполагается, что uriString это текстовое URI файла для загрузки
    var uri = Windows.Foundation.Uri(uriString);
    var downloader = new Windows.Networking.BackgroundTransfer.BackgroundDownloader();
    
    // Создание новой операции загрузки.
    var download = downloader.createDownload(uri, newFile);
    
    // Запуск загрузки
    download.startAsync().then(complete, error, progress);
    }
            

    Когда операция выполняется, следующие свойства предоставляют дополнительные сведения о передаче данных:

  • requestedUri и resultFile Те же, которые были переданы createDownload.
  • guid Уникальный идентификатор, присвоенный операции.
  • ), содержащая следующие члены: ): idle, running, pausedByApplication, pausedCostedNetwork, pausedNoNetwork, canceled, error, и completed).
  • Вот несколько методов DownloadOperation, которые так же можно использовать при передаче данных:

  • pause и resume Управляют производимой загрузкой. Больше о них – в разделе "Приостановка, возобновление, перезапуск фоновых передач данных" ниже.
  • ) со свойствами headers (коллекция заголовков ответа сервера), actualUri, isResumable, и statusCode (с сервера). Повторяющиеся вызовые этого метода возвращают те же самые данные до тех пор, пока свойство hasResponseChanged не будет установлено в true.
  • getResultStreamAt Возвращает IInputStream для загруженного содержимого, с тем, что уже загружено, или, когда операция завершена, со всеми загруженными данными.
  • В Сценарии 1 примера, функция прогресса – переданная promise-объекту, возвращенному startAsync, использует getResponseInformation и getResultStreamAt для показа частично загруженного изображения:

    var currentProgress = download.progress;
    
    // ...
    
    // Получает заголовок ответа Content-Type.
    var contentType = download.getResponseInformation().headers.lookup("Content-Type");
    
    // Проверяет, является ли поток изображением.
    if (contentType.indexOf("image/") === 0) {
    .
    // Получает поток, с позиции 0
    imageStream = download.getResultStreamAt(0);
    
    // Конвертирует поток в тип WinRT
    var msStream = MSApp.createStreamFromInputStream(contentType, imageStream);
    var imageUrl = URL.createObjectURL(msStream);
    
    // Передает URL потока в тег HTML image.
    id("imageHolder").src = imageUrl;
    
    // Закрывает поток, когда изображение отображено.
    id("imageHolder").onload = function () {
    if (imageStream) {
    imageStream.close();
    imageStream = null;
    }
    };
    }
            

    Все это работает, потому что API фоновой передачи данных сохраняет загруженные данные во временный файл и предоставляет поток для него, а функция наподобие URL.createObjectURL делает то же самое, что мы уже делали с ее помощью напрямую с объектом StorageFile. Как только объект DownloadOperation выйдет из области видимости и будет произведена сборка мусора, однако, данный временный файл будет удален.

    Данный временный файл существует еще и потому, что, как отмечено выше, нет необходимости предоставлять объект StorageFile для размещения в нем загруженных данных. Таким образом, вы можете передать в качестве второго аргумента null в createDownload и работать с данными посредством DownloadOperation.getResultStreamAt. Это вполне подходит, если конечной целью не является отдельный файл.

    Существуют и вариант createDownload, который принимает второй аргумент StorageFile , содержимого которого представляет собой тело запросов HTTP GET или FTP RETR, которое будет отправлено на URI сервера перед началом загрузке. Это подходит для некоторых веб-сайтов, которые требуют, чтобы вы, перед началом загрузки, заполнили форму.

    Врезка: Где команда отмены?

    Вы уже могли заметить, что ни у DownloadOperation ни у UploadOperation нет метода для отмены операции. Как же это сделать? Вы отменяете передачу, отменяя операцию startAsync — то есть, вызывая метод cancel promise-объекта, возвращенного startAsync. Это означает, что вам нужно пологаться на promise-объекты каждой вызванной вами операции передачи данных.

    Основы отправки данных

    Сценарий 2 примера "Фоновая передача данных" (js/uploadFile.js) использует возможность фоновой отправки данных, в частности, он отправляет некоторые файлы (выбранные с помощью средства выбора файлов) по URI, который может их принять. По умолчанию URI указывает на http://localhost/BackgroundTransferSample/upload.aspx, на страницу, установленная с помощью скрипта PowerShell, который использовался для настройки сервера. Как и в случае со Сценарием 1, элемент управления для ввода URI заблокирован, так как пример не проверяет его, как, снова повторю, всегда нужно делать в приложениях, которые принимают любые URI из недоверенных источников (от пользователя, в данном случае). Для тестовых целей, конечно, вы можете убрать disabled="disabled" из элемента serverAddressField element вhtml/uploadFile.html и ввести другие URI, которые позволят испытать ваши собственные сервисы по приему отправленных файлов. Это особенно удобно, если вы исполняете серверную часть примера в Visual Studio 2012 Express для Web, когда URI нуждается в номере порта локального хоста, присвоенного отладчиком.

    В дополнении к кнопкам для начала отправки и отмены, пример предоставляет еще одну кнопку, для начала составной, состоящей из нескольких частей, операции отправки данных. Больше об этом – в разделе "Составная отправка данных" ниже.

    В коде отправка выглядит очень похожей на загрузку. Предполагая, что у вас имеется ) для передачи данных с помощью ). Это так же можно использовать для того, чтобы разбить большие файлы на отдельные части, если сервер может их принять. Смотрите "Разбивка больших файлов" ниже.

    Когда есть объект операции, можно настроить некоторые параметры передачи, переопределив свойства по умолчанию, заданные BackgroundUploader. Среди них – method (HTTP POST или PUT, или FTP STOR), costPolicy, и group. По поводу последних, смотрите разделы "Задание стоимостной политики" и "Группировка нескольких запросов" ниже.

    Как только все готово, вызов метода операции startAsync приведет к началу отправки данных :

    // Предполагается, что uri это объект Windows.Foundation.Uri и file это StorageFile для отправки
    var uploader = new Windows.Networking.BackgroundTransfer.BackgroundUploader();
    var upload = uploader.createUpload(uri, file);
    promise = upload.startAsync().then(complete, error, progress);
            

    Когда операция выполняется, следующие свойства предоставляют дополнительные сведения о передаче данных:

  • requestedUri и sourceFile То же самое, что передано createUpload (операция, созданная с помощью createUploadFromStreamAsync поддерживает лишь requestedUri).
  • guid Уникальный идентификатор, присвоенный операции.
  • progress Структура BackgroundUploadProgress (http://msdn.microsoft.com/library/windows/apps/windows.networking.backgroundtransfer.backgrounduploadprogress.aspx) со следующими членами: ), с возможными значениями idle, running, pausedByApplication, pausedCostedNetwork, pausedNoNetwork, canceled, error, и completed).

    В отличие от операции загрузки, UploadOperation не имеет методов для приостановки или возобновления, но имеет те же методы getResponseInformation и getResultStreamAt. В случае с отправкой данных, ответ от сервера менее интересен, так как он не содержит переданых данных, лишь заголовки, сведения о состояниях и содержимое, которое возвращает страница, осуществляющая прием передаваемых данных. Если данная страница возвращает какой-нибудь интересный HTML-код, однако, вы можете использовать эти данные как часть справочных данных, выводимых приложением при отправке данных.

    Как было упомянуто ранее, для отмены операции UploadOperation, вызовите метод cancel promise-объекта, возвращенного startAsync.

    Разбивка больших файлов

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

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

    С точки зрения клиента, каждая часть передается в отдельной UploadOperation; это более, чем очевидно. Сложная часть этой задачи заключается в разбиении большого файла на клиенте. Вся эта непростая задача, весьма вероятно, может разрешиться в виде сложной цепочки вложенных асинхронных операций, возможно, с созданием множества временных файлов из единственного источника. Если вы заняты этим вопросом, я предлагая вам написать программу для этого и куда-нибудь ее отправить, чтобы все мы могли это увидеть!

    Но есть и более простой подход, с использованием ). С помощью его метода getInputStreamAt затем вы IInputStream для каждой из начальных точек в потоке (то есть, задавая смещения в зависимости от размеров фрагмента). Затем вы создаете UploadOperation с каждым из входных потоков, используя createUploadFromStreamAsync. Последнее условие – указать, что операция обслуживает лишь некоторую часть этого потока. Это делается с помощью вызова ее метода setRequestHeader("content-length", <length> ), где <length> - это размер сегмента, плюс – размер других данных в запросе; так же вам понадобится доавить заголовок для идентификации сегмента для данной операции отправки информации. После всего этого можно вызывать методы startAsync операций для начала передачи данных.

    Составная отправка данных

    В дополнение к методам createUpload и createUploadFromStreamAsync, объект BackgroundUploader предоставляет другой метод, который называется createUploadAsync (в трех вариантах). Он обрабатывает то, что называется составной отправкой данных (multipart upload).

    С точки зрения сервера, составная отправка данных – это один HTTP-запрос, который содержит разные фрагменты данных (части), такие. как идентификаторы приложений, маркеры авторизации, и так далее, вместе с содержимым, где каждая часть может быть отделена с помощью специальной строки. Подобная отправка используется сетевыми сервисами, наподобие Flickr и YouTube, каждый из которых принимает запросы с multipart Content-Type. (Смотрите материал "Content-type: multipart" (http://msdn.microsoft.com/library/ms527355.aspx)). Например, как показано в примере "Отправка фото – POST" ( http://www.flickr.com/services/api/upload.example.html), Flickr ожидает запросов с типом содержимого multipart/form-data, с частями api_key, auth_token, api_sig, photo, и, наконец, с содержимым файла. В случае с YouTube, как описано в "YouTube API v2.0 – Прямая отправка данных" (https:xmlns//developers.google.com/youtube/2.0/developers_guide_protocol_direct_uploading), сервис ожидает типа содержимого multipart/related с частями, которые содержат XML-данные запроса, тип видео, и, затем двоичный файл данных.

    Средство фоновой отправки данных поддерживает все это посредством метода ), каждый из которых представляет одну из частей отправляемых данных. Результирующая операция отправит запрос с типом содержимого multipart/form-data со случайным GUID для строк-разделителей. Второй вариант createUploadAsync позволяет задавать тип содержимого напрямую (посредством подтипов, таких, как related), и третий вариант добавляет разделительную строку.

    То есть, учитывая, что parts - это как массив частей, методы выглядят так:

    var uploadOpPromise1 = uploader.createUploadAsync(uri, parts);
    var uploadOpPromise2 = uploader.createUploadAsync(uri, parts, "related");
    var uploadOpPromise3 = uploader.createUploadAsync(uri, parts, "form-data", "-------123456");
            

    Для создания каждой из частей, сначала создайте ):

  • new BackgroundContentPart() Создает часть по умолчанию.
  • new BackgroundContentPart(<name> ) Создает часть с заданным именем.
  • new BackgroundContentPart(<name> , <file>) Создает часть с заданным именем и локальным именем файла.
  • В каждом случае выполняют дальнейшую инициализацию частей, вызывая их методы setText, setHeader, и setFile. Первый, setText, присваивает части значение. Второй, setHeader, можно вызывать несколько раз для предоставления части значений заголовков. Третий, setFile, это способ предоставления StorageFile части, созданной с помощью третьего из вышеописанных вариантов.

    Сценарий 2 исходного примера "Фоновая передача данных" показывает последний вариант с использованием массива из выбранных файлов, но, возможно, немногие сервисы смогут принять подобный запрос. Вместо этого давайте посмотрим, как можно создать составной запрос на отправку данных, на примере "Отправка фото – POST" (http://www.flickr.com/services/api/upload.example.html). Для этой цели я создал упражнение "Multipart Upload", которое можно найти в дополнительных материалах к этой лекции. Вот код из js/uploadMultipart.js, который создает все необходимые части с использованием файла tinyimage.jpg из пакета приложения:

    // К этому моменту переменные file и uri уже установлены. bt – это короткое имя для пространства имен
    var bt = Windows.Networking.BackgroundTransfer;
    var uploader = new bt.BackgroundUploader();
    var contentParts = [];
    
    // Вместо отправки нескольких файлов (как в исходном примере), мы создаем эти части, которые
    // совпадают с примером POST дляFlickr on http://www.flickr.com/services/api/upload.example.html
    var part;
    
    part = new bt.BackgroundTransferContentPart();
    part.setHeader("Content-Disposition", "form-data; name=\"api_key\"");
    part.setText("3632623532453245");
    contentParts.push(part);
    
    part = new bt.BackgroundTransferContentPart();
    part.setHeader("Content-Disposition", "form-data; name=\"auth_token\"");
    part.setText("436436545");
    contentParts.push(part);
    
    part = new bt.BackgroundTransferContentPart();
    part.setHeader("Content-Disposition", "form-data; name=\"api_sig\"");
    part.setText("43732850932746573245");
    contentParts.push(part);
    
    part = new bt.BackgroundTransferContentPart();
    part.setHeader("Content-Disposition", "form-data; name=\"photo\"; filename=\"" + file.name + "\"");
    part.setHeader("Content-Type", "image/jpeg");
    part.setFile(file);
    contentParts.push(part);
    
    // Создаем новую операцию отправки, задаем разделительную строку.
    uploader.createUploadAsync(uri, contentParts,
    "form-data", "-----------------------------7d44e178b0434")
    .then(function (uploadOperation) {
    // Start the upload and persist the promise
    upload = uploadOperation;
    promise = uploadOperation.startAsync().then(complete, error, progress);
    }
    );
            

    Итоговый запрос будет выглядеть так, как показано ниже, что очень похоже на то, что показано на странице Flickr (лишь с несколькими дополнительными заголовками):

    POST /website/multipartupload.aspx HTTP/1.1
    Cache-Control=no-cache Connection=Keep-Alive Content-Length=1328
    Content-Type=multipart/form-data; boundary="-----------------------------7d44e178b0434" Accept=*/*
    Accept-Encoding=gzip, deflate
    Host=localhost:60355
    User-Agent=Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; Win64; x64; Trident/6.0; Touch) UA-CPU=AMD64
    -------------------------------7d44e178b0434
    Content-Disposition: form-data; name="api_key"
    
    3632623532453245
    -------------------------------7d44e178b0434
    Content-Disposition: form-data; name="auth_token"
    
    436436545
    -------------------------------7d44e178b0434
    Content-Disposition: form-data; name="api_sig"
    
    43732850932746573245
    -------------------------------7d44e178b0434
    Content-Disposition: form-data; name="photo"; filename="tinysquare.jpg" Content-Type: image/jpeg
    
    {RAW JFIF DATA}
    -------------------------------7d44e178b0434--
            

    Для того, чтобы запустить пример и увидеть, как принимается этот запрос, перейдите в папку MultipartUploadServer в дополнительных материалах к этой лекции. Загрузите website.sln в Visual Studio 2012 Express для Web, откройте MultipartUploadServer.aspx, и установите точку останова на первое выражение if внутри метода Page_Load. Затем запустите сайт в Internet Explorer для открытия данной страницы с помощью отладочного порта локального хоста. Скопируйте URI страницы для выполнения следующего шага.

    В упражнении "Multipart Upload" вставьте данный URI в поле URI и нажмите на кнопку Start Multipart Transfer (Начать составную передачу данных). Когда будет вызван метод startAsync операции, должна сработать точка останова серверной страницы в Visual Studio для Web. Вы можете пошагово исполнить данный код, если нужно, и изучить объект Request; в конце код скопирует запрос в файл на сервере, который называется multipart-request.txt. Он будет включать в себя содержимое запроса, как показано выше, где вы можете увидеть взаимосвязь между тем, как вы настраиваете части на клиенте и как они принимаются сервером.

    Предоставление заголовков и учетных данных

    При работе с объектами BackgroundDownloader и BackgroundUploader есть возможность устанавливать значения для отдельных HTTP-заголовков, используя их методы setRequestHeader. Оба принимают имя заголовка и значение, и их вызывают несколько раз, если нужно установить больше одного заголовка.

    Похожим образом, и тот и другой объекты имеют два свойства для учетных данных: serverCredential и proxyCredential, которые используются в зависимости от нужд URI сервера. Оба свойства это объекты ). Так как цель операции фоновой передачи данных – предоставление учетных данных для сервера, обычно PasswordCredential создают так:

    var cred = new Windows.Security.Credentials.PasswordCredential
              (resource, userName, password);

    В данном случае resource это обычная строка, которая идентифицирует ресурс, к которому применяются учетные данные. Это используется для управления учетными данными в хранилище учетных данных, как мы увидим в разделе "Проверка подлинности, учетные данные и профиль пользователя" ниже. Сейчас создание учетных данных таким способом – это все, что нужно для того, чтобы пройти проверку подлинности сервером при выполнении передачи данных.

    Примечание В настоящий момент установка свойства serverCredential не работает с URI, которые описывают FTP-сервер. Чтобы решить эту проблему, включайте учетные данные прямо в URI, в форме ftp://<user> : <password>@server.com/file.ext (например, ftp://admin:password1@server.com/file.bin).

    Задание стоимостной политики

    Как упомянуто выше, в разделе "Сведения о стоимости передачи данных", политика Магазина Windows требует, чтобы приложения осторожно относились к передаче больших объемов данных в лимитированных сетях. API фоновой передачи данных учитывает это, основываясь на значениях из перечисления BackgroundTransferCostPolicy:

  • default Разрешает передачу данных в сетях с оплатой.
  • unrestrictedOnly Не разрешает передачу данных в сетях с оплатой.
  • always Всегда разрешает загрузку вне зависимости от стоимости передачи данных.
  • Для того, чтобы применить правила к последующим передачам данных, установите значение BackgroundDownloader.costPolicy и/или BackgroundUploader.costPolicy. Правила для отдельной операции можно установить с помощью свойств DownloadOperation.costPolicy и UploadOperation.costPolicy.

    Обычно политику изменяют после того, как запрашивают согласие пользователя, или позволяют пользователю задать поведение программы посредством параметров. Например, если у пользователь запретил загрузку или отправку данных в лимитированной сети, приложению следует устанавливать общий параметр costPolicy в unrestrictedOnly. Если устройство подключено к лимитированной сети, где применяется плата за роуминт и пользователь разрешил передачу данных, параметр costPolicy каждой отдельной операции устанавливается в always. В противном случае API не выполнит передачу данных, так как по умолчанию в лимитированных сетях это не разрешено.

    Когда передача данных заблокирована политикой, свойство операции progress.status будет содержать BackgroundTransferStatus.pausedCostedNetwork.

    Группировка нескольких запросов

    Свойство group, которое есть у объектов BackgroundDownloader, BackgroundUploader, DownloadOperation, и UploadOperation это простая строка, которая указывает на то, что операция передачи данных принадлежит некоторой группе. Свойство можно установить только посредством BackgroundDownloader и BackgroundUploader. Его задают до создания последовательностей отдельных операций. В этих операциях свойство group доступно, но только для чтения.

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

    Свойство group не имеет отношения к самой передаче, она не связана с серверной страницей, которая принимает данные.

    Приостановка, возобновление, перезапуск фоновых передач данных

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

    Когда приложение восстанавливается из состояния приостановки, оно может проверить состояние неоконченных передач данных с помощью использования методов BackgroundDownloader.getCurrentDownloadsAsync и BackgroundUploader.getCurrentUploadsAsync. В обоих случаях существуют два варианта этих методов: один, который перечисляет все передачи данных, и другой, который перечисляет лишь те операции, которые относятся к определенной группе (к той, которая совпадает со свойством group операций).

    Список, который возвращают эти методы, это вектор объектов DownloadOperation и UploadOperation, и, как всегда, к вектору можно обращаться как к массиву. Вот как может выглядеть код для обхода списка:

    Windows.Networking.BackgroundTransfer.BackgroundDownloader.getCurrentDownloadsAsync()
    .done(function (downloads) {
    for (var i = 0; i<downloads.size; i++) {	
    var download = downloads[i];	
    }	
    });	
    
    Windows.Networking.BackgroundTransfer.BackgroundUploader.getCurrentUploadsAsync()
    .done(function (uploads) {	
    for (var i = 0; i<uploads.size; i++) {	
    var upload = uploads[i];	
    }	
    });
            

    В каждом случае, свойство progress каждой операции сообщает о том, насколько продвинулась операция передачи данных. Свойство ), которое может принимать одно из следующих значений: idle, running, pausedByApplication, pausedCostedNetwork, pausedNoNetwork, canceled, error, и completed. Очевидно, это необходимо для предоставления соответствующих оповещений пользователю и для того, чтобы дать ему возможность повторно запустить операция, которая приостановлена или завершилась с ошибкой, для приостановки выполняющейся операции и для того, чтобы выполнить что-либо при завершении передачи данных.

    Кстати, при использовании API фоновой передачи данных, приложению всегда следует давать пользователю возможность управления начатыми операциями. Загрузку можно приостановить с помощью метода DownloadOperation.pause и снова начать с помощью метода DownloadOperation.resume. (Для отправки данных эквивалентов нет.) Операции загрузки и отправки отменяют, отменяя promise-вызовы, которые возвращены startAsync.

    Возникает интересная ситуация: если ваше приложение закрыто и позже запущено снова, как заново снова запустить операцию передачи данных, которая была приостановлена? Ответ довольно прост. При перечислении операций с помощью getCurrentDownloadsAsync и getCurrentUploadsAsync, незавершенные передачи данных автоматически запускаются. Но как получить promise-объекты, которые изначально были возвращены методами startAsync? Они не являются значениями, которые можно сохранить в составе состояния сеанса приложения и загрузить при запуске, и, все же, они нужны для того, чтобы можно было отменить операции, если нужно, и так же для того, чтобы присоединить к ним обработчики завершения, ошибок и прогресса.

    По этой причине и DownloadOperation и UploadOperation предоставляют метод, который называется attachAsync, который возвращает promise-объект для операции, так же, как изначально это делает startAsync. Затем вы можете вызвать методы promise-объекта then или done для предоставления собственных обработчиков:

      promise = download.attachAsync().then(complete, error, progress);

    А так же, если нужно, вызвать promise.cancel(). Короче говоря, когда Windows снова запускает фоновую передачу данных и обычным образом вызывает startAsync от имени вашего приложения, эти promise-объекты хранятся внутри. Методы attachAsync просто возвращают эти новые promise-объекты.

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

    Вернуться к учебному плану