Skip to content

InvalidOperationException thrown if attempting to document a controller endpoint without an explicit HTTP method #60914

Open
@martincostello

Description

@martincostello

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

If Microsoft.AspNetCore.OpenApi is used to document an endpoint that does not have an explicit HTTP method (e.g. with [HttpGet]) then rendering the OpenAPI document fails with an HTTP 500 due to the following exception:

    [2025-03-13 13:15:45Z] fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
System.InvalidOperationException: Unsupported HTTP method: 
   at ApiDescriptionExtensions.GetOperationType(ApiDescription apiDescription)
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationsAsync(IGrouping`2 descriptions, HashSet`1 capturedTags, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiPathsAsync(HashSet`1 capturedTags, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiDocumentAsync(IServiceProvider scopedServiceProvider, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions.<>c__DisplayClass0_0.<<MapOpenApi>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Http.Generated.<GeneratedRouteBuilderExtensions_g>F56B68D2B55B5B7B373BA2E4796D897848BC0F04A969B1AF6260183E8B9E0BAF2__GeneratedRouteBuilderExtensionsCore.<>c__DisplayClass2_0.<<MapGet0>g__RequestHandler|5>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

The exception is coming from here:

public static OperationType GetOperationType(this ApiDescription apiDescription) =>
apiDescription.HttpMethod?.ToUpperInvariant() switch
{
"GET" => OperationType.Get,
"POST" => OperationType.Post,
"PUT" => OperationType.Put,
"DELETE" => OperationType.Delete,
"PATCH" => OperationType.Patch,
"HEAD" => OperationType.Head,
"OPTIONS" => OperationType.Options,
"TRACE" => OperationType.Trace,
_ => throw new InvalidOperationException($"Unsupported HTTP method: {apiDescription.HttpMethod}"),
};

My hunch is that the operation isn't set by MVC in this case, with the rest of the framework treating it as a GET, but that isn't catered for by the switch statement, which then throws an exception.

The exception is easily avoided by adding the relevant attribute, such as:

using WebApi.Models;
using Microsoft.AspNetCore.Mvc;

namespace WebApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class TimeController(TimeProvider timeProvider) : ControllerBase
{
+   [HttpGet]
    public ActionResult<TimeModel> Now()
        => Ok(new TimeModel { UtcNow = timeProvider.GetUtcNow() });
}

Expected Behavior

The endpoint is rendered in the OpenAPI document as an HTTP GET to match MVC's behaviour when the endpoint is invoked.

Steps To Reproduce

<Project Sdk="Microsoft.NET.Sdk.Web">
  <PropertyGroup>
    <GenerateDocumentationFile>true</GenerateDocumentationFile>
    <RootNamespace>WebApi</RootNamespace>
    <TargetFramework>net9.0</TargetFramework>
  </PropertyGroup>
  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
  </ItemGroup>
</Project>
using System.Text.Json;
using WebApi;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddSingleton(TimeProvider.System);

builder.Services.AddControllers().AddJsonOptions((p) => ConfigureJsonSerialization(p.JsonSerializerOptions));
builder.Services.ConfigureHttpJsonOptions((p) => ConfigureJsonSerialization(p.SerializerOptions));

builder.Services.AddOpenApi();

var app = builder.Build();

app.UseRouting();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Time}/{action=Now}/{id?}");

app.MapOpenApi();

app.Run();

static void ConfigureJsonSerialization(JsonSerializerOptions options)
    => options.TypeInfoResolverChain.Add(MvcSerializerContext.Default);
using WebApi.Models;
using Microsoft.AspNetCore.Mvc;

namespace WebApi.Controllers;

[ApiController]
[Route("api/[controller]")]
public class TimeController(TimeProvider timeProvider) : ControllerBase
{
    public ActionResult<TimeModel> Now()
        => Ok(new TimeModel { UtcNow = timeProvider.GetUtcNow() });
}
namespace WebApi.Models;

public class TimeModel
{
    public DateTimeOffset UtcNow { get; set; }
}
using System.Text.Json.Serialization;
using WebApi.Models;

namespace WebApi;

[JsonSerializable(typeof(DateTimeOffset))]
[JsonSerializable(typeof(TimeModel))]
[JsonSourceGenerationOptions(
    DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
    PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase,
    WriteIndented = true)]
public sealed partial class MvcSerializerContext : JsonSerializerContext;

Exceptions (if any)

    [2025-03-13 13:15:45Z] fail: Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware[1]
      An unhandled exception has occurred while executing the request.
System.InvalidOperationException: Unsupported HTTP method: 
   at ApiDescriptionExtensions.GetOperationType(ApiDescription apiDescription)
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationsAsync(IGrouping`2 descriptions, HashSet`1 capturedTags, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiPathsAsync(HashSet`1 capturedTags, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiDocumentAsync(IServiceProvider scopedServiceProvider, CancellationToken cancellationToken)
   at Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions.<>c__DisplayClass0_0.<<MapOpenApi>b__0>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Http.Generated.<GeneratedRouteBuilderExtensions_g>F56B68D2B55B5B7B373BA2E4796D897848BC0F04A969B1AF6260183E8B9E0BAF2__GeneratedRouteBuilderExtensionsCore.<>c__DisplayClass2_0.<<MapGet0>g__RequestHandler|5>d.MoveNext()
--- End of stack trace from previous location ---
   at Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

.NET Version

9.0.201

Anything else?

No response

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templatesfeature-openapi

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions