続き。
カテゴリ一覧で「カテゴリ」を選択したときに、商品一覧を表示します。商品はカテゴリの中に含まれているもので Web API のほうでは openapi.yaml で “api/categories/{id}” として定義しています。
/categories/{id}:
get:
tags:
- categories
summary: カテゴリ詳細取得
description: 指定されたIDのカテゴリ情報を取得
parameters:
- $ref: '#/components/parameters/IdPath'
responses:
'200':
description: 成功
content:
application/json:
schema:
type: object
properties:
data:
$ref: '#/components/schemas/Category'
'404':
$ref: '#/components/responses/NotFound'
実際のレスポンス(JSON形式)
{
"data": {
"id": 6,
"name": "サイドメニュー",
"description": "寿司と一緒に楽しめるサイドメニューです。",
"created_at": "2026-05-15T03:00:01.000000Z",
"updated_at": "2026-05-15T03:00:01.000000Z",
"products": [
{
"id": 19,
"name": "茶碗蒸し",
"price": "280.00",
"description": "なめらかな茶碗蒸し",
"image_url": null,
"created_at": "2026-05-15T03:00:01.000000Z",
"updated_at": "2026-05-15T03:00:01.000000Z",
"pivot": {
"category_id": 6,
"product_id": 19
}
},
{
"id": 20,
"name": "枝豆",
"price": "180.00",
"description": "塩ゆで枝豆",
"image_url": null,
"created_at": "2026-05-15T03:00:01.000000Z",
"updated_at": "2026-05-15T03:00:01.000000Z",
"pivot": {
"category_id": 6,
"product_id": 20
}
}
]
}
}
カテゴリの詳細情報を get で取ってきているのに、カテゴリの中に商品(product)の配列があるのはどうなの? という気がしないでもないのですが、カテゴリに紐づく商品の一覧が取得できます。これは書籍用に api 呼び出しが煩雑にならないようにしたのですが、実務的には “/api/products/category/{id}” のように /api/products のほうに紐づけたほうがよさそうです。
商品一覧を作る
Blazor で商品一覧を表示するための Products.razor を作ります。
- パラメータは、”/products/category/{CategoryId:int}” のように渡される
- [Parameter] public int CategoryId で受け取る
- GetFromJsonAsync メソッドで受信した JSON 形式のデータを、Category クラスで受け取る
ところまで書いておきます。
画面への表示はシンプルに div タグで表示させているだけです。
@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 です。

レイアウトを変更する
Claude Code を使ってレイアウトを修正して貰います。

<PageTitle>商品一覧</PageTitle>
<div class="container py-4">
<div class="mb-4">
<h2 class="fw-bold text-danger">🍣 商品一覧</h2>
<p class="text-secondary">商品を選択してください</p>
</div>
@if (isLoading)
{
<div class="d-flex justify-content-center py-5">
<div class="spinner-border text-danger" role="status">
<span class="visually-hidden">読み込み中...</span>
</div>
</div>
}
else if (products.Count == 0)
{
<div class="alert alert-warning">商品が見つかりませんでした。</div>
}
else
{
<div class="row row-cols-1 row-cols-md-3 g-4">
@foreach (var it in products)
{
<div class="col">
<div class="card h-100 shadow-sm border-0">
<div class="card-body">
<h5 class="card-title fw-bold">@it.Name</h5>
<p class="card-text text-secondary">@it.Description</p>
</div>
<div class="card-footer bg-white border-0 d-flex justify-content-between align-items-center">
<span class="fw-bold text-danger">¥@it.Price</span>
<button class="btn btn-danger btn-sm" @onclick="() => AddToCart(it)">カートに追加</button>
</div>
</div>
</div>
}
</div>
}
</div>
これで「カートに追加」ボタンをクリックすると、商品をカートに入れる準備ができました。AddToCart メソッドを実装すれば良い状態になっています。
@inject BlazorOrderApp.Services.CartService Cart
...
@code {
...
private async Task AddToCart(Product product)
{
await Cart.AddAsync(product.Id, product.Name, product.Price);
}
}
カート機能のメモリは、Blazor アプリケーション全体で共通して持つことになるので、ひと工夫が必要です。
Blazor の状態管理ということで、以下を使います。
ASP.NET Core Blazor 状態管理の概要 | Microsoft Learn
https://learn.microsoft.com/ja-jp/aspnet/core/blazor/state-management/?view=aspnetcore-10.0
書籍のほうは、この部分が抜けてしまったので、後日補足。
