📦

NuGetパッケージでソリューションテンプレートをリリースする方法

2025/03/05に公開

はじめに

C#開発において、NuGetパッケージとテンプレートは非常に重要な役割を果たしています。この記事では、NuGetパッケージを使用してソリューションテンプレートを作成し、配布する方法について説明します。

NuGetとは

NuGetは.NET向けのパッケージマネージャーです。以下のような機能を提供します:

  • ライブラリやツールの配布
  • バージョン管理
  • 依存関係の解決
  • パッケージの更新管理

開発者は NuGet を使用して、自作のライブラリを公開したり、他の開発者が作成したパッケージを利用したりすることができます。

.NETのテンプレートシステム

.NETのテンプレートシステムは、プロジェクトやファイルの雛形を提供する機能です。Microsoftのドキュメントによると、テンプレートシステムには2つの重要な概念があります:

  1. テンプレートパッケージ: NuGetパッケージとして提供され、dotnet new install {package-id} コマンドでインストールします。これは複数のテンプレートを含むことができる「コンテナ」のようなものです。

  2. テンプレート: インストールされたパッケージに含まれる実際のテンプレートで、dotnet new <テンプレート名> コマンドで使用します。これによって新しいプロジェクトや項目が生成されます。

テンプレートは、生成する内容によって主に以下の種類に分類されます:

  1. プロジェクトテンプレート

    • 単一のプロジェクトを生成
    • 例:コンソールアプリ、Web APIなど
  2. アイテムテンプレート

    • 個別のファイルやコンポーネントを生成
    • 例:クラス、インターフェース、設定ファイルなど
  3. ソリューションテンプレート

    • 複数のプロジェクトを含む完全なソリューションを生成
    • プロジェクト間の参照関係も含む

ソリューションテンプレートとは

ソリューションテンプレートは、複数のプロジェクトを含む開発環境の雛形です。最近では、.NET Aspireのスターターテンプレートのように、マイクロサービスアーキテクチャを採用したアプリケーションの開発に必要な以下のような複数のプロジェクトを一括で生成し、プロジェクト間の依存関係も適切に設定してくれる便利なテンプレートが提供されています:

  • フロントエンドのWebアプリケーション(Blazorなど)
  • バックエンドのWebAPI
  • バックグラウンドワーカーサービス
  • 共通ライブラリ(ドメインモデル、DTO等)
  • アプリケーションのオーケストレーションを行うAspireホストプロジェクト
  • 開発環境の設定(docker-compose、launchSettings.jsonなど)

このような使いやすいテンプレートを見て、「自分たちのプロジェクトでも同様のテンプレートがあれば、新規プロジェクトの立ち上げがもっと効率的になるのでは?」と考えた方も多いのではないでしょうか。

ソリューションテンプレートには以下のような特徴があります:

  • 複数プロジェクトの構成を一括で生成
  • プロジェクト間の依存関係を自動設定
  • 共通の設定やファイルを含める
  • ベストプラクティスやアーキテクチャパターンの実装例を提供
  • チーム内での開発標準の共有と展開が容易

テンプレートのリリース手順

1. 既存ソリューションからテンプレートを作成

既存のソリューションをテンプレート化する場合は、以下の手順で行います:

  1. ソリューションをクリーンアップ

    • bin、objフォルダの削除
    • ユーザー固有の設定ファイルの削除
    • 機密情報の削除
  2. プレースホルダーの設定

    • プロジェクト名の置換
    • 名前空間の置換
    • カスタマイズ可能な値の特定

2. テンプレートプロジェクトの作成

dotnet new install Microsoft.TemplateEngine.Authoring.Templates
dotnet new templatepack

上記のコマンドで、『テンプレートパッケージ』の雛形を作ります。これをしない、プロジェクト単位のテンプレートの場合は、これが不要ではあるのですが、プロジェクト単位のテンプレートでも、「テンプレートパッケージ」を使用することができますので、この方法にした上で、以下で説明する構造にするのが良いです。

3. テンプレートの設定

以下のように、必要な情報をセットします。下記は実際にSekiban用のテンプレートで使用している設定です。
このプロジェクトは、”テンプレートパッケージ”の設定となります。

https://github.com/J-Tech-Japan/Sekiban/blob/0a147a89060e6d29d7cfae38e8ad9f1e60130dc0/templates/Sekiban.Pure.Templates/Sekiban.Pure.Templates.csproj#L1-L44

4. ソリューションテンプレートの構造化

テンプレートパッケージプロジェクトは、以下のような構造になります。Sekiban.Pure.Templatesの実際の構造を参考にしています:

Sekiban.Pure.Templates/                # テンプレートパッケージのルートフォルダ
├── content/                          # 複数のテンプレートを含むコンテナフォルダ
│   ├── Sekiban.Orleans.Aspire/      # テンプレート1
│   │   ├── .template.config/        # テンプレート設定
│   │   │   └── template.json        # テンプレート設定ファイル
│   │   ├── OrleansSekiban.ApiService/  # プロジェクト1
│   │   ├── OrleansSekiban.AppHost/     # プロジェクト2
│   │   ├── OrleansSekiban.Domain/      # プロジェクト3
│   │   └── OrleansSekiban.Playwright/  # プロジェクト4
│   │
│   └── AnotherTemplate/             # テンプレート2(別のテンプレート)
│       ├── .template.config/
│       │   └── template.json
│       ├── Project1/
│       ├── Project2/
│       └── ...
│
├── README.md                         # テンプレートの説明
└── Sekiban.Pure.Templates.csproj     # パッケージプロジェクトファイル

この構造の重要なポイント:

  1. 一つのテンプレートパッケージ(NuGetパッケージ)に複数のテンプレートを含めることができます
  2. 各テンプレートは独自の.template.configフォルダとその中のtemplate.json設定ファイルを持ちます
  3. 各テンプレートは複数のプロジェクトを含むことができ、それらが一つのソリューションを形成します

5. template.jsonの設定

各テンプレートフォルダ内の.template.config/template.jsonファイルは、テンプレートの動作を定義する重要な設定ファイルです。以下は実際のSekiban.Orleans.Aspireテンプレートの設定例と、ソリューションテンプレート設定のポイントです:

{
  "$schema": "http://json.schemastore.org/template",
  "author": "J-Tech Japan",
  "classifications": [
    "Aspire", "Orleans", "Sekiban", "EventSourcing"
  ],
  "name": "Microsoft Orleans with Sekiban Event Sourcing Aspire Template",
  "description": "SekibanPureOrleansAspire",
  "identity": "Sekiban.Pure.Orleans.Aspire",
  "shortName": "sekiban-orleans-aspire",
  "tags": {
    "language": "C#",
    "type": "solution"
  },
  "sourceName": "OrleansSekiban",
  "preferNameDirectory": true,
  "sources": [
    {
      "source": "./",
      "target": "./",
      "exclude": [
        ".template.config/**/*",
        ".idea",
        "**/bin/**",
        "**/obj/**"
      ]
    }
  ],
  "primaryOutputs": [
    {
      "path": "OrleansSekiban.sln"
    }
  ],
  "postActions": [
    {
      "id": "restore",
      "actionId": "210D431B-A78B-4D2F-B762-4ED3E3EA9025",
      "manualInstructions": [
        {
          "text": "Run 'dotnet restore'"
        }
      ],
      "continueOnError": true
    }
  ]
}

ソリューションテンプレート設定のポイント

  1. 基本メタデータ

    • author: テンプレート作成者
    • classifications: テンプレートの分類タグ(検索やフィルタリングに使用)
    • name: テンプレートの完全な名前
    • description: 簡単な説明
    • identity: テンプレートの一意識別子
    • shortName: コマンドラインで使用する短い名前(dotnet new <shortName>
  2. ソリューション固有の設定

    • tags.type: "solution"を指定してソリューションテンプレートであることを示す
    • sourceName: テンプレート内で置換される名前(プロジェクト名やファイル名に使用)
    • preferNameDirectory: プロジェクト名のディレクトリを自動作成するかどうか
  3. ファイル処理

    • sources: 含める/除外するファイルを定義
    • exclude: 除外するファイルパターン(.template.config, bin, objフォルダなど)
  4. 出力設定

    • primaryOutputs: 主要な出力ファイル(ソリューションファイル)を定義
  5. 生成後のアクション

    • postActions: テンプレート生成後に実行するアクション(dotnet restoreなど)

6. パッケージの作成とリリース

# パッケージのビルド
dotnet pack

# NuGetへの公開
dotnet nuget push bin/Release/Your.Template.Package.1.0.0.nupkg --api-key your-api-key --source https://api.nuget.org/v3/index.json

Github Actionsを使ったリリース方法

以下のように、Github Actionsにリリースを設定することも可能です。API-Keyはnuget.orgで自分のアカウントで設定します。

https://github.com/J-Tech-Japan/Sekiban/blob/0a147a89060e6d29d7cfae38e8ad9f1e60130dc0/.github/workflows/packageTemplate.yml#L1-L23

テンプレートの使用方法

テンプレートパッケージをリリースした後、ユーザーは以下のコマンドでテンプレートパッケージをインストールして、その中に含まれるテンプレートを使用できます:

# テンプレートパッケージのインストール
dotnet new install Your.Template.Package

# インストールされたテンプレートの一覧を確認
dotnet new list

# 特定のテンプレートを使用して新しいソリューションを作成
# ここでsolutionnameはテンプレートのshortName
dotnet new solutionname -n YourNewSolution

# パラメータを指定してソリューションを作成
dotnet new solutionname -n YourNewSolution --DatabaseType PostgreSQL --IncludeTests false

特定のテンプレートで利用可能なパラメータの一覧は以下のコマンドで確認できます:

dotnet new solutionname --help

テンプレートのカスタマイズ

パラメータの設定

template.jsonでは、以下のようにパラメータを定義できます:

基本的なパラメータの例

{
  "symbols": {
    "DatabaseType": {
      "type": "parameter",
      "datatype": "choice",
      "choices": [
        {
          "choice": "SQLServer",
          "description": "Microsoft SQL Server"
        },
        {
          "choice": "PostgreSQL",
          "description": "PostgreSQL Database"
        }
      ],
      "defaultValue": "SQLServer",
      "description": "データベースの種類を選択"
    },
    "IncludeTests": {
      "type": "parameter",
      "datatype": "bool",
      "defaultValue": "true",
      "description": "テストプロジェクトを含めるかどうか"
    }
  }
}

パラメータの使用例

コード内での使用

// DatabaseType パラメータの使用例
public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
#if (DatabaseType == "SQLServer")
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
#elif (DatabaseType == "PostgreSQL")
        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection")));
#endif
    }
}

パラメータのバリデーション

template.jsonでパラメータのバリデーションを設定できます:

{
  "symbols": {
    "Port": {
      "type": "parameter",
      "datatype": "integer",
      "minValue": 1024,
      "maxValue": 65535,
      "defaultValue": "5000",
      "description": "アプリケーションのポート番号"
    },
    "ProjectName": {
      "type": "parameter",
      "datatype": "string",
      "forms": {
        "global": [ "pascal", "kebab" ]
      },
      "replaces": "MyProject",
      "fileRename": {
        "MyProject": {
          "default": "pascal"
        }
      }
    }
  }
}

エラーハンドリング

テンプレート生成時のエラーハンドリングは以下のように実装できます:

{
  "symbols": {
    "ErrorHandling": {
      "type": "computed",
      "value": "(Port >= 1024 && Port <= 65535) ? '' : 'Invalid port number'"
    }
  },
  "forms": {
    "identifier": {
      "validator": {
        "pattern": "^[a-zA-Z][a-zA-Z0-9_]*$",
        "errorMessage": "Invalid identifier format"
      }
    }
  }
}

条件付きコンテンツ

.template.config/template.jsonで条件付きの含有を設定できます。これにより、パラメータの値に応じて特定のファイルやフォルダを含めるかどうかを制御できます:

{
  "sources": [
    {
      "modifiers": [
        {
          "condition": "(IncludeTests)",
          "include": ["Test/**/*"],
          "exclude": ["Test/obj/**/*", "Test/bin/**/*"]
        }
      ]
    }
  ]
}

ベストプラクティス

  1. テンプレートの構造化

    • 明確なフォルダ構造
    • 適切な名前付け規則
    • ドキュメントの提供
  2. バージョン管理

    • セマンティックバージョニングの使用
    • 変更履歴の記録
    • 後方互換性の考慮
  3. テスト

    • テンプレートの動作確認
    • 異なる設定での生成テスト
    • CI/CDパイプラインでの自動テスト

テンプレートのデバッグ

デバッグモードの有効化

テンプレート開発時のデバッグを容易にするため、以下の環境変数を設定できます:

# Windows
set DOTNET_NEW_DEBUG=1

# macOS/Linux
export DOTNET_NEW_DEBUG=1

トラブルシューティング

  1. テンプレート生成の詳細ログ

    dotnet new solutionname --debug
    
  2. テンプレートの検証

    dotnet new --debug-template-processing
    
  3. 一般的な問題の解決

    • テンプレートが見つからない場合は、インストール状態を確認
    dotnet new --list
    
    • キャッシュのクリア
    dotnet new --debug-template-cache --clear-cache
    

デバッグ用の設定例

template.jsonにデバッグ情報を追加:

{
  "symbols": {
    "debug": {
      "type": "generated",
      "generator": "debug",
      "parameters": {
        "format": "json"
      }
    }
  }
}

まとめ

ソリューションテンプレートは、プロジェクトの初期設定を効率化し、一貫性のある開発環境を提供するための強力なツールです。NuGetパッケージとして配布することで、チーム内での共有や、コミュニティへの公開が容易になります。適切なパラメータ設定とカスタマイズオプションを提供することで、より柔軟で再利用可能なテンプレートを作成できます。

今回リリースした、Sekiban + Orleans + Aspireのテンプレート

上記の方法を使って、Sekibanを使って、すぐに開発を開始できるテンプレートをリリースしました。

Orleans と Aspire 統合を備えた新しい Sekiban プロジェクトをすぐに作成するには:

# Sekibanテンプレートをインストール
dotnet new install Sekiban.Pure.Templates

# 新しいプロジェクトを作成
dotnet new sekiban-orleans-aspire -n MyProject

これだけで開始することができます。

このテンプレートには以下が含まれています:

  • Orleansのための.NET Aspireホスト
  • クラスターストレージ
  • グレイン永続ストレージ
  • キューストレージ

これに加え、LLM向けのSekibanの使い方をこちらにまとめています。

https://github.com/J-Tech-Japan/Sekiban/blob/main/README_Pure_For_LLM.md

よろしければご使用ください。

Discussion