GitHub Copilot + GPT-4o/Claude Sonnet で ASP.NET Core minimal な web api を作成する

Copilot の Agent を “GPT-4o” に変えて、minmal web api を作成していきます。vscode 上で使っている間はどちらもで変わらないし、いま使っている限りではどちらが上という感じはしません。課金的に安いほうを選べばいいんじゃないでしょうか?

- データベース定義 : `ddl.md`
- API 定義 : `mos-api.yaml`
- 開発環境 : `asp.net minimal`
- データベース : `MySQL`

だけ決めておいて、プロンプトをスタートさせます。

dotnet new webapi 

でプロジェクトを作成した後に、次のプロンプトでスタートです。

ddl.md と mos-api.yaml を参照して asp.net minimal で web api を作成して。
データベースは MySQL を使用して。

mos-api.yaml には products や orders の CRUD が含まれているのですが、それは追えていないようです。これが gpt-4o だからなのかは不明ですが、ビルドが終わったら少しずつ追加していきます。

最初の修正

DefaultConnection を追加して。
ポート番号を 8000 にして。
エンドポイントを localhost:8000/api/categories でアクセスできるようにして。
Models のクラスを ddl.md のカラム名にあわせて。

なんと!Models/Category.cs のプロパティ名に合わせてデータベースのほうを修正しようとします。しかも MySQL なのに pgsql を使おうとするので、ここでストップ。

どうやら、GPT-4o では無理そうなので、Claude Sonnet 4 に切り替えます。

まあ、普通はそうですね。C# の場合は Column 属性で名前を合わせます。データベースが MySQL の場合は大文字小文字が区別されるので、テーブルを参照するときのカラム名はすべて設定しておいたほうが無難です。

using System;
using System.ComponentModel.DataAnnotations.Schema;

namespace aspnet_minimal_sample.Models
{
    public class Category
    {
        public int Id { get; set; }
        public string Slug { get; set; } = string.Empty;
        public string Title { get; set; } = string.Empty;
        public string? Description { get; set; }
        public string? Image { get; set; }
        
        [Column("sortid")]
        public int SortId { get; set; } = 0;
        
        public int Display { get; set; } = 1;
        
        [Column("created_at")]
        public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
        
        [Column("updated_at")]
        public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
        
        [Column("deleted_at")]
        public DateTime? DeletedAt { get; set; }
    }
}

GPT-4o の尻ぬぐいは Claude Sonnet にやってもらいましょう。

mos-api.yaml に従って、products や orders のエンドポイントも作って。
categories も mos-api.yaml のように修正して。
レスポンスの JSON を categoryId ではなく category_id のように mos-api.yaml に揃えて。
localhost:8000/mos/api/products/1 のレスポンスを mos-api.yaml にあわせて

あちこち抜けているので、mos-api.yaml の仕様にあっているかチェックしてもらいます。

JSON のレスポンスが mos-api.yaml の仕様にあっているか全体をチェックして。

指示した視点でコードのレビューができるので、git へのプルリクエストのコードのチェックとかできるもしれません。この場合 PR のマスター役がやるというよりも、PR を出す人があらかじめ Claude Code などでコードレビューをしておいてね、という感じでしょうか。

nuxt-sample と接続してみる

さて asp.net minimal がほどよく作成できたので、実際にクライアントと接続して試してみましょう。既にできている(筈)の nuxt-sample を実行します。

npm run dev

カテゴリ一覧からカテゴリを選択すると「読み込み中…」で止まってしまいます。

商品一覧で画像が表示されません。「品切れ」はよくわからない。こんな機能をいれたっけ?

商品をクリックすると「読み込み中…」になってしまいます。

このあたり、nuxt-sample のバグなのか asp.net minimal のバグかわからないので、vscode でひとつのワークスペースで扱って調査していきます。

バグ取り開始

next-sample でカテゴリを選択したとき「読み込み中...」のまま止まります。

vscode で aspnet-minimal-sample と next-sample をひとつのワークスペースにいれておきます。これで、両方のプロジェクトを Claude Sonnet が参照できるようになります。

ひとまず aspnet 側の CORS 問題だったらしいので、カテゴリ内の商品表示ができるようになりました。

商品詳細のページも表示できるようになっています。

画像に関しては products.image カラムのデータに拡張子を含むようにしたので、next-sample のページで “.jpeg” しているところを外します。

画像ファイルは next-sample のほうではなく、aspnet-minimal-sample のサーバーのほうに置きたいので尋ねてみます。

aspnet-minimal-sample のほうに public を置けますか?

next-sample のほうの img タグのほうも書き変えて、画像が表示されるようになります。

レイアウトにつかうときの画像ファイルは next-sample のほうの public に置けばよいのですが、商品データの画像はデータベースや public の適当なところに置くことになるので aspnet-minimal-sample のほうに置きます。

あとは、カートに入れて注文番号が表示されれば ok です。

ひとまず、これで web api のほうも含めて作成完了です。

ASP.NET Core minimal なのでワンコードで確認ができる。

Laravel の場合と比較すると、aspnet minimal の場合は Program.cs に全て詰め込まれています。

// カテゴリ API エンドポイント
app.MapGet("/mos/api/categories", async (AppDbContext db, int? display) =>
{
    var query = db.Categories.AsQueryable();
    
    if (display.HasValue)
        query = query.Where(c => c.Display == display.Value);
    
    var categories = await query.ToListAsync();
    return Results.Ok(new { items = categories, total = categories.Count });
});

app.MapPost("/mos/api/categories", async (AppDbContext db, Category category) =>
{
    db.Categories.Add(category);
    await db.SaveChangesAsync();
    return Results.Created($"/mos/api/categories/{category.Id}", category);
});

app.MapGet("/mos/api/categories/{id}", async (AppDbContext db, int id) =>
{
    var category = await db.Categories.FindAsync(id);
    return category is not null ? Results.Ok(category) : Results.NotFound();
});

app.MapPut("/mos/api/categories/{id}", async (AppDbContext db, int id, Category category) =>
{
    var existingCategory = await db.Categories.FindAsync(id);
    if (existingCategory is null) return Results.NotFound();
    
    existingCategory.Slug = category.Slug;
    existingCategory.Title = category.Title;
    existingCategory.Description = category.Description;
    existingCategory.Image = category.Image;
    existingCategory.SortId = category.SortId;
    existingCategory.Display = category.Display;
    existingCategory.UpdatedAt = DateTime.UtcNow;
    
    await db.SaveChangesAsync();
    return Results.Ok(existingCategory);
});
...

本格的な仕事コードだとエラー処理とかの不安があるので、MVC パターンの Controller ベースにしたいところですが、このぐらいの実験コードとか管理モードで社内ツールとして使う場合にはルーティングやデータベースアクセス(特にCRUDのみ)はひとつにまとまっておいたほうが修正がしやすいです。Claude Code のような AI を使う場合にはファイルを横断してくれますが、人が手動で修正する場合は「置換」を多用するのでひとつのファイルにまとまっていたほうが便利です。

実際に、web api の機能としては Program.cs のコード量は 300行弱でしかないので、あれこれファイルを分割するよりもひとつのファイルにまとまった方が編集もしやすいでしょう。

参照先

mos-ai-sample/src/webapi/aspnet-minimal-sample at master · moonmile/mos-ai-sample https://github.com/moonmile/mos-ai-sample/tree/master/src/webapi/aspnet-minimal-sample

カテゴリー: 開発 パーマリンク