From 9e15ee2e4dd484c683549aea16f350172d3a6de8 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Wed, 15 Feb 2023 19:26:06 -0800 Subject: [PATCH 1/7] Expand FromX support and add route vs. query param inference --- .../gen/RequestDelegateGenerator.cs | 2 +- .../gen/RequestDelegateGeneratorSources.cs | 7 + .../Emitters/EndpointEmitter.cs | 10 +- .../Emitters/EndpointParameterEmitter.cs | 80 +- .../gen/StaticRouteHandlerModel/Endpoint.cs | 4 +- .../EndpointParameter.cs | 59 +- ...Param_ComplexReturn_Snapshot.generated.txt | 37 +- ...eParam_SimpleReturn_Snapshot.generated.txt | 7 + ...Source_SimpleReturn_Snapshot.generated.txt | 1804 +++++++++++++++++ ...pecialTypeParam_StringReturn.generated.txt | 7 + ...ipleStringParam_StringReturn.generated.txt | 7 + ...aram_StringReturn_WithFilter.generated.txt | 7 + ...ngValueProvided_StringReturn.generated.txt | 7 + ...ngValueProvided_StringReturn.generated.txt | 7 + ...ngValueProvided_StringReturn.generated.txt | 7 + ...ngleStringParam_StringReturn.generated.txt | 7 + ...pAction_NoParam_StringReturn.generated.txt | 7 + ...tion_WithParams_StringReturn.generated.txt | 7 + .../RequestDelegateGeneratorTestBase.cs | 26 - .../RequestDelegateGeneratorTests.cs | 388 +++- .../RequestDelegateGenerator/SharedTypes.cs | 21 + src/Shared/RoslynUtils/SymbolExtensions.cs | 21 + 22 files changed, 2363 insertions(+), 166 deletions(-) create mode 100644 src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt diff --git a/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs b/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs index 99b337a3df8e..9d7f2058c1d0 100644 --- a/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs +++ b/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs @@ -120,7 +120,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) lineNumber); } """); - } + } return code.ToString(); }); diff --git a/src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs b/src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs index 2f7d8116a90f..b1dec81715ac 100644 --- a/src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs +++ b/src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs @@ -134,6 +134,13 @@ private static Task ExecuteObjectResult(object? obj, HttpContext httpContext) } return (false, default); } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } } {{GeneratedCodeAttribute}} diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs index 88da446fc7d0..1e4599affa1c 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs @@ -21,8 +21,14 @@ internal static string EmitParameterPreparation(this Endpoint endpoint) Source: EndpointParameterSource.SpecialType } => parameter.EmitSpecialParameterPreparation(), { - Source: EndpointParameterSource.Query, - } => parameter.EmitQueryParameterPreparation(), + Source: EndpointParameterSource.Query or EndpointParameterSource.Header, + } => parameter.EmitQueryOrHeaderParameterPreparation(), + { + Source: EndpointParameterSource.Route, + } => parameter.EmitRouteParameterPreparation(), + { + Source: EndpointParameterSource.RouteOrQuery + } => parameter.EmitRouteOrQueryParameterPreparation(), { Source: EndpointParameterSource.JsonBody } => parameter.EmitJsonBodyParameterPreparationString(), diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs index b8a4ce26719f..02894cfcac09 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs @@ -9,11 +9,11 @@ internal static class EndpointParameterEmitter internal static string EmitSpecialParameterPreparation(this EndpointParameter endpointParameter) { return $""" - var {endpointParameter.Name}_local = {endpointParameter.AssigningCode}; + var {endpointParameter.HandlerArgument} = {endpointParameter.AssigningCode}; """; } - internal static string EmitQueryParameterPreparation(this EndpointParameter endpointParameter) + internal static string EmitQueryOrHeaderParameterPreparation(this EndpointParameter endpointParameter) { var builder = new StringBuilder(); @@ -24,7 +24,7 @@ internal static string EmitQueryParameterPreparation(this EndpointParameter endp // Grab raw input from HttpContext. builder.AppendLine($$""" - var {{endpointParameter.Name}}_raw = {{endpointParameter.AssigningCode}}; + var {{endpointParameter.AssigningCodeResult}} = {{endpointParameter.AssigningCode}}; """); // If we are not optional, then at this point we can just assign the string value to the handler argument, @@ -34,23 +34,89 @@ internal static string EmitQueryParameterPreparation(this EndpointParameter endp if (endpointParameter.IsOptional) { builder.AppendLine($$""" - var {{endpointParameter.HandlerArgument}} = {{endpointParameter.Name}}_raw.Count > 0 ? {{endpointParameter.Name}}_raw.ToString() : null; + var {{endpointParameter.HandlerArgument}} = {{endpointParameter.AssigningCodeResult}}.Count > 0 ? {{endpointParameter.AssigningCodeResult}}.ToString() : null; """); } else { builder.AppendLine($$""" - if (StringValues.IsNullOrEmpty({{endpointParameter.Name}}_raw)) + if (StringValues.IsNullOrEmpty({{endpointParameter.AssigningCodeResult}})) { wasParamCheckFailure = true; } - var {{endpointParameter.HandlerArgument}} = {{endpointParameter.Name}}_raw.ToString(); + var {{endpointParameter.HandlerArgument}} = {{endpointParameter.AssigningCodeResult}}.ToString(); """); } return builder.ToString(); } + internal static string EmitRouteParameterPreparation(this EndpointParameter endpointParameter) + { + var builder = new StringBuilder(); + + builder.AppendLine($""" + {endpointParameter.EmitParameterDiagnosticComment()} +"""); + + // Throw an exception of if the route parameter name that was specific in the `FromRoute` + // attribute or in the parameter name does not appear in the actual route. + builder.AppendLine($$""" + if (options?.RouteParameterNames?.Contains("{{endpointParameter.Name}}", StringComparer.OrdinalIgnoreCase) != true) + { + throw new InvalidOperationException($"'{{endpointParameter.Name}}' is not a route parameter."); + } +"""); + + builder.AppendLine($$""" + var {{endpointParameter.AssigningCodeResult}} = {{endpointParameter.AssigningCode}}; +"""); + + if (!endpointParameter.IsOptional) + { + builder.AppendLine($$""" + if ({{endpointParameter.AssigningCodeResult}} == null) + { + wasParamCheckFailure = true; + } +"""); + } + builder.AppendLine($""" + var {endpointParameter.HandlerArgument} = {endpointParameter.AssigningCodeResult}; +"""); + + return builder.ToString(); + } + + internal static string EmitRouteOrQueryParameterPreparation(this EndpointParameter endpointParameter) + { + var builder = new StringBuilder(); + + builder.AppendLine($""" + {endpointParameter.EmitParameterDiagnosticComment()} +"""); + + builder.AppendLine($$""" + var {{endpointParameter.AssigningCodeResult}} = {{endpointParameter.AssigningCode}}; +"""); + + if (!endpointParameter.IsOptional) + { + builder.AppendLine($$""" + if ({{endpointParameter.AssigningCodeResult}} is StringValues { Count: 0 }) + { + wasParamCheckFailure = true; + } +"""); + } + + builder.AppendLine($""" + var {endpointParameter.HandlerArgument} = {endpointParameter.AssigningCodeResult}; +"""); + + return builder.ToString(); + } + internal static string EmitJsonBodyParameterPreparationString(this EndpointParameter endpointParameter) { var builder = new StringBuilder(); @@ -62,7 +128,7 @@ internal static string EmitJsonBodyParameterPreparationString(this EndpointParam // Grab raw input from HttpContext. builder.AppendLine($$""" - var (isSuccessful, {{endpointParameter.Name}}_local) = {{endpointParameter.AssigningCode}}; + var (isSuccessful, {{endpointParameter.HandlerArgument}}) = {{endpointParameter.AssigningCode}}; """); // If binding from the JSON body fails, we exit early. Don't diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs index f1268465675c..49ae1ef12325 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs @@ -91,7 +91,7 @@ public static bool SignatureEquals(Endpoint a, Endpoint b) for (var i = 0; i < a.Parameters.Length; i++) { - if (!a.Parameters[i].Equals(b.Parameters[i])) + if (!a.Parameters[i].SignatureEquals(b.Parameters[i])) { return false; } @@ -108,7 +108,7 @@ public static int GetSignatureHashCode(Endpoint endpoint) foreach (var parameter in endpoint.Parameters) { - hashCode.Add(parameter); + hashCode.Add(parameter.Type, SymbolEqualityComparer.Default); } return hashCode.ToHashCode(); diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs index a9769681aca8..fe46f30ab0b9 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs @@ -18,18 +18,39 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp Name = parameter.Name; Source = EndpointParameterSource.Unknown; HandlerArgument = $"{parameter.Name}_local"; + AssigningCodeResult = $"{parameter.Name}_raw"; var fromQueryMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromQueryMetadata); var fromServiceMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata); + var fromRouteMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata); + var fromHeaderMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromHeaderMetadata); - if (parameter.HasAttributeImplementingInterface(fromQueryMetadataInterfaceType)) + if (parameter.HasAttributeImplementingInterface(fromRouteMetadataInterfaceType, out var fromRouteAttribute)) + { + Source = EndpointParameterSource.Route; + Name = fromRouteAttribute.TryGetNamedArgumentValue("Name", out var fromRouteName) + ? fromRouteName + : parameter.Name; + AssigningCode = $"httpContext.Request.RouteValues[\"{Name}\"]?.ToString()"; + IsOptional = parameter.IsOptional(); + } + else if (parameter.HasAttributeImplementingInterface(fromQueryMetadataInterfaceType, out var fromQueryAttribute)) { Source = EndpointParameterSource.Query; - AssigningCode = $"httpContext.Request.Query[\"{parameter.Name}\"]"; - IsOptional = parameter.Type is INamedTypeSymbol - { - NullableAnnotation: NullableAnnotation.Annotated - }; + Name = fromQueryAttribute.TryGetNamedArgumentValue("Name", out var fromQueryName) + ? fromQueryName + : parameter.Name; + AssigningCode = $"httpContext.Request.Query[\"{Name}\"]"; + IsOptional = parameter.IsOptional(); + } + else if (parameter.HasAttributeImplementingInterface(fromHeaderMetadataInterfaceType, out var fromHeaderAttribute)) + { + Source = EndpointParameterSource.Header; + Name = fromHeaderAttribute.TryGetNamedArgumentValue("Name", out var fromHeaderName) + ? fromHeaderName + : parameter.Name; + AssigningCode = $"httpContext.Request.Headers[\"{Name}\"]"; + IsOptional = parameter.IsOptional(); } else if (TryGetExplicitFromJsonBody(parameter, wellKnownTypes, out var jsonBodyAssigningCode, out var isOptional)) { @@ -50,6 +71,12 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp Source = EndpointParameterSource.SpecialType; AssigningCode = specialTypeAssigningCode; } + else if (parameter.Type.SpecialType == SpecialType.System_String) + { + Source = EndpointParameterSource.RouteOrQuery; + AssigningCode = $"GeneratedRouteBuilderExtensionsCore.ResolveFromRouteOrQuery(httpContext, \"{parameter.Name}\", options?.RouteParameterNames)"; + IsOptional = parameter.IsOptional(); + } else { // TODO: Inferencing rules go here - but for now: @@ -64,13 +91,15 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp public string Name { get; } public string? AssigningCode { get; } public string HandlerArgument { get; } + + public string AssigningCodeResult { get; } public bool IsOptional { get; } public string EmitArgument() => Source switch { - EndpointParameterSource.SpecialType or EndpointParameterSource.Query or EndpointParameterSource.Service => HandlerArgument, - EndpointParameterSource.JsonBody => IsOptional ? HandlerArgument : $"{HandlerArgument}!", - _ => throw new Exception("Unreachable!") + EndpointParameterSource.JsonBody or EndpointParameterSource.Route or EndpointParameterSource.RouteOrQuery => IsOptional ? HandlerArgument : $"{HandlerArgument}!", + EndpointParameterSource.Unknown => throw new Exception("Unreachable!"), + _ => HandlerArgument }; // TODO: Handle special form types like IFormFileCollection that need special body-reading logic. @@ -125,13 +154,7 @@ private static bool TryGetExplicitFromJsonBody(IParameterSymbol parameter, isOptional = false; if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromBodyMetadata), out var fromBodyAttribute)) { - foreach (var namedArgument in fromBodyAttribute.NamedArguments) - { - if (namedArgument.Key == "AllowEmpty") - { - isOptional |= namedArgument.Value.Value is true; - } - } + isOptional |= fromBodyAttribute.TryGetNamedArgumentValue("AllowEmpty", out var allowEmptyValue) && allowEmptyValue; isOptional |= (parameter.NullableAnnotation == NullableAnnotation.Annotated || parameter.HasExplicitDefaultValue); assigningCode = $"await GeneratedRouteBuilderExtensionsCore.TryResolveBody<{parameter.Type}>(httpContext, {(isOptional ? "true" : "false")})"; return true; @@ -145,6 +168,10 @@ obj is EndpointParameter other && other.Name == Name && SymbolEqualityComparer.Default.Equals(other.Type, Type); + public bool SignatureEquals(object obj) => + obj is EndpointParameter other && + SymbolEqualityComparer.Default.Equals(other.Type, Type); + public override int GetHashCode() { var hashCode = new HashCode(); diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt index 8d6be6f1194c..0742119b9fb1 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Builder internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapPost( this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern, - global::System.Func> handler, + global::System.Func> handler, [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "", [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0) { @@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Http.Generated }, (del, options, inferredMetadataResult) => { - var handler = (Func>)del; + var handler = (Func>)del; EndpointFilterDelegate? filteredInvocation = null; if (options?.EndpointBuilder?.FilterFactories.Count > 0) @@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0))); + return ValueTask.FromResult(handler(ic.GetArgument(0))); }, options.EndpointBuilder, handler.Method); @@ -113,8 +113,8 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: todo (Type = TestMapActions.Todo, IsOptional = False, Source = JsonBody) - var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, false); + // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, Source = JsonBody) + var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, false); if (!isSuccessful) { return; @@ -132,8 +132,8 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: todo (Type = TestMapActions.Todo, IsOptional = False, Source = JsonBody) - var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, false); + // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, Source = JsonBody) + var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, false); if (!isSuccessful) { return; @@ -143,7 +143,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, todo_local!)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, todo_local!)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -160,7 +160,7 @@ namespace Microsoft.AspNetCore.Http.Generated }, (del, options, inferredMetadataResult) => { - var handler = (Func>)del; + var handler = (Func>)del; EndpointFilterDelegate? filteredInvocation = null; if (options?.EndpointBuilder?.FilterFactories.Count > 0) @@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0))); + return ValueTask.FromResult(handler(ic.GetArgument(0))); }, options.EndpointBuilder, handler.Method); @@ -180,8 +180,8 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: todo (Type = TestMapActions.Todo?, IsOptional = True, Source = JsonBody) - var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, true); + // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo?, IsOptional = True, Source = JsonBody) + var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, true); if (!isSuccessful) { return; @@ -199,8 +199,8 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: todo (Type = TestMapActions.Todo?, IsOptional = True, Source = JsonBody) - var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, true); + // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo?, IsOptional = True, Source = JsonBody) + var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, true); if (!isSuccessful) { return; @@ -210,7 +210,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, todo_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, todo_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -299,6 +299,13 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt index 5b8cb702c274..183f05f046aa 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt @@ -378,6 +378,13 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt new file mode 100644 index 000000000000..4c264b94fbbd --- /dev/null +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt @@ -0,0 +1,1804 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable + +namespace Microsoft.AspNetCore.Builder +{ + %GENERATEDCODEATTRIBUTE% + internal class SourceKey + { + public string Path { get; init; } + public int Line { get; init; } + + public SourceKey(string path, int line) + { + Path = path; + Line = line; + } + } + + // This class needs to be internal so that the compiled application + // has access to the strongly-typed endpoint definitions that are + // generated by the compiler so that they will be favored by + // overload resolution and opt the runtime in to the code generated + // implementation produced here. + %GENERATEDCODEATTRIBUTE% + internal static class GenerateRouteBuilderEndpoints + { + private static readonly string[] GetVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Get }; + private static readonly string[] PostVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Post }; + private static readonly string[] PutVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Put }; + private static readonly string[] DeleteVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Delete }; + private static readonly string[] PatchVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Patch }; + + internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet( + this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, + [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern, + global::System.Func handler, + [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "", + [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0) + { + return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore( + endpoints, + pattern, + handler, + GetVerb, + filePath, + lineNumber); + } + + } +} + +namespace Microsoft.AspNetCore.Http.Generated +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using System.IO; + using Microsoft.AspNetCore.Routing; + using Microsoft.AspNetCore.Routing.Patterns; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Metadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.FileProviders; + using Microsoft.Extensions.Primitives; + + using MetadataPopulator = System.Func; + using RequestDelegateFactoryFunc = System.Func; + + file static class GeneratedRouteBuilderExtensionsCore + { + + private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() + { + [(@"TestMapActions.cs", 16)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + + if (options?.EndpointBuilder?.FilterFactories.Count > 0) + { + filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic => + { + if (ic.HttpContext.Response.StatusCode == 400) + { + return ValueTask.FromResult(Results.Empty); + } + return ValueTask.FromResult(handler(ic.GetArgument(0))); + }, + options.EndpointBuilder, + handler.Method); + } + + Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + // Endpoint Parameter: queryValue (Type = string?, IsOptional = True, Source = Query) + var queryValue_raw = httpContext.Request.Query["queryValue"]; + var queryValue_local = queryValue_raw.Count > 0 ? queryValue_raw.ToString() : null; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return Task.CompletedTask; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(queryValue_local); + return httpContext.Response.WriteAsync(result); + } + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + // Endpoint Parameter: queryValue (Type = string?, IsOptional = True, Source = Query) + var queryValue_raw = httpContext.Request.Query["queryValue"]; + var queryValue_local = queryValue_raw.Count > 0 ? queryValue_raw.ToString() : null; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, queryValue_local)); + await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); + } + + RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered; + var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; + return new RequestDelegateResult(targetDelegate, metadata); + }), + [(@"TestMapActions.cs", 17)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 17)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + + if (options?.EndpointBuilder?.FilterFactories.Count > 0) + { + filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic => + { + if (ic.HttpContext.Response.StatusCode == 400) + { + return ValueTask.FromResult(Results.Empty); + } + return ValueTask.FromResult(handler(ic.GetArgument(0))); + }, + options.EndpointBuilder, + handler.Method); + } + + Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + // Endpoint Parameter: headerValue (Type = string?, IsOptional = True, Source = Header) + var headerValue_raw = httpContext.Request.Headers["headerValue"]; + var headerValue_local = headerValue_raw.Count > 0 ? headerValue_raw.ToString() : null; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return Task.CompletedTask; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(headerValue_local); + return httpContext.Response.WriteAsync(result); + } + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + // Endpoint Parameter: headerValue (Type = string?, IsOptional = True, Source = Header) + var headerValue_raw = httpContext.Request.Headers["headerValue"]; + var headerValue_local = headerValue_raw.Count > 0 ? headerValue_raw.ToString() : null; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, headerValue_local)); + await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); + } + + RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered; + var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; + return new RequestDelegateResult(targetDelegate, metadata); + }), + [(@"TestMapActions.cs", 18)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + + if (options?.EndpointBuilder?.FilterFactories.Count > 0) + { + filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic => + { + if (ic.HttpContext.Response.StatusCode == 400) + { + return ValueTask.FromResult(Results.Empty); + } + return ValueTask.FromResult(handler(ic.GetArgument(0))); + }, + options.EndpointBuilder, + handler.Method); + } + + Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + // Endpoint Parameter: routeValue (Type = string?, IsOptional = True, Source = Route) + if (options?.RouteParameterNames?.Contains("routeValue", StringComparer.OrdinalIgnoreCase) != true) + { + throw new InvalidOperationException($"'routeValue' is not a route parameter."); + } + var routeValue_raw = httpContext.Request.RouteValues["routeValue"]?.ToString(); + var routeValue_local = routeValue_raw; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return Task.CompletedTask; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(routeValue_local); + return httpContext.Response.WriteAsync(result); + } + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + // Endpoint Parameter: routeValue (Type = string?, IsOptional = True, Source = Route) + if (options?.RouteParameterNames?.Contains("routeValue", StringComparer.OrdinalIgnoreCase) != true) + { + throw new InvalidOperationException($"'routeValue' is not a route parameter."); + } + var routeValue_raw = httpContext.Request.RouteValues["routeValue"]?.ToString(); + var routeValue_local = routeValue_raw; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, routeValue_local)); + await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); + } + + RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered; + var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; + return new RequestDelegateResult(targetDelegate, metadata); + }), + [(@"TestMapActions.cs", 19)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 19)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + + if (options?.EndpointBuilder?.FilterFactories.Count > 0) + { + filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic => + { + if (ic.HttpContext.Response.StatusCode == 400) + { + return ValueTask.FromResult(Results.Empty); + } + return ValueTask.FromResult(handler(ic.GetArgument(0))); + }, + options.EndpointBuilder, + handler.Method); + } + + Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + // Endpoint Parameter: value (Type = string?, IsOptional = True, Source = RouteOrQuery) + var value_raw = GeneratedRouteBuilderExtensionsCore.ResolveFromRouteOrQuery(httpContext, "value", options?.RouteParameterNames); + var value_local = value_raw; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return Task.CompletedTask; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(value_local); + return httpContext.Response.WriteAsync(result); + } + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + // Endpoint Parameter: value (Type = string?, IsOptional = True, Source = RouteOrQuery) + var value_raw = GeneratedRouteBuilderExtensionsCore.ResolveFromRouteOrQuery(httpContext, "value", options?.RouteParameterNames); + var value_local = value_raw; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, value_local)); + await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); + } + + RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered; + var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; + return new RequestDelegateResult(targetDelegate, metadata); + }), + + }; + + internal static RouteHandlerBuilder MapCore( + this IEndpointRouteBuilder routes, + string pattern, + Delegate handler, + IEnumerable httpMethods, + string filePath, + int lineNumber) + { + var (populateMetadata, createRequestDelegate) = map[(filePath, lineNumber)]; + return RouteHandlerServices.Map(routes, pattern, handler, httpMethods, populateMetadata, createRequestDelegate); + } + + private static EndpointFilterDelegate BuildFilterDelegate(EndpointFilterDelegate filteredInvocation, EndpointBuilder builder, MethodInfo mi) + { + var routeHandlerFilters = builder.FilterFactories; + var context0 = new EndpointFilterFactoryContext + { + MethodInfo = mi, + ApplicationServices = builder.ApplicationServices, + }; + var initialFilteredInvocation = filteredInvocation; + for (var i = routeHandlerFilters.Count - 1; i >= 0; i--) + { + var filterFactory = routeHandlerFilters[i]; + filteredInvocation = filterFactory(context0, filteredInvocation); + } + return filteredInvocation; + } + + private static Task ExecuteObjectResult(object? obj, HttpContext httpContext) + { + if (obj is IResult r) + { + return r.ExecuteAsync(httpContext); + } + else if (obj is string s) + { + return httpContext.Response.WriteAsync(s); + } + else + { + return httpContext.Response.WriteAsJsonAsync(obj); + } + } + + private static async ValueTask<(bool, T?)> TryResolveBody(HttpContext httpContext, bool allowEmpty) + { + var feature = httpContext.Features.Get(); + + if (feature?.CanHaveBody == true) + { + if (!httpContext.Request.HasJsonContentType()) + { + httpContext.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType; + return (false, default); + } + try + { + var bodyValue = await httpContext.Request.ReadFromJsonAsync(); + if (!allowEmpty && bodyValue == null) + { + httpContext.Response.StatusCode = StatusCodes.Status400BadRequest; + return (false, bodyValue); + } + return (true, bodyValue); + } + catch (IOException) + { + return (false, default); + } + catch (System.Text.Json.JsonException) + { + httpContext.Response.StatusCode = StatusCodes.Status400BadRequest; + return (false, default); + } + } + return (false, default); + } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } + } + + %GENERATEDCODEATTRIBUTE% + file class EndpointFilterInvocationContext : EndpointFilterInvocationContext, IList + { + internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0) + { + HttpContext = httpContext; + Arg0 = arg0; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + + public int Count => 1; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + %GENERATEDCODEATTRIBUTE% + file class EndpointFilterInvocationContext : EndpointFilterInvocationContext, IList + { + internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + + public int Count => 2; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + %GENERATEDCODEATTRIBUTE% + file class EndpointFilterInvocationContext : EndpointFilterInvocationContext, IList + { + internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + + public int Count => 3; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + %GENERATEDCODEATTRIBUTE% + file class EndpointFilterInvocationContext : EndpointFilterInvocationContext, IList + { + internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + + public int Count => 4; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + %GENERATEDCODEATTRIBUTE% + file class EndpointFilterInvocationContext : EndpointFilterInvocationContext, IList + { + internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + Arg4 = arg4; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + 4 => Arg4, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + case 4: + Arg4 = (T4)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + public T4 Arg4 { get; set; } + + public int Count => 5; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + 4 => (T)(object)Arg4!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + %GENERATEDCODEATTRIBUTE% + file class EndpointFilterInvocationContext : EndpointFilterInvocationContext, IList + { + internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + Arg4 = arg4; + Arg5 = arg5; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + 4 => Arg4, + 5 => Arg5, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + case 4: + Arg4 = (T4)(object?)value!; + break; + case 5: + Arg5 = (T5)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + public T4 Arg4 { get; set; } + public T5 Arg5 { get; set; } + + public int Count => 6; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + 4 => (T)(object)Arg4!, + 5 => (T)(object)Arg5!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + %GENERATEDCODEATTRIBUTE% + file class EndpointFilterInvocationContext : EndpointFilterInvocationContext, IList + { + internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + Arg4 = arg4; + Arg5 = arg5; + Arg6 = arg6; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + 4 => Arg4, + 5 => Arg5, + 6 => Arg6, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + case 4: + Arg4 = (T4)(object?)value!; + break; + case 5: + Arg5 = (T5)(object?)value!; + break; + case 6: + Arg6 = (T6)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + public T4 Arg4 { get; set; } + public T5 Arg5 { get; set; } + public T6 Arg6 { get; set; } + + public int Count => 7; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + 4 => (T)(object)Arg4!, + 5 => (T)(object)Arg5!, + 6 => (T)(object)Arg6!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + %GENERATEDCODEATTRIBUTE% + file class EndpointFilterInvocationContext : EndpointFilterInvocationContext, IList + { + internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + Arg4 = arg4; + Arg5 = arg5; + Arg6 = arg6; + Arg7 = arg7; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + 4 => Arg4, + 5 => Arg5, + 6 => Arg6, + 7 => Arg7, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + case 4: + Arg4 = (T4)(object?)value!; + break; + case 5: + Arg5 = (T5)(object?)value!; + break; + case 6: + Arg6 = (T6)(object?)value!; + break; + case 7: + Arg7 = (T7)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + public T4 Arg4 { get; set; } + public T5 Arg5 { get; set; } + public T6 Arg6 { get; set; } + public T7 Arg7 { get; set; } + + public int Count => 8; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + 4 => (T)(object)Arg4!, + 5 => (T)(object)Arg5!, + 6 => (T)(object)Arg6!, + 7 => (T)(object)Arg7!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + %GENERATEDCODEATTRIBUTE% + file class EndpointFilterInvocationContext : EndpointFilterInvocationContext, IList + { + internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + Arg4 = arg4; + Arg5 = arg5; + Arg6 = arg6; + Arg7 = arg7; + Arg8 = arg8; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + 4 => Arg4, + 5 => Arg5, + 6 => Arg6, + 7 => Arg7, + 8 => Arg8, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + case 4: + Arg4 = (T4)(object?)value!; + break; + case 5: + Arg5 = (T5)(object?)value!; + break; + case 6: + Arg6 = (T6)(object?)value!; + break; + case 7: + Arg7 = (T7)(object?)value!; + break; + case 8: + Arg8 = (T8)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + public T4 Arg4 { get; set; } + public T5 Arg5 { get; set; } + public T6 Arg6 { get; set; } + public T7 Arg7 { get; set; } + public T8 Arg8 { get; set; } + + public int Count => 9; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + 4 => (T)(object)Arg4!, + 5 => (T)(object)Arg5!, + 6 => (T)(object)Arg6!, + 7 => (T)(object)Arg7!, + 8 => (T)(object)Arg8!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + %GENERATEDCODEATTRIBUTE% + file class EndpointFilterInvocationContext : EndpointFilterInvocationContext, IList + { + internal EndpointFilterInvocationContext(HttpContext httpContext, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5, T6 arg6, T7 arg7, T8 arg8, T9 arg9) + { + HttpContext = httpContext; + Arg0 = arg0; + Arg1 = arg1; + Arg2 = arg2; + Arg3 = arg3; + Arg4 = arg4; + Arg5 = arg5; + Arg6 = arg6; + Arg7 = arg7; + Arg8 = arg8; + Arg9 = arg9; + } + + public object? this[int index] + { + get => index switch + { + 0 => Arg0, + 1 => Arg1, + 2 => Arg2, + 3 => Arg3, + 4 => Arg4, + 5 => Arg5, + 6 => Arg6, + 7 => Arg7, + 8 => Arg8, + 9 => Arg9, + _ => new IndexOutOfRangeException() + }; + set + { + switch (index) + { + case 0: + Arg0 = (T0)(object?)value!; + break; + case 1: + Arg1 = (T1)(object?)value!; + break; + case 2: + Arg2 = (T2)(object?)value!; + break; + case 3: + Arg3 = (T3)(object?)value!; + break; + case 4: + Arg4 = (T4)(object?)value!; + break; + case 5: + Arg5 = (T5)(object?)value!; + break; + case 6: + Arg6 = (T6)(object?)value!; + break; + case 7: + Arg7 = (T7)(object?)value!; + break; + case 8: + Arg8 = (T8)(object?)value!; + break; + case 9: + Arg9 = (T9)(object?)value!; + break; + default: + break; + } + } + } + + public override HttpContext HttpContext { get; } + + public override IList Arguments => this; + + public T0 Arg0 { get; set; } + public T1 Arg1 { get; set; } + public T2 Arg2 { get; set; } + public T3 Arg3 { get; set; } + public T4 Arg4 { get; set; } + public T5 Arg5 { get; set; } + public T6 Arg6 { get; set; } + public T7 Arg7 { get; set; } + public T8 Arg8 { get; set; } + public T9 Arg9 { get; set; } + + public int Count => 10; + + public bool IsReadOnly => false; + + public bool IsFixedSize => true; + + public void Add(object? item) + { + throw new NotSupportedException(); + } + + public void Clear() + { + throw new NotSupportedException(); + } + + public bool Contains(object? item) + { + return IndexOf(item) >= 0; + } + + public void CopyTo(object?[] array, int arrayIndex) + { + for (int i = 0; i < Arguments.Count; i++) + { + array[arrayIndex++] = Arguments[i]; + } + } + + public IEnumerator GetEnumerator() + { + for (int i = 0; i < Arguments.Count; i++) + { + yield return Arguments[i]; + } + } + + public override T GetArgument(int index) + { + return index switch + { + 0 => (T)(object)Arg0!, + 1 => (T)(object)Arg1!, + 2 => (T)(object)Arg2!, + 3 => (T)(object)Arg3!, + 4 => (T)(object)Arg4!, + 5 => (T)(object)Arg5!, + 6 => (T)(object)Arg6!, + 7 => (T)(object)Arg7!, + 8 => (T)(object)Arg8!, + 9 => (T)(object)Arg9!, + _ => throw new IndexOutOfRangeException() + }; + } + + public int IndexOf(object? item) + { + return Arguments.IndexOf(item); + } + + public void Insert(int index, object? item) + { + throw new NotSupportedException(); + } + + public bool Remove(object? item) + { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) + { + throw new NotSupportedException(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } +} \ No newline at end of file diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt index acf588132686..9b44d2ff226f 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt @@ -222,6 +222,13 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt index d028b89c503d..51a769a098aa 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt @@ -250,6 +250,13 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt index c784a70f380c..bbf5cacea4db 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt @@ -220,6 +220,13 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt index b905c1d65cf7..0a88867dc913 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt @@ -226,6 +226,13 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn.generated.txt index b905c1d65cf7..0a88867dc913 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn.generated.txt @@ -226,6 +226,13 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn.generated.txt index b905c1d65cf7..0a88867dc913 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn.generated.txt @@ -226,6 +226,13 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleStringParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleStringParam_StringReturn.generated.txt index 7fc52c37eb9c..87e2f4a1c6ef 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleStringParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleStringParam_StringReturn.generated.txt @@ -234,6 +234,13 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt index 2f9f055e5a71..7b95b9f115d0 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt @@ -415,6 +415,13 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt index 814152da56dc..136f98d7b7da 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt @@ -362,6 +362,13 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) + : httpContext.Request.Query[$"{parameterName}"]; + } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs index b1533eb20ec1..5f6c77e714e3 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs @@ -204,25 +204,6 @@ public static IEndpointRouteBuilder MapTestEndpoints(this IEndpointRouteBuilder {{sources}} return app; } - - public interface ITodo - { - public int Id { get; } - public string? Name { get; } - public bool IsComplete { get; } - } - - public class Todo - { - public int Id { get; set; } - public string? Name { get; set; } = "Todo"; - public bool IsComplete { get; set; } - } - - public class FromBodyAttribute : Attribute, IFromBodyMetadata - { - public bool AllowEmpty { get; set; } - } } """; private static Task CreateCompilationAsync(string sources) @@ -374,13 +355,6 @@ public DefaultEndpointRouteBuilder(IApplicationBuilder applicationBuilder) public IServiceProvider ServiceProvider => ApplicationBuilder.ApplicationServices; } - public class Todo - { - public int Id { get; set; } - public string Name { get; set; } = "Todo"; - public bool IsComplete { get; set; } - } - internal sealed class RequestBodyDetectionFeature : IHttpRequestBodyDetectionFeature { public RequestBodyDetectionFeature(bool canHaveBody) diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs index 3341420e9706..31e9ae722952 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs @@ -1,10 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Collections; using System.Globalization; using System.Text.Json; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel; +using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Http.Generators.Tests; @@ -35,75 +37,58 @@ public async Task MapAction_NoParam_StringReturn(string source, string httpMetho await VerifyResponseBodyAsync(httpContext, expectedBody); } - [Fact] - public async Task MapAction_SingleStringParam_StringReturn() - { - var (results, compilation) = await RunGeneratorAsync(""" -app.MapGet("/hello", ([FromQuery]string p) => p); -"""); - - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); - var endpoint = GetEndpointFromCompilation(compilation); - - Assert.Equal("/hello", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - var p = Assert.Single(endpointModel.Parameters); - Assert.Equal(EndpointParameterSource.Query, p.Source); - Assert.Equal("p", p.Name); - - var httpContext = CreateHttpContext(); - httpContext.Request.QueryString = new QueryString("?p=Hello%20world!"); - - await endpoint.RequestDelegate(httpContext); - await VerifyResponseBodyAsync(httpContext, "Hello world!"); - await VerifyAgainstBaselineUsingFile(compilation); - } - - [Fact] - public async Task MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn() + public static object[][] MapAction_ExplicitQueryParam_StringReturn_Data { - var (results, compilation) = await RunGeneratorAsync(""" -app.MapGet("/hello", ([FromQuery]string? p) => p ?? "Error!"); -"""); - - var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); - var endpoint = GetEndpointFromCompilation(compilation); - - Assert.Equal("/hello", endpointModel.RoutePattern); - Assert.Equal("MapGet", endpointModel.HttpMethod); - var p = Assert.Single(endpointModel.Parameters); - Assert.Equal(EndpointParameterSource.Query, p.Source); - Assert.Equal("p", p.Name); - - var httpContext = CreateHttpContext(); - httpContext.Request.QueryString = new QueryString("?p=Hello%20world!"); + get + { + var expectedBody = "TestQueryValue"; + var fromQueryRequiredSource = $"""app.MapGet("/", ([{typeof(FromQueryAttribute)}] string queryValue) => queryValue);"""; + var fromQueryWithNameRequiredSource = $"""app.MapGet("/", ([{typeof(FromQueryAttribute)}(Name = "queryValue")] string parameterName) => parameterName);"""; + var fromQueryNullableSource = $"""app.MapGet("/", ([{typeof(FromQueryAttribute)}] string? queryValue) => queryValue ?? string.Empty);"""; + var fromQueryDefaultValueSource = $""" +#nullable disable +string getQueryWithDefault([{typeof(FromQueryAttribute)}] string queryValue = null) => queryValue ?? string.Empty; +app.MapGet("/", getQueryWithDefault); +#nullable restore +"""; - await endpoint.RequestDelegate(httpContext); - await VerifyResponseBodyAsync(httpContext, "Hello world!"); - await VerifyAgainstBaselineUsingFile(compilation); + return new[] + { + new object[] { fromQueryRequiredSource, expectedBody, 200, expectedBody }, + new object[] { fromQueryRequiredSource, null, 400, string.Empty }, + new object[] { fromQueryWithNameRequiredSource, expectedBody, 200, expectedBody }, + new object[] { fromQueryWithNameRequiredSource, null, 400, string.Empty }, + new object[] { fromQueryNullableSource, expectedBody, 200, expectedBody }, + new object[] { fromQueryNullableSource, null, 200, string.Empty }, + new object[] { fromQueryDefaultValueSource, expectedBody, 200, expectedBody }, + new object[] { fromQueryDefaultValueSource, null, 200, string.Empty }, + }; + } } - [Fact] - public async Task MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn() + [Theory] + [MemberData(nameof(MapAction_ExplicitQueryParam_StringReturn_Data))] + public async Task MapAction_ExplicitQueryParam_StringReturn(string source, string queryValue, int expectedStatusCode, string expectedBody) { - var (results, compilation) = await RunGeneratorAsync(""" -app.MapGet("/hello", ([FromQuery]string? p) => p ?? "Was null!"); -"""); + var (results, compilation) = await RunGeneratorAsync(source); var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); var endpoint = GetEndpointFromCompilation(compilation); - Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("/", endpointModel.RoutePattern); Assert.Equal("MapGet", endpointModel.HttpMethod); var p = Assert.Single(endpointModel.Parameters); Assert.Equal(EndpointParameterSource.Query, p.Source); - Assert.Equal("p", p.Name); + Assert.Equal("queryValue", p.Name); var httpContext = CreateHttpContext(); + if (queryValue is not null) + { + httpContext.Request.QueryString = new QueryString($"?queryValue={queryValue}"); + } await endpoint.RequestDelegate(httpContext); - await VerifyResponseBodyAsync(httpContext, "Was null!"); - await VerifyAgainstBaselineUsingFile(compilation); + await VerifyResponseBodyAsync(httpContext, expectedBody, expectedStatusCode); } [Fact] @@ -291,13 +276,18 @@ public async Task MapAction_NoParam_AnyReturn(string source, string expectedBody await VerifyResponseBodyAsync(httpContext, expectedBody); } - [Theory] - [InlineData(@"app.MapGet(""/"", () => new Todo() { Name = ""Test Item""});")] - [InlineData(""" -object GetTodo() => new Todo() { Name = "Test Item"}; + public static IEnumerable MapAction_NoParam_ComplexReturn_Data => new List() + { + new object[] { $$"""app.MapGet("/", () => new {{typeof(Todo)}}() { Name = "Test Item"});""" }, + new object[] { $$""" +object GetTodo() => new {{typeof(Todo)}}() { Name = "Test Item"}; app.MapGet("/", GetTodo); -""")] - [InlineData(@"app.MapGet(""/"", () => TypedResults.Ok(new Todo() { Name = ""Test Item""}));")] +"""}, + new object[] { $$"""app.MapGet("/", () => TypedResults.Ok(new {{typeof(Todo)}}() { Name = "Test Item"}));""" } + }; + + [Theory] + [MemberData(nameof(MapAction_NoParam_ComplexReturn_Data))] public async Task MapAction_NoParam_ComplexReturn(string source) { var expectedBody = """{"id":0,"name":"Test Item","isComplete":false}"""; @@ -314,12 +304,17 @@ public async Task MapAction_NoParam_ComplexReturn(string source) await VerifyResponseBodyAsync(httpContext, expectedBody); } + public static IEnumerable MapAction_ProducesCorrectContentType_Data => new List() + { + new object[] { @"app.MapGet(""/"", () => Console.WriteLine(""Returns void""));", null }, + new object[] { @"app.MapGet(""/"", () => TypedResults.Ok(""Alright!""));", null }, + new object[] { @"app.MapGet(""/"", () => Results.NotFound(""Oops!""));", null }, + new object[] { $$"""app.MapGet("/", () => Task.FromResult(new {{typeof(Todo)}}() { Name = "Test Item"}));""", "application/json" }, + new object[] { @"app.MapGet(""/"", () => ""Hello world!"");", "text/plain" } + }; + [Theory] - [InlineData(@"app.MapGet(""/"", () => Console.WriteLine(""Returns void""));", null)] - [InlineData(@"app.MapGet(""/"", () => TypedResults.Ok(""Alright!""));", null)] - [InlineData(@"app.MapGet(""/"", () => Results.NotFound(""Oops!""));", null)] - [InlineData(@"app.MapGet(""/"", () => Task.FromResult(new Todo() { Name = ""Test Item""}));", "application/json")] - [InlineData(@"app.MapGet(""/"", () => ""Hello world!"");", "text/plain")] + [MemberData(nameof(MapAction_ProducesCorrectContentType_Data))] public async Task MapAction_ProducesCorrectContentType(string source, string expectedContentType) { var (result, compilation) = await RunGeneratorAsync(source); @@ -331,10 +326,15 @@ public async Task MapAction_ProducesCorrectContentType(string source, string exp Assert.Equal(expectedContentType, endpointModel.Response.ContentType); } + public static IEnumerable MapAction_NoParam_TaskOfTReturn_Data => new List() + { + new object[] { @"app.MapGet(""/"", () => Task.FromResult(""Hello world!""));", "Hello world!" }, + new object[] { $$"""app.MapGet("/", () => Task.FromResult(new {{typeof(Todo)}}() { Name = "Test Item"}));""", """{"id":0,"name":"Test Item","isComplete":false}""" }, + new object[] { $$"""app.MapGet("/", () => Task.FromResult(TypedResults.Ok(new {{typeof(Todo)}}() { Name = "Test Item"})));""", """{"id":0,"name":"Test Item","isComplete":false}""" } + }; + [Theory] - [InlineData(@"app.MapGet(""/"", () => Task.FromResult(""Hello world!""));", "Hello world!")] - [InlineData(@"app.MapGet(""/"", () => Task.FromResult(new Todo() { Name = ""Test Item""}));", """{"id":0,"name":"Test Item","isComplete":false}""")] - [InlineData(@"app.MapGet(""/"", () => Task.FromResult(TypedResults.Ok(new Todo() { Name = ""Test Item""})));", """{"id":0,"name":"Test Item","isComplete":false}""")] + [MemberData(nameof(MapAction_NoParam_TaskOfTReturn_Data))] public async Task MapAction_NoParam_TaskOfTReturn(string source, string expectedBody) { var (result, compilation) = await RunGeneratorAsync(source); @@ -351,10 +351,15 @@ public async Task MapAction_NoParam_TaskOfTReturn(string source, string expected await VerifyResponseBodyAsync(httpContext, expectedBody); } + public static IEnumerable MapAction_NoParam_ValueTaskOfTReturn_Data => new List() + { + new object[] { @"app.MapGet(""/"", () => ValueTask.FromResult(""Hello world!""));", "Hello world!" }, + new object[] { $$"""app.MapGet("/", () => ValueTask.FromResult(new {{typeof(Todo)}}() { Name = "Test Item"}));""", """{"id":0,"name":"Test Item","isComplete":false}""" }, + new object[] { $$"""app.MapGet("/", () => ValueTask.FromResult(TypedResults.Ok(new {{typeof(Todo)}}() { Name = "Test Item"})));""", """{"id":0,"name":"Test Item","isComplete":false}""" } + }; + [Theory] - [InlineData(@"app.MapGet(""/"", () => ValueTask.FromResult(""Hello world!""));", "Hello world!")] - [InlineData(@"app.MapGet(""/"", () => ValueTask.FromResult(new Todo() { Name = ""Test Item""}));", """{"id":0,"name":"Test Item","isComplete":false}""")] - [InlineData(@"app.MapGet(""/"", () => ValueTask.FromResult(TypedResults.Ok(new Todo() { Name = ""Test Item""})));", """{"id":0,"name":"Test Item","isComplete":false}""")] + [MemberData(nameof(MapAction_NoParam_ValueTaskOfTReturn_Data))] public async Task MapAction_NoParam_ValueTaskOfTReturn(string source, string expectedBody) { var (result, compilation) = await RunGeneratorAsync(source); @@ -371,13 +376,18 @@ public async Task MapAction_NoParam_ValueTaskOfTReturn(string source, string exp await VerifyResponseBodyAsync(httpContext, expectedBody); } + public static IEnumerable MapAction_NoParam_TaskLikeOfObjectReturn_Data => new List() + { + new object[] { @"app.MapGet(""/"", () => new ValueTask(""Hello world!""));", "Hello world!" }, + new object[] { @"app.MapGet(""/"", () => Task.FromResult(""Hello world!""));", "Hello world!" }, + new object[] { $$"""app.MapGet("/", () => new ValueTask(new {{typeof(Todo)}}() { Name = "Test Item"}));""", """{"id":0,"name":"Test Item","isComplete":false}""" }, + new object[] { $$"""app.MapGet("/", () => Task.FromResult(new {{typeof(Todo)}}() { Name = "Test Item"}));""", """{"id":0,"name":"Test Item","isComplete":false}""" }, + new object[] { $$"""app.MapGet("/", () => new ValueTask(TypedResults.Ok(new {{typeof(Todo)}}() { Name = "Test Item"})));""", """{"id":0,"name":"Test Item","isComplete":false}""" }, + new object[] { $$"""app.MapGet("/", () => Task.FromResult(TypedResults.Ok(new {{typeof(Todo)}}() { Name = "Test Item"})));""", """{"id":0,"name":"Test Item","isComplete":false}""" } + }; + [Theory] - [InlineData(@"app.MapGet(""/"", () => new ValueTask(""Hello world!""));", "Hello world!")] - [InlineData(@"app.MapGet(""/"", () => Task.FromResult(""Hello world!""));", "Hello world!")] - [InlineData(@"app.MapGet(""/"", () => new ValueTask(new Todo() { Name = ""Test Item""}));", """{"id":0,"name":"Test Item","isComplete":false}""")] - [InlineData(@"app.MapGet(""/"", () => Task.FromResult(new Todo() { Name = ""Test Item""}));", """{"id":0,"name":"Test Item","isComplete":false}""")] - [InlineData(@"app.MapGet(""/"", () => new ValueTask(TypedResults.Ok(new Todo() { Name = ""Test Item""})));", """{"id":0,"name":"Test Item","isComplete":false}""")] - [InlineData(@"app.MapGet(""/"", () => Task.FromResult(TypedResults.Ok(new Todo() { Name = ""Test Item""})));", """{"id":0,"name":"Test Item","isComplete":false}""")] + [MemberData(nameof(MapAction_NoParam_TaskLikeOfObjectReturn_Data))] public async Task MapAction_NoParam_TaskLikeOfObjectReturn(string source, string expectedBody) { var (result, compilation) = await RunGeneratorAsync(source); @@ -509,21 +519,21 @@ public static object[][] MapAction_ExplicitBodyParam_ComplexReturn_Data var withFilter = """ .AddEndpointFilter((c, n) => n(c)); """; - var fromBodyRequiredSource = """app.MapPost("/", ([FromBody] Todo todo) => TypedResults.Ok(todo));"""; - var fromBodyAllowEmptySource = """app.MapPost("/", ([FromBody(AllowEmpty = true)] Todo todo) => TypedResults.Ok(todo));"""; - var fromBodyNullableSource = """app.MapPost("/", ([FromBody] Todo? todo) => TypedResults.Ok(todo));"""; - var fromBodyDefaultValueSource = """ + var fromBodyRequiredSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo) => TypedResults.Ok(todo));"""; + var fromBodyAllowEmptySource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}(AllowEmpty = true)] {typeof(Todo)} todo) => TypedResults.Ok(todo));"""; + var fromBodyNullableSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)}? todo) => TypedResults.Ok(todo));"""; + var fromBodyDefaultValueSource = $""" #nullable disable -IResult postTodoWithDefault([FromBody] Todo todo = null) => TypedResults.Ok(todo); +IResult postTodoWithDefault([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo = null) => TypedResults.Ok(todo); app.MapPost("/", postTodoWithDefault); #nullable restore """; - var fromBodyRequiredWithFilterSource = $"""app.MapPost("/", ([FromBody] Todo todo) => TypedResults.Ok(todo)){withFilter}"""; - var fromBodyAllowEmptyWithFilterSource = $"""app.MapPost("/", ([FromBody(AllowEmpty = true)] Todo todo) => TypedResults.Ok(todo)){withFilter}"""; - var fromBodyNullableWithFilterSource = $"""app.MapPost("/", ([FromBody] Todo? todo) => TypedResults.Ok(todo)){withFilter}"""; + var fromBodyRequiredWithFilterSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo) => TypedResults.Ok(todo)){withFilter}"""; + var fromBodyAllowEmptyWithFilterSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}(AllowEmpty = true)] {typeof(Todo)} todo) => TypedResults.Ok(todo)){withFilter}"""; + var fromBodyNullableWithFilterSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)}? todo) => TypedResults.Ok(todo)){withFilter}"""; var fromBodyDefaultValueWithFilterSource = $""" #nullable disable -IResult postTodoWithDefault([FromBody] Todo todo = null) => TypedResults.Ok(todo); +IResult postTodoWithDefault([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo = null) => TypedResults.Ok(todo); app.MapPost("/", postTodoWithDefault){withFilter} #nullable restore """; @@ -580,10 +590,10 @@ public async Task MapAction_ExplicitBodyParam_ComplexReturn_Snapshot() Name = "Test Item", IsComplete = false }; - var source = """ -app.MapPost("/fromBodyRequired", ([FromBody] Todo todo) => TypedResults.Ok(todo)); + var source = $""" +app.MapPost("/fromBodyRequired", ([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo) => TypedResults.Ok(todo)); #pragma warning disable CS8622 -app.MapPost("/fromBodyOptional", ([FromBody] Todo? todo) => TypedResults.Ok(todo)); +app.MapPost("/fromBodyOptional", ([{typeof(FromBodyAttribute)}] {typeof(Todo)}? todo) => TypedResults.Ok(todo)); #pragma warning restore CS8622 """; var (_, compilation) = await RunGeneratorAsync(source); @@ -615,6 +625,176 @@ public async Task MapAction_ExplicitBodyParam_ComplexReturn_Snapshot() await VerifyResponseBodyAsync(httpContext, string.Empty); } + public static object[][] MapAction_ExplicitHeaderParam_SimpleReturn_Data + { + get + { + var expectedBody = "Test header value"; + var fromHeaderRequiredSource = $"""app.MapGet("/", ([{typeof(FromHeaderAttribute)}] string headerValue) => headerValue);"""; + var fromHeaderWithNameRequiredSource = $"""app.MapGet("/", ([{typeof(FromHeaderAttribute)}(Name = "headerValue")] string parameterName) => parameterName);"""; + var fromHeaderNullableSource = $"""app.MapGet("/", ([{typeof(FromHeaderAttribute)}] string? headerValue) => headerValue ?? string.Empty);"""; + var fromHeaderDefaultValueSource = $""" +#nullable disable +string getHeaderWithDefault([{typeof(FromHeaderAttribute)}] string headerValue = null) => headerValue ?? string.Empty; +app.MapGet("/", getHeaderWithDefault); +#nullable restore +"""; + + return new[] + { + new object[] { fromHeaderRequiredSource, expectedBody, 200, expectedBody }, + new object[] { fromHeaderRequiredSource, null, 400, string.Empty }, + new object[] { fromHeaderWithNameRequiredSource, expectedBody, 200, expectedBody }, + new object[] { fromHeaderWithNameRequiredSource, null, 400, string.Empty }, + new object[] { fromHeaderNullableSource, expectedBody, 200, expectedBody }, + new object[] { fromHeaderNullableSource, null, 200, string.Empty }, + new object[] { fromHeaderDefaultValueSource, expectedBody, 200, expectedBody }, + new object[] { fromHeaderDefaultValueSource, null, 200, string.Empty }, + }; + } + } + + [Theory] + [MemberData(nameof(MapAction_ExplicitHeaderParam_SimpleReturn_Data))] + public async Task MapAction_ExplicitHeaderParam_SimpleReturn(string source, string requestData, int expectedStatusCode, string expectedBody) + { + var (_, compilation) = await RunGeneratorAsync(source); + var endpoint = GetEndpointFromCompilation(compilation); + + var httpContext = CreateHttpContext(); + if (requestData is not null) + { + httpContext.Request.Headers["headerValue"] = requestData; + } + + await endpoint.RequestDelegate(httpContext); + await VerifyResponseBodyAsync(httpContext, expectedBody, expectedStatusCode); + } + + public static object[][] MapAction_ExplicitRouteParam_SimpleReturn_Data + { + get + { + var expectedBody = "Test route value"; + var fromRouteRequiredSource = $$"""app.MapGet("/{routeValue}", ([{{typeof(FromRouteAttribute)}}] string routeValue) => routeValue);"""; + var fromRouteWithNameRequiredSource = $$"""app.MapGet("/{routeValue}", ([{{typeof(FromRouteAttribute)}}(Name = "routeValue" )] string parameterName) => parameterName);"""; + var fromRouteNullableSource = $$"""app.MapGet("/{routeValue}", ([{{typeof(FromRouteAttribute)}}] string? routeValue) => routeValue ?? string.Empty);"""; + var fromRouteDefaultValueSource = $$""" +#nullable disable +string getRouteWithDefault([{{typeof(FromRouteAttribute)}}] string routeValue = null) => routeValue ?? string.Empty; +app.MapGet("/{routeValue}", getRouteWithDefault); +#nullable restore +"""; + + return new[] + { + new object[] { fromRouteRequiredSource, expectedBody, 200, expectedBody }, + new object[] { fromRouteRequiredSource, null, 400, string.Empty }, + new object[] { fromRouteWithNameRequiredSource, expectedBody, 200, expectedBody }, + new object[] { fromRouteWithNameRequiredSource, null, 400, string.Empty }, + new object[] { fromRouteNullableSource, expectedBody, 200, expectedBody }, + new object[] { fromRouteNullableSource, null, 200, string.Empty }, + new object[] { fromRouteDefaultValueSource, expectedBody, 200, expectedBody }, + new object[] { fromRouteDefaultValueSource, null, 200, string.Empty }, + }; + } + } + + [Theory] + [MemberData(nameof(MapAction_ExplicitRouteParam_SimpleReturn_Data))] + public async Task MapAction_ExplicitRouteParam_SimpleReturn(string source, string requestData, int expectedStatusCode, string expectedBody) + { + var (_, compilation) = await RunGeneratorAsync(source); + var endpoint = GetEndpointFromCompilation(compilation); + + var httpContext = CreateHttpContext(); + if (requestData is not null) + { + httpContext.Request.RouteValues["routeValue"] = requestData; + } + + await endpoint.RequestDelegate(httpContext); + await VerifyResponseBodyAsync(httpContext, expectedBody, expectedStatusCode); + } + + [Fact] + public async Task MapAction_ExplicitRouteParamWithInvalidName_SimpleReturn() + { + var source = $$"""app.MapGet("/{routeValue}", ([{{typeof(FromRouteAttribute)}}(Name = "invalidName" )] string parameterName) => parameterName);"""; + var (_, compilation) = await RunGeneratorAsync(source); + var endpoint = GetEndpointFromCompilation(compilation); + + var httpContext = CreateHttpContext(); + + var exception = await Assert.ThrowsAsync(() => endpoint.RequestDelegate(httpContext)); + Assert.Equal("'invalidName' is not a route parameter.", exception.Message); + } + + public static object[][] MapAction_RouteOrQueryParam_SimpleReturn_Data + { + get + { + var expectedBody = "ValueFromRouteOrQuery"; + var implicitRouteRequiredSource = """app.MapGet("/{value}", (string value) => value);"""; + var implicitQueryRequiredSource = """app.MapGet("", (string value) => value);"""; + var implicitRouteNullableSource = """app.MapGet("/{value}", (string? value) => value ?? string.Empty);"""; + var implicitQueryNullableSource = """app.MapGet("/", (string? value) => value ?? string.Empty);"""; + var implicitRouteDefaultValueSource = """ +#nullable disable +string getRouteWithDefault(string value = null) => value ?? string.Empty; +app.MapGet("/{value}", getRouteWithDefault); +#nullable restore +"""; + + var implicitQueryDefaultValueSource = """ +#nullable disable +string getQueryWithDefault(string value = null) => value ?? string.Empty; +app.MapGet("/", getQueryWithDefault); +#nullable restore +"""; + + return new[] + { + new object[] { implicitRouteRequiredSource, true, false, 200, expectedBody }, + new object[] { implicitRouteRequiredSource, false, false, 400, string.Empty }, + new object[] { implicitQueryRequiredSource, false, true, 200, expectedBody }, + new object[] { implicitQueryRequiredSource, false, false, 400, string.Empty }, + + new object[] { implicitRouteNullableSource, true, false, 200, expectedBody }, + new object[] { implicitRouteNullableSource, false, false, 200, string.Empty }, + new object[] { implicitQueryNullableSource, false, true, 200, expectedBody }, + new object[] { implicitQueryNullableSource, false, false, 200, string.Empty }, + + new object[] { implicitRouteDefaultValueSource, true, false, 200, expectedBody }, + new object[] { implicitRouteDefaultValueSource, false, false, 200, string.Empty }, + new object[] { implicitQueryDefaultValueSource, false, true, 200, expectedBody }, + new object[] { implicitQueryDefaultValueSource, false, false, 200, string.Empty }, + }; + } + } + + [Theory] + [MemberData(nameof(MapAction_RouteOrQueryParam_SimpleReturn_Data))] + public async Task MapAction_RouteOrQueryParam_SimpleReturn(string source, bool hasRoute, bool hasQuery, int expectedStatusCode, string expectedBody) + { + var (_, compilation) = await RunGeneratorAsync(source); + var endpoint = GetEndpointFromCompilation(compilation); + + var httpContext = CreateHttpContext(); + if (hasRoute) + { + httpContext.Request.RouteValues["value"] = expectedBody; + } + + if (hasQuery) + { + httpContext.Request.QueryString = new QueryString($"?value={expectedBody}"); + } + + await endpoint.RequestDelegate(httpContext); + await VerifyResponseBodyAsync(httpContext, expectedBody, expectedStatusCode); + } + [Fact] public async Task MapAction_UnknownParameter_EmitsDiagnostic_NoSource() { @@ -644,20 +824,20 @@ public static object[][] MapAction_ExplicitServiceParam_SimpleReturn_Data { get { - var fromServiceRequiredSource = $$"""app.MapPost("/", ([FromServices]{{typeof(TestService)}} svc) => svc.TestServiceMethod());"""; - var fromServiceNullableSource = $$"""app.MapPost("/", ([FromServices]{{typeof(TestService)}}? svc) => svc?.TestServiceMethod() ?? string.Empty);"""; + var fromServiceRequiredSource = $$"""app.MapPost("/", ([{{typeof(FromServicesAttribute)}}]{{typeof(TestService)}} svc) => svc.TestServiceMethod());"""; + var fromServiceNullableSource = $$"""app.MapPost("/", ([{{typeof(FromServicesAttribute)}}]{{typeof(TestService)}}? svc) => svc?.TestServiceMethod() ?? string.Empty);"""; var fromServiceDefaultValueSource = $$""" #nullable disable -string postServiceWithDefault([FromServices]{{typeof(TestService)}} svc = null) => svc?.TestServiceMethod() ?? string.Empty; +string postServiceWithDefault([{{typeof(FromServicesAttribute)}}]{{typeof(TestService)}} svc = null) => svc?.TestServiceMethod() ?? string.Empty; app.MapPost("/", postServiceWithDefault); #nullable restore """; - var fromServiceEnumerableRequiredSource = $$"""app.MapPost("/", ([FromServices]IEnumerable<{{typeof(TestService)}}> svc) => svc.FirstOrDefault()?.TestServiceMethod() ?? string.Empty);"""; - var fromServiceEnumerableNullableSource = $$"""app.MapPost("/", ([FromServices]IEnumerable<{{typeof(TestService)}}>? svc) => svc?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty);"""; + var fromServiceEnumerableRequiredSource = $$"""app.MapPost("/", ([{{typeof(FromServicesAttribute)}}]IEnumerable<{{typeof(TestService)}}> svc) => svc.FirstOrDefault()?.TestServiceMethod() ?? string.Empty);"""; + var fromServiceEnumerableNullableSource = $$"""app.MapPost("/", ([{{typeof(FromServicesAttribute)}}]IEnumerable<{{typeof(TestService)}}>? svc) => svc?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty);"""; var fromServiceEnumerableDefaultValueSource = $$""" #nullable disable -string postServiceWithDefault([FromServices]IEnumerable<{{typeof(TestService)}}> svc = null) => svc?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty; +string postServiceWithDefault([{{typeof(FromServicesAttribute)}}]IEnumerable<{{typeof(TestService)}}> svc = null) => svc?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty; app.MapPost("/", postServiceWithDefault); #nullable restore """; @@ -712,9 +892,9 @@ public async Task MapAction_ExplicitServiceParam_SimpleReturn(string source, boo public async Task MapAction_ExplicitServiceParam_SimpleReturn_Snapshot() { var source = $$""" -app.MapGet("/fromServiceRequired", ([FromServices]{{typeof(TestService)}} svc) => svc.TestServiceMethod()); -app.MapGet("/enumerableFromService", ([FromServices]IEnumerable<{{typeof(TestService)}}> svc) => svc?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty); -app.MapGet("/multipleFromService", ([FromServices]{{typeof(TestService)}}? svc, [FromServices]IEnumerable<{{typeof(TestService)}}> svcs) => +app.MapGet("/fromServiceRequired", ([{{typeof(FromServicesAttribute)}}]{{typeof(TestService)}} svc) => svc.TestServiceMethod()); +app.MapGet("/enumerableFromService", ([{{typeof(FromServicesAttribute)}}]IEnumerable<{{typeof(TestService)}}> svc) => svc?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty); +app.MapGet("/multipleFromService", ([{{typeof(FromServicesAttribute)}}]{{typeof(TestService)}}? svc, [FromServices]IEnumerable<{{typeof(TestService)}}> svcs) => $"{(svcs?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty)}, {svc?.TestServiceMethod()}"); """; var httpContext = CreateHttpContext(); @@ -756,6 +936,20 @@ public async Task MapAction_ExplicitServiceParam_SimpleReturn_Snapshot() await VerifyResponseBodyAsync(httpContext, $"{expectedBody}, {expectedBody}"); } + [Fact] + public async Task MapAction_ExplicitSource_SimpleReturn_Snapshot() + { + var source = $$""" +app.MapGet("/fromQuery", ([{{typeof(FromQueryAttribute)}}] string? queryValue) => queryValue ?? string.Empty); +app.MapGet("/fromHeader", ([{{typeof(FromHeaderAttribute)}}] string? headerValue) => headerValue ?? string.Empty); +app.MapGet("/fromHeader/{routeValue}", ([{{typeof(FromRouteAttribute)}}] string? routeValue) => routeValue ?? string.Empty); +app.MapGet("/{value}", (string? value) => value ?? string.Empty); +"""; + var (_, compilation) = await RunGeneratorAsync(source); + + await VerifyAgainstBaselineUsingFile(compilation); + } + [Fact] public async Task MapAction_RequestDelegateHandler_DoesNotEmit() { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs index 5ec3302a9908..c9fd62c7f454 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs @@ -1,8 +1,29 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.AspNetCore.Http.Metadata; + namespace Microsoft.AspNetCore.Http.Generators.Tests; public class TestService { public string TestServiceMethod() => "Produced from service!"; } + +public class Todo +{ + public int Id { get; set; } + public string Name { get; set; } = "Todo"; + public bool IsComplete { get; set; } +} + +public interface ITodo +{ + public int Id { get; } + public string Name { get; } + public bool IsComplete { get; } +} + +public class FromBodyAttribute : Attribute, IFromBodyMetadata +{ + public bool AllowEmpty { get; set; } +} diff --git a/src/Shared/RoslynUtils/SymbolExtensions.cs b/src/Shared/RoslynUtils/SymbolExtensions.cs index e80bb5054eff..fa83aacdf4d8 100644 --- a/src/Shared/RoslynUtils/SymbolExtensions.cs +++ b/src/Shared/RoslynUtils/SymbolExtensions.cs @@ -82,4 +82,25 @@ public static ImmutableArray GetParameters(this ISymbol? symbo public static ISymbol? GetAnySymbol(this SymbolInfo info) => info.Symbol ?? info.CandidateSymbols.FirstOrDefault(); + + public static bool IsOptional(this IParameterSymbol parameterSymbol) => + parameterSymbol.Type is INamedTypeSymbol + { + NullableAnnotation: NullableAnnotation.Annotated + } || parameterSymbol.HasExplicitDefaultValue; + + public static bool TryGetNamedArgumentValue(this AttributeData attribute, string argumentName, [NotNullWhen(true)] out T? argumentValue) + { + argumentValue = default; + foreach (var namedArgument in attribute.NamedArguments) + { + if (namedArgument.Key == argumentName) + { + var routeParameterNameConstant = namedArgument.Value; + argumentValue = (T)routeParameterNameConstant.Value!; + return true; + } + } + return false; + } } From 27a71f641294d3412f686bd021dfc4e787c3ccbf Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Thu, 16 Feb 2023 17:08:04 -0800 Subject: [PATCH 2/7] Fix incrementality test --- .../RequestDelegateGeneratorIncrementalityTests.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs index a44c5894adb9..6c75e73f08c4 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.CodeAnalysis; + namespace Microsoft.AspNetCore.Http.Generators.Tests; public class RequestDelegateGeneratorIncrementalityTests : RequestDelegateGeneratorTestBase @@ -44,8 +45,12 @@ public async Task MapAction_ChangeReturnType_TriggersUpdate() [Fact] public async Task MapAction_ChangeBodyParamNullability_TriggersUpdate() { - var source = @"app.MapGet(""/"", ([FromBody] Todo todo) => TypedResults.Ok(todo));"; - var updatedSource = @"app.MapGet(""/"", ([FromBody] Todo? todo) => TypedResults.Ok(todo));"; + var source = $"""app.MapGet("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo) => TypedResults.Ok(todo));"""; + var updatedSource = $""" +#pragma warning disable CS8622 +app.MapGet("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)}? todo) => TypedResults.Ok(todo)); +#pragma warning disable CS8622 +"""; var (result, compilation) = await RunGeneratorAsync(source, updatedSource); var outputSteps = GetRunStepOutputs(result); From 4a2d762e59aebcb3f600b002e6a358e4b78d1aa6 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Tue, 21 Feb 2023 12:54:29 -0800 Subject: [PATCH 3/7] Move code emissions out of parsing logic --- .../Emitters/EndpointEmitter.cs | 3 + .../Emitters/EndpointParameterEmitter.cs | 58 ++++++++++++------- .../gen/StaticRouteHandlerModel/Endpoint.cs | 4 -- .../EndpointParameter.cs | 40 +++---------- 4 files changed, 49 insertions(+), 56 deletions(-) diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs index 1e4599affa1c..28ba68a9e5dd 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Linq; using System.Text; namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters; @@ -51,4 +52,6 @@ internal static string EmitParameterPreparation(this Endpoint endpoint) return parameterPreparationBuilder.ToString(); } + + public static string EmitArgumentList(this Endpoint endpoint) => string.Join(", ", endpoint.Parameters.Select(p => p.EmitArgument())); } diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs index 02894cfcac09..2e7c11800ebf 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using System.Text; namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters; @@ -9,22 +10,22 @@ internal static class EndpointParameterEmitter internal static string EmitSpecialParameterPreparation(this EndpointParameter endpointParameter) { return $""" - var {endpointParameter.HandlerArgument} = {endpointParameter.AssigningCode}; + var {endpointParameter.EmitHandlerArgument()} = {endpointParameter.AssigningCode}; """; } internal static string EmitQueryOrHeaderParameterPreparation(this EndpointParameter endpointParameter) { var builder = new StringBuilder(); - - // Preamble for diagnostics purposes. builder.AppendLine($""" {endpointParameter.EmitParameterDiagnosticComment()} """); - // Grab raw input from HttpContext. + var assigningCode = endpointParameter.Source is EndpointParameterSource.Header + ? $"httpContext.Request.Headers[\"{endpointParameter.Name}\"]" + : $"httpContext.Request.Query[\"{endpointParameter.Name}\"]"; builder.AppendLine($$""" - var {{endpointParameter.AssigningCodeResult}} = {{endpointParameter.AssigningCode}}; + var {{endpointParameter.EmitAssigningCodeResult()}} = {{assigningCode}}; """); // If we are not optional, then at this point we can just assign the string value to the handler argument, @@ -34,17 +35,17 @@ internal static string EmitQueryOrHeaderParameterPreparation(this EndpointParame if (endpointParameter.IsOptional) { builder.AppendLine($$""" - var {{endpointParameter.HandlerArgument}} = {{endpointParameter.AssigningCodeResult}}.Count > 0 ? {{endpointParameter.AssigningCodeResult}}.ToString() : null; + var {{endpointParameter.EmitHandlerArgument()}} = {{endpointParameter.EmitAssigningCodeResult()}}.Count > 0 ? {{endpointParameter.EmitAssigningCodeResult()}}.ToString() : null; """); } else { builder.AppendLine($$""" - if (StringValues.IsNullOrEmpty({{endpointParameter.AssigningCodeResult}})) + if (StringValues.IsNullOrEmpty({{endpointParameter.EmitAssigningCodeResult()}})) { wasParamCheckFailure = true; } - var {{endpointParameter.HandlerArgument}} = {{endpointParameter.AssigningCodeResult}}.ToString(); + var {{endpointParameter.EmitHandlerArgument()}} = {{endpointParameter.EmitAssigningCodeResult()}}.ToString(); """); } @@ -54,7 +55,6 @@ internal static string EmitQueryOrHeaderParameterPreparation(this EndpointParame internal static string EmitRouteParameterPreparation(this EndpointParameter endpointParameter) { var builder = new StringBuilder(); - builder.AppendLine($""" {endpointParameter.EmitParameterDiagnosticComment()} """); @@ -68,21 +68,22 @@ internal static string EmitRouteParameterPreparation(this EndpointParameter endp } """); + var assigningCode = $"httpContext.Request.RouteValues[\"{endpointParameter.Name}\"]?.ToString()"; builder.AppendLine($$""" - var {{endpointParameter.AssigningCodeResult}} = {{endpointParameter.AssigningCode}}; + var {{endpointParameter.EmitAssigningCodeResult()}} = {{assigningCode}}; """); if (!endpointParameter.IsOptional) { builder.AppendLine($$""" - if ({{endpointParameter.AssigningCodeResult}} == null) + if ({{endpointParameter.EmitAssigningCodeResult()}} == null) { wasParamCheckFailure = true; } """); } builder.AppendLine($""" - var {endpointParameter.HandlerArgument} = {endpointParameter.AssigningCodeResult}; + var {endpointParameter.EmitHandlerArgument()} = {endpointParameter.EmitAssigningCodeResult()}; """); return builder.ToString(); @@ -91,19 +92,20 @@ internal static string EmitRouteParameterPreparation(this EndpointParameter endp internal static string EmitRouteOrQueryParameterPreparation(this EndpointParameter endpointParameter) { var builder = new StringBuilder(); - builder.AppendLine($""" {endpointParameter.EmitParameterDiagnosticComment()} """); + var assigningCode = $"GeneratedRouteBuilderExtensionsCore.ResolveFromRouteOrQuery(httpContext, \"{endpointParameter.Name}\", options?.RouteParameterNames)"; + builder.AppendLine($$""" - var {{endpointParameter.AssigningCodeResult}} = {{endpointParameter.AssigningCode}}; + var {{endpointParameter.EmitAssigningCodeResult()}} = {{assigningCode}}; """); if (!endpointParameter.IsOptional) { builder.AppendLine($$""" - if ({{endpointParameter.AssigningCodeResult}} is StringValues { Count: 0 }) + if ({{endpointParameter.EmitAssigningCodeResult()}} is StringValues { Count: 0 }) { wasParamCheckFailure = true; } @@ -111,7 +113,7 @@ internal static string EmitRouteOrQueryParameterPreparation(this EndpointParamet } builder.AppendLine($""" - var {endpointParameter.HandlerArgument} = {endpointParameter.AssigningCodeResult}; + var {endpointParameter.EmitHandlerArgument()} = {endpointParameter.EmitAssigningCodeResult()}; """); return builder.ToString(); @@ -120,15 +122,13 @@ internal static string EmitRouteOrQueryParameterPreparation(this EndpointParamet internal static string EmitJsonBodyParameterPreparationString(this EndpointParameter endpointParameter) { var builder = new StringBuilder(); - - // Preamble for diagnostics purposes. builder.AppendLine($""" {endpointParameter.EmitParameterDiagnosticComment()} """); - // Grab raw input from HttpContext. + var assigningCode = $"await GeneratedRouteBuilderExtensionsCore.TryResolveBody<{endpointParameter.Type}>(httpContext, {(endpointParameter.IsOptional ? "true" : "false")})"; builder.AppendLine($$""" - var (isSuccessful, {{endpointParameter.HandlerArgument}}) = {{endpointParameter.AssigningCode}}; + var (isSuccessful, {{endpointParameter.EmitHandlerArgument()}}) = {{assigningCode}}; """); // If binding from the JSON body fails, we exit early. Don't @@ -153,10 +153,14 @@ internal static string EmitServiceParameterPreparation(this EndpointParameter en {endpointParameter.EmitParameterDiagnosticComment()} """); + var assigningCode = endpointParameter.IsOptional ? + $"httpContext.RequestServices.GetService<{endpointParameter.Type}>();" : + $"httpContext.RequestServices.GetRequiredService<{endpointParameter.Type}>()"; + // Requiredness checks for services are handled by the distinction // between GetRequiredService and GetService in the AssigningCode. builder.AppendLine($$""" - var {{endpointParameter.HandlerArgument}} = {{endpointParameter.AssigningCode}}; + var {{endpointParameter.EmitHandlerArgument()}} = {{assigningCode}}; """); return builder.ToString(); @@ -164,4 +168,16 @@ internal static string EmitServiceParameterPreparation(this EndpointParameter en private static string EmitParameterDiagnosticComment(this EndpointParameter endpointParameter) => $"// Endpoint Parameter: {endpointParameter.Name} (Type = {endpointParameter.Type}, IsOptional = {endpointParameter.IsOptional}, Source = {endpointParameter.Source})"; + + private static string EmitHandlerArgument(this EndpointParameter endpointParameter) => $"{endpointParameter.Name}_local"; + private static string EmitAssigningCodeResult(this EndpointParameter endpointParameter) => $"{endpointParameter.Name}_raw"; + + public static string EmitArgument(this EndpointParameter endpointParameter) => endpointParameter.Source switch + { + EndpointParameterSource.JsonBody or EndpointParameterSource.Route or EndpointParameterSource.RouteOrQuery => endpointParameter.IsOptional + ? endpointParameter.EmitHandlerArgument() + : $"{endpointParameter.EmitHandlerArgument()}!", + EndpointParameterSource.Unknown => throw new Exception("Unreachable!"), + _ => endpointParameter.EmitHandlerArgument() + }; } diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs index 49ae1ef12325..83cf8bf65bdb 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs @@ -13,8 +13,6 @@ namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel; internal class Endpoint { - private string? _argumentListCache; - public Endpoint(IInvocationOperation operation, WellKnownTypes wellKnownTypes) { Operation = operation; @@ -67,8 +65,6 @@ public Endpoint(IInvocationOperation operation, WellKnownTypes wellKnownTypes) public string? RoutePattern { get; } public EndpointResponse? Response { get; } public EndpointParameter[] Parameters { get; } = Array.Empty(); - public string EmitArgumentList() => _argumentListCache ??= string.Join(", ", Parameters.Select(p => p.EmitArgument())); - public List Diagnostics { get; } = new List(); public (string File, int LineNumber) Location { get; } diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs index fe46f30ab0b9..da8ed8837bd1 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs @@ -17,8 +17,6 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp Type = parameter.Type; Name = parameter.Name; Source = EndpointParameterSource.Unknown; - HandlerArgument = $"{parameter.Name}_local"; - AssigningCodeResult = $"{parameter.Name}_raw"; var fromQueryMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromQueryMetadata); var fromServiceMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata); @@ -31,7 +29,6 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp Name = fromRouteAttribute.TryGetNamedArgumentValue("Name", out var fromRouteName) ? fromRouteName : parameter.Name; - AssigningCode = $"httpContext.Request.RouteValues[\"{Name}\"]?.ToString()"; IsOptional = parameter.IsOptional(); } else if (parameter.HasAttributeImplementingInterface(fromQueryMetadataInterfaceType, out var fromQueryAttribute)) @@ -40,7 +37,6 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp Name = fromQueryAttribute.TryGetNamedArgumentValue("Name", out var fromQueryName) ? fromQueryName : parameter.Name; - AssigningCode = $"httpContext.Request.Query[\"{Name}\"]"; IsOptional = parameter.IsOptional(); } else if (parameter.HasAttributeImplementingInterface(fromHeaderMetadataInterfaceType, out var fromHeaderAttribute)) @@ -49,22 +45,17 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp Name = fromHeaderAttribute.TryGetNamedArgumentValue("Name", out var fromHeaderName) ? fromHeaderName : parameter.Name; - AssigningCode = $"httpContext.Request.Headers[\"{Name}\"]"; IsOptional = parameter.IsOptional(); } - else if (TryGetExplicitFromJsonBody(parameter, wellKnownTypes, out var jsonBodyAssigningCode, out var isOptional)) + else if (TryGetExplicitFromJsonBody(parameter, wellKnownTypes, out var isOptional)) { Source = EndpointParameterSource.JsonBody; - AssigningCode = jsonBodyAssigningCode; IsOptional = isOptional; } else if (parameter.HasAttributeImplementingInterface(fromServiceMetadataInterfaceType)) { Source = EndpointParameterSource.Service; IsOptional = parameter.Type is INamedTypeSymbol { NullableAnnotation: NullableAnnotation.Annotated } || parameter.HasExplicitDefaultValue; - AssigningCode = IsOptional ? - $"httpContext.RequestServices.GetService<{parameter.Type}>();" : - $"httpContext.RequestServices.GetRequiredService<{parameter.Type}>()"; } else if (TryGetSpecialTypeAssigningCode(Type, wellKnownTypes, out var specialTypeAssigningCode)) { @@ -74,7 +65,6 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp else if (parameter.Type.SpecialType == SpecialType.System_String) { Source = EndpointParameterSource.RouteOrQuery; - AssigningCode = $"GeneratedRouteBuilderExtensionsCore.ResolveFromRouteOrQuery(httpContext, \"{parameter.Name}\", options?.RouteParameterNames)"; IsOptional = parameter.IsOptional(); } else @@ -87,21 +77,12 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp public ITypeSymbol Type { get; } public EndpointParameterSource Source { get; } - // TODO: If the parameter has [FromRoute("AnotherName")] or similar, prefer that. + // Omly used for SpecialType parameters that need + // to be resolved by a specific WellKnownType + internal string? AssigningCode { get; set; } public string Name { get; } - public string? AssigningCode { get; } - public string HandlerArgument { get; } - - public string AssigningCodeResult { get; } public bool IsOptional { get; } - public string EmitArgument() => Source switch - { - EndpointParameterSource.JsonBody or EndpointParameterSource.Route or EndpointParameterSource.RouteOrQuery => IsOptional ? HandlerArgument : $"{HandlerArgument}!", - EndpointParameterSource.Unknown => throw new Exception("Unreachable!"), - _ => HandlerArgument - }; - // TODO: Handle special form types like IFormFileCollection that need special body-reading logic. private static bool TryGetSpecialTypeAssigningCode(ITypeSymbol type, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out string? callingCode) { @@ -147,19 +128,16 @@ private static bool TryGetSpecialTypeAssigningCode(ITypeSymbol type, WellKnownTy private static bool TryGetExplicitFromJsonBody(IParameterSymbol parameter, WellKnownTypes wellKnownTypes, - [NotNullWhen(true)] out string? assigningCode, out bool isOptional) { - assigningCode = null; isOptional = false; - if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromBodyMetadata), out var fromBodyAttribute)) + if (!parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromBodyMetadata), out var fromBodyAttribute)) { - isOptional |= fromBodyAttribute.TryGetNamedArgumentValue("AllowEmpty", out var allowEmptyValue) && allowEmptyValue; - isOptional |= (parameter.NullableAnnotation == NullableAnnotation.Annotated || parameter.HasExplicitDefaultValue); - assigningCode = $"await GeneratedRouteBuilderExtensionsCore.TryResolveBody<{parameter.Type}>(httpContext, {(isOptional ? "true" : "false")})"; - return true; + return false; } - return false; + isOptional |= fromBodyAttribute.TryGetNamedArgumentValue("AllowEmpty", out var allowEmptyValue) && allowEmptyValue; + isOptional |= (parameter.NullableAnnotation == NullableAnnotation.Annotated || parameter.HasExplicitDefaultValue); + return true; } public override bool Equals(object obj) => From d0f04e0e025c1ba76c3c0a852edfa8f7bb7c4bae Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Thu, 23 Feb 2023 13:32:58 -0800 Subject: [PATCH 4/7] Address feedback from peer review - Add support for EmpyBodyBehavior check - Update source snapshots to implicit tests - Avoid codegenning `TryResolveRouteOrQuery` if not needed - Clean up test types --- .../gen/RequestDelegateGeneratorSources.cs | 7 - .../Emitters/EndpointParameterEmitter.cs | 11 +- .../EndpointParameter.cs | 2 +- .../StaticRouteHandlerModel.Emitter.cs | 2 +- ...Param_ComplexReturn_Snapshot.generated.txt | 11 +- ...eParam_SimpleReturn_Snapshot.generated.txt | 13 +- ...Source_SimpleReturn_Snapshot.generated.txt | 166 ++++++++++++++---- ...pecialTypeParam_StringReturn.generated.txt | 9 +- ...ipleStringParam_StringReturn.generated.txt | 9 +- ...aram_StringReturn_WithFilter.generated.txt | 7 - ...ngValueProvided_StringReturn.generated.txt | 9 +- ...pAction_NoParam_StringReturn.generated.txt | 7 - ...tion_WithParams_StringReturn.generated.txt | 13 +- ...estDelegateGeneratorIncrementalityTests.cs | 1 + .../RequestDelegateGeneratorTests.cs | 14 +- .../RequestDelegateGenerator/SharedTypes.cs | 12 -- src/Shared/RoslynUtils/WellKnownTypeData.cs | 2 + 17 files changed, 162 insertions(+), 133 deletions(-) diff --git a/src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs b/src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs index b1dec81715ac..2f7d8116a90f 100644 --- a/src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs +++ b/src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs @@ -134,13 +134,6 @@ private static Task ExecuteObjectResult(object? obj, HttpContext httpContext) } return (false, default); } - - private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) - { - return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true - ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) - : httpContext.Request.Query[$"{parameterName}"]; - } } {{GeneratedCodeAttribute}} diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs index 2e7c11800ebf..092f1fd69847 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs @@ -96,7 +96,10 @@ internal static string EmitRouteOrQueryParameterPreparation(this EndpointParamet {endpointParameter.EmitParameterDiagnosticComment()} """); - var assigningCode = $"GeneratedRouteBuilderExtensionsCore.ResolveFromRouteOrQuery(httpContext, \"{endpointParameter.Name}\", options?.RouteParameterNames)"; + var parameterName = endpointParameter.Name; + var assigningCode = $@"options?.RouteParameterNames?.Contains(""{parameterName}"", StringComparer.OrdinalIgnoreCase) == true"; + assigningCode += $@"? new StringValues(httpContext.Request.RouteValues[$""{parameterName}""]?.ToString())"; + assigningCode += $@": httpContext.Request.Query[$""{parameterName}""];"; builder.AppendLine($$""" var {{endpointParameter.EmitAssigningCodeResult()}} = {{assigningCode}}; @@ -153,12 +156,14 @@ internal static string EmitServiceParameterPreparation(this EndpointParameter en {endpointParameter.EmitParameterDiagnosticComment()} """); + // Requiredness checks for services are handled by the distinction + // between GetRequiredService and GetService in the assigningCode. + // Unlike other scenarios, this will result in an exception being thrown + // at runtime. var assigningCode = endpointParameter.IsOptional ? $"httpContext.RequestServices.GetService<{endpointParameter.Type}>();" : $"httpContext.RequestServices.GetRequiredService<{endpointParameter.Type}>()"; - // Requiredness checks for services are handled by the distinction - // between GetRequiredService and GetService in the AssigningCode. builder.AppendLine($$""" var {{endpointParameter.EmitHandlerArgument()}} = {{assigningCode}}; """); diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs index da8ed8837bd1..4112145966bc 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs @@ -135,7 +135,7 @@ private static bool TryGetExplicitFromJsonBody(IParameterSymbol parameter, { return false; } - isOptional |= fromBodyAttribute.TryGetNamedArgumentValue("AllowEmpty", out var allowEmptyValue) && allowEmptyValue; + isOptional |= fromBodyAttribute.TryGetNamedArgumentValue("EmptyBodyBehavior", out var emptyBodyBehaviorValue) && emptyBodyBehaviorValue == 1; isOptional |= (parameter.NullableAnnotation == NullableAnnotation.Annotated || parameter.HasExplicitDefaultValue); return true; } diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs index e57fa73f5359..f7df123053fb 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs @@ -185,7 +185,7 @@ public static string EmitFilteredArgumentList(this Endpoint endpoint) for (var i = 0; i < endpoint.Parameters.Length; i++) { - sb.Append($"ic.GetArgument<{endpoint.Parameters[i].Type}>({i})"); + sb.Append($"ic.GetArgument<{endpoint.Parameters[i].Type.ToDisplayString(EmitterConstants.DisplayFormat)}>({i})"); if (i < endpoint.Parameters.Length - 1) { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt index 0742119b9fb1..769f925b994d 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt @@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0))); + return ValueTask.FromResult(handler(ic.GetArgument(0))); }, options.EndpointBuilder, handler.Method); @@ -171,7 +171,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0))); + return ValueTask.FromResult(handler(ic.GetArgument(0))); }, options.EndpointBuilder, handler.Method); @@ -299,13 +299,6 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } - - private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) - { - return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true - ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) - : httpContext.Request.Query[$"{parameterName}"]; - } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt index 183f05f046aa..4be53c6dce38 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt @@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0))); + return ValueTask.FromResult(handler(ic.GetArgument(0))); }, options.EndpointBuilder, handler.Method); @@ -193,7 +193,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument>(0))); + return ValueTask.FromResult(handler(ic.GetArgument>(0))); }, options.EndpointBuilder, handler.Method); @@ -252,7 +252,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0), ic.GetArgument>(1))); + return ValueTask.FromResult(handler(ic.GetArgument(0), ic.GetArgument>(1))); }, options.EndpointBuilder, handler.Method); @@ -378,13 +378,6 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } - - private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) - { - return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true - ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) - : httpContext.Request.Query[$"{parameterName}"]; - } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt index 4c264b94fbbd..06c18d82a72f 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Builder internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet( this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern, - global::System.Func handler, + global::System.Func handler, [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "", [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0) { @@ -93,7 +93,7 @@ namespace Microsoft.AspNetCore.Http.Generated }, (del, options, inferredMetadataResult) => { - var handler = (Func)del; + var handler = (Func)del; EndpointFilterDelegate? filteredInvocation = null; if (options?.EndpointBuilder?.FilterFactories.Count > 0) @@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0))); + return ValueTask.FromResult(handler(ic.GetArgument(0))); }, options.EndpointBuilder, handler.Method); @@ -113,9 +113,13 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: queryValue (Type = string?, IsOptional = True, Source = Query) + // Endpoint Parameter: queryValue (Type = string, IsOptional = False, Source = Query) var queryValue_raw = httpContext.Request.Query["queryValue"]; - var queryValue_local = queryValue_raw.Count > 0 ? queryValue_raw.ToString() : null; + if (StringValues.IsNullOrEmpty(queryValue_raw)) + { + wasParamCheckFailure = true; + } + var queryValue_local = queryValue_raw.ToString(); if (wasParamCheckFailure) { @@ -129,15 +133,19 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: queryValue (Type = string?, IsOptional = True, Source = Query) + // Endpoint Parameter: queryValue (Type = string, IsOptional = False, Source = Query) var queryValue_raw = httpContext.Request.Query["queryValue"]; - var queryValue_local = queryValue_raw.Count > 0 ? queryValue_raw.ToString() : null; + if (StringValues.IsNullOrEmpty(queryValue_raw)) + { + wasParamCheckFailure = true; + } + var queryValue_local = queryValue_raw.ToString(); if (wasParamCheckFailure) { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, queryValue_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, queryValue_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -154,7 +162,7 @@ namespace Microsoft.AspNetCore.Http.Generated }, (del, options, inferredMetadataResult) => { - var handler = (Func)del; + var handler = (Func)del; EndpointFilterDelegate? filteredInvocation = null; if (options?.EndpointBuilder?.FilterFactories.Count > 0) @@ -165,7 +173,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0))); + return ValueTask.FromResult(handler(ic.GetArgument(0))); }, options.EndpointBuilder, handler.Method); @@ -174,9 +182,13 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: headerValue (Type = string?, IsOptional = True, Source = Header) + // Endpoint Parameter: headerValue (Type = string, IsOptional = False, Source = Header) var headerValue_raw = httpContext.Request.Headers["headerValue"]; - var headerValue_local = headerValue_raw.Count > 0 ? headerValue_raw.ToString() : null; + if (StringValues.IsNullOrEmpty(headerValue_raw)) + { + wasParamCheckFailure = true; + } + var headerValue_local = headerValue_raw.ToString(); if (wasParamCheckFailure) { @@ -190,15 +202,19 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: headerValue (Type = string?, IsOptional = True, Source = Header) + // Endpoint Parameter: headerValue (Type = string, IsOptional = False, Source = Header) var headerValue_raw = httpContext.Request.Headers["headerValue"]; - var headerValue_local = headerValue_raw.Count > 0 ? headerValue_raw.ToString() : null; + if (StringValues.IsNullOrEmpty(headerValue_raw)) + { + wasParamCheckFailure = true; + } + var headerValue_local = headerValue_raw.ToString(); if (wasParamCheckFailure) { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, headerValue_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, headerValue_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -215,7 +231,7 @@ namespace Microsoft.AspNetCore.Http.Generated }, (del, options, inferredMetadataResult) => { - var handler = (Func)del; + var handler = (Func)del; EndpointFilterDelegate? filteredInvocation = null; if (options?.EndpointBuilder?.FilterFactories.Count > 0) @@ -226,7 +242,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0))); + return ValueTask.FromResult(handler(ic.GetArgument(0))); }, options.EndpointBuilder, handler.Method); @@ -235,12 +251,16 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: routeValue (Type = string?, IsOptional = True, Source = Route) + // Endpoint Parameter: routeValue (Type = string, IsOptional = False, Source = Route) if (options?.RouteParameterNames?.Contains("routeValue", StringComparer.OrdinalIgnoreCase) != true) { throw new InvalidOperationException($"'routeValue' is not a route parameter."); } var routeValue_raw = httpContext.Request.RouteValues["routeValue"]?.ToString(); + if (routeValue_raw == null) + { + wasParamCheckFailure = true; + } var routeValue_local = routeValue_raw; if (wasParamCheckFailure) @@ -249,25 +269,29 @@ namespace Microsoft.AspNetCore.Http.Generated return Task.CompletedTask; } httpContext.Response.ContentType ??= "text/plain"; - var result = handler(routeValue_local); + var result = handler(routeValue_local!); return httpContext.Response.WriteAsync(result); } async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: routeValue (Type = string?, IsOptional = True, Source = Route) + // Endpoint Parameter: routeValue (Type = string, IsOptional = False, Source = Route) if (options?.RouteParameterNames?.Contains("routeValue", StringComparer.OrdinalIgnoreCase) != true) { throw new InvalidOperationException($"'routeValue' is not a route parameter."); } var routeValue_raw = httpContext.Request.RouteValues["routeValue"]?.ToString(); + if (routeValue_raw == null) + { + wasParamCheckFailure = true; + } var routeValue_local = routeValue_raw; if (wasParamCheckFailure) { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, routeValue_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, routeValue_local!)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -284,7 +308,76 @@ namespace Microsoft.AspNetCore.Http.Generated }, (del, options, inferredMetadataResult) => { - var handler = (Func)del; + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + + if (options?.EndpointBuilder?.FilterFactories.Count > 0) + { + filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic => + { + if (ic.HttpContext.Response.StatusCode == 400) + { + return ValueTask.FromResult(Results.Empty); + } + return ValueTask.FromResult(handler(ic.GetArgument(0))); + }, + options.EndpointBuilder, + handler.Method); + } + + Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + // Endpoint Parameter: value (Type = string, IsOptional = False, Source = RouteOrQuery) + var value_raw = options?.RouteParameterNames?.Contains("value", StringComparer.OrdinalIgnoreCase) == true? new StringValues(httpContext.Request.RouteValues[$"value"]?.ToString()): httpContext.Request.Query[$"value"];; + if (value_raw is StringValues { Count: 0 }) + { + wasParamCheckFailure = true; + } + var value_local = value_raw; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return Task.CompletedTask; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(value_local!); + return httpContext.Response.WriteAsync(result); + } + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + // Endpoint Parameter: value (Type = string, IsOptional = False, Source = RouteOrQuery) + var value_raw = options?.RouteParameterNames?.Contains("value", StringComparer.OrdinalIgnoreCase) == true? new StringValues(httpContext.Request.RouteValues[$"value"]?.ToString()): httpContext.Request.Query[$"value"];; + if (value_raw is StringValues { Count: 0 }) + { + wasParamCheckFailure = true; + } + var value_local = value_raw; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, value_local!)); + await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); + } + + RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered; + var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; + return new RequestDelegateResult(targetDelegate, metadata); + }), + [(@"TestMapActions.cs", 20)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 20)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; EndpointFilterDelegate? filteredInvocation = null; if (options?.EndpointBuilder?.FilterFactories.Count > 0) @@ -295,7 +388,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0))); + return ValueTask.FromResult(handler(ic.GetArgument(0))); }, options.EndpointBuilder, handler.Method); @@ -304,8 +397,12 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: value (Type = string?, IsOptional = True, Source = RouteOrQuery) - var value_raw = GeneratedRouteBuilderExtensionsCore.ResolveFromRouteOrQuery(httpContext, "value", options?.RouteParameterNames); + // Endpoint Parameter: value (Type = string, IsOptional = False, Source = RouteOrQuery) + var value_raw = options?.RouteParameterNames?.Contains("value", StringComparer.OrdinalIgnoreCase) == true? new StringValues(httpContext.Request.RouteValues[$"value"]?.ToString()): httpContext.Request.Query[$"value"];; + if (value_raw is StringValues { Count: 0 }) + { + wasParamCheckFailure = true; + } var value_local = value_raw; if (wasParamCheckFailure) @@ -314,21 +411,25 @@ namespace Microsoft.AspNetCore.Http.Generated return Task.CompletedTask; } httpContext.Response.ContentType ??= "text/plain"; - var result = handler(value_local); + var result = handler(value_local!); return httpContext.Response.WriteAsync(result); } async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: value (Type = string?, IsOptional = True, Source = RouteOrQuery) - var value_raw = GeneratedRouteBuilderExtensionsCore.ResolveFromRouteOrQuery(httpContext, "value", options?.RouteParameterNames); + // Endpoint Parameter: value (Type = string, IsOptional = False, Source = RouteOrQuery) + var value_raw = options?.RouteParameterNames?.Contains("value", StringComparer.OrdinalIgnoreCase) == true? new StringValues(httpContext.Request.RouteValues[$"value"]?.ToString()): httpContext.Request.Query[$"value"];; + if (value_raw is StringValues { Count: 0 }) + { + wasParamCheckFailure = true; + } var value_local = value_raw; if (wasParamCheckFailure) { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, value_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, value_local!)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -417,13 +518,6 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } - - private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) - { - return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true - ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) - : httpContext.Request.Query[$"{parameterName}"]; - } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt index 9b44d2ff226f..f3e5aef16d89 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt @@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0), ic.GetArgument(1))); + return ValueTask.FromResult(handler(ic.GetArgument(0), ic.GetArgument(1))); }, options.EndpointBuilder, handler.Method); @@ -222,13 +222,6 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } - - private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) - { - return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true - ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) - : httpContext.Request.Query[$"{parameterName}"]; - } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt index 51a769a098aa..d0304c9c4c15 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt @@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0), ic.GetArgument(1))); + return ValueTask.FromResult(handler(ic.GetArgument(0), ic.GetArgument(1))); }, options.EndpointBuilder, handler.Method); @@ -250,13 +250,6 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } - - private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) - { - return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true - ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) - : httpContext.Request.Query[$"{parameterName}"]; - } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt index bbf5cacea4db..c784a70f380c 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt @@ -220,13 +220,6 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } - - private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) - { - return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true - ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) - : httpContext.Request.Query[$"{parameterName}"]; - } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt index 0a88867dc913..311224a6f0e3 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt @@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0))); + return ValueTask.FromResult(handler(ic.GetArgument(0))); }, options.EndpointBuilder, handler.Method); @@ -226,13 +226,6 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } - - private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) - { - return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true - ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) - : httpContext.Request.Query[$"{parameterName}"]; - } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt index 7b95b9f115d0..2f9f055e5a71 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt @@ -415,13 +415,6 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } - - private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) - { - return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true - ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) - : httpContext.Request.Query[$"{parameterName}"]; - } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt index 136f98d7b7da..2ad43031cfee 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt @@ -134,7 +134,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0))); + return ValueTask.FromResult(handler(ic.GetArgument(0))); }, options.EndpointBuilder, handler.Method); @@ -189,7 +189,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0))); + return ValueTask.FromResult(handler(ic.GetArgument(0))); }, options.EndpointBuilder, handler.Method); @@ -244,7 +244,7 @@ namespace Microsoft.AspNetCore.Http.Generated { return ValueTask.FromResult(Results.Empty); } - return ValueTask.FromResult(handler(ic.GetArgument(0), ic.GetArgument(1))); + return ValueTask.FromResult(handler(ic.GetArgument(0), ic.GetArgument(1))); }, options.EndpointBuilder, handler.Method); @@ -362,13 +362,6 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } - - private static StringValues ResolveFromRouteOrQuery(HttpContext httpContext, string parameterName, IEnumerable? routeParameterNames) - { - return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true - ? new StringValues(httpContext.Request.RouteValues[$"{parameterName}"]?.ToString()) - : httpContext.Request.Query[$"{parameterName}"]; - } } %GENERATEDCODEATTRIBUTE% diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs index 6c75e73f08c4..f728b5b2f2de 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs @@ -1,5 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Microsoft.AspNetCore.Mvc; using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Http.Generators.Tests; diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs index 31e9ae722952..84e72f281533 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs @@ -7,6 +7,7 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Http.Generators.Tests; @@ -520,7 +521,7 @@ public static object[][] MapAction_ExplicitBodyParam_ComplexReturn_Data .AddEndpointFilter((c, n) => n(c)); """; var fromBodyRequiredSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo) => TypedResults.Ok(todo));"""; - var fromBodyAllowEmptySource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}(AllowEmpty = true)] {typeof(Todo)} todo) => TypedResults.Ok(todo));"""; + var fromBodyAllowEmptySource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}(EmptyBodyBehavior = {typeof(EmptyBodyBehavior)}.Allow)] {typeof(Todo)} todo) => TypedResults.Ok(todo));"""; var fromBodyNullableSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)}? todo) => TypedResults.Ok(todo));"""; var fromBodyDefaultValueSource = $""" #nullable disable @@ -529,7 +530,7 @@ public static object[][] MapAction_ExplicitBodyParam_ComplexReturn_Data #nullable restore """; var fromBodyRequiredWithFilterSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo) => TypedResults.Ok(todo)){withFilter}"""; - var fromBodyAllowEmptyWithFilterSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}(AllowEmpty = true)] {typeof(Todo)} todo) => TypedResults.Ok(todo)){withFilter}"""; + var fromBodyAllowEmptyWithFilterSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}(EmptyBodyBehavior = {typeof(EmptyBodyBehavior)}.Allow)] {typeof(Todo)} todo) => TypedResults.Ok(todo)){withFilter}"""; var fromBodyNullableWithFilterSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)}? todo) => TypedResults.Ok(todo)){withFilter}"""; var fromBodyDefaultValueWithFilterSource = $""" #nullable disable @@ -940,10 +941,11 @@ public async Task MapAction_ExplicitServiceParam_SimpleReturn_Snapshot() public async Task MapAction_ExplicitSource_SimpleReturn_Snapshot() { var source = $$""" -app.MapGet("/fromQuery", ([{{typeof(FromQueryAttribute)}}] string? queryValue) => queryValue ?? string.Empty); -app.MapGet("/fromHeader", ([{{typeof(FromHeaderAttribute)}}] string? headerValue) => headerValue ?? string.Empty); -app.MapGet("/fromHeader/{routeValue}", ([{{typeof(FromRouteAttribute)}}] string? routeValue) => routeValue ?? string.Empty); -app.MapGet("/{value}", (string? value) => value ?? string.Empty); +app.MapGet("/fromQuery", ([{{typeof(FromQueryAttribute)}}] string queryValue) => queryValue ?? string.Empty); +app.MapGet("/fromHeader", ([{{typeof(FromHeaderAttribute)}}] string headerValue) => headerValue ?? string.Empty); +app.MapGet("/fromRoute/{routeValue}", ([{{typeof(FromRouteAttribute)}}] string routeValue) => routeValue ?? string.Empty); +app.MapGet("/fromRouteRequiredImplicit/{value}", (string value) => value); +app.MapGet("/fromQueryRequiredImplicit", (string value) => value); """; var (_, compilation) = await RunGeneratorAsync(source); diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs index c9fd62c7f454..b0649543d49f 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs @@ -15,15 +15,3 @@ public class Todo public string Name { get; set; } = "Todo"; public bool IsComplete { get; set; } } - -public interface ITodo -{ - public int Id { get; } - public string Name { get; } - public bool IsComplete { get; } -} - -public class FromBodyAttribute : Attribute, IFromBodyMetadata -{ - public bool AllowEmpty { get; set; } -} diff --git a/src/Shared/RoslynUtils/WellKnownTypeData.cs b/src/Shared/RoslynUtils/WellKnownTypeData.cs index ba1732b5e45b..8632c5b66f2f 100644 --- a/src/Shared/RoslynUtils/WellKnownTypeData.cs +++ b/src/Shared/RoslynUtils/WellKnownTypeData.cs @@ -101,6 +101,7 @@ public enum WellKnownType Microsoft_AspNetCore_Mvc_ViewFeatures_SaveTempDataAttribute, Microsoft_AspNetCore_Mvc_SkipStatusCodePagesAttribute, Microsoft_AspNetCore_Mvc_ValidateAntiForgeryTokenAttribute, + Microsoft_AspNetCore_Mvc_ModelBinding_EmptyBodyBehavior, Microsoft_AspNetCore_Authorization_AllowAnonymousAttribute, Microsoft_AspNetCore_Authorization_AuthorizeAttribute } @@ -201,6 +202,7 @@ public enum WellKnownType "Microsoft.AspNetCore.Mvc.ViewFeatures.SaveTempDataAttribute", "Microsoft.AspNetCore.Mvc.SkipStatusCodePagesAttribute", "Microsoft.AspNetCore.Mvc.ValidateAntiForgeryTokenAttribute", + "Microsoft.AspNetCore.Mvc.ModelBinding.EmptyBodyBehavior", "Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute", "Microsoft.AspNetCore.Authorization.AuthorizeAttribute" }; From e47e69d78adaf722a5d60ea89671321ed52dfbaf Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Thu, 23 Feb 2023 15:13:54 -0800 Subject: [PATCH 5/7] Remove string interpolation in source under test --- ...Param_ComplexReturn_Snapshot.generated.txt | 8 +- ...eParam_SimpleReturn_Snapshot.generated.txt | 12 +- ...Source_SimpleReturn_Snapshot.generated.txt | 20 ++-- ...pecialTypeParam_StringReturn.generated.txt | 4 +- ...ipleStringParam_StringReturn.generated.txt | 4 +- ...aram_StringReturn_WithFilter.generated.txt | 4 +- ...ngValueProvided_StringReturn.generated.txt | 4 +- ...pAction_NoParam_StringReturn.generated.txt | 16 +-- ...tion_WithParams_StringReturn.generated.txt | 12 +- .../RequestDelegateGeneratorTestBase.cs | 2 + .../RequestDelegateGeneratorTests.cs | 112 +++++++++--------- 11 files changed, 100 insertions(+), 98 deletions(-) diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt index 769f925b994d..f365a0812565 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt @@ -84,11 +84,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 16)] = ( + [(@"TestMapActions.cs", 18)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -151,11 +151,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 18)] = ( + [(@"TestMapActions.cs", 20)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 20)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt index 4be53c6dce38..80943c8df46b 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt @@ -114,11 +114,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 16)] = ( + [(@"TestMapActions.cs", 18)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -173,11 +173,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 17)] = ( + [(@"TestMapActions.cs", 19)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 17)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 19)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -232,11 +232,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 18)] = ( + [(@"TestMapActions.cs", 20)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 20)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt index 06c18d82a72f..bd636d5b516a 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt @@ -84,11 +84,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 16)] = ( + [(@"TestMapActions.cs", 18)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -153,11 +153,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 17)] = ( + [(@"TestMapActions.cs", 19)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 17)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 19)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -222,11 +222,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 18)] = ( + [(@"TestMapActions.cs", 20)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 20)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -299,11 +299,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 19)] = ( + [(@"TestMapActions.cs", 21)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 19)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 21)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -368,11 +368,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 20)] = ( + [(@"TestMapActions.cs", 22)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 20)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 22)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt index f3e5aef16d89..72415067bb11 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt @@ -84,11 +84,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 16)] = ( + [(@"TestMapActions.cs", 18)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt index d0304c9c4c15..5a83758fb6cf 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt @@ -84,11 +84,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 16)] = ( + [(@"TestMapActions.cs", 18)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt index c784a70f380c..b9924bfcc456 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_NoParam_StringReturn_WithFilter.generated.txt @@ -84,11 +84,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 16)] = ( + [(@"TestMapActions.cs", 18)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt index 311224a6f0e3..a485faffb44c 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt @@ -84,11 +84,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 16)] = ( + [(@"TestMapActions.cs", 18)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt index 2f9f055e5a71..710b0e849edc 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_NoParam_StringReturn.generated.txt @@ -114,11 +114,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 16)] = ( + [(@"TestMapActions.cs", 18)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -169,11 +169,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 17)] = ( + [(@"TestMapActions.cs", 19)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 17)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 19)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -224,11 +224,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 18)] = ( + [(@"TestMapActions.cs", 20)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 20)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -279,11 +279,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 19)] = ( + [(@"TestMapActions.cs", 21)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 19)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 21)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt index 2ad43031cfee..910981621629 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt @@ -114,11 +114,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 16)] = ( + [(@"TestMapActions.cs", 18)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 16)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -169,11 +169,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 17)] = ( + [(@"TestMapActions.cs", 19)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 17)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 19)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -224,11 +224,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 18)] = ( + [(@"TestMapActions.cs", 20)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 18)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 20)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs index 5f6c77e714e3..b9a57c00683c 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs @@ -195,7 +195,9 @@ private static string GetMapActionString(string sources) => $$""" using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Metadata; using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Routing; +using Microsoft.AspNetCore.Http.Generators.Tests; public static class TestMapActions { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs index 84e72f281533..a8345a886449 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs @@ -43,12 +43,12 @@ public static object[][] MapAction_ExplicitQueryParam_StringReturn_Data get { var expectedBody = "TestQueryValue"; - var fromQueryRequiredSource = $"""app.MapGet("/", ([{typeof(FromQueryAttribute)}] string queryValue) => queryValue);"""; - var fromQueryWithNameRequiredSource = $"""app.MapGet("/", ([{typeof(FromQueryAttribute)}(Name = "queryValue")] string parameterName) => parameterName);"""; - var fromQueryNullableSource = $"""app.MapGet("/", ([{typeof(FromQueryAttribute)}] string? queryValue) => queryValue ?? string.Empty);"""; - var fromQueryDefaultValueSource = $""" + var fromQueryRequiredSource = """app.MapGet("/", ([FromQuery] string queryValue) => queryValue);"""; + var fromQueryWithNameRequiredSource = """app.MapGet("/", ([FromQuery(Name = "queryValue")] string parameterName) => parameterName);"""; + var fromQueryNullableSource = """app.MapGet("/", ([FromQuery] string? queryValue) => queryValue ?? string.Empty);"""; + var fromQueryDefaultValueSource = """ #nullable disable -string getQueryWithDefault([{typeof(FromQueryAttribute)}] string queryValue = null) => queryValue ?? string.Empty; +string getQueryWithDefault([FromQuery] string queryValue = null) => queryValue ?? string.Empty; app.MapGet("/", getQueryWithDefault); #nullable restore """; @@ -279,12 +279,12 @@ public async Task MapAction_NoParam_AnyReturn(string source, string expectedBody public static IEnumerable MapAction_NoParam_ComplexReturn_Data => new List() { - new object[] { $$"""app.MapGet("/", () => new {{typeof(Todo)}}() { Name = "Test Item"});""" }, - new object[] { $$""" -object GetTodo() => new {{typeof(Todo)}}() { Name = "Test Item"}; + new object[] { """app.MapGet("/", () => new Todo() { Name = "Test Item"});""" }, + new object[] { """ +object GetTodo() => new Todo() { Name = "Test Item"}; app.MapGet("/", GetTodo); """}, - new object[] { $$"""app.MapGet("/", () => TypedResults.Ok(new {{typeof(Todo)}}() { Name = "Test Item"}));""" } + new object[] { """app.MapGet("/", () => TypedResults.Ok(new Todo() { Name = "Test Item"}));""" } }; [Theory] @@ -310,7 +310,7 @@ public async Task MapAction_NoParam_ComplexReturn(string source) new object[] { @"app.MapGet(""/"", () => Console.WriteLine(""Returns void""));", null }, new object[] { @"app.MapGet(""/"", () => TypedResults.Ok(""Alright!""));", null }, new object[] { @"app.MapGet(""/"", () => Results.NotFound(""Oops!""));", null }, - new object[] { $$"""app.MapGet("/", () => Task.FromResult(new {{typeof(Todo)}}() { Name = "Test Item"}));""", "application/json" }, + new object[] { @"app.MapGet(""/"", () => Task.FromResult(new Todo() { Name = ""Test Item"" }));", "application/json" }, new object[] { @"app.MapGet(""/"", () => ""Hello world!"");", "text/plain" } }; @@ -330,8 +330,8 @@ public async Task MapAction_ProducesCorrectContentType(string source, string exp public static IEnumerable MapAction_NoParam_TaskOfTReturn_Data => new List() { new object[] { @"app.MapGet(""/"", () => Task.FromResult(""Hello world!""));", "Hello world!" }, - new object[] { $$"""app.MapGet("/", () => Task.FromResult(new {{typeof(Todo)}}() { Name = "Test Item"}));""", """{"id":0,"name":"Test Item","isComplete":false}""" }, - new object[] { $$"""app.MapGet("/", () => Task.FromResult(TypedResults.Ok(new {{typeof(Todo)}}() { Name = "Test Item"})));""", """{"id":0,"name":"Test Item","isComplete":false}""" } + new object[] { @"app.MapGet(""/"", () => Task.FromResult(new Todo() { Name = ""Test Item"" }));", """{"id":0,"name":"Test Item","isComplete":false}""" }, + new object[] { @"app.MapGet(""/"", () => Task.FromResult(TypedResults.Ok(new Todo() { Name = ""Test Item"" })));", """{"id":0,"name":"Test Item","isComplete":false}""" } }; [Theory] @@ -355,8 +355,8 @@ public async Task MapAction_NoParam_TaskOfTReturn(string source, string expected public static IEnumerable MapAction_NoParam_ValueTaskOfTReturn_Data => new List() { new object[] { @"app.MapGet(""/"", () => ValueTask.FromResult(""Hello world!""));", "Hello world!" }, - new object[] { $$"""app.MapGet("/", () => ValueTask.FromResult(new {{typeof(Todo)}}() { Name = "Test Item"}));""", """{"id":0,"name":"Test Item","isComplete":false}""" }, - new object[] { $$"""app.MapGet("/", () => ValueTask.FromResult(TypedResults.Ok(new {{typeof(Todo)}}() { Name = "Test Item"})));""", """{"id":0,"name":"Test Item","isComplete":false}""" } + new object[] { @"app.MapGet(""/"", () => ValueTask.FromResult(new Todo() { Name = ""Test Item""}));", """{"id":0,"name":"Test Item","isComplete":false}""" }, + new object[] { @"app.MapGet(""/"", () => ValueTask.FromResult(TypedResults.Ok(new Todo() { Name = ""Test Item""})));", """{"id":0,"name":"Test Item","isComplete":false}""" } }; [Theory] @@ -381,10 +381,10 @@ public async Task MapAction_NoParam_ValueTaskOfTReturn(string source, string exp { new object[] { @"app.MapGet(""/"", () => new ValueTask(""Hello world!""));", "Hello world!" }, new object[] { @"app.MapGet(""/"", () => Task.FromResult(""Hello world!""));", "Hello world!" }, - new object[] { $$"""app.MapGet("/", () => new ValueTask(new {{typeof(Todo)}}() { Name = "Test Item"}));""", """{"id":0,"name":"Test Item","isComplete":false}""" }, - new object[] { $$"""app.MapGet("/", () => Task.FromResult(new {{typeof(Todo)}}() { Name = "Test Item"}));""", """{"id":0,"name":"Test Item","isComplete":false}""" }, - new object[] { $$"""app.MapGet("/", () => new ValueTask(TypedResults.Ok(new {{typeof(Todo)}}() { Name = "Test Item"})));""", """{"id":0,"name":"Test Item","isComplete":false}""" }, - new object[] { $$"""app.MapGet("/", () => Task.FromResult(TypedResults.Ok(new {{typeof(Todo)}}() { Name = "Test Item"})));""", """{"id":0,"name":"Test Item","isComplete":false}""" } + new object[] { @"app.MapGet(""/"", () => new ValueTask(new Todo() { Name = ""Test Item""}));", """{"id":0,"name":"Test Item","isComplete":false}""" }, + new object[] { @"app.MapGet(""/"", () => Task.FromResult(new Todo() { Name = ""Test Item""}));", """{"id":0,"name":"Test Item","isComplete":false}""" }, + new object[] { @"app.MapGet(""/"", () => new ValueTask(TypedResults.Ok(new Todo() { Name = ""Test Item""})));", """{"id":0,"name":"Test Item","isComplete":false}""" }, + new object[] { @"app.MapGet(""/"", () => Task.FromResult(TypedResults.Ok(new Todo() { Name = ""Test Item""})));", """{"id":0,"name":"Test Item","isComplete":false}""" } }; [Theory] @@ -520,21 +520,21 @@ public static object[][] MapAction_ExplicitBodyParam_ComplexReturn_Data var withFilter = """ .AddEndpointFilter((c, n) => n(c)); """; - var fromBodyRequiredSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo) => TypedResults.Ok(todo));"""; - var fromBodyAllowEmptySource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}(EmptyBodyBehavior = {typeof(EmptyBodyBehavior)}.Allow)] {typeof(Todo)} todo) => TypedResults.Ok(todo));"""; - var fromBodyNullableSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)}? todo) => TypedResults.Ok(todo));"""; - var fromBodyDefaultValueSource = $""" + var fromBodyRequiredSource = """app.MapPost("/", ([FromBody] Todo todo) => TypedResults.Ok(todo));"""; + var fromBodyAllowEmptySource = """app.MapPost("/", ([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Todo todo) => TypedResults.Ok(todo));"""; + var fromBodyNullableSource = """app.MapPost("/", ([FromBody] Todo? todo) => TypedResults.Ok(todo));"""; + var fromBodyDefaultValueSource = """ #nullable disable -IResult postTodoWithDefault([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo = null) => TypedResults.Ok(todo); +IResult postTodoWithDefault([FromBody] Todo todo = null) => TypedResults.Ok(todo); app.MapPost("/", postTodoWithDefault); #nullable restore """; - var fromBodyRequiredWithFilterSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo) => TypedResults.Ok(todo)){withFilter}"""; - var fromBodyAllowEmptyWithFilterSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}(EmptyBodyBehavior = {typeof(EmptyBodyBehavior)}.Allow)] {typeof(Todo)} todo) => TypedResults.Ok(todo)){withFilter}"""; - var fromBodyNullableWithFilterSource = $"""app.MapPost("/", ([{typeof(FromBodyAttribute)}] {typeof(Todo)}? todo) => TypedResults.Ok(todo)){withFilter}"""; + var fromBodyRequiredWithFilterSource = $"""app.MapPost("/", ([FromBody] Todo todo) => TypedResults.Ok(todo)){withFilter}"""; + var fromBodyAllowEmptyWithFilterSource = $"""app.MapPost("/", ([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Todo todo) => TypedResults.Ok(todo)){withFilter}"""; + var fromBodyNullableWithFilterSource = $"""app.MapPost("/", ([FromBody] Todo? todo) => TypedResults.Ok(todo)){withFilter}"""; var fromBodyDefaultValueWithFilterSource = $""" #nullable disable -IResult postTodoWithDefault([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo = null) => TypedResults.Ok(todo); +IResult postTodoWithDefault([FromBody] Todo todo = null) => TypedResults.Ok(todo); app.MapPost("/", postTodoWithDefault){withFilter} #nullable restore """; @@ -592,9 +592,9 @@ public async Task MapAction_ExplicitBodyParam_ComplexReturn_Snapshot() IsComplete = false }; var source = $""" -app.MapPost("/fromBodyRequired", ([{typeof(FromBodyAttribute)}] {typeof(Todo)} todo) => TypedResults.Ok(todo)); +app.MapPost("/fromBodyRequired", ([FromBody] Todo todo) => TypedResults.Ok(todo)); #pragma warning disable CS8622 -app.MapPost("/fromBodyOptional", ([{typeof(FromBodyAttribute)}] {typeof(Todo)}? todo) => TypedResults.Ok(todo)); +app.MapPost("/fromBodyOptional", ([FromBody] Todo? todo) => TypedResults.Ok(todo)); #pragma warning restore CS8622 """; var (_, compilation) = await RunGeneratorAsync(source); @@ -631,12 +631,12 @@ public static object[][] MapAction_ExplicitHeaderParam_SimpleReturn_Data get { var expectedBody = "Test header value"; - var fromHeaderRequiredSource = $"""app.MapGet("/", ([{typeof(FromHeaderAttribute)}] string headerValue) => headerValue);"""; - var fromHeaderWithNameRequiredSource = $"""app.MapGet("/", ([{typeof(FromHeaderAttribute)}(Name = "headerValue")] string parameterName) => parameterName);"""; - var fromHeaderNullableSource = $"""app.MapGet("/", ([{typeof(FromHeaderAttribute)}] string? headerValue) => headerValue ?? string.Empty);"""; - var fromHeaderDefaultValueSource = $""" + var fromHeaderRequiredSource = """app.MapGet("/", ([FromHeader] string headerValue) => headerValue);"""; + var fromHeaderWithNameRequiredSource = """app.MapGet("/", ([FromHeader(Name = "headerValue")] string parameterName) => parameterName);"""; + var fromHeaderNullableSource = """app.MapGet("/", ([FromHeader] string? headerValue) => headerValue ?? string.Empty);"""; + var fromHeaderDefaultValueSource = """ #nullable disable -string getHeaderWithDefault([{typeof(FromHeaderAttribute)}] string headerValue = null) => headerValue ?? string.Empty; +string getHeaderWithDefault([FromHeader] string headerValue = null) => headerValue ?? string.Empty; app.MapGet("/", getHeaderWithDefault); #nullable restore """; @@ -677,12 +677,12 @@ public static object[][] MapAction_ExplicitRouteParam_SimpleReturn_Data get { var expectedBody = "Test route value"; - var fromRouteRequiredSource = $$"""app.MapGet("/{routeValue}", ([{{typeof(FromRouteAttribute)}}] string routeValue) => routeValue);"""; - var fromRouteWithNameRequiredSource = $$"""app.MapGet("/{routeValue}", ([{{typeof(FromRouteAttribute)}}(Name = "routeValue" )] string parameterName) => parameterName);"""; - var fromRouteNullableSource = $$"""app.MapGet("/{routeValue}", ([{{typeof(FromRouteAttribute)}}] string? routeValue) => routeValue ?? string.Empty);"""; - var fromRouteDefaultValueSource = $$""" + var fromRouteRequiredSource = """app.MapGet("/{routeValue}", ([FromRoute] string routeValue) => routeValue);"""; + var fromRouteWithNameRequiredSource = """app.MapGet("/{routeValue}", ([FromRoute(Name = "routeValue" )] string parameterName) => parameterName);"""; + var fromRouteNullableSource = """app.MapGet("/{routeValue}", ([FromRoute] string? routeValue) => routeValue ?? string.Empty);"""; + var fromRouteDefaultValueSource = """ #nullable disable -string getRouteWithDefault([{{typeof(FromRouteAttribute)}}] string routeValue = null) => routeValue ?? string.Empty; +string getRouteWithDefault([FromRoute] string routeValue = null) => routeValue ?? string.Empty; app.MapGet("/{routeValue}", getRouteWithDefault); #nullable restore """; @@ -721,7 +721,7 @@ public async Task MapAction_ExplicitRouteParam_SimpleReturn(string source, strin [Fact] public async Task MapAction_ExplicitRouteParamWithInvalidName_SimpleReturn() { - var source = $$"""app.MapGet("/{routeValue}", ([{{typeof(FromRouteAttribute)}}(Name = "invalidName" )] string parameterName) => parameterName);"""; + var source = $$"""app.MapGet("/{routeValue}", ([FromRoute(Name = "invalidName" )] string parameterName) => parameterName);"""; var (_, compilation) = await RunGeneratorAsync(source); var endpoint = GetEndpointFromCompilation(compilation); @@ -825,20 +825,20 @@ public static object[][] MapAction_ExplicitServiceParam_SimpleReturn_Data { get { - var fromServiceRequiredSource = $$"""app.MapPost("/", ([{{typeof(FromServicesAttribute)}}]{{typeof(TestService)}} svc) => svc.TestServiceMethod());"""; - var fromServiceNullableSource = $$"""app.MapPost("/", ([{{typeof(FromServicesAttribute)}}]{{typeof(TestService)}}? svc) => svc?.TestServiceMethod() ?? string.Empty);"""; - var fromServiceDefaultValueSource = $$""" + var fromServiceRequiredSource = """app.MapPost("/", ([FromServices]TestService svc) => svc.TestServiceMethod());"""; + var fromServiceNullableSource = """app.MapPost("/", ([FromServices]TestService? svc) => svc?.TestServiceMethod() ?? string.Empty);"""; + var fromServiceDefaultValueSource = """ #nullable disable -string postServiceWithDefault([{{typeof(FromServicesAttribute)}}]{{typeof(TestService)}} svc = null) => svc?.TestServiceMethod() ?? string.Empty; +string postServiceWithDefault([FromServices]TestService svc = null) => svc?.TestServiceMethod() ?? string.Empty; app.MapPost("/", postServiceWithDefault); #nullable restore """; - var fromServiceEnumerableRequiredSource = $$"""app.MapPost("/", ([{{typeof(FromServicesAttribute)}}]IEnumerable<{{typeof(TestService)}}> svc) => svc.FirstOrDefault()?.TestServiceMethod() ?? string.Empty);"""; - var fromServiceEnumerableNullableSource = $$"""app.MapPost("/", ([{{typeof(FromServicesAttribute)}}]IEnumerable<{{typeof(TestService)}}>? svc) => svc?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty);"""; - var fromServiceEnumerableDefaultValueSource = $$""" + var fromServiceEnumerableRequiredSource = """app.MapPost("/", ([FromServices]IEnumerable svc) => svc.FirstOrDefault()?.TestServiceMethod() ?? string.Empty);"""; + var fromServiceEnumerableNullableSource = """app.MapPost("/", ([FromServices]IEnumerable? svc) => svc?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty);"""; + var fromServiceEnumerableDefaultValueSource = """ #nullable disable -string postServiceWithDefault([{{typeof(FromServicesAttribute)}}]IEnumerable<{{typeof(TestService)}}> svc = null) => svc?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty; +string postServiceWithDefault([FromServices]IEnumerable svc = null) => svc?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty; app.MapPost("/", postServiceWithDefault); #nullable restore """; @@ -892,10 +892,10 @@ public async Task MapAction_ExplicitServiceParam_SimpleReturn(string source, boo [Fact] public async Task MapAction_ExplicitServiceParam_SimpleReturn_Snapshot() { - var source = $$""" -app.MapGet("/fromServiceRequired", ([{{typeof(FromServicesAttribute)}}]{{typeof(TestService)}} svc) => svc.TestServiceMethod()); -app.MapGet("/enumerableFromService", ([{{typeof(FromServicesAttribute)}}]IEnumerable<{{typeof(TestService)}}> svc) => svc?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty); -app.MapGet("/multipleFromService", ([{{typeof(FromServicesAttribute)}}]{{typeof(TestService)}}? svc, [FromServices]IEnumerable<{{typeof(TestService)}}> svcs) => + var source = """ +app.MapGet("/fromServiceRequired", ([FromServices]TestService svc) => svc.TestServiceMethod()); +app.MapGet("/enumerableFromService", ([FromServices]IEnumerable svc) => svc?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty); +app.MapGet("/multipleFromService", ([FromServices]TestService? svc, [FromServices]IEnumerable svcs) => $"{(svcs?.FirstOrDefault()?.TestServiceMethod() ?? string.Empty)}, {svc?.TestServiceMethod()}"); """; var httpContext = CreateHttpContext(); @@ -940,10 +940,10 @@ public async Task MapAction_ExplicitServiceParam_SimpleReturn_Snapshot() [Fact] public async Task MapAction_ExplicitSource_SimpleReturn_Snapshot() { - var source = $$""" -app.MapGet("/fromQuery", ([{{typeof(FromQueryAttribute)}}] string queryValue) => queryValue ?? string.Empty); -app.MapGet("/fromHeader", ([{{typeof(FromHeaderAttribute)}}] string headerValue) => headerValue ?? string.Empty); -app.MapGet("/fromRoute/{routeValue}", ([{{typeof(FromRouteAttribute)}}] string routeValue) => routeValue ?? string.Empty); + var source = """ +app.MapGet("/fromQuery", ([FromQuery] string queryValue) => queryValue ?? string.Empty); +app.MapGet("/fromHeader", ([FromHeader] string headerValue) => headerValue ?? string.Empty); +app.MapGet("/fromRoute/{routeValue}", ([FromRoute] string routeValue) => routeValue ?? string.Empty); app.MapGet("/fromRouteRequiredImplicit/{value}", (string value) => value); app.MapGet("/fromQueryRequiredImplicit", (string value) => value); """; From 99f9af90a2127ca578aaf72d113a356eb092f12d Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Fri, 24 Feb 2023 11:38:17 -0800 Subject: [PATCH 6/7] Address review feedback - Guard parameter name from attribute against null values - Support FromBody with AllowEmpty named argument --- .../Emitters/EndpointParameterEmitter.cs | 4 +-- .../EndpointParameter.cs | 20 ++++++------- .../StaticRouteHandlerModel.Emitter.cs | 2 +- ...Param_ComplexReturn_Snapshot.generated.txt | 20 ++++++------- ...eParam_SimpleReturn_Snapshot.generated.txt | 22 +++++++------- ...Source_SimpleReturn_Snapshot.generated.txt | 30 +++++++++---------- ...pecialTypeParam_StringReturn.generated.txt | 2 +- ...ipleStringParam_StringReturn.generated.txt | 10 +++---- ...ngValueProvided_StringReturn.generated.txt | 6 ++-- ...tion_WithParams_StringReturn.generated.txt | 6 ++-- .../RequestDelegateGeneratorTests.cs | 19 ++++++++++-- .../RequestDelegateGenerator/SharedTypes.cs | 5 ++++ src/Shared/RoslynUtils/SymbolExtensions.cs | 6 ++-- 13 files changed, 86 insertions(+), 66 deletions(-) diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs index 092f1fd69847..837ee73c4a55 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs @@ -129,7 +129,7 @@ internal static string EmitJsonBodyParameterPreparationString(this EndpointParam {endpointParameter.EmitParameterDiagnosticComment()} """); - var assigningCode = $"await GeneratedRouteBuilderExtensionsCore.TryResolveBody<{endpointParameter.Type}>(httpContext, {(endpointParameter.IsOptional ? "true" : "false")})"; + var assigningCode = $"await GeneratedRouteBuilderExtensionsCore.TryResolveBody<{endpointParameter.Type.ToDisplayString(EmitterConstants.DisplayFormat)}>(httpContext, {(endpointParameter.IsOptional ? "true" : "false")})"; builder.AppendLine($$""" var (isSuccessful, {{endpointParameter.EmitHandlerArgument()}}) = {{assigningCode}}; """); @@ -172,7 +172,7 @@ internal static string EmitServiceParameterPreparation(this EndpointParameter en } private static string EmitParameterDiagnosticComment(this EndpointParameter endpointParameter) => - $"// Endpoint Parameter: {endpointParameter.Name} (Type = {endpointParameter.Type}, IsOptional = {endpointParameter.IsOptional}, Source = {endpointParameter.Source})"; + $"// Endpoint Parameter: {endpointParameter.Name} (Type = {endpointParameter.Type.ToDisplayString(EmitterConstants.DisplayFormat)}, IsOptional = {endpointParameter.IsOptional}, Source = {endpointParameter.Source})"; private static string EmitHandlerArgument(this EndpointParameter endpointParameter) => $"{endpointParameter.Name}_local"; private static string EmitAssigningCodeResult(this EndpointParameter endpointParameter) => $"{endpointParameter.Name}_raw"; diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs index 4112145966bc..ef493e6b6d35 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs @@ -26,25 +26,19 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp if (parameter.HasAttributeImplementingInterface(fromRouteMetadataInterfaceType, out var fromRouteAttribute)) { Source = EndpointParameterSource.Route; - Name = fromRouteAttribute.TryGetNamedArgumentValue("Name", out var fromRouteName) - ? fromRouteName - : parameter.Name; + Name = GetParameterName(fromRouteAttribute, parameter.Name); IsOptional = parameter.IsOptional(); } else if (parameter.HasAttributeImplementingInterface(fromQueryMetadataInterfaceType, out var fromQueryAttribute)) { Source = EndpointParameterSource.Query; - Name = fromQueryAttribute.TryGetNamedArgumentValue("Name", out var fromQueryName) - ? fromQueryName - : parameter.Name; + Name = GetParameterName(fromQueryAttribute, parameter.Name); IsOptional = parameter.IsOptional(); } else if (parameter.HasAttributeImplementingInterface(fromHeaderMetadataInterfaceType, out var fromHeaderAttribute)) { Source = EndpointParameterSource.Header; - Name = fromHeaderAttribute.TryGetNamedArgumentValue("Name", out var fromHeaderName) - ? fromHeaderName - : parameter.Name; + Name = GetParameterName(fromHeaderAttribute, parameter.Name); IsOptional = parameter.IsOptional(); } else if (TryGetExplicitFromJsonBody(parameter, wellKnownTypes, out var isOptional)) @@ -77,7 +71,7 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp public ITypeSymbol Type { get; } public EndpointParameterSource Source { get; } - // Omly used for SpecialType parameters that need + // Only used for SpecialType parameters that need // to be resolved by a specific WellKnownType internal string? AssigningCode { get; set; } public string Name { get; } @@ -136,10 +130,16 @@ private static bool TryGetExplicitFromJsonBody(IParameterSymbol parameter, return false; } isOptional |= fromBodyAttribute.TryGetNamedArgumentValue("EmptyBodyBehavior", out var emptyBodyBehaviorValue) && emptyBodyBehaviorValue == 1; + isOptional |= fromBodyAttribute.TryGetNamedArgumentValue("AllowEmpty", out var allowEmptyValue) && allowEmptyValue; isOptional |= (parameter.NullableAnnotation == NullableAnnotation.Annotated || parameter.HasExplicitDefaultValue); return true; } + private static string GetParameterName(AttributeData attribute, string parameterName) => + attribute.TryGetNamedArgumentValue("Name", out var fromSourceName) + ? (fromSourceName ?? parameterName) + : parameterName; + public override bool Equals(object obj) => obj is EndpointParameter other && other.Source == Source && diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs index f7df123053fb..4a606629b005 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs @@ -207,7 +207,7 @@ public static string EmitFilterInvocationContextTypeArgs(this Endpoint endpoint) for (var i = 0; i < endpoint.Parameters.Length; i++) { - sb.Append(endpoint.Parameters[i].Type.ToDisplayString(endpoint.Parameters[i].IsOptional ? NullableFlowState.MaybeNull : NullableFlowState.NotNull)); + sb.Append(endpoint.Parameters[i].Type.ToDisplayString(endpoint.Parameters[i].IsOptional ? NullableFlowState.MaybeNull : NullableFlowState.NotNull, EmitterConstants.DisplayFormat)); if (i < endpoint.Parameters.Length - 1) { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt index f365a0812565..7523d383cc99 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitBodyParam_ComplexReturn_Snapshot.generated.txt @@ -113,8 +113,8 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, Source = JsonBody) - var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, false); + // Endpoint Parameter: todo (Type = global::Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, Source = JsonBody) + var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, false); if (!isSuccessful) { return; @@ -132,8 +132,8 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, Source = JsonBody) - var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, false); + // Endpoint Parameter: todo (Type = global::Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, Source = JsonBody) + var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, false); if (!isSuccessful) { return; @@ -143,7 +143,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, todo_local!)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, todo_local!)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -180,8 +180,8 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo?, IsOptional = True, Source = JsonBody) - var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, true); + // Endpoint Parameter: todo (Type = global::Microsoft.AspNetCore.Http.Generators.Tests.Todo?, IsOptional = True, Source = JsonBody) + var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, true); if (!isSuccessful) { return; @@ -199,8 +199,8 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo?, IsOptional = True, Source = JsonBody) - var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, true); + // Endpoint Parameter: todo (Type = global::Microsoft.AspNetCore.Http.Generators.Tests.Todo?, IsOptional = True, Source = JsonBody) + var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, true); if (!isSuccessful) { return; @@ -210,7 +210,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, todo_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, todo_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt index 80943c8df46b..03171192a27f 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitServiceParam_SimpleReturn_Snapshot.generated.txt @@ -143,7 +143,7 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: svc (Type = Microsoft.AspNetCore.Http.Generators.Tests.TestService, IsOptional = False, Source = Service) + // Endpoint Parameter: svc (Type = global::Microsoft.AspNetCore.Http.Generators.Tests.TestService, IsOptional = False, Source = Service) var svc_local = httpContext.RequestServices.GetRequiredService(); if (wasParamCheckFailure) @@ -158,14 +158,14 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: svc (Type = Microsoft.AspNetCore.Http.Generators.Tests.TestService, IsOptional = False, Source = Service) + // Endpoint Parameter: svc (Type = global::Microsoft.AspNetCore.Http.Generators.Tests.TestService, IsOptional = False, Source = Service) var svc_local = httpContext.RequestServices.GetRequiredService(); if (wasParamCheckFailure) { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, svc_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, svc_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -202,7 +202,7 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: svc (Type = System.Collections.Generic.IEnumerable, IsOptional = False, Source = Service) + // Endpoint Parameter: svc (Type = global::System.Collections.Generic.IEnumerable, IsOptional = False, Source = Service) var svc_local = httpContext.RequestServices.GetRequiredService>(); if (wasParamCheckFailure) @@ -217,14 +217,14 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: svc (Type = System.Collections.Generic.IEnumerable, IsOptional = False, Source = Service) + // Endpoint Parameter: svc (Type = global::System.Collections.Generic.IEnumerable, IsOptional = False, Source = Service) var svc_local = httpContext.RequestServices.GetRequiredService>(); if (wasParamCheckFailure) { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext>(httpContext, svc_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext>(httpContext, svc_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -261,10 +261,10 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: svc (Type = Microsoft.AspNetCore.Http.Generators.Tests.TestService?, IsOptional = True, Source = Service) + // Endpoint Parameter: svc (Type = global::Microsoft.AspNetCore.Http.Generators.Tests.TestService?, IsOptional = True, Source = Service) var svc_local = httpContext.RequestServices.GetService();; - // Endpoint Parameter: svcs (Type = System.Collections.Generic.IEnumerable, IsOptional = False, Source = Service) + // Endpoint Parameter: svcs (Type = global::System.Collections.Generic.IEnumerable, IsOptional = False, Source = Service) var svcs_local = httpContext.RequestServices.GetRequiredService>(); if (wasParamCheckFailure) @@ -279,17 +279,17 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: svc (Type = Microsoft.AspNetCore.Http.Generators.Tests.TestService?, IsOptional = True, Source = Service) + // Endpoint Parameter: svc (Type = global::Microsoft.AspNetCore.Http.Generators.Tests.TestService?, IsOptional = True, Source = Service) var svc_local = httpContext.RequestServices.GetService();; - // Endpoint Parameter: svcs (Type = System.Collections.Generic.IEnumerable, IsOptional = False, Source = Service) + // Endpoint Parameter: svcs (Type = global::System.Collections.Generic.IEnumerable, IsOptional = False, Source = Service) var svcs_local = httpContext.RequestServices.GetRequiredService>(); if (wasParamCheckFailure) { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext>(httpContext, svc_local, svcs_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext>(httpContext, svc_local, svcs_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt index bd636d5b516a..6ce289c79b34 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt @@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: queryValue (Type = string, IsOptional = False, Source = Query) + // Endpoint Parameter: queryValue (Type = global::System.String, IsOptional = False, Source = Query) var queryValue_raw = httpContext.Request.Query["queryValue"]; if (StringValues.IsNullOrEmpty(queryValue_raw)) { @@ -133,7 +133,7 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: queryValue (Type = string, IsOptional = False, Source = Query) + // Endpoint Parameter: queryValue (Type = global::System.String, IsOptional = False, Source = Query) var queryValue_raw = httpContext.Request.Query["queryValue"]; if (StringValues.IsNullOrEmpty(queryValue_raw)) { @@ -145,7 +145,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, queryValue_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, queryValue_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -182,7 +182,7 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: headerValue (Type = string, IsOptional = False, Source = Header) + // Endpoint Parameter: headerValue (Type = global::System.String, IsOptional = False, Source = Header) var headerValue_raw = httpContext.Request.Headers["headerValue"]; if (StringValues.IsNullOrEmpty(headerValue_raw)) { @@ -202,7 +202,7 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: headerValue (Type = string, IsOptional = False, Source = Header) + // Endpoint Parameter: headerValue (Type = global::System.String, IsOptional = False, Source = Header) var headerValue_raw = httpContext.Request.Headers["headerValue"]; if (StringValues.IsNullOrEmpty(headerValue_raw)) { @@ -214,7 +214,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, headerValue_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, headerValue_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -251,7 +251,7 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: routeValue (Type = string, IsOptional = False, Source = Route) + // Endpoint Parameter: routeValue (Type = global::System.String, IsOptional = False, Source = Route) if (options?.RouteParameterNames?.Contains("routeValue", StringComparer.OrdinalIgnoreCase) != true) { throw new InvalidOperationException($"'routeValue' is not a route parameter."); @@ -275,7 +275,7 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: routeValue (Type = string, IsOptional = False, Source = Route) + // Endpoint Parameter: routeValue (Type = global::System.String, IsOptional = False, Source = Route) if (options?.RouteParameterNames?.Contains("routeValue", StringComparer.OrdinalIgnoreCase) != true) { throw new InvalidOperationException($"'routeValue' is not a route parameter."); @@ -291,7 +291,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, routeValue_local!)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, routeValue_local!)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -328,7 +328,7 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: value (Type = string, IsOptional = False, Source = RouteOrQuery) + // Endpoint Parameter: value (Type = global::System.String, IsOptional = False, Source = RouteOrQuery) var value_raw = options?.RouteParameterNames?.Contains("value", StringComparer.OrdinalIgnoreCase) == true? new StringValues(httpContext.Request.RouteValues[$"value"]?.ToString()): httpContext.Request.Query[$"value"];; if (value_raw is StringValues { Count: 0 }) { @@ -348,7 +348,7 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: value (Type = string, IsOptional = False, Source = RouteOrQuery) + // Endpoint Parameter: value (Type = global::System.String, IsOptional = False, Source = RouteOrQuery) var value_raw = options?.RouteParameterNames?.Contains("value", StringComparer.OrdinalIgnoreCase) == true? new StringValues(httpContext.Request.RouteValues[$"value"]?.ToString()): httpContext.Request.Query[$"value"];; if (value_raw is StringValues { Count: 0 }) { @@ -360,7 +360,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, value_local!)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, value_local!)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -397,7 +397,7 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: value (Type = string, IsOptional = False, Source = RouteOrQuery) + // Endpoint Parameter: value (Type = global::System.String, IsOptional = False, Source = RouteOrQuery) var value_raw = options?.RouteParameterNames?.Contains("value", StringComparer.OrdinalIgnoreCase) == true? new StringValues(httpContext.Request.RouteValues[$"value"]?.ToString()): httpContext.Request.Query[$"value"];; if (value_raw is StringValues { Count: 0 }) { @@ -417,7 +417,7 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: value (Type = string, IsOptional = False, Source = RouteOrQuery) + // Endpoint Parameter: value (Type = global::System.String, IsOptional = False, Source = RouteOrQuery) var value_raw = options?.RouteParameterNames?.Contains("value", StringComparer.OrdinalIgnoreCase) == true? new StringValues(httpContext.Request.RouteValues[$"value"]?.ToString()): httpContext.Request.Query[$"value"];; if (value_raw is StringValues { Count: 0 }) { @@ -429,7 +429,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, value_local!)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, value_local!)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt index 72415067bb11..12fe9c871b46 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt @@ -133,7 +133,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, req_local, res_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, req_local, res_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt index 5a83758fb6cf..d0e6b6258132 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt @@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: p1 (Type = string, IsOptional = False, Source = Query) + // Endpoint Parameter: p1 (Type = global::System.String, IsOptional = False, Source = Query) var p1_raw = httpContext.Request.Query["p1"]; if (StringValues.IsNullOrEmpty(p1_raw)) { @@ -121,7 +121,7 @@ namespace Microsoft.AspNetCore.Http.Generated } var p1_local = p1_raw.ToString(); - // Endpoint Parameter: p2 (Type = string, IsOptional = False, Source = Query) + // Endpoint Parameter: p2 (Type = global::System.String, IsOptional = False, Source = Query) var p2_raw = httpContext.Request.Query["p2"]; if (StringValues.IsNullOrEmpty(p2_raw)) { @@ -141,7 +141,7 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: p1 (Type = string, IsOptional = False, Source = Query) + // Endpoint Parameter: p1 (Type = global::System.String, IsOptional = False, Source = Query) var p1_raw = httpContext.Request.Query["p1"]; if (StringValues.IsNullOrEmpty(p1_raw)) { @@ -149,7 +149,7 @@ namespace Microsoft.AspNetCore.Http.Generated } var p1_local = p1_raw.ToString(); - // Endpoint Parameter: p2 (Type = string, IsOptional = False, Source = Query) + // Endpoint Parameter: p2 (Type = global::System.String, IsOptional = False, Source = Query) var p2_raw = httpContext.Request.Query["p2"]; if (StringValues.IsNullOrEmpty(p2_raw)) { @@ -161,7 +161,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, p1_local, p2_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, p1_local, p2_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt index a485faffb44c..629831833520 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt @@ -113,7 +113,7 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: p (Type = string?, IsOptional = True, Source = Query) + // Endpoint Parameter: p (Type = global::System.String?, IsOptional = True, Source = Query) var p_raw = httpContext.Request.Query["p"]; var p_local = p_raw.Count > 0 ? p_raw.ToString() : null; @@ -129,7 +129,7 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: p (Type = string?, IsOptional = True, Source = Query) + // Endpoint Parameter: p (Type = global::System.String?, IsOptional = True, Source = Query) var p_raw = httpContext.Request.Query["p"]; var p_local = p_raw.Count > 0 ? p_raw.ToString() : null; @@ -137,7 +137,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, p_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, p_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt index 910981621629..e1c2ea654f0f 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/Multiple_MapAction_WithParams_StringReturn.generated.txt @@ -161,7 +161,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, req_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, req_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -216,7 +216,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, res_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, res_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -273,7 +273,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, req_local, res_local)); + var result = await filteredInvocation(new EndpointFilterInvocationContext(httpContext, req_local, res_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs index a8345a886449..8766a3f5e58e 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs @@ -45,6 +45,7 @@ public static object[][] MapAction_ExplicitQueryParam_StringReturn_Data var expectedBody = "TestQueryValue"; var fromQueryRequiredSource = """app.MapGet("/", ([FromQuery] string queryValue) => queryValue);"""; var fromQueryWithNameRequiredSource = """app.MapGet("/", ([FromQuery(Name = "queryValue")] string parameterName) => parameterName);"""; + var fromQueryWithNullNameRequiredSource = """app.MapGet("/", ([FromQuery(Name = null)] string queryValue) => queryValue);"""; var fromQueryNullableSource = """app.MapGet("/", ([FromQuery] string? queryValue) => queryValue ?? string.Empty);"""; var fromQueryDefaultValueSource = """ #nullable disable @@ -59,6 +60,8 @@ public static object[][] MapAction_ExplicitQueryParam_StringReturn_Data new object[] { fromQueryRequiredSource, null, 400, string.Empty }, new object[] { fromQueryWithNameRequiredSource, expectedBody, 200, expectedBody }, new object[] { fromQueryWithNameRequiredSource, null, 400, string.Empty }, + new object[] { fromQueryWithNullNameRequiredSource, expectedBody, 200, expectedBody }, + new object[] { fromQueryWithNullNameRequiredSource, null, 400, string.Empty }, new object[] { fromQueryNullableSource, expectedBody, 200, expectedBody }, new object[] { fromQueryNullableSource, null, 200, string.Empty }, new object[] { fromQueryDefaultValueSource, expectedBody, 200, expectedBody }, @@ -521,7 +524,8 @@ public static object[][] MapAction_ExplicitBodyParam_ComplexReturn_Data .AddEndpointFilter((c, n) => n(c)); """; var fromBodyRequiredSource = """app.MapPost("/", ([FromBody] Todo todo) => TypedResults.Ok(todo));"""; - var fromBodyAllowEmptySource = """app.MapPost("/", ([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Todo todo) => TypedResults.Ok(todo));"""; + var fromBodyEmptyBodyBehaviorSource = """app.MapPost("/", ([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Todo todo) => TypedResults.Ok(todo));"""; + var fromBodyAllowEmptySource = """app.MapPost("/", ([CustomFromBody(AllowEmpty = true)] Todo todo) => TypedResults.Ok(todo));"""; var fromBodyNullableSource = """app.MapPost("/", ([FromBody] Todo? todo) => TypedResults.Ok(todo));"""; var fromBodyDefaultValueSource = """ #nullable disable @@ -530,7 +534,8 @@ public static object[][] MapAction_ExplicitBodyParam_ComplexReturn_Data #nullable restore """; var fromBodyRequiredWithFilterSource = $"""app.MapPost("/", ([FromBody] Todo todo) => TypedResults.Ok(todo)){withFilter}"""; - var fromBodyAllowEmptyWithFilterSource = $"""app.MapPost("/", ([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Todo todo) => TypedResults.Ok(todo)){withFilter}"""; + var fromBodyEmptyBehaviorWithFilterSource = $"""app.MapPost("/", ([FromBody(EmptyBodyBehavior = EmptyBodyBehavior.Allow)] Todo todo) => TypedResults.Ok(todo)){withFilter}"""; + var fromBodyAllowEmptyWithFilterSource = $"""app.MapPost("/", ([CustomFromBody(AllowEmpty = true)] Todo todo) => TypedResults.Ok(todo)){withFilter}"""; var fromBodyNullableWithFilterSource = $"""app.MapPost("/", ([FromBody] Todo? todo) => TypedResults.Ok(todo)){withFilter}"""; var fromBodyDefaultValueWithFilterSource = $""" #nullable disable @@ -543,6 +548,8 @@ public static object[][] MapAction_ExplicitBodyParam_ComplexReturn_Data { new object[] { fromBodyRequiredSource, todo, 200, expectedBody }, new object[] { fromBodyRequiredSource, null, 400, string.Empty }, + new object[] { fromBodyEmptyBodyBehaviorSource, todo, 200, expectedBody }, + new object[] { fromBodyEmptyBodyBehaviorSource, null, 200, string.Empty }, new object[] { fromBodyAllowEmptySource, todo, 200, expectedBody }, new object[] { fromBodyAllowEmptySource, null, 200, string.Empty }, new object[] { fromBodyNullableSource, todo, 200, expectedBody }, @@ -551,6 +558,8 @@ public static object[][] MapAction_ExplicitBodyParam_ComplexReturn_Data new object[] { fromBodyDefaultValueSource, null, 200, string.Empty }, new object[] { fromBodyRequiredWithFilterSource, todo, 200, expectedBody }, new object[] { fromBodyRequiredWithFilterSource, null, 400, string.Empty }, + new object[] { fromBodyEmptyBehaviorWithFilterSource, todo, 200, expectedBody }, + new object[] { fromBodyEmptyBehaviorWithFilterSource, null, 200, string.Empty }, new object[] { fromBodyAllowEmptyWithFilterSource, todo, 200, expectedBody }, new object[] { fromBodyAllowEmptyWithFilterSource, null, 200, string.Empty }, new object[] { fromBodyNullableWithFilterSource, todo, 200, expectedBody }, @@ -633,6 +642,7 @@ public static object[][] MapAction_ExplicitHeaderParam_SimpleReturn_Data var expectedBody = "Test header value"; var fromHeaderRequiredSource = """app.MapGet("/", ([FromHeader] string headerValue) => headerValue);"""; var fromHeaderWithNameRequiredSource = """app.MapGet("/", ([FromHeader(Name = "headerValue")] string parameterName) => parameterName);"""; + var fromHeaderWithNullNameRequiredSource = """app.MapGet("/", ([FromHeader(Name = null)] string headerValue) => headerValue);"""; var fromHeaderNullableSource = """app.MapGet("/", ([FromHeader] string? headerValue) => headerValue ?? string.Empty);"""; var fromHeaderDefaultValueSource = """ #nullable disable @@ -647,6 +657,8 @@ public static object[][] MapAction_ExplicitHeaderParam_SimpleReturn_Data new object[] { fromHeaderRequiredSource, null, 400, string.Empty }, new object[] { fromHeaderWithNameRequiredSource, expectedBody, 200, expectedBody }, new object[] { fromHeaderWithNameRequiredSource, null, 400, string.Empty }, + new object[] { fromHeaderWithNullNameRequiredSource, expectedBody, 200, expectedBody }, + new object[] { fromHeaderWithNullNameRequiredSource, null, 400, string.Empty }, new object[] { fromHeaderNullableSource, expectedBody, 200, expectedBody }, new object[] { fromHeaderNullableSource, null, 200, string.Empty }, new object[] { fromHeaderDefaultValueSource, expectedBody, 200, expectedBody }, @@ -679,6 +691,7 @@ public static object[][] MapAction_ExplicitRouteParam_SimpleReturn_Data var expectedBody = "Test route value"; var fromRouteRequiredSource = """app.MapGet("/{routeValue}", ([FromRoute] string routeValue) => routeValue);"""; var fromRouteWithNameRequiredSource = """app.MapGet("/{routeValue}", ([FromRoute(Name = "routeValue" )] string parameterName) => parameterName);"""; + var fromRouteWithNullNameRequiredSource = """app.MapGet("/{routeValue}", ([FromRoute(Name = null )] string routeValue) => routeValue);"""; var fromRouteNullableSource = """app.MapGet("/{routeValue}", ([FromRoute] string? routeValue) => routeValue ?? string.Empty);"""; var fromRouteDefaultValueSource = """ #nullable disable @@ -693,6 +706,8 @@ public static object[][] MapAction_ExplicitRouteParam_SimpleReturn_Data new object[] { fromRouteRequiredSource, null, 400, string.Empty }, new object[] { fromRouteWithNameRequiredSource, expectedBody, 200, expectedBody }, new object[] { fromRouteWithNameRequiredSource, null, 400, string.Empty }, + new object[] { fromRouteWithNullNameRequiredSource, expectedBody, 200, expectedBody }, + new object[] { fromRouteWithNullNameRequiredSource, null, 400, string.Empty }, new object[] { fromRouteNullableSource, expectedBody, 200, expectedBody }, new object[] { fromRouteNullableSource, null, 200, string.Empty }, new object[] { fromRouteDefaultValueSource, expectedBody, 200, expectedBody }, diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs index b0649543d49f..7f8776b8f80f 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs @@ -15,3 +15,8 @@ public class Todo public string Name { get; set; } = "Todo"; public bool IsComplete { get; set; } } + +public class CustomFromBodyAttribute : Attribute, IFromBodyMetadata +{ + public bool AllowEmpty { get; set; } +} diff --git a/src/Shared/RoslynUtils/SymbolExtensions.cs b/src/Shared/RoslynUtils/SymbolExtensions.cs index fa83aacdf4d8..1809be26edc7 100644 --- a/src/Shared/RoslynUtils/SymbolExtensions.cs +++ b/src/Shared/RoslynUtils/SymbolExtensions.cs @@ -89,15 +89,15 @@ parameterSymbol.Type is INamedTypeSymbol NullableAnnotation: NullableAnnotation.Annotated } || parameterSymbol.HasExplicitDefaultValue; - public static bool TryGetNamedArgumentValue(this AttributeData attribute, string argumentName, [NotNullWhen(true)] out T? argumentValue) + public static bool TryGetNamedArgumentValue(this AttributeData attribute, string argumentName, out T? argumentValue) { argumentValue = default; foreach (var namedArgument in attribute.NamedArguments) { - if (namedArgument.Key == argumentName) + if (string.Equals(namedArgument.Key, argumentName, StringComparison.Ordinal)) { var routeParameterNameConstant = namedArgument.Value; - argumentValue = (T)routeParameterNameConstant.Value!; + argumentValue = (T?)routeParameterNameConstant.Value; return true; } } From c7b6fb1591215508bd6d8e9dd0c44ba86e9a1248 Mon Sep 17 00:00:00 2001 From: Safia Abdalla Date: Fri, 24 Feb 2023 11:42:30 -0800 Subject: [PATCH 7/7] Undo format change --- src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs b/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs index 9d7f2058c1d0..99b337a3df8e 100644 --- a/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs +++ b/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs @@ -120,7 +120,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) lineNumber); } """); - } + } return code.ToString(); });