BlazorアプリからPostGraphile（GraphQL）とTelerikのGridコンポーネントを使ってみた

# 記事の内容

- Visual Studio 2019でBlazorアプリのテンプレートを選んでプロジェクトを新規作成すると、データソースがjsonファイルになりましたが、それを[PostGraphile](https://www.graphile.org/postgraphile/)（GraphQL API）に置き換えます。
- その後、[Telerik UI for Blazor](https://www.telerik.com/blazor-ui)（有償商品です）のGridコンポーネントを使用して、データをグリッドで一覧表示するWebページを追加します。サードパーティーのコンポーネントを導入すると、定型的な処理の開発時間を大幅に短縮でき、プログラマが追加でコードを記述すれば小回りも利きますので、ローコードの感覚になります。

※ PostGraphileのトップページに「No N+1 problem」と書かれています。いいですね～
※ [Hasura](https://hasura.io/)もPostGraphileと類似のプロダクトと認識していますが、本記事では私が利用経験のあるPostGraphileを使用しました。
※ 私はTelerikの回し者ではありません。

## ソースコード

[GitHub](https://github.com/kanedaq/SamplePostGraphile_sol)に置きました。

## 参考ページ（感謝します）

[C#（ASP.NET Core）で GraphQL API を提供する](https://miso-soup3.hateblo.jp/entry/2017/11/30/162445)

# Blazorアプリのプロジェクトを新規作成する

Visual Studio 2019のプロジェクト新規作成画面で、以下のようにBlazorアプリのテンプレートを選択します。

![a01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/c50668bd-a478-bbc7-28cf-0f00e5dd294b.png)

プロジェクト名を「SamplePostGraphile」、ソリューション名を「SamplePostGraphile_sol」にしましたが、名前は何でも良いです。

![a02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/21cc697b-1530-a959-c559-df3e9fb616cb.png)

Blazor WebAssembly Appを選択します。今回はhttpsは外しました。

![a03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/b34944d9-f20c-8c9e-d42c-69ab2b6242b6.png)

以下のファイルが自動生成されました。
ソースコードを読むと、データソースとしてwwwroot\sample-data\weather.jsonが使用されています。

![a04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/4fcf2adf-2894-7303-eb69-ab9696506ec2.png)

このままビルドして動かしてみます。
左メニューから「Fetch data」を選択すると、以下のように右側にjsonファイルのデータが一覧表示されました。

![a05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/2849120c-0367-1a7f-7007-a58a3078f397.png)

この時点でgit commitしました。
手順は、Visual Studioのgitメニューからgitリポジトリを作成し、GitHubにpushしました。
以下のコミットメッセージは、Visual Studioが自動生成したものです。

![a55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/313b74bf-8195-6b2b-d06c-e5b3f109658b.png)

# PostgreSQLにデータを用意し、PostGraphileを立ち上げる

## ERモデリングツールでの作業

ERモデリングツールは[A5:SQL Mk-2](https://a5m2.mmatsubara.com/)を使用します。

それでは、BlazorアプリのデータソースをPostGraphileに置き換えます。
以下のjsonファイルの中を見ながら、これと類似のテストデータをPostgreSQLに用意します。

```wwwroot\sample-data\weather.json

[
  {
    "date": "2018-05-06",
    "temperatureC": 1,
    "summary": "Freezing"
  },
  {
    "date": "2018-05-07",
    "temperatureC": 14,
    "summary": "Bracing"
  },
  {
    "date": "2018-05-08",
    "temperatureC": -13,
    "summary": "Freezing"
  },
  {
    "date": "2018-05-09",
    "temperatureC": -16,
    "summary": "Balmy"
  },
  {
    "date": "2018-05-10",
    "temperatureC": -2,
    "summary": "Chilly"
  }
]
```

以下のER図を描きました。エンティティ1つだけですね。

![a10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/2bc3e939-c1e7-662b-6e37-adf4d3762f6b.png)

ER図メニューから「DDLを作成する」を選択します。

![a11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/580e4a58-3c1e-afa8-7c47-79e72c0f8d5c.png)

RDBMS種類でPostgreSQLを選択し、DDL生成ボタンを押します。

![a12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/2756a184-d9f9-db3c-3fcc-7085699f6f29.png)

以下のDDLが生成されました。

```sql

-- RDBMS Type   : PostgreSQL
-- Application  : A5:SQL Mk-2

/*
  BackupToTempTable, RestoreFromTempTable疑似命令が付加されています。
  これにより、drop table, create table 後もデータが残ります。
  この機能は一時的に $$TableName のような一時テーブルを作成します。
*/

-- WeatherForecast
--* BackupToTempTable
DROP TABLE if exists weather_forecasts CASCADE;

--* RestoreFromTempTable
CREATE TABLE weather_forecasts (
  id integer NOT NULL
  , dt date NOT NULL
  , temperature_c double precision NOT NULL
  , summary character varying NOT NULL
  , CONSTRAINT weather_forecasts_PKC PRIMARY KEY (id)
) ;

COMMENT ON TABLE weather_forecasts IS 'WeatherForecast';
COMMENT ON COLUMN weather_forecasts.id IS 'Id';
COMMENT ON COLUMN weather_forecasts.dt IS 'Date';
COMMENT ON COLUMN weather_forecasts.temperature_c IS 'TemperatureC';
COMMENT ON COLUMN weather_forecasts.summary IS 'Summary';
```

## PostgreSQLの作業

本記事ではLinux上のPostgreSQLを使用します。
psqlを起動します。

```

psql --host=localhost --username=postgres --password
```

![a09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/7f3aa0ff-dbe6-20cd-abdc-6b83838dbca5.png)

データベースを作成します。名前を「sample_db」にしましたが、何でも良いです。

```sql

CREATE DATABASE sample_db;
```

![a13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/5c542d5f-80a4-12d1-3129-503b02fde128.png)

カレントデータベースを、作成したsample_dbに切り替えます。

```
\c sample_db
```

![a14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/199852b4-9249-3385-bc68-2e441bf918cf.png)

先ほどERモデリングツールが生成したDDLをpsqlにコピペして実行します。

![a15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/c5a76dc0-911e-ddd3-f71d-c9f145a96b91.png)

以下のINSERT文を流して、2000年1月1日から150日分のテストデータを作成します。
テーブルにはidの降順でINSERTしてみます。

```sql

INSERT INTO weather_forecasts (
    id,
    dt,
    temperature_c,
    summary
)
SELECT
    id,
    ('1999-12-31'::DATE + (id::TEXT || ' days')::INTERVAL)::DATE AS dt,
    (random() * 75 - 20)::INT AS temperature_c,
    CASE (random() * 1000)::INT % 10
        WHEN 0 THEN 'Freezing'
        WHEN 1 THEN 'Bracing'
        WHEN 2 THEN 'Chilly'
        WHEN 3 THEN 'Cool'
        WHEN 4 THEN 'Mild'
        WHEN 5 THEN 'Warm'
        WHEN 6 THEN 'Balmy'
        WHEN 7 THEN 'Hot'
        WHEN 8 THEN 'Sweltering'
        WHEN 9 THEN 'Scorching'
    END AS summary
FROM
    generate_series(1, 150) AS id
ORDER BY id DESC;
```

![a16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/86580e77-2bc5-cbe5-75ae-59a8c8fb916e.png)

以下のSELECT文を流して、データが作成されたか確認します。

```sql

SELECT
    *
FROM
    weather_forecasts;
```

以下のようにidの降順で表示されましたが、順番に意味はありません。

![a17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/7b0b6a6c-6f69-faec-5d12-a96f5c49b52c.png)

psqlから抜けます。

![a18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/c127c795-8346-9b8b-fabb-9c6f56a80306.png)

## PostGraphileの作業

本記事ではPostGraphileをPostgreSQLと同じLinuxホストにインストールします。
[このページ](https://www.graphile.org/postgraphile/usage-cli/)を参考にして、PostGraphileをインストール＆起動します。
Dockerを使う方法もあります。

インストール

```

npm install -g postgraphile
```

起動コマンド例

```

postgraphile --connection postgres://postgres:secret@localhost/sample_db --port 15000 --schema public --export-schema-graphql ~/schema.graphql --cors

```

起動画面

![a28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/a8f4b61d-00d2-d7ca-ca10-0e8823142949.png)

本記事ではBlazorアプリでのCORSエラーを避けるために、単に「--cors」オプションを付けてPostGraphileを起動しましたが、本番環境では安全な方法でCORSエラーを回避してください。

postgraphileコマンドを起動するだけで、PostgreSQLのスキーマを読み取ってGraphQLエンドポイントを自動生成してくれます。
とても楽で、これもノーコードと言えるかもしれません。

起動画面によれば、URLは

- GraphQLエンドポイント：http://localhost:15000/graphql
- GraphiQL：http://localhost:15000/graphiql

となっています。
本記事では、このLinuxホストのIPアドレスは「192.168.1.7」です。
GraphQLエンドポイントのURLの「localhost」を「192.168.1.7」に書き換えて、後ほどBlazorアプリで使用します。

ここでブラウザからGraphiQLにアクセスして、クエリーを発行したりドキュメントを見たりしてみましょう。

クエリー例

```graphql

query allWeatherForecasts {
  allWeatherForecasts {
    nodes {
      id
      dt
      temperatureC
      summary
    }
  }
}
```

ブラウザ画面

![a27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/7ccf26a9-a361-f42a-5038-1be95e600e2b.png)

レスポンス

```

{
  "data": {
    "allWeatherForecasts": {
      "nodes": [
        {
          "id": 1,
          "dt": "2000-01-01",
          "temperatureC": 7,
          "summary": "Hot"
        },
        {
          "id": 2,
          "dt": "2000-01-02",
          "temperatureC": -16,
          "summary": "Cool"
        },
        {
          "id": 3,
          "dt": "2000-01-03",
          "temperatureC": 17,
          "summary": "Hot"
        },

        （中略）

        {
          "id": 150,
          "dt": "2000-05-29",
          "temperatureC": 14,
          "summary": "Freezing"
        }
      ]
    }
  }
}
```

psqlからSELECT文を実行したときはid列の降順で表示されましたが、今回のレスポンスを見ると昇順になっていますね。
この順番は気にしないことにして、先に進みます。

# BlazorアプリのデータソースをjsonファイルからPostGraphileに置き換える

Visual Studioでの作業に戻ります。

## ファイル削除：wwwroot\sample-data\weather.json

Blazorアプリのデータソースは「wwwroot\sample-data\weather.json」でしたが、もう使用しませんのでsample-dataディレクトリごと削除します。
削除後のファイルは以下の通り。

![a07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/31335fb7-b514-224b-0bd8-f1843d53ddb1.png)

## パッケージのインストール

NuGetで以下の3パッケージをインストールします。

![a29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/62f7fc32-b115-d2ea-ae6b-3df380c29067.png)

## ファイル新規作成：Shared/WeatherForecast.cs

GraphQL APIのレスポンスデータを格納するデータ構造を作成します。
Sharedディレクトリ配下に「WeatherForecast.cs」を追加します。

![a31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/28363a57-b002-fc0e-4d51-ed436ec736bc.png)

![a32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/4b2db9d7-e35b-f62c-ca72-3b58801b39fa.png)

以下の内容にします。

```Shared/WeatherForecast.cs

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace SamplePostGraphile.Shared
{
    // クエリー
    // query allWeatherForecasts {
    //   allWeatherForecasts {
    //     nodes {
    //       id
    //       dt
    //       temperatureC
    //       summary
    //     }
    //   }
    // }
    public class WeatherForecast
    {
        public int Id { get; set; }

        public DateTime Dt { get; set; }

        private double _tempC;
        public double TemperatureC
        {
            get
            {
                return _tempC;
            }
            set
            {
                _tempC = value;
            }
        }

        public double TemperatureF
        {
            get
            {
                return 32 + (_tempC / 0.5556);
            }
            set
            {
                _tempC = (value - 32) * 0.5556;
            }
        }

        public string Summary { get; set; }

        public WeatherForecast()
        {
            Dt = DateTime.Now.Date;
        }
    }

    // レスポンス例
    // {
    //   "data": {
    //     "allWeatherForecasts": {
    //       "nodes": [
    //         {
    //           "id": 1,
    //           "dt": "2000-01-01",
    //           "temperatureC": 7,
    //           "summary": "Hot"
    //         },
    //         {
    //           "id": 2,
    //           "dt": "2000-01-02",
    //           "temperatureC": -16,
    //           "summary": "Cool"
    //         },
    //
    //         （中略）
    //
    //       ]
    //     }
    //   }
    // }
    public class AllWeatherForecastsResponse
    {
        public AllWeatherForecastsContent allWeatherForecasts { get; set; }

        public class AllWeatherForecastsContent
        {
            public List<WeatherForecast> Nodes { get; set; }
        }
    }
}
```

## ファイル新規作成：Services/WeatherForecastService.cs

GraphQLクエリーを発行して、そのレスポンスからデータを取り出してリターンするメソッドを持つクラスを作成します。
本記事では、CRUDのうちR（Read）のみ実装しました。
プロジェクト配下に「Services」というディレクトリを作成します。

![a22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/792a5a9d-5124-f89e-5809-927aa32e0b53.png)

Servicesディレクトリ配下に「WeatherForecastService.cs」を追加します。

![a23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/6f900373-eaa4-3f9a-e593-0e6ac140a43d.png)

以下の内容にします。

```SamplePostGraphile/Services/WeatherForecastService.cs

using SamplePostGraphile.Shared;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using GraphQL.Client.Http;
using GraphQL.Client.Serializer.Newtonsoft;
using GraphQL;

namespace SamplePostGraphile.Services
{
    public class WeatherForecastService
    {
        // 行儀が良くないですが、今回はここにGraphQLエンドポイントのURLを書いてしまいます
        private const string graphql_http = "http://192.168.1.7/15000/graphql";

        public async Task<List<WeatherForecast>> GetForecastListAsync()
        {
            using var graphQLClient = new GraphQLHttpClient(graphql_http, new NewtonsoftJsonSerializer());
            var allWeatherForecasts = new GraphQLRequest
            {
                Query = @"
query allWeatherForecasts {
  allWeatherForecasts {
    nodes {
      id
      dt
      temperatureC
      summary
    }
  }
}
",
                OperationName = "allWeatherForecasts",
            };
            var graphQLResponse = await graphQLClient.SendQueryAsync<AllWeatherForecastsResponse>(allWeatherForecasts);
            return graphQLResponse.Data.allWeatherForecasts.Nodes;
        }

        //public async Task UpdateForecastAsync(WeatherForecast forecastToUpdate)
        //{
        //    未実装
        //}

        //public async Task DeleteForecastAsync(WeatherForecast forecastToRemove)
        //{
        //    未実装
        //}

        //public async Task InsertForecastAsync(WeatherForecast forecastToInsert)
        //{
        //    未実装
        //}
    }
}
```

## 変更：Program.cs

プロジェクト内でWeatherForecastServiceクラスを使えるようにします。
変更内容は以下の通りです。

```Program.cs

+ using SamplePostGraphile.Services;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;

namespace SamplePostGraphile
{
    public class Program
    {
        public static async Task Main(string[] args)
        {
            var builder = WebAssemblyHostBuilder.CreateDefault(args);
            builder.RootComponents.Add<App>("app");

            builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
+             builder.Services.AddScoped<WeatherForecastService>();

            await builder.Build().RunAsync();
        }
    }
}
```

## 変更：Pages/FetchData.razor

データソースをjsonファイルからPostGraphileに置き換えるようにソースコードを変更します。
変更内容は以下の通りです。

```Pages/FetchData.razor

@page "/fetchdata"
- @inject HttpClient Http
+ @using SamplePostGraphile.Shared
+ @using SamplePostGraphile.Services
+ @inject WeatherForecastService ForecastService

<h1>Weather forecast</h1>

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

@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.Dt.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
-     private WeatherForecast[] forecasts;
+     List<WeatherForecast> forecasts { get; set; }

    protected override async Task OnInitializedAsync()
    {
-         forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>("sample-data/weather.json");
+         await GetForecasts();
    }

+     async Task GetForecasts()
+     {
+         forecasts = await ForecastService.GetForecastListAsync();
+     }
-     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);
-     }
}
```

ビルドして動かしてみます。
左メニューから「Fetch data」を選択すると、以下のように右側にPostgreSQLデータベースから取得したデータが一覧表示されました。

![a33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/2ea73e1d-a5f7-0cc0-498f-35152e51f9d3.png)

以上でデータソースの置き換えは完了です。
この時点でgit commitしました。

```

git add -A
git commit -m "(1)データソースをweather.jsonからPostGraphileに変更します"
```

ここまでのソースコードは、Telerikのコンポーネントがなくてもビルド／実行できます。

# Telerik UI for BlazorのGridコンポーネントを使ってデータを一覧表示する

これ以降は、Telerikのプロダクトがインストールされた環境で作業します。

## プロジェクトをTelerik UI for Blazorのアプリケーションにコンバートする

以下のように、Visual Studioの拡張機能メニューからTelerikアプリケーションにコンバートします。

![a51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/ed70191e-6a1a-6985-ee2b-a6cf2b08b0d6.png)

NuGetパッケージの管理画面で、Telerik.UI.for.Blazorがインストールされたことを確認します。

![a52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/6f5c41f2-61b1-ee03-6a1f-4909cbe99dd3.png)

コンバート完了時点で、一旦git commitしました。

```

git add -A
git commit -m "(2)プロジェクトをTelerikアプリケーションにコンバートします"
```

## ファイル新規作成：Pages/Grid.razor

TelerikのGridコンポーネントを使用して、データを一覧表示するページを作成します。
Pagesディレクトリ配下に「Grid.razor」を追加します。

![a42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/2465841b-5610-1a51-8a22-7bdbb2bb6c3b.png)

![a43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/312f2b48-0628-4d77-20d0-4dbadef95b1a.png)

以下の内容にします。

```Pages/Grid.razor

@page "/grid"
@using SamplePostGraphile.Shared
@using SamplePostGraphile.Services
@inject WeatherForecastService ForecastService

<div class="container-fluid">
    <div class='row my-4'>
        <div class='col-12 col-lg-9 border-right'>
            <TelerikGrid Data="@forecasts" Height="550px" FilterMode="@GridFilterMode.FilterMenu"
                         Sortable="true" Pageable="true" PageSize="20" Groupable="true" Resizable="true" Reorderable="true"
                         OnUpdate="@UpdateHandler" OnDelete="@DeleteHandler" OnCreate="@CreateHandler" EditMode="@GridEditMode.Inline">
                <GridColumns>
                    <GridColumn Field="Id" Title="Id" Width="100px" Editable="false" Groupable="false" />
                    <GridColumn Field="Dt" Title="Date" Width="220px" DisplayFormat="{0:dddd, dd MMM yyyy}" />
                    <GridColumn Field="TemperatureC" Title="Temp. C" Width="100px" DisplayFormat="{0:N1}" />
                    <GridColumn Field="TemperatureF" Title="Temp. F" Width="100px" DisplayFormat="{0:N1}" />
                    <GridColumn Field="Summary" />
                    <GridCommandColumn Width="200px" Resizable="false">
                        <GridCommandButton Command="Save" Icon="@IconName.Save" ShowInEdit="true">Update</GridCommandButton>
                        <GridCommandButton Command="Edit" Icon="@IconName.Edit" Primary="true">Edit</GridCommandButton>
                        <GridCommandButton Command="Delete" Icon="@IconName.Delete">Delete</GridCommandButton>
                        <GridCommandButton Command="Cancel" Icon="@IconName.Cancel" ShowInEdit="true">Cancel</GridCommandButton>
                    </GridCommandColumn>
                </GridColumns>
                <GridToolBar>
                    <GridCommandButton Command="Add" Icon="@IconName.Plus" Primary="true">Add Forecast</GridCommandButton>
                    <GridCommandButton Command="ExcelExport" Icon="@IconName.FileExcel">Export to Excel</GridCommandButton>
                </GridToolBar>
                <GridExport>
                    <GridExcelExport FileName="weather-forecasts" AllPages="true" />
                </GridExport>
            </TelerikGrid>
        </div>
        <div class='col-12 col-lg-3 mt-3 mt-lg-0'>
            <h3>Telerik UI for Blazor Grid</h3>
        </div>
    </div>
</div>

@code {
    List<WeatherForecast> forecasts { get; set; }

    protected override async Task OnInitializedAsync()
    {
        await GetForecasts();
    }

    async Task GetForecasts()
    {
        forecasts = await ForecastService.GetForecastListAsync();
    }

    public async Task DeleteHandler(GridCommandEventArgs args)
    {
        //WeatherForecast currItem = args.Item as WeatherForecast;

        //await ForecastService.DeleteForecastAsync(currItem);

        //await GetForecasts();
    }

    public async Task CreateHandler(GridCommandEventArgs args)
    {
        //WeatherForecast currItem = args.Item as WeatherForecast;

        //await ForecastService.InsertForecastAsync(currItem);

        //await GetForecasts();
    }

    public async Task UpdateHandler(GridCommandEventArgs args)
    {
        //WeatherForecast currItem = args.Item as WeatherForecast;

        //await ForecastService.UpdateForecastAsync(currItem);

        //await GetForecasts();
    }
}
```

## 変更：Shared/NavMenu.razor

実行時の左メニューにGridを追加します。
変更内容は以下の通りです。

```Shared/NavMenu.razor

<div class="top-row pl-4 navbar navbar-dark">
    <a class="navbar-brand" href="">SamplePostGraphile</a>
    <button class="navbar-toggler" @onclick="ToggleNavMenu">
        <span class="navbar-toggler-icon"></span>
    </button>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <ul class="nav flex-column">
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="counter">
                <span class="oi oi-plus" aria-hidden="true"></span> Counter
            </NavLink>
        </li>
        <li class="nav-item px-3">
            <NavLink class="nav-link" href="fetchdata">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
            </NavLink>
        </li>
+         <li class="nav-item px-3">
+             <NavLink class="nav-link" href="grid">
+                 <span class="oi oi-grid-four-up" aria-hidden="true"></span> Grid
+             </NavLink>
+         </li>
    </ul>
</div>

@code {
    private bool collapseNavMenu = true;

    private string NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}
```

ビルドして動かしてみます。
以下のように左メニューに「Grid」が追加されました。これを選択すると、右側にTelerikのGridコンポーネントでデータが一覧表示されました。

![a53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/7dc66040-e3c4-556e-0a51-190cb48ccd8d.png)

CRUDのうちRしか実装していませんが、試しに任意の行のEditボタンを押してDate列の右端をクリックしてみます。以下のようにカレンダー入力が出てきました。

![a54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/a62c9e5a-3505-424b-2d56-4e89e9955fee.png)

以上で作業が完了しましたので、git commitしました。

```

git add -A
git commit -m "(3)Grid.razorページを追加します"
```

GitHubにもpushしました。

![a56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/327312/1c1d71bb-776d-fd13-02ce-5740e19bec27.png)

# 今後

[TelerikのGridコンポーネント](https://www.telerik.com/blazor-ui/grid)は機能がリッチだそうですので、深堀りしてみたいですね。
サードパーティーのコンポーネントに習熟すれば、ノーコードに劣らないスピード感でアプリを開発できそうです。
むしろ、数多あるNoCodeから適切なものを選ぶ→NoCodeで開発する→場合によってはYesCodeで作り直す、というステップを踏むより負担が少ない気がします。

以上です。