非同期 ASP.NET カスタムコントロール サンプル

非同期 ASP.NET Page でカスタムコントロールを使った場合のサンプルを作成してみた。

 

ソースコードは http://jsub.sakura.ne.jp/src/DispRss.zip に置いてある。(下に貼ったコードよりも少し拡張してある。VisualStudio 2008 SP1 向け)

カスタムコントロールのソース: ほとんどテンプレートの利用のためのコードで占められているが、メインは OnLoad のところ。

非同期タスクの登録は Page クラスにしか存在していないので、カスタムコントロールからは配置された Page に対して登録するというようになっている。

using System;
using System.ComponentModel;
using System.ServiceModel.Syndication;
using System.Web.UI;
using System.Web.UI.WebControls;

namespace AsyncRss
{
    /// <summary>
    /// InstantiateIn でこのコントロールの中にテンプレートが展開されます
    /// テンプレート中では Container という変数に格納されています
    /// </summary>
    [ToolboxItem(false)] // ツールボックスには表示しない
    public class Rss表示コンテナ : Control, INamingContainer
    {
        private readonly RssFeedAsyncReader _rssreader;

        internal Rss表示コンテナ(RssFeedAsyncReader rssreader)
        {
            _rssreader = rssreader;
        }

        public SyndicationFeed RssFeed取得()
        {
            return _rssreader.RssFeed取得();
        }

        public RssFeedAsyncReader reader
        {
            get { return _rssreader; }
        }
    }

    [ParseChildren(true)] // Templateを持つためには ParseChildren が必要
    [ToolboxData("<{0}:Rss表示 runat=\"server\" />")]
    [ToolboxItem(true)]
    public class Rss表示 : WebControl
    {
        private const string RssUrl = @"http://kamiyn.spaces.live.com/feed.rss";
        private const bool 並列実行するか = true;
        private RssFeedAsyncReader rss1;

        /// <summary>
        /// エラーレスポンス時の表示
        /// </summary>
        [Browsable(false)]
        [TemplateContainer(typeof (WebControlEmptyContainer))]
        [PersistenceMode(PersistenceMode.InnerDefaultProperty), TemplateInstance(TemplateInstance.Single)]
        public ITemplate ErrorTemplate { get; set; }

        /// <summary>
        /// Rss本体表示部分
        /// </summary>
        [Browsable(false)]
        [TemplateContainer(typeof (Rss表示コンテナ))]
        [PersistenceMode(PersistenceMode.InnerDefaultProperty), TemplateInstance(TemplateInstance.Single)]
        public ITemplate RssTemplate { get; set; }

        /// <summary>
        /// タイムアウト時の表示
        /// </summary>
        [Browsable(false)]
        [TemplateContainer(typeof (WebControlEmptyContainer))]
        [PersistenceMode(PersistenceMode.InnerDefaultProperty), TemplateInstance(TemplateInstance.Single)]
        public ITemplate TimeoutTemplate { get; set; }

        protected override void OnLoad(EventArgs e)
        {
            if (!Page.IsAsync) throw new ApplicationException("Asyncページに配置されていません");

            rss1 = new RssFeedAsyncReader(RssUrl);
            var task1 = new PageAsyncTask(rss1.非同期開始タスク, rss1.非同期終了タスク, rss1.タイムアウトタスク, null /* state */, 並列実行するか);
            Page.RegisterAsyncTask(task1);
            Page.PreRenderComplete += Page_PreRenderComplete; // PreRenderComplete はページに対してしか存在しない
        }

        private void Page_PreRenderComplete(object sender, EventArgs e)
        {
            if (rss1.タイムアウト発生したか())
            {
                var container = new WebControlEmptyContainer();
                TimeoutTemplate.InstantiateIn(container);
                Controls.Add(container);
                return;
            }
            if (rss1.エラー発生したか())
            {
                var container = new WebControlEmptyContainer();
                ErrorTemplate.InstantiateIn(container);
                Controls.Add(container);
                return;
            }
            {
                // 正常にRSS取得した場合
                {
                    var container = new Rss表示コンテナ(rss1);
                    RssTemplate.InstantiateIn(container);
                    Controls.Add(container);
                    container.DataBind();
                }
            }
        }
    }
}

支援クラス RssFeedAsyncReader (上記の OnLoad 中で rss1 変数として作成されている。「非同期開始タスク」と「非同期終了タスク」がメイン)

using System;
using System.Collections;
using System.Net;
using System.ServiceModel.Syndication;
using System.Threading;
using System.Xml;

namespace AsyncRss
{
    public class RssFeedAsyncReader
    {
        private readonly string _rssuri = String.Empty;
        private SyndicationFeed _feed;
        private WebRequest _req;
        private bool _エラー発生;
        private bool _タイムアウト発生;
        public DateTime リクエスト開始時刻 = DateTime.MinValue;
        public DateTime リクエスト完了時刻 = DateTime.MinValue;
        public int 開始時スレッドId = -1;
        public int 完了時スレッドId = -1;

        public RssFeedAsyncReader(string rssuri)
        {
            _rssuri = rssuri;
        }

        public IAsyncResult 非同期開始タスク(object sender, EventArgs e, AsyncCallback cb, object state)
        {
            _req = WebRequest.Create(_rssuri);
            _エラー発生 = true;
            _タイムアウト発生 = false;
            リクエスト開始時刻 = DateTime.Now;
            開始時スレッドId =  Thread.CurrentThread.ManagedThreadId;
            return _req.BeginGetResponse(cb, state);
        }

        /// <summary>
        /// IEnumerable が空かどうかをチェックするヘルパー関数
        /// </summary>
        /// <param name="source"></param>
        /// <returns></returns>
        static protected bool IsEmpty(IEnumerable source)
        {
            IEnumerator en = source.GetEnumerator();
            en.Reset();
            if (!en.MoveNext())
            {
                return true; // Reset() の後の MoveNext() に失敗すると中身が空ということになる
            }
            return false;
        }

        public void 非同期終了タスク(IAsyncResult ar)
        {
            using (WebResponse resp = _req.EndGetResponse(ar))
            using (XmlReader xr = XmlReader.Create(resp.GetResponseStream()))
            {
                _feed = SyndicationFeed.Load(xr);
            }
            if (_feed != null && IsEmpty(_feed.Items))
            {
                // アイテムが無かった場合はエラーと判定する
                _エラー発生 = true;
            }
            else
            {
                _エラー発生 = false;
                _タイムアウト発生 = false;
            }
            リクエスト完了時刻 = DateTime.Now;
            完了時スレッドId = Thread.CurrentThread.ManagedThreadId;
        }
        
        public void タイムアウトタスク(IAsyncResult ar)
        {
            _タイムアウト発生 = true;
            _feed = null;
            リクエスト完了時刻 = DateTime.Now;
            完了時スレッドId = Thread.CurrentThread.ManagedThreadId;
        }

        public SyndicationFeed RssFeed取得()
        {
            return _feed;
        }

        public bool タイムアウト発生したか()
        {
            return _タイムアウト発生;
        }

        public bool エラー発生したか()
        {
            return _エラー発生;
        }
    }
}

利用する aspx ページ。

@ Page ディレクティブの Async="true" が重要。カスタムコントロール側で Page.IsAsync でチェックをしているが、その明示的なチェックをしなければ Async="false" で非同期でない状態で動作はしてくれる。

<%@ Page Language="C#" AutoEventWireup="true" Async="true" AsyncTimeout="30" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Rss非同期読込テスト</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <AsyncRss:Rss表示 runat="server" ID="rss1">
            <RssTemplate>
                Rssタイトル:
                <%# Container.RssFeed取得().Title.Text %><br />
            </RssTemplate>
            <TimeoutTemplate>
                RSS読込がタイムアウトしました。</TimeoutTemplate>
            <ErrorTemplate>
                RSS読込時にエラーが発生しました。</ErrorTemplate>
        </AsyncRss:Rss表示>
    </div>
    </form>
</body>
</html>
広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。