diff --git a/src/Features/JsonPatch.SystemTextJson/test/JsonPatchContentTypeEndToEndTest.cs b/src/Features/JsonPatch.SystemTextJson/test/JsonPatchContentTypeEndToEndTest.cs new file mode 100644 index 000000000000..0fe46eb05d36 --- /dev/null +++ b/src/Features/JsonPatch.SystemTextJson/test/JsonPatchContentTypeEndToEndTest.cs @@ -0,0 +1,59 @@ +// 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; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.InternalTesting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace Microsoft.AspNetCore.JsonPatch.SystemTextJson; + +public class JsonPatchContentTypeEndToEndTest : LoggedTest +{ + [Theory] + [InlineData("/untyped", "application/json-patch+json", true)] + [InlineData("/typed", "application/json-patch+json", true)] + [InlineData("/untyped", "application/json", false)] + [InlineData("/typed", "application/json", false)] + [InlineData("/untyped", "text/plain", false)] + [InlineData("/typed", "text/plain", false)] + public async Task PatchContentTypes_AreHandledAsExpected(string route, string contentType, bool shouldBeAccepted) + { + using var host = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .UseTestServer() + .ConfigureServices(services => services.AddRouting().AddSingleton(LoggerFactory)) + .Configure(app => + { + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapPatch("/untyped", (JsonPatchDocument patch) => { }); + endpoints.MapPatch("/typed", (JsonPatchDocument patch) => { }); + }); + }); + }) + .Build(); + + await host.StartAsync(); + + using var client = host.GetTestClient(); + using var content = new StringContent("[]", Encoding.UTF8, contentType); + + using var response = await client.PatchAsync(route, content); + + var expectedStatusCode = shouldBeAccepted ? HttpStatusCode.OK : HttpStatusCode.UnsupportedMediaType; + Assert.Equal(expectedStatusCode, response.StatusCode); + + await host.StopAsync(); + } +} diff --git a/src/Features/JsonPatch.SystemTextJson/test/Microsoft.AspNetCore.JsonPatch.SystemTextJson.Tests.csproj b/src/Features/JsonPatch.SystemTextJson/test/Microsoft.AspNetCore.JsonPatch.SystemTextJson.Tests.csproj index dea24171d66e..7cfdd2c41586 100644 --- a/src/Features/JsonPatch.SystemTextJson/test/Microsoft.AspNetCore.JsonPatch.SystemTextJson.Tests.csproj +++ b/src/Features/JsonPatch.SystemTextJson/test/Microsoft.AspNetCore.JsonPatch.SystemTextJson.Tests.csproj @@ -10,9 +10,15 @@ + + + + + + diff --git a/src/Features/JsonPatch/src/JsonPatchDocument.cs b/src/Features/JsonPatch/src/JsonPatchDocument.cs index e3f79c455806..e9db23acc7f9 100644 --- a/src/Features/JsonPatch/src/JsonPatchDocument.cs +++ b/src/Features/JsonPatch/src/JsonPatchDocument.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Reflection; using Microsoft.AspNetCore.JsonPatch.Adapters; using Microsoft.AspNetCore.JsonPatch.Converters; using Microsoft.AspNetCore.JsonPatch.Exceptions; @@ -13,22 +12,13 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -#if NET -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http.Metadata; -#endif - namespace Microsoft.AspNetCore.JsonPatch; // Implementation details: the purpose of this type of patch document is to allow creation of such // documents for cases where there's no class/DTO to work on. Typical use case: backend not built in // .NET or architecture doesn't contain a shared DTO layer. [JsonConverter(typeof(JsonPatchDocumentConverter))] -#if NET -public class JsonPatchDocument : IJsonPatchDocument, IEndpointParameterMetadataProvider -#else public class JsonPatchDocument : IJsonPatchDocument -#endif { public List Operations { get; private set; } @@ -230,14 +220,4 @@ IList IJsonPatchDocument.GetOperations() return allOps; } -#if NET - /// - static void IEndpointParameterMetadataProvider.PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder) - { - ArgumentNullException.ThrowIfNull(parameter); - ArgumentNullException.ThrowIfNull(builder); - - builder.Metadata.Add(new AcceptsMetadata(["application/json-patch+json"], parameter.ParameterType)); - } -#endif } diff --git a/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs b/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs index afefe6b4a9d1..88099e346a39 100644 --- a/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs +++ b/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs @@ -6,7 +6,6 @@ using System.Globalization; using System.Linq; using System.Linq.Expressions; -using System.Reflection; using Microsoft.AspNetCore.JsonPatch.Adapters; using Microsoft.AspNetCore.JsonPatch.Converters; using Microsoft.AspNetCore.JsonPatch.Exceptions; @@ -16,11 +15,6 @@ using Newtonsoft.Json; using Newtonsoft.Json.Serialization; -#if NET -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http.Metadata; -#endif - namespace Microsoft.AspNetCore.JsonPatch; // Implementation details: the purpose of this type of patch document is to ensure we can do type-checking @@ -28,11 +22,7 @@ namespace Microsoft.AspNetCore.JsonPatch; // including type data in the JsonPatchDocument serialized as JSON (to allow for correct deserialization) - that's // not according to RFC 6902, and would thus break cross-platform compatibility. [JsonConverter(typeof(TypedJsonPatchDocumentConverter))] -#if NET -public class JsonPatchDocument : IJsonPatchDocument, IEndpointParameterMetadataProvider where TModel : class -#else public class JsonPatchDocument : IJsonPatchDocument where TModel : class -#endif { public List> Operations { get; private set; } @@ -666,17 +656,6 @@ IList IJsonPatchDocument.GetOperations() return allOps; } -#if NET - /// - static void IEndpointParameterMetadataProvider.PopulateMetadata(ParameterInfo parameter, EndpointBuilder builder) - { - ArgumentNullException.ThrowIfNull(parameter); - ArgumentNullException.ThrowIfNull(builder); - - builder.Metadata.Add(new AcceptsMetadata(["application/json-patch+json"], typeof(TModel))); - } -#endif - // Internal for testing internal string GetPath(Expression> expr, string position) { diff --git a/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj index 65f81617bc6e..f4027ea5fd71 100644 --- a/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj +++ b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj @@ -24,7 +24,6 @@ - diff --git a/src/Features/JsonPatch/test/JsonPatchContentTypeEndToEndTest.cs b/src/Features/JsonPatch/test/JsonPatchContentTypeEndToEndTest.cs new file mode 100644 index 000000000000..2c095fbca752 --- /dev/null +++ b/src/Features/JsonPatch/test/JsonPatchContentTypeEndToEndTest.cs @@ -0,0 +1,61 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +#if NET +using System.Net; +using System.Net.Http; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.InternalTesting; +using Microsoft.AspNetCore.TestHost; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; +using Xunit; + +namespace Microsoft.AspNetCore.JsonPatch; + +public class JsonPatchContentTypeEndToEndTest : LoggedTest +{ + [Theory] + [InlineData("/untyped", "application/json-patch+json", true)] + [InlineData("/typed", "application/json-patch+json", true)] + [InlineData("/untyped", "application/json", true)] + [InlineData("/typed", "application/json", true)] + [InlineData("/untyped", "text/plain", false)] + [InlineData("/typed", "text/plain", false)] + public async Task PatchContentTypes_AreHandledAsExpected(string route, string contentType, bool shouldBeAccepted) + { + using var host = new HostBuilder() + .ConfigureWebHost(webHostBuilder => + { + webHostBuilder + .UseTestServer() + .ConfigureServices(services => services.AddRouting().AddSingleton(LoggerFactory)) + .Configure(app => + { + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapPatch("/untyped", (JsonPatchDocument patch) => { }); + endpoints.MapPatch("/typed", (JsonPatchDocument patch) => { }); + }); + }); + }) + .Build(); + + await host.StartAsync(); + + using var client = host.GetTestClient(); + using var content = new StringContent("""{ "operations": [] }""", Encoding.UTF8, contentType); + + using var response = await client.PatchAsync(route, content); + + var expectedStatusCode = shouldBeAccepted ? HttpStatusCode.OK : HttpStatusCode.UnsupportedMediaType; + Assert.Equal(expectedStatusCode, response.StatusCode); + + await host.StopAsync(); + } +} +#endif diff --git a/src/Features/JsonPatch/test/Microsoft.AspNetCore.JsonPatch.Tests.csproj b/src/Features/JsonPatch/test/Microsoft.AspNetCore.JsonPatch.Tests.csproj index 0654f43a711b..4387a430e4d8 100644 --- a/src/Features/JsonPatch/test/Microsoft.AspNetCore.JsonPatch.Tests.csproj +++ b/src/Features/JsonPatch/test/Microsoft.AspNetCore.JsonPatch.Tests.csproj @@ -8,7 +8,21 @@ - + + + + + + + + + + + + + + + diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.RequestBody.cs b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.RequestBody.cs index 8a38a89952f4..b642b9f49638 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.RequestBody.cs +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Services/OpenApiDocumentService/OpenApiDocumentServiceTests.RequestBody.cs @@ -1230,6 +1230,8 @@ await VerifyOpenApiDocument(builder, document => Assert.NotNull(operation.RequestBody); Assert.NotNull(operation.RequestBody.Content); var content = Assert.Single(operation.RequestBody.Content); + Assert.DoesNotContain("application/json", operation.RequestBody.Content.Keys); + Assert.DoesNotContain("application/json-patch+json", operation.RequestBody.Content.Keys); Assert.Equal("application/vnd.github.patch+json", content.Key); var schema = Assert.IsType(content.Value.Schema); Assert.Equal("JsonPatchDocument", schema.Reference.Id); @@ -1253,6 +1255,8 @@ await VerifyOpenApiDocument(builder, document => Assert.NotNull(operation.RequestBody); Assert.NotNull(operation.RequestBody.Content); var content = Assert.Single(operation.RequestBody.Content); + Assert.DoesNotContain("application/json", operation.RequestBody.Content.Keys); + Assert.DoesNotContain("application/json-patch+json", operation.RequestBody.Content.Keys); Assert.Equal("application/vnd.github.patch+json", content.Key); var schema = Assert.IsType(content.Value.Schema); Assert.Equal("JsonPatchDocument", schema.Reference.Id); @@ -1356,6 +1360,8 @@ await VerifyOpenApiDocument(builder, document => Assert.NotNull(operation.RequestBody); Assert.NotNull(operation.RequestBody.Content); var content = Assert.Single(operation.RequestBody.Content); + Assert.DoesNotContain("application/json", operation.RequestBody.Content.Keys); + Assert.DoesNotContain("application/json-patch+json", operation.RequestBody.Content.Keys); Assert.Equal("application/vnd.github.patch+json", content.Key); var schema = Assert.IsType(content.Value.Schema); Assert.Equal("JsonPatchDocument", schema.Reference.Id); @@ -1379,6 +1385,8 @@ await VerifyOpenApiDocument(builder, document => Assert.NotNull(operation.RequestBody); Assert.NotNull(operation.RequestBody.Content); var content = Assert.Single(operation.RequestBody.Content); + Assert.DoesNotContain("application/json", operation.RequestBody.Content.Keys); + Assert.DoesNotContain("application/json-patch+json", operation.RequestBody.Content.Keys); Assert.Equal("application/vnd.github.patch+json", content.Key); var schema = Assert.IsType(content.Value.Schema); Assert.Equal("JsonPatchDocument", schema.Reference.Id);