diff --git a/src/Features/JsonPatch.SystemTextJson/src/JsonPatchDocument.cs b/src/Features/JsonPatch.SystemTextJson/src/JsonPatchDocument.cs index c57febaff4e9..3e2c41f56e82 100644 --- a/src/Features/JsonPatch.SystemTextJson/src/JsonPatchDocument.cs +++ b/src/Features/JsonPatch.SystemTextJson/src/JsonPatchDocument.cs @@ -3,8 +3,11 @@ using System; using System.Collections.Generic; +using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http.Metadata; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Adapters; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Converters; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Exceptions; @@ -18,7 +21,7 @@ namespace Microsoft.AspNetCore.JsonPatch.SystemTextJson; // 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))] -public class JsonPatchDocument : IJsonPatchDocument +public class JsonPatchDocument : IJsonPatchDocument, IEndpointParameterMetadataProvider { public List Operations { get; private set; } @@ -218,4 +221,17 @@ IList IJsonPatchDocument.GetOperations() return allOps; } + + /// + /// Populates metadata for the related endpoint when this type is used as a parameter. + /// + /// The for the endpoint parameter. + /// The endpoint builder for the endpoint being constructed. + 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)); + } } diff --git a/src/Features/JsonPatch.SystemTextJson/src/JsonPatchDocumentOfT.cs b/src/Features/JsonPatch.SystemTextJson/src/JsonPatchDocumentOfT.cs index afb3f9ab0858..2e2d3fcd4604 100644 --- a/src/Features/JsonPatch.SystemTextJson/src/JsonPatchDocumentOfT.cs +++ b/src/Features/JsonPatch.SystemTextJson/src/JsonPatchDocumentOfT.cs @@ -9,6 +9,8 @@ using System.Reflection; using System.Text.Json; using System.Text.Json.Serialization; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Http.Metadata; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Adapters; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Converters; using Microsoft.AspNetCore.JsonPatch.SystemTextJson.Exceptions; @@ -23,7 +25,7 @@ namespace Microsoft.AspNetCore.JsonPatch.SystemTextJson; // 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(JsonPatchDocumentConverterFactory))] -public class JsonPatchDocument : IJsonPatchDocument where TModel : class +public class JsonPatchDocument : IJsonPatchDocument, IEndpointParameterMetadataProvider where TModel : class { public List> Operations { get; private set; } @@ -657,6 +659,19 @@ IList IJsonPatchDocument.GetOperations() return allOps; } + /// + /// Populates metadata for the related endpoint when this type is used as a parameter. + /// + /// The for the endpoint parameter. + /// The endpoint builder for the endpoint being constructed. + 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))); + } + // Internal for testing internal string GetPath(Expression> expr, string position) { diff --git a/src/Features/JsonPatch.SystemTextJson/src/Microsoft.AspNetCore.JsonPatch.SystemTextJson.csproj b/src/Features/JsonPatch.SystemTextJson/src/Microsoft.AspNetCore.JsonPatch.SystemTextJson.csproj index 8174fe7c0c92..434a8cf7373a 100644 --- a/src/Features/JsonPatch.SystemTextJson/src/Microsoft.AspNetCore.JsonPatch.SystemTextJson.csproj +++ b/src/Features/JsonPatch.SystemTextJson/src/Microsoft.AspNetCore.JsonPatch.SystemTextJson.csproj @@ -16,6 +16,10 @@ + + + + diff --git a/src/Features/JsonPatch/src/JsonPatchDocument.cs b/src/Features/JsonPatch/src/JsonPatchDocument.cs index 5b9152ccdb12..c2e31879ef9f 100644 --- a/src/Features/JsonPatch/src/JsonPatchDocument.cs +++ b/src/Features/JsonPatch/src/JsonPatchDocument.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Reflection; using Microsoft.AspNetCore.JsonPatch.Adapters; using Microsoft.AspNetCore.JsonPatch.Converters; using Microsoft.AspNetCore.JsonPatch.Exceptions; @@ -12,13 +13,22 @@ 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; } @@ -218,4 +228,19 @@ IList IJsonPatchDocument.GetOperations() return allOps; } + +#if NET + /// + /// Populates metadata for the related endpoint when this type is used as a parameter. + /// + /// The for the endpoint parameter. + /// The endpoint builder for the endpoint being constructed. + 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 a5340ef515c4..c4481b7f824b 100644 --- a/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs +++ b/src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs @@ -6,6 +6,7 @@ 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; @@ -15,6 +16,11 @@ 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 @@ -22,7 +28,11 @@ 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; } @@ -656,6 +666,21 @@ IList IJsonPatchDocument.GetOperations() return allOps; } +#if NET + /// + /// Populates metadata for the related endpoint when this type is used as a parameter. + /// + /// The for the endpoint parameter. + /// The endpoint builder for the endpoint being constructed. + 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 f4027ea5fd71..063436f41eb5 100644 --- a/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj +++ b/src/Features/JsonPatch/src/Microsoft.AspNetCore.JsonPatch.csproj @@ -16,6 +16,11 @@ + + + + + @@ -24,6 +29,7 @@ + diff --git a/src/OpenApi/sample/Endpoints/MapSchemasEndpoints.cs b/src/OpenApi/sample/Endpoints/MapSchemasEndpoints.cs index ee1840d07164..5aea239062db 100644 --- a/src/OpenApi/sample/Endpoints/MapSchemasEndpoints.cs +++ b/src/OpenApi/sample/Endpoints/MapSchemasEndpoints.cs @@ -4,6 +4,7 @@ using System.Collections.Immutable; using System.ComponentModel; using Microsoft.AspNetCore.Http.HttpResults; +using Microsoft.AspNetCore.JsonPatch.SystemTextJson; public static class SchemasEndpointsExtensions { @@ -36,6 +37,7 @@ public static IEndpointRouteBuilder MapSchemasEndpoints(this IEndpointRouteBuild schemas.MapPost("/location", (LocationContainer location) => { }); schemas.MapPost("/parent", (ParentObject parent) => Results.Ok(parent)); schemas.MapPost("/child", (ChildObject child) => Results.Ok(child)); + schemas.MapPatch("/json-patch", (JsonPatchDocument patchDoc) => Results.NoContent()); return endpointRouteBuilder; } diff --git a/src/OpenApi/sample/Sample.csproj b/src/OpenApi/sample/Sample.csproj index f68e61f29ba8..457b8d6f8651 100644 --- a/src/OpenApi/sample/Sample.csproj +++ b/src/OpenApi/sample/Sample.csproj @@ -22,6 +22,7 @@ + diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/snapshots/OpenApi3_0/OpenApiDocumentIntegrationTests.VerifyOpenApiDocument_documentName=schemas-by-ref.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/snapshots/OpenApi3_0/OpenApiDocumentIntegrationTests.VerifyOpenApiDocument_documentName=schemas-by-ref.verified.txt index d512ac884eb9..1ea7808f7afc 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/snapshots/OpenApi3_0/OpenApiDocumentIntegrationTests.VerifyOpenApiDocument_documentName=schemas-by-ref.verified.txt +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/snapshots/OpenApi3_0/OpenApiDocumentIntegrationTests.VerifyOpenApiDocument_documentName=schemas-by-ref.verified.txt @@ -507,6 +507,28 @@ } } } + }, + "/schemas-by-ref/json-patch": { + "patch": { + "tags": [ + "Sample" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/JsonPatchDocumentOfParentObject" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } } }, "components": { @@ -593,6 +615,7 @@ } } }, + "JsonPatchDocumentOfParentObject": { }, "LocationContainer": { "required": [ "location" diff --git a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/snapshots/OpenApi3_1/OpenApiDocumentIntegrationTests.VerifyOpenApiDocument_documentName=schemas-by-ref.verified.txt b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/snapshots/OpenApi3_1/OpenApiDocumentIntegrationTests.VerifyOpenApiDocument_documentName=schemas-by-ref.verified.txt index 705e2527a13d..acd911123de5 100644 --- a/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/snapshots/OpenApi3_1/OpenApiDocumentIntegrationTests.VerifyOpenApiDocument_documentName=schemas-by-ref.verified.txt +++ b/src/OpenApi/test/Microsoft.AspNetCore.OpenApi.Tests/Integration/snapshots/OpenApi3_1/OpenApiDocumentIntegrationTests.VerifyOpenApiDocument_documentName=schemas-by-ref.verified.txt @@ -507,6 +507,28 @@ } } } + }, + "/schemas-by-ref/json-patch": { + "patch": { + "tags": [ + "Sample" + ], + "requestBody": { + "content": { + "application/json-patch+json": { + "schema": { + "$ref": "#/components/schemas/JsonPatchDocumentOfParentObject" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "OK" + } + } + } } }, "components": { @@ -593,6 +615,7 @@ } } }, + "JsonPatchDocumentOfParentObject": { }, "LocationContainer": { "required": [ "location"