Skip to content

Remove form support from minimal APIs #31646

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
4 commits merged into from
Apr 9, 2021
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
30 changes: 15 additions & 15 deletions AspNetCore.sln
Original file line number Diff line number Diff line change
Expand Up @@ -1600,8 +1600,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BlazorWinFormsApp", "src\Co
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.AspNetCore.Http.Abstractions.Microbenchmarks", "src\Http\Http.Abstractions\perf\Microbenchmarks\Microsoft.AspNetCore.Http.Abstractions.Microbenchmarks.csproj", "{3F752B48-2936-4FCA-B0DC-4AB0F788F897}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MapActionSample", "src\Http\samples\MapActionSample\MapActionSample.csproj", "{A661D867-708A-494E-8B6B-6558804F9A3F}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{F0849E7E-61DB-4849-9368-9E7BC125DCB0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinFormsTestApp", "src\Components\WebView\Platforms\WindowsForms\testassets\WinFormsTestApp\WinFormsTestApp.csproj", "{99EE7769-3C81-477B-B947-0A5CBCD5B27D}"
Expand All @@ -1626,6 +1624,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.SpaSer
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.AspNetCore.SpaServices.Extensions.Tests", "src\Middleware\Spa\SpaServices.Extensions\test\Microsoft.AspNetCore.SpaServices.Extensions.Tests.csproj", "{AAB50C64-39AA-4AED-8E9C-50D68E7751AD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MinimalSample", "src\Http\samples\MinimalSample\MinimalSample.csproj", "{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -7613,18 +7613,6 @@ Global
{3F752B48-2936-4FCA-B0DC-4AB0F788F897}.Release|x64.Build.0 = Release|Any CPU
{3F752B48-2936-4FCA-B0DC-4AB0F788F897}.Release|x86.ActiveCfg = Release|Any CPU
{3F752B48-2936-4FCA-B0DC-4AB0F788F897}.Release|x86.Build.0 = Release|Any CPU
{A661D867-708A-494E-8B6B-6558804F9A3F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A661D867-708A-494E-8B6B-6558804F9A3F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A661D867-708A-494E-8B6B-6558804F9A3F}.Debug|x64.ActiveCfg = Debug|Any CPU
{A661D867-708A-494E-8B6B-6558804F9A3F}.Debug|x64.Build.0 = Debug|Any CPU
{A661D867-708A-494E-8B6B-6558804F9A3F}.Debug|x86.ActiveCfg = Debug|Any CPU
{A661D867-708A-494E-8B6B-6558804F9A3F}.Debug|x86.Build.0 = Debug|Any CPU
{A661D867-708A-494E-8B6B-6558804F9A3F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A661D867-708A-494E-8B6B-6558804F9A3F}.Release|Any CPU.Build.0 = Release|Any CPU
{A661D867-708A-494E-8B6B-6558804F9A3F}.Release|x64.ActiveCfg = Release|Any CPU
{A661D867-708A-494E-8B6B-6558804F9A3F}.Release|x64.Build.0 = Release|Any CPU
{A661D867-708A-494E-8B6B-6558804F9A3F}.Release|x86.ActiveCfg = Release|Any CPU
{A661D867-708A-494E-8B6B-6558804F9A3F}.Release|x86.Build.0 = Release|Any CPU
{99EE7769-3C81-477B-B947-0A5CBCD5B27D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{99EE7769-3C81-477B-B947-0A5CBCD5B27D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{99EE7769-3C81-477B-B947-0A5CBCD5B27D}.Debug|x64.ActiveCfg = Debug|Any CPU
Expand Down Expand Up @@ -7709,6 +7697,18 @@ Global
{AAB50C64-39AA-4AED-8E9C-50D68E7751AD}.Release|x64.Build.0 = Release|Any CPU
{AAB50C64-39AA-4AED-8E9C-50D68E7751AD}.Release|x86.ActiveCfg = Release|Any CPU
{AAB50C64-39AA-4AED-8E9C-50D68E7751AD}.Release|x86.Build.0 = Release|Any CPU
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}.Debug|Any CPU.Build.0 = Debug|Any CPU
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}.Debug|x64.ActiveCfg = Debug|Any CPU
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}.Debug|x64.Build.0 = Debug|Any CPU
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}.Debug|x86.ActiveCfg = Debug|Any CPU
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}.Debug|x86.Build.0 = Debug|Any CPU
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}.Release|Any CPU.ActiveCfg = Release|Any CPU
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}.Release|Any CPU.Build.0 = Release|Any CPU
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}.Release|x64.ActiveCfg = Release|Any CPU
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}.Release|x64.Build.0 = Release|Any CPU
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}.Release|x86.ActiveCfg = Release|Any CPU
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -8501,7 +8501,6 @@ Global
{3BA297F8-1CA1-492D-AE64-A60B825D8501} = {D4E9A2C5-0838-42DF-BC80-C829C4C9137E}
{CC740832-D268-47A3-9058-B9054F8397E2} = {D3B76F4E-A980-45BF-AEA1-EA3175B0B5A1}
{3F752B48-2936-4FCA-B0DC-4AB0F788F897} = {DCBBDB52-4A49-4141-8F4D-81C0FFFB7BD5}
{A661D867-708A-494E-8B6B-6558804F9A3F} = {EB5E294B-9ED5-43BF-AFA9-1CD2327F3DC1}
{F0849E7E-61DB-4849-9368-9E7BC125DCB0} = {D4E9A2C5-0838-42DF-BC80-C829C4C9137E}
{99EE7769-3C81-477B-B947-0A5CBCD5B27D} = {F0849E7E-61DB-4849-9368-9E7BC125DCB0}
{94D0D6F3-8632-41DE-908B-47A787D570FF} = {5241CF68-66A0-4724-9BAA-36DB959A5B11}
Expand All @@ -8514,6 +8513,7 @@ Global
{7F99E967-3DC1-4198-9D55-47CD9471D0B6} = {0A064174-8E5C-4F97-B941-A4E302661DF2}
{DF4637DA-5F07-4903-8461-4E2DAB235F3C} = {7F99E967-3DC1-4198-9D55-47CD9471D0B6}
{AAB50C64-39AA-4AED-8E9C-50D68E7751AD} = {7F99E967-3DC1-4198-9D55-47CD9471D0B6}
{9647D8B7-4616-4E05-B258-BAD5CAEEDD38} = {EB5E294B-9ED5-43BF-AFA9-1CD2327F3DC1}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3E8720B3-DBDD-498C-B383-2CC32A054E8F}
Expand Down
16 changes: 0 additions & 16 deletions src/Http/Http.Abstractions/src/Metadata/IFromFormMetadata.cs

This file was deleted.

2 changes: 0 additions & 2 deletions src/Http/Http.Abstractions/src/PublicAPI.Unshipped.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,6 @@ Microsoft.AspNetCore.Http.IResult
Microsoft.AspNetCore.Http.IResult.ExecuteAsync(Microsoft.AspNetCore.Http.HttpContext! httpContext) -> System.Threading.Tasks.Task!
Microsoft.AspNetCore.Http.Metadata.IFromBodyMetadata
Microsoft.AspNetCore.Http.Metadata.IFromBodyMetadata.AllowEmpty.get -> bool
Microsoft.AspNetCore.Http.Metadata.IFromFormMetadata
Microsoft.AspNetCore.Http.Metadata.IFromFormMetadata.Name.get -> string?
Microsoft.AspNetCore.Http.Metadata.IFromHeaderMetadata
Microsoft.AspNetCore.Http.Metadata.IFromHeaderMetadata.Name.get -> string?
Microsoft.AspNetCore.Http.Metadata.IFromQueryMetadata
Expand Down
123 changes: 25 additions & 98 deletions src/Http/Http.Extensions/src/RequestDelegateFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -203,48 +203,20 @@ private static Expression CreateArgument(ParameterInfo parameter, FactoryContext
}
else if (parameterCustomAttributes.OfType<IFromBodyMetadata>().FirstOrDefault() is { } bodyAttribute)
{
if (factoryContext.RequestBodyMode is RequestBodyMode.AsJson)
if (factoryContext.JsonRequestBodyType is not null)
{
throw new InvalidOperationException("Action cannot have more than one FromBody attribute.");
}

if (factoryContext.RequestBodyMode is RequestBodyMode.AsForm)
{
ThrowCannotReadBodyDirectlyAndAsForm();
}

factoryContext.RequestBodyMode = RequestBodyMode.AsJson;
factoryContext.JsonRequestBodyType = parameter.ParameterType;
factoryContext.AllowEmptyRequestBody = bodyAttribute.AllowEmpty;

return Expression.Convert(BodyValueExpr, parameter.ParameterType);
}
else if (parameterCustomAttributes.OfType<IFromFormMetadata>().FirstOrDefault() is { } formAttribute)
{
if (factoryContext.RequestBodyMode is RequestBodyMode.AsJson)
{
ThrowCannotReadBodyDirectlyAndAsForm();
}

factoryContext.RequestBodyMode = RequestBodyMode.AsForm;

return BindParameterFromProperty(parameter, FormExpr, formAttribute.Name ?? parameter.Name, factoryContext);
}
else if (parameter.CustomAttributes.Any(a => typeof(IFromServiceMetadata).IsAssignableFrom(a.AttributeType)))
{
return Expression.Call(GetRequiredServiceMethod.MakeGenericMethod(parameter.ParameterType), RequestServicesExpr);
}
else if (parameter.ParameterType == typeof(IFormCollection))
{
if (factoryContext.RequestBodyMode is RequestBodyMode.AsJson)
{
ThrowCannotReadBodyDirectlyAndAsForm();
}

factoryContext.RequestBodyMode = RequestBodyMode.AsForm;

return Expression.Property(HttpRequestExpr, nameof(HttpRequest.Form));
}
else if (parameter.ParameterType == typeof(HttpContext))
{
return HttpContextExpr;
Expand Down Expand Up @@ -446,67 +418,41 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,

private static Func<object?, HttpContext, Task> HandleRequestBodyAndCompileRequestDelegate(Expression responseWritingMethodCall, FactoryContext factoryContext)
{
if (factoryContext.RequestBodyMode is RequestBodyMode.AsJson)
if (factoryContext.JsonRequestBodyType is null)
{
// We need to generate the code for reading from the body before calling into the delegate
var invoker = Expression.Lambda<Func<object?, HttpContext, object?, Task>>(
responseWritingMethodCall, TargetExpr, HttpContextExpr, BodyValueExpr).Compile();

var bodyType = factoryContext.JsonRequestBodyType!;
object? defaultBodyValue = null;

if (factoryContext.AllowEmptyRequestBody && bodyType.IsValueType)
{
defaultBodyValue = Activator.CreateInstance(bodyType);
}
return Expression.Lambda<Func<object?, HttpContext, Task>>(
responseWritingMethodCall, TargetExpr, HttpContextExpr).Compile();
}

return async (target, httpContext) =>
{
object? bodyValue;
// We need to generate the code for reading from the body before calling into the delegate
var invoker = Expression.Lambda<Func<object?, HttpContext, object?, Task>>(
responseWritingMethodCall, TargetExpr, HttpContextExpr, BodyValueExpr).Compile();

if (factoryContext.AllowEmptyRequestBody && httpContext.Request.ContentLength == 0)
{
bodyValue = defaultBodyValue;
}
else
{
try
{
bodyValue = await httpContext.Request.ReadFromJsonAsync(bodyType);
}
catch (IOException ex)
{
Log.RequestBodyIOException(httpContext, ex);
return;
}
catch (InvalidDataException ex)
{
Log.RequestBodyInvalidDataException(httpContext, ex);
httpContext.Response.StatusCode = 400;
return;
}
}
var bodyType = factoryContext.JsonRequestBodyType!;
object? defaultBodyValue = null;

await invoker(target, httpContext, bodyValue);
};
if (factoryContext.AllowEmptyRequestBody && bodyType.IsValueType)
{
defaultBodyValue = Activator.CreateInstance(bodyType);
}
else if (factoryContext.RequestBodyMode is RequestBodyMode.AsForm)

return async (target, httpContext) =>
{
var invoker = Expression.Lambda<Func<object?, HttpContext, Task>>(
responseWritingMethodCall, TargetExpr, HttpContextExpr).Compile();
object? bodyValue;

return async (target, httpContext) =>
if (factoryContext.AllowEmptyRequestBody && httpContext.Request.ContentLength == 0)
{
bodyValue = defaultBodyValue;
}
else
{
// Generating async code would just be insane so if the method needs the form populate it here
// so the within the method it's cached
try
{
await httpContext.Request.ReadFormAsync();
bodyValue = await httpContext.Request.ReadFromJsonAsync(bodyType);
}
catch (IOException ex)
{
Log.RequestBodyIOException(httpContext, ex);
httpContext.Abort();
return;
}
catch (InvalidDataException ex)
Expand All @@ -515,15 +461,10 @@ private static Expression AddResponseWritingToMethodCall(Expression methodCall,
httpContext.Response.StatusCode = 400;
return;
}
}

await invoker(target, httpContext);
};
}
else
{
return Expression.Lambda<Func<object?, HttpContext, Task>>(
responseWritingMethodCall, TargetExpr, HttpContextExpr).Compile();
}
await invoker(target, httpContext, bodyValue);
};
}

private static MethodInfo GetEnumTryParseMethod()
Expand Down Expand Up @@ -793,22 +734,8 @@ private static async Task ExecuteTaskResult<T>(Task<T> task, HttpContext httpCon
await (await task).ExecuteAsync(httpContext);
}

[StackTraceHidden]
private static void ThrowCannotReadBodyDirectlyAndAsForm()
{
throw new InvalidOperationException("Action cannot mix FromBody and FromForm on the same method.");
}

private enum RequestBodyMode
{
None,
AsJson,
AsForm,
}

private class FactoryContext
{
public RequestBodyMode RequestBodyMode { get; set; }
public Type? JsonRequestBodyType { get; set; }
public bool AllowEmptyRequestBody { get; set; }

Expand Down
Loading