Skip to content

Commit 39f36e7

Browse files
[RDG] Support serializing polymorphic response types with STJ (#46791)
* Resolving jsonoptions * Rename to EndpointJsonResponseEmitter * Fix up baselines and emitted code * Add test --------- Co-authored-by: Safia Abdalla <[email protected]>
1 parent cb21c61 commit 39f36e7

19 files changed

+251
-2
lines changed

src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context)
8080
codeWriter.WriteLine($"var handler = ({endpoint.EmitHandlerDelegateCast()})del;");
8181
codeWriter.WriteLine("EndpointFilterDelegate? filteredInvocation = null;");
8282
endpoint.EmitRouteOrQueryResolver(codeWriter);
83+
endpoint.EmitJsonPreparation(codeWriter);
8384
codeWriter.WriteLineNoTabs(string.Empty);
8485
codeWriter.WriteLine("if (options?.EndpointBuilder?.FilterFactories.Count > 0)");
8586
codeWriter.StartBlock();

src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,19 +47,24 @@ namespace Microsoft.AspNetCore.Http.Generated
4747
using System.Collections.Generic;
4848
using System.Collections.ObjectModel;
4949
using System.Diagnostics;
50+
using System.Diagnostics.CodeAnalysis;
5051
using System.Globalization;
5152
using System.Linq;
5253
using System.Reflection;
54+
using System.Text.Json;
55+
using System.Text.Json.Serialization.Metadata;
5356
using System.Threading.Tasks;
5457
using System.IO;
5558
using Microsoft.AspNetCore.Routing;
5659
using Microsoft.AspNetCore.Routing.Patterns;
5760
using Microsoft.AspNetCore.Builder;
5861
using Microsoft.AspNetCore.Http;
62+
using Microsoft.AspNetCore.Http.Json;
5963
using Microsoft.AspNetCore.Http.Metadata;
6064
using Microsoft.Extensions.DependencyInjection;
6165
using Microsoft.Extensions.FileProviders;
6266
using Microsoft.Extensions.Primitives;
67+
using Microsoft.Extensions.Options;
6368
6469
using MetadataPopulator = System.Func<System.Reflection.MethodInfo, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions?, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult>;
6570
using RequestDelegateFactoryFunc = System.Func<System.Delegate, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult?, Microsoft.AspNetCore.Http.RequestDelegateResult>;
@@ -109,6 +114,17 @@ private static Func<HttpContext, StringValues> ResolveFromRouteOrQuery(string pa
109114
: (httpContext) => httpContext.Request.Query[parameterName];
110115
}
111116
117+
private static Task WriteToResponseAsync<T>(T? value, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo, JsonSerializerOptions options)
118+
{
119+
var runtimeType = value?.GetType();
120+
if (runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.PolymorphismOptions is not null)
121+
{
122+
return httpContext.Response.WriteAsJsonAsync(value!, jsonTypeInfo);
123+
}
124+
125+
return httpContext.Response.WriteAsJsonAsync(value!, options.GetTypeInfo(runtimeType));
126+
}
127+
112128
private static async ValueTask<(bool, T?)> TryResolveBody<T>(HttpContext httpContext, bool allowEmpty)
113129
{
114130
var feature = httpContext.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpRequestBodyDetectionFeature>();
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Text;
5+
6+
namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters;
7+
8+
internal static class EndpointJsonResponseEmitter
9+
{
10+
internal static void EmitJsonPreparation(this Endpoint endpoint, CodeWriter codeWriter)
11+
{
12+
if (endpoint.Response.IsSerializable)
13+
{
14+
var typeName = endpoint.Response.ResponseType.ToDisplayString(EmitterConstants.DisplayFormat);
15+
16+
codeWriter.WriteLine("var serviceProvider = options?.ServiceProvider ?? options?.EndpointBuilder?.ApplicationServices;");
17+
codeWriter.WriteLine("var serializerOptions = serviceProvider?.GetService<IOptions<JsonOptions>>()?.Value.SerializerOptions ?? new JsonOptions().SerializerOptions;");
18+
codeWriter.WriteLine($"var jsonTypeInfo = (JsonTypeInfo<{typeName}>)serializerOptions.GetTypeInfo(typeof({typeName}));");
19+
}
20+
}
21+
22+
internal static string EmitJsonResponse(this Endpoint endpoint)
23+
{
24+
if (endpoint.Response.ResponseType.IsSealed || endpoint.Response.ResponseType.IsValueType)
25+
{
26+
return $"httpContext.Response.WriteAsJsonAsync(result, jsonTypeInfo);";
27+
}
28+
else
29+
{
30+
return $"GeneratedRouteBuilderExtensionsCore.WriteToResponseAsync(result, httpContext, jsonTypeInfo, serializerOptions);";
31+
}
32+
}
33+
}

src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointResponse.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ internal class EndpointResponse
1919
public bool IsAwaitable { get; set; }
2020
public bool IsVoid { get; set; }
2121
public bool IsIResult { get; set; }
22+
public bool IsSerializable { get; set; }
2223

2324
private WellKnownTypes WellKnownTypes { get; init; }
2425

@@ -30,6 +31,7 @@ internal EndpointResponse(IMethodSymbol method, WellKnownTypes wellKnownTypes)
3031
IsAwaitable = GetIsAwaitable(method);
3132
IsVoid = method.ReturnsVoid;
3233
IsIResult = GetIsIResult();
34+
IsSerializable = !IsIResult && !IsVoid && ResponseType.SpecialType != SpecialType.System_String && ResponseType.SpecialType != SpecialType.System_Object;
3335
ContentType = GetContentType(method);
3436
}
3537

src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,7 @@ private static string EmitResponseWritingCall(this Endpoint endpoint)
132132
}
133133
else if (!endpoint.Response.IsVoid)
134134
{
135-
return $"{returnOrAwait} httpContext.Response.WriteAsJsonAsync(result);";
135+
return $"{returnOrAwait} {endpoint.EmitJsonResponse()}";
136136
}
137137
else if (!endpoint.Response.IsAwaitable && endpoint.Response.IsVoid)
138138
{

src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,24 @@ namespace Microsoft.AspNetCore.Http.Generated
6363
using System.Collections.Generic;
6464
using System.Collections.ObjectModel;
6565
using System.Diagnostics;
66+
using System.Diagnostics.CodeAnalysis;
6667
using System.Globalization;
6768
using System.Linq;
6869
using System.Reflection;
70+
using System.Text.Json;
71+
using System.Text.Json.Serialization.Metadata;
6972
using System.Threading.Tasks;
7073
using System.IO;
7174
using Microsoft.AspNetCore.Routing;
7275
using Microsoft.AspNetCore.Routing.Patterns;
7376
using Microsoft.AspNetCore.Builder;
7477
using Microsoft.AspNetCore.Http;
78+
using Microsoft.AspNetCore.Http.Json;
7579
using Microsoft.AspNetCore.Http.Metadata;
7680
using Microsoft.Extensions.DependencyInjection;
7781
using Microsoft.Extensions.FileProviders;
7882
using Microsoft.Extensions.Primitives;
83+
using Microsoft.Extensions.Options;
7984

8085
using MetadataPopulator = System.Func<System.Reflection.MethodInfo, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions?, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult>;
8186
using RequestDelegateFactoryFunc = System.Func<System.Delegate, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult?, Microsoft.AspNetCore.Http.RequestDelegateResult>;
@@ -276,6 +281,17 @@ namespace Microsoft.AspNetCore.Http.Generated
276281
: (httpContext) => httpContext.Request.Query[parameterName];
277282
}
278283

284+
private static Task WriteToResponseAsync<T>(T? value, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo, JsonSerializerOptions options)
285+
{
286+
var runtimeType = value?.GetType();
287+
if (runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.PolymorphismOptions is not null)
288+
{
289+
return httpContext.Response.WriteAsJsonAsync(value!, jsonTypeInfo);
290+
}
291+
292+
return httpContext.Response.WriteAsJsonAsync(value!, options.GetTypeInfo(runtimeType));
293+
}
294+
279295
private static async ValueTask<(bool, T?)> TryResolveBody<T>(HttpContext httpContext, bool allowEmpty)
280296
{
281297
var feature = httpContext.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpRequestBodyDetectionFeature>();

src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,19 +93,24 @@ namespace Microsoft.AspNetCore.Http.Generated
9393
using System.Collections.Generic;
9494
using System.Collections.ObjectModel;
9595
using System.Diagnostics;
96+
using System.Diagnostics.CodeAnalysis;
9697
using System.Globalization;
9798
using System.Linq;
9899
using System.Reflection;
100+
using System.Text.Json;
101+
using System.Text.Json.Serialization.Metadata;
99102
using System.Threading.Tasks;
100103
using System.IO;
101104
using Microsoft.AspNetCore.Routing;
102105
using Microsoft.AspNetCore.Routing.Patterns;
103106
using Microsoft.AspNetCore.Builder;
104107
using Microsoft.AspNetCore.Http;
108+
using Microsoft.AspNetCore.Http.Json;
105109
using Microsoft.AspNetCore.Http.Metadata;
106110
using Microsoft.Extensions.DependencyInjection;
107111
using Microsoft.Extensions.FileProviders;
108112
using Microsoft.Extensions.Primitives;
113+
using Microsoft.Extensions.Options;
109114

110115
using MetadataPopulator = System.Func<System.Reflection.MethodInfo, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions?, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult>;
111116
using RequestDelegateFactoryFunc = System.Func<System.Delegate, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult?, Microsoft.AspNetCore.Http.RequestDelegateResult>;
@@ -354,6 +359,17 @@ namespace Microsoft.AspNetCore.Http.Generated
354359
: (httpContext) => httpContext.Request.Query[parameterName];
355360
}
356361

362+
private static Task WriteToResponseAsync<T>(T? value, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo, JsonSerializerOptions options)
363+
{
364+
var runtimeType = value?.GetType();
365+
if (runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.PolymorphismOptions is not null)
366+
{
367+
return httpContext.Response.WriteAsJsonAsync(value!, jsonTypeInfo);
368+
}
369+
370+
return httpContext.Response.WriteAsJsonAsync(value!, options.GetTypeInfo(runtimeType));
371+
}
372+
357373
private static async ValueTask<(bool, T?)> TryResolveBody<T>(HttpContext httpContext, bool allowEmpty)
358374
{
359375
var feature = httpContext.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpRequestBodyDetectionFeature>();

src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,24 @@ namespace Microsoft.AspNetCore.Http.Generated
6363
using System.Collections.Generic;
6464
using System.Collections.ObjectModel;
6565
using System.Diagnostics;
66+
using System.Diagnostics.CodeAnalysis;
6667
using System.Globalization;
6768
using System.Linq;
6869
using System.Reflection;
70+
using System.Text.Json;
71+
using System.Text.Json.Serialization.Metadata;
6972
using System.Threading.Tasks;
7073
using System.IO;
7174
using Microsoft.AspNetCore.Routing;
7275
using Microsoft.AspNetCore.Routing.Patterns;
7376
using Microsoft.AspNetCore.Builder;
7477
using Microsoft.AspNetCore.Http;
78+
using Microsoft.AspNetCore.Http.Json;
7579
using Microsoft.AspNetCore.Http.Metadata;
7680
using Microsoft.Extensions.DependencyInjection;
7781
using Microsoft.Extensions.FileProviders;
7882
using Microsoft.Extensions.Primitives;
83+
using Microsoft.Extensions.Options;
7984

8085
using MetadataPopulator = System.Func<System.Reflection.MethodInfo, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions?, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult>;
8186
using RequestDelegateFactoryFunc = System.Func<System.Delegate, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult?, Microsoft.AspNetCore.Http.RequestDelegateResult>;
@@ -506,6 +511,17 @@ namespace Microsoft.AspNetCore.Http.Generated
506511
: (httpContext) => httpContext.Request.Query[parameterName];
507512
}
508513

514+
private static Task WriteToResponseAsync<T>(T? value, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo, JsonSerializerOptions options)
515+
{
516+
var runtimeType = value?.GetType();
517+
if (runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.PolymorphismOptions is not null)
518+
{
519+
return httpContext.Response.WriteAsJsonAsync(value!, jsonTypeInfo);
520+
}
521+
522+
return httpContext.Response.WriteAsJsonAsync(value!, options.GetTypeInfo(runtimeType));
523+
}
524+
509525
private static async ValueTask<(bool, T?)> TryResolveBody<T>(HttpContext httpContext, bool allowEmpty)
510526
{
511527
var feature = httpContext.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpRequestBodyDetectionFeature>();

src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,24 @@ namespace Microsoft.AspNetCore.Http.Generated
6363
using System.Collections.Generic;
6464
using System.Collections.ObjectModel;
6565
using System.Diagnostics;
66+
using System.Diagnostics.CodeAnalysis;
6667
using System.Globalization;
6768
using System.Linq;
6869
using System.Reflection;
70+
using System.Text.Json;
71+
using System.Text.Json.Serialization.Metadata;
6972
using System.Threading.Tasks;
7073
using System.IO;
7174
using Microsoft.AspNetCore.Routing;
7275
using Microsoft.AspNetCore.Routing.Patterns;
7376
using Microsoft.AspNetCore.Builder;
7477
using Microsoft.AspNetCore.Http;
78+
using Microsoft.AspNetCore.Http.Json;
7579
using Microsoft.AspNetCore.Http.Metadata;
7680
using Microsoft.Extensions.DependencyInjection;
7781
using Microsoft.Extensions.FileProviders;
7882
using Microsoft.Extensions.Primitives;
83+
using Microsoft.Extensions.Options;
7984

8085
using MetadataPopulator = System.Func<System.Reflection.MethodInfo, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions?, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult>;
8186
using RequestDelegateFactoryFunc = System.Func<System.Delegate, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult?, Microsoft.AspNetCore.Http.RequestDelegateResult>;
@@ -200,6 +205,17 @@ namespace Microsoft.AspNetCore.Http.Generated
200205
: (httpContext) => httpContext.Request.Query[parameterName];
201206
}
202207

208+
private static Task WriteToResponseAsync<T>(T? value, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo, JsonSerializerOptions options)
209+
{
210+
var runtimeType = value?.GetType();
211+
if (runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.PolymorphismOptions is not null)
212+
{
213+
return httpContext.Response.WriteAsJsonAsync(value!, jsonTypeInfo);
214+
}
215+
216+
return httpContext.Response.WriteAsJsonAsync(value!, options.GetTypeInfo(runtimeType));
217+
}
218+
203219
private static async ValueTask<(bool, T?)> TryResolveBody<T>(HttpContext httpContext, bool allowEmpty)
204220
{
205221
var feature = httpContext.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpRequestBodyDetectionFeature>();

src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,19 +63,24 @@ namespace Microsoft.AspNetCore.Http.Generated
6363
using System.Collections.Generic;
6464
using System.Collections.ObjectModel;
6565
using System.Diagnostics;
66+
using System.Diagnostics.CodeAnalysis;
6667
using System.Globalization;
6768
using System.Linq;
6869
using System.Reflection;
70+
using System.Text.Json;
71+
using System.Text.Json.Serialization.Metadata;
6972
using System.Threading.Tasks;
7073
using System.IO;
7174
using Microsoft.AspNetCore.Routing;
7275
using Microsoft.AspNetCore.Routing.Patterns;
7376
using Microsoft.AspNetCore.Builder;
7477
using Microsoft.AspNetCore.Http;
78+
using Microsoft.AspNetCore.Http.Json;
7579
using Microsoft.AspNetCore.Http.Metadata;
7680
using Microsoft.Extensions.DependencyInjection;
7781
using Microsoft.Extensions.FileProviders;
7882
using Microsoft.Extensions.Primitives;
83+
using Microsoft.Extensions.Options;
7984

8085
using MetadataPopulator = System.Func<System.Reflection.MethodInfo, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions?, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult>;
8186
using RequestDelegateFactoryFunc = System.Func<System.Delegate, Microsoft.AspNetCore.Http.RequestDelegateFactoryOptions, Microsoft.AspNetCore.Http.RequestDelegateMetadataResult?, Microsoft.AspNetCore.Http.RequestDelegateResult>;
@@ -228,6 +233,17 @@ namespace Microsoft.AspNetCore.Http.Generated
228233
: (httpContext) => httpContext.Request.Query[parameterName];
229234
}
230235

236+
private static Task WriteToResponseAsync<T>(T? value, HttpContext httpContext, JsonTypeInfo<T> jsonTypeInfo, JsonSerializerOptions options)
237+
{
238+
var runtimeType = value?.GetType();
239+
if (runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.PolymorphismOptions is not null)
240+
{
241+
return httpContext.Response.WriteAsJsonAsync(value!, jsonTypeInfo);
242+
}
243+
244+
return httpContext.Response.WriteAsJsonAsync(value!, options.GetTypeInfo(runtimeType));
245+
}
246+
231247
private static async ValueTask<(bool, T?)> TryResolveBody<T>(HttpContext httpContext, bool allowEmpty)
232248
{
233249
var feature = httpContext.Features.Get<Microsoft.AspNetCore.Http.Features.IHttpRequestBodyDetectionFeature>();

0 commit comments

Comments
 (0)