ASP.Net Core で Nancy する その３


[前回](http://qiita.com/t-koyama/items/ef4ae5eebc5e0cae2c9a)はGETリクエストされたら View を返してみました。

今回は[前々回](http://qiita.com/t-koyama/items/2cb9682967092f55abfd)に作成したGETリクエストで JSON を返すやつを使って View に表示してみます。

ここで本題に入る前に少しプロジェクトの構成を変えます。

## プロジェクト構成を変えたい
やることは以下の2点です。
1. SampleModule.cs 内の Person クラスを Models フォルダーに移動
2. SampleModule.cs 内の モジュールをModulesフォルダに移動

### Models
まずは、Person クラスを定義します。

```csharp:Person.cs
namespace WebApplication1.Models
{
    class Person
    {
        public int Id { get; set; }
        public string Name { get; set; }
    }
}
```

### Modules
次はモジュールなんですが、モジュールを移動させるついでに View を返すモジュールと JSON を返すモジュールに分けちゃいます。

まずは View を返すモジュールを定義します。

```csharp:IndexModule,cs
using Nancy;

namespace WebApplication1.Modules
{
    public class IndexModule : NancyModule
    {
        public IndexModule()
        {
            // View を返す
            Get["/"] = _ => View["index"];
        }
    }
}
```

次は JSON を返すモジュールを定義します。こちらは少し内容を変更します。

```csharp
using Nancy;
using System.Collections.Generic;
using System.Linq;

namespace WebApplication1.Modules
{
    public class PersonModule : NancyModule
    {
        //TODO: スタブ
        List<Person> _values = new List<Person>
        {
            new Person { Id = 1, Name = "Francis" },
            new Person { Id = 2, Name = "Nancy" },
        };

        public PersonModule() : base("/api/v1/person")
        {
            // JSON に変換して返す
            Get["/"] = _ => Response.AsJson(_values);

            // 指定された ID の Person を JSON に変換して返す
            Get["/{id}"] = p => Response.AsJson(
                _values
                .Where(x => x.Id == p.id)
                .FirstOrDefault()
                );
        }
    }
}
```

基本クラスのコンストラクタ引数に基本となるルートを指定するようにしました。これでこのモジュールにリクエストを追加するときに同じルートを何度も書く必要がなくなるので記述がシンプルになります。あと、パスに v1 とか付けてみました。

この状態で実行するとちゃんと View が表示されると思います。モジュールの配置場所について特別な設定は必要ありません。モジュールを分割したり移動したりしましたが Nancy が見つけて面倒をみてくれます。

これで構成の変更は終わりです。SampleModule.cs は不要になったので削除します。
次は必要なJSライブラリなどを揃えます。

## ライブラリのインストール
ASP.NET Core プロジェクトでは Bower を使ってライブラリを管理します。

まず、右クリックでコンテキストメニューから追加を選択して、左のツリーから [.Net Core]- [Client-side] を選択して Bower 構成ファイルの新規作成を行います。すると以下の2のファイルがプロジェクトのフォルダに追加されます。

```json:.bowerrc
{
  "directory": "wwwroot/lib"
}
```

```json:bower.json
{
	"name": "asp.net",
	"private": true,
	"dependencies": {
	}
}
```

この二つのファイルはソリューションエクスプローラ上から通常の状態では表示されていません。ソリューションエクスプローラで表示する場合は『全てのファイルを表示』ボタンを押しあげてください。

.bowerrc の "directory" でパッケージのインストール先を指定しています。インストール先は lib のままでもいいんですが個人的には bower_components のほうがいいので変更します。

```json:.bowerrc
{
  "directory": "wwwroot/bower_components"
}
```

次はソリューションのNuGet のパッケージ管理を開いて bootstrap と検索します。すると bootstrap のところに

>非互換: 代わりにBowerを使用してください

と表示されると思います。このBower のところがリンクになっているのでクリックしちゃいます。クリックするとBower パッケージの管理タブが開くのでここから bootstrap をインストールします。インストールボタンを押すと先ほど .bowerrc で指定したディレクトリにフォルダが作成されてその下に bootstrap がインストールされます。

ちなみに、Bower 構成ファイルを作成していない場合は wwwroot フォルダと同じ階層に bower_components フォルダが作成されてその下に bootstrap がインストールされます。

最後に AngularJs をインストールします。Bower パッケージの管理タブを閉じてしまった場合は、依存関係の下にBower が追加されているのでこの Bower を右クリックすることで 再度タブを開くことができます。

管理タブで angular と検索してインストールします。

これで今回の実装に必要なファイルが揃いました。
パッケージの構成を参考までに載せておきます。

```json:bower.json
{
  "name": "asp.net",
  "private": true,
  "dependencies": {
    "bootstrap": "3.3.6",
    "angular": "1.5.5"
  }
}
```

## index.html の場所を変える
wwwroot フォルダの下に app フォルダを作成してその下に index.html を移動します。
移動後に実行するとまた HTTP 500 で内部サーバエラーを起こします。

### Nancy の View の読み込み
デフォルトの状態だとルートパスを基準とした以下のディレクトリにある index(-ja).htmlを探しに行くようになっています。
* views/モジュール名/
* モジュール名/
* views/
* ルートパス直下

しかし、今回は app/index.html を見つけて欲しいので処理を追加してあげる必要があります。

### Nancy の静的コンテンツの読み込み

また、スタイルシートなどの静的コンテンツについてもデフォルトだとルートパスの直下にある Contents フォルダを見に行きます。これだと Bower で入れたパッケージやこれから追加するファイルが使えないのでこちらについても処理を追加する必要があります。

### 処理を追加する

処理の追加は BootStrapper.cs に行います。

```csharp:BootStrapper.cs
using Nancy;
using Nancy.Conventions;

namespace WebApplication1
{
    public class BootStrapper : DefaultNancyBootstrapper
    {
        protected override IRootPathProvider RootPathProvider
        {
            get { return new RootPathProvider(); }
        }

        protected override void ConfigureConventions(NancyConventions nancyConventions)
        {
            // View のロケーションを追加
            nancyConventions.ViewLocationConventions
                .Add((viewName, model, viewLocationContext) => string.Concat("app/", viewName));

            // 静的コンテンツの場所をクリア
            nancyConventions.StaticContentsConventions.Clear();

            // 静的コンテンツの場所を追加
            
            // Bower
            nancyConventions.StaticContentsConventions
                .Add(StaticContentConventionBuilder.AddDirectory("bower_components"));

            // JSファイル
            nancyConventions.StaticContentsConventions
                .Add(StaticContentConventionBuilder.AddDirectory("scripts", @"app\scripts"));

            // CSSファイル
            nancyConventions.StaticContentsConventions
                .Add(StaticContentConventionBuilder.AddDirectory("styles", @"app\styles"));

            base.ConfigureConventions(nancyConventions);
        }
    }
}
```

ConfigureConventions() をオーバーライドして処理を追加しています。

View のロケーションを追加して wwwroot/app を見てもらうようにしました。

また、View のロケーションについてはこの処理を追加しないでモジュール内で

```csharp
Get["/"] = _ => View["app/index"];
```

としてもOKです。

今回は  Contents フォルダは使わないのでいったんクリアしています。
そのあとで今回使う静的コンテンツのディレクトリを指定しています。

これで準備が整ったのでView の実装に入ります。

## View の実装

HTML と CSS をいじります。

まずは HTML から


```html:index.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width">
    <title>ASP.Net Core で Nancy する</title>
    <link href="bower_components/bootstrap/dist/css/bootstrap.css" rel="stylesheet" />
    <link href="styles/app.css" rel="stylesheet" />
</head>
<body ng-app="app" ng-controller="mainController as main">
    <!-- ナビゲーション -->
    <nav class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <a class="navbar-brand" href="#">Hello World!</a>
            </div>
        </div>
    </nav>
    <!-- メインコンテンツ -->
    <div class="container">
        <table class="table">
            <thead>
                <tr>
                    <th>Id</th>
                    <th>Name</th>
                </tr>
            </thead>
            <tbody>
                <tr ng-repeat="item in main.items">
                    <td>{{ item.id }}</td>
                    <td>{{ item.name }}</td>
                </tr>
            </tbody>
        </table>
    </div>
    <script src="bower_components/jquery/dist/jquery.js"></script>
    <script src="bower_components/bootstrap/dist/js/bootstrap.js"></script>
    <script src="bower_components/angular/angular.js"></script>
    <script src="scripts/app.js"></script>
    <script src="scripts/controllers/mainController.js"></script>
</body>
</html>
```

ナビゲーションバーを追加してテーブルを追加しただけです。ナビゲーションバーは特にナビゲートするものはないのですが、かっこいいので入れました。

次はスタイルシートを作成します。 app フォルダに styles フォルダを作成し app.css ファイルを作成します。

```css:app.css
body {
    padding-top: 60px;
}
```

ナビゲーションバーで .navbar-fixed-top を指定しているので body 要素の上部に余白を取っています。

## javascript

JSファイルを追加していきます。app フォルダに scripts フォルダーを作成します。

scripts フォルダーにて 右クリックでコンテキストメニューから追加を選択して、左のツリーから [.Net Core]- [Client-side] を選択してください。Visual Studio 2015 には AngularJs のテンプレートが標準で用意されています。

* AngularJs Controller
* $scope を使用する AngularJs Controller
* AngularJs Directive
* AngularJs Factory
* AngularJs Module

まずはこの中から AngularJs Module を選択して追加します。名前はデフォルトの app.js でOKです。
今回は画面遷移とかは行わないので 'ngRoute' の部分はコメントアウトします。

```javascript:app.js
(function () {
    'use strict';

    angular.module('app', [
        // Angular modules 
        //'ngRoute'

        // Custom modules 

        // 3rd Party Modules
        
    ]);
})();
```

これで app という名前のモジュールが生成されます。

### Controller 
つぎは Controller を追加します。scripts フォルダの下に controllers フォルダを作成して、今度は AngularJs Controller を選択して追加します。 $scope を使用しないほうを選んでください。名前は mainController.js にします。

```javascript:mainController.js
(function () {
    'use strict';

    angular
        .module('app')
        .controller('mainController', mainController);

    mainController.$inject = ['$location']; 

    function mainController($location) {
        /* jshint validthis:true */
        var vm = this;
        vm.items = [{ id: 1, name: 'Nancy' }, { id: 2, name: '関' }];
        activate();

        function activate() { }
    }
})();
```

app モジュールに mainController を追加しています。

上のコードではお試しで ViewModel に items を追加して適当なデータを入れています。
実行すると ViewModel で設定した items の値がテーブルに表示されます。

### HTMLファイルでしていること
簡単ですが説明しておきます。

index.html の body タグで ng-app に今回作成したモジュールと ng-controller にコントローラを指定しています。

```html
<body ng-app="app" ng-controller="mainController as main">
```

これによって body タグ内で app モジュールにある mainController の ViewModel に定義されているプロパティを要素にバインドしたりできるようになります。プロパティには as で宣言している名前を使って

```html
main.hoge
```

の形でアクセスすることができます。

また、値をバインディングするときは {{ }} で囲ってあげます。

```html
<div>{{ main.fuga }}<div>
```

次はテーブルに items の内容を表示するために ng-repeat を使っています。

```html
<tr ng-repeat="item in main.items">
    <td>{{ item.id }}</td>
    <td>{{ item.name }}</td>
</tr>
```

ng-repeat は宣言されているタグの内容をテンプレートにして、コレクション数に応じて foreach みたいに追加していきます。コレクション内に存在する要素のプロパティには in で宣言している名前を使って

```html
item.piyo
```

の形でアクセスできます。

## Nancy から JSON を受け取る
mainController.js で画面を表示したときにGETリクエストして JSON を受け取ってテーブルに表示するように変更します。

変更後のコードは以下となります。

```javascript
(function () {
    'use strict';

    angular
        .module('app')
        .controller('mainController', mainController);

    mainController.$inject = ['$location', '$http'];

    function mainController($location, $http) {
        /* jshint validthis:true */
        var vm = this;
        vm.items = [];
        activate();

        function activate() {

            // GETリクエストして JSON を受け取る
            $http.get("/api/v1/person/").success(function (data, status, headers, config) {

                // 成功した場合は取得したデータを設定
                vm.items = data;
            });
        }
    }
})();
```

変更点は３つです。

* $injectプロパティに $http を追加して依存性を注入(DI)
* コンストラクタに $http を追加
* active() に $http.get() して JSON を受け取る処理を追加

$http はサーバと通信する機能を提供してくれます。他にも post, put, delete なんかもできます。
また、今回は成功時の挙動だけ処理を書いていますが失敗時の挙動もチェーンして書くことができます。

```javascript
$http.get("/hoge").success(function (data, status, headers, config) {
    // 成功した時
}).error(function (data, status, headers, config) {
    // 失敗した時
});
```

これで、準備ができました。
実行すると JSON の内容が View に表示されます。