ASP.NET Coreで動的Web APIを自動生成する高度なテクニック

序文

動的Web API(Dynamic Web API)という言葉に触れたのは数年前のことです。当時、ABPフレームワークでこの技術に興味を持ち、ABPのコードを分析し、独立したコンポーネントとして利用する試みもしましたが、ABPへの依存が多すぎたため断念しました。十数日前、友人の熊猫氏がこのコードをABPから正常に分離し、シンプルなデモを作成してくれました。長い間(本当に怠けていました)このコードを修正し、機能を追加し、パッケージ化した結果、現在では独立したコンポーネントとして利用できるようになりました。プロジェクトはGitHubでオープンソース化されています(https://github.com/dotnetauth/Panda.DynamicWebApi)。役に立ったと思われる方はStarをいただけると幸いです。

本記事では使用方法についてのみ説明し、原理の詳細は後の記事で解説します。

紹介

伝統的な3層アーキテクチャ、DDDの古典的な4層アーキテクチャ(DDD Lite)、またはその他のアプリケーションロジック層(ビジネスロジック層)を持つアーキテクチャであっても、Webアプリケーション開発において、私たちのビジネスロジックは最終的にWeb APIを通じて呼び出される必要があります。ここで、私たちは繰り返しの操作に直面することがあります:ビジネスロジックの記述 -> APIの記述。このような繰り返しの操作を解決する方法はありませんか?ビジネスロジックを記述した後、自動的にWeb APIを生成してくれれば理想的です。その答えはもちろんあります。

ここで本記事の主役を紹介します:`Panda.DynamicWebApi`(https://github.com/dotnetauth/Panda.DynamicWebApi)。ABPから派生した、独立して利用可能で、ビジネスロジック層に基づいてASP.NET Core Web API層を自動生成するオープンソースコンポーネントです。生成されるAPIはRESTfulスタイルに準拠しており、条件を満たすクラスに基づいてWeb APIを生成します。MVCフレームワークが直接ロジックを呼び出すため、パフォーマンスの問題はなく、Swaggerと完璧に連携してAPIドキュメントを構築できます。

使用方法

ここでは、DDDの古典的な4層アーキテクチャにおけるアプリケーションロジック層を例に説明します。

1. 準備

(1) 2つのプロジェクトを作成します。1つはアプリケーションロジック層のクラスライブラリプロジェクト、もう1つはWeb APIホストとなるASP.NET Core Web APIプロジェクトです。

(2) アプリケーションロジックの記述

アプリケーションロジックインターフェースを定義します。すべてのアプリケーションロジックはこれを実装する必要があります:

public interface IBaseService
{
}

学生管理ロジックインターフェースを定義し、アプリケーションロジックインターフェースを継承します。

public interface IStudentService : IBaseService
{
    /// <summary>
    /// IDで学生を取得します
    /// </summary>
    /// <param name="studentId"></param>
    /// <returns></returns>
    StudentDto Get(int studentId);

    /// <summary>
    /// すべての学生を取得します
    /// </summary>
    /// <returns></returns>
    List<StudentDto> GetAll();

    /// <summary>
    /// 学生情報を更新します
    /// </summary>
    /// <param name="input"></param>
    void Update(UpdateStudentDto input);

    /// <summary>
    /// 学生の年齢を更新します
    /// </summary>
    /// <param name="age"></param>
    [HttpPatch("{id:int}/age")]
    void UpdateAge(int age);

    /// <summary>
    /// IDで学生を削除します
    /// </summary>
    /// <param name="studentId"></param>
    [HttpDelete("{id:int}")]
    void Delete(int studentId);

    /// <summary>
    /// 学生を追加します
    /// </summary>
    /// <param name="input"></param>
    void Create(CreateStudentDto input);
}

学生ロジック管理インターフェースを実装します:

public class StudentService : IStudentService
{
    /// <summary>
    /// IDで学生を取得します
    /// </summary>
    /// <param name="studentId"></param>
    /// <returns></returns>
    [HttpGet("{id:int}")]
    public StudentDto Get(int studentId)
    {
        return new StudentDto() { Id = 101, Age = 20, Name = "田中" };
    }

    /// <summary>
    /// すべての学生を取得します
    /// </summary>
    /// <returns></returns>
    public List<StudentDto> GetAll()
    {
        return new List<StudentDto>()
        {
            new StudentDto(){ Id = 101, Age = 20, Name = "田中" },
            new StudentDto(){ Id = 102, Age = 21, Name = "鈴木" }
        };
    }

    /// <summary>
    /// 学生情報を更新します
    /// </summary>
    /// <param name="input"></param>
    public void Update(UpdateStudentDto input)
    {
        throw new System.NotImplementedException();
    }

    /// <summary>
    /// 学生の年齢を更新します
    /// </summary>
    /// <param name="age"></param>
    [HttpPatch("{id:int}/age")]
    public void UpdateAge(int age)
    {
        throw new System.NotImplementedException();
    }

    /// <summary>
    /// IDで学生を削除します
    /// </summary>
    /// <param name="studentId"></param>
    [HttpDelete("{id:int}")]
    public void Delete(int studentId)
    {
        throw new System.NotImplementedException();
    }

    /// <summary>
    /// 学生を追加します
    /// </summary>
    /// <param name="input"></param>
    public void Create(CreateStudentDto input)
    {
        throw new System.NotImplementedException();
    }
}

(3) Web APIホストプロジェクトにSwaggerを設定します。

Install-Package Swashbuckle.AspNetCore

Startupでの設定

public void ConfigureServices(IServiceCollection services)
{
    services.AddControllers();

    services.AddSwaggerGen(c =>
    {
        c.SwaggerDoc("v1", new OpenApiInfo { Title = "学生管理システム WebApi", Version = "v1" });

        c.DocumentFilter<AllDocumentFilter>();

        c.IncludeXmlComments(@"bin\Debug
etcoreapp3.1\Xc.StuMgr.WebApiHost.xml");
        c.IncludeXmlComments(@"bin\Debug
etcoreapp3.1\Xc.StuMgr.Application.xml");
    });
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        app.UseDeveloperExceptionPage();
    }

    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "学生管理システム WebApi");
    });

    app.UseRouting();
    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

実行すると、デフォルトのValuesControllerの5つのAPIが表示されます。

2. 動的Web API

NuGetを通じてApplicationプロジェクトにコンポーネントをインストールします:

Install-Package Panda.DynamicWebApi

インターフェース `IBaseService` に `IDynamicWebApi` を継承させ、特性 `[DynamicWebApi]` を追加します。

[DynamicWebApi]
public interface IBaseService : IDynamicWebApi
{
}

Web APIホストプロジェクトのStartupで動的Web APIを設定します:

// 動的Web APIを追加するには、AddControllersの後に配置する必要があります
services.AddDynamicWebApi();

ブラウザを開いてアクセスすると、以下のようになります:

StudentServiceのWeb APIが正常に生成され、Swaggerと完璧に連携していることが確認できます。

詳細な説明

上記の説明から、使用方法は非常に簡単であることがわかります。2つのステップだけです:

ステップ1:クラス(またはそのインターフェース、継承する抽象クラス、ただし親クラスには配置しない)に `IDynamicWebApi` を継承させ、特性 `[DynamicWebApi]` を追加します。

ステップ2:Startupで登録します。

// 動的Web APIを追加するには、AddControllersの後に配置する必要があります
services.AddDynamicWebApi();

MVCのクラスを使用して処理する必要があるため、AddControllersの後に配置する必要があります。このコンポーネントにはチェック機能があります。

1. ルール

このコンポーネントは「規約優先」を採用しているため、実際の使用においていくつかのルールがあります:

(1) クラスが動的APIを生成するためには、2つの条件を満たす必要があります。1つはそのクラスが `IDynamicWebApi` を**直接**または**間接的に**実装していること、もう1つはそのクラス**自体**または**親の抽象クラス**または**実装するインターフェース**が特性 `DynamicWebApi` を持っていることです。

(2) 特性 `[NonDynamicWebApi]` を追加すると、クラスまたはメソッドがAPIを生成しないようにできます。`[NonDynamicWebApi]` は最も高い優先順位を持ちます。

(3) 規則に合致する動的APIの**クラス名**から接尾辞が削除されます。例えば、前述の `StudentService` からは、Service接尾辞が削除されます。このルールは動的に設定できます。

(4) APIルートプレフィックスが自動的に追加されます。デフォルトでは、すべてのAPIに `api` プレフィックスが追加されます。

(5) デフォルトのHTTP動詞は `POST` です。`HttpGet`/`HttpPost`/`HttpDelete` などのASP.NET Coreの組み込み特性で上書きできます。

(6) `HttpGet`/`HttpPost`/`HttpDelete` などの組み込み特性でデフォルトのルートを上書きできます。

(7) デフォルトでは、メソッド名に基づいてHTTP動詞が設定されます。例えば、CreateAppleまたはCreateは `POST` 動詞のAPIを生成します。以下の対応表に一致する場合(大文字小文字を無視)、そのAPI名のこの動詞部分が省略されます。例えば、CreateAppleはAppleになります。以下の対応表にない場合は、デフォルトの動詞 `POST` が使用されます。

メソッド名の先頭 動詞
create POST
add POST
post POST
get GET
find GET
fetch GET
query GET
update PUT
put PUT
delete DELETE
remove DELETE

(8) メソッド名はパスカルケース(PascalCase)を使用することを強く推奨します。これにより、API名の自動処理がより適切に行われ、上記の対応表の動詞を使用しやすくなります。例:

リンゴを追加 -> Add/AddApple/Create/CreateApple

リンゴを更新 -> Update/UpdateApple

...

(9) `[DynamicWebApi]` 特性は継承可能であるため、親クラスが誤って識別されないように、抽象クラスまたはインターフェース以外の親クラスには配置しないでください。

2. 設定

すべての設定は `DynamicWebApiOptions` オブジェクト内にあります。以下に説明します:

プロパティ名 必須か 説明
DefaultHttpVerb いいえ デフォルト値:POST。デフォルトのHTTP動詞
DefaultAreaName いいえ デフォルト値:空。Areaルート名
DefaultApiPrefix いいえ デフォルト値:api。APIルートプレフィックス
RemoveControllerPostfixes いいえ デフォルト値:AppService/ApplicationService。クラス名から削除する接尾辞
RemoveActionPostfixes いいえ デフォルト値:Async。メソッド名から削除する接尾辞
FormBodyBindingIgnoredTypes いいえ デフォルト値:IFormFile。MVCがパラメータリストにバインドしない型。

トラブルシューティング

問題が発生した場合は、Issuesで質問してください。

終わり

プロジェクトのオープンソースアドレス:https://github.com/dotnetauth/Panda.DynamicWebApi。Starをいただけると幸いです。

本記事のデモアドレス:XiaoChen.StudentManagement

ABP:https://github.com/aspnetboilerplate/aspnetboilerplate

タグ: ASP.NET Core Dynamic Web API Panda.DynamicWebApi Swagger DDD

7月19日 18:04 投稿