Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

This article is a Private article. Only a writer and users who know the URL can access it.
Please change open range to public in publish setting if you want to share this article with other users.

ASP.NET Core Minimal API + Vertical Slice + Transaction Script の実装例

0
Posted at

はじめに

タイトルの通り、ASP.NET Core Minimal API + Vertical Slice + Transaction Script の実装例についての備忘録です。

改訂履歴

  • 2026/08/26 : 初版公開。

本文

1. 環境

  • .NET 8.0
  • Visual Studio Community 2022

2. 構成

ソリューションの構成は以下の通りです。Vertical Slice らしく、Features 配下を機能単位で分割しています。

MinimalApiExample/
│
├── Entities/
│   ├── OrderEntity.cs
│   ├── OrderItemEntity.cs
│   ├── TableNames.cs
│   └── UserEntity.cs
│
├── Features/
│   ├── Orders/
│   │   ├── CreateOrder.cs
│   │   └── DeleteOrder.cs
│   │
│   ├── Users/
│   │   ├── CreateUser.cs
│   │   ├── DeleteUser.cs
│   │   ├── GetAllUsers.cs
│   │   ├── GetUser.cs
│   │   └── UpdateUser.cs
│   │
│   └── IEndpoint.cs
│
├── Infrastructure/
│   └── DbConnectionFactory.cs
│
├── Utils/
│   └── TypeExtensions.cs
│
└── Program.cs

今回は API 毎に 1 クラスを用意し、各クラスの中で HTTP エンドポイントの定義と、そのエンドポイントで必要となる処理を完結させています。API は以下の通りです。

3. 実装例

全てのソースを記載すると、それなりの分量になるため、大事なところだけをピックアップしていきます。

3-1. IEndpoint

まずは IEndpoint です。このインターフェースでは MapEndpoint を必須としています。static abstract を利用することで、各クラスにインスタンスを生成することなく、エンドポイント登録用のメソッドを統一できます。

IEndpoint.cs
namespace MinimalApiExample.Features;

/// <summary>
/// エンドポイントを表します。
/// </summary>
public interface IEndpoint
{
    /// <summary>
    /// エンドポイントを追加します。
    /// </summary>
    /// <param name="app">ルートビルダー。</param>
    public static abstract void MapEndpoint(IEndpointRouteBuilder app);
}

3-2. CreateUser

次は IEndpoint を実装した CreateUser クラスです。GetNamespaceName() は名前空間の末尾を取得する自作の拡張メソッドです。Swagger のタグとして利用しています。

CreateUser.cs
using System.Data;
using Dapper;
using MinimalApiExample.Entities;
using MinimalApiExample.Infrastructure;
using MinimalApiExample.Utils;

namespace MinimalApiExample.Features.Users;

/// <summary>
/// ユーザー作成を提供します。
/// </summary>
public sealed class CreateUser: IEndpoint
{
    private static readonly string s_route = "/users";

    /// <summary>
    /// ユーザー作成の要求を表します。
    /// </summary>
    /// <param name="Name">ユーザー名。</param>
    /// <param name="Email">ユーザーの E メール。</param>
    public sealed record Request(string Name, string Email);

    /// <inheritdoc/>
    public static void MapEndpoint(IEndpointRouteBuilder app)
        => app
            .MapPost(s_route, Execute)
            .WithTags(typeof(CreateUser).GetNamespaceName())
            .WithSummary("ユーザー作成 API");

    private static async Task<IResult> Execute(
        Request request,
        DbConnectionFactory factory,
        CancellationToken ct)
    {
        var parameter = new UserEntity
        {
            Id = Guid.NewGuid(),
            Name = request.Name,
            Email = request.Email,
            CreatedAt = DateTime.Now
        };

        string sql = $"""
            INSERT INTO {TableNames.User}
            (
                {nameof(UserEntity.Id)}
               ,{nameof(UserEntity.Name)}
               ,{nameof(UserEntity.Email)}
               ,{nameof(UserEntity.CreatedAt)}
            )
            VALUES
            (
                @{nameof(UserEntity.Id)}
               ,@{nameof(UserEntity.Name)}
               ,@{nameof(UserEntity.Email)}
               ,@{nameof(UserEntity.CreatedAt)}
            )
            """;

        using IDbConnection db = factory.Create();

        await db.ExecuteAsync(
            new CommandDefinition(
                sql,
                parameter,
                cancellationToken: ct));

        return Results.Created($"{s_route}/{parameter.Id}", parameter);
    }
}

3-3. Program

最後はエントリポイントの Program クラスです。Swagger のオプション設定、DbConnectionFactory クラスの DI 登録、各エンドポイントの登録を追加しています。

Program.cs
using MinimalApiExample.Features.Orders;
using MinimalApiExample.Features.Users;
using MinimalApiExample.Infrastructure;

namespace MinimalApiExample;

/// <summary>
/// アプリケーションのエントリポイントを定義します。
/// </summary>
public class Program
{
    /// <summary>
    /// アプリケーションのエントリポイントです。
    /// </summary>
    public static void Main(string[] args)
    {
        WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
        builder.Services.AddAuthorization();
        builder.Services.AddEndpointsApiExplorer();

-       builder.Services.AddSwaggerGen();
+       builder.Services.AddSwaggerGen(options =>
+       {
+           // 名前衝突を回避するため FullName で一意化する。
+           options.CustomSchemaIds(type => type.FullName!.Replace("+", "."));
+       });

+       builder.Services.AddSingleton<DbConnectionFactory>();
        WebApplication app = builder.Build();

        if (app.Environment.IsDevelopment())
        {
            app.UseSwagger();
            app.UseSwaggerUI();
        }

        app.UseAuthorization();

+       // 各エンドポイントの登録。
+       CreateOrder.MapEndpoint(app);
+       CreateUser.MapEndpoint(app);
+       DeleteOrder.MapEndpoint(app);
+       DeleteUser.MapEndpoint(app);
+       GetUser.MapEndpoint(app);
+       GetAllUsers.MapEndpoint(app);
+       UpdateUser.MapEndpoint(app);

        app.Run();
    }
}

4. 参考

おわりに

最初はこれくらいシンプルで良いと思います。記事を書きながら気付きましたが IEndpoint を使えば、各エンドポイントの登録はもっとスマートに実装できそうですね。

0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?