Загружаемый файл с кодом доступен в коллекции кода MSDN
Обзор кода в интерактивном режиме

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

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

Дино Эспозито (Dino Esposito)

Cодержание

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

В прошлом месяце я обсуждал способы, позволяющие предоставить приложению Silverlight некоторое динамически генерируемое содержимое на этапе запуска и даже «по требованию». Существует множество примеров, на которых можно показать, как для загрузки ресурса, основанного на URL, можно использовать класс WebClient и его модель асинхронного вызова. В частности, я уделил основное внимание тому, что требуется для загрузки пакета XAP, в который входит XAML и управляемый код (см. выпуск рубрики «Новейшие технологии» за январь 2009 г..

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

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

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

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

Что касается динамического содержимого 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.

fig01.gif

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

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

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

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

\..\..\myfile.txt 

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

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

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

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

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

fig02.gif

В классе 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 показана архитектура проекта, к которой следует стремиться.

fig04.gif

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

fig05.gif

Рис. 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, следует помнить об этом.

Свои вопросы и комментарии для Дино отправляйте на cutting@microsoft.com.

Дино Эспозито (Dino Esposito) работает архитектором ПО в компании IDesign и является одним из соавторов книги Microsoft .NET: Создание архитектуры приложений для предприятия (издательство «Microsoft Press», 2008). Проживая в Италии, Дино часто выступает на различных профессиональных мероприятиях по всему миру. Связаться с ним можно в блоге по адресу weblogs.asp.net/despos.