Skip to content

Commit 0a4f4ab

Browse files
authored
Support struct FromBody parameters in minimal actions (#35124)
1 parent 79aefbb commit 0a4f4ab

File tree

2 files changed

+39
-41
lines changed

2 files changed

+39
-41
lines changed

src/Http/Http.Extensions/src/RequestDelegateFactory.cs

Lines changed: 26 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,9 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4-
using System;
5-
using System.Collections.Generic;
6-
using System.IO;
7-
using System.Linq;
84
using System.Linq.Expressions;
95
using System.Reflection;
106
using System.Security.Claims;
11-
using System.Threading;
12-
using System.Threading.Tasks;
137
using Microsoft.AspNetCore.Http.Features;
148
using Microsoft.AspNetCore.Http.Metadata;
159
using Microsoft.Extensions.DependencyInjection;
@@ -168,7 +162,7 @@ public static RequestDelegate Create(MethodInfo methodInfo, Func<HttpContext, ob
168162

169163
var arguments = CreateArguments(methodInfo.GetParameters(), factoryContext);
170164

171-
var responseWritingMethodCall = factoryContext.CheckParams.Count > 0 ?
165+
var responseWritingMethodCall = factoryContext.ParamCheckExpressions.Count > 0 ?
172166
CreateParamCheckingResponseWritingMethodCall(methodInfo, targetExpression, arguments, factoryContext) :
173167
CreateResponseWritingMethodCall(methodInfo, targetExpression, arguments);
174168

@@ -325,15 +319,21 @@ private static Expression CreateParamCheckingResponseWritingMethodCall(
325319
// };
326320
// }
327321

328-
var localVariables = new ParameterExpression[factoryContext.CheckParams.Count + 1];
329-
var checkParamAndCallMethod = new Expression[factoryContext.CheckParams.Count + 1];
322+
var localVariables = new ParameterExpression[factoryContext.ExtraLocals.Count + 1];
323+
var checkParamAndCallMethod = new Expression[factoryContext.ParamCheckExpressions.Count + 1];
330324

331-
for (var i = 0; i < factoryContext.CheckParams.Count; i++)
325+
326+
for (var i = 0; i < factoryContext.ExtraLocals.Count; i++)
327+
{
328+
localVariables[i] = factoryContext.ExtraLocals[i];
329+
}
330+
331+
for (var i = 0; i < factoryContext.ParamCheckExpressions.Count; i++)
332332
{
333-
(localVariables[i], checkParamAndCallMethod[i]) = factoryContext.CheckParams[i];
333+
checkParamAndCallMethod[i] = factoryContext.ParamCheckExpressions[i];
334334
}
335335

336-
localVariables[factoryContext.CheckParams.Count] = WasParamCheckFailureExpr;
336+
localVariables[factoryContext.ExtraLocals.Count] = WasParamCheckFailureExpr;
337337

338338
var set400StatusAndReturnCompletedTask = Expression.Block(
339339
Expression.Assign(StatusCodeExpr, Expression.Constant(400)),
@@ -345,7 +345,7 @@ private static Expression CreateParamCheckingResponseWritingMethodCall(
345345
set400StatusAndReturnCompletedTask,
346346
AddResponseWritingToMethodCall(methodCall, methodInfo.ReturnType));
347347

348-
checkParamAndCallMethod[factoryContext.CheckParams.Count] = checkWasParamCheckFailure;
348+
checkParamAndCallMethod[factoryContext.ParamCheckExpressions.Count] = checkWasParamCheckFailure;
349349

350350
return Expression.Block(localVariables, checkParamAndCallMethod);
351351
}
@@ -561,7 +561,8 @@ private static Expression BindParameterFromValue(ParameterInfo parameter, Expres
561561
)
562562
);
563563

564-
factoryContext.CheckParams.Add((argument, checkRequiredStringParameterBlock));
564+
factoryContext.ExtraLocals.Add(argument);
565+
factoryContext.ParamCheckExpressions.Add(checkRequiredStringParameterBlock);
565566
return argument;
566567
}
567568

@@ -652,7 +653,7 @@ private static Expression BindParameterFromValue(ParameterInfo parameter, Expres
652653
// if (tempSourceString == null)
653654
// {
654655
// wasParamCheckFailure = true;
655-
// Log.RequiredParameterNotProvided(httpContext, "Int32", "param1");
656+
// Log.RequiredParameterNotProvided(httpContext, "Int32", "param1");
656657
// }
657658
var checkRequiredParaseableParameterBlock = Expression.Block(
658659
Expression.IfThen(TempSourceStringNullExpr,
@@ -692,7 +693,8 @@ private static Expression BindParameterFromValue(ParameterInfo parameter, Expres
692693
// if (tempSourceString != null) { ... }
693694
ifNotNullTryParse);
694695

695-
factoryContext.CheckParams.Add((argument, fullParamCheckBlock));
696+
factoryContext.ExtraLocals.Add(argument);
697+
factoryContext.ParamCheckExpressions.Add(fullParamCheckBlock);
696698

697699
return argument;
698700
}
@@ -720,36 +722,30 @@ private static Expression BindParameterFromBody(ParameterInfo parameter, bool al
720722
factoryContext.JsonRequestBodyType = parameter.ParameterType;
721723
factoryContext.AllowEmptyRequestBody = allowEmpty || isOptional;
722724

723-
var convertedBodyValue = Expression.Convert(BodyValueExpr, parameter.ParameterType);
724-
var argument = Expression.Variable(parameter.ParameterType, $"{parameter.Name}_local");
725-
726725
if (!factoryContext.AllowEmptyRequestBody)
727726
{
728727
// If the parameter is required or the user has not explicitly
729728
// set allowBody to be empty then validate that it is required.
730729
//
731-
// Todo body_local = Convert(bodyValue, ToDo);
732-
// if (body_local == null)
730+
// if (bodyValue == null)
733731
// {
734732
// wasParamCheckFailure = true;
735733
// Log.RequiredParameterNotProvided(httpContext, "Todo", "body");
736734
// }
737735
var checkRequiredBodyBlock = Expression.Block(
738-
Expression.Assign(argument, convertedBodyValue),
739736
Expression.IfThen(
740-
Expression.Equal(argument, Expression.Constant(null)),
737+
Expression.Equal(BodyValueExpr, Expression.Constant(null)),
741738
Expression.Block(
742739
Expression.Assign(WasParamCheckFailureExpr, Expression.Constant(true)),
743740
Expression.Call(LogRequiredParameterNotProvidedMethod,
744741
HttpContextExpr, Expression.Constant(parameter.ParameterType.Name), Expression.Constant(parameter.Name))
745742
)
746743
)
747744
);
748-
factoryContext.CheckParams.Add((argument, checkRequiredBodyBlock));
749-
return argument;
750-
}
751745

752-
if (parameter.HasDefaultValue)
746+
factoryContext.ParamCheckExpressions.Add(checkRequiredBodyBlock);
747+
}
748+
else if (parameter.HasDefaultValue)
753749
{
754750
// Convert(bodyValue ?? SomeDefault, Todo)
755751
return Expression.Convert(
@@ -758,7 +754,7 @@ private static Expression BindParameterFromBody(ParameterInfo parameter, bool al
758754
}
759755

760756
// Convert(bodyValue, Todo)
761-
return convertedBodyValue;
757+
return Expression.Convert(BodyValueExpr, parameter.ParameterType);
762758
}
763759

764760
private static MethodInfo GetMethodInfo<T>(Expression<T> expr)
@@ -953,7 +949,8 @@ private class FactoryContext
953949
public List<string>? RouteParameters { get; set; }
954950

955951
public bool UsingTempSourceString { get; set; }
956-
public List<(ParameterExpression, Expression)> CheckParams { get; } = new();
952+
public List<ParameterExpression> ExtraLocals { get; } = new();
953+
public List<Expression> ParamCheckExpressions { get; } = new();
957954
}
958955

959956
private static partial class Log

src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs

Lines changed: 13 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,7 @@
33

44
#nullable enable
55

6-
using System;
7-
using System.Collections.Generic;
86
using System.Globalization;
9-
using System.IO;
10-
using System.Linq;
117
using System.Linq.Expressions;
128
using System.Net;
139
using System.Net.Sockets;
@@ -18,9 +14,6 @@
1814
using System.Text;
1915
using System.Text.Json;
2016
using System.Text.Json.Serialization;
21-
using System.Threading;
22-
using System.Threading.Tasks;
23-
2417
using Microsoft.AspNetCore.Http;
2518
using Microsoft.AspNetCore.Http.Features;
2619
using Microsoft.AspNetCore.Http.Json;
@@ -645,21 +638,27 @@ void TestExplicitFromBody(HttpContext httpContext, [FromBody] Todo todo)
645638
httpContext.Items.Add("body", todo);
646639
}
647640

648-
void TestImpliedFromBody(HttpContext httpContext, Todo myService)
641+
void TestImpliedFromBody(HttpContext httpContext, Todo todo)
642+
{
643+
httpContext.Items.Add("body", todo);
644+
}
645+
646+
void TestImpliedFromBodyInterface(HttpContext httpContext, ITodo todo)
649647
{
650-
httpContext.Items.Add("body", myService);
648+
httpContext.Items.Add("body", todo);
651649
}
652650

653-
void TestImpliedFromBodyInterface(HttpContext httpContext, ITodo myService)
651+
void TestImpliedFromBodyStruct(HttpContext httpContext, TodoStruct todo)
654652
{
655-
httpContext.Items.Add("body", myService);
653+
httpContext.Items.Add("body", todo);
656654
}
657655

658656
return new[]
659657
{
660658
new[] { (Action<HttpContext, Todo>)TestExplicitFromBody },
661659
new[] { (Action<HttpContext, Todo>)TestImpliedFromBody },
662660
new[] { (Action<HttpContext, ITodo>)TestImpliedFromBodyInterface },
661+
new object[] { (Action<HttpContext, TodoStruct>)TestImpliedFromBodyStruct },
663662
};
664663
}
665664
}
@@ -703,7 +702,7 @@ public async Task RequestDelegatePopulatesFromBodyParameter(Delegate action)
703702

704703
var deserializedRequestBody = httpContext.Items["body"];
705704
Assert.NotNull(deserializedRequestBody);
706-
Assert.Equal(originalTodo.Name, ((Todo)deserializedRequestBody!).Name);
705+
Assert.Equal(originalTodo.Name, ((ITodo)deserializedRequestBody!).Name);
707706
}
708707

709708
[Theory]
@@ -1785,6 +1784,8 @@ private class Todo : ITodo
17851784
public bool IsComplete { get; set; }
17861785
}
17871786

1787+
private record struct TodoStruct(int Id, string? Name, bool IsComplete) : ITodo;
1788+
17881789
private interface ITodo
17891790
{
17901791
public int Id { get; }

0 commit comments

Comments
 (0)