« [.NET] Wintellect's Power Collections for .NET | トップページ | [.NET] private な拡張メソッドでメソッド チェーン »

2010年1月19日 (火)

[SL3] Silverlight で RSS リーダーを作る

わんくま同盟 東京勉強会 #42 の最後のセッションでマイクロソフト大西さんが、 サクっと RSS リーダーを作ってくれた …と思います。
※ 当日の中継が調子悪くて切れまくりで、 何が起きていたのかほとんど把握できなかったのよ~ (;;)

Silverlight 3/4 で RSS リーダーを作るのは、 とても簡単です。
最大の難関は、 他ドメインのアプリケーションに RSS フィードを公開してくれているサイトを見つけ出すこと、 だったりします。 f(^^;
Yahoo! Pipes が公開してくれていますので、 その中から "Silverlight Aggregate RSS" の RSS フィード を読み出すプログラムを作ってみました。

ソース一式 (VS2010用)
SilverlightAggregateRssReaderProject20100119.zip (471 KB)

ソースの解説の前に、 上に書いた 「他ドメインのアプリケーションに RSS フィードを公開している」 という話を。

Silverlight はインターネット等からダウンロードされて直接ブラウザーで実行されますので、 いろいろと制限が掛かっています。 そのひとつに、 クロス ドメイン アクセスの禁止があります。 このため、 違うドメインに置いた Silverlight のアプリから自由に RSS フィードなどにアクセスすることは出来ません。 詳しくは、 MSDN の 「ドメインの境界を越えてサービスを利用できるようにする」 などを読んでください。

http://pipes.yahooapis.com/ には clientaccesspolicy.xml が置かれていて、 自由にアクセスしていいよ、 という内容が書かれています。

<?xml version="1.0" encoding="utf-8"?>
<access-policy>
  <cross-domain-access>
    <policy>
      <allow-from http-request-headers="*">
        <domain uri="*"/>
      </allow-from>
      <grant-to>
        <resource path="/" include-subpaths="true"/>
      </grant-to>

    </policy>
  </cross-domain-access>
</access-policy>


20100119_sl01 それでは、 コードの中身を。
…っと、 そうそう。 Visual Studio 2010 用のプロジェクトになってます。

Silverlight アプリケーションのプロジェクトに入っている主なコードは、 次の 3つです。

  • MainPage.xaml.cs
  • Downloader.cs
  • RssConverter.cs

その他に LinkFormatter.cs があります。 これは、 データを UI にバインドするところで、 型変換を担当しています。

MainPage.xaml.cs
MainPage.xaml.cs の MainPage クラスでは、 UI の表示と、 処理の流れをコントロールしています。

private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
    this.StartRead();
}

private void button1_Click(object sender, RoutedEventArgs e)
{
    this.StartRead();
}

まず、 UI が表示されたとき (LayoutRoot_Loaded) と、 ボタンがクリックされたときに、 StartRead() メソッドを呼び出しています。 これでダウンロードが非同期に開始されます。

private void StartRead()
{
    (new SilverlightDependent.Downloader()).StartRead(
        RssUri,
        RssDownloadCompletedDelegate);
    this.DisableButton();
}

StartRead() の中では、 Downloader クラスの StartRead() メソッドを呼び出しています。 2つめの引数として、 ダウンロードが完了したときにコールバックしてもらうメソッド RssDownloadCompletedDelegate() を渡しています。

※ わざわざ Downloader クラスを切り分けているのは、 なぜか?
実際にインターネットにアクセスする部分の動作は、 実行環境に依存します。 ブラウザ内、 または、 アウト オブ ブラウザーの実行環境では、 正常にアクセスできます。 それ以外では (たとえば、 Visual Studio のユニット テスト環境など)、 例外が出てしまいます。
このサンプル程度では、 ユニット テストの必要性は大して感じませんけれど、 実際の開発ではきっと必要になるでしょう。 ユニット テストをするときには、 UI 部分と、 Silverlight の実行環境に依存する部分は、 上手くテストできないので除外します。 それを見越して、 ここでは Downloader クラスを別にしてあります。

さて、 ダウンロードが完了すると次のメソッドがコールバックされます。

private void RssDownloadCompletedDelegate(string result, Exception error)
{
    this.EnableButton();

    if (error != null)
    {
        MessageBox.Show(
            error.ToString(),
            "RSS が取得できませんでした",
            MessageBoxButton.OK);

        return;
    }

    SyndicationFeed feed

   = RssConverter.ConvertFromRss20(result);

    this.UpdateUI(feed);
}

エラーが発生していなければ、 ダウンロードしたデータが第1引数の result に入ってきます。 データを RssConverter クラスに渡して、 SyndicationFeed 型のデータに変換してもらいます。 それを UpdateUI() メソッドに渡して、 画面を更新してもらいます。


Downloader.cs
RSS フィードをダウンロードしてくる、 いわば本プログラムの中核部分です。 定型的な HTTP アクセス (GET, POST) は、 WebClient クラスを使うと簡単です。
ただし、 .NET Framework の WebClient クラスは同期アクセスもサポートしていますが、 Silverlight では非同期だけのサポートです。 ちょっとお試しプログラムを書くには、 非同期だけってのは少々面倒。

まずは、 リクエストの開始から。

internal void StartRead(Uri rssUri, DownloadCompletedDelegate completed)
{
    if (completed == null)
        throw new ArgumentNullException(

       "completed",
       "結果を受け取る delegate は必須です。");

    WebClient cli = new WebClient();
    cli.DownloadStringCompleted

    += new DownloadStringCompletedEventHandler(CompletedHandler);
    cli.Encoding = new System.Text.UTF8Encoding();

    cli.DownloadStringAsync(rssUri, completed);
}

WebClient オブジェクトを生成したら、 非同期アクセスが完了したときに呼び出してもらうイベント ハンドラーを登録します。
また、 SL3 では文字エンコーディングも忘れずに指定します。
※ SL4 では、 指定しなければ自動判定してくれるようになると思われます。 ⇒ 「[.NET] フィードバック その後 - WebClient オブジェクトは Encoding を自動認識してほしい

準備が出来たら、 非同期アクセスを開始します。 DownloadStringAsync() メソッドの第2引数に渡したオブジェクトは、 非同期アクセスが完了したときにイベントハンドラーに渡されることになります。

private void CompletedHandler(object sender, DownloadStringCompletedEventArgs e)
{
    string result = null;
    if (e.Error == null)
        result = e.Result;

    DownloadCompletedDelegate downloadCompleted

    = (DownloadCompletedDelegate)e.UserState;
    downloadCompleted.Invoke(result, e.Error);
}

非同期アクセスが完了したときのイベントハンドラーでは、 e.Result にダウンロードした結果が入ってきます。 ただし、 e.Error に例外オブジェクトが入っているときは、 e.Result にアクセスすると例外が出ますので要注意です。
e.UserState には、 非同期アクセス開始時にコードから渡したオブジェクトが入ってきます。 ここでは、 コールバック用の DownloadCompletedDelegate デリゲート オブジェクトですので、 キャストしてから呼び出してあげます。


RssConverter.cs
文字列として受け取った RSS フィードの内容を、 RssConverter クラスで SyndicationFeed オブジェクトに変換します。

internal static SyndicationFeed ConvertFromRss20(string rss)
{
    StringReader stringReader = new StringReader(rss);
    XmlReader xmlReader = XmlReader.Create(stringReader);
    return SyndicationFeed.Load(xmlReader);
}

これはもう SyndicationFeed クラスの Load () メソッドが全部やってくれてるようなものです。
これも、 なぜわざわざ独立させているかというと。 SyndicationFeed.Load() がサポートしているのは RSS 2.0 と Atom 1.0 だけだからです。 まだまだ世の中には RSS 1.0 しか提供していないサイトも多いので、 それに対応するときには RssConverter クラスの中身が増えることでしょう。 また、 この RssConverter クラスはユニット テスト可能です。 UI や実行環境に依存していてユニット テストに向かない部分と、 楽にユニット テストできる部分とは、 テストしやすいように分離しておくべきです。

ちなみに、 RSS 1.0 を読み取るためのライブラリ クラスを作ってくれた人もいます。

Syndicating and Consuming RSS 1.0 (RDF) Feeds in ASP.NET 3.5
March 18, 2009

ただし .NET Framework 用なので、 Silverlight で使うには改造する必要があると思います。

|

« [.NET] Wintellect's Power Collections for .NET | トップページ | [.NET] private な拡張メソッドでメソッド チェーン »

プログラミング」カテゴリの記事

-プログラミング ( わんくま )」カテゴリの記事

-プログラミング ( Silverlight )」カテゴリの記事

コメント

コメントを書く



(ウェブ上には掲載しません)


コメントは記事投稿者が公開するまで表示されません。



トラックバック

この記事のトラックバックURL:
http://app.cocolog-nifty.com/t/trackback/209349/47338315

この記事へのトラックバック一覧です: [SL3] Silverlight で RSS リーダーを作る:

« [.NET] Wintellect's Power Collections for .NET | トップページ | [.NET] private な拡張メソッドでメソッド チェーン »