ASP.NET MVCで作る携帯サイト(2)

ソースに関して最初に言っておくと、絵文字変換について、.NETでのやりかたの検証用に作ってみたものなので、実用性の検証はしていません。
中身についても、割とエエエェェェ(´д`)ェェェエエエな事をしています。


まあ、そこは了承してもらうということで、絵文字変換ライブラリSmart.Web.Mobileのソース&サンプルとその解説です(・∀・)

ソースとサンプル

CodePlexにあげておきました(・∀・)
http://usaxusa.codeplex.com/Release/ProjectReleases.aspx?ReleaseId=37417


っで、まずは使い方と言うことで、サンプルの解説をしてみたいと思います。

ブラウザ定義ファイル

ASP.NETにはブラウザ定義ファイルの仕組みがありますが、Smart.Web.Mobileではそれは使用しません(・ω・)
っというかむしろ邪魔なので、App_Browsersフォルダ下に下記のような内容の定義ファイルを作って、標準設定を無効化しています。

<browser refID="Docomo">
  <capabilities>
    <capability name="preferredRequestEncoding"  value="" />
    <capability name="preferredResponseEncoding" value="" />
  </capabilities>
</browser>

System.Web.UI.Pageクラスは、preferredRequestEncoding、preferredResponseEncodingが設定されている場合にその内容をContentEncodingに設定してくれちゃいます(´д`;)
Smart.Web.Mobileではキャリア毎のエンコードは自前で行うため、この処理が動かないように設定を上書きして無効化しています。
なお、基本方針としてWeb.configのglobalization等の設定は行わず、エンコードの処理はSmart.Web.Mobileに任せることになります。

設定ファイル

Smart.Web.Mobileの設定ファイルとして、App_Data/Mobile下に4つのファイルがあります。
mobile.xmlは全体の定義で、キャリアの判定ルール等が記述してあります。
同様の仕組みはブラウザ定義ファイルにもありますが、そこには依存しない方針なので自前でやっています。(ただし割とおおざっぱ(´д`;))


っで、その他のdocomo.xmlau.xmlsoftbank.xmlはキャリア毎の絵文字の定義と、キャリア間の変換ルールの定義になります。
中身はこんな風になっていて(・ω・)

<?xml version="1.0" encoding="utf-8" ?>
<carrier name="docomo">

  <pictgrams>
    <pictgram no="1"    sjis="F89F" unicode="E63E" name="晴れ" />
    <pictgram no="2"    sjis="F8A0" unicode="E63F" name="曇り" />
    <pictgram no="3"    sjis="F8A1" unicode="E640" name="雨" />
    <pictgram no="4"    sjis="F8A2" unicode="E641" name="雪" />
    <pictgram no="5"    sjis="F8A3" unicode="E642" name="雷" />
    <pictgram no="6"    sjis="F8A4" unicode="E643" name="台風" />
    <pictgram no="7"    sjis="F8A5" unicode="E644" name="霧" />
...
    <pictgram no="1001" sjis="F9B1" unicode="E70C" name="iアプリ" />
    <pictgram no="1002" sjis="F9B2" unicode="E70D" name="iアプリ(枠付き)" />
...
  </pictgrams>

  <converts>
    <convert no="1"    au="44"        softbank="74" />
    <convert no="2"    au="107"       softbank="73" />
    <convert no="3"    au="95"        softbank="75" />
    <convert no="4"    au="191"       softbank="72" />
    <convert no="5"    au="16"        softbank="161" />
    <convert no="6"    au="190"       softbank="467" />
    <convert no="7"    au="305"       softbank="[霧]" />
...
    <convert no="1001" au="[iアプリ]" softbank="[iアプリ]" />
    <convert no="1002" au="[iアプリ]" softbank="[iアプリ]" />
...
  </converts>

</carrier>

pictgrams下が絵文字の定義で、No、Shift-JIS、Unicodeの対応表になっています。
converts下は絵文字の変換ルールで、他のキャリアでどのように表示するかを定義しています。
変換ルールは、値が数値の場合はそのキャリアでの絵文字Noで、数値以外の場合は文字列として出力する内容になります。
また、タグも許可しているので、ここを弄ればタグで装飾した内容に変換することも可能です。
サンプルの設定では、PC用(other)についてはimgタグに変換するようにしてあります。*1

初期化/入力設定

Smart.Web.Mobileを使用するための初期化処理をHttpApplicationで行います。

public class MvcApplication : System.Web.HttpApplication
{
    public MvcApplication()
    {
        this.PreRequestHandlerExecute += OnPreRequestHandlerExecute;
    }

    protected void Application_Start()
    {
        RegisterRoutes( RouteTable.Routes );

        // 初期化
        MobileSettings.Default.Init( Server.MapPath( "App_Data\\Mobile\\mobile.xml" ) );
    }

    private void OnPreRequestHandlerExecute(object sender, System.EventArgs e)
    {
        if( Response.ContentType == "text/html" )
        {
            // コンテキスト初期化
            WebMobileContext.Current.Initialize( Request.UserAgent, MobileSettings.Default );

            // エンコーディング
            Request.ContentEncoding = SitePolicy.GetEncodingForCurrentTarget();
        }
    }
}

初期化処理として、MobileSettingsに設定ファイルを読み込んでおきます。


また、リクエストに対してはUser-Agentによるキャリアの判別と、エンコーディングの設定を行います。
なお、キャリア毎にどの文字コードを使うか、絵文字の出力方法をどうするか等の設定については、アプリケーション固有のSitePolicyクラスにまとめていますが、その説明については後述します(・ω・)

出力設定

レスポンスに対する設定を行うため、下記のような属性を作成しています。

[AttributeUsage( AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false )]
public class MobileResponseAttribute : FilterAttribute, IActionFilter, IResultFilter
{
    public void OnActionExecuting(ActionExecutingContext filterContext)
    {
        // 出力設定
        SitePolicy.SetResponseForCurrentTarget( filterContext.HttpContext.Response );
    }

    public void OnActionExecuted(ActionExecutedContext filterContext) {}

    public void OnResultExecuting(ResultExecutingContext filterContext) {}

    public void OnResultExecuted(ResultExecutedContext filterContext)
    {
        if( filterContext.HttpContext.Response.ContentType == "text/html" )
        {
            SitePolicy.SetContentTypeForCurrentTarget( filterContext.HttpContext.Response );
        }
    }
}

っで、Controllerクラスに対して属性を付与することにより、そのControllerのアクションからの出力をSmart.Web.Mobileで処理するようにしています。

[MobileResponse]
public class MobileController : Controller
{
...
}

詳細はSitePolicyクラスの説明で書きますが、SitePolicy.SetResponseForCurrentTarget()で行っているのはHttpRespinse.ContentEncoding、HttpRespinse.Filterの設定になります。
また、SitePolicy.SetContentTypeForCurrentTargetで行っているのはHttpRespinse.ContentTypeの設定になります。

SitePolicy

サイト固有の携帯対応の設定については、SitePolicyクラスに処理をまとめています。
SitePolicyではキャリア毎の文字コードの設定、絵文字の出力方法の設定、Content-Typeの設定を行っていますが、まずは文字コードの設定について、サンプルでは下記のようにしています。

public static Encoding GetEncodingForCurrentTarget()
{
    Carrier carrier = WebMobileContext.Current.Carrier;

    Encoding encoding = Encoding.UTF8;
    if( WebMobileContext.Current.IsMobile )
    {
        string encode = ( ( ( carrier == Carrier.DoCoMo ) || ( carrier == Carrier.Au ) ) ? "Shift_JIS" : "UTF-8" );
        encoding = WebMobileContext.Current.CarrierSettings.GetMobileEncoding( encode, PictgramTreat.Convert );
    }

    return encoding;
}

DoCoMoauはSjift_JISベース、SoftbankとPCはUTF-8ベースの設定になります。
なお、携帯についてはEncoding.GetEncoding()で取得したEncodingではなく、Smart.Web.Mobileで用意したMobileEncodingを返すようにしています。
MobileEncodingは、絵文字の削除機能と、キャリア規定のマッピングによるShift_JIS-Unicode変換機能を持ったEncodingの実装です。
CarrierSettings.GetMobileEncoding()はそのファクトリです。


次に、出力設定は下記のようになっています。

public static void SetResponseForCurrentTarget(HttpResponseBase response)
{
    Carrier carrier = WebMobileContext.Current.Carrier;

    // レンダリング
    IPictgramRender render = null;
    if( carrier == Carrier.DoCoMo )   { render = new DocomoMixedRender(); }
    if( carrier == Carrier.Au )       { render = new SjisNumberRender(); }
    if( carrier == Carrier.Softbank ) { render = new UnicodeNumberRender(); }

    // エンコーディング
    Encoding encoding = GetEncodingForCurrentTarget();

    // 出力設定
    response.ContentEncoding = new MobileOutputEncoding( Encoding.Unicode, encoding );
    response.Filter =
        new MobileOutputStream( response.Filter,
                                Encoding.Unicode,
                                encoding,
                                WebMobileContext.Current.CarrierSettings )
        {
            Trim = true,
            Effect = (x) => KanaConverter.Convert( x, KanaOption.Narrow ),
            PictgramRender = render,
        };
}

IPictgramRenderは絵文字の出力方法の指定で、この例ではキャリア毎に設定を変える内容になっています。
例えば、Softbank用にはUnicodeNumberRenderクラスを設定していますが、これはUnicode実体参照を使った出力方法になります。


また、絵文字のキャリア間変換及びその他の設定として、MobileOutputEncodingクラスとMobileOutputStreamクラスをHttpResponseに設定しています。
MobileOutputEncodingとMobileOutputStreamはニコイチで動作する仕掛けになっていますが、その辺の話はソースの解説の時に行います。
なお、MobileOutputStreamにはオプションとして、空行削除のTrimプロパティ、かな文字の半角変換等を行うためのEffectプロパティ、IPictgramRenderの指定を行うPictgramRenderプロパティが存在します。


後はContent-Typeの設定ですが、サンプルではそのキャリアに対しても「application/xhtml+xml」を設定するようにしています。

public static void SetContentTypeForCurrentTarget(HttpResponseBase response)
{
    Carrier carrier = WebMobileContext.Current.Carrier;

    if( carrier == Carrier.DoCoMo )   { response.ContentType = "application/xhtml+xml"; }
    if( carrier == Carrier.Au )       { response.ContentType = "application/xhtml+xml"; }
    if( carrier == Carrier.Softbank ) { response.ContentType = "application/xhtml+xml"; }
}

この辺は使用する文字コードとキャリアにあわせて設定します。
なお、Content-Typeのcharserの設定はASP.NETがやってくれるので、自分では記述していません(・ω・)

ビュー

まず、マスターページMobile.masterはこんな風にしています。

<%@ Master Language="C#" Inherits="System.Web.Mvc.ViewMasterPage" %>

<% Carrier carrier = WebMobileContext.Current.Carrier; %>
<% if( carrier == Carrier.DoCoMo ) { %>
<%= Html.Xml() %>
<!DOCTYPE html PUBLIC "-//i-mode group (ja)//DTD XHTML i-XHTML(Locale/Ver.=ja/2.0) 1.0//EN" "i-xhtml_4ja_10.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<% } else if( carrier == Carrier.Au ) { %>
<%= Html.Xml() %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML Basic 1.0//EN" "http://www.w3.org/TR/xhtml-basic/xhtml-basic10.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<% } else if( carrier == Carrier.Softbank ) { %>
<%= Html.Xml() %>
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD WML 2.0//EN" "http://www.wapforum.org/wml20.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xmlns:wml="http://www.wapforum.org/2001/wml">
<% } else  { %>
<%= Html.Xml() %>
<!DOCTYPE html PUBLIC "-//WAPFORUM//DTD XHTML Mobile 1.0//EN" "http://www.wapforum.org/DTD/xhtml-mobile10.dtd" >
<html xmlns="http://www.w3.org/1999/xhtml">
<% } %>

<head runat="server">

<title>
<asp:ContentPlaceHolder ID="TitleContent" runat="server">
</asp:ContentPlaceHolder>
</title>
<%= Html.Charset() %>
</head>

...

ベタな書き方ですが、キャリア毎にDOCTYPE宣言を変えてみたりしています。
なお、Html.Xml()、Html.Charset()は文字コードを考慮した出力用のヘルパーですが、詳細はサンプルの中を見てくださいな。
また、ビューIndex.aspxで以下のように絵文字の出力をしています。

<%= Html.Pictgram( Carrier.DoCoMo, 125 ) %>:DoCoMo 125<br/>
<%= Html.Pictgram( Carrier.Au, 181 ) %>:au 181<br/>
<%= Html.Pictgram( Carrier.Softbank, 230 ) %>:Softbank 230<br/>

Pictgram()メソッドもヘルパーとして用意していますが、中身はSmart.Web.Mobileの機能を使って、Noに対するUnicodeの1文字を出力するだけのものです。
その内容が、MobileOutputStreamを通して絵文字として出力される仕組みとなっています。


っで、長くなったので、Smart.Web.Mobileのソースの解説はまた明日(´д`;)
とりあえず試してみるだけなら、サンプルと今日の内容で大丈夫じゃないかと思いま。

*1:これもちょっと微妙な対応なんだけど…(´Д`)