diff --git a/src/OpenApi/src/Extensions/ApiDescriptionExtensions.cs b/src/OpenApi/src/Extensions/ApiDescriptionExtensions.cs
index 55e8d3ed765b..6758d349fbaf 100644
--- a/src/OpenApi/src/Extensions/ApiDescriptionExtensions.cs
+++ b/src/OpenApi/src/Extensions/ApiDescriptionExtensions.cs
@@ -16,11 +16,22 @@ internal static class ApiDescriptionExtensions
/// Maps the HTTP method of the ApiDescription to the HttpMethod.
///
/// The ApiDescription to resolve an HttpMethod from.
- /// The associated with the given , if known.
- public static HttpMethod? GetHttpMethod(this ApiDescription apiDescription) =>
- apiDescription.HttpMethod?.ToUpperInvariant() switch
+ ///
+ /// The associated with the given , including custom methods
+ /// when the provided HTTP method token is valid. Returns when the HTTP method is missing or invalid.
+ ///
+ public static HttpMethod? GetHttpMethod(this ApiDescription apiDescription)
+ {
+ var httpMethod = apiDescription.HttpMethod;
+ if (string.IsNullOrWhiteSpace(httpMethod))
+ {
+ return null;
+ }
+
+ var normalizedHttpMethod = httpMethod.Trim();
+
+ return normalizedHttpMethod.ToUpperInvariant() switch
{
- // Only add methods documented in the OpenAPI spec: https://spec.openapis.org/oas/v3.2.0.html#path-item-object
"GET" => HttpMethod.Get,
"POST" => HttpMethod.Post,
"PUT" => HttpMethod.Put,
@@ -30,9 +41,22 @@ internal static class ApiDescriptionExtensions
"OPTIONS" => HttpMethod.Options,
"TRACE" => HttpMethod.Trace,
"QUERY" => HttpMethod.Query,
- _ => null,
+ _ => TryCreateHttpMethod(normalizedHttpMethod),
};
+ static HttpMethod? TryCreateHttpMethod(string httpMethod)
+ {
+ try
+ {
+ return new HttpMethod(httpMethod);
+ }
+ catch (FormatException)
+ {
+ return null;
+ }
+ }
+ }
+
///
/// Maps the relative path included in the ApiDescription to the path
/// that should be included in the OpenApiDocument. This typically
diff --git a/src/OpenApi/src/Services/OpenApiConstants.cs b/src/OpenApi/src/Services/OpenApiConstants.cs
index 47e1d67e53bc..6756b0ec5335 100644
--- a/src/OpenApi/src/Services/OpenApiConstants.cs
+++ b/src/OpenApi/src/Services/OpenApiConstants.cs
@@ -1,8 +1,6 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
-using System.Net.Http;
-
namespace Microsoft.AspNetCore.OpenApi;
internal static class OpenApiConstants
@@ -19,21 +17,6 @@ internal static class OpenApiConstants
internal const string RefPrefix = "#";
internal const string NullableProperty = "x-is-nullable-property";
internal const string DefaultOpenApiResponseKey = "default";
- // Since there's a finite set of HTTP methods that can be included in a given
- // OpenApiPaths, we can pre-allocate an array of these methods and use a direct
- // lookup on the OpenApiPaths dictionary to avoid allocating an enumerator
- // over the KeyValuePairs in OpenApiPaths.
- internal static readonly HttpMethod[] HttpMethods = [
- HttpMethod.Get,
- HttpMethod.Post,
- HttpMethod.Put,
- HttpMethod.Delete,
- HttpMethod.Options,
- HttpMethod.Head,
- HttpMethod.Patch,
- HttpMethod.Trace,
- HttpMethod.Query
- ];
// Represents primitive types that should never be represented as
// a schema reference and always inlined.
internal static readonly List PrimitiveTypes =
diff --git a/src/OpenApi/src/Services/OpenApiDocumentService.cs b/src/OpenApi/src/Services/OpenApiDocumentService.cs
index c45b76e82b67..2ac9af88a658 100644
--- a/src/OpenApi/src/Services/OpenApiDocumentService.cs
+++ b/src/OpenApi/src/Services/OpenApiDocumentService.cs
@@ -163,14 +163,13 @@ internal async Task ForEachOperationAsync(
{
foreach (var pathItem in document.Paths.Values)
{
- for (var i = 0; i < OpenApiConstants.HttpMethods.Length; i++)
+ if (pathItem.Operations is null)
{
- var httpMethod = OpenApiConstants.HttpMethods[i];
- if (pathItem.Operations is null || !pathItem.Operations.TryGetValue(httpMethod, out var operation))
- {
- continue;
- }
+ continue;
+ }
+ foreach (var operation in pathItem.Operations.Values)
+ {
if (operation.Metadata is { } annotations &&
annotations.TryGetValue(OpenApiConstants.DescriptionId, out var descriptionId) &&
descriptionId is string descriptionIdString &&
@@ -294,7 +293,7 @@ private async Task> GetOperationsAsync(
if (description.GetHttpMethod() is not { } method)
{
- // Skip unsupported HTTP methods
+ // Skip descriptions with a missing or invalid HTTP method.
continue;
}
diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/ApiDescriptionExtensionsTests.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/ApiDescriptionExtensionsTests.cs
index 91952fa9c278..93a16e579da6 100644
--- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/ApiDescriptionExtensionsTests.cs
+++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Extensions/ApiDescriptionExtensionsTests.cs
@@ -58,7 +58,7 @@ public void MapRelativePathToItemPath_WithRoutePattern_HandlesRoutesThatStartWit
public static class HttpMethodTestData
{
- public static IEnumerable