ASP.NET MVCでページャの表示

ページング表示(とグリッド表示)の例がMVCContribにも入っていますが、ページャの見せ方はアプリケーション固有のものなので、MVCContribを参考にしてカスタムページャを作ってみるテスト(・∀・)


とりあえず対象とするデータ構造は以下の様な感じで。

// ページング用モデルのインタフェース
public interface IPagination
{
    // 現在ページ
    int Page { get; }

    // ページ数
    int MaxPage { get; }

    // 前ページあり?
    bool HasPreviousPage { get; }

    // 次ページあり?
    bool HasNextPage { get; }
}

実装の例としては、次の様な感じで。

// シンプルなページング用モデルの実装
public class Pagination<T> : IEnumerable<T>, IPagination
{
    private readonly IEnumerable<T> dataSource;

    public int Page { get; private set; }

    public int MaxPage { get; private set; }

    public bool HasPreviousPage
    {
        get { return Page > 1; }
    }

    public bool HasNextPage
    {
        get { return Page < MaxPage; }
    }

    public Pagination(IEnumerable<T> dataSource, int page, int maxPage)
    {
        this.dataSource = dataSource;
        Page = page;
        MaxPage = maxPage;
    }

    public IEnumerator<T> GetEnumerator()
    {
        return this.dataSource.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

Controllerでは一覧の検索と件数の取得を行い、それからPaginationを作って、ModelとしてViewに渡すカンジで。


っで、次がページャUI用の拡張メソッド

public static class AppHelper
{
    // 拡張メソッド
    public static Pager Pager(this HtmlHelper htmlHelper, IPagination pagination)
    {
        return new Pager( pagination, htmlHelper.ViewContext.HttpContext.Request );
    }
}

htmlの構築自体は次のPagerクラスのToString()で行うので、ここではPagerのインスタンスを作って、htmlの構築に必要なIPaginationとHttpRequestBaseを渡すだけ。

// ページャUI構築クラス
public class Pager
{
    private readonly IPagination pagination;
    private readonly HttpRequestBase request;

    private string prevString = "&lt;前";
    private string nextString = "次&gt;";

    private string pagerCss = "pager";
    private string currentCss = "current";
    private string disableCss = "disable";

    private string parameterName = "page";

    private int showLinks = 10;

    // コンストラクタ
    public Pager(IPagination pagination, HttpRequestBase request)
    {
        this.pagination = pagination;
        this.request = request;
    }

    // Fluentなカンジで設定するためのメソッド類
    public Pager ParameterName(string parameterName)
    {
        this.parameterName = parameterName;
        return this;
    }

...

    public Pager ShowLinks(int showLinks)
    {
        this.showLinks = showLinks;
        return this;
    }

    // タグ構築ベタ書き(´д`;)
    public override string ToString()
    {
        StringBuilder builder = new StringBuilder();

        // 描画範囲
        int start;
        int end;
        if( this.pagination.MaxPage > this.showLinks )
        {
            start = this.pagination.Page - ( this.showLinks / 2 );
            end = this.pagination.Page + ( this.showLinks / 2 );
            if( start < 1 )
            {
                start = 1;
            }
            else if( end > this.pagination.MaxPage )
            {
                start = this.pagination.MaxPage - this.showLinks + 1;
            }

            end = start + this.showLinks - 1;
        }
        else
        {
            start = 1;
            end = this.pagination.MaxPage;
        }

        // 開始
        builder.Append( String.Format( "<div class=\"{0}\">\n", this.pagerCss ) );

        // 先頭
        if( this.pagination.Page > 1 )
        {
            builder.Append( CreatePageLink( this.pagination.Page - 1, this.prevString ) );
            builder.Append( " " );
        }
        else
        {
            builder.Append( String.Format( "<span class=\"{0}\">{1}</span> ", this.disableCss, this.prevString ) );
        }

        // ...
        if( this.pagination.HasPreviousPage )
        {
            builder.Append( "... " );
        }

        // ページ
        for( int i = start; i <= end; i++ )
        {
            if( i != start )
            {
                builder.Append( " " );
            }

            if( i == this.pagination.Page )
            {
                builder.Append( String.Format( "<span class=\"{0}\">{1}</span>", this.currentCss, i ) );
            }
            else
            {
                builder.Append( CreatePageLink( i, i.ToString() ) );
            }
        }

        // ...
        if( end < this.pagination.MaxPage )
        {
            builder.Append( " ..." );
        }

        // 最後
        if( this.pagination.HasNextPage )
        {
            builder.Append( " " );
            builder.Append( CreatePageLink( this.pagination.Page + 1, this.nextString ) );
        }
        else
        {
            builder.Append( String.Format( " <span class=\"{0}\">{1}</span>", this.disableCss, this.nextString ) );
        }

        // 終了
        builder.Append( "\n</div>\n" );

        return builder.ToString();
    }

    // リンク作成
    private string CreatePageLink(int page, string text)
    {
        string queryString = CreateQueryString( this.request.QueryString );
        return string.Format( "<a href=\"{0}?{1}={2}{3}\">{4}</a>", this.request.FilePath, this.parameterName, page, queryString, text );
    }

    // クエリ文字列作成
    private string CreateQueryString(NameValueCollection values)
    {
        StringBuilder builder = new StringBuilder();

        foreach( string key in values.Keys )
        {
            if( key == this.parameterName )
            {
                continue;
            }

            foreach( var value in values.GetValues( key ) )
            {
                builder.AppendFormat( "&amp;{0}={1}", key, value );
            }
        }

        return builder.ToString();
    }
}

先頭と最後へのナビゲーションは省略して、それっぽいHTMLが出力されるような処理を書いてみました(´Д`)


っで、使い方はこげな風(・ω・)

<%= Html.Pager( Model ) %>

パラメータを指定したい場合はこげな風(・ω・)

<%= Html.Pager( Model ).ParameterName( "p" ).ShowLinks( 20 ) %>

ちなにみ出力されるのはこんなカンジのHTML。

<div class="pager">
<span class="disable">&lt;</span> <span class="current">1</span> <a href="/Hoge?page=2">2</a> <a href="/Hoge?page=3">3</a> <a href="/Hoge?page=2">&gt;</a>
</div>

StringBuilderでの構築なら、JavaのタグライブラリやPHP用に作ってたヘルパーなんかから、処理自体はほぼそのまま持ってくることができますね。
タグクラウドとか、レーティングとか(・∀・)
まあ、使わないんだけど。