using BlazorOrderApp.Components;
using BlazorOrderApp.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
builder.Services.AddScoped<CartService>(); // ★ここを追加
カテゴリの詳細情報を get で取ってきているのに、カテゴリの中に商品(product)の配列があるのはどうなの? という気がしないでもないのですが、カテゴリに紐づく商品の一覧が取得できます。これは書籍用に api 呼び出しが煩雑にならないようにしたのですが、実務的には “/api/products/category/{id}” のように /api/products のほうに紐づけたほうがよさそうです。
@page "/products/category/{CategoryId:int}"
@rendermode InteractiveServer
@inject NavigationManager Navigation
@inject HttpClient Http
@inject IJSRuntime JSRuntime
<PageTitle>カテゴリ一覧</PageTitle>
<div class="container py-4">
<div class="mb-4">
<h2 class="fw-bold text-danger">🍣 商品一覧</h2>
<p class="text-secondary">商品を選択してください</p>
</div>
<div class="row">
@foreach (var it in products)
{
<div>@it.Id : @it.Name @it.Price</div>
}
</div>
</div>
@code {
[Parameter] public int CategoryId { get; set; }
class Product
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Description { get; set; } = "";
public string Price { get; set; } = "";
public string? ImageUrl { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
class Category
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Description { get; set; } = "";
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public Product[] Products { get; set; } = [];
}
class CategoryResponse {
public Category Data { get; set; } = default!;
}
private List<Product> products = new() ;
protected override async Task OnInitializedAsync()
{
await onSearch();
}
private async Task onSearch()
{
System.Text.Json.JsonSerializerOptions JsonOptions = new()
{
PropertyNamingPolicy = System.Text.Json.JsonNamingPolicy.SnakeCaseLower
};
var res = await Http.GetFromJsonAsync<CategoryResponse>($"api/categories/{CategoryId}", JsonOptions);
if (res != null)
products = res.Data.Products.ToList();
}
}
GetFromJsonAsync メソッドの戻り値を CategoryResponse クラスとして定義しないといけないのが、ちょっと厄介ですが、レスポンスの型チェックとして必要不可欠…というか、このあたりは Web API から提供される openapi.yaml から自動生成してしまうのがよいです。ここでは code ブロックに定義していますが、実際は models とかに突っ込めば ok です。
Blazor でクライアントサイドのアプリ(実際には SSR でサーバーサイドのプログラミングも担当できますが)、となるとサーバーサイドに Web API があるわけで、クライアントサイドとサーバーサイドの棲み分け(実際には組み合わせの汎用性を考えるという形で)、Blazor と Laravel での Web API の例です。
基本 .NET な人が何故に PHP/Laravel なのか?ってのもありますが、新人研修で Vue/Nuxt と PHP/Laravel を使っていてですね、サーバーサイドの Web API 実装本がないので、教科書代わりに書いた。というのが真相です。サーバーサイドだから Web API のサンプルとして ASP.NET Core でもいいのではないか、という気もしますし、暫くはサーバーサイドの実験は .NET 系でやっていました(新人教育では、Vue/Nuxt のフロントエンドでおしまいになることが多いので)、ただ、HTTP プロトコルの細かいところやら認証やセキュリティまわりの解説となると実際の PHP/Laravel の実装が欲しかったのです。
実は、同時期に「API Platformを活用したPHPによる本格的なWeb API開発」 https://amzn.asia/d/09ZdjoNj が出版されたので、被らなくてよかった…と思いつつ、フレームワークが API Platform と Laravel と異なるので、実装の違いが良く分かるようになる…かもしれません。 Web API の本は、設計やテストの本はあるのですが、実際のフレームワーク前提の実装本がないのでちょっと苦労していたのですが(Laravel 本だとフロントエンドの話が多いので、Web API のバックエンドの話がちょっぴりしかないので)…果たして。
これは、Next.js で作っているのですが、当然のことながら Web API さえ使えればクライアントの種類は問いません。そのあたりは、ローカルPCにサーバーサイドの Docker を動かして、自由にクライアントを作ってみようという趣旨なわけですが…まあ、ちょっと amaqzon の評がなくて苦戦中ですね。いや、同時期の API Platform 本のほうも苦戦中?っぽいので、Web API 限定というのが地味なのかもしれません。
class Category
{
public int Id { get; set; }
public string Name { get; set; } = "";
public string Description { get; set; } = "";
public int products_count { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
class CategoriesResponse
{
public Category[] Data { get; set; } = default!;
}
private List<Category> categories = new List<Category>();
private bool isLoading = true;
protected override async Task OnInitializedAsync()
{
await onSearch();
}
private async Task onSearch()
{
isLoading = true;
var res = await Http.GetFromJsonAsync<CategoriesResponse>("api/categories");
if (res != null)
categories = res.Data.ToList();
isLoading = false;
}
private void SelectCategory(Category category)
{
System.Diagnostics.Debug.WriteLine($"Selected: {category.Name}");
Navigation.NavigateTo($"/products/{category.Id}");
}
多分、Blazor でバイブコーディングしてもこれぐらいは一気に行けると思うのですが、クラス化したり Web API の動きを確認したりしながらやる場合は、最初に Web API 呼び出しの C# コードだけ作って、後から HTML 部分で AI に化粧してもらうのが安全そうです。 Category と CategoriesResponse クラスは後で Models のほうに放り込みます。
商品一覧を表示して、カートに入れる機能のほうは後日。
カテゴリー:開発|Blazor から Laravel の Web API 呼び出しの組み合わせ はコメントを受け付けていません
このレポジトリは web api の laravel-webapi とサンプルクライアントの order-tablet 等が混在しているので、対象となるプロジェクトを指定しないといけないのが面倒です。実際はクライアントとサーバーで別のレポジトリに分けるので、混乱は少ないと思います。 ただ、その分、設計書関係(agents/画面設計.md や 概要設計.md)が別フォルダーに配置されることになるので、このあたりのフォルダー構造は悩みどころ。
実際の Web API を呼び出して、「握り寿司」カテゴリーから商品一覧がでるようになっています。 openapi.yaml さえ作成してしまえば、クライアントサイドの lib/api.ts は AI エージェントで自動生成できるので、この部分は比較的簡単です。実際はサーバーのシステムが出来上がっていないことが多いので、なんらかのモックアップ用のサーバーを作ることになるのですが。Web API の仕様をクライアント側からチェックしたいときに使うとよいです。
おそらく、AI がデザインする UI にしても、典型的なパターンはいけそうなのですが、それ以上となると結構大変そうです。そういう意味では、HTML/CSS をこねくり回さなくても一定の画面がでてくるのが AI の良いところでもあり、それ以上に踏み込めない(デザイナーの人が AI を使ってデザインすると、変わるかも?)領域となりそうなので、この点に関しては、私はここでギブアップです。
BLE を使った Android 実機の自動テストコードを考えます。AI を含めたスパイラル開発では、Web API を使ったほうが実用的なような気がするのですが…ひとまず BLE のほうからやってみます。Web API 利用のほうは、先日執筆した PHP & Laravel 開発のサンプルコードを使っていずれやってみます。 テストの基準にもいろいろあるのですが、
BLE を使った Android アプリの場合、いちばん面倒臭いのが疎通確認です。電波の状態が届いているのか、ライブラリがきちんと動作しているのか、送受信がうまくいっているのか、という諸々の理由から、ビルドして新しいアプリを入れ直したときに疎通確認は必須です。なんといっても、BLE が繋がっていないままにテストコードを動かしても「全然繋がらない」ために、原因を特定するのたいへんです。アプリのあれこれの動作を確認するために「確実に2台で繋がっている」あるいは「確実にメッセージが送受信されている」という状態を確保しなければいけません。ネットワーク系のテストをするときにはここが最重要ですね。
2026-04-30 10:08:18.601 14575-14575 BLE5Chat/B...nerManager net.moonmile.ble5_chat.claude D Duplicate filtered: 3d71fb74-69da-44ad-ab9d-15205c8f437f
2026-04-30 10:08:18.726 14575-14575 BLE5Chat/B...nerManager net.moonmile.ble5_chat.claude D Duplicate filtered: d44b0b62-7253-4a11-bb7d-052e0c82b652
2026-04-30 10:08:18.868 14575-14575 BLE5Chat/B...nerManager net.moonmile.ble5_chat.claude D Received: 870038ea-059e-43b7-a4e5-611339da2d92 from 11112222
2026-04-30 10:08:18.869 14575-14575 BLE5Chat/MainActivity net.moonmile.ble5_chat.claude D Received: 870038ea-059e-43b7-a4e5-611339da2d92 from 11112222
2026-04-30 10:08:18.943 14575-14575 AndroidRuntime net.moonmile.ble5_chat.claude D Shutting down VM
2026-04-30 10:08:18.948 14575-14575 AndroidRuntime net.moonmile.ble5_chat.claude E FATAL EXCEPTION: main (Fix with AI)
Process: net.moonmile.ble5_chat.claude, PID: 14575
java.lang.IllegalArgumentException: Key "870038ea-059e-43b7-a4e5-611339da2d92" was already used. If you are using LazyColumn/Row please make sure you provide a unique key for each item.
at androidx.compose.ui.internal.InlineClassHelperKt.throwIllegalArgumentException(InlineClassHelper.kt:36)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:1366)
at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope.subcompose(SubcomposeLayout.kt:1231)
at androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScopeImpl.compose(LazyLayoutMeasureScope.kt:94)
at androidx.compose.foundation.lazy.layout.LazyLayoutMeasuredItemProvider.getPlaceables-3p2s80s(LazyLayoutMeasuredItem.kt:60)
at androidx.compose.foundation.lazy.LazyListMeasuredItemProvider.getAndMeasure-0kLqBqw(LazyListMeasuredItemProvider.kt:53)
at androidx.compose.foundation.lazy.LazyListMeasuredItemProvider.getAndMeasure-0kLqBqw$default(LazyListMeasuredItemProvider.kt:47)
at androidx.compose.foundation.lazy.LazyListMeasureKt.measureLazyList-LCrQqZ4(LazyListMeasure.kt:224)
おそらく、ダブりで受信「Received: 870038ea-059e-43b7-a4e5-611339da2d92 from 11112222」したときに、リストに追加する際にキーが重複しているような気がします。ここは後で。
簡単なテストコードを書いておく
BLE チャーッとツールには、明確なロジッククラスがあるわけではないので、わざわざテストコードを書く必要もないのですが、「設計から生成されたコードを確認するためのテストコード」という観点から、簡易的なテストコードを追加しておきます。
ここまで来ると、人間が開発する場合は「新しく作り直したほうが手っ取り早い」という形になりそうなのですが、果たして AI コーディングをする場合にはどうすれいいでしょうか? という話ですね。FolkBears の場合は、BLE の通信部分(GATT, Advertiser, Scanner)をライブラリ化して、それを流用する設計にしたのですが、この BLE チャットツールの場合はどうなるのか?
果たして、この UI 設計書が今後のリファクタリング等に耐えられるかどうかは判断しかねます。UI 設計にしては、コードのファイル名やメソッド名が直書きになっているので、コード変更に弱い感じがしますが…アプリ開発時の一時的な参考としてはこれで十分でしょう。 すくなくとも、UI 設計書から Claude Code や GitHub Copilot などの異なる AI エージェントを使っても、同じようなコード(完全に同じとは言えないが)が生成される「再現性」が担保できれば ok です。
- 「お気に入り機能」を 概要設計.md に追加 - UI 設計書をひとつにまとめる
作業を追加しておきます。いわゆる、機能追加や仕様変更を、設計書に反映する作業です。
いわゆる、情報のトレーサビリティの問題で、概要設計書やUI設計書から過不足なくコードに反映されていればよいという発想です。細かいところは、かつては詳細設計という形で詰めていたのですが、昨今はコードが残ればだいたい ok です。 コードからドキュメントを再作成するツールはかつてもありましたが、UML の出力やクラスのメソッドの一覧などの留まっていました。
AI が設計書の差分から確認し始めるので、なんらかの形で差分がわかるような設計書の作り方にしたほうがいいかもしれません。あるいは Git の差分を見て AI 自身が判断するようがよいのかも。実際、コードの差分などは Git diff を取ることが多いので。
お気に入り画面の背景色がちょっとミスっているのはさておき、ほぼ Claude Code で作ったコードと同じものができます。これで、概要設計やUI設計が「再現性がある」という状態ということが確認できます。
AI を含めたスパイラル開発
仕様駆動開発の場合には、要件定義や設計を煮詰めるところから始まるのですが、プロトタイプをもとにしたスパイラル開発の場合は、最初は「数行のプロンプト」で描けるような所謂バイブコーディングでも構いません、というスタンスになります。ただし、そこでできあがるのは、あくまで「動作確認」のコードなので、そこからいったん「設計生成」を生成した後に AI によるコード生成をします。
こうすると、設計からコードへの再現性を確認できるし、バイブコーディング的な AI コード生成の動作確認も可能になるのでは?と思うのですがどうでしょうか。
このスパイラル開発のループに「自動テスト」を入れることになります。テストコードとしては
動作確認を自動化するための自動実行コード
設計から生成されたコードを確認するためのテストコード
完成品の品質を確認するためのテストコード
などが考えられます。
これを次から組み入れていきます。毎度、Android Studio でビルドした後に、チャットメッセージの動作確認を手作業でやっているのですが、これが意外と面倒です。できれば自動化したいですよね。果たして自動化できるのでしょうか?
実に Claude Code と同じ画面ができあがります…が、右上の歯車ボタンが「・・・」になっているのが、ちょっと違いますね。画面遷移自体は「設定」→「著作権表示」となっているのですが。
このあたり UI 設計にある
## 2. 画面遷移
```mermaid
flowchart LR
A[チャット画面] -->|メニューから設定| B[設定画面]
B -->|戻る| A
B -->|著作権情報| C[著作権情報画面]
C -->|戻る| B
B -->|送信者ID編集| D[送信者ID編集ダイアログ]
D -->|保存| B
D -->|キャンセル| B
の部分で「メニューからの設定」の解釈の違いっぽいです。
考察
このように後から UI 設計書を追加して、それに従って既存のコードに画面を追加することは可能です。プロンプトでちまちま追加するりも、設計書.md のようにまとめて AI に渡していたほうが効率的なのは確かです。
が、じゃあ、UI 設計書はどこまで作ればいいのか?という問題があります。今回のように、数行のプロンプトで作った場合、Claude Code と GitHub Copilot では異なる画面ができてしまいます。個人開発ならばそれでもいいのですが、会社のシステムのような場合はこれでは困ります。となると、なんらかの画面設計書を作った上で、その画面フォーマットンに合わせて、どちらの AI を使っても同じ画面できるのが望ましいのです。