From 3b668ad07d03b4e794c94caf68cb5200e2a1ee03 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 Aug 2025 23:05:37 +0000 Subject: [PATCH 1/5] Initial plan From fcd74e834f352661c3088fca844b02c57371f0c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 Aug 2025 23:22:24 +0000 Subject: [PATCH 2/5] Implement HTTP QUERY method support - Add HttpMethods.Query constant and IsQuery method - Add HttpQueryAttribute for MVC controllers - Add MapQuery extension methods for endpoint routing - Update GetCanonicalizedValue to support QUERY method - Add comprehensive tests for all new functionality - Update PublicAPI files for new public APIs Co-authored-by: BrennanConroy <7574801+BrennanConroy@users.noreply.github.com> --- src/Http/Http.Abstractions/src/HttpMethods.cs | 17 ++++++ .../src/PublicAPI.Unshipped.txt | 2 + .../test/HttpMethodslTests.cs | 1 + .../test/MapQueryExtensionTests.cs | 54 +++++++++++++++++++ .../Builder/EndpointRouteBuilderExtensions.cs | 35 ++++++++++++ src/Http/Routing/src/PublicAPI.Unshipped.txt | 2 + src/Mvc/Mvc.Core/src/HttpQueryAttribute.cs | 33 ++++++++++++ src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt | 3 ++ .../HttpMethodProviderAttributesTests.cs | 1 + 9 files changed, 148 insertions(+) create mode 100644 src/Http/Http.Extensions/test/MapQueryExtensionTests.cs create mode 100644 src/Mvc/Mvc.Core/src/HttpQueryAttribute.cs diff --git a/src/Http/Http.Abstractions/src/HttpMethods.cs b/src/Http/Http.Abstractions/src/HttpMethods.cs index 187591b4e3eb..3be3155db0d3 100644 --- a/src/Http/Http.Abstractions/src/HttpMethods.cs +++ b/src/Http/Http.Abstractions/src/HttpMethods.cs @@ -49,6 +49,10 @@ public static class HttpMethods /// public static readonly string Put = "PUT"; /// + /// HTTP "QUERY" method. + /// + public static readonly string Query = "QUERY"; + /// /// HTTP "TRACE" method. /// public static readonly string Trace = "TRACE"; @@ -149,6 +153,18 @@ public static bool IsPut(string method) return Equals(Put, method); } + /// + /// Returns a value that indicates if the HTTP request method is QUERY. + /// + /// The HTTP request method. + /// + /// if the method is QUERY; otherwise, . + /// + public static bool IsQuery(string method) + { + return Equals(Query, method); + } + /// /// Returns a value that indicates if the HTTP request method is TRACE. /// @@ -175,6 +191,7 @@ string _ when IsDelete(method) => Delete, string _ when IsOptions(method) => Options, string _ when IsHead(method) => Head, string _ when IsPatch(method) => Patch, + string _ when IsQuery(method) => Query, string _ when IsTrace(method) => Trace, string _ when IsConnect(method) => Connect, string _ => method diff --git a/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt b/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt index 2944a853cdf2..d0fc0872dd90 100644 --- a/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt +++ b/src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt @@ -4,3 +4,5 @@ Microsoft.AspNetCore.Http.Metadata.IDisableValidationMetadata Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Description.get -> string? Microsoft.AspNetCore.Http.ProducesResponseTypeMetadata.Description.set -> void Microsoft.AspNetCore.Http.Metadata.IProducesResponseTypeMetadata.Description.get -> string? +static Microsoft.AspNetCore.Http.HttpMethods.IsQuery(string! method) -> bool +static readonly Microsoft.AspNetCore.Http.HttpMethods.Query -> string! diff --git a/src/Http/Http.Abstractions/test/HttpMethodslTests.cs b/src/Http/Http.Abstractions/test/HttpMethodslTests.cs index c9d79ecbd6f6..c22cb5a92689 100644 --- a/src/Http/Http.Abstractions/test/HttpMethodslTests.cs +++ b/src/Http/Http.Abstractions/test/HttpMethodslTests.cs @@ -20,6 +20,7 @@ public void CanonicalizedValue_Success() (new string[] { "CONNECT", "Connect", "connect" }, HttpMethods.Connect), (new string[] { "OPTIONS", "Options", "options" }, HttpMethods.Options), (new string[] { "PATCH", "Patch", "patch" }, HttpMethods.Patch), + (new string[] { "QUERY", "Query", "query" }, HttpMethods.Query), (new string[] { "TRACE", "Trace", "trace" }, HttpMethods.Trace) }; diff --git a/src/Http/Http.Extensions/test/MapQueryExtensionTests.cs b/src/Http/Http.Extensions/test/MapQueryExtensionTests.cs new file mode 100644 index 000000000000..aa2b66b8dca2 --- /dev/null +++ b/src/Http/Http.Extensions/test/MapQueryExtensionTests.cs @@ -0,0 +1,54 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing; +using Microsoft.Extensions.DependencyInjection; +using Xunit; + +namespace Microsoft.AspNetCore.Http.Tests.Extensions; + +public class MapQueryExtensionTests +{ + [Fact] + public void MapQuery_WithRequestDelegate_ReturnsCorrectConventionBuilder() + { + // Arrange + var services = new ServiceCollection(); + services.AddRouting(); + var serviceProvider = services.BuildServiceProvider(); + + var app = new ApplicationBuilder(serviceProvider); + var endpoints = app.New().UseRouting().UseEndpoints(builder => { }).ApplicationServices.GetRequiredService(); + + RequestDelegate handler = context => Task.CompletedTask; + + // Act + var result = endpoints.MapQuery("/test", handler); + + // Assert + Assert.NotNull(result); + } + + [Fact] + public void MapQuery_WithDelegate_ReturnsCorrectRouteHandlerBuilder() + { + // Arrange + var services = new ServiceCollection(); + services.AddRouting(); + var serviceProvider = services.BuildServiceProvider(); + + var app = new ApplicationBuilder(serviceProvider); + var endpoints = app.New().UseRouting().UseEndpoints(builder => { }).ApplicationServices.GetRequiredService(); + + Func handler = () => "Hello"; + + // Act + var result = endpoints.MapQuery("/test", handler); + + // Assert + Assert.NotNull(result); + Assert.IsType(result); + } +} \ No newline at end of file diff --git a/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs b/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs index aa745b1b9829..60137474239e 100644 --- a/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs +++ b/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs @@ -24,6 +24,7 @@ public static class EndpointRouteBuilderExtensions private static readonly string[] PutVerb = new[] { HttpMethods.Put }; private static readonly string[] DeleteVerb = new[] { HttpMethods.Delete }; private static readonly string[] PatchVerb = new[] { HttpMethods.Patch }; + private static readonly string[] QueryVerb = new[] { HttpMethods.Query }; /// /// Creates a for defining endpoints all prefixed with the specified . @@ -134,6 +135,22 @@ public static IEndpointConventionBuilder MapPatch( return MapMethods(endpoints, pattern, PatchVerb, requestDelegate); } + /// + /// Adds a to the that matches HTTP QUERY requests + /// for the specified pattern. + /// + /// The to add the route to. + /// The route pattern. + /// The delegate executed when the endpoint is matched. + /// A that can be used to further customize the endpoint. + public static IEndpointConventionBuilder MapQuery( + this IEndpointRouteBuilder endpoints, + [StringSyntax("Route")] string pattern, + RequestDelegate requestDelegate) + { + return MapMethods(endpoints, pattern, QueryVerb, requestDelegate); + } + /// /// Adds a to the that matches HTTP requests /// for the specified HTTP methods and pattern. @@ -307,6 +324,24 @@ public static RouteHandlerBuilder MapPatch( return MapMethods(endpoints, pattern, PatchVerb, handler); } + /// + /// Adds a to the that matches HTTP QUERY requests + /// for the specified pattern. + /// + /// The to add the route to. + /// The route pattern. + /// The executed when the endpoint is matched. + /// A that can be used to further customize the endpoint. + [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] + [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] + public static RouteHandlerBuilder MapQuery( + this IEndpointRouteBuilder endpoints, + [StringSyntax("Route")] string pattern, + Delegate handler) + { + return MapMethods(endpoints, pattern, QueryVerb, handler); + } + /// /// Adds a to the that matches HTTP requests /// for the specified HTTP methods and pattern. diff --git a/src/Http/Routing/src/PublicAPI.Unshipped.txt b/src/Http/Routing/src/PublicAPI.Unshipped.txt index 0612dc9ff2b0..6c5ffaa429cf 100644 --- a/src/Http/Routing/src/PublicAPI.Unshipped.txt +++ b/src/Http/Routing/src/PublicAPI.Unshipped.txt @@ -1,3 +1,5 @@ #nullable enable Microsoft.AspNetCore.Builder.ValidationEndpointConventionBuilderExtensions +static Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions.MapQuery(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, Microsoft.AspNetCore.Http.RequestDelegate! requestDelegate) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder! +static Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions.MapQuery(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! handler) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! static Microsoft.AspNetCore.Builder.ValidationEndpointConventionBuilderExtensions.DisableValidation(this TBuilder builder) -> TBuilder diff --git a/src/Mvc/Mvc.Core/src/HttpQueryAttribute.cs b/src/Mvc/Mvc.Core/src/HttpQueryAttribute.cs new file mode 100644 index 000000000000..78c76efedd8e --- /dev/null +++ b/src/Mvc/Mvc.Core/src/HttpQueryAttribute.cs @@ -0,0 +1,33 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics.CodeAnalysis; +using Microsoft.AspNetCore.Mvc.Routing; + +namespace Microsoft.AspNetCore.Mvc; + +/// +/// Identifies an action that supports the HTTP QUERY method. +/// +public class HttpQueryAttribute : HttpMethodAttribute +{ + private static readonly IEnumerable _supportedMethods = new[] { "QUERY" }; + + /// + /// Creates a new . + /// + public HttpQueryAttribute() + : base(_supportedMethods) + { + } + + /// + /// Creates a new with the given route template. + /// + /// The route template. May not be null. + public HttpQueryAttribute([StringSyntax("Route")] string template) + : base(_supportedMethods, template) + { + ArgumentNullException.ThrowIfNull(template); + } +} \ No newline at end of file diff --git a/src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt index 1cd6a2b5200c..9b28659203b9 100644 --- a/src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt @@ -4,6 +4,9 @@ Microsoft.AspNetCore.Mvc.ApiExplorer.IApiRequestFormatMetadataProvider.GetSuppor Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseMetadataProvider.Description.get -> string? *REMOVED*Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseTypeMetadataProvider.GetSupportedContentTypes(string! contentType, System.Type! objectType) -> System.Collections.Generic.IReadOnlyList? Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseTypeMetadataProvider.GetSupportedContentTypes(string? contentType, System.Type! objectType) -> System.Collections.Generic.IReadOnlyList? +Microsoft.AspNetCore.Mvc.HttpQueryAttribute +Microsoft.AspNetCore.Mvc.HttpQueryAttribute.HttpQueryAttribute() -> void +Microsoft.AspNetCore.Mvc.HttpQueryAttribute.HttpQueryAttribute(string! template) -> void Microsoft.AspNetCore.Mvc.ProducesAttribute.Description.get -> string? Microsoft.AspNetCore.Mvc.ProducesAttribute.Description.set -> void Microsoft.AspNetCore.Mvc.ProducesDefaultResponseTypeAttribute.Description.get -> string? diff --git a/src/Mvc/Mvc.Core/test/Routing/HttpMethodProviderAttributesTests.cs b/src/Mvc/Mvc.Core/test/Routing/HttpMethodProviderAttributesTests.cs index 42412b2fa00f..73617badd6e7 100644 --- a/src/Mvc/Mvc.Core/test/Routing/HttpMethodProviderAttributesTests.cs +++ b/src/Mvc/Mvc.Core/test/Routing/HttpMethodProviderAttributesTests.cs @@ -27,6 +27,7 @@ public static TheoryData> HttpMet data.Add(new HttpDeleteAttribute(), new[] { "DELETE" }); data.Add(new HttpHeadAttribute(), new[] { "HEAD" }); data.Add(new HttpOptionsAttribute(), new[] { "OPTIONS" }); + data.Add(new HttpQueryAttribute(), new[] { "QUERY" }); data.Add(new AcceptVerbsAttribute("MERGE", "OPTIONS"), new[] { "MERGE", "OPTIONS" }); return data; From 5ff683bc74b5e421447f4dce00af6f164f2b9950 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 Aug 2025 23:31:22 +0000 Subject: [PATCH 3/5] Add comprehensive tests for HTTP QUERY method support - Add routing unit tests for MapQuery extension methods - Verify HTTP method metadata is correctly set - Test both RequestDelegate and Delegate overloads - All tests passing successfully Co-authored-by: BrennanConroy <7574801+BrennanConroy@users.noreply.github.com> --- .../test/MapQueryExtensionTests.cs | 54 --------------- .../Builder/MapQueryExtensionTests.cs | 68 +++++++++++++++++++ 2 files changed, 68 insertions(+), 54 deletions(-) delete mode 100644 src/Http/Http.Extensions/test/MapQueryExtensionTests.cs create mode 100644 src/Http/Routing/test/UnitTests/Builder/MapQueryExtensionTests.cs diff --git a/src/Http/Http.Extensions/test/MapQueryExtensionTests.cs b/src/Http/Http.Extensions/test/MapQueryExtensionTests.cs deleted file mode 100644 index aa2b66b8dca2..000000000000 --- a/src/Http/Http.Extensions/test/MapQueryExtensionTests.cs +++ /dev/null @@ -1,54 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing; -using Microsoft.Extensions.DependencyInjection; -using Xunit; - -namespace Microsoft.AspNetCore.Http.Tests.Extensions; - -public class MapQueryExtensionTests -{ - [Fact] - public void MapQuery_WithRequestDelegate_ReturnsCorrectConventionBuilder() - { - // Arrange - var services = new ServiceCollection(); - services.AddRouting(); - var serviceProvider = services.BuildServiceProvider(); - - var app = new ApplicationBuilder(serviceProvider); - var endpoints = app.New().UseRouting().UseEndpoints(builder => { }).ApplicationServices.GetRequiredService(); - - RequestDelegate handler = context => Task.CompletedTask; - - // Act - var result = endpoints.MapQuery("/test", handler); - - // Assert - Assert.NotNull(result); - } - - [Fact] - public void MapQuery_WithDelegate_ReturnsCorrectRouteHandlerBuilder() - { - // Arrange - var services = new ServiceCollection(); - services.AddRouting(); - var serviceProvider = services.BuildServiceProvider(); - - var app = new ApplicationBuilder(serviceProvider); - var endpoints = app.New().UseRouting().UseEndpoints(builder => { }).ApplicationServices.GetRequiredService(); - - Func handler = () => "Hello"; - - // Act - var result = endpoints.MapQuery("/test", handler); - - // Assert - Assert.NotNull(result); - Assert.IsType(result); - } -} \ No newline at end of file diff --git a/src/Http/Routing/test/UnitTests/Builder/MapQueryExtensionTests.cs b/src/Http/Routing/test/UnitTests/Builder/MapQueryExtensionTests.cs new file mode 100644 index 000000000000..a3cf0c4cd47d --- /dev/null +++ b/src/Http/Routing/test/UnitTests/Builder/MapQueryExtensionTests.cs @@ -0,0 +1,68 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Http.Metadata; +using Microsoft.AspNetCore.Routing; +using Xunit; + +namespace Microsoft.AspNetCore.Builder; + +public class MapQueryExtensionTests +{ + private EndpointDataSource GetBuilderEndpointDataSource(IEndpointRouteBuilder endpointRouteBuilder) => + Assert.Single(endpointRouteBuilder.DataSources); + + [Fact] + public void MapQuery_Delegate_CreatesEndpointWithCorrectMethod() + { + // Arrange + var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(EmptyServiceProvider.Instance)); + + Func handler = () => "Hello World"; + + // Act + var app = builder.MapQuery("/", handler); + + // Assert + var dataSource = GetBuilderEndpointDataSource(builder); + var endpoint = Assert.Single(dataSource.Endpoints); + + var routeEndpoint = Assert.IsType(endpoint); + Assert.Equal("/", routeEndpoint.RoutePattern.RawText); + + var httpMethodMetadata = endpoint.Metadata.GetMetadata(); + Assert.NotNull(httpMethodMetadata); + Assert.Contains("QUERY", httpMethodMetadata.HttpMethods); + } + + [Fact] + public void MapQuery_RequestDelegate_CreatesEndpointWithCorrectMethod() + { + // Arrange + var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(EmptyServiceProvider.Instance)); + + RequestDelegate handler = context => Task.CompletedTask; + + // Act + var app = builder.MapQuery("/", handler); + + // Assert + var dataSource = GetBuilderEndpointDataSource(builder); + var endpoint = Assert.Single(dataSource.Endpoints); + + var routeEndpoint = Assert.IsType(endpoint); + Assert.Equal("/", routeEndpoint.RoutePattern.RawText); + + var httpMethodMetadata = endpoint.Metadata.GetMetadata(); + Assert.NotNull(httpMethodMetadata); + Assert.Contains("QUERY", httpMethodMetadata.HttpMethods); + } + + // Helper class + private class EmptyServiceProvider : IServiceProvider + { + public static EmptyServiceProvider Instance { get; } = new EmptyServiceProvider(); + public object GetService(Type serviceType) => null; + } +} \ No newline at end of file From af2e23ceabef66af0fdbbce2c69e243e2d8acd0b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 Aug 2025 23:33:54 +0000 Subject: [PATCH 4/5] Add comprehensive documentation and examples for HTTP QUERY method - Create detailed usage examples for HttpMethods.Query - Show MVC controller examples with HttpQueryAttribute - Demonstrate minimal API usage with MapQuery - Include HTTP client examples - Document benefits and use cases of QUERY method - All functionality fully implemented and tested Co-authored-by: BrennanConroy <7574801+BrennanConroy@users.noreply.github.com> --- HTTP_QUERY_USAGE_EXAMPLES.md | 117 +++++++++++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 HTTP_QUERY_USAGE_EXAMPLES.md diff --git a/HTTP_QUERY_USAGE_EXAMPLES.md b/HTTP_QUERY_USAGE_EXAMPLES.md new file mode 100644 index 000000000000..832ac4ea8359 --- /dev/null +++ b/HTTP_QUERY_USAGE_EXAMPLES.md @@ -0,0 +1,117 @@ +# HTTP QUERY Method Support in ASP.NET Core + +This document provides examples of how to use the new HTTP QUERY method support added to ASP.NET Core. + +## HttpMethods Class + +The `HttpMethods` class now includes support for the QUERY method: + +```csharp +using Microsoft.AspNetCore.Http; + +// New QUERY method constant +string queryMethod = HttpMethods.Query; // "QUERY" + +// New IsQuery method +bool isQuery = HttpMethods.IsQuery("QUERY"); // true +bool isQueryLowercase = HttpMethods.IsQuery("query"); // true + +// GetCanonicalizedValue now supports QUERY +string canonicalized = HttpMethods.GetCanonicalizedValue("query"); // Returns HttpMethods.Query +``` + +## MVC Controller Support + +Use the new `HttpQueryAttribute` in MVC controllers: + +```csharp +using Microsoft.AspNetCore.Mvc; + +[ApiController] +[Route("api/[controller]")] +public class DataController : ControllerBase +{ + [HttpQuery] + public IActionResult Search([FromQuery] string q, [FromQuery] int limit = 10) + { + // Handle QUERY request with query parameters in the body + return Ok(new { query = q, limit = limit }); + } + + [HttpQuery("search/{category}")] + public IActionResult SearchByCategory(string category, [FromQuery] string q) + { + // Handle QUERY request with both route and query parameters + return Ok(new { category = category, query = q }); + } +} +``` + +## Minimal API Support + +Use the new `MapQuery` extension methods with minimal APIs: + +```csharp +using Microsoft.AspNetCore.Http; + +var builder = WebApplication.CreateBuilder(args); +var app = builder.Build(); + +// MapQuery with RequestDelegate +app.MapQuery("/search", async (HttpContext context) => +{ + var query = context.Request.Query["q"].ToString(); + return Results.Ok(new { query = query }); +}); + +// MapQuery with typed delegate +app.MapQuery("/search/{category}", (string category, string q) => +{ + return Results.Ok(new { category = category, query = q }); +}); + +// MapQuery with async delegate +app.MapQuery("/async-search", async (string q, ILogger logger) => +{ + logger.LogInformation("Processing query: {Query}", q); + await Task.Delay(100); // Simulate async work + return Results.Ok(new { query = q, timestamp = DateTime.UtcNow }); +}); + +app.Run(); +``` + +## Usage with HTTP Clients + +Example of making QUERY requests: + +```csharp +using System.Net.Http; + +var client = new HttpClient(); + +// Simple QUERY request +var request = new HttpRequestMessage(new HttpMethod("QUERY"), "https://api.example.com/search?q=test"); +var response = await client.SendAsync(request); + +// QUERY request with content body (if supported by the API) +var queryRequest = new HttpRequestMessage(new HttpMethod("QUERY"), "https://api.example.com/advanced-search") +{ + Content = JsonContent.Create(new { + query = "search terms", + filters = new { category = "books", minPrice = 10 } + }) +}; +var queryResponse = await client.SendAsync(queryRequest); +``` + +## Benefits of HTTP QUERY Method + +The HTTP QUERY method is designed for safe queries that may include request bodies: + +- **Safe**: Like GET, QUERY requests should not modify server state +- **Cacheable**: QUERY responses can be cached like GET responses +- **Request Body**: Unlike GET, QUERY allows request bodies for complex query parameters +- **Semantic Clarity**: Clearly indicates query operations vs. data retrieval (GET) + +For more information, see the HTTP QUERY method specification: https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/ \ No newline at end of file From 51da70dae9a8e70900e227684f03f37517329d6e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 14 Aug 2025 23:44:58 +0000 Subject: [PATCH 5/5] Remove unapproved MVC and routing functionality, keep only HttpMethods.Query support Co-authored-by: BrennanConroy <7574801+BrennanConroy@users.noreply.github.com> --- HTTP_QUERY_USAGE_EXAMPLES.md | 117 ------------------ .../Builder/EndpointRouteBuilderExtensions.cs | 35 ------ src/Http/Routing/src/PublicAPI.Unshipped.txt | 2 - .../Builder/MapQueryExtensionTests.cs | 68 ---------- src/Mvc/Mvc.Core/src/HttpQueryAttribute.cs | 33 ----- src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt | 3 - .../HttpMethodProviderAttributesTests.cs | 1 - 7 files changed, 259 deletions(-) delete mode 100644 HTTP_QUERY_USAGE_EXAMPLES.md delete mode 100644 src/Http/Routing/test/UnitTests/Builder/MapQueryExtensionTests.cs delete mode 100644 src/Mvc/Mvc.Core/src/HttpQueryAttribute.cs diff --git a/HTTP_QUERY_USAGE_EXAMPLES.md b/HTTP_QUERY_USAGE_EXAMPLES.md deleted file mode 100644 index 832ac4ea8359..000000000000 --- a/HTTP_QUERY_USAGE_EXAMPLES.md +++ /dev/null @@ -1,117 +0,0 @@ -# HTTP QUERY Method Support in ASP.NET Core - -This document provides examples of how to use the new HTTP QUERY method support added to ASP.NET Core. - -## HttpMethods Class - -The `HttpMethods` class now includes support for the QUERY method: - -```csharp -using Microsoft.AspNetCore.Http; - -// New QUERY method constant -string queryMethod = HttpMethods.Query; // "QUERY" - -// New IsQuery method -bool isQuery = HttpMethods.IsQuery("QUERY"); // true -bool isQueryLowercase = HttpMethods.IsQuery("query"); // true - -// GetCanonicalizedValue now supports QUERY -string canonicalized = HttpMethods.GetCanonicalizedValue("query"); // Returns HttpMethods.Query -``` - -## MVC Controller Support - -Use the new `HttpQueryAttribute` in MVC controllers: - -```csharp -using Microsoft.AspNetCore.Mvc; - -[ApiController] -[Route("api/[controller]")] -public class DataController : ControllerBase -{ - [HttpQuery] - public IActionResult Search([FromQuery] string q, [FromQuery] int limit = 10) - { - // Handle QUERY request with query parameters in the body - return Ok(new { query = q, limit = limit }); - } - - [HttpQuery("search/{category}")] - public IActionResult SearchByCategory(string category, [FromQuery] string q) - { - // Handle QUERY request with both route and query parameters - return Ok(new { category = category, query = q }); - } -} -``` - -## Minimal API Support - -Use the new `MapQuery` extension methods with minimal APIs: - -```csharp -using Microsoft.AspNetCore.Http; - -var builder = WebApplication.CreateBuilder(args); -var app = builder.Build(); - -// MapQuery with RequestDelegate -app.MapQuery("/search", async (HttpContext context) => -{ - var query = context.Request.Query["q"].ToString(); - return Results.Ok(new { query = query }); -}); - -// MapQuery with typed delegate -app.MapQuery("/search/{category}", (string category, string q) => -{ - return Results.Ok(new { category = category, query = q }); -}); - -// MapQuery with async delegate -app.MapQuery("/async-search", async (string q, ILogger logger) => -{ - logger.LogInformation("Processing query: {Query}", q); - await Task.Delay(100); // Simulate async work - return Results.Ok(new { query = q, timestamp = DateTime.UtcNow }); -}); - -app.Run(); -``` - -## Usage with HTTP Clients - -Example of making QUERY requests: - -```csharp -using System.Net.Http; - -var client = new HttpClient(); - -// Simple QUERY request -var request = new HttpRequestMessage(new HttpMethod("QUERY"), "https://api.example.com/search?q=test"); -var response = await client.SendAsync(request); - -// QUERY request with content body (if supported by the API) -var queryRequest = new HttpRequestMessage(new HttpMethod("QUERY"), "https://api.example.com/advanced-search") -{ - Content = JsonContent.Create(new { - query = "search terms", - filters = new { category = "books", minPrice = 10 } - }) -}; -var queryResponse = await client.SendAsync(queryRequest); -``` - -## Benefits of HTTP QUERY Method - -The HTTP QUERY method is designed for safe queries that may include request bodies: - -- **Safe**: Like GET, QUERY requests should not modify server state -- **Cacheable**: QUERY responses can be cached like GET responses -- **Request Body**: Unlike GET, QUERY allows request bodies for complex query parameters -- **Semantic Clarity**: Clearly indicates query operations vs. data retrieval (GET) - -For more information, see the HTTP QUERY method specification: https://datatracker.ietf.org/doc/draft-ietf-httpbis-safe-method-w-body/ \ No newline at end of file diff --git a/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs b/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs index 60137474239e..aa745b1b9829 100644 --- a/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs +++ b/src/Http/Routing/src/Builder/EndpointRouteBuilderExtensions.cs @@ -24,7 +24,6 @@ public static class EndpointRouteBuilderExtensions private static readonly string[] PutVerb = new[] { HttpMethods.Put }; private static readonly string[] DeleteVerb = new[] { HttpMethods.Delete }; private static readonly string[] PatchVerb = new[] { HttpMethods.Patch }; - private static readonly string[] QueryVerb = new[] { HttpMethods.Query }; /// /// Creates a for defining endpoints all prefixed with the specified . @@ -135,22 +134,6 @@ public static IEndpointConventionBuilder MapPatch( return MapMethods(endpoints, pattern, PatchVerb, requestDelegate); } - /// - /// Adds a to the that matches HTTP QUERY requests - /// for the specified pattern. - /// - /// The to add the route to. - /// The route pattern. - /// The delegate executed when the endpoint is matched. - /// A that can be used to further customize the endpoint. - public static IEndpointConventionBuilder MapQuery( - this IEndpointRouteBuilder endpoints, - [StringSyntax("Route")] string pattern, - RequestDelegate requestDelegate) - { - return MapMethods(endpoints, pattern, QueryVerb, requestDelegate); - } - /// /// Adds a to the that matches HTTP requests /// for the specified HTTP methods and pattern. @@ -324,24 +307,6 @@ public static RouteHandlerBuilder MapPatch( return MapMethods(endpoints, pattern, PatchVerb, handler); } - /// - /// Adds a to the that matches HTTP QUERY requests - /// for the specified pattern. - /// - /// The to add the route to. - /// The route pattern. - /// The executed when the endpoint is matched. - /// A that can be used to further customize the endpoint. - [RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)] - [RequiresDynamicCode(MapEndpointDynamicCodeWarning)] - public static RouteHandlerBuilder MapQuery( - this IEndpointRouteBuilder endpoints, - [StringSyntax("Route")] string pattern, - Delegate handler) - { - return MapMethods(endpoints, pattern, QueryVerb, handler); - } - /// /// Adds a to the that matches HTTP requests /// for the specified HTTP methods and pattern. diff --git a/src/Http/Routing/src/PublicAPI.Unshipped.txt b/src/Http/Routing/src/PublicAPI.Unshipped.txt index 6c5ffaa429cf..0612dc9ff2b0 100644 --- a/src/Http/Routing/src/PublicAPI.Unshipped.txt +++ b/src/Http/Routing/src/PublicAPI.Unshipped.txt @@ -1,5 +1,3 @@ #nullable enable Microsoft.AspNetCore.Builder.ValidationEndpointConventionBuilderExtensions -static Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions.MapQuery(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, Microsoft.AspNetCore.Http.RequestDelegate! requestDelegate) -> Microsoft.AspNetCore.Builder.IEndpointConventionBuilder! -static Microsoft.AspNetCore.Builder.EndpointRouteBuilderExtensions.MapQuery(this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder! endpoints, string! pattern, System.Delegate! handler) -> Microsoft.AspNetCore.Builder.RouteHandlerBuilder! static Microsoft.AspNetCore.Builder.ValidationEndpointConventionBuilderExtensions.DisableValidation(this TBuilder builder) -> TBuilder diff --git a/src/Http/Routing/test/UnitTests/Builder/MapQueryExtensionTests.cs b/src/Http/Routing/test/UnitTests/Builder/MapQueryExtensionTests.cs deleted file mode 100644 index a3cf0c4cd47d..000000000000 --- a/src/Http/Routing/test/UnitTests/Builder/MapQueryExtensionTests.cs +++ /dev/null @@ -1,68 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Metadata; -using Microsoft.AspNetCore.Routing; -using Xunit; - -namespace Microsoft.AspNetCore.Builder; - -public class MapQueryExtensionTests -{ - private EndpointDataSource GetBuilderEndpointDataSource(IEndpointRouteBuilder endpointRouteBuilder) => - Assert.Single(endpointRouteBuilder.DataSources); - - [Fact] - public void MapQuery_Delegate_CreatesEndpointWithCorrectMethod() - { - // Arrange - var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(EmptyServiceProvider.Instance)); - - Func handler = () => "Hello World"; - - // Act - var app = builder.MapQuery("/", handler); - - // Assert - var dataSource = GetBuilderEndpointDataSource(builder); - var endpoint = Assert.Single(dataSource.Endpoints); - - var routeEndpoint = Assert.IsType(endpoint); - Assert.Equal("/", routeEndpoint.RoutePattern.RawText); - - var httpMethodMetadata = endpoint.Metadata.GetMetadata(); - Assert.NotNull(httpMethodMetadata); - Assert.Contains("QUERY", httpMethodMetadata.HttpMethods); - } - - [Fact] - public void MapQuery_RequestDelegate_CreatesEndpointWithCorrectMethod() - { - // Arrange - var builder = new DefaultEndpointRouteBuilder(new ApplicationBuilder(EmptyServiceProvider.Instance)); - - RequestDelegate handler = context => Task.CompletedTask; - - // Act - var app = builder.MapQuery("/", handler); - - // Assert - var dataSource = GetBuilderEndpointDataSource(builder); - var endpoint = Assert.Single(dataSource.Endpoints); - - var routeEndpoint = Assert.IsType(endpoint); - Assert.Equal("/", routeEndpoint.RoutePattern.RawText); - - var httpMethodMetadata = endpoint.Metadata.GetMetadata(); - Assert.NotNull(httpMethodMetadata); - Assert.Contains("QUERY", httpMethodMetadata.HttpMethods); - } - - // Helper class - private class EmptyServiceProvider : IServiceProvider - { - public static EmptyServiceProvider Instance { get; } = new EmptyServiceProvider(); - public object GetService(Type serviceType) => null; - } -} \ No newline at end of file diff --git a/src/Mvc/Mvc.Core/src/HttpQueryAttribute.cs b/src/Mvc/Mvc.Core/src/HttpQueryAttribute.cs deleted file mode 100644 index 78c76efedd8e..000000000000 --- a/src/Mvc/Mvc.Core/src/HttpQueryAttribute.cs +++ /dev/null @@ -1,33 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.Mvc.Routing; - -namespace Microsoft.AspNetCore.Mvc; - -/// -/// Identifies an action that supports the HTTP QUERY method. -/// -public class HttpQueryAttribute : HttpMethodAttribute -{ - private static readonly IEnumerable _supportedMethods = new[] { "QUERY" }; - - /// - /// Creates a new . - /// - public HttpQueryAttribute() - : base(_supportedMethods) - { - } - - /// - /// Creates a new with the given route template. - /// - /// The route template. May not be null. - public HttpQueryAttribute([StringSyntax("Route")] string template) - : base(_supportedMethods, template) - { - ArgumentNullException.ThrowIfNull(template); - } -} \ No newline at end of file diff --git a/src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt b/src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt index 9b28659203b9..1cd6a2b5200c 100644 --- a/src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt +++ b/src/Mvc/Mvc.Core/src/PublicAPI.Unshipped.txt @@ -4,9 +4,6 @@ Microsoft.AspNetCore.Mvc.ApiExplorer.IApiRequestFormatMetadataProvider.GetSuppor Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseMetadataProvider.Description.get -> string? *REMOVED*Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseTypeMetadataProvider.GetSupportedContentTypes(string! contentType, System.Type! objectType) -> System.Collections.Generic.IReadOnlyList? Microsoft.AspNetCore.Mvc.ApiExplorer.IApiResponseTypeMetadataProvider.GetSupportedContentTypes(string? contentType, System.Type! objectType) -> System.Collections.Generic.IReadOnlyList? -Microsoft.AspNetCore.Mvc.HttpQueryAttribute -Microsoft.AspNetCore.Mvc.HttpQueryAttribute.HttpQueryAttribute() -> void -Microsoft.AspNetCore.Mvc.HttpQueryAttribute.HttpQueryAttribute(string! template) -> void Microsoft.AspNetCore.Mvc.ProducesAttribute.Description.get -> string? Microsoft.AspNetCore.Mvc.ProducesAttribute.Description.set -> void Microsoft.AspNetCore.Mvc.ProducesDefaultResponseTypeAttribute.Description.get -> string? diff --git a/src/Mvc/Mvc.Core/test/Routing/HttpMethodProviderAttributesTests.cs b/src/Mvc/Mvc.Core/test/Routing/HttpMethodProviderAttributesTests.cs index 73617badd6e7..42412b2fa00f 100644 --- a/src/Mvc/Mvc.Core/test/Routing/HttpMethodProviderAttributesTests.cs +++ b/src/Mvc/Mvc.Core/test/Routing/HttpMethodProviderAttributesTests.cs @@ -27,7 +27,6 @@ public static TheoryData> HttpMet data.Add(new HttpDeleteAttribute(), new[] { "DELETE" }); data.Add(new HttpHeadAttribute(), new[] { "HEAD" }); data.Add(new HttpOptionsAttribute(), new[] { "OPTIONS" }); - data.Add(new HttpQueryAttribute(), new[] { "QUERY" }); data.Add(new AcceptVerbsAttribute("MERGE", "OPTIONS"), new[] { "MERGE", "OPTIONS" }); return data;