数据点

联合的数据和在 Silverlight 的独立的存储

John Papa

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

内容

获取已完成
HTTP Web 请求和线程处理
添加一个源
解析源
源的跨域请求
基本的独立的存储
组织独立的存储
wrap-up

Silverlight 是适合构建联合的新闻读取器应用程序。它可以读取 RSS 和 AtomPub 联合的格式,与 Web 服务通信通过 HTTP 请求,并处理跨域策略。一旦收到联合的数据能读取到一个类结构 LINQ,分析,并显示给用户通过基于 XAML 的数据绑定。

在本专栏中,我将演示如何利用这些独特的功能来构建联合的新闻阅读器。我还您展示如何调试 Silverlight 的 Web 服务通信问题以及如何存储本地使用独立的存储的数据。所有示例都在 MSDN 代码下载中可用。

获取已完成

源的联合提供对通过 Web 服务,RSS 和 AtomPub 格式的访问。返回包含源的项目使用 RSS 或 AtomPub 格式的 XML 的一个 URI 可通过访问每个源。示例应用程序提供此列演示读取到 Silverlight 应用程序中使用 Web 服务请求的同步的数据的技术。深入详细信息之前, 可能有用如果我显示应用程序的已完成的版本。然后我将讨论逻辑和应用程序的代码各种其他方面。

图 1显示 Silverlight 应用程序读取这些两个联合的源:

 http://pipes.yahooapis.com/pipes/pipe.run?_id=957­
    d9624940693fb9f9644d7b12fb0e9&_render=rss 
http://pipes.yahooapis.com/pipes/pipe.run?_id=057559bac7aad6640b­
    c17529f3421db0&_render=rss 

图 1 </a0>-SilverlightSyndicationApplication

为收集源的数据,当用户单击添加按钮时发出 Web 请求。在客户端计算机上本地存储订阅源的 URI,并订阅源的服务的标题将显示在上部 data­grid 控件中。

将源的 URI 存储在客户端计算机上,将允许 Silverlight 应用程序获取这些源的源的数据,在应用程序启动时。源的数据收集时, 项放置到对象的列表,并且然后分析使用 LINQ。结果是然后绑定到较低的 data­grid 控件,排序顺序。

HTTP Web 请求和线程处理

使用 Silverlight 应用程序中的联合的源开头能够发出 Web 请求。同时,Web­client 和 HttpWebRequest 类可以使 HTTP Web 请求 Silverlight 应用程序。该过程在第一步决定用于发出 HTTP Web 请求的方法。在大多数的情况下 WebClient 类是所有需要因为它是使用更简单 (它还会调用在底层 HttpWebRequest)。HttpWebRequest 用于请求多个自定义。

此代码段演示了如何通过 WebClient 请求:

//feedUri is of type Uri 
WebClient request = new WebClient();
request.DownloadStringCompleted += AddFeedCompleted;
request.DownloadStringAsync(feedUri);

下面的代码在另一方面,说明如何使用 HttpWebRequest 类似调用:

//feedUri is of type Uri 
WebRequest request = HttpWebRequest.Create(feedUri);
request.BeginGetResponse(new AsyncCallback(ReadCallback), request);

WebClient 类会更易于使用,因为它换行的某些 HttpWeb­request 类的功能。

因为通过 WebClient 和 HttpWebRequest 异步发出 Silverlight 中的所有 HTTP Web 请求,很重要,了解如何处理数据,当调用返回。 HttpWebRequest 将其异步的调用并完成时, 已完成的事件处理程序不能保证将运行在 UI 线程。 如果数据检索,为了显示在用户界面元素中,必须进行调用用户界面元素,使用将控制转移回到 UI 线程上,从后台线程的一个方法。 这可以使用调度程序,或通过 SynchronizationContext 类。 下面的代码说明如何使用 Dispatcher 类的 BeginInvoke 方法的 UI 线程调用:

Deployment.Current.Dispatcher.BeginInvoke(() =>
     {
         MyDataGrid.DataContext = productList;
     });

此代码示例将 productList 变量 (这填充根据从 Web 服务调用返回的数据),并将它设置为一个 UI 元素的 DataContext。 在这种情况下 DataGrid 将立即被绑定到的产品列表。 调度程序不需要,但是,如果通过 WebClient 类进行调用。 只在这种情况下代码可能需将设置该产品列表直接为用户界面元素的 DataContext。

若要使用 SynchronizationContext 类,synchronization­context 类的实例必须创建在 UI 线程已知可用的地方。 构造函数和 Load 事件处理程序是正确的位置创建一个 synchronization­context 的实例。 此代码示例显示在类构造函数中初始化 _syncContext 域:

public Page() {
_syncContext = SynchronizationContext.Current;
}

此代码显示进行 LoadProducts 方法调用中使用其 Post 方法在 SynchronizationContext 实例。 它确保,LoadProducts 方法有权在 UI 线程:

if (_syncContext != null) {
  _syncContext.Post(delegate(object state){ LoadProducts(products); } 
  ,null);
}

除了在易于使用,WebClient 请求始终回来 UI 线程上。 这意味着在 WebClient 请求中的任何结果可以轻松地绑定 UI 元素而无需涉及调度程序 (或,或者,synchronization­context 类而不是调度程序)。 读取联合的数据,WebClient 类不足,将能为此列的示例应用程序。

添加一个源

在示例应用程序中,当用户输入一个源的地址,并单击添加按钮将执行 图 2 所示的代码。 首先代码会尝试从尽可能使用 uri.TryCreate 方法在该地址创建一个 URI。 如果它可以创建一个 URI,本地的变量 feedUri 被返回 URI。 否则为 feedUri 保留为空,并且退出代码。

图 2 中添加一个源

private void btnAdd_Click(object sender, RoutedEventArgs e)
{
    Uri feedUri;
    Uri.TryCreate(txtAddress.Text, UriKind.Absolute, out feedUri);
    if (feedUri == null)
        return;

    LoadFeed(feedUri);
}

public void LoadFeed(Uri feedUri)
{
    WebClient request = new WebClient();
    request.DownloadStringCompleted += AddFeedCompleted;
    request.DownloadStringAsync(feedUri);
}

一旦创建有效的 URI,LoadFeed 方法将执行,发出 HTTP 请求来收集使用 Web­client 类将源的数据。 WebClient 创建,并在事件处理程序分配到 DownloadStringCompleted 事件。 当 DownloadStringAsync 方法执行并可以随时返回其数据时,它需要知道转到何种事件处理程序。 这就是在执行异步事件之前,必须分配事件处理程序 (在本例中 AddFeedCompleted) 的原因。

在请求完成后将执行 AddFeedCompleted 事件处理程序 (参见 图 3 )。 DownloadStringCompleted­EventArgs 参数包含一个结果属性和这两个非常重要检查每个 Web 请求后的错误属性。 e.error 属性是如果在请求期间时没有出错。 e.result 属性包含 Web 请求的结果。 示例应用程序 e.result 将包含表示源的数据的 XML。

图 3 AddFeedCompleted

private void AddFeedCompleted(object sender, 
    DownloadStringCompletedEventArgs e)
{
    if (e.Error != null)
        return;
    string xml = e.Result;
    if (xml.Length == 0)
        return;
    StringReader stringReader = new StringReader(xml);
    XmlReader reader = XmlReader.Create(stringReader);
    SyndicationFeed feed = SyndicationFeed.Load(reader);
    if (_feeds.Where(f => f.Title.Text == feed.Title.Text).ToList().Count > 0)
        return;
    _feeds.Add(feed); // This also saves the feeds to isolated storage
    ReBindAggregatedItems();
    txtAddress.Text = string.Empty;
}

一旦源的数据收集,能读取到 System.ser­vice­model.SyndicationFeed 类使用 SyndicationFeed 类的 Load 方法。 请注意,检索源的数据时,只读的方式使用它、 使用 LINQ to XML 检索订阅源和自定义的对象中加载它可能 SyndicationFeed 比SSCE 更。 SyndicationFeed 具有更多的功能,但如果不是在使用值添加到该 XAP 其他大小不能,SyndicationFeed 将大约 150KB 添加到该 XAP 同时 LINQ to XML 添加关于 40KB。 与其他功能的 SyndicationFeed 您还有一些成本的大小。

SyndicationFeed 作为对象是一个特殊的类,知道如何表示源的数据 (RSS 和 AtomPub)。 它有描述如标题和说明,本身,源属性以及包含一个 IEnumerable <syndicationitem> 的项目属性。 每个 SyndicationItem 类实例表示源的源的项目。 是例如由该 SyndicationFeed 类的实例表示的源,并重其项目集合包含单个张贴内容,从这些源。

一旦 SyndicationFeed 类加载源和其项目中, 图 3 所示,代码将检查是否具有已被收集相同的源。 如果是这样,会立即退出代码。 否则,源被添加到名为 _feeds 在本地 ObservableCollection <Syndication­Feed>。 通过在 ReBindAggregatedItems 方法从已加载的源的所有源的项目然后筛选、 排序,并绑定到较低的 DataGrid。 由于 WebClient 类发出 HTTP Web 请求,AddFeedCompleted 事件处理程序必须访问 UI 线程。 这就是为什么 ReBind­ag­gregatedItems 方法代码可以绑定调度程序的帮助在 DataGrid 的 UI 元素到数据的原因。

解析源

ReBindAggregatedItems 方法在执行时则会将源的数据存储在 SyndicatedFeed 实例的集合和 SyndicatedItem 实例及其相应集合。 LINQ 是适用于查询源的数据,因为它现在是一个对象结构。 数据不需要被加载到 SyndicatedFeed 对象。 而是它未能已被保留以本机 XML 格式 (作为 RSS 或 AtomPub) 并且它可能已被分析使用 XmlReader 或 LINQ to XML。 但是,SyndicatedFeed 类使得更易于管理,LINQ 可以仍能用于查询数据。

显示多个源的源的项目需要的源的项目所有 mashed 一起使用。 图 4 所示的 LINQ 查询演示如何为所有源 (SyndicationFeed 实例) 都获取的所有源项目 (Syndication­Item 实例),并按其发布日期对它们进行排序。

图 4 查询与 LINQ 的源

private void ReBindAggregatedItems()
{
    //Read the feed items and bind them to the lower list
    var query = from f in _feeds
                from i in f.Items
                orderby i.PublishDate descending
                select new SyndicationItemExtra
                        { FeedTitle = f.Title.Text, Item = i };

    var items = query.ToList();
    feedItemsGridLayout.DataContext = items;
}

注意 图 4 中查询返回 SyndicationItemExtra 类的列表。SyndicationItemExtra 类是具有类型为 String FeedTitle 属性和类型 SyndicationItem 的项目属性的自定义类。该应用程序所的 DataGrid 中显示源的项目,并且在 SyndicationItem 类中找到符合大多数的数据。

但是,因为应用程序一起 mashes 多个源中的项目,显示为每个源项目订阅源的标题可以清除每个的源项目是从。源标题不能从在 SyndicationItem 类访问,因此应用程序使用一个名为它将存储在 SyndicationItem 和订阅源的标题的 SyndicationItemExtra 的自定义类。

源的项目然后绑定到网格面板 feedItemsGridLayout,Silverlight 应用程序中。网格面板包含 DataGrid 以及显示源项目的信息的数据绑定操作中涉及的其他用户界面元素 (如 TextBlock 中显示的项目数)。

源的跨域请求

要收集源的请求是 HTTP Web 请求的通常情况下发出请求到不同的 Web 域。从与之外的其他域通信的承载 Silverlight 应用程序的 Silverlight 任何 Web 请求必须符合远程域的跨域策略。图 5 中图演示了这种情况。

图 5 跨域调用一个源

有关跨域策略的详细信息,请参阅我2008 年 9 数据点列. 在该列中我将讨论文件格式和策略的工作方式。

一个 HTTP Web 请求进行跨域时, Silverlight 将 preempts 请求通过第一个从远程 Web 服务器请求跨域策略文件。Silverlight 首先查找 clientaccesspolicy.xml 文件 (Silverlight 跨域策略文件),如果它未找到它然后查找 crossdomain.xml 文件 (闪存的跨域策略文件)。如果找到没有文件,请求失败,并且引发一个错误。可以在 DownloadStringCompleted 事件处理程序中捕获并显示给在的用户,如果需要此错误。

是例如如果 URI http://johnpapa。net/feed/default.aspx 输入示例应用程序,其中一个跨域策略文件在 johnpapa 上将首先查找 Silverlight。net Web 服务器的根目录。如果不将文件是找到,然后错误返回到应用程序,此时应用程序可以通知用户如果需要。图 6 显示了插件,它正在从浏览器中跟踪所有请求的在 FireBug。它显示查找跨域策略文件不查找这些,和返回而不实际进行请求该 RSS 源浏览器。

图 6 调试跨域源调用

fireBug 是一个很好的工具,用于监视 Firefox 中的 HTTP 请求,,Web 开发帮助器是一个很好的工具,当使用 Internet Explorer。另一种方法是 Fiddler2,可以监视您的计算机上的所有通信的独立应用程序。

此问题的一个解决方案是请求订阅源的 Web 管理员,clientaccesspolicy.xml 文件放在 Web 服务器的根目录中。这可能不是实际,因为最有可能您不控制远程 Web 服务器和您知道谁。另一个选择是查看源是否使用的中间服务 (如 Yahoo 管道。是例如主新闻复制在 johnpapa。可以通过使用 URI http://pipes.yahooapis.com/pipes/pipe.run?\_id=057559bac7aad6640bc17529f­3421db0&\_render=r Yahoo 管道检索网络。因为跨域策略文件位于允许打开访问的 http://pipes.yahooapis.com/clientaccesspolicy.xml 这将是很好的替代选项。

一个第三个选项是使用一个服务 (如 Popfly 或 FeedBurner 聚合有效地将其中继通过也有一个打开的跨域策略的服务在这些源。最后,一个第四个选项是编写您自己自定义的 Web 服务的收集源,然后将其中继到 Silverlight 应用程序。使用如 Popfly 或 Yahoo 的管道的服务提供了最简单的解决方案。

基本的独立的存储

示例应用程序允许用户添加多个源和查看所有项目的每个这些源。如果用户输入 10 个源,并决定他需要关闭该应用程序和回更高版本读取,他可能会希望将源存储的应用程序。否则,他就必须输入每个源的 URI,每次打开应用程序时。由于这些源是特定于某个用户,则它们可存储使用一些标记标识输入它们的用户在服务器上或在用户计算机上。

Silverlight 允许将存储到用户的计算机使用 System.IO.IsolatedStorage 命名空间中的类的受保护区域的数据。Silverlight 独立存储就像 steroids 的 Cookie: 它允许您存储简单的标量值或甚至存储在客户端计算机上的序列化的复杂对象关系图。将保存到独立存储在最简单方法是创建 ApplicationSettings 条目,并在其中,容纳您的数据,如下所示:

private void SaveFeedsToStorage_UsingSettings()
{
    string data = GetFeedsFromStorage_UsingSettings() + FEED_DELIMITER + 
        txtAddress.Text;
    if (IsolatedStorageSettings.ApplicationSettings.Contains(FEED_DATA))
        IsolatedStorageSettings.ApplicationSettings[FEED_DATA] = data;
    else
        IsolatedStorageSettings.ApplicationSettings.Add(FEED_DATA, data);
}

<syndicationfeed>这可以调用每次一个 SyndicationFeed 是添加或从中调用 _feeds Ob­serv­ableCollection < SyndicationFeed > 字段实例。 由于在 ObservableCollection 公开一个 CollectionChanged 事件,一个处理程序可以分配给执行该的保存,如下所示的事件中:

_feeds.CollectionChanged += ((sender, e) => { 
                               SaveFeedsToStorage_UsingSettings(); });

执行 SaveFeedsToStorage_UsingSettings 方法时, 它首先会调用该 GetFeedsFromStorage_UsingSettings 为方法获取所有源从独立存储的地址,并将其置于由特殊字符分隔的一个字符串中。

应用程序首次启动时 LoadFeedsFromStorage_UsingSettings 方法将从独立存储检索源:

private void LoadFeedsFromStorage_UsingSettings()
{
    string data = LoadFeedsFromStorage_UsingSettings();
    string[] feedList = data.Split(new string[1] { FEED_DELIMITER }, 
      StringSplitOptions.RemoveEmptyEntries);
    foreach (var address in feedList)
        LoadFeed(new Uri(address));
}

该代码首先从独立存储中读取每个源的 URI 地址的列表。 然后它会遍历该地址,并加载源一次使用 LoadFeed 方法每个人。

组织独立的存储

此功能允许记住用户的源的地址,并在用户运行该应用程序时加载这些应用程序。 装箱到一个带分隔符的字符串的 URI 地址很简单而明确既大量。 是例如如果您希望存储不止一个标量值,这将复杂使用此技术。

独立存储中存储数据的另一个方法是使用该的 isolated­StorageFile 和存储包括更复杂的数据结构的使序列化每个用户的对象的 IsolatedStorageFileStream 类。 数据可以甚至分割成不同的文件和独立存储中的文件夹中。 这是适用于组织将保存在独立存储的数据。 是例如所有的静态列表的数据,未能将创建一个文件夹,并且可以创建单独的文件为每个列表。 因此独立存储中文件夹中,文件可能存在的另一个用于性别的名称前缀和另一种为美国 说明。

示例应用程序可以包含的 URI 地址列表的独立存储中创建文件。 该数据必须首先是序列化,然后发送到该文件独立存储中 (如 图 7 中所示)。 首先,使用 GetUserStoreForApplication 方法创建当前用户的 IsolatedStorageFile 类的实例。 然后使应用程序可以写入 URI 地址,要创建文件流。 数据将序列化并写入 isolated­StorageFileStream 实例。 为此应用程序示例序列化一个的 String 值值,但独立存储可以被写入任何可序列化对象。

图 7 保存序列化数据的独立的存储文件

private void SaveFeedsToStorage_UsingFile() {
    using (var isoStore = IsolatedStorageFile.GetUserStoreForApplication())
{
        List<string> data = GetFeedsFromStorage_UsingFile();
        if (data == null)
            if (txtAddress.Text.Length == 0)
                return;
            else
                data = new List<string>();
         using (var isoStoreFileStream =
                new IsolatedStorageFileStream(FEED_FILENAME,
                   FileMode.Create, isoStore)) {
            data.Add(txtAddress.Text);
            byte[] bytes = Serialize(data);
            isoStoreFileStream.Write(bytes, 0, bytes.Length);
        }
    }
}

从独立存储中读取文件出的序列化的数据有点多涉及与前面的示例。 图 8 显示的第一个必须获取该的用户的 IsolatedStorageFile 类的实例,然后请检查该文件是否存在您读取它之前。 如果该文件存在,将通过流类型 IsolatedStorageFileStream 允许读取数据的读访问打开文件。 数据是从流中读取,整理,然后反序列化以便它可以使用加载联合的源。

图 8 阅读系列从独立的存储文件的数据

private List<string> GetFeedsFromStorage_UsingFile() {
    byte[] feedBytes;
    var ms = new MemoryStream();
    using (var isoStore = 
      solatedStorageFile.GetUserStoreForApplication())
    {
        if (!isoStore.FileExists(FEED_FILENAME)) return null;
        using (var stream = isoStore.OpenFile(FEED_FILENAME, 
          FileMode.Open, FileAccess.Read))  {
            while (true) {
                byte[] tempBytes = new byte[1024];
                int read = stream.Read(tempBytes, 0, tempBytes.Length);
                if (read <= 0) {
                    //feedBytes = ms.ToArray();
                    break;
                }
                ms.Write(tempBytes, 0, read);
            }
        }
        feedBytes = ms.ToArray();
        List<string> feedList = Deserialize(typeof(List<string>), 
            feedBytes) as List<string>;
        return feedList;
    }
}

private void LoadFeedsFromStorage_UsingFile() {
    var feedList = GetFeedsFromStorage_UsingFile();
    foreach (var address in feedList) {
        Uri feedUri;
        Uri.TryCreate(address, UriKind.Absolute, out feedUri);
        if (feedUri != null)
            LoadFeed(feedUri);
    }
}

用于简单的数据结构使用序列化对象,独立存储中的文件可能不需要。 但是,独立的存储用于本地存储多种类型时, 它可以帮助组织数据,并提供读取和向其中写入的轻松访问。 应明智地使用独立的存储来存储应本地缓存的数据。

此外请注意用户可以最终清除存储在任何时候因为它们具有完全控制它们的设置。 这意味着存储在独立存储中的数据不应认为有保证的持久存储。 另一个很好的示例存储美国的列表 指出独立存储中因此 Web 请求 (和数据库命中) 不必进行每次一个组合框需要填充的美国列表 说明。

wrap-up

示例应用程序说明 RSS 和 AtomPub 源加载到 Silverlight 应用程序是多么容易。 Silverlight 可以使 Web 请求、 接受其结果、 处理跨域策略调用、 源的数据加载到 SyndicationFeed 类、 LINQ 查询它们、 将它们绑定到用户界面元素和在独立存储中存储源的数据。

上个月和本月,Hanu Kommalapati 包括构建其文章中可以读取有关的 Silverlight 业务线应用程序" Silverlight: 生成行处业务企业应用程序使用 Silverlight,第 1 部分"和" Silverlight: 生成使用 Silverlight,Part 2 的行处业务企业应用程序."

将您的问题和意见发送 John 到 mmdata@Microsoft.com.

John Papa ( johnpapa。 net) 是一个高级顾问 ASPSOFT并在棒球风扇花费夏天整夜的利用在大多数时光都其家人。 一个 C# MVP 和 INETA 发言人,John,已编写多个联机且现在工作他最新上, 名为 Data-Driven Services with Silverlight 2。 他经常讲述在 DevConnections 和 VSLive 的会议。