連載
» 2013年11月18日 12時22分 公開

連載:Windowsストア・アプリ開発入門:第6回 データを取得して表示する (2/5)

[山本康彦(http://www.bluewatersoft.jp/),BluewaterSoft]

ロジックを実装する

 それでは順番に実装していこう*4。プロジェクトに「Logic」というフォルダを新しく作る。そこに新しいクラスとして、「FeedDownloader.cs」/「FeedProcessor.cs」/「DataLoader.cs」の3つのファイルを作る。そうしたら、それぞれに以下の実装を行っていく。

*4当初公開していた第4回と第5回のソース・コードには不具合がある。11月14日の夜に修正したソース・コードに差し替えさせていただいたので、ダウンロードしたソース・コードをベースに実装を進める場合には、最新版をダウンロードし直してくださるようお願いする。


○FeedDownloaderクラスの実装

 WebサービスからRSSフィードを得るには、自分でHTTPのレスポンスを取得してXMLのパースをしてもよいが、それは面倒だ。代わってSyndicationClientクラス(Windows.Web.Syndication名前空間)を利用できると楽ができる(RSSフィードのフォーマットや文字コードによっては使えないこともある)。SyndicationClientクラスのRetrieveFeedAsyncメソッドを使うと、RSSフィードの内容を持つSyndicationFeedクラス(Windows.Web.Syndication名前空間)のオブジェクトが得られる。SyndicationFeedオブジェクトは、Titleプロパティ(=RSSフィードのタイトル)などを公開しているので、生のRSSフィード(=XMLで書かれている)のノードをたどるコードを書かなくてよいのだ。

 引数にUriオブジェクトを受け取り、RSSフィードをダウンロードしてSyndicationFeedオブジェクトを返すGetFeedAsyncメソッドは、次のコードのように書ける。RetrieveFeedAsyncメソッドは非同期なので、その前にawaitキーワード、メソッドのシグネチャにasyncキーワードが、それぞれ必要だ。

using System;
using System.Threading.Tasks;

namespace AtmarkItReader.Logic
{
  internal class FeedDownloder
  {
    internal static async Task<Windows.Web.Syndication.SyndicationFeed> GetFeedAsync(Uri feedUri)
    {
      try
      {
        // SyndicationClientオブジェクトを作り、
        var client = new Windows.Web.Syndication.SyndicationClient();
        // Uriを指定してダウンロードさせて、その結果のSyndicationFeedオブジェクトを返す
        return await client.RetrieveFeedAsync(feedUri); //
      }
      catch (Exception ex)
      {
        // 発生する例外は汎用的なException例外なので、HttpRequestException例外でラップする
        throw new System.Net.Http.HttpRequestException(ex.Message, ex);
      }
    }
  }
}

FeedDownloaderクラス(C#)
このメソッドのシグネチャにはasyncキーワードがついており、メソッドの実行途中でリターンする。コメントに「」印を付けた行でRetrieveFeedAsyncメソッドの非同期実行が始まった時点で、Task<SyndicationFeed>オブジェクトを返すのだ。その後、RetrieveFeedAsyncメソッドの処理が終わると残りの処理が実行され(といっても、このコードではreturnステートメントだけであるが)、メソッドを抜けるときに、先に返したTask<SyndicationFeed>オブジェクトの中に結果が格納される。メソッドのシグネチャで返す型がTask<SyndicationFeed>クラスになっているのに、returnステートメントで返すのがSyndicationFeedオブジェクトであるのは、そういう仕組みによるものだ。

 上のコードで、RetrieveFeedAsyncメソッド実行中にエラーが起きると、Exception例外が発生する。そのままでは他の場所で発生した例外と区別が付かなくなるので、いったんキャッチしてHttpRequestException例外(System.Net.Http名前空間)で包み直してから、あらためて例外をスローし直している(前述の「ロジックの各メソッドのスペック」表を参照)。

 また、このクラスもメソッドも、同じプロジェクト内から呼び出すだけなので、いずれもinternalとした。

○FeedProcessorクラスの実装

 FeedProcessorクラスのAddメソッドは、引数にSyndicationFeedオブジェクトと、中身が空のFeedsDataオブジェクトを受け取る。その処理は、まずFeedオブジェクトを作る。次いで、SyndicationFeedオブジェクトのItemsプロパティ(=記事ごとのデータのコレクション)に含まれる記事データからFeedItemオブジェクトを作っては、Feedオブジェクトに追加していく(次のコード)。

namespace AtmarkItReader.Logic
{
  internal class FeedProcessor
  {
    internal static AtmarkItReader.DataModel.FeedsData Add(
                        Windows.Web.Syndication.SyndicationFeed syndicationFeed,
                        DataModel.FeedsData feedsData)
    {
      // 新しくFeedオブジェクトを作り、FeedsDataオブジェクトに追加する
      var newFeed = new DataModel.Feed(syndicationFeed.Title.Text);
      feedsData.Feeds.Add(newFeed);

      // フィードの各記事(=Itemsプロパティに格納されているSyndicationItemオブジェクト)
      // の内容からFeedItemオブジェクトを生成し、それをFeedオブジェクトに追加していく
      foreach (var syndicationItem in syndicationFeed.Items)
      {
        newFeed.Items.Add(
          new DataModel.FeedItem(
            syndicationItem.Title.Text, // 記事タイトル
            syndicationItem.Links[0].Uri.ToString(), // 記事のURL
            syndicationItem.PublishedDate.ToString("yyyy/MM/dd HH:mm:ss") // 記事の発行日時
            )
          );
      }
      return feedsData;
    }
  }
}

FeedProcessorクラス(C#)
SyndicationFeedオブジェクトのプロパティは、ほとんどが独自の型になっている。例えば、Titleプロパティは、文字列型ではなくSyndicationTextクラスになっているので、文字列を得るためにそのTextプロパティを参照しなければならない。
なお、今回の使用目的からは、AddメソッドがFeedsDataオブジェクトを返す必要はない。これは将来、メソッド・チェーンで使うことを想定しているためだ。

 このクラスとメソッドも、やはり同じプロジェクト内から呼び出すだけなので、いずれもinternalとした。

○DataLoaderクラスの実装

 DataLoaderクラスは、RSSフィードをダウンロードするWebサービスのURLを知っていないと、FeedDownloaderクラスを呼び出せない。今回は、クラスの静的メンバ「URLs」として固定的に持つことにしよう。

 スペックのところで決めたLoadStartメソッドは返り値を持たないが、それでは非同期処理の完了タイミングが分からないのでテストがやりにくい。そこで、Task<DataModel.FeedsData>オブジェクトを返すLoadAsyncメソッドを別に作り、実装の本体はそちらに置こう。

 実装の本体は、WebサービスのURLごとに、Uriオブジェクトを生成してFeedDownloder.GetFeedAsyncメソッドを呼び出し、その結果をFeedProcessor.Addメソッドに渡していくだけだ。ただし、最後に空のFeedオブジェクトを作って「お気に入り」のデータとして追加する。

 以上を実装するコードは次のようになる。

using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace AtmarkItReader.Logic
{
  public class DataLoader
  {
    static readonly string[] URLs = new []
    {
      "http://rss.rssad.jp/rss/itmatmarkit/fdotnet/rss.xml", // @IT Insider.NETフォーラム
      "http://rss.rssad.jp/rss/itmatmarkit/news/rss.xml", // @IT News 最新記事一覧
    };

    public static async void LoadStart(DataModel.FeedsData feedsData)
    {
      await LoadAsync(feedsData);
    }

    internal static async Task<DataModel.FeedsData> LoadAsync(DataModel.FeedsData feedsData)
    {
      // RSSフィードを取得し、FeedsDataオブジェクトに追加する
      for (int i = 0; i < URLs.Length; i++)
      {
        var uri = new Uri(URLs[i]);
        var syndicationFeed
          = await FeedDownloder.GetFeedAsync(uri);
        FeedProcessor.Add(syndicationFeed, feedsData);
      }

      // 最後に、空の「お気に入り」を追加する
      AddEmptyFavorite(feedsData);

      return feedsData;
    }

    private static void AddEmptyFavorite(DataModel.FeedsData feedsData)
    {
      feedsData.Feeds.Add(new DataModel.Feed("お気に入り"));
    }
  }
}

DataLoaderクラス(C#)

 このクラスとメソッドも、やはり同じプロジェクト内から呼び出すだけなのでinternalでよいのだが、外部から使ってよいことを強調する意味でクラスとLoadStartメソッドはpublicとした。

Copyright© Digital Advantage Corp. All Rights Reserved.

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。