Blazor WebAssemblyでRestSharpを使ってみる（その2）

## はじめに
* 前回の「[Blazor WebAssemblyでRestSharpを使ってみる（その1）](https://qiita.com/HAGITAKO/items/809fb6667c8704d002e9)」の続き
* RestSharpを使って、ユカイ工学さんが提供している「**BOCCO emo**」のAPIを使用してみます。


## RestSharp
* [RestSharp](https://github.com/restsharp/RestSharp)

> 注意！！
> v107以降で記述方法が大分変更となっています。
> 現在は、V108です。

* そのため、Postmanで表示されるコードは、使用できません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/747a7127-867a-6ecf-311e-5dad66fdb000.png)

## BOCCO emo
* ユカイ工学さんが提供しているコミュニケーションロボットです。
* とってもかわいいロボットなんですが、法人向けにAPIが用意されています。
    * 使用するには、法人アカウントを取得する必要があります。
    * APIには、メッセージをおくるとBoccoがしゃべったり、モーションをさせたり、センサーの情報を取得できたりします。
> BOCCO emo API

https://platform-api.bocco.me/api-docs/#overview

> BOCCO emo
 
https://www.bocco.me/

## 開発環境
- Visual Studio 2022 (C#)
- [MudBlazor 6.0.9](https://github.com/MudBlazor/MudBlazor/)
- [MudBlazor/Template](https://github.com/MudBlazor/Templates)
- [RestSharp v108](https://github.com/restsharp/RestSharp)

## RestSharpの適用
* **.Server**を右クリックして、Nugetパッケージの管理 を選択
    * **RestSharp**と入力、その際、プレリリースを含めるのチェックボックスはオフにする。

> 前回は、Clientで追加していますが、今回は、ServerにRestSharpを適用しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/b0c08f0c-b449-3f00-6e7e-6514706e6eb0.png)

## Newton.Jsonの適用
* **.Server**を右クリックして、Nugetパッケージの管理 を選択
    * **Newtonsoft.json**と入力、その際、プレリリースを含めるのチェックボックスはオフにする。

> 別にNewtonsoft.jsonじゃなくてもよいのですが、使い慣れているので、適用しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/c0890b7b-0e65-3385-846a-fc39e0000270.png)




## 実装
* ClientのPagesに新しいRazorコンポ―ネットを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/ab3ac6de-acc0-683c-51ec-97bc3988c591.png)

* BoccoSensor.razorを作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/c2b7a6af-51df-23fe-57f9-58b9e23eb8d5.png)

### アクセストークンの取得処理

#### モデルの作成
##### データモデルの作成
* ***.Shared**のプロジェクトを右クリックして、追加 ＞ クラスで「**ApiTokenModel**」を作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/1549e8d1-2747-da1a-3317-b2f00b423464.png)

* データモデル
    * internalをpublicへ変更

```diff_c_sharp:ApiTokenModel.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ApexChartsSample.Shared
{
-    internal class ApiTokenModel
+    public class ApiTokenModel
    {
+        public string access_token { get; set; }
+        public string refresh_token { get; set; }
    }
}

```

##### レスポンスモデルの作成
* ***.Shared**のプロジェクトを右クリックして、追加 ＞ クラスで「**BoccoApiTokenResponse**」を作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/ef35572f-b246-0763-c3c6-96befaccf4bc.png)

* レスポンスモデル
    * internalをpublicへ変更

```diff_c_sharp:BoccoApiTokenResponse.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ApexChartsSample.Shared
{
-    internal class BoccoApiTokenResponse
+    public class BoccoApiTokenResponse
    {
+        /// <summary>
+        /// 処理結果
+        /// </summary>
+        public bool IsSucceed { get; set; }
+
+        /// <summary>
+        /// メッセージ
+        /// </summary>
+        public string? ResponseMessage { get; set; }
+
+        /// <summary>
+        /// APIトークン
+        /// </summary>
+        public ApiTokenModel? ApiToken { get; set; }
    }
}


```

#### リポジトリの作成
> もしかしたら、この手順は不要かもしれませんが、いままでWPFで作ってたやり方をBlazorでそのまま使用しているので、インターフェース等を作っちゃってます。

* ***.Server**のプロジェクトを右クリックして、追加 ＞ 新しいフォルダ―で「**Repositories**」を作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/f809dce5-4271-8daa-3916-c19e23767aad.png)

* 「**Repositories**」を右クリックして、追加 ＞ 新しいフォルダ―で「**Interface**」を作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/1b9d39af-f28b-fdac-15de-0c1c4850fcc6.png)

* 「**Interface**」を右クリックして、追加 ＞ 新規項目 ＞ インターフェースで「IBoccoServiceRepository」を作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/0a52e251-bb86-9508-5dfb-b0abb49f70e2.png)

* IBoccoServiceRepositoryの記述

```diff_c_sharp:IBoccoServiceRepository.cs
using ApexChartsSample.Shared;

namespace ApexChartsSample.Server.Repositories.Interface
{
    public interface IBoccoServiceRepository
    {
+        /// <summary>
+        /// アクセストークンの取得
+        /// </summary>
+        /// <returns></returns>
+        public Task<BoccoApiTokenResponse> GetAccessToken();
    }
}

```

* 「**Repositories**」を右クリックして、追加 ＞ クラスで、「BoccoServiceRepository」を作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/aabd3184-2378-eddc-bb1b-1df1e6344263.png)

* BoccoServiceRepositoryの記述
    * IBoccoServiceRepositoryを継承

```diff_c_sharp:BoccoServiceRepository.cs
+ using ApexChartsSample.Shared;
+ using ApexChartsSample.Server.Repositories.Interface;
+ using RestSharp;
+ using Newtonsoft.Json;

namespace ApexChartsSample.Server.Repositories
{
-    public class BoccoServiceRepository
+    public class BoccoServiceRepository : IBoccoServiceRepository
    {
+        // Bocco API リフレッシュトークン
+        private readonly string _refreshToken = "{法人アカウントで割り振られたリフレッシュトークン}";

+        /// <summary>
+        /// Boccoアクセストークンの取得
+        /// </summary>
+        /// <returns>トークンモデル</returns>
+        public async Task<BoccoApiTokenResponse> GetAccessToken()
+        {
+            var restClient = new RestClient();
+            var url = "https://platform-api.bocco.me/oauth/token/refresh";
+            var request = new RestRequest(url, Method.Post);
+            request.AddHeader("Content-Type", "application/json");
+            
+            var body = new { refresh_token = _refreshToken };
+            var bodyJson = JsonConvert.SerializeObject(body);
+            request.AddBody(bodyJson, "application/json");
+            RestResponse response = await restClient.ExecuteAsync(request);
+
+            var model = new ApiTokenModel();
+            var returnModel = new BoccoApiTokenResponse();
+            if (response.IsSuccessful)
+            {
+                model = JsonConvert.DeserializeObject<ApiTokenModel>(response.Content);
+                returnModel.IsSucceed = true;
+                returnModel.ResponseMessage = response.StatusDescription;
+                returnModel.ApiToken = model;
+            }
+            else
+            {
+                model = null;
+
+                returnModel.IsSucceed = false;
+                returnModel.ResponseMessage = response.ErrorMessage;
+                returnModel.ApiToken = model;
+            }
+
+            return returnModel;
+        }
    }
}

```

#### 補足：Postmanの場合
* PostmanでRestsharp変換を見ると

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/cb6db8dc-306b-cabc-d3e0-20a22b32ff3e.png)

* AddParameterの記述の仕方等が異なることがわかるかと思います。
* なので、Postmanでテストしてそのままコードが流用できないのが残念。。。。

```csharp:Postmanのコード変換機能の場合
var client = new RestClient("https://platform-api.bocco.me/oauth/token/refresh");
client.Timeout = -1;
var request = new RestRequest(Method.POST);
request.AddHeader("Content-Type", "application/json");
var body = @"{""refresh_token"": ""{法人アカウントで割り振られたリフレッシュトークン}""}";
request.AddParameter("application/json", body,  ParameterType.RequestBody);
IRestResponse response = client.Execute(request);
Console.WriteLine(response.Content);
```



#### コントローラーの作成
* ***.Server**のプロジェクトを右クリックして、追加 ＞ 新しい項目で「**BoccoServiceController**」を作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/1e6c83f1-7032-0bb8-fc62-2af49873ecde.png)

* ちょっと変則な処理ですが、コントローラー側では、GetでRepositoryでは、Postになっているのは、Repository側でPostパラメータを処理しているため、そのような形にしました。

```diff_c_sharp:BoccoServiceController.cs
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
+ using ApexChartsSample.Server.Repositories;
+ using ApexChartsSample.Server.Repositories.Interface;
+ using ApexChartsSample.Shared;

namespace ApexChartsSample.Server.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class BoccoServiceController : ControllerBase
    {
+        private readonly ILogger<BoccoServiceController> _logger;
+
+        private IBoccoServiceRepository boccoServiceRepository;
+
+        public BoccoServiceController(ILogger<BoccoServiceController> logger)
+        {
+            _logger = logger;
+            this.boccoServiceRepository = new BoccoServiceRepository();
+        }
+
+        /// <summary>
+        /// アクセストークン取得
+        /// </summary>
+        /// <returns></returns>
+        [HttpGet("/GetAccessToken")]
+        public async Task<BoccoApiTokenResponse> GetAccessToken()
+        {
+            var returnModel = new BoccoApiTokenResponse();
+            returnModel = await boccoServiceRepository.GetAccessToken();
+            return returnModel;
+        }
    }
}
```

#### ページデザイン
* デザインを作成
    * MudOverlayは、MudBlazorのページ読み込み時等に使用できるモーダルパーツでLoadingを表示したいときに使用してます。

```diff_html:BoccoSensor.razor（デザイン部分）

@page "/BoccoSensor"

@using ApexChartsSample.Shared

@using RestSharp
@inject RestClient _restClient;
@inject NavigationManager _navigationManager
@inject ISnackbar Snackbar;

<PageTitle>部屋センサー ログ</PageTitle>

<MudText Typo="Typo.h4" GutterBottom="true">部屋センサー ログ</MudText>
<div class="d-flex flex-grow-1 flex-row">
    <MudGrid>
        <MudItem xs="12" >
            <MudPaper Class="d-flex">
                <MudTextField @bind-Value="_accessToken" Label="Read Only" ReadOnly="true" Variant="Variant.Text" />
                <MudButton OnClick="@(() => GetToken())" Variant="Variant.Outlined" Color="MudBlazor.Color.Secondary" Size="Size.Small" >トークン取得</MudButton>
            </MudPaper>
        </MudItem>
    </MudGrid>
</div>

<MudOverlay @bind-Visible="_loading" DarkBackground="true" ZIndex="9999" AutoClose="false">
    <center><MudProgressCircular Color="MudBlazor.Color.Primary" Size="Size.Large" Indeterminate="true"/></center>
    <MudText Typo="Typo.h1" Color="MudBlazor.Color.Dark">Loading...</MudText>
</MudOverlay>
```

* 処理部分
```diff_html:BoccoSensor.razor（処理部分）
@code {
+    /// <summary>
+    /// 画面抑止用
+    /// </summary>
+    private bool _loading;
+
+    /// <summary>
+    /// アクセストークン
+    /// </summary>
+    public string _accessToken { get; set; } = String.Empty;
+
+    /// <summary>
+    /// 初期処理
+    /// </summary>
+    /// <returns></returns>
+    protected override async Task OnInitializedAsync()
+    {
+        _loading = true;
+
+        try
+        {
+            await Task.Delay(2000);
+        }
+        catch (Exception ex)
+        {
+            Snackbar.Add(ex.Message, Severity.Error);
+        }
+        finally
+        {
+            _loading = false;
+        }
+    }
+
+    /// <summary>
+    /// アクセストークンの取得
+    /// </summary>
+    /// <returns></returns>
+    private async Task GetToken()
+    {
+        _loading = true;
+        try
+        {
+            var returnModel = new BoccoApiTokenResponse();
+            var url = _navigationManager.BaseUri + "GetAccessToken";
+            returnModel = await _restClient.GetJsonAsync<BoccoApiTokenResponse>(url);
+
+            if(returnModel.IsSucceed == true)
+            {
+                _accessToken = returnModel.ApiToken.access_token;
+                StateHasChanged();
+            }
+        }
+        catch (Exception ex)
+        {
+            Snackbar.Add(ex.Message, Severity.Error);
+        }
+        finally
+        {
+            _loading = false;
+        }
+    }
}
```

* ナビゲーションの追加
    * 先ほど作成したページへ遷移できるようにナビゲーションメニューに項目を追加

```diff_html:NavMenu.razor
<MudNavMenu>
    <MudNavLink Href="" Match="NavLinkMatch.All" Icon="@Icons.Material.Filled.Home">Home</MudNavLink>
    <MudNavLink Href="counter" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Add">Counter</MudNavLink>
    <MudNavLink Href="fetchdata" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.List">Fetch data</MudNavLink>
    
+    <MudNavLink Href="BoccoSensor" Match="NavLinkMatch.Prefix" Icon="@Icons.Material.Filled.Sensors">部屋センサー</MudNavLink>
</MudNavMenu>

```

## デバッグ実行
* デバッグを実行し、ナビゲーションからトークン取得ボタンを押下すると、InputBoxにトークンが無事表示されていることが確認できました。

![デバッグ.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/90597/03972240-913c-d3b9-e035-5ff0d0d90a3d.gif)

## 補足
* タイトルの部屋センサーの取得までやろうと思ったのですが、一旦トークンの取得までで投稿しました。