RicoSuter/NSwag · error · InvalidOperationException

API Explorer not registered in DI.

Error message

API Explorer not registered in DI.

What it means

OpenApiDocumentMiddleware relies on ASP.NET Core's IApiDescriptionGroupCollectionProvider, which is only registered when API Explorer services (AddApiExplorer / AddMvc with explorer) are present in the DI container. When resolving it via GetService returns null, the constructor throws InvalidOperationException('API Explorer not registered in DI.').

Solutions

  1. Call builder.Services.AddEndpointsApiExplorer() (minimal hosting) or services.AddApiExplorer() / AddMvc() in ConfigureServices.
  2. Verify services.AddOpenApiDocument()/AddSwaggerDocument() is called before building the pipeline.
  3. Ensure the middleware is added in the same app that registered the MVC/API Explorer services.

Example fix

// before
var app = builder.Build();
app.UseOpenApi();
// after
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddOpenApiDocument();
var app = builder.Build();
app.UseOpenApi();
Defensive patterns

Strategy: validation

Validate before calling

// before UseOpenApi
if (!builder.Services.Any(d => d.ServiceType.Name == "IApiDescriptionGroupCollectionProvider"))
    builder.Services.AddEndpointsApiExplorer();

Type guard

bool ApiExplorerRegistered(IServiceProvider sp) => sp.GetService<Microsoft.AspNetCore.Mvc.ApiExplorer.IApiDescriptionGroupCollectionProvider>() != null;

Try / catch

try { app.UseOpenApi(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("API Explorer not registered")) { logger.LogError(ex, "Call AddEndpointsApiExplorer()/AddMvc() before UseOpenApi"); throw; }

Prevention

When it happens

Trigger: Mapping UseOpenApi()/UseSwagger() middleware in an app whose services were never registered with AddApiExplorer (or AddMvc/AddControllers variants that register it), so serviceProvider.GetService<IApiDescriptionGroupCollectionProvider>() returns null.

Common situations: Minimal APIs without AddEndpointsApiExplorer/AddApiExplorer; hosting NSwag middleware in a bare WebApplication or non-MVC host; upgrading to minimal hosting and dropping explorer registration.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


AI-assisted analysis of RicoSuter/NSwag@63daf8fcc3 (2026-09-14). Data as JSON: /api/errors/d945204bfbba5946. Report an issue: GitHub.

Appendix: source

Thrown at src/NSwag.AspNetCore/Middlewares/OpenApiDocumentMiddleware.cs:45

        private int _version;
        private readonly object _documentsCacheLock = new object();
        private readonly Dictionary<string, Tuple<string, ExceptionDispatchInfo, DateTimeOffset>> _documentsCache = [];

        /// <summary>Initializes a new instance of the <see cref="OpenApiDocumentMiddleware"/> class.</summary>
        /// <param name="nextDelegate">The next delegate.</param>
        /// <param name="serviceProvider">The service provider.</param>
        /// <param name="documentName">The document name.</param>
        /// <param name="path">The document path.</param>
        /// <param name="settings">The settings.</param>
        public OpenApiDocumentMiddleware(RequestDelegate nextDelegate, IServiceProvider serviceProvider, string documentName, string path, OpenApiDocumentMiddlewareSettings settings)
        {
            _nextDelegate = nextDelegate;

            _documentName = documentName;
            _path = path.StartsWith('/') ? path : '/' + path;

            _apiDescriptionGroupCollectionProvider = serviceProvider.GetService<IApiDescriptionGroupCollectionProvider>() ??
                throw new InvalidOperationException("API Explorer not registered in DI.");
            _documentProvider = serviceProvider.GetService<OpenApiDocumentProvider>() ??
                throw new InvalidOperationException("The NSwag DI services are not registered: Call " + nameof(NSwagServiceCollectionExtensions.AddSwaggerDocument) + "() in ConfigureServices().");

            _settings = settings;
        }

        /// <summary>Invokes the specified context.</summary>
        /// <param name="context">The context.</param>
        /// <returns>The task.</returns>
        public async Task Invoke(HttpContext context)
        {
            if (context.Request.Path.HasValue && string.Equals(context.Request.Path.Value, _path, StringComparison.OrdinalIgnoreCase))
            {
                var schemaJson = await GetDocumentAsync(context);
                context.Response.StatusCode = 200;
                context.Response.Headers["Content-Type"] = _path.Contains(".yaml", StringComparison.OrdinalIgnoreCase) ?
                    "application/yaml; charset=utf-8" :
                    "application/json; charset=utf-8";

View on GitHub (pinned to 63daf8fcc3)