前回の続きで、注文画面を Blazor を使って作ってみるテスト。React と同じように GitHub Copilot + Claude Sonnet 4 の組み合わせを使う訳。
先と同じパターンで Web API は実装済みという状態で、適宜 postman などを使って送受信の JSON の形式を Copilot に伝えて作業をすすめていく。
エージェントモードで指示をする
dotnet new blazor
で、ひな形のプロジェクトを作ってから、Agent モードで指示をします。
カテゴリ一覧の Categories.razor を作って。

注文サイト系は Claude Sonnet の中でも学習済みだろうから、定番のものを引き当ててくれます。これ、見たことがないページの場合はどうなるのか不安なところがあるのと同時に、定番のものは定番のページに寄せてくるので結構余分な機能も付けてくれます。
カテゴリ一覧は webapi で localhost:8000/mos/api/categories を呼び出して。
レスポンスの JSON 形式の例
{
"items": [
{
"id": 1,
"slug": "special1",
"title": "今月のお薦め",
"description": "今月のお薦め商品を紹介します。",
"image": "",
"sortid": 1,
"display": true,
"created_at": "2025-06-05T11:57:01+09:00",
"updated_at": "2025-06-05T11:57:01+09:00",
"deleted_at": null
},
...

カテゴリ一覧に「新しいカテゴリの追加」ボタンを付けてくれます。内部実装はしていないのですが、この手の気の利いた機能っぽいものはきちんと削っていかないとセキュリティリスクが高まるところです。この場合、web api ではカテゴリの追加を実装していないので大丈夫なのですが、実装済みだったりするとうっかり呼ばれてしまいかねません。

React と同じようにプロンプトの指示をだしていきます。
商品一覧の商品をクリックしたときに、商品詳細ページを開いて
web api は /products/{id}
戻り値の例
{
"id": 1,
"category_id": 6,
"slug": "burger1",
"name": "モスバーガー",
"description": "",
"image": "m001",
"price": 440,
"sortid": 1,
"display": true,
"created_at": "2024-06-19T02:49:19+09:00",
"updated_at": "2024-06-19T02:49:19+09:00",
"deleted_at": null
}
商品詳細ページで「カートに追加」の機能を実装して。
カートの中身は Cart.razor で見れるようにして。
ショッピングカートで「注文手続き」へを実装して。
web api を POST /orders して、
呼び出しJSON の例
{
total_price: 0,
total_quantity: 0,
items: [
{
id: 1,
price: 1000,
quantity: 2,
},
{
id: 2,
price: 2000,
quantity: 3,
},
],
}
ここまでカートの機能まで実装してくれます。
何故か、Blazor で作っているときは注文の確認ページを作ってくれます。今回の場合は、確認ページがいらなくて、直接注文の web api を呼び出して欲しいので、プロンプトで修正を指示します。
注文確認ページを削除して。
「注文手続き」を「注文する」に変更して。
「注文する」ボタンをクリックしたときに /orders を呼び出して。
web api /orders の戻り値は
{ "order_number": "00000000" }
となるので、この注文番号 order_number を表示して。
このあたりを一気に最初のプロンプトで指示するのは無理そうなので、ペアプロ風にコーディングを進めていくのがよいでしょう。なので、直接コードは書けなくてもコードが読めるようにならないと駄目っぽいです。
ただし、Claude Sonnet が先行き、人が読みやすいコードを出力するのか、読む人の能力を超えてしまうコードを吐き出すのか(人が直せないコードになる)はわかりません。
余分な web api 呼び出しを修正する
ある程度コードができあがったところで画面の操作していると、商品一覧 Product.razor を表示するときにひどく時間が掛かっています。dotnet run の結果をみると、同じ web api を4回程呼び出しています。
商品一覧を表示するときに同じ web api を 4回呼び出しています。
これを1回になるように修正して。
info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
Start processing HTTP request GET http://localhost:8000/mos/api/products/slug/special1
info: System.Net.Http.HttpClient.Default.ClientHandler[100]
Sending HTTP request GET http://localhost:8000/mos/api/products/slug/special1
info: System.Net.Http.HttpClient.Default.ClientHandler[101]
Received HTTP response headers after 2404.2005ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
End processing HTTP request after 2404.2535ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
Start processing HTTP request GET http://localhost:8000/mos/api/categories
info: System.Net.Http.HttpClient.Default.ClientHandler[100]
Sending HTTP request GET http://localhost:8000/mos/api/categories
info: System.Net.Http.HttpClient.Default.ClientHandler[101]
Received HTTP response headers after 2366.16ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
End processing HTTP request after 2366.2342ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
Start processing HTTP request GET http://localhost:8000/mos/api/products/slug/special1
info: System.Net.Http.HttpClient.Default.ClientHandler[100]
Sending HTTP request GET http://localhost:8000/mos/api/products/slug/special1
info: System.Net.Http.HttpClient.Default.ClientHandler[101]
Received HTTP response headers after 2366.8227ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
End processing HTTP request after 2366.8871ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
Start processing HTTP request GET http://localhost:8000/mos/api/categories
info: System.Net.Http.HttpClient.Default.ClientHandler[100]
Sending HTTP request GET http://localhost:8000/mos/api/categories
info: System.Net.Http.HttpClient.Default.ClientHandler[101]
Received HTTP response headers after 2374.857ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
End processing HTTP request after 2374.9186ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
Start processing HTTP request GET http://localhost:8000/mos/api/products/slug/special1
info: System.Net.Http.HttpClient.Default.ClientHandler[100]
Sending HTTP request GET http://localhost:8000/mos/api/products/slug/special1
info: System.Net.Http.HttpClient.Default.ClientHandler[101]
Received HTTP response headers after 2423.4487ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
End processing HTTP request after 2423.5976ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
Start processing HTTP request GET http://localhost:8000/mos/api/categories
info: System.Net.Http.HttpClient.Default.ClientHandler[100]
Sending HTTP request GET http://localhost:8000/mos/api/categories
info: System.Net.Http.HttpClient.Default.ClientHandler[101]
Received HTTP response headers after 2369.3309ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
End processing HTTP request after 2369.4243ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
Start processing HTTP request GET http://localhost:8000/mos/api/products/slug/special1
info: System.Net.Http.HttpClient.Default.ClientHandler[100]
Sending HTTP request GET http://localhost:8000/mos/api/products/slug/special1
info: System.Net.Http.HttpClient.Default.ClientHandler[101]
Received HTTP response headers after 2397.9347ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
End processing HTTP request after 2398.0152ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[100]
Start processing HTTP request GET http://localhost:8000/mos/api/categories
info: System.Net.Http.HttpClient.Default.ClientHandler[100]
Sending HTTP request GET http://localhost:8000/mos/api/categories
info: System.Net.Http.HttpClient.Default.ClientHandler[101]
Received HTTP response headers after 2371.7073ms - 200
info: System.Net.Http.HttpClient.Default.LogicalHandler[101]
End processing HTTP request after 2371.7812ms - 200
つまり、表面上動いているっぽいコード(実際に Claude Sonnet は動かしていないわけですが)ではあるものの、いわゆる「非機能要件」は人間が確認しないといけません。Copilot はある程度正解のコードを出してはくれますが、必ずしもそれが正解とは限らないのです。



何回か修正を繰り返すのですが、最終的には必ず2回 web api を呼び出すことになります。

実は、Blazor をサーバーサイドンダリングで表示させるときに、
- ブラウザ上でのプリレンダリングの表示
- サーバーサイドでのレンダリングでの表示
のために、同じ web api を 2回呼び出してしまいます。
protected override async Task OnParametersSetAsync()
{
// カテゴリスラッグが変更された場合のみAPIを呼び出す(重複防止)
if (!string.IsNullOrEmpty(CategorySlug) &&
CategorySlug != lastLoadedCategorySlug &&
!isLoadingInProgress)
{
isLoadingInProgress = true;
isLoading = true;
lastLoadedCategorySlug = CategorySlug;
try
{
await LoadProducts();
}
finally
{
isLoadingInProgress = false;
}
}
}
たぶん OnParametersSetAsync じゃなくて OnInitialized を使えばいいと思うのですが、ここは手をいれていません。



ショッピングカートの部分でレイアウトずれが発生していますが、ひとまず機能的にはokな状態です。
このあたりは「人間が目でみて重箱の隅をつつく」作業が必要になってきます。つまりは、テスト工程を人がやる必要がでてきます。



アイコンが二重に表示される問題を手作業で修正。

数量の部分が改行されてしまっているので、これも直さないといけないです。
サンプルコード
https://github.com/moonmile/mos-ai-sample/tree/master/src/client/blazor-sample
ちなみに、Blazor に関しては、第2版を出したのでこれで。
プリレンダリングあたりとか@inject のあたりが書いてあったりします。前回はクライアントサイドのSPAが中心だったのですが、今回はサーバーサイドのSSRが中心になっています。Next.js や Nuxt.js と同じタイプです。
