Управление доставкой динамического содержимого в Silverlight

ОГЛАВЛЕНИЕ

Пользователи любого функционально насыщенного веб-приложения (RIA) испытывают обоснованную озабоченность относительно безопасности и размера загружаемых файлов. Приложения Silverlight поддерживаются подмножеством полнофункциональной среды Microsoft .NET Framework и, по существу, обладают потенциальной возможностью выполнять операции, наносящие вред локальному компьютеру пользователя. По этой причине группа Silverlight разработала новую модель безопасности, делающую невозможным обращение приложений ни к одному из важных с точки зрения безопасности классов Core CLR (выпуск .NET Framework, поддерживающий Silverlight).

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

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

Размер приложений Silverlight

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

Безусловно, приложение может занимать гораздо больше места, в особенности если в него входят длинные алгоритмы, графическое и мультимедийное содержимое или анимации. В случае громоздких приложений, когда время загрузки становится проблемой, можно выбрать один из двух основных вариантов. Первый вариант заключается в создании потока содержимого Silverlight тем способом, который обсуждался на веб-узле по адресу silverlight.live.com. Другой вариант состоит в разбиении приложения на независимые части, которые можно загружать по отдельности и по запросу.


Динамически создаваемый код XAML

Подключаемый модуль Silverlight предназначен, по существу, для отображения содержимого в формате XAML. Если код XAML сопровождается некоторым кодом поддержки, тогда подключаемый модуль обрабатывает код для создания пользовательского интерфейса и поддержки любого закодированного поведения или эффектов. Если требуется загрузить только код XAML, можно указать непосредственно его URL-адрес; в противном случае для ссылки на пакет Silverlight можно использовать расширение XAP.

В пакет XAP входит манифест и одна или несколько сборок. Одна из этих загрузок содержит точку входа приложения; остальные сборки — это сборки, на которые имеются ссылки. XAML для пользовательского интерфейса хранится в файлах ресурсов сборки точки входа. Пакет XAP создается расширением Visual Studio 2008 для Silverlight 2 при создании и сборке проекта.

Единственное, что умеет делать подключаемый модуль Silverlight — это обработка потоков XAML и XAP. Однако для загрузки такого содержимого подключаемому модулю не обязательно указывать физический ресурс XAML или XAP на сервере. Можно, например, указать подключаемому модулю URL-адрес, возвращающий динамически создаваемое содержимое XAML или XAP.

На рис. 1 показан пример обработчика HTTP в ASP.NET, возвращающего оперативно созданный код XAML. Метод ProcessRequest устанавливает тип содержимого объекта Response, а затем записывает некоторое содержимое XAML, например составленный динамически код XAML, на основе данных настройки, параметров или условий этапа выполнения. Настраивая свойство Expires объекта Response, можно также предотвратить кэширование файла ресурса на клиентском компьютере. Это удобно, если содержимое, с которым вы работаете, периодически изменяется и требует обновления.

Рис. 1 Обработчик HTTP, возвращающий код XAML

<%@ WebHandler Language="C#" Class="XamlGenHandler" %>

using System;
using System.Web;

public class XamlGenHandler : IHttpHandler 
{
   public void ProcessRequest (HttpContext context) 
   {
     // Prevent caching of the response
     context.Response.Expires = -1;

     // Set the type of data we're returning
     context.Response.ContentType = "text/xaml";

     // Create some XAML and return it down the wire
     context.Response.Write("<Canvas xmlns=
       'http://schemas.microsoft.com/client/2007' " +
       "xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>" +
       "<TextBlock Foreground='black' Padding='10' FontSize='20'>
       <Run>XAML content</Run><LineBreak/>" + 
       "<Run>[generated " + DateTime.Now.ToLongTimeString() + "]</Run>" +
       "</TextBlock></Canvas>");
   }

   public bool IsReusable 
   {
     get {return true;}
   }

}


Динамически создаваемый пакет XAP

Процедура возврата динамически созданного пакета XAP не слишком отличается от возврата необработанного текста XAML, за исключением того, что пакет XAP не является обычным текстовым файлом. Пакет XAP представляет собой файл ZIP, в который входит манифест XML и одна или несколько сборок. Используя формат пакета, рабочая группа свела к минимуму число циклов обработки, необходимых для загрузки всего содержимого, требуюемого для приложения Silverlight. На рис. 2 показан обработчик HTTP в ASP.NET, записывающий содержимое файла XAP в поток ответа HTTP.

Рис. 2 Обработчик HTTP, возвращающий пакет XAP

<%@ WebHandler Language="C#" Class="XapGenHandler" %>

using System;
using System.Web;

public class XapGenHandler : IHttpHandler 
{
   public void ProcessRequest (HttpContext context) 
   {
     // XAP file to return 
     string xapFile = "...";

     // Set the type of data we're returning
     context.Response.ContentType = "application/octet-stream";

     // Create some XAML and return it down the wire
     content.Response.WriteFile(xapFile);
   }


   public bool IsReusable 
   {
     get {return true;}
   }

}

В примере кода выполняется чтение данных XAP из существующего файла. Излишне говорить о том, что если внедрить в проект библиотеку ZIP, можно без труда оперативно собирать пакет, объединяя разные библиотеки DLL, а затем создавая соответствущий файл манифеста XML.

Возвращая содержимое XAP, вы устанавливаете тип application/octet-stream для содержимого ответа — тип MIME, который обычно определяет обобщенное двоичное содержимое.

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

<asp:Silverlight ID="Xaml1" runat="server" 
   Source="~/xap.ashx" 
   MinimumVersion="2.0.30523" 
   Width="100%" 
   Height="100%" />

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

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


Содержимое по запросу

Silverlight 2 предоставляет функционально насыщенный и универсальный интерфейс API для загрузки по запросу кода и/или XAML и внедрения его в существующую объектную модель документа XAML.

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

StackPanel1.Children.Add(downloadedContent);

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

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

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

Кэширование загружаемого содержимого

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

Отмечу, что если требуется загрузить ресурс XAP из обозревателя, кэширование можно контролировать с помощью имеющихся на странице параметров, которые обычно вставляются с помощью метатегов или атрибутов директив ASP.NET. Если ресурс XAP требуется загрузить с помощью обработчика HTTP, как в предыдущем примере, в этом случае можно управлять кэшированием для конкретного запроса.

Еще один вопрос, на который следует обратить внимание, заключается в том, что кэшируется исходное содержимое XAP, включая сборки и XAML. Следовательно, работающее приложение может программно модифицировать исходный код XAML. Однако, такие изменения не будут кэшироваться автоматически, и точно так же не будет выполняться изолированное кэширование любого ресурса, извлекаемого из пакета XAP (мультимедийные данные, изображения и т.п.). Таким образом, когда бы пользователь ни посетил страницу, пакет XAP не загружается повторно (если только он не является просроченным), но все ресурсы извлекаются повторно. Более того, утрачиваются все изменения, которые могли быть внесены в эти ресурсы в предыдущих сеансах. Для сохранения изменений объектной модели документа XAML необходимо организовать свой собственный специальный кэш. (Эта современная методика будет обсуждаться во второй части статьи, посвященной этой теме..

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


Средство загрузки файлов

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

WebClient wc = new WebClient();
wc.DownloadStringCompleted += 
   new DownloadStringCompletedEventHandler(callback);
wc.DownloadStringAsync(address);

Метод DownloadStringAsync использует команду HTTP GET и получает ответ формата URL в виде строки. Ниже показано, как эту строку получает соответствующий обратный вызов.

void callback(object sender, DownloadStringCompletedEventArgs e)
{
  if (e.Error != null)
   return;

  string response = e.Result;
...
}

Вскоре вы увидите, что этот метод идеально подходит для загрузки обычного XAML, не сопровождаемого кодом поддержки. Для программной загрузки двоичных данных, например пакета XAP, требуется поток и несколько иной подход. Класс WebClient по-прежнему полезен, поскольку он предоставляет подходящий метод в OpenReadAsync.

WebClient wc = new WebClient();
wc.OpenReadCompleted += 
   new OpenReadCompletedEventHandler(callback);
wc.OpenReadAsync(address);

Структура соответствующего обратного вызова точно такая же, как в предыдущем случае. В конечном счете, для получения простой строки используется метод DownloadStringAsync; для получения потока данных используется метод OpenReadAsync. Выбор между загрузкой строки или потока является, главным образом, вопросом предпочтения и зависит, по существу, от того, как предполагается использовать получаемые данные.

Обратите также внимание на то, что WebClient предоставляет пару методов для написания удаленного URL-адреса: метод UploadStringAsync для создания строки и метод OpenWriteAsync для загрузки данных на URL-адрес с помощью потока.

Свойство Headers можно использовать для указания дополнительных заголовков, поскольку по умолчанию данный класс не указывает никаких заголовков. Однако необходимо обратить внимание на то, что некоторые из созданных заголовков платформа вырезает и управляет ими внутренним образом. К ним относятся Referer, Connection и User-Agent. Заголовок Content-Type, если он создан, сохраняется.

Когда дело доходит до загрузки ресурсов, класс WebClient, прежде чем пытаться установить подключение, прозрачно использует кэш обозревателя. Если в кэше нет ресурса XAML или XAP, класс приступает к загрузке. Процесс загрузки содержимого из приложения Silverlight выполняется в результате совместной работы среды выполнения Silverlight и внутренного интерфейса API, предоставляемого размещающим обозревателем подключаемому модулю. Это означает, что под прикрытием WebClient среда выполнения Silverlight обменивается информацией с «песочницей» обозревателя, чтобы проверить, присутствует ли в кэше запрошенный ресурс. Если нет, Silverlight переходит к выполнению своих собственных политик безопасности для авторизации запроса. Когда данные, в конце концов, возвращаются из конечной точки, среда выполнения Silverlight кэширует их локально посредством служб обозревателя в полном соответствии с текущими политиками кэширования.

К классу WebClient и другим классам HTTP из пространства имен Silverlight System.Net применяется ряд ограничений безопасности. В частности, при загрузке посредством потока класс WebClient поддерживает только схемы HTTP и HTTPS, а при загрузке чистого XAML — схему FILE. Доступ с использованием разных схем полностью запрещен, поэтому невозможно классу WebClient указать, например, ресурс HTTPS, если страница размещения загружается посредством HTTP (или наоборот). Запрос WebClient может, вообще говоря, достичь URL-адреса в зоне другого обозревателя, но не в том случае, когда он пытается переместиться из некоторой зоны Интернета в зону с более строгими ограничениями. В настоящее время Silverlight поддерживает зоны только в операционной системе Windows.

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

Один объект WebClient не в состоянии обрабатывать одновременно несколькo запросов. Необходимо проверить свойство IsBusy, имеющее логическое значение, чтобы определить, может ли ваш код безопасным образом поместить новый запрос посредством этого же экземпляра WebClient. При использовании нескольких объектов WebClient (возможно, на разных потоках) можно одновременно запустить несколько загрузок.


Загрузка только данных XAML

Посмотрим, как используется WebClient для загрузки данных XAML и включения их в визуальное дерево. В приложениях Silverlight 2 динамическая загрузка простых данных XAML совсем не обязательно обеспечивает требуемые вам возможности программирования. Строка XAML должна быть простым кодом XAML, не содержащим ссылок, требующих разрешения на этапе выполнения, таких как привязки или ссылки на стили и события.

После того, как строка XAML загружена, используется класс XamlReader для преобразования ее в элемент интерфейса пользователя, который можно добавить в существующую объектную модель документа. В следующем далее коде показано, как программным образом загрузить строку XAML с URL-адреса. Отмечу, что URL-адрес потребуется предоставить в виде объекта Uri.

WebClient client = new WebClient();
client.DownloadStringCompleted += 
  new DownloadStringCompletedEventHandler(OnDownloadCompleted);
Uri uri = new Uri("xaml.ashx", UriKind.Relative);
client.DownloadStringAsync(uri);

URL-адрес может указывать на ресурс с простым кодом XAML или на конечную точку, возвращающую ответ text/xaml. В следующем коде загруженный код XAML обрабатывается и добавляется к местозаполнителю в визуальном дереве.

void OnDownloadCompleted(object sender, DownloadStringCompletedEventArgs e)
{
   // Parse XAML to a UI element 
   string xaml = e.Result;
   UIElement dom = XamlReader.Load(xaml) as UIElement;

   // Append to the DOM
   Placeholder.Children.Clear();
   Placeholder.Children.Add(dom);
}

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

Сериализация XAML, выполняемая посредством классов XamlReader и XamlWriter, основывается на принципе отмены ссылок расширения и сохранении значений этапа выполнения поверх параметров этапа разработки. А что, если требуется загрузить содержимое XAML и настроить его до отображения, например посредством динамической привязки данных? Возможности встроить привязки в исходный код XAML не существует, но в загруженном коде XAML можно определить местозаполнители, извлечь их с помощью синтаксического анализа и настроить их программным образом на любое требуемое значение. Однако в Silverlight 2 более предпочтительным решением является, безусловно, загрузка пакета XAP.


Работа с пакетами XAP

Пакет XAP содержит всё приложение Silverlight, пользовательский интерфейс которого состоит, по существу, из пользовательского элемента управления, являющегося на деле просто контейнером разметки и кода XAML.

Как упоминалось, в пакет XAP входит одна или несколько сборок, отслеживаемых файлом манифеста. Обработка пакета XAP, помимо загрузки, влечет два дополнительных этапа. Необходимо выделить основную сборку и затем создать экземпляр класса точки входа, запускающий загруженное приложение. Излишне говорить о том, что в этом случае в XAML можно использовать привязку, стили, события и все, что потребуется. При работе с пакетом XAP именно среда выполнения Silverlight, а не интерфейс API сериализации, обрабатывает XAML и затем разрешает ссылки. С точки зрения возможностей программирования это совершенно другой подход.

Для загрузки и обработки пакета XAP тpебуется несколько больший объем работы, чем при простом создании объектной модели из строки. Часть этой работы (как правило, загрузку содержимого и извлечение сборки) можно переместить в допускающий повторное использование класс загрузчика (см. рис. 3).

Рис. 3. Динамическая загрузка пакета XAP

public partial class Page : UserControl
{
   private UIElement content = null;
   private TabItem item = null;

   public Page()
   {
     InitializeComponent();
   }

   private void chkNewContent_Click(object sender, RoutedEventArgs e)
   {
     bool shouldDisplay = (sender as CheckBox).IsChecked.Value;  

     if (shouldDisplay)
     {
       if (!IsContentAvailable())
         DownloadContent();
       else
         ShowContent();
     }
     else
     {
       HideContent();
     }
   }

   private bool IsContentAvailable()
   {
     return (content != null);
   }

   private void DownloadContent()
   {
     Downloader dl = new Downloader();
     dl.XapDownloaded += 
       new EventHandler<XapEventArgs>(OnPackageDownload);
     dl.LoadPackage("more.xap", "more.dll", "More.ExtraTab");
   }

   void OnPackageDownload(object sender, XapEventArgs e)
   {
     content = e.DownloadedContent;
     ShowContent();
   }

   private void HideContent()
   {
     this.TabList.Items.Remove(item);
   }

   private void ShowContent()
   {
     item = new TabItem();
     item.Header = "Extra tab";
     item.Content = content;
     this.TabList.Items.Add(item);
   }
}

В коде на рис. 3 показан пример приложения со вкладками, которое загружает новую вкладку, когда пользователь в первый раз устанавливает флажок. В этом случае загружается новый пакет XAP, и пользовательский элемент управления, содержащийся в его пользовательском интерфейсе, вставляется во вновь созданный TabItem. Скрытие содержимого из вновь загруженного пакета является простой операцией клиента. А повторное его отображение не требует второго цикла по следующим двум причинам: содержимое кэшировано в памяти, а пакет, из которого оно было создано, кэширован в кэше обозревателя.

На рис. 4 показан пользовательский интерфейс этого примера приложения. Содержимое, вставленное в дополнительную вкладку, происходит из нового проекта приложения Silverlight. С точки зрения развертывания всё, что вам требуется сделать, это сохранить пакет XAP в папке ClientBin размещающего веб-узла (папка, в которой WebClient по умолчанию ищет соответствующее содержимое) или в любом другом месте, которого WebClient может достичь безопасным образом.

 

Рис. 4 Приложение, способное динамически загружать части пользовательского интерфейса

Теперь давайте изучим код вспомогательного класса Downloader. Отмечу, что класс Downloader, используемый в этом примере, является внутренним классом и не имеет никакого отношения к объекту Downloader платформы Silverlight, который доступен для вызывающего кода JavaScript. Объект загрузчика JavaScript является, по существу, оберткой WebClient, вызываемой из сценария.


Обработка содержимого XAP

Класс Downloader использует WebClient для загрузки пакета XAP, а затем извлекает сборку, содержащую приложение, из сжатого потока и создает экземпляр указанного корневого класса. Вся эта логика спрятана за относительно простым программным фасадом, созданным из метода LoadPackage и события XapDownloaded. Этот метод имеет следующую сигнатуру.

public void LoadPackage(
   string xapURL, string assemblyName, string className);

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

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

public event 
   EventHandler<XapEventArgs> XapDownloaded;

Класс аргумента события возвращает следующую информацию.

public class XapEventArgs : EventArgs
{
  public UserControl DownloadedContent;
}

Давайте разберем логику метода LoadPackage. Метод использует двоичный интерфейс класса WebClient для указания на пакет XAP.

void LoadPackage(string package, string asm, string cls)
{
   assemblyName = asm;
   className = cls;

   Uri address = new Uri(package, UriKind.RelativeOrAbsolute); 
   WebClient client = new WebClient();
   client.OpenReadCompleted += 
    new OpenReadCompletedEventHandler(OnCompleted);
   client.OpenReadAsync(address);
}

Загрузка продолжается асинхронно и по завершении создает событие OpenReadCompleted. На рис. 5 показана реализация обработчика события.

Рис. 5 Загрузка завершена

void OnCompleted(object sender, OpenReadCompletedEventArgs e)
{
   if (PackageDownloaded == null)
     return;

   if (e.Error != null)
     return;

   // Load a particular assembly from the XAP package
   Assembly a = GetAssemblyFromPackage(assemblyName, e.Result);

   // Get an instance of the XAML object
   object page = a.CreateInstance(className);  

   // Fire the event
   XapEventArgs args = new XapEventArgs();
   args.DownloadedContent = page as UserControl;
   XapDownloaded(this, args);
}

В отсутствие ошибок вспомогательная функция извлекает указанную сборку из пакета и получает требуемый для добавления экземпляр класса пользовательского элемента управления в фоновом коде блока XAML. Затем создается пользовательское событие для вызывающего кода, чтобы можно было обработать поддерево XAML. В этом месте остается обсудить следующее: детали того, каким образом сборка извлекается из сжатого потока, загруженного с URL-адреса. Этот код показан на рис. 6. Это стандартный код для извлечения сборки из пакета XAP. StreamResourceInfo и Application.GetResourceStream также широко используются для извлечения пакетов ресурсов, таких как изображения или мультимедийное содержимое, из текущего пакета XAP.

Рис. 6. Извлечение сборки из пакета XAP

Assembly GetAssemblyFromPackage(string assemblyName, Stream xap)
{
   // Local variables
   Uri assemblyUri = null;
   StreamResourceInfo resPackage = null;
   StreamResourceInfo resAssembly = null;
   AssemblyPart part = null;

   // Initialize
   assemblyUri = new Uri(assemblyName, UriKind.Relative);
   resPackage = new StreamResourceInfo(xap, null);
   resAssembly = Application.GetResourceStream(resPackage, assemblyUri);

   // Extract an assembly 
   part = new AssemblyPart();
   Assembly a = part.Load(assemblySri.Stream);
   return a; 
}

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

Класс WebClient предлагает эффективный и асинхронный интерфейс программирования API для загрузки любых ресурсов, к которым можно получить доступ по Интернету. Расширяемая модель для объектной модели Silverlight позволяет быстро внедрять в существующую структуру любые изменения. Наконец, основанный на потоках интерфейс API, центральную роль в котором играет класс StreamResourceInfo, облегчает извлечение содержимого из ресурсов сборок и пакетов XAP. 


Причины необходимости в постоянном кэше

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

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

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

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

Постоянный кэш, управляемый приложением, решает оба этих вопроса. Пакеты XAP, хранящиеся в таком постоянном кэше, не были бы затронуты при выполнении пользователем очистки кэша обозревателя. Для постоянного хранения пакетов Silverlight XAP необходимо получить доступ к локальной файловой системе. Из соображений безопасности Silverlight не предоставляет приложениям доступа к локальной файловой системе в целом. Однако здесь приходит на выручку интерфейс API изолированного хранилища. Дополнительные сведения о безопасности в Silverlight см. в статье «CLR вдоль и поперек. Безопасность в Silverlight 2».


Принципы изолированного хранилища

Изолированное хранилище не было создано специально для Silverlight. Изолированное хранилище входит в Microsoft .NET Framework, начиная с версии 1.0. Предназначенное для частично проверенных приложений, изолированное хранилище дает возможность таким приложениям хранить данные на локальном компьютере, соблюдая при этом все действующие политики безопасности. У классического приложения .NET, обладающего полным доверием, вообще не возникает необходимости проходить через слой изолированного хранилища для сохранения своих данных, но для частично проверенного приложения изолированное хранилище является единственной возможностью сохранить данные на клиенте.

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

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

Итак, можно ли безопасно хранить пакеты XAP, загруженные в изолированное хранилище Silverlight? В Silverlight, в отличие от настольной CLR, любая часть исполняемого кода по умолчанию считается непроверенной, и ей не позволено вызывать критические методы или повышать привилегии стека вызывающей программы. В Silverlight любой код, сохраненный для последующего выполнения, не в состоянии выполнить никаких опасных действий. Это ничуть не более опасно, чем выполнение любого другого фрагмента кода Silverlight. Создавая постоянный кэш пакетов Silverlight, вы организуете локальное хранение сегмента приложения Silverlight, который вы сознательно запускаете на выполнение.

В Silverlight роль изолированного хранилища подобна (постольку, поскольку это касается постоянства) роли файлов Сookies HTTP в классических веб-приложениях. Изолированное хранилище в Silverlight следует рассматривать как набор больших файлов Сookie, в которых могут содержаться данные любого типа, включая исполняемый код. В этом случае, однако, защиту обеспечивает базовая среда CLR Silverlight. В действительности, в соответствии с моделью безопасности Silverlight, базовая среда CLR создает исключение каждый раз, когда код приложения собирается выполнить критический метод. В отличие от файлов Сookie HTTP, изолированное хранилище в Silverlight не связано с сетевым вводом-выводом, и с запросами не передается никакое содержимое.

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

И снова модель в целом не слишком отличается от того, что происходит с файлами Сookie HTTP. У администратора всегда имеется возможность найти и даже изменить содержимое файлов Сookie. Если в вашем контексте это целесообразно, для добавления еще одного уровня защиты данных можно использовать шифрование.

Если вы все еще беспокоитесь по поводу некоторого загруженного исполняемого кода, находящегося на вашем компьютере, следует освежить свое понимание модели безопасности Silverlight. Вкратце, базовая среда CLR в Silverlight создает исключение каждый раз, когда код приложения пытается выполнить критический метод. В библиотеке Silverlight BCL (Base Class Library) методы и классы, выполняющие операции, требующие привилегий высокого уровня, помечаются специальным атрибутом SecurityCritical. Отмечу, что это характерно для большей части содержимого пространства имен System.IO.

В модели безопасности Silverlight признается, что некоторым классам платформы могут потребоваться безопасные вызовы критических методов. Такие классы и методы помечаются атрибутом SecuritySafeCritical. Это именно так для классов в интерфейсе System.IO.IsolatedStorage API (см. рис. 1). Важнейший момент модели безопасности Silverlight заключается в том, что никакая часть кода приложения никогда не может быть помечена атрибутом SecurityCritical или SecuritySafeCritical. Этот атрибут зарезервирован для классов в сборках, имеющих цифровую подпись корпорации Майкрософтft и загруженных в память из установочного каталога Silverlight.

Рис. 1 Обзор внутренней структуры интерфейса API изолированного хранилища

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


API изолированного хранилища

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

\..\..\myfile.txt 

Корневой папкой для поддерева изолированного хранилища является папка, находящаяся на следующем по отношению к текущему пути пользователя уровне. Например, в Windows Vista корневая папка для папки изолированного хранилища находится в каталоге Users.

Приложение Silverlight получает доступ к точке входа в изолированное хранилище, которая предназначена для определенного приложения, посредством вызова метода.

using (IsolatedStorageFile iso = 
    IsolatedStorageFile.GetUserStoreForApplication()) 
{
  ...
}

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

Интерфейс API изолированного хранилища Silverlight поставляет классы для работы с файлами и каталогами в пределах защищенного поддерева файловой системы. К счастью, список классов, которые требуется знать, очень короткий; они перечислены на рис. 2.

 

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

Рис. 3 Создание файла изолированного хранилища

using (IsolatedStorageFile iso = 
    IsolatedStorageFile.GetUserStoreForApplication())
{
   // Open or create the low level stream
   IsolatedStorageFileStream fileStream;
   fileStream = new IsolatedStorageFileStream(fileName, 
     FileMode.OpenOrCreate, iso);

   // Encapsulate the raw stream in a more convenient writer
   StreamWriter writer = new StreamWriter(stream);

   // Write some data
   writer.Write(DateTime.Now.ToString());

   // Clean up
   writer.Close();
   stream.Close();
}

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


Создание постоянного кэша пакетов

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

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

 

Рис. 4 Компонент загрузчика и остальная часть приложения

 

Рис. 5 Выделение интерфейса

В исходном коде, приведенном в прошлом месяце, класс Downloader представлял собой монолитную часть кода. Для повышения гибкости проекта давайте выделим из него интерфейс. Как показано на рис. 5, в Visual Studio имеется контекстное меню, которое, хотя и не такое многофункциональное, как аналогичное меню коммерческих инструментов рефакторинга, тем не менее, помогает при извлечении интерфейса из класса.

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

interface IDownloader
{
   void LoadPackage(string xapUrl, string asm, string cls);
   event EventHandler<Samples.XapEventArgs> XapDownloaded;
}

В частности, метод LoadPackage будет переписан для включения логики, которая проверяет наличие в изолированном хранилище указанного пакета XAP и в случае отсутствия загружает его из сети Интернет. На рис. 6 представлена существенная часть кода для класса Downloader. Сначала метод выполняет попытку получить поток для пакета XAP из внутреннего кэша. Если эта попытка не удается, метод продолжает работу и загружает пакет с сервера размещения пакета. (Именно это подробно обсуждалось в прошлом месяце).

Рис. 6. Поддержка кэша для компонента загрузчика

public void LoadPackage(string xap, string asm, string cls)
{
   // Cache data within the class
   Initialize(xap, asm, cls, PackageContent.ClassFromAssembly);

   // Have a look in the cache
   Stream xapStream = LookupCacheForPackage();
   if (xapStream == null)
     StartDownload();
   else
   {
     // Process and extract resources
     FindClassFromAssembly(xapStream);
   }
}

protected Stream LookupCacheForPackage()
{
   // Look up the XAP package for the assembly.
   // Assuming the XAP URL is a file name with no HTTP information
   string xapFile = m_data.XapName;

   return DownloadCache.Load(xapFile);
}

protected void StartDownload()
{
   Uri address = new Uri(m_data.XapName, UriKind.RelativeOrAbsolute);
   WebClient client = new WebClient();

   switch (m_data.ActionRequired)
   {
     case PackageContent.ClassFromAssembly:
       client.OpenReadCompleted += 
         new OpenReadCompletedEventHandler(OnCompleted);
       break;
     default:
       return;
   }
   client.OpenReadAsync(address);
}

private void OnCompleted(object sender, OpenReadCompletedEventArgs e)
{
   // Handler registered at the application level?
   if (XapDownloaded == null)
     return;

   if (e.Error != null)
     return;

   // Save to the cache
   DownloadCache.Add(m_data.XapName, e.Result);

   // Process and extract resources
   FindClassFromAssembly(e.Result);
}

private void FindClassFromAssembly(Stream content)
{
   // Load a particular assembly from XAP
   Assembly a = GetAssemblyFromPackage(m_data.AssemblyName, content);

   // Get an instance of the specified user control class
   object page = a.CreateInstance(m_data.ClassName);

   // Fire the event
   XapEventArgs args = new XapEventArgs();
   args.DownloadedContent = page as UserControl;
   XapDownloaded(this, args);
}

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

Метод LoadPackage в классе Downloader используется для загрузки пользовательских элементов управления Silverlight с целью вставки в текущее дерево XAML. Поскольку пакет XAP является многофайловым контейнером, необходимо указать, в какой сборке содержится пользовательский элемент управления и имя класса. В коде, приведенном на рис. 6, указанная сборка просто извлекается из пакета, загружается в текущий домен приложения, а затем создается экземпляр входящего в нее указанного класса.

А что если у сборки имеются зависимости? Как раз этот случай не предусмотрен в коде из рис. 6. В результате, если у сборки, передаваемой в качестве аргумента методу LoadPackage, имеется зависимость от другой сборки (даже из того же пакета XAP), то возникает исключение, как только поток выполнения добирается до класса из сборки, от которой зависит текущая. Дело в том, что все сборки пакета должны быть загружены в память. Для этого необходимо получить доступ к файлу манифеста, получить информацию о развернутых сборках и обработать их. На рис. 7 показано, как загрузить в память все сборки, на которые ссылается файл манифеста.

Рис. 7 Загрузка всех сборок из манифеста

private Assembly GetAssemblyFromPackage(
   string assemblyName, Stream xapStream)
{
   // Local variables
   StreamResourceInfo resPackage = null;
   StreamResourceInfo resAssembly = null;

   // Initialize
   Uri assemblyUri = new Uri(assemblyName, UriKind.Relative);
   resPackage = new StreamResourceInfo(xapStream, null);
   resAssembly = Application.GetResourceStream(
                resPackage, assemblyUri);

   // Extract the primary assembly and load into the AppDomain 
   AssemblyPart part = new AssemblyPart();
   Assembly a = part.Load(resAssembly.Stream);

   // Load other assemblies (dependencies) from manifest
   Uri manifestUri = new Uri("AppManifest.xaml", UriKind.Relative);
   Stream manifestStream = Application.GetResourceStream(
     resPackage, manifestUri).Stream; 
   string manifest = new StreamReader(manifestStream).ReadToEnd();

   // Parse the manifest to get the list of referenced assemblies
   List<AssemblyPart> parts = ManifestHelper.GetDeploymentParts(manifest);

   foreach (AssemblyPart ap in parts)  
   {
     // Skip over primary assembly (already processed) 
     if (!ap.Source.ToLower().Equals(assemblyName))
     {
       StreamResourceInfo sri = null;
       sri = Application.GetResourceStream(
         resPackage, new Uri(ap.Source, UriKind.Relative));
       ap.Load(sri.Stream);
     }
   }

   // Close stream and returns
   xapStream.Close();
   return a;
}

Файл манифеста является файлом XML, как показано ниже.

<Deployment EntryPointAssembly="More" EntryPointType="More.App" 
       RuntimeVersion="2.0.31005.0">
  <Deployment.Parts>
   <AssemblyPart x:Name="More" Source="More.dll" />
   <AssemblyPart x:Name="TestLib" Source="TestLib.dll" />
  </Deployment.Parts>
</Deployment> 

Для анализа этого файла можно использовать запрос LINQ-to-XML. В исходном коде содержится пример класса ManifestHelper, содержащего метод, который возвращает список объектов AssemblyPart (см. рис. 8). Стоит отметить, что в бета-версиях Silverlight 2 для анализа файла манифеста объекта Deployment можно было использовать класс XamlReader.

// This code throws in Silverlight 2 RTM
Deployment deploy = XamlReader.Load(manifest) as Deployment;

Рис. 8. Анализ манифеста с использованием запроса LINQ-to-XML

public class ManifestHelper
{
  public static List<AssemblyPart> GetDeploymentParts(string manifest)
  {
    XElement deploymentRoot = XDocument.Parse(manifest).Root;
    List<AssemblyPart> parts = 
      (from n in deploymentRoot.Descendants().Elements() 
      select new AssemblyPart() { 
         Source = n.Attribute("Source").Value }
      ).ToList();

      return parts;
  }
}

В окончательной версии объект Deployment был преобразован в singleton-класс, и, следовательно, его невозможно использовать для динамической загрузки сборок. Следовательно, XML манифеста необходимо анализировать вручную.


Политики срока действия

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

Давайте посмотрим, что потребуется для добавления простой политики срока действия, которая по истечении указанного промежутка времени после загрузки выводит кэшированный файл XAP из употребления. Подходящим местом для настройки политики сроков действия является класс DownloadCache, такой как на рис. 6. При добавлении в кэш файла XAP необходимо сохранить некоторые сведения о моменте загрузки. При попытке получить доступ к кэшу для выбора пакета необходимо проверить срок действия пакета (см. рис. 9).

Рис. 9 Проверка срока действия

public bool IsExpired(string xapFile)
{
   bool expired = true;
   if (m_ItemsIndex.ContainsKey(xapFile))
   {
     DateTime dt = (DateTime)m_ItemsIndex[xapFile];

     // Expires after 1 hour
     expired = dt.AddSeconds(3600) < DateTime.Now;   
     if (expired)
       Remove(xapFile);
   }

   return expired;
}

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

Рис. 10 Сохранение сведений о загрузке в изолированном хранилище

public static Stream Load(string file)
{
   IsolatedStorageFile iso;
   iso = IsolatedStorageFile.GetUserStoreForApplication();

   if (!iso.FileExists(file))
   {
     iso.Dispose();
     return null;
   }

   // Check some expiration policy
   CacheIndex m_Index = new CacheIndex();
   if (!m_Index.IsExpired(file))
     return iso.OpenFile(file, FileMode.Open);

   // Force reload
   iso.Dispose();
   return null;
}

CacheIndex является вспомогательным классом, который использует встроенный в Silverlight API настроек приложения для сохранения словаря имен XAP и моментов времени загрузки в изолированном хранилище. Член m_ItemIndex является простым объектом Dictionary, созданным в конструкторе CacheIndex, как показано на рис. 11.

Рис. 11. Класс CacheIndex

public class CacheIndex
{
   private const string XAPCACHENAME = "XapCache";
   private Dictionary<string, object> m_ItemsIndex; 
   public CacheIndex()
   {
    IsolatedStorageSettings iss;
    iss = IsolatedStorageSettings.ApplicationSettings;
    if (iss.Contains(XAPCACHENAME))
     m_ItemsIndex = iss[XAPCACHENAME] as Dictionary<string, object>;
    else
    {
     m_ItemsIndex = new Dictionary<string, object>();
     iss[XAPCACHENAME] = m_ItemsIndex;
     iss.Save();
    }
  }
  ...
}

ApplicationSettings является очень удобным средством Silverlight 2. Оно состоит из словаря строк/объектов, автоматически считывается из хранилища при загрузке приложения и сохраняется обратно в хранилище после завершения работы. Любой (сериализуемый) объект, добавляемый в словарь, сохраняется автоматически.

Создавая в словаре запись XAPCACHENAME, вы реорганизуете содержимое словаря XAP. В словаре XAP для каждого загруженного пакета содержится одна запись о времени загрузки, как видно из рис. 12. Отмечу, что API для ApplicationSettings предоставляет также метод Save для принудительного сохранения перед завершением работы приложения.

Рисунок 12 Добавление сведений о загрузке

public void Add(string xapFile)
{
   m_ItemsIndex[xapFile] = DateTime.Now;
   IsolatedStorageSettings iss;
   iss = IsolatedStorageSettings.ApplicationSettings;
   iss.Save();         
}

public void Remove(string xapFile)
{
   m_ItemsIndex.Remove(xapFile);
   IsolatedStorageSettings iss;
   iss = IsolatedStorageSettings.ApplicationSettings;
   iss.Save();
}


Резюме

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

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

Но класс Downloader, кроме того, предоставляет возможность выбирать из хранилища файл XAP, если можно отыскать пакет, у которого не истек срок действия. Отмечу, что это верно в рамках нескольких сеансов приложения. Вы загружаете пакет один раз, работаете, а затем закрываете приложение. При возобновлении работы пакет повторно загружается из хранилища, если срок действия пакета не истек. Что происходит, если срок действия пакета истек? В этом случае загрузчик проходит через WebClient. Но в этот момент WebClient должен просто вернуть копию этого же пакета, кэшированную ранее обозревателем.

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

Несколько заключительных замечаний

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

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

Отмечу также, что локальным хранилищем можно управлять посредством диалогового окна настройки Silverlight (щелкните правой кнопкой мыши приложение Silverlight). Можно даже полностью его отключить. В этом случае при попытке получения маркера создается исключение. Дисковая квота для локального хранилища также распределяется по доменам; по умолчанию на один домен выделяется 1 МБ. Планируя использование постоянного кэша Silverlight, следует помнить об этом.

Скачать исходники примеров 

Автор: Дино Эспозито (Dino Esposito)
Источник: http://msdn.microsoft.com/ru-ru/magazine/