Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.IO;
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.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
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 factory = new JsonPatchWebApplicationFactory(LoggerFactory);
using var client = factory.CreateClient();

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);
}

private sealed class JsonPatchWebApplicationFactory(ILoggerFactory loggerFactory) : WebApplicationFactory<JsonPatchContentTypeEndToEndTest>
{
protected override void ConfigureWebHost(IWebHostBuilder builder) => builder.UseContentRoot(Directory.GetCurrentDirectory());

protected override IHostBuilder CreateHostBuilder()
{
return 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<SimpleObject> patch) => { });
});
});
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,15 @@
<Reference Include="Microsoft.AspNetCore.Http.Connections" />
<Reference Include="Microsoft.AspNetCore.Http.Extensions" />
<Reference Include="Microsoft.AspNetCore.Http.Features" />
<Reference Include="Microsoft.AspNetCore.Hosting" />
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
<Reference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" />
<Reference Include="Microsoft.AspNetCore.Mvc.Testing" />
<Reference Include="Microsoft.AspNetCore.Routing" />
<Reference Include="Microsoft.AspNetCore.Routing.Abstractions" />
<Reference Include="Microsoft.AspNetCore.TestHost" />
<Reference Include="Microsoft.AspNetCore.JsonPatch.SystemTextJson" />
<Reference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>

</Project>
20 changes: 0 additions & 20 deletions src/Features/JsonPatch/src/JsonPatchDocument.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Operation> Operations { get; private set; }

Expand Down Expand Up @@ -230,14 +220,4 @@ IList<Operation> IJsonPatchDocument.GetOperations()
return allOps;
}

#if NET
/// <inheritdoc/>
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
}
21 changes: 0 additions & 21 deletions src/Features/JsonPatch/src/JsonPatchDocumentOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -16,23 +15,14 @@
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
// when producing a JsonPatchDocument. However, we cannot send this "typed" over the wire, as that would require
// 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<TModel> : IJsonPatchDocument, IEndpointParameterMetadataProvider where TModel : class
#else
public class JsonPatchDocument<TModel> : IJsonPatchDocument where TModel : class
#endif
{
public List<Operation<TModel>> Operations { get; private set; }

Expand Down Expand Up @@ -666,17 +656,6 @@ IList<Operation> IJsonPatchDocument.GetOperations()
return allOps;
}

#if NET
/// <inheritdoc/>
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<TProp>(Expression<Func<TModel, TProp>> expr, string position)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
<ItemGroup>
<Reference Include="Microsoft.CSharp" Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'" />
<Reference Include="Newtonsoft.Json" />
<Reference Include="Microsoft.AspNetCore.Http.Abstractions" Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'" />
</ItemGroup>

<ItemGroup>
Expand Down
69 changes: 69 additions & 0 deletions src/Features/JsonPatch/test/JsonPatchContentTypeEndToEndTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
// 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.IO;
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.Http;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
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 factory = new JsonPatchWebApplicationFactory(LoggerFactory);
using var client = factory.CreateClient();

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);
}

private sealed class JsonPatchWebApplicationFactory(ILoggerFactory loggerFactory) : WebApplicationFactory<JsonPatchContentTypeEndToEndTest>
{
protected override void ConfigureWebHost(IWebHostBuilder builder) => builder.UseContentRoot(Directory.GetCurrentDirectory());

protected override IHostBuilder CreateHostBuilder()
{
return 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<SimpleObject> patch) => { });
});
});
});
}
}
}
#endif
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,21 @@
<Reference Include="Microsoft.AspNetCore.JsonPatch" />
<Reference Include="Microsoft.CSharp" Condition="'$(TargetFrameworkIdentifier)' != '.NETCoreApp'" />
<Reference Include="Newtonsoft.Json" />
<Reference Include="Microsoft.AspNetCore.Http.Abstractions" Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'" />
</ItemGroup>

<ItemGroup Condition="'$(TargetFrameworkIdentifier)' == '.NETCoreApp'">
<Reference Include="Microsoft.AspNetCore.Hosting" />
<Reference Include="Microsoft.AspNetCore.Hosting.Abstractions" />
<Reference Include="Microsoft.AspNetCore.Hosting.Server.Abstractions" />
<Reference Include="Microsoft.AspNetCore.Http" />
<Reference Include="Microsoft.AspNetCore.Http.Abstractions" />
<Reference Include="Microsoft.AspNetCore.Http.Extensions" />
<Reference Include="Microsoft.AspNetCore.Http.Features" />
<Reference Include="Microsoft.AspNetCore.Mvc.Testing" />
<Reference Include="Microsoft.AspNetCore.Routing" />
<Reference Include="Microsoft.AspNetCore.Routing.Abstractions" />
<Reference Include="Microsoft.AspNetCore.TestHost" />
<Reference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>

</Project>
Original file line number Diff line number Diff line change
Expand Up @@ -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<OpenApiSchemaReference>(content.Value.Schema);
Assert.Equal("JsonPatchDocument", schema.Reference.Id);
Expand All @@ -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<OpenApiSchemaReference>(content.Value.Schema);
Assert.Equal("JsonPatchDocument", schema.Reference.Id);
Expand Down Expand Up @@ -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<OpenApiSchemaReference>(content.Value.Schema);
Assert.Equal("JsonPatchDocument", schema.Reference.Id);
Expand All @@ -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<OpenApiSchemaReference>(content.Value.Schema);
Assert.Equal("JsonPatchDocument", schema.Reference.Id);
Expand Down
Loading