Start Debugging

解決: The 'interceptors' feature is not enabled in this namespace

CS9137 は Microsoft.AspNetCore.OpenApi のソースジェネレーターが原因です。PackageReference を持つプロジェクトだけでなく、AddOpenApi を呼び出すすべてのプロジェクトに InterceptorsNamespaces を追加してください。

PackageReference を持つプロジェクトではなく、コンパイラーがエラーで名指ししたプロジェクトに次を追加してください。

<!-- .NET 10 / .NET 11, Microsoft.AspNetCore.OpenApi 10.0.x -->
<PropertyGroup>
  <InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>
</PropertyGroup>

Microsoft.AspNetCore.OpenApi に含まれる XML コメント用のソースジェネレーターはインターセプターを出力しますが、その名前空間を許可する MSBuild プロパティはパッケージの build/ フォルダーで配布されています。NuGet は build/ProjectReference 越しにも推移的なパッケージ依存越しにも流しませんが、アナライザーは流します。そのためジェネレーターだけを継承してプロパティを継承しなかったプロジェクトは、コンパイルに失敗します。以下の内容はすべて SDK 10.0.201 と Microsoft.AspNetCore.OpenApi 10.0.10 で検証しています。

エラーの全体像

コンパイラーは、自分では書いた覚えのない obj 配下のファイルを指し示します。

obj\Debug\net10.0\Microsoft.AspNetCore.OpenApi.SourceGenerators\Microsoft.AspNetCore.OpenApi.SourceGenerators.XmlCommentGenerator\OpenApiXmlCommentSupport.generated.cs(598,10):
error CS9137: The 'interceptors' feature is not enabled in this namespace. Add '<InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>' to your project.

CS9137 には必ず名前空間が添えられており、その名前空間からどのジェネレーターが不満を述べているのかが分かります。

メッセージ内の名前空間ジェネレーター
Microsoft.AspNetCore.OpenApi.GeneratedOpenAPI 向けの XML ドキュメントコメント
Microsoft.Extensions.Validation.GeneratedMinimal API の検証 (.NET 10 以降)
Microsoft.AspNetCore.Http.Validation.Generated同じジェネレーターの .NET 10 プレビュー時の名前
Microsoft.AspNetCore.Http.GeneratedRequest delegate generator (Native AOT の Minimal API)
Microsoft.Extensions.Configuration.Binder.SourceGeneration構成バインダーのジェネレーター

正式リリース済みの SDK で手作業の対応が必要なのは、最初の 1 つだけです。残りの 3 つは .NET SDK 自身が Microsoft.NET.Sdk.FrameworkReferenceResolution.targets で面倒を見ています。

<!-- SDK 10.0.201, Sdks/Microsoft.NET.Sdk/targets/Microsoft.NET.Sdk.FrameworkReferenceResolution.targets -->
<InterceptorsPreviewNamespaces Condition="'$(EnableRequestDelegateGenerator)' == 'true'">$(InterceptorsPreviewNamespaces);Microsoft.AspNetCore.Http.Generated</InterceptorsPreviewNamespaces>
<InterceptorsPreviewNamespaces Condition="'$(EnableConfigurationBindingGenerator)' == 'true'">$(InterceptorsPreviewNamespaces);Microsoft.Extensions.Configuration.Binder.SourceGeneration</InterceptorsPreviewNamespaces>
<InterceptorsPreviewNamespaces Condition="'$(_TargetFrameworkVersionWithoutV)' != '' and $([MSBuild]::VersionGreaterThanOrEquals('$(_TargetFrameworkVersionWithoutV)', '10.0'))">$(InterceptorsPreviewNamespaces);Microsoft.Extensions.Validation.Generated</InterceptorsPreviewNamespaces>

もし今も Microsoft.AspNetCore.Http.Validation.Generated という綴りが出ているなら、.NET 10 のプレビュー SDK を使っています。この名前空間はリリース前に改名されたため、2025 年の記事からコピーした対処は今では効果のない文字列にすぎません。

なぜ起きるのか

インターセプターは名前空間単位のオプトインです。Roslyn は、[InterceptsLocation] 属性を含む名前空間が /features:InterceptorsNamespaces でコンパイラーに渡されていない限りその属性を受け付けません。そして MSBuild はこのスイッチを 2 つのプロパティから組み立てており、どちらも Microsoft.CSharp.Core.targets で転送されます。

<!-- SDK 10.0.201, Roslyn/Microsoft.CSharp.Core.targets -->
InterceptorsNamespaces="$(InterceptorsNamespaces)"
InterceptorsPreviewNamespaces="$(InterceptorsPreviewNamespaces)"

OpenAPI の XML コメントジェネレーターは .NET 10 からインターセプターを出力しています。EmitCompilerGeneratedFiles を有効にすると、生成物をそのまま読めます。

// Generated by Microsoft.AspNetCore.OpenApi.SourceGenerators 10.0.10
namespace Microsoft.AspNetCore.OpenApi.Generated
{
    file static class GeneratedServiceCollectionExtensions
    {
        [global::System.Runtime.CompilerServices.InterceptsLocationAttribute(1, "iPD7FWRAJiLeb9V88cfKbz0BAABDbGFzczEuY3M=")]
        public static IServiceCollection AddOpenApi(this IServiceCollection services, string documentName)
        {
            return services.AddOpenApi(documentName, options =>
            {
                options.AddSchemaTransformer(new XmlCommentSchemaTransformer());
                options.AddOperationTransformer(new XmlCommentOperationTransformer());
            });
        }
    }
}

機能の全体はこれだけです。AddOpenApi() の呼び出し箇所がコンパイル時に書き換えられ、XML コメントを運ぶ 2 つのトランスフォーマーも登録する呼び出しになります。ここに任意の部分も遅延評価もないため、許可されていない名前空間は警告ではなくビルドエラーになります。

パッケージは 1 つのファイルで、この名前空間を代わりに有効化します。

<!-- microsoft.aspnetcore.openapi/10.0.10/build/Microsoft.AspNetCore.OpenApi.targets -->
<Project>
  <PropertyGroup>
    <InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>
  </PropertyGroup>
  <Target Name="GenerateAdditionalXmlFilesForOpenApi" AfterTargets="ResolveReferences">
    ...
  </Target>
</Project>

フォルダー名に注目してください。buildTransitive ではなく build です。この非対称性がエラー全体の土台になっています。ASP.NET Core 自身のパッケージング設定でも、その行は明示的です。

<!-- dotnet/aspnetcore, src/OpenApi/src/Microsoft.AspNetCore.OpenApi.csproj -->
<None Include="..\build\Microsoft.AspNetCore.OpenApi.targets" Pack="true" PackagePath="build" Visible="false" />

NuGet が間接的な利用者に流すのは buildTransitive/ のアセットだけです。一方でアナライザーは流れます。したがって ProjectReference 経由で Microsoft.AspNetCore.OpenApi を受け取ったプロジェクトは、ソースジェネレーターだけを手にし、その出力を正当化する MSBuild の仕組みは何も手に入りません。原因と対処が別々のプロジェクトに存在するため、初見ではエラーが意味不明に見えます。プロパティはすでに追加済みで、ただコンパイラーが文句を言っているプロジェクトに入っていないだけなのです。

プロジェクトが境界のどちら側にいるかは、コマンド 1 つで分かります。

dotnet msbuild MyApi.csproj -getProperty:InterceptorsNamespaces

値が空ならプロパティは届いていません。;Microsoft.AspNetCore.OpenApi.Generated なら届いています。

最小限の再現

プロジェクトは 2 つです。1 つ目がパッケージを持ち、2 つ目は 1 つ目を参照するだけです。

<!-- Defaults.csproj -- .NET 10, Microsoft.AspNetCore.OpenApi 10.0.10 -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" />
  </ItemGroup>
</Project>
<!-- Modules.csproj -- .NET 10, no PackageReference of its own -->
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>net10.0</TargetFramework>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
  </PropertyGroup>
  <ItemGroup>
    <ProjectReference Include="..\Defaults\Defaults.csproj" />
  </ItemGroup>
</Project>

どちらも AddOpenApi を呼び出します。

// Modules/Class1.cs -- .NET 10, C# 14
using Microsoft.Extensions.DependencyInjection;

namespace Modules;

/// <summary>Module registrations.</summary>
public static class ModuleExtensions
{
    /// <summary>Adds the module's OpenAPI document.</summary>
    public static IServiceCollection AddModule(this IServiceCollection services)
        => services.AddOpenApi("module");
}

dotnet build Defaults は成功します。dotnet build Modules は CS9137 で失敗します。この形は、利用側のプロジェクトが Microsoft.NET.Sdk でも Microsoft.NET.Sdk.Web でも変わりません。dotnet new web で作った API から共有の既定値ライブラリへの ProjectReference だけがパッケージへの経路という構成でも再現しました。Web プロジェクトであることは、ここでは何の助けにもなりません。

実際のコードベースがこれを踏むパターンは、見分けのつく 3 種類です。AddOpenApi を集約する Aspire 風の ServiceDefaults ライブラリ、各モジュールが自前のドキュメントを登録するモジュラーモノリス、そしてパッケージが 1 階層上のベースパッケージに置かれているプラグインや CMS のホスト (Umbraco と OrchardCore のモジュールが issue の報告に登場します) です。

対処法、おすすめ順

1. 失敗しているプロジェクトにプロパティを追加する

エラーメッセージから名前空間をコピーし、エラーが名指しした .csproj にプロパティを貼り付けます。

<!-- .NET 10 / .NET 11, Microsoft.AspNetCore.OpenApi 10.0.x -->
<PropertyGroup>
  <InterceptorsNamespaces>$(InterceptorsNamespaces);Microsoft.AspNetCore.OpenApi.Generated</InterceptorsNamespaces>
</PropertyGroup>

先頭の $(InterceptorsNamespaces); は残してください。他のジェネレーターも同じプロパティに追記するため、素の代入ではそのエントリーを捨ててしまいます。この方法ならジェネレーターは動き続けるので、XML コメントも引き続きドキュメントに届きます。

ソリューション内の複数プロジェクトがドキュメントを登録しているなら、各ファイルに繰り返し書くのではなく Directory.Build.props に一度だけ書きます。パッケージの targets はプロジェクト本体の後にインポートされ、見つけた値に追記するので、両者がぶつかることはありません。

2. プロジェクトに自前の PackageReference を持たせる

そのプロジェクトが実際に AddOpenApi を呼んでいるなら、パッケージを持つ正当な理由があります。

dotnet add Modules package Microsoft.AspNetCore.OpenApi

これで build/Microsoft.AspNetCore.OpenApi.targets が取り込まれ、プロパティが自動的に設定されます。他に何も変更せずエラーが消えることを確認しました。推移的な構成よりも素直ですし、後から誰かがプロジェクト参照を組み替えても壊れません。

3. そのプロジェクトでジェネレーターを止める

XML コメントに関心のないプロジェクトなら、出力を許可するのではなくアナライザーを取り除きます。公式ドキュメントはパッケージパスのプロパティを使う書き方を示していますが、項目ベースの書き方のほうが壊れにくいです。

<!-- .NET 10, disables the OpenAPI XML comment generator for this project only -->
<Target Name="DisableOpenApiXmlGenerator" BeforeTargets="CoreCompile">
  <ItemGroup>
    <Analyzer Remove="@(Analyzer)"
              Condition="'%(Filename)' == 'Microsoft.AspNetCore.OpenApi.SourceGenerators'" />
  </ItemGroup>
</Target>

ビルドは通るようになり、その代償としてそのプロジェクトの OpenAPI 出力から <summary><param> のテキストが消えます。同じ効果を反対側から得るには、上流の PackageReferencePrivateAssets="analyzers" を付けます。これでそのライブラリのどの利用者にもジェネレーターが届かなくなります。

<!-- Defaults.csproj -- .NET 10, keeps the generator out of downstream projects -->
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="10.0.10" PrivateAssets="analyzers" />

どちらも検証済みの対処です。ビルドサーバーが赤で、ドキュメントよりもパイプラインの復旧が先というときに使ってください。

ビルドが赤のままになる落とし穴

InterceptorsPreviewNamespaces は今も動きますが、現代的な書き方ではありません。 SDK 10.0.201 の C# 14 コンパイラーは古いプロパティを警告なしで受け付けますし、SDK 自身も 3 つの名前空間に対して今なおこちらを使っています。新しいコードでは InterceptorsNamespaces を使ってください。ただし、動いているプロパティを改名して既存のビルドを「直す」必要はありません。

XML ドキュメントを無効にしてもジェネレーターは止まりません。 <GenerateDocumentationFile>false</GenerateDocumentationFile> は XML コメントのジェネレーターを回避できそうに見えますが、回避できません。ジェネレーターは変わらず AddOpenApi をインターセプトし、同じ CS9137 が 598 行目ではなく 597 行目で返ってきます。止めたいならアナライザーを取り除いてください。

インターセプトは呼び出し箇所ごと、コンパイル単位ごとです。 これはビルドが緑になった後で何時間も奪う類の話です。トランスフォーマーは AddOpenApi() の呼び出しが物理的に置かれているコンパイルに焼き込まれ、そのコンパイルの XML コメントだけを運びます。呼び出しを共有の既定値ライブラリへ移すと、API プロジェクトの概要は何も言わずにドキュメントから消えます。上記の再現環境で、AddOpenApi() を API プロジェクト内に直接書いた場合の計測結果です。

{
  "summary": "Gets a single widget by id.",
  "parameters": [
    { "name": "id", "description": "The widget identifier." }
  ]
}

同じエンドポイントを同じように文書化しても、AddOpenApi() を参照先ライブラリから呼んだ場合は summary もパラメーターの description も出力されません。コメントを反映させたいプロジェクトの中に AddOpenApi() の呼び出しを置くか、他のアセンブリの XML ファイルを AdditionalFiles で渡してください。

リテラルでないドキュメント名はインターセプトされません。 documentName が変数である AddOpenApi(documentName) はインターセプターをまったく生成しないため、エラーも出ませんし XML コメントも入りません。認識されるのはリテラル文字列だけです。

EmitCompilerGeneratedFiles はより厄介な二次エラーを生むことがあります。 生成されたファイルを覗くのは正しい発想ですが、CompilerGeneratedFilesOutputPath をプロジェクトディレクトリ内のフォルダーに向けると、出力されたファイルが次のビルドで本物のコンパイル入力になります。

Generated\...\OpenApiXmlCommentSupport.generated.cs(67,42):
error CS0433: The type 'XmlComment' exists in both 'Api' and 'Api'

出力パスは obj の下に置くか、対応する <Compile Remove> を追加してください。

インターセプターは、見た目ほど LangVersion の問題ではありません。 C# 12 以降で正当な機能なので、名前空間さえ許可されていれば明示的な <LangVersion>12.0</LangVersion> でも生成ファイルは問題なくコンパイルされます。言語バージョンを追いかける必要はありません。

単一のプロジェクトではなく依存グラフ全体を確認してください。 エラーで名指しされたプロジェクトを直すと、次のビルドでは 1 つ上流のプロジェクトが同じように失敗することがよくあります。ソリューション全体に対して dotnet msbuild <project> -getProperty:InterceptorsNamespaces を実行すれば、一度にすべて見つかります。

関連記事

このエラーの背後にあるジェネレーターは、ソースジェネレーターとは何か、いつ必要になるのか で説明した仕組みそのものです。インターセプターの機構自体は C# 12 のインターセプター で扱っています。アップグレードの途中でここにたどり着いた方へ。同じパッケージ系統のもう 1 つのビルド破壊は 消えた OpenApiReference 型 で、より大きな移行は Swashbuckle から組み込みの OpenAPI ジェネレーターへの移行 にまとめてあります。ドキュメントが再びビルドできたら、次の一手は 操作トランスフォーマーとスキーマトランスフォーマーによるカスタマイズ で、Scalar での配信 がその前面に UI を用意します。

参考資料

Comments

Sign in with GitHub to comment. Reactions and replies thread back to the comments repo.

< 戻る