ASP.NET Core + React + TypeScriptでTodoアプリを作成する②(バックエンド開発編)

ASP.NET Core

バックエンドにASP.NET Core Web API、フロントエンドにReact+TypeScriptを使ったTodoアプリを作成する方法を解説します。

第1回では環境構築をメインに解説しました。第2回ではバックエンドのAPIを作成し、DBと連携するところまでを解説します。(第2回はフロントエンド要素は一切ありません)


フレームワークはASP.NET Core Web API(C#)を使用するので、もし触ったことがない場合は以下の記事を参考にチュートリアルをやってみることをおすすめします。

なお、この記事で扱うコードは基本的にWeb APIチュートリアルで登場するものと同じです。

スポンサーリンク

環境

  • Windows 10 (Mac OSでも動作確認済み)
  • Visual Studio Code 1.72
  • .NET 6.0 SDK (6.0.401)
  • Node.js 16.17.1
  • React 18.2.0
  • TypeScript 4.8.4

準備

バックエンド開発に必要なVSCodeの拡張機能を事前にインストールしておきましょう。

VSCode拡張機能「SQLite」

今回はDBにSQLiteを使用するため、対応する拡張機能をインストールします。

VSCode拡張機能「REST Client」

作成したAPIの動作確認に使用するため、こちらの拡張機能もインストールしておきます。

モデルの追加

まずTodoItemのモデルを作成します。
プロジェクトのルート直下に「Models」というフォルダを新規作成し、その中に「TodoItem.cs」を追加してください。

namespace todo_react_app.Models;

public class TodoItem
{
    public int Id { get; set; }

    public string? Name { get; set; }

    public bool IsComplete { get; set; }
}

主キーとなるIdと、Todoアイテムの名前、Todoアイテムが完了したかどうかのフラグの情報を持つシンプルなモデルです。

データベースコンテキストの追加

Entity Framework Coreを使用してDBと連携するため、DBコンテキストも作成します。

「Models」フォルダ内に「TodoContext.cs」を追加してください。

using Microsoft.EntityFrameworkCore;

namespace todo_react_app.Models;

public class TodoContext : DbContext
{
    public TodoContext (DbContextOptions<TodoContext> options)
        : base(options)
    {
    }

    public DbSet<TodoItem> TodoItem { get; set; } = null!;
}

パッケージ「EntityFrameworkCore」をまだ入れていないのでエラーが出ますが、後の手順で追加するので今は無視して大丈夫です。

コントローラーのスキャフォールディング

CRUD機能を持ったAPIを手で1から作成してもいいのですが、今回はAPIコントローラーを自動生成してくれる「スキャフォールディング」機能を使ってお手軽に作成したいと思います。

設定ファイルの変更

スキャフォールディングの実行に先立って、Program.csとappsettings.jsonを書き換えます。

Program.csに、作成したDBコンテキストを登録するための記述を追加します。

using Microsoft.EntityFrameworkCore;
using todo_react_app.Models;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

builder.Services.AddDbContext<TodoContext>(options =>
        options.UseSqlite(builder.Configuration.GetConnectionString("TodoContext")));

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.MapControllers();

app.MapFallbackToFile("index.html");;

app.Run();

appsettings.jsonに接続文字列の記述を追加します。

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "TodoContext": "Data Source=TodoItem.db"
  }
}

接続文字列をこのように書くと、マイグレーション実行後にプロジェクトのルート直下にSQLiteのDBファイルが作成されます。

VSCodeでのスキャフォールディング

VSCodeの場合は、必要なツールやSQLiteの操作に必要なパッケージをインストールした後にスキャフォールディングを実行します。

上部メニューの「ターミナル」→「新しいターミナル」からターミナルを開き、以下のコマンドを実行してください。

dotnet tool uninstall --global dotnet-aspnet-codegenerator
dotnet tool install --global dotnet-aspnet-codegenerator
dotnet tool uninstall --global dotnet-ef
dotnet tool install --global dotnet-ef
dotnet add package Microsoft.EntityFrameworkCore.SQLite
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design
dotnet add package Microsoft.EntityFrameworkCore.Tools
dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design
dotnet aspnet-codegenerator controller -name TodoItemsController --useAsyncActions -api -m TodoItem -dc TodoContext -outDir Controllers -sqlite

※SQL Server用のパッケージはスキャフォールディングを実行するために必要です。

【参考】dotnet-aspnet-codegeneratorについて

コマンドライン上でスキャフォールディングを実行する際に必要なツールです。
今回使用しているオプションを簡単に説明します。

  • -name:作成するコントローラーの名前
  • -async:非同期アクションメソッドを作成する
  • -api:ビューがないAPIコントローラーを生成
  • -m:使用するモデルクラス名
  • -dc:使用するDbContextクラス名
  • -outDir:出力先フォルダの相対パス
  • -sqlite:SQLiteを使用する場合に指定

その他のオプションなどは公式ドキュメントを参照してください。

dotnet aspnet-codegenerator コマンド
dotnet aspnet-codegenerator コマンドで、ASP.NET Core プロジェクトがスキャフォールディングされます。

作成されたAPIコントローラーの確認

コマンドの実行が完了すると「Controllers」フォルダ内に「TodoItemsController.cs」が作成されます。

自動でTodoItemに対するCRUDメソッドが生成されていることも確認できます。

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using todo_react_app.Models;

namespace todo_react_app.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class TodoItemsController : ControllerBase
    {
        private readonly TodoContext _context;

        public TodoItemsController(TodoContext context)
        {
            _context = context;
        }

        // GET: api/TodoItems
        [HttpGet]
        public async Task<ActionResult<IEnumerable<TodoItem>>> GetTodoItems()
        {
            return await _context.TodoItems.ToListAsync();
        }

        // GET: api/TodoItems/5
        [HttpGet("{id}")]
        public async Task<ActionResult<TodoItem>> GetTodoItem(int id)
        {
            var todoItem = await _context.TodoItems.FindAsync(id);

            if (todoItem == null)
            {
                return NotFound();
            }

            return todoItem;
        }

        // PUT: api/TodoItems/5
        [HttpPut("{id}")]
        public async Task<IActionResult> PutTodoItem(int id, TodoItem todoItem)
        {
            if (id != todoItem.Id)
            {
                return BadRequest();
            }

            _context.Entry(todoItem).State = EntityState.Modified;

            try
            {
                await _context.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!TodoItemExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return NoContent();
        }

        // POST: api/TodoItems
        [HttpPost]
        public async Task<ActionResult<TodoItem>> PostTodoItem(TodoItem todoItem)
        {
            _context.TodoItems.Add(todoItem);
            await _context.SaveChangesAsync();

            return CreatedAtAction("GetTodoItem", new { id = todoItem.Id }, todoItem);
        }

        // DELETE: api/TodoItems/5
        [HttpDelete("{id}")]
        public async Task<IActionResult> DeleteTodoItem(int id)
        {
            var todoItem = await _context.TodoItems.FindAsync(id);
            if (todoItem == null)
            {
                return NotFound();
            }

            _context.TodoItems.Remove(todoItem);
            await _context.SaveChangesAsync();

            return NoContent();
        }

        private bool TodoItemExists(int id)
        {
            return _context.TodoItems.Any(e => e.Id == id);
        }
    }
}

マイグレーションの実行

DBにテーブルを作成するため、以下のコマンドでマイグレーションを実行します。

dotnet ef migrations add InitialCreate
dotnet ef database update

実行が完了すると、プロジェクトのルート直下に「Migrations」フォルダと、SQLiteのDBファイルである「TodoItem.db」が作成されます。

dbファイルの中身は、VSCodeの拡張機能「SQLite」を使用して確認できます。

「TodoItem.db」を右クリックして「Open Database」をクリックします。

すると、下部に「SQLITE EXPRORER」が表示されるので、展開するとテーブルの構造を確認することができます。

※もしここでエラーになる場合は、拡張機能「SQLite」の再インストールとVSCodeの再起動を試してみてください。

モデルとテーブルの構造が一致していることが確認できました。

APIの動作確認

それでは正しくAPIが動くかどうかを確認してみましょう。

事前にインストールしたVSCodeの拡張機能「REST Client」を使用します。

データの登録

まずはapi/TodoItemsにPOSTリクエストを投げて、新たなTodoアイテムを登録してみたいと思います。

ルート直下に「post.rest」(拡張子がrestまたはhttpであればファイル名は何でも可)というファイルを作成し、以下の内容を記述してください。
※<ポート番号>はデバッグ実行時にブラウザのアドレスバーに表示されているポート番号を設定してください。

POST https://localhost:<ポート番号>/api/todoitems HTTP/1.1
content-type: application/json

{
    "id": 1,
    "name": "プログラミング",
    "isComplete": false
}

F5でデバッグ実行してアプリが実行中の状態で、Ctrl + Alt + R(Macの場合はcommand + option + R)を押すとAPIにリクエストが送信できます。

このようなレスポンスが返ってくれば成功です。

HTTP/1.1 201 Created
X-Powered-By: Express
content-type: application/json; charset=utf-8
date: Tue, 18 Oct 2022 10:39:32 GMT
server: Kestrel
location: https://localhost:<ポート番号>/api/TodoItems/1
transfer-encoding: chunked
connection: close

{
  "id": 1,
  "name": "プログラミング",
  "isComplete": false
}

データの取得

次に、登録したデータが取得できることを確認するため、api/TodoItemsにGETリクエストを送ってみます。

「get.rest」を作成し、同様の手順でリクエストを送信してみてください。

GET https://localhost:<ポート番号>/api/todoitems

このようなレスポンスが返ってくれば成功です。

HTTP/1.1 200 OK
X-Powered-By: Express
content-type: application/json; charset=utf-8
date: Tue, 18 Oct 2022 23:26:09 GMT
server: Kestrel
transfer-encoding: chunked
connection: close

[
  {
    "id": 1,
    "name": "プログラミング",
    "isComplete": false
  }
]

以上でAPIが正しく動作することが確認できました。

興味のある方はPUTやDELETEのAPIも試してみてください。

まとめ

今回はASP.NET Core Web APIを使用して、CRUDができるAPIを作成しました。

次回はReact + TypeScriptを使って画面を作成し、画面から今回作成したAPIを呼び出せるようにします。

おすすめ書籍

著:増田 智明
¥3,366 (2022/11/30 23:31時点 | Amazon調べ)
\楽天ポイント5倍セール!/
楽天市場
\ポイント5%還元!/
Yahooショッピング

コメント

タイトルとURLをコピーしました