記事の内容
- Visual Studio 2019でBlazorアプリのテンプレートを選んでプロジェクトを新規作成すると、データソースがjsonファイルになりましたが、それをPostGraphile(GraphQL API)に置き換えます。
- その後、Telerik UI for Blazor(有償商品です)のGridコンポーネントを使用して、データをグリッドで一覧表示するWebページを追加します。サードパーティーのコンポーネントを導入すると、定型的な処理の開発時間を大幅に短縮でき、プログラマが追加でコードを記述すれば小回りも利きますので、ローコードの感覚になります。
※ PostGraphileのトップページに「No N+1 problem」と書かれています。いいですね~
※ HasuraもPostGraphileと類似のプロダクトと認識していますが、本記事では私が利用経験のあるPostGraphileを使用しました。
※ 私はTelerikの回し者ではありません。
ソースコード
GitHubに置きました。
参考ページ(感謝します)
C#(ASP.NET Core)で GraphQL API を提供する
Blazorアプリのプロジェクトを新規作成する
Visual Studio 2019のプロジェクト新規作成画面で、以下のようにBlazorアプリのテンプレートを選択します。
プロジェクト名を「SamplePostGraphile」、ソリューション名を「SamplePostGraphile_sol」にしましたが、名前は何でも良いです。
Blazor WebAssembly Appを選択します。今回はhttpsは外しました。
以下のファイルが自動生成されました。
ソースコードを読むと、データソースとしてwwwroot\sample-data\weather.jsonが使用されています。
このままビルドして動かしてみます。
左メニューから「Fetch data」を選択すると、以下のように右側にjsonファイルのデータが一覧表示されました。
この時点でgit commitしました。
手順は、Visual Studioのgitメニューからgitリポジトリを作成し、GitHubにpushしました。
以下のコミットメッセージは、Visual Studioが自動生成したものです。
PostgreSQLにデータを用意し、PostGraphileを立ち上げる
ERモデリングツールでの作業
ERモデリングツールはA5:SQL Mk-2を使用します。
それでは、BlazorアプリのデータソースをPostGraphileに置き換えます。
以下のjsonファイルの中を見ながら、これと類似のテストデータをPostgreSQLに用意します。
[{"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つだけですね。
ER図メニューから「DDLを作成する」を選択します。
RDBMS種類でPostgreSQLを選択し、DDL生成ボタンを押します。
以下のDDLが生成されました。
-- RDBMS Type : PostgreSQL-- Application : A5:SQL Mk-2/*
BackupToTempTable, RestoreFromTempTable疑似命令が付加されています。
これにより、drop table, create table 後もデータが残ります。
この機能は一時的に $$TableName のような一時テーブルを作成します。
*/-- WeatherForecast--* BackupToTempTableDROPTABLEifexistsweather_forecastsCASCADE;--* RestoreFromTempTableCREATETABLEweather_forecasts(idintegerNOTNULL,dtdateNOTNULL,temperature_cdoubleprecisionNOTNULL,summarycharactervaryingNOTNULL,CONSTRAINTweather_forecasts_PKCPRIMARYKEY(id));COMMENTONTABLEweather_forecastsIS'WeatherForecast';COMMENTONCOLUMNweather_forecasts.idIS'Id';COMMENTONCOLUMNweather_forecasts.dtIS'Date';COMMENTONCOLUMNweather_forecasts.temperature_cIS'TemperatureC';COMMENTONCOLUMNweather_forecasts.summaryIS'Summary';PostgreSQLの作業
本記事ではLinux上のPostgreSQLを使用します。
psqlを起動します。
psql --host=localhost --username=postgres --password
データベースを作成します。名前を「sample_db」にしましたが、何でも良いです。
CREATEDATABASEsample_db;カレントデータベースを、作成したsample_dbに切り替えます。
\c sample_db
先ほどERモデリングツールが生成したDDLをpsqlにコピペして実行します。
以下のINSERT文を流して、2000年1月1日から150日分のテストデータを作成します。
テーブルにはidの降順でINSERTしてみます。
INSERTINTOweather_forecasts(id,dt,temperature_c,summary)SELECTid,('1999-12-31'::DATE+(id::TEXT||' days')::INTERVAL)::DATEASdt,(random()*75-20)::INTAStemperature_c,CASE(random()*1000)::INT%10WHEN0THEN'Freezing'WHEN1THEN'Bracing'WHEN2THEN'Chilly'WHEN3THEN'Cool'WHEN4THEN'Mild'WHEN5THEN'Warm'WHEN6THEN'Balmy'WHEN7THEN'Hot'WHEN8THEN'Sweltering'WHEN9THEN'Scorching'ENDASsummaryFROMgenerate_series(1,150)ASidORDERBYidDESC;以下のSELECT文を流して、データが作成されたか確認します。
SELECT*FROMweather_forecasts;以下のようにidの降順で表示されましたが、順番に意味はありません。
psqlから抜けます。
PostGraphileの作業
本記事ではPostGraphileをPostgreSQLと同じホスト(Linux)にインストールします。
このページを参考にして、PostGraphileをインストール&起動します。
Dockerを使う方法もあります。
インストール
npm install -g postgraphile
起動コマンド例
postgraphile --connection postgres://postgres:secret@localhost/sample_db --port 15000 --schema public --export-schema-graphql ~/schema.graphql --cors
起動画面
本記事では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は、後ほどBlazorアプリから使用します。
ここではブラウザからGraphiQLにアクセスして、クエリーを発行したりドキュメントを見たりしてみましょう。
クエリー例
queryallWeatherForecasts{allWeatherForecasts{nodes{iddttemperatureCsummary}}}ブラウザ画面
レスポンス
{
"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ディレクトリごと削除します。
削除後のファイルは以下の通り。
パッケージのインストール
NuGetで以下の3パッケージをインストールします。
ファイル新規作成:Shared/WeatherForecast.cs
GraphQL APIのレスポンスデータを格納するデータ構造を作成します。
Sharedディレクトリ配下に「WeatherForecast.cs」を追加します。
以下の内容にします。
usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading.Tasks;namespaceSamplePostGraphile.Shared{// クエリー// query allWeatherForecasts {// allWeatherForecasts {// nodes {// id// dt// temperatureC// summary// }// }// }publicclassWeatherForecast{publicintId{get;set;}publicDateTimeDt{get;set;}privatedouble_tempC;publicdoubleTemperatureC{get{return_tempC;}set{_tempC=value;}}publicdoubleTemperatureF{get{return32+(_tempC/0.5556);}set{_tempC=(value-32)*0.5556;}}publicstringSummary{get;set;}publicWeatherForecast(){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"// },//// (中略)//// ]// }// }// }publicclassAllWeatherForecastsResponse{publicAllWeatherForecastsContentallWeatherForecasts{get;set;}publicclassAllWeatherForecastsContent{publicList<WeatherForecast>Nodes{get;set;}}}}ファイル新規作成:Services/WeatherForecastService.cs
GraphQLクエリーを発行して、そのレスポンスからデータを取り出してリターンするメソッドを持つクラスを作成します。
本記事では、CRUDのうちR(Read)のみ実装しました。
プロジェクト配下に「Services」というディレクトリを作成します。
Servicesディレクトリ配下に「WeatherForecastService.cs」を追加します。
以下の内容にします。
usingSamplePostGraphile.Shared;usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Threading.Tasks;usingGraphQL.Client.Http;usingGraphQL.Client.Serializer.Newtonsoft;usingGraphQL;namespaceSamplePostGraphile.Services{publicclassWeatherForecastService{// 行儀が良くないですが、今回はここにGraphQLエンドポイントのURLを書いてしまいますprivateconststringgraphql_http="http://192.168.1.7/15000/graphql";publicasyncTask<List<WeatherForecast>>GetForecastListAsync(){usingvargraphQLClient=newGraphQLHttpClient(graphql_http,newNewtonsoftJsonSerializer());varallWeatherForecasts=newGraphQLRequest{Query=@"
query allWeatherForecasts {
allWeatherForecasts {
nodes {
id
dt
temperatureC
summary
}
}
}
",OperationName="allWeatherForecasts",};vargraphQLResponse=awaitgraphQLClient.SendQueryAsync<AllWeatherForecastsResponse>(allWeatherForecasts);returngraphQLResponse.Data.allWeatherForecasts.Nodes;}//public async Task UpdateForecastAsync(WeatherForecast forecastToUpdate)//{// 未実装//}//public async Task DeleteForecastAsync(WeatherForecast forecastToRemove)//{// 未実装//}//public async Task InsertForecastAsync(WeatherForecast forecastToInsert)//{// 未実装//}}}変更:Program.cs
プロジェクト内でWeatherForecastServiceクラスを使えるようにします。
変更内容は以下の通りです。
+usingSamplePostGraphile.Services;usingMicrosoft.AspNetCore.Components.WebAssembly.Hosting;usingMicrosoft.Extensions.Configuration;usingMicrosoft.Extensions.DependencyInjection;usingMicrosoft.Extensions.Logging;usingSystem;usingSystem.Collections.Generic;usingSystem.Net.Http;usingSystem.Text;usingSystem.Threading.Tasks;namespaceSamplePostGraphile{publicclassProgram{publicstaticasyncTaskMain(string[]args){varbuilder=WebAssemblyHostBuilder.CreateDefault(args);builder.RootComponents.Add<App>("app");builder.Services.AddScoped(sp=>newHttpClient{BaseAddress=newUri(builder.HostEnvironment.BaseAddress)});+builder.Services.AddScoped<WeatherForecastService>();awaitbuilder.Build().RunAsync();}}}変更:Pages/FetchData.razor
データソースをjsonファイルからPostGraphileに置き換えるようにソースコードを変更します。
変更内容は以下の通りです。
@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データベースから取得したデータが一覧表示されました。
以上でデータソースの置き換えは完了です。
この時点で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アプリケーションにコンバートします。
NuGetパッケージの管理画面で、Telerik.UI.for.Blazorがインストールされたことを確認します。
コンバート完了時点で、一旦git commitしました。
git add -A
git commit -m "(2)プロジェクトをTelerikアプリケーションにコンバートします"
ファイル新規作成:Pages/Grid.razor
TelerikのGridコンポーネントを使用して、データを一覧表示するページを作成します。
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を追加します。
変更内容は以下の通りです。
<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コンポーネントでデータが一覧表示されました。
CRUDのうちRしか実装していませんが、試しに任意の行のEditボタンを押してDate列の右端をクリックしてみます。以下のようにカレンダー入力が出てきました。
以上で作業が完了しましたので、git commitしました。
git add -A
git commit -m "(3)Grid.razorページを追加します"
GitHubにもpushしました。
今後
TelerikのGridコンポーネントは機能がリッチだそうですので、深堀りしてみたいですね。
サードパーティーのコンポーネントに習熟すれば、ノーコードに劣らないスピード感でアプリを開発できそうです。
むしろ、数多あるNoCodeから適切なものを選ぶ→NoCodeで開発する→場合によってはYesCodeで作り直す、というステップを踏むより負担が少ない気がします。
以上です。































