BlazorアプリをFirebase Databaseと連携させる

BlazorクライアントアプリケーションをFirebase Hostingにデプロイし、Firebase Databaseと連携したいと思います。


![ezgif-5-3d431567faf1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110304/6305e479-b728-2a90-2388-6552d8e7bcad.gif)


#### 開発環境
Windows10 
Visual Studio 2019 Preview
.Net Core 3.0 Preview 9
[Firebase CLI for Windows(beta)](https://github.com/abeisgoat/firepit/releases/tag/0.1.0)

# Firebaseの準備

## Firebase-CLIを使えるようにする
VisualStudioのプロジェクトをFirebaseにデプロイするために、Firebase-Cliを準備します。[Firebase CLI for Windows(beta)](https://github.com/abeisgoat/firepit/releases/tag/0.1.0)は、Node.jsなどのインストールが不要なので便利です。
![firebase-cli.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110304/da20a875-3268-602b-1ffe-05212ea8299d.jpeg)

PATHを通しておきます。
![firebase-cli-path.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110304/42eeb2f8-56fa-cd80-c656-addc49174c3b.png)

## Firebaseにログインし、新しいプロジェクトを作成
Firebaseアカウントを作成し、ログインして、新しいプロジェクトを作成します。
左側の開発メニューから「Authentication」を選択し、認証方法を設定します。今回は匿名(anonymous)を有効にします。
![auth-tokumei.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110304/d3a2bdbe-7ddf-27ad-4002-de3e189512b5.jpeg)

## Firebase Databaseの作成
左側の開発メニューから「Database」を選択し、データベースの作成を行います。
保護ルールは、「テストモードで開始」を選択し、ロケーションはデフォルトのままにします。

Database (Realtime Database)の「ルール」で、**.read:true , .write:true** になっていることを確認します。
なっていない場合、画面上で書き換えます。

![database_rule.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110304/7a32cf7f-bd96-a056-5c6c-8b17b75dac2f.jpeg)

Firebase Databaseの設定はこれで終わりです。Firebase Databaseは「Realtime Database」を使います。

## マイアプリの追加
Firebaseの左側の歯車メニューから「プロジェクトの設定」を選びます。
「全般」の「マイアプリ」のウェブアプリ「</>」を選択します。
アプリの登録で「アプリのニックネーム」を付け、「アプリを登録」します。「□このアプリのFirebase Hostingも設定します」はチェックを外した状態で登録します。

次画面で出てくる Firebase SDKの追加画面では、apikey情報が表示されるので控えておきます。
今回必要になるのは**apikey**と**databaseURL**です。
![firebase-webapp-s.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110304/f41cabd0-c0a7-e5ed-7151-ddd6df8114ff.png)


# ベースのプロジェクトをデプロイ
## VisualStudio2019 でBlazorクライアントアプリケーションを作成
新しいプロジェクトから、Blazorアプリを選択し、「Blazor WebAssembly App」を選択します。Server-Sideはホスティングしませんので、「ASP.NET Core hosted」はチェックしないで作成します。

## 何もしない状態で、デプロイしてみる

こちら様のサイトを参考に、Firebase Hostingにデプロイしてみます。

[(参考)Qiita Blazorで作成したウェブサイトをFirebaseで公開する](https://qiita.com/nobu17/items/67c11cf622f2e4721635)

`firebase deploy`コマンドの途中で**HTTP Error: 503**エラーが発生する場合は、何度か試すと成功します。

以上でFirebaseの設定は完了です。

## BlazorアプリのFetchDataのページをFirebase Databaseと連携させる

デフォルトのFetchDataページは、静的ファイルの中身を表示しているだけのものです。Firebase Databaseと連携させます。

## Firebase Nugetパッケージの追加
FirebaseAuthentication.net と FirebaseDatabase.netを「Nugetパッケージの管理」から追加します。

[(参考)FirebaseAuthentication.net](https://www.nuget.org/packages/FirebaseAuthentication.net/)
[(参考)FirebaseDatabase.net](https://www.nuget.org/packages/FirebaseDatabase.net/)

## Pages/FetchData.razorのWeatherForecastをModel.csに移動
**Model.cs**を新規作成し、モデルクラスとなる**Pages/FetchData.razor**の**WeatherForecast定義**を移動します。

```c#:Model.cs
namespace BlazorApp
{
    public class Model
    {
        public class WeatherForecast
        {
            public DateTime Date { get; set; }
            public int TemperatureC { get; set; }
            public string Summary { get; set; }
            public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
        }
    }
}
```

## Firebaseアクセス用のAPIクラスを追加
Firebase Databaseとやりとりを行う**API.cs**を新規に追加します。

実装するのは、認証とデータの取得、追加を行う関数です。

```c#:API.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

// 参照の追加
using Firebase.Database;
using Firebase.Database.Query;
using Firebase.Auth;

namespace BlazorApp
{
    public class API
    {
        private FirebaseAuth _auth = null;
        public async Task Auth()
        {
            try
            {
                if (_auth == null)
                {
                    // Firebase Database apiKey
                    var auth = new FirebaseAuthProvider(new FirebaseConfig("Firebase Database apikey")); 
                    _auth = await auth.SignInAnonymouslyAsync();
                    Console.WriteLine("Auth OK");
                }
            }
            catch
            {
                Console.WriteLine("Auth NG");
            }
        }
        
        private ChildQuery GetDatabaseQuery(string path = null)
        {
            Console.WriteLine("GetDatabaseQuery Start");
            if (_auth == null)
            {
                throw new NullReferenceException();
            }
            Console.WriteLine("GetDatabaseQuery End");

            return new FirebaseClient(
                 // Firebase Database databaseURL
                "https://Firebase Database databaseURL.firebaseio.com",      
                new FirebaseOptions
                {
                    AuthTokenAsyncFactory = () => Task.FromResult(_auth.FirebaseToken),
                })
                .Child(path);
        }

        /// <summary>
        /// Firebase Databaseからデータ取得
        /// </summary>
        /// <returns></returns>
        public async Task<Model.WeatherForecast[]> GetData()
        {
            Console.WriteLine("GetData Start");
            try
            {
                var query = GetDatabaseQuery("/WeatherForecast");
                var result = await query.OnceAsync<Model.WeatherForecast>();
                var resultobject = result.Select(o => o.Object);
                Console.WriteLine("Object count:" + resultobject.Count().ToString());
                Console.WriteLine("GetData End");
                return resultobject.ToArray();
            }
            catch
            {
                Console.WriteLine("GetData NG");
                return null;
            }
        }

        /// <summary>
        /// Firebase Databaseへデータ追加
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        public async Task SetData(Model.WeatherForecast value)
        {
            Console.WriteLine("SetData Start");
            try
            {
                var query = GetDatabaseQuery("/WeatherForecast");
                await query.PostAsync(value);
                Console.WriteLine("SetData End");
            }
            catch
            {
                Console.WriteLine("SetData NG");
            }
        }
    }
}
```

## Pages/FetchData.razorからAPI.csを呼び出す
最後に **FetchData.razor**内で、**API.cs**の処理を呼び出します。

```c#:FetchData.razor
@page "/fetchdata"
@inject HttpClient Http

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

<div style="display:inline-flex">
    <p>Date</p>
    <input type="date" @bind-value="addItem.Date" />
    <p>Temp. (C)</p>
    <input type="text" @bind-value="addItem.TemperatureC"/>
    <p>Temp. (F)</p>
    <input type="text" value="@addItem.TemperatureF"/>
    <p>Summary</p>
    <input type="text" @bind-value="addItem.Summary"/>
    <button class="btn btn-primary" @onclick="SetWeatherForecast">Add</button>
</div>

<br />
<br />


@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    //WeatherForecast[] forecasts;
    // 表示用データ
    Model.WeatherForecast[] forecasts;
    // 追加用データ
    Model.WeatherForecast addItem;
    // Firebase APIクラスのインスタンス
    API firebaseAPI = null;

    protected override async Task OnInitializedAsync()
    {
        //forecasts = await Http.GetJsonAsync<WeatherForecast[]>("sample-data/weather.json");
        addItem = new Model.WeatherForecast();
        addItem.Date = DateTime.Now;
        // データ取得
        await GetWeatherForecast();
    }

    //public class WeatherForecast
    //{
    //    public DateTime Date { get; set; }
    //    public int TemperatureC { get; set; }
    //    public string Summary { get; set; }
    //    public int TemperatureF => 32 + (int)(TemperatureC / 0.5556);
    //}

    // データ取得
    async Task GetWeatherForecast()
    {
        if(firebaseAPI == null)
        {
            firebaseAPI = new API();
        }
        // 認証後、Firebase Databaseから取得
        await firebaseAPI.Auth().ContinueWith(async task =>
        {
            forecasts = await firebaseAPI.GetData();
            this.StateHasChanged();
        });
    }

    // データ設定
    async void SetWeatherForecast()
    {
        if (firebaseAPI == null)
        {
            firebaseAPI = new API();
        }
        // 認証後、Firebase Databaseへ追加、その後、データ再取得
        await firebaseAPI.Auth()
                         .ContinueWith(async task1 =>
                         {
                            await firebaseAPI.SetData(addItem).ContinueWith(async task2 =>
                                                            {
                                                                await GetWeatherForecast();
                                                            });
                         }
                         );
    }
}
```

## プロジェクトを発行して、Firebase Hostingへデプロイする

プロジェクトを発行後、Firebase CLIから `firebase deploy` コマンドでデプロイします。
デプロイ後、アプリケーションのURLを開き動作を確認します。

## Firebase Databaseの状態

データが追加されたFirebase Databaseには、ツリー上にデータが出来上がっているのが確認できます。

![firebase-database.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110304/46df7934-189a-e420-1d42-aaa5eb9a6252.png)

## うまく動作しない時は

Blazorで作成したクライアントアプリケーションのデバッグは容易ではありません。そこで、ソースでは`Console.WriteLine()`を使いログを吐き出し、Chromeなどのデバッグ画面を使って、コンソールログを確認します。
![consolelog.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/110304/81031cbe-7aba-ca63-5a7f-5bef2809b16b.jpeg)


# 関連記事
[Blazorで作成したウェブサイトをFirebaseで公開する](https://qiita.com/nobu17/items/67c11cf622f2e4721635)
[C#でFirebaseを使ってみよう！（１）　FirebaseとEmail-Password認証](http://kmycode.hatenablog.jp/entry/2017/02/09/205655)
[C#でFirebaseを使ってみよう！（３）　Databaseを使ってみる](http://kmycode.hatenablog.jp/entry/2017/02/19/182704)