Skip to content

Using JsonTypeInfo to read request body in RDF #45932

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
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
55 changes: 25 additions & 30 deletions src/Http/Http.Extensions/src/RequestDelegateFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -261,7 +261,7 @@ private static RequestDelegateFactoryContext CreateFactoryContext(RequestDelegat

var serviceProvider = options?.ServiceProvider ?? options?.EndpointBuilder?.ApplicationServices ?? EmptyServiceProvider.Instance;
var endpointBuilder = options?.EndpointBuilder ?? new RdfEndpointBuilder(serviceProvider);
var jsonSerializerOptions = serviceProvider.GetService<IOptions<JsonOptions>>()?.Value.SerializerOptions;
var jsonSerializerOptions = serviceProvider.GetService<IOptions<JsonOptions>>()?.Value.SerializerOptions ?? JsonOptions.DefaultSerializerOptions;

var factoryContext = new RequestDelegateFactoryContext
{
Expand Down Expand Up @@ -1000,23 +1000,23 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
methodCall,
HttpContextExpr,
factoryContext.JsonSerializerOptionsExpression,
Expression.Constant(factoryContext.JsonSerializerOptions?.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
Expression.Constant(factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
}
else if (returnType == typeof(ValueTask<object>))
{
return Expression.Call(ExecuteValueTaskOfObjectMethod,
methodCall,
HttpContextExpr,
factoryContext.JsonSerializerOptionsExpression,
Expression.Constant(factoryContext.JsonSerializerOptions?.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
Expression.Constant(factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
}
else if (returnType == typeof(Task<object>))
{
return Expression.Call(ExecuteTaskOfObjectMethod,
methodCall,
HttpContextExpr,
factoryContext.JsonSerializerOptionsExpression,
Expression.Constant(factoryContext.JsonSerializerOptions?.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
Expression.Constant(factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeof(object)), typeof(JsonTypeInfo<object>)));
}
else if (AwaitableInfo.IsTypeAwaitable(returnType, out _))
{
Expand Down Expand Up @@ -1052,9 +1052,9 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
}
else
{
var jsonTypeInfo = factoryContext.JsonSerializerOptions?.GetReadOnlyTypeInfo(typeArg);
var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeArg);

if (jsonTypeInfo?.IsPolymorphicSafe() == true)
if (jsonTypeInfo.IsPolymorphicSafe() == true)
{
return Expression.Call(
ExecuteTaskOfTFastMethod.MakeGenericMethod(typeArg),
Expand Down Expand Up @@ -1093,9 +1093,9 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
}
else
{
var jsonTypeInfo = factoryContext.JsonSerializerOptions?.GetReadOnlyTypeInfo(typeArg);
var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(typeArg);

if (jsonTypeInfo?.IsPolymorphicSafe() == true)
if (jsonTypeInfo.IsPolymorphicSafe() == true)
{
return Expression.Call(
ExecuteValueTaskOfTFastMethod.MakeGenericMethod(typeArg),
Expand Down Expand Up @@ -1137,9 +1137,9 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
}
else
{
var jsonTypeInfo = factoryContext.JsonSerializerOptions?.GetReadOnlyTypeInfo(returnType);
var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(returnType);

if (jsonTypeInfo?.IsPolymorphicSafe() == true)
if (jsonTypeInfo.IsPolymorphicSafe() == true)
{
return Expression.Call(
JsonResultWriteResponseOfTFastAsyncMethod.MakeGenericMethod(returnType),
Expand Down Expand Up @@ -1204,6 +1204,7 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
Debug.Assert(factoryContext.JsonRequestBodyParameter is not null, "factoryContext.JsonRequestBodyParameter is null for a JSON body.");

var bodyType = factoryContext.JsonRequestBodyParameter.ParameterType;
var jsonTypeInfo = factoryContext.JsonSerializerOptions.GetReadOnlyTypeInfo(bodyType);
var parameterTypeName = TypeNameHelper.GetTypeDisplayName(factoryContext.JsonRequestBodyParameter.ParameterType, fullName: false);
var parameterName = factoryContext.JsonRequestBodyParameter.Name;

Expand Down Expand Up @@ -1236,7 +1237,7 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
parameterName,
factoryContext.AllowEmptyRequestBody,
factoryContext.ThrowOnBadRequest,
factoryContext.JsonSerializerOptions);
jsonTypeInfo);

if (!successful)
{
Expand All @@ -1261,7 +1262,7 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
parameterName,
factoryContext.AllowEmptyRequestBody,
factoryContext.ThrowOnBadRequest,
factoryContext.JsonSerializerOptions);
jsonTypeInfo);

if (!successful)
{
Expand All @@ -1279,7 +1280,7 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
string parameterName,
bool allowEmptyRequestBody,
bool throwOnBadRequest,
JsonSerializerOptions? jsonSerializerOptions)
JsonTypeInfo jsonTypeInfo)
{
object? defaultBodyValue = null;

Expand All @@ -1301,7 +1302,7 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
}
try
{
bodyValue = await httpContext.Request.ReadFromJsonAsync(bodyType, jsonSerializerOptions);
bodyValue = await httpContext.Request.ReadFromJsonAsync(jsonTypeInfo);
}
catch (IOException ex)
{
Expand Down Expand Up @@ -2091,9 +2092,9 @@ private static MemberInfo GetMemberInfo<T>(Expression<T> expr)
// if necessary and restart the cycle until we've reached a terminal state (unknown type).
// We currently don't handle Task<unknown> or ValueTask<unknown>. We can support this later if this
// ends up being a common scenario.
private static Task ExecuteValueTaskOfObject(ValueTask<object> valueTask, HttpContext httpContext, JsonSerializerOptions? options, JsonTypeInfo<object>? jsonTypeInfo)
private static Task ExecuteValueTaskOfObject(ValueTask<object> valueTask, HttpContext httpContext, JsonSerializerOptions options, JsonTypeInfo<object> jsonTypeInfo)
{
static async Task ExecuteAwaited(ValueTask<object> valueTask, HttpContext httpContext, JsonSerializerOptions? options, JsonTypeInfo<object>? jsonTypeInfo)
static async Task ExecuteAwaited(ValueTask<object> valueTask, HttpContext httpContext, JsonSerializerOptions options, JsonTypeInfo<object> jsonTypeInfo)
{
await ExecuteAwaitedReturn(await valueTask, httpContext, options, jsonTypeInfo);
}
Expand All @@ -2106,9 +2107,9 @@ static async Task ExecuteAwaited(ValueTask<object> valueTask, HttpContext httpCo
return ExecuteAwaited(valueTask, httpContext, options, jsonTypeInfo);
}

private static Task ExecuteTaskOfObject(Task<object> task, HttpContext httpContext, JsonSerializerOptions? options, JsonTypeInfo<object>? jsonTypeInfo)
private static Task ExecuteTaskOfObject(Task<object> task, HttpContext httpContext, JsonSerializerOptions options, JsonTypeInfo<object> jsonTypeInfo)
{
static async Task ExecuteAwaited(Task<object> task, HttpContext httpContext, JsonSerializerOptions? options, JsonTypeInfo<object>? jsonTypeInfo)
static async Task ExecuteAwaited(Task<object> task, HttpContext httpContext, JsonSerializerOptions options, JsonTypeInfo<object> jsonTypeInfo)
{
await ExecuteAwaitedReturn(await task, httpContext, options, jsonTypeInfo);
}
Expand All @@ -2121,7 +2122,7 @@ static async Task ExecuteAwaited(Task<object> task, HttpContext httpContext, Jso
return ExecuteAwaited(task, httpContext, options, jsonTypeInfo);
}

private static Task ExecuteAwaitedReturn(object obj, HttpContext httpContext, JsonSerializerOptions? options, JsonTypeInfo<object>? jsonTypeInfo)
private static Task ExecuteAwaitedReturn(object obj, HttpContext httpContext, JsonSerializerOptions options, JsonTypeInfo<object> jsonTypeInfo)
{
// Terminal built ins
if (obj is IResult result)
Expand Down Expand Up @@ -2157,11 +2158,11 @@ static async Task ExecuteAwaited(Task<T> task, HttpContext httpContext, JsonType
return ExecuteAwaited(task, httpContext, jsonTypeInfo);
}

private static Task ExecuteTaskOfT<T>(Task<T> task, HttpContext httpContext, JsonSerializerOptions? options, JsonTypeInfo<T> jsonTypeInfo)
private static Task ExecuteTaskOfT<T>(Task<T> task, HttpContext httpContext, JsonSerializerOptions options, JsonTypeInfo<T> jsonTypeInfo)
{
EnsureRequestTaskNotNull(task);

static async Task ExecuteAwaited(Task<T> task, HttpContext httpContext, JsonSerializerOptions? options, JsonTypeInfo<T> jsonTypeInfo)
static async Task ExecuteAwaited(Task<T> task, HttpContext httpContext, JsonSerializerOptions options, JsonTypeInfo<T> jsonTypeInfo)
{
await WriteJsonResponse(httpContext.Response, await task, options, jsonTypeInfo);
}
Expand Down Expand Up @@ -2262,9 +2263,9 @@ static async Task ExecuteAwaited(ValueTask<T> task, HttpContext httpContext, Jso
return ExecuteAwaited(task, httpContext, jsonTypeInfo);
}

private static Task ExecuteValueTaskOfT<T>(ValueTask<T> task, HttpContext httpContext, JsonSerializerOptions? options, JsonTypeInfo<T> jsonTypeInfo)
private static Task ExecuteValueTaskOfT<T>(ValueTask<T> task, HttpContext httpContext, JsonSerializerOptions options, JsonTypeInfo<T> jsonTypeInfo)
{
static async Task ExecuteAwaited(ValueTask<T> task, HttpContext httpContext, JsonSerializerOptions? options, JsonTypeInfo<T> jsonTypeInfo)
static async Task ExecuteAwaited(ValueTask<T> task, HttpContext httpContext, JsonSerializerOptions options, JsonTypeInfo<T> jsonTypeInfo)
{
await WriteJsonResponse(httpContext.Response, await task, options, jsonTypeInfo);
}
Expand Down Expand Up @@ -2326,16 +2327,10 @@ private static async Task ExecuteResultWriteResponse(IResult? result, HttpContex
private static Task WriteJsonResponseFast<T>(HttpResponse response, T value, JsonTypeInfo<T> jsonTypeInfo)
=> HttpResponseJsonExtensions.WriteAsJsonAsync(response, value, jsonTypeInfo, default);

private static Task WriteJsonResponse<T>(HttpResponse response, T? value, JsonSerializerOptions? options, JsonTypeInfo<T>? jsonTypeInfo)
private static Task WriteJsonResponse<T>(HttpResponse response, T? value, JsonSerializerOptions options, JsonTypeInfo<T> jsonTypeInfo)
{
var runtimeType = value?.GetType();

// Edge case but possible if the RequestDelegateFactoryOptions.ServiceProvider and
// RequestDelegateFactoryOptions.EndpointBuilder.ServiceProvider are null
// In this situation both options and jsonTypeInfo are null.
options ??= response.HttpContext.RequestServices.GetService<IOptions<JsonOptions>>()?.Value.SerializerOptions ?? JsonOptions.DefaultSerializerOptions;
jsonTypeInfo ??= (JsonTypeInfo<T>)options.GetTypeInfo(typeof(T));

if (runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.IsPolymorphicSafe())
{
// In this case the polymorphism is not
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,6 @@ internal sealed class RequestDelegateFactoryContext
public List<ParameterInfo> Parameters { get; set; } = new();

// Grab these options upfront to avoid the per request DI scope that would be made otherwise to get the options when writing Json
public JsonSerializerOptions? JsonSerializerOptions { get; set; }
public required JsonSerializerOptions JsonSerializerOptions { get; set; }
public required Expression JsonSerializerOptionsExpression { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -1881,7 +1881,7 @@ public async Task RequestDelegatePopulatesFromBodyParameter(Delegate action)
});
httpContext.RequestServices = mock.Object;

var factoryResult = RequestDelegateFactory.Create(action);
var factoryResult = RequestDelegateFactory.Create(action, new RequestDelegateFactoryOptions() { ServiceProvider = mock.Object });
var requestDelegate = factoryResult.RequestDelegate;

await requestDelegate(httpContext);
Expand Down