ASP.NET Core MVC で CRUD して Realm Object Server と連携してみよう

今年 8/15 に Realm の [.NET Core Support](https://blog.realm.io/introducing-dot-net-core-support-for-realm/) がアナウンスされました．

今までモバイル環境，特に RealmXamarin を使用していたので，サーバーサイドともモデル定義が共通化出来るため，非常に管理が容易になりモデル定義および別のDB とのいい感じのマイグレーションとかを考えることが短縮されて嬉しい感じがあります．
本記事ではサンプルとして作成した[リポジトリのコード](https://github.com/yamachu/RealmNetCoreSample)を使って，ASP.NET Core で マスターデータをクライアントに配信するWebアプリを作るまでの流れを見てみようと思います．

記事の内容としてはリポジトリのコードの捕捉，という側面が大きいので，ぜひともコードを見て，実際に手を動かしてみて欲しいです．
なお本コードは 弱いユーザ名やパスワードで Basic認証を行って Admin ページへのアクセスを許していたり，パスワードなどのハッシュやアクセストークンの生成なども雑でソルトなども使っていなくて推定されやすいセキュリティ的には危ない感じのものが含まれているので，そのままプロダクトに使用する，ということがないようお願いいたします．

## 環境
### 必要

* .NET Core 2.0 が使える環境
  * mac とかで IDE を使う場合って Alpha じゃないとだめなんでしたっけ？詰まったら[こちら](http://teitoku-window.hatenablog.com/entry/2017/08/27/105029)を御覧ください  
* Docker が使える環境（Realm Object Serverのホスティングのため）

### 任意

* Shell Script が動作する環境（特にシェル芸とかしてないのでそのままコマンド叩いても大丈夫です）

## 実践

ひとまずリポジトリを見てみましょう．
自分でビルドを試してみたい方は

```shell-session
$ git clone git@github.com:yamachu/RealmNetCoreSample.git
```

### Realm の導入

まずはじめに Realm を使えるように Nuget で導入しましょう．
私は Visual Studio Code とターミナルでだいたい完結させるのが好きなのでコマンドから入れちゃいます．

```shell-session
$ dotnet add package Realm
```

これで Realm パッケージの追加が出来ました．
ですが，これ以外に必要なフローがあります（おそらく IDE から行うといい感じにやってくれるのかもしれない）．

[公式ドキュメント](https://realm.io/docs/xamarin/latest/#installation)にあるように， __FodyWeaver.xml__ を追加します．
中身はリポジトリのファイルを参考にしてみてください．
これで動作する環境が揃いました．

### ビルドしてみる

さてビルドしてみましょう．
IDE 使ってるのであればビルドのボタン押すだけですが，コマンドラインから行う場合ちょっと注意が必要です．

またまた[公式ドキュメント](https://realm.io/docs/xamarin/latest/#prerequisites)にあるように `dotnet` コマンドではだめで，`msbuild`を使えとのことです．
プロジェクトディレクトリを指定しないと Fody Task がうまくいかないようなので，カレントディレクトリにプロジェクトがある場合は

```shell-session
$ msbuild build /p:SolutionDir=${pwd}
```

みたいな感じでプロパティで与えてあげましょう．これでビルドが通ったと思います．

### モデルを設計してみる

[公式ドキュメント](https://realm.io/docs/xamarin/latest/#models)（以下略）を参考にモデル定義を行います．

今回は共通のお知らせマスターデータをクライアントで同期して参照するという設計にしたので，Announce モデルみたいのを作ってみます．

```csharp
public class Announcement : RealmObject
{
    [PrimaryKey]
    public string Id { get; set; } = Guid.NewGuid().ToString();

    [Required]
    public string Title { get; set; }

    [Required]
    public string Body { get; set; }

    public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.Now;
 
    public DateTimeOffset ModifiedAt { get; set; }
}
```

こんな感じで ユニークID を持たせて最低限お知らせに必要なフィールドを作っていきます．
CreatedAt や ModifiedAt を持たせているのは表示順序などを決定したりするのに使おうかなぁと考えてたためです．
このソートも Linq だと簡単に出来るの本当にすごいですよね．

### Realm インスタンスの生成

このあたりはまた公式ドキュメントかリポジトリを...という感じなのですが，自分がはまってしまったところを共有します．

```csharp
var sharedDatabaseConfig = new SyncConfiguration(adminUser, new Uri($"realm://{DatabaseServerDomain}:{DatabaseServerPort}/~/Shared"));
sharedDatabaseConfig.ObjectClasses = new Type[]{
    typeof(RealmNetCoreSample.Models.Announcement)
};
SharedRealmConfiguration = sharedDatabaseConfig;
```

こんな感じで Realm サーバーに接続するための設定を書いて，接続して初めて Realm Object が生成されるのですが，ここで `ObjectClasses` を設定しないと，そのプロジェクトに含まれる __RealmObject__ を継承した物が全部 Realm の Scheme に入ってしまいますので注意してください

### 接続のための Realm Object Server を立てる

[リポジトリにある docker-compose の設定ファイル](https://github.com/yamachu/RealmNetCoreSample/blob/master/docker-compose.yml)を使って立てちゃいましょう．

```yaml
realm:
  build: .
  dockerfile: Dockerfile-realm
  container_name: realm
  volumes:
    - ./realm-object:/var/lib/realm/object-server
    - ./configuration.yml:/etc/realm/configuration.yml
  ports:
    - "9080:9080"
```

Realm Object Server の振る舞いは同じディレクトリに設置してある configuration.yml を書き換えることで変えることが出来ます．
またデータは同一ディレクトリの realm-object ディレクトリで共有するようになっているので，Realm Object Server でデータベースを作るとどんなファイルが作られるのかを調べるのに使ったりしてみましょう．

これで最低限 ASP.NET Core アプリケーションから Realm Object Server に接続し，Realm を使う環境は整いました．

わーい，お　わ　り

とはいかず，データーベースの編集のためにわざわざコード書いたりするの面倒ですよね．
編集するための Web UI を作ってみましょう．

### CURD のための Web UI を自動生成する

ただ HTML とコントローラーを書くだけです！！！

...わざわざモデル一つずつのためにページなんか作ってられませんよね．
そこで使うのが `Microsoft.VisualStudio.Web.CodeGeneration.Tools`です．

これを使うことで既存のモデルとデータベースのコンテキストから Controller や View をいい感じに自動生成することが出来ます．
一つ問題があるとすれば，これは EntityFrameworkCore に依存しているということです．

今回このプロジェクトは Realm を使っていて，MySQL や SQLite や SQLServer に依存していないものになっています．

今更 View 作るために使えと...?というのも面倒なので，Dummy の DbContext を作って対応します．

```csharp
public class DummyDatabaseContext : DbContext
{
    public DummyDatabaseContext()
    {
    }

    public DummyDatabaseContext(DbContextOptions<DummyDatabaseContext> options) : base(options)
    {
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseInMemoryDatabase("dummy");
    }
}
```

メモリ上にデータベースを作るような感じです．

これを __Startup.cs__ で DI設定します．

```csharp
services.AddDbContext<DummyDatabaseContext>(options => options.UseInMemoryDatabase("dummy"));
```

雑ですが，こんなもんでツールが使えるようになります．

あとはコマンドラインで

```shell-session
$ dotnet aspnet-codegenerator --no-build razorpage -m ${model_name} -dc DummyDatabaseContext -outDir Pages/Admin/${model_name}
```

みたいに実行します．
今回は Web UI を Razor で Pages のネームスペースにまとめて， Controllers に API をしたいので，razorpage を指定しています．
また，前述したとおり `dotnet` コマンドを使うとプロジェクトのディレクトリが渡せないので，`--no-build` でビルドをスキップしています．
これで CRUDのための Web UI がいい感じに生成されます．

このままだと DummyDatabaseContext を DBアクセスに使用するようになってしまっているので，リポジトリの変更を参考に書き換えてみてください．

これで CRUD 出来る管理 UI が完成しました．

## まとめ

コードもあまり載せずまた説明もだいぶ端折っちゃいましたが，これとリポジトリのコードを見れば（たぶん）いい感じに行くと思います．

次はクライアントを作成し，余力があれば Xamarin.Forms とかでも使えるようにしてみるという内容でお知らせできればと思います．