代码下载可从 MSDN 代码库
浏览代码联机

领先

管理动态内容的传递在 Silverlight,Part 2

Dino Esposito

内容

永久的缓存的动机
独立存储的基础知识
独立的存储 API
生成包的永久的缓存
过期策略
将它放在所有一起使用
一些最终版本说明

上个月我讨论了如何在启动和甚至在点播方案中,提供某些动态生成的内容 Silverlight 应用程序。许多示例存在存在介绍如何使用 WebClient 类和其异步调用模式来下载基于 URL 的资源。我特别,重点花费下载 XAP 软件包,其中包括 XAML 和托管的代码 (请参阅在1 月 2009 期,领先的.

基本理念是您下载一个压缩的流,然后提取需要的任何程序集。接下来,您实例化您需要包含该程序集的任何类。类是 XAML 用户控件,XAML Visual 树的整个段,然后可以追加到任何占位符中在当前 XAML 文档对象模型 (DOM)。

到浏览器,任何已下载的 XAP 资源是资源的完全不可区分的从任何其他类型。因此,浏览器会缓存 XAP 包正如任何其他已下载的资源。内置的机制提供了使您的优化的第一个级别重复反复获取同一软件包的往返操作。WebClient 类 downloader 组件上个月的专栏中讨论的核心,基于浏览器的连接引擎,并不下载可用资源本地和不过期但。

一天末尾必须 downloader 组件以延迟任何外部的包,您可能需要的加载。此外,您还必须释放缓存的任何动态下载资源的功能。此外,您可能想添加永久本地缓存的包也能够处理可视树动态更改的。让我们了解如何实现此目的。

永久的缓存的动机

当涉及动态 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 的角度独立的存储是一个功能强大的工具和跨浏览器的方式,并且不是例如影响 HTTP Cookie 的限制的任何保留相对较大块数据的唯一可能的方法。务必了解这一点: 在 Silverlight,独立的存储是唯一可能必须在本地计算机上的缓存数据。如果 Silverlight 应用程序需要将某些数据保存,任何类型的数据本地,然后它只能执行,通过独立存储。此外,独立存储每个应用程序可以保留自己隔离从任何其他应用程序或从该站点外的其他应用程序的数据。

如果您需要独立的存储和其最常见的使用情况一个常规、 面向.NET 的简介,您应该阅读该到独立存储的.NET Framework 开发人员指南. 文章提到了几个位置使用独立的存储不适合的方案。特别,准则假设您应该不使用独立的存储来存储敏感数据、 代码或配置设置 (而不用户首选项)。这样准则从常规安全意识的来源,和执行不一定意味着任何使用独立存储的内在的危险。

因此,可以安全地存储在中 XAP 软件包下载到 Silverlight 独立存储?在 Silverlight 中, 与不同在桌面的 CLR 中可执行代码的任何一处默认情况下不受信任,不允许调用重要的方法或提升权限调用堆栈。在 Silverlight 中, 存储更高版本执行的任何代码将无法执行任何危险。这是不执行任何其他段 Silverlight 代码没有危险。通过构建 Silverlight 包的永久性缓存,您最终本地存储一段 consciously 执行 Silverlight 应用程序。

Silverlight,独立存储的作用是类似,就在而言持久性,传统的 Web 应用程序中的 HTTP Cookie 的角色。在 Silverlight 中, 您应该查看独立存储为一组可以包含任何类型的数据,包括可执行代码的更大 Cookie。在这种情况下通过,Silverlight 核心 CLR 提供保护。实际上,在 Silverlight 安全模型核心 CLR 将引发异常每当应用程序代码要执行关键的方法。与 HTTP Cookie 不同在 Silverlight 中的独立的存储不链接到网络 I / O,并没有内容传输请求。

在独立存储中的数据隔离应用程序,并且没有其他 Silverlight 应用程序可以访问存储。数据都存储在本地文件系统上但是,因此计算机的管理员会肯定能够访问它。

再次,总体模型与不真正不同怎样使用 HTTP Cookie。管理员可以始终定位并甚至更改 Cookie 的内容。如果很值得上下文中可以添加另一个级别的数据保护来使用加密。

如果您还担心某些下载的可执行代码,可解决您的计算机,您应刷新了解 Silverlight 的安全模型。Brief,Silverlight 核心 CLR 会引发的异常任何时间应用程序代码尝试执行一个关键方法。Silverlight 文章类库 (BCL) 中, 方法和执行操作需要高权限的类均标有特殊的 SecurityCritical 属性。请注意这是 System.IO 命名空间的大小写与大部分内容。

Silverlight 安全确认类可能需要将安全的某些平台调用关键方法。用 SecuritySafeCritical 属性然后标记类的类和方法。是这样使用 System.IO.IsolatedStorage API 中的类 (请参见 图 1 )。有关 Silverlight 安全性的要点是没有一段应用程序代码标记有 SecurityCritical 或 SecuritySafeCritical 属性。此属性保留供程序集经过 Microsoft 数字签名和加载到 Silverlight 安装目录中的内存中的类。

fig01.gif

图 1 在独立存储 API 浏览

正如您所看到甚至在很遗憾 (和不大可能) 的情况下某些恶意的人 penetrates 您的计算机和替换下载 Silverlight 内容,损坏局限于透明方式可执行的常规操作。

独立的存储 API

Silverlight BCL 提供其自己的独立存储 tailor-made 对于 Web 方案的实现。独立的存储提供访问完整本地文件系统的一个子树,并没有方法或属性允许运行代码以找出的文件存储实际位于用户计算机。Silverlight 应用程序不允许使用通过独立存储的绝对文件系统路径。同样,驱动器信息不可用不支持并同样适用于包含如下的省略号的相对路径:

\..\..\myfile.txt 

独立的存储子树的根级位于当前的用户路径下的文件夹中。 是例如 Windows Vista 上位于独立的存储文件夹的用户目录下。

Silverlight 应用程序获得访问特定于应用程序的独立的存储入口点,通过方法调用:

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

静态方法 GetUserStoreForApplication 返回标记使用对独立存储的任何进一步的访问。 进行第一次调用 GetUserStoreForApplication 时, 特定于应用程序的子目录树是,如果不存在,创建已。

Silverlight 独立存储 API 提供类来使用文件和受保护的文件系统夹子树中的目录。 令人高兴的是,您需要了解的类的列表很短,您会发现这些 图 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 包装类来隐藏的一些样本代码需要下载 XAP 程序包并提取程序集和其他资源。 在下载程序类但是,不只是一个帮助器类。 从概念上说,它表示逻辑,您可能希望找出应用程序代码的原因的其他重要的一的段。

springs 想到的第一个原因是 testability。 通过公开 downloader 组件通过接口的功能,可以快速而有效地 mocking 在 downloader 出于测试目的的可能性。 此外,一个接口代表,也许,只是将简单的 downloader 替换更充分利用该工具复杂一个恰好支持包缓存。 图 4 显示了应旨在为设计的体系结构。

fig04.gif

图 4 </a0>-下载程序组件和应用程序的其他部分

fig05.gif

图 5 提取接口

在上个月的源代码下载程序类是一单一段代码。 进行更灵活的设计,让我们提取它的接口。 如 图 5 所示 Visual Studio 将具有上下文菜单,虽然不为格式与商业的重构工具不会提供与出类的接口的提取的一些帮助。

现在,Silverlight 应用程序的核心讨论对 IDownloader 接口,包缓存的所有逻辑必须都转实际 downloader 类的内部:

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

特别,LoadPackage 方法将被重写以合并,将检查存在指定的 XAP 包,独立存储内的它从 Internet 下载否则逻辑。 图 6 显示了下载程序类的代码的一大的部分。 该方法首先尝试从内部缓存流获取 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 方法用于下载当前的 XAML 树中插入的 Silverlight­user 控件。 因为 XAP 包是一个多文件容器,您必须指定哪一个程序集包含用户控件和类名称。 图 6 中的代码只是从包中提取指定的程序集,以将其加载到在当前的 AppDomain,,然后创建指定的包含类的实例。

如果该程序集有某些依赖项? 图 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 到 XML。 源代码包含示例 ManifestHelper 类方法返回的 AssemblyPart 对象 (请参见 图 8 ) 列表的。 值得注意在 Silverlight 2 Beta 版本,可以使用 XamlReader 类来分析清单文件部署对象:

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

图 8 分析使用 LINQ 到 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;
   }
}

在发布版本中部署对象已被转换为一个单独,随后它不能使用以动态加载程序集。 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;
}

这显然简单算法的实现被 hindered 一个值得注意的事实。 在 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 对象如 图 11 所示在 CacheIndex 的构造函数中实例化。

图 11 </a0>-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 字典的内容的排列。 图 12 所示 XAP 词典包含一项每个下载程序包与该的下载时间。 请注意 ApplicationSettings API 还提供您要强制应用程序关闭之前的持久性的 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();
}

将它放在所有一起使用

公共的类的窗帘后面发生的所有更改和为止所讨论的详细信息,在下载程序类。 通过合并 Silverlight 应用程序中的此类,您可以获得多个级别的单个的快照中的缓存。 使用正确编码可以缓存下载应用程序会话持续期间用户控件。 如果您下载的说转到,内容,一个选项卡项可以隐藏并显示选项卡项重复而无需反复下载程序包。

如果您下载通过 WebClient 类 (作为在上个月的专栏中),您将通过在浏览器引擎,并且获取浏览器级别缓存。 用户的计算机上的任何已下载的 XAP 软件包居住,直到用户清除浏览器缓存。 最后,您获得本专栏的下载程序类支持通过独立存储永久性缓存。 这意味着的每当您下载通过 WebClient,XAP 包还保存到本地存储。

在下载程序类但是,还提供了选取 XAP 文件从存储,如果找不到有效的程序包。 注意这能跨应用程序会话。 一次下载程序包,您工作,然后您关闭该应用程序。 恢复时, 包则重新加载从存储如果尚未结束。 如果该程序包过期? 在这种情况下,downloader 通过 WebClient。 但到目前为止 WebClient 可能只是返回同一软件包的以前的浏览器缓存的副本。

这是设计使然。 要绕过浏览器功能的级别缓存,必须原始 HTTP 请求为讨论上个月中禁用它。 您必须缓存在页级别的属性或通过 HTTP 处理程序,可以更精确地而不影响页的其他资源设置过期策略获取软件包。

一些最终版本说明

缓存 XAP 包并不意味着缓存单独的资源,如 XAML 的动画的 DLL 或多媒体内容。 在当前的实现中的资源被提取从 XAP 包每次使用它们。 但是,这是可以进一步提高使用下载程序类的一个方面。 同样,包提供用户控件,但不跟踪用户可以强制其用户界面的更改。 跟踪到 XAML 树的动态更改另一个主题并且工具应得自己的项目。

有两种方法来访问本地用户的计算机上的 Silverlight 专用的文件系统。 在所有代码段为该列中,,我使用方法 GetUserStoreForApplication IsolatedStorageFile 类上。 此方法返回一个令牌访问独立每个应用程序,这意味着所有和程序仅集与应用程序将使用同一个存储在文件系统的一部分。 可以选择存储,并在驻留在同一站点上的所有应用程序之间共享。 在这种情况下您通过方法 GetUserStoreForSite 获得令牌。

注意过本地存储可以是通过 Silverlight 配置对话框 (右键单击 Silverlight 应用程序) 来管理,甚至被完全禁用。 在这种情况下尝试获取该令牌时, 将引发异常。 磁盘配额也适用于基于每个域的本地存储,; 其默认值为 1MB。 请记住这永久性的 Silverlight 缓存的规划时。

将向 Dino 您想询问的问题和提出的意见提出发送至 cutting@Microsoft.com.

Dino Esposito 是 IDesign 和 Microsoft .NET: Architecting Applications for the Enterprise (Microsoft Press 2008) 的 co-author 架构师。 基于在意大利,Dino 是世界各地的业内活动中发表演讲。 您可以加入在他的博客 weblogs.asp。 net / despos.