Razor テンプレートを使用した HTML ビューの構築

モバイル開発の世界では、"ハイブリッド アプリ" という用語は通常、その画面の一部 (またはすべて) を、ホストされた Web ビューアー コントロール内で HTML ページとして表示するアプリケーションを指します。

完全に HTML と JavaScript でモバイル アプリを構築できる開発環境がいくつかありますが、それらのアプリでは、複雑な処理や UI 効果を実現しようとするとパフォーマンスの問題が発生する可能性があり、アクセスできるプラットフォーム機能にも制限があります。

Xamarin では、特に Razor HTML テンプレート エンジンを使用する場合に、両方の長所が提供されます。 Xamarin を使用すると、JavaScript と CSS を使用したクロスプラットフォーム テンプレートによる HTML ビューを柔軟に構築できますが、基になるプラットフォーム API への完全なアクセスと C# を使用した高速処理もできます。

このドキュメントでは、モバイル プラットフォーム間で使用できる HTML+JavaScript+CSS ビューを Xamarin を使って構築するための、Razor テンプレート エンジンの使用方法について説明します。

プログラムによる Web ビューの使用

Razor について確認する前に、このセクションでは、Web ビューを使用して HTML コンテンツ (特にアプリ内で生成される HTML コンテンツ) を直接表示する方法について説明します。

Xamarin では、iOS と Android の両方で基になるプラットフォームの API に完全にアクセスできるため、C# を使用して HTML を簡単に作成して表示できます。 各プラットフォームの基本的な構文を次に示します。

iOS

Xamarin.iOS の UIWebView コントロールで HTML を表示する場合も、必要なコードは数行のみです。

var webView = new UIWebView (View.Bounds);
View.AddSubview(webView);
string contentDirectoryPath = Path.Combine (NSBundle.MainBundle.BundlePath, "Content/");
var html = "<html><h1>Hello</h1><p>World</p></html>";
webView.LoadHtmlString(html, NSBundle.MainBundle.BundleUrl);

UIWebView コントロールの使用方法の詳細については、iOS UIWebView のレシピを参照してください。

Android

Xamarin.Android を使用して WebView コントロールで HTML を表示する場合、わずか数行のコードでそれを行うことができます。

// webView is declared in an AXML layout file
var webView = FindViewById<WebView> (Resource.Id.webView);

// enable JavaScript execution in your html view so you can provide "alerts" and other js
webView.SetWebChromeClient(new WebChromeClient());

var html = "<html><h1>Hello</h1><p>World</p></html>";
webView.LoadDataWithBaseURL("file:///android_asset/", html, "text/html", "UTF-8", null);

WebView コントロールの使用方法の詳細については、Android WebView のレシピを参照してください。

ベース ディレクトリの指定

どちらのプラットフォームにも、HTML ページのベース ディレクトリを指定するパラメーターがあります。 これは、画像や CSS ファイルなどのリソースへの相対参照を解決するために使用される、デバイスのファイル システム上の場所です。 たとえば、次のようなタグは

<link rel="stylesheet" href="style.css" />
<img src="monkey.jpg" />
<script type="text/javascript" src="jscript.js">

これらのファイルを参照します: style.cssmonkey.jpgjscript.js。 ベース ディレクトリ設定は、これらのファイルをページに読み込むことができるように、これらのファイルが配置されている場所を Web ビューに指示します。

iOS

テンプレートの出力は、次の C# コードを使用して iOS でレンダリングされます。

webView.LoadHtmlString (page, NSBundle.MainBundle.BundleUrl);

ベース ディレクトリは、アプリケーションがインストールされているディレクトリを参照する NSBundle.MainBundle.BundleUrl として指定されます。 Resources フォルダー内のすべてのファイルが、この場所にコピーされます。たとえば、次に示す style.css ファイルなどです。

iPhoneHybrid solution

すべての静的コンテンツ ファイルのビルド アクションは BundleResource とする必要があります。

iOS project build action: BundleResource

Android

Android では、html 文字列が Web ビューに表示されるときにも、ベース ディレクトリをパラメーターとして渡す必要があります。

webView.LoadDataWithBaseURL("file:///android_asset/", page, "text/html", "UTF-8", null);

特殊な文字列 file:///android_asset/ は、アプリ内の Android Assets フォルダーを参照します。ここでは、style.css ファイルが含まれています。

AndroidHybrid solution

すべての静的コンテンツ ファイルのビルド アクションは、AndroidAsset とする必要があります。

Android project build action: AndroidAsset

HTML と JavaScript からの C# の呼び出し

HTML ページが Web ビューに読み込まれると、ページがサーバーから読み込まれた場合と同様に、リンクとフォームが処理されます。 つまり、ユーザーがリンクをクリックした場合やフォームを送信した場合は、Web ビューで、指定されたターゲットへの移動が試みられます。

外部サーバー (google.com など) へのリンクの場合、Web ビューで外部 Web サイトの読み込みが試みられます (インターネットに接続されていることが前提)。

<a href="http://google.com/">Google</a>

相対リンクの場合、Web ビューでベース ディレクトリからそのコンテンツの読み込みが試みられます。 当然ながら、コンテンツはデバイス上のアプリに保存されるため、これが機能するためにネットワーク接続は必要ありません。

<a href="somepage.html">Local content</a>

フォーム アクションも同じルールに従います。

<form method="get" action="http://google.com/"></form>
<form method="get" action="somepage.html"></form>

クライアント上で Web サーバーをホストすることにはなりません。ただし、現在のレスポンシブ デザイン パターンで採用されているのと同じサーバー通信手法を使用して、HTTP GET 経由でサービスを呼び出し、JavaScript を出力して (または Web ビューで既にホストされている JavaScript を呼び出して) 応答を非同期的に処理することができます。 これにより、HTML から C# コードにデータを簡単に渡して処理し、結果を HTML ページに戻って表示することができます。

iOS と Android の両方に、アプリケーション コードがこれらのナビゲーション イベントをインターセプトして、アプリ コードが (必要な場合に) 応答できるようにするメカニズムが用意されています。 この機能は、ハイブリッド アプリを構築するためになくてはならないものです。なぜなら、それによってネイティブ コードが Web ビューとやり取りできるようになるためです。

iOS

アプリケーション コードでナビゲーション要求 (リンクのクリックなど) を処理できるように、iOS の Web ビューの ShouldStartLoad イベントをオーバーライドできます。 メソッド パラメーターで、すべての情報を提供します

bool HandleShouldStartLoad (UIWebView webView, NSUrlRequest request, UIWebViewNavigationType navigationType) {
    // return true if handled in code
    // return false to let the web view follow the link
}

次に、イベント ハンドラーを割り当てます。

webView.ShouldStartLoad += HandleShouldStartLoad;

Android

Android では単に、WebViewClient をサブクラス化し、ナビゲーション要求に応答するコードを実装します。

class HybridWebViewClient : WebViewClient {
    public override bool ShouldOverrideUrlLoading (WebView webView, IWebResourceRequest request) {
        // return true if handled in code
        // return false to let the web view follow the link
    }
}

次に、Web ビューにクライアントを設定します。

webView.SetWebViewClient (new HybridWebViewClient ());

C# からの JavaScript の呼び出し

C# コードを使用して、新しい HTML ページを読み込むように Web ビューに指示するだけでなく、現在表示されているページ内で JavaScript を実行することもできます。 C# 文字列を使用して、JavaScript コード ブロック全体を作成して実行することも、script タグを使用して、ページで既に使用可能な JavaScript へのメソッド呼び出しを作成することもできます。

Android

実行する JavaScript コードを作成し、その先頭に "javascript:" を付けて、その文字列を読み込むよう Web ビューに指示します。

var js = "alert('test');";
webView.LoadUrl ("javascript:" + js);

iOS

iOS の Web ビューには、JavaScript を呼び出すメソッドが用意されています。

var js = "alert('test');";
webView.EvaluateJavascript (js);

まとめ

このセクションでは、Android と iOS の両方で Xamarin を使用してハイブリッド アプリケーションを構築できる、Web ビュー コントロールの機能について説明しました。

  • コードで生成された文字列から HTML を読み込む機能。
  • ローカル ファイル (CSS、JavaScript、画像、またはその他の HTML ファイル) を参照する機能。
  • C# コードでナビゲーション要求をインターセプトする機能。
  • C# コードから JavaScript を呼び出す機能。

次のセクションでは、ハイブリッド アプリで使用する HTML を簡単に作成できる Razor について説明します。

Razor とは

Razor は ASP.NET MVC で導入されたテンプレート エンジンで、もともとはサーバー上で実行され、Web ブラウザーに提供される HTML を生成するためのものでした。

Razor テンプレート エンジンは、標準の HTML 構文を C# で拡張して、レイアウトを表現し、CSS スタイルシートと JavaScript を簡単に組み込むことができるようにしています。 テンプレートで Model クラスを参照できます。これは、任意のカスタム型にすることができ、そのプロパティにテンプレートから直接アクセスできます。 その主な利点の 1 つは、HTML と C# 構文を簡単に混在できることです。

Razor テンプレートはサーバー側での使用に限定されるものではなく、Xamarin アプリに組み込むこともできます。 Razor テンプレートを、Web ビューをプログラムで操作する機能と共に使用すると、高度なクロスプラットフォームのハイブリッド アプリケーションを Xamarin で構築できます。

Razor テンプレートの基本

Razor テンプレート ファイルは、ファイル拡張子が .cshtml です。 [新しいファイル] ダイアログの [テキスト テンプレート] セクションから Xamarin プロジェクトに追加できます。

New File - Razor Template

シンプルな Razor テンプレート (RazorView.cshtml) を次に示します。

@model string
<html>
    <body>
    <h1>@Model</h1>
    </body>
</html>

通常の HTML ファイルとの次のような違いに注意してください。

  • @ 記号は Razor テンプレートにおいて特別な意味を持ち、その後に続く式が評価される対象の C# であることを示します。
  • @model ディレクティブは常に、Razor テンプレート ファイルの最初の行として記述されます。
  • @model ディレクティブの後に続けて型を指定する必要があります。 この例では単純な文字列をテンプレートに渡していますが、これは任意のカスタム クラスとすることができます。
  • @Model をテンプレート全体で参照すると、テンプレートの生成時にテンプレートに渡されるオブジェクトへの参照が提供されます (この例では文字列になります)。
  • IDE では、テンプレートの部分クラス (拡張子が .cshtml のファイル) が自動的に生成されます。 このコードは表示できますが、編集しないでください。 RazorView.cshtml 部分クラスには、.cshtml テンプレート ファイル名と一致する RazorView という名前が付けられています。 これは、C# コードでテンプレートを参照するために使用される名前です。
  • @using ステートメントを Razor テンプレートの先頭に挿入して、追加の名前空間を含めることもできます。

最終的な HTML 出力は、次の C# コードで生成できます。 Model を文字列 "Hello World" に指定し、それがレンダリングされたテンプレート出力に組み込まれることに注意してください。

var template = new RazorView () { Model = "Hello World" };
var page = template.GenerateString ();

iOS Simulator と Android Emulator の Web ビューに表示される出力を次に示します。

Hello World

その他の Razor 構文

このセクションでは、Razor の使用を開始するのに役立つ基本的な構文を紹介します。 このセクションの例では、次のクラスにデータを設定し、Razor を使用して表示します。

public class Monkey {
    public string Name { get; set; }
    public DateTime Birthday { get; set; }
    public List<string> FavoriteFoods { get; set; }
}

すべての例で次のデータ初期化コードを使用します

var animal = new Monkey {
    Name = "Rupert",
    Birthday=new DateTime(2011, 04, 01),
    FavoriteFoods = new List<string>
        {"Bananas", "Banana Split", "Banana Smoothie"}
};

モデルのプロパティの表示

モデルがプロパティを持つクラスである場合、次のテンプレート例に示すように、Razor テンプレートで簡単に参照できます。

@model Monkey
<html>
    <body>
    <h1>@Model.Name</h1>
    <p>Birthday: @(Model.Birthday.ToString("d MMMM yyyy"))</p>
    </body>
</html>

これは、次のコードを使用して文字列にレンダリングできます。

var template = new RazorView () { Model = animal };
var page = template.GenerateString ();

最終的な出力は、iOS Simulator と Android Emulator の Web ビューに次のように表示されます。

Rupert

C# ステートメント

テンプレートには、この例の Model プロパティの更新や Age 計算のように、より複雑な C# を含めることができます。

@model Monkey
<html>
    <body>
    @{
        Model.Name = "Rupert X. Monkey";
        Model.Birthday = new DateTime(2011,3,1);
    }
    <h1>@Model.Name</h1>
    <p>Birthday: @Model.Birthday.ToString("d MMMM yyyy")</p>
    <p>Age: @(Math.Floor(DateTime.Now.Date.Subtract (Model.Birthday.Date).TotalDays/365)) years old</p>
    </body>
</html>

コードを @() で囲むことで、複雑な単一行の C# 式 (age の書式設定など) を記述できます。

複数の C# ステートメントは @{} で囲んで記述できます。

If-else ステートメント

コード ブランチは、このテンプレートの例に示すように、@if で表すことができます。

@model Monkey
<html>
    <body>
    <h1>@Model.Name</h1>
    <p>Birthday: @(Model.Birthday.ToString("d MMMM yyyy"))</p>
    <p>Age: @(Math.Floor(DateTime.Now.Date.Subtract (Model.Birthday.Date).TotalDays/365)) years old</p>
    <p>Favorite Foods:</p>
    @if (Model.FavoriteFoods.Count == 0) {
        <p>No favorites</p>
    } else {
        <p>@Model.FavoriteFoods.Count favorites</p>
    }
    </body>
</html>

ループ

foreach のようなループ構造を追加することもできます。 @ プレフィックスをループ変数で使用して (この例では @food)、HTML でレンダリングできます。

@model Monkey
<html>
    <body>
    <h1>@Model.Name</h1>
    <p>Birthday: @Model.Birthday.ToString("d MMMM yyyy")</p>
    <p>Age: @(Math.Floor(DateTime.Now.Date.Subtract (Model.Birthday.Date).TotalDays/365)) years old</p>
    <p>Favorite Foods:</p>
    @if (Model.FavoriteFoods.Count == 0) {
        <p>No favorites</p>
    } else {
        <ul>
            @foreach (var food in @Model.FavoriteFoods) {
                <li>@food</li>
            }
        </ul>
    }
    </body>
</html>

iOS Simulator と Android Emulator で実行されている上記のテンプレートの出力を示します。

Rupert X Monkey

このセクションでは、Razor テンプレートを使用してシンプルな読み取り専用ビューをレンダリングする方法の基本について説明しました。 次のセクションでは、Razor を使用して、ユーザー入力を受け入れて HTML ビューの JavaScript と C# の間で相互運用できる、より完全なアプリを構築する方法について説明します。

Xamarin での Razor テンプレートの使用

このセクションでは、Visual Studio for Mac でソリューション テンプレートを使用して、独自のハイブリッド アプリケーションを構築する方法について説明します。 [ファイル] > [新規作成] > [ソリューション...] ウィンドウから使用できるテンプレートは 3 つあります。

  • Android > アプリ > Android WebView アプリケーション
  • iOS > アプリ > WebView アプリケーション
  • ASP.NET MVC プロジェクト

iPhone および Android プロジェクトの [新しいソリューション] ウィンドウは次のようになります。右側のソリューションの説明では、Razor テンプレート エンジンのサポートが強調表示されています。

Creating iPhone and Android solutions

.cshtml Razor テンプレートは、あらゆる既存の Xamarin プロジェクトに簡単に追加できることに注意してください。必ずしもこれらのソリューション テンプレートを使用する必要はありません。 iOS プロジェクトでは、Razor を使用するためにストーリーボードも必要ありません。UIWebView コントロールをプログラムで任意のビューに追加するだけで、Razor テンプレートを完全に C# コードでレンダリングできます。

iPhone および Android プロジェクトの既定のテンプレート ソリューションの内容を次に示します。

iPhone and Android templates

テンプレートによって、データ モデル オブジェクトを含む Razor テンプレートを読み込み、ユーザー入力を処理して、JavaScript 経由でユーザーと通信するための、すぐに使えるアプリケーション インフラストラクチャが提供されます。

ソリューションの重要な部分は次のとおりです。

  • style.css ファイルなどの静的コンテンツ。
  • RazorView.cshtml などの Razor .cshtml テンプレート ファイル。
  • ExampleModel.cs などの Razor テンプレートで参照されるモデル クラス。
  • Web ビューを作成し、テンプレートをレンダリングするプラットフォーム固有のクラス (Android での MainActivity や iOS での iPhoneHybridViewController など)。

次のセクションでは、プロジェクトがどのように機能するかについて説明します。

静的コンテンツ

静的コンテンツには、CSS スタイルシート、画像、JavaScript ファイル、または Web ビューに表示される HTML ファイルからリンクまたは参照できるその他のコンテンツが含まれます。

テンプレート プロジェクトには、ハイブリッド アプリに静的コンテンツを含める方法を示す最小限のスタイル シートが含まれています。 CSS スタイルシートは、テンプレート内で次のように参照されます。

<link rel="stylesheet" href="style.css" />

JQuery のようなフレームワークを含め、必要なスタイルシートや JavaScript ファイルをどのようなものでも追加できます。

Razor cshtml テンプレート

このテンプレートには、Razor .cshtml ファイルが含まれています。その内容は、HTML/JavaScript と C# の間でのデータ通信に役立つ、あらかじめ記述されたコードです。 これにより、モデルからの読み取り専用データを表示するだけでなく、HTML でのユーザー入力を受け入れ、それを処理または保存のために C# コードに戻す、高度なハイブリッド アプリを構築できるようになります。

テンプレートをレンダリングする

テンプレートで GenerateString を呼び出すと、そのまま Web ビューに表示できる HTML がレンダリングされます。 テンプレートでモデルを使用する場合は、レンダリングの前に指定する必要があります。 この図は、レンダリングがどのように機能するかを示しています。静的リソースが実行時に Web ビューによって解決され、指定されたファイルを検索するために提供されたベース ディレクトリが使用されるということではありません。

Razor flowchart

テンプレートからの C# コードの呼び出し

レンダリングされた Web ビューから C# にコールバックする通信は、Web ビューの URL を設定し、C# で要求をインターセプトして、Web ビューを再読み込みせずにネイティブ要求を処理することで行われます。

RazorView のボタンの処理方法に例を見ることができます。 ボタンの HTML は次のとおりです。

<input type="button" name="UpdateLabel" value="Click" onclick="InvokeCSharpWithFormValues(this)" />

InvokeCSharpWithFormValues JavaScript 関数は、HTML フォームからすべての値を読み取り、Web ビューの location.href を設定します。

location.href = "hybrid:" + elm.name + "?" + qs;

これにより、カスタム スキーム (例: hybrid:) を含む URL への Web ビューの遷移が試みられます

hybrid:UpdateLabel?textbox=SomeValue&UpdateLabel=Click

ネイティブ Web ビューがこのナビゲーション要求を処理するときに、それをインターセプトする機会があります。 iOS では、これは UIWebView の HandleShouldStartLoad イベントを処理することによって行われます。 Android では、フォームで使用される WebViewClient をサブクラス化し、ShouldOverrideUrlLoading をオーバーライドするだけです。

これら 2 つのナビゲーション インターセプターの内部は基本的に同じです。

まず、Web ビューが読み込もうとする URL をチェックし、カスタム スキーム (hybrid:) で始まらない場合は、通常どおりナビゲーションを実行できるようにします。

カスタム URL スキームの場合、URL のスキームと "?" の間にあるすべての内容 は、処理するメソッド名です (この例では "UpdateLabel")。 クエリ文字列内のすべてのものが、メソッド呼び出しのパラメーターとして扱われます。

var resources = url.Substring(scheme.Length).Split('?');
var method = resources [0];
var parameters = System.Web.HttpUtility.ParseQueryString(resources[1]);

このサンプル内の UpdateLabel は、テキストボックス パラメーターに対して最小限の文字列操作 (文字列の先頭に "C# says" を付ける) を実行し、Web ビューを呼び出します。

URL の処理後、メソッドはナビゲーションを中止し、Web ビューがカスタム URL への移動を終了しないようにします。

C# からのテンプレートの操作

C# からレンダリングされた HTML Web ビューへの通信は、Web ビューで JavaScript を呼び出すことによって行われます。 iOS では、これは UIWebView で EvaluateJavascript を呼び出すことによって行われます。

webView.EvaluateJavascript (js);

Android では、"javascript:" URL スキームを使用して JavaScript を URL として読み込むことで、Web ビューで JavaScript を呼び出すことができます。

webView.LoadUrl ("javascript:" + js);

アプリを真にハイブリッドにする

これらのテンプレートは、各プラットフォームでネイティブ コントロールを使用しません。画面全体に 1 つの Web ビューが表示されます。

HTML はプロトタイプ作成に適しており、リッチ テキストやレスポンシブ レイアウトなど、Web に最適な種類のものを表示するのに適しています。 ただし、すべてのタスクが HTML や JavaScript に適しているわけではありません。たとえば、長いデータ リストをスクロールする場合は、ネイティブ UI コントロール (iOS の UITableView や Android の ListView など) を使用した方がパフォーマンスが良くなります。

テンプレート内の Web ビューは、プラットフォーム固有のコントロールで簡単に拡張できます。Mac で Xcode を使用して、または Android で Resources/layout/Main.axml を使用して、MainStoryboard.storyboard を編集するだけです。

RazorTodo サンプル

RazorTodo リポジトリには、完全に HTML で駆動されるアプリと、HTML にネイティブ コントロールを組み合わせたアプリとの違いを示す、2 つの異なるソリューションが含まれています。

  • RazorTodo - Razor テンプレートを使用した、完全に HTML で駆動されるアプリ。
  • RazorNativeTodo - iOS および Android 用のネイティブ リスト ビュー コントロールを使用しますが、HTML と Razor で編集画面を表示します。

これらの Xamarin アプリは iOS と Android の両方で実行され、ポータブル クラス ライブラリ (PCL) を使用して、データベースやモデル クラスなどの共通コードを共有します。 Razor .cshtml テンプレートを PCL に含めて、プラットフォーム間で簡単に共有することもできます。

どちらのサンプル アプリにもネイティブ プラットフォームの Twitter 共有およびテキスト読み上げ API が組み込まれており、Xamarin を使用したハイブリッド アプリケーションが HTML Razor テンプレートで駆動されるビューの基になるすべての機能にもアクセスできることを示します。

RazorTodo アプリは、リストおよび編集ビューに HTML Razor テンプレートを使用しています。 これは、共有ポータブル クラス ライブラリ (データベースと .cshtml Razor テンプレートを含む) でアプリをほぼ完全に構築できることを意味します。 次のスクリーンショットは、iOS と Android のアプリを示しています。

RazorTodo

RazorNativeTodo アプリは、編集ビューに HTML Razor テンプレートを使用しますが、各プラットフォームのネイティブ スクロール リストを実装します。 これには、次のような多くの利点があります。

  • パフォーマンス - ネイティブ スクロール コントロールは仮想化を使用して、非常に長いデータ リストでも高速でスムーズなスクロールを実現します。
  • ネイティブ エクスペリエンス - iOS および Android での高速スクロール インデックスのサポートなど、プラットフォーム固有の UI 要素が簡単に有効になります。

Xamarin を使用してハイブリッド アプリを構築する主な利点は、完全に HTML で駆動されるユーザー インターフェイス (最初のサンプルなど) から始めて、必要に応じてプラットフォーム固有の機能を追加できることです (2 番目のサンプルに示すように)。 iOS と Android の両方のネイティブ リスト画面と HTML Razor 編集画面を次に示します。

RazorNativeTodo

まとめ

この記事では、ハイブリッド アプリケーションの構築を容易にする、iOS および Android で使用できる Web ビュー コントロールの機能について説明しました。

次に、Razor テンプレート エンジンと、Xamarin アプリで cshtml Razor テンプレート ファイルを使用して HTML を簡単に生成するために使用できる構文について説明しました。 また、Xamarin でハイブリッド アプリケーションの構築をすぐに開始できる Visual Studio for Mac ソリューション テンプレートについても説明しました。

最後に、Web ビューにネイティブ ユーザー インターフェイスと API を組み合わせる方法を示す RazorTodo サンプルを紹介しました。