C# + Nancy (OWIN, SelfHost, SSVE) + Herokuで、Highchartsを使ってみた

NancyでHighchartsを使ってみようと思った時に、いろいろと悩んだことがあったので、メモとして残しておきます。

 

環境

 

使うView Engineについて

NancyのView Engineは自分で選ぶことができます。NuGetにも複数登録されています。
View engines · NancyFx/Nancy Wiki · GitHub

 
最初はASP.NET MVCでも使われているRazorを使おうとしましたが、SelfHostのせいかいくつかエラーが出て使えませんでした。Stackoverflowを見ると、過去には同様の例がありました*2

 
そのため、今回はNancyのデフォルトのViewEngineである、The Super Simple View Engine (以下SSVE)を使うことにしました。

なお、SSVEにはインテリセンスはないようです。
c# - How to get Intellisense recognize SSVE? - Stack Overflow

 

Nancy + OWIN + SelfHostのプロジェクト作成

以前と同じく、コンソールアプリケーションのプロジェクトを作成し、上記のNuGetパッケージをインストールします。

次にProgram.csやStartup.csも作成しますが、ここも同様です。

 

C#でHighchartsを使う

Highchartsのライブラリとして、以前Rubyのgemlazy_high_chartsを使ったことがありました。

C#でも同じようなライブラリがないかを探してみたところ、DotNet.Highchartsというライブラリがありました。
DotNet.Highcharts - Home

良さそうなライブラリでしたが、HighchartsクラスがSystem.Web.IHtmlStringを継承していたため、Nancyで使おうとすると参照の追加が必要そうでした。
DotNet.Highcharts - Source Code

そのため、今回はHighchartsをそのまま使うことにしました。

 

Highchartsのダウンロード

公式よりダウンロードします(現時点の最新は、4.0.4)。
Highcharts - Download

なお、「DOWNLOAD BUILDER」で「Standalone framework」を選択するとjQuery無しでもいけそうでしたが、「EXPERIMENTAL」との記載があったので、今回は使いませんでした。

 
ダウンロード後は、path\to\project\Scriptsディレクトリにhighcharts.jsファイルのみをコピーしておきます。

 

JavaScriptファイルの用意

今回のHighchartsデータソースに対応するJavaScriptは、以前作成したPadrionoプロジェクトJavaScriptを流用し、Highchartsと同じディレクトリに入れます。

JSONPでの書き方については以下を参考に、$.getJSONを使いました*3
JSONPで悩むある程度の人々へ - tsujimotter

また、JSONPを返すサーバーは上記のPadrionoプロジェクトを一部修正して、JSONPを返すようにしました。
How to create a JSONP cross-domain webservice with Sinatra and Ruby - ruby - json, jsonp, sinatra, jquery

 

Viewの用意

path\to\project\Views\の下に作成します。今回はSSVEなので、以下の3ファイルを用意しました。

ファイル名 内容
_master.sshtml JSON,JSONPで共通する部分のView
json.sshtml JSON用の個別View
jsonp.sshtml JSONP用の個別View

 
SSVEの書き方についてはNancy公式に記載がありますが、今回の使用内容をメモとして残しておきます。
The Super Simple View Engine · NancyFx/Nancy Wiki · GitHub

 

共通する部分のViewの作成

共通する部分のHTMLを作ります。

ただ、JSON用/JSONP用で使うJavaScriptが異なるため、それぞれのViewごとにscriptタグの指定を変える必要があります。今回は、@Section['<name>'];を使いました(Razorの@sectionのようなもの)。

また、@Modelを使うことで、Nancyから渡したModelのプロパティの値を設定しています(Razorの@modelのようなもの)。

今回は、TitleやH1・現在時刻の表示で@Modelを使っています。

<html lang="en" xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta charset="utf-8" />
    <title>@Model.Title</title>
    <script src="//ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
    <script src="../../Scripts/highcharts-4.0.4.js"></script>
    
    @Section['Scripts'];
</head>
<body>
    <div id='container'></div>
    <h1>@Model.H1</h1>
    <p>現在時刻 - @Model.CurrentDatetime</p>
</body>
</html>

 

個別Viewの作成

まず、@Master['<ファイル名>']を使うことで、共通する部分をロードします。

次に、@Section['<セクション名>'] ~ @EndSectionを使って、scriptタグの記述を@Masterへと渡します。

@Master['_master.sshtml']

@Section['Scripts']
    <script src="../../Scripts/json.js"></script>
@EndSection

 

静的コンテンツの設定

ViewやJavaScriptが用意できたので、それらに必要な設定を行います。

 

Bootstrapperを使った、静的コンテンツディレクトリの追加

デフォルトでは、Nancyで静的コンテンツのディレクトリは path\to\project\Content となります。
NancyFxチュートリアル「2. プロジェクトの細かい設定をする(+ モデルを作成する)」 - らびたろちゃんにっき

そこで、記事にあるようにBootstrapperを作成し、JavaScriptのあるディレクトリ(今回はpath\to\project\Scripts)を静的コンテンツディレクトリとして追加します。

 

出力ディレクトリへのコピーを追加

ViewやJavaScriptのプロパティにおいて、デフォルトでは出力ディレクトリへのコピーが「なし」となっているため、アクセス時にエラーが発生します。

そのため、出力ディレクトリへのコピーを「常にコピーする」などへと変更します。

なお、StackOverflowのコメントとは異なりますが、手元では常にコピーするが必要そうでした。
How to serve static content in Nancy - Stack Overflow

 

Modelの用意

Viewの@Modelで使うためのクラスを用意します。

public class WebPageModel
{
    public string Title { get; set; }
    public string H1 { get; set; }
    public string CurrentDatetime { get; set; }
}

 

Moduleの用意

Viewを表示したり、JSONを返すようなModuleを用意します。

指定したViewを返す場合

Viewを使います。また、Viewで使うModelのインスタンスを第二引数で渡すこともできます。

Get["/json"] = _ =>
{
    var webpage = new WebPageModel() {
        Title = "さつまいも編", 
        H1 = "さつまいもグラフ", 
        CurrentDatetime = DateTime.Now.ToString()
    };
    return View["json", webpage];
};

 

JSONを返す場合

Response.AsJsonを使います。

Get["/api/json"] = _ =>
{
    var potato = new PotatoModel[]
    {
        new PotatoModel(){ Name = "紅はるか", Amount = 20, Color = "Pink"},
        new PotatoModel(){ Name = "ベニアズマ", Amount = 10, Color = "DarkViolet"},
        new PotatoModel(){ Name = "金時いも", Amount = 5, Color = "MistyRose"},
    };
    return Response.AsJson(potato);
};

Response.AsJsonの場合、JSONPにも自動で対応しているようです。
Nancy を使って JSON API を実装するメリット - しばやん雑記

 

実行結果

ローカルとHeroku、どちらでも実行できました。

f:id:thinkAmi:20150110062724p:plain

 

ソースコード

GitHubに上げました。
thinkAmi-sandbox/NancyHighcharts-sample · GitHub

 
Herokuボタンも置いておきます。

Deploy

*1:NuGetにもありましたが、今回はGoogle CDNを使いました

*2:現在は解消されているのかもしれませんが、今回はHighchartsを動かすことを優先し、コードは追いませんでした。

*3:`$.ajaxの場合は、次が参考になりました : jQuery $.ajaxでJSON・JSONP読み込みに使用する主なオプション | iwb.jp