diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs index 88da446fc7d0..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; @@ -21,8 +22,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(), @@ -45,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 b8a4ce26719f..837ee73c4a55 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.Name}_local = {endpointParameter.AssigningCode}; + var {endpointParameter.EmitHandlerArgument()} = {endpointParameter.AssigningCode}; """; } - internal static string EmitQueryParameterPreparation(this EndpointParameter endpointParameter) + 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.Name}}_raw = {{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,35 +35,103 @@ 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.EmitHandlerArgument()}} = {{endpointParameter.EmitAssigningCodeResult()}}.Count > 0 ? {{endpointParameter.EmitAssigningCodeResult()}}.ToString() : null; """); } else { builder.AppendLine($$""" - if (StringValues.IsNullOrEmpty({{endpointParameter.Name}}_raw)) + if (StringValues.IsNullOrEmpty({{endpointParameter.EmitAssigningCodeResult()}})) { wasParamCheckFailure = true; } - var {{endpointParameter.HandlerArgument}} = {{endpointParameter.Name}}_raw.ToString(); + var {{endpointParameter.EmitHandlerArgument()}} = {{endpointParameter.EmitAssigningCodeResult()}}.ToString(); """); } return builder.ToString(); } - internal static string EmitJsonBodyParameterPreparationString(this EndpointParameter endpointParameter) + internal static string EmitRouteParameterPreparation(this EndpointParameter endpointParameter) { var builder = new StringBuilder(); + builder.AppendLine($""" + {endpointParameter.EmitParameterDiagnosticComment()} +"""); - // Preamble for diagnostics purposes. + // 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."); + } +"""); + + var assigningCode = $"httpContext.Request.RouteValues[\"{endpointParameter.Name}\"]?.ToString()"; + builder.AppendLine($$""" + var {{endpointParameter.EmitAssigningCodeResult()}} = {{assigningCode}}; +"""); + + if (!endpointParameter.IsOptional) + { + builder.AppendLine($$""" + if ({{endpointParameter.EmitAssigningCodeResult()}} == null) + { + wasParamCheckFailure = true; + } +"""); + } + builder.AppendLine($""" + var {endpointParameter.EmitHandlerArgument()} = {endpointParameter.EmitAssigningCodeResult()}; +"""); + + return builder.ToString(); + } + + internal static string EmitRouteOrQueryParameterPreparation(this EndpointParameter endpointParameter) + { + var builder = new StringBuilder(); builder.AppendLine($""" {endpointParameter.EmitParameterDiagnosticComment()} """); - // Grab raw input from HttpContext. + 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 (isSuccessful, {{endpointParameter.Name}}_local) = {{endpointParameter.AssigningCode}}; + var {{endpointParameter.EmitAssigningCodeResult()}} = {{assigningCode}}; +"""); + + if (!endpointParameter.IsOptional) + { + builder.AppendLine($$""" + if ({{endpointParameter.EmitAssigningCodeResult()}} is StringValues { Count: 0 }) + { + wasParamCheckFailure = true; + } +"""); + } + + builder.AppendLine($""" + var {endpointParameter.EmitHandlerArgument()} = {endpointParameter.EmitAssigningCodeResult()}; +"""); + + return builder.ToString(); + } + + internal static string EmitJsonBodyParameterPreparationString(this EndpointParameter endpointParameter) + { + var builder = new StringBuilder(); + builder.AppendLine($""" + {endpointParameter.EmitParameterDiagnosticComment()} +"""); + + var assigningCode = $"await GeneratedRouteBuilderExtensionsCore.TryResolveBody<{endpointParameter.Type.ToDisplayString(EmitterConstants.DisplayFormat)}>(httpContext, {(endpointParameter.IsOptional ? "true" : "false")})"; + builder.AppendLine($$""" + var (isSuccessful, {{endpointParameter.EmitHandlerArgument()}}) = {{assigningCode}}; """); // If binding from the JSON body fails, we exit early. Don't @@ -88,14 +157,32 @@ internal static string EmitServiceParameterPreparation(this EndpointParameter en """); // Requiredness checks for services are handled by the distinction - // between GetRequiredService and GetService in the AssigningCode. + // 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}>()"; + builder.AppendLine($$""" - var {{endpointParameter.HandlerArgument}} = {{endpointParameter.AssigningCode}}; + var {{endpointParameter.EmitHandlerArgument()}} = {{assigningCode}}; """); return builder.ToString(); } 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"; + + 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 f1268465675c..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; } @@ -91,7 +87,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 +104,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..ef493e6b6d35 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs @@ -17,39 +17,50 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp Type = parameter.Type; Name = parameter.Name; Source = EndpointParameterSource.Unknown; - HandlerArgument = $"{parameter.Name}_local"; 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 = GetParameterName(fromRouteAttribute, parameter.Name); + 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 = GetParameterName(fromQueryAttribute, parameter.Name); + IsOptional = parameter.IsOptional(); + } + else if (parameter.HasAttributeImplementingInterface(fromHeaderMetadataInterfaceType, out var fromHeaderAttribute)) + { + Source = EndpointParameterSource.Header; + Name = GetParameterName(fromHeaderAttribute, parameter.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)) { Source = EndpointParameterSource.SpecialType; AssigningCode = specialTypeAssigningCode; } + else if (parameter.Type.SpecialType == SpecialType.System_String) + { + Source = EndpointParameterSource.RouteOrQuery; + IsOptional = parameter.IsOptional(); + } else { // TODO: Inferencing rules go here - but for now: @@ -60,19 +71,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. + // Only 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 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!") - }; - // 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) { @@ -118,33 +122,34 @@ 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)) - { - foreach (var namedArgument in fromBodyAttribute.NamedArguments) - { - if (namedArgument.Key == "AllowEmpty") - { - isOptional |= namedArgument.Value.Value is true; - } - } - isOptional |= (parameter.NullableAnnotation == NullableAnnotation.Annotated || parameter.HasExplicitDefaultValue); - assigningCode = $"await GeneratedRouteBuilderExtensionsCore.TryResolveBody<{parameter.Type}>(httpContext, {(isOptional ? "true" : "false")})"; - return true; + if (!parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromBodyMetadata), out var fromBodyAttribute)) + { + return false; } - 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 && 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/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs index e57fa73f5359..4a606629b005 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) { @@ -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 8d6be6f1194c..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 @@ -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) { @@ -84,16 +84,16 @@ 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) => { - 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 = 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 = TestMapActions.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); } @@ -151,16 +151,16 @@ 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) => { - 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 = 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 = TestMapActions.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 5b8cb702c274..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 @@ -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) => @@ -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); @@ -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); } @@ -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) => @@ -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); @@ -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); } @@ -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) => @@ -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); @@ -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 new file mode 100644 index 000000000000..6ce289c79b34 --- /dev/null +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt @@ -0,0 +1,1898 @@ +//------------------------------------------------------------------------------ +// +// 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", 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: queryValue (Type = global::System.String, IsOptional = False, Source = Query) + var queryValue_raw = httpContext.Request.Query["queryValue"]; + if (StringValues.IsNullOrEmpty(queryValue_raw)) + { + wasParamCheckFailure = true; + } + var queryValue_local = queryValue_raw.ToString(); + + 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 = global::System.String, IsOptional = False, Source = Query) + var queryValue_raw = httpContext.Request.Query["queryValue"]; + 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)); + 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: headerValue (Type = global::System.String, IsOptional = False, Source = Header) + var headerValue_raw = httpContext.Request.Headers["headerValue"]; + if (StringValues.IsNullOrEmpty(headerValue_raw)) + { + wasParamCheckFailure = true; + } + var headerValue_local = headerValue_raw.ToString(); + + 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 = global::System.String, IsOptional = False, Source = Header) + var headerValue_raw = httpContext.Request.Headers["headerValue"]; + 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)); + 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) + { + 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 = global::System.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; + 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 = global::System.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!)); + 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", 21)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 21)); + 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 = 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 }) + { + 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 = 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 }) + { + 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", 22)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 22)); + 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 = 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 }) + { + 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 = 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 }) + { + 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); + }), + + }; + + 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); + } + } + + %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..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 @@ -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) => @@ -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); @@ -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 d028b89c503d..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 @@ -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) => @@ -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); @@ -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_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 b905c1d65cf7..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 @@ -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) => @@ -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,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/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..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 814152da56dc..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 @@ -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) => @@ -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); @@ -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); } @@ -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) => @@ -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); @@ -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); } @@ -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) => @@ -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); @@ -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/RequestDelegateGeneratorIncrementalityTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs index a44c5894adb9..f728b5b2f2de 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorIncrementalityTests.cs @@ -1,6 +1,8 @@ // 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; public class RequestDelegateGeneratorIncrementalityTests : RequestDelegateGeneratorTestBase @@ -44,8 +46,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); diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs index b1533eb20ec1..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 { @@ -204,25 +206,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 +357,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..8766a3f5e58e 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs @@ -1,10 +1,13 @@ // 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.AspNetCore.Mvc.ModelBinding; using Microsoft.Extensions.DependencyInjection; namespace Microsoft.AspNetCore.Http.Generators.Tests; @@ -35,75 +38,61 @@ 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("/", ([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 +string getQueryWithDefault([FromQuery] 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[] { 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 }, + 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 +280,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(""" + public static IEnumerable MapAction_NoParam_ComplexReturn_Data => new List() + { + new object[] { """app.MapGet("/", () => new Todo() { Name = "Test Item"});""" }, + new object[] { """ object GetTodo() => new Todo() { Name = "Test Item"}; app.MapGet("/", GetTodo); -""")] - [InlineData(@"app.MapGet(""/"", () => TypedResults.Ok(new Todo() { Name = ""Test Item""}));")] +"""}, + new object[] { """app.MapGet("/", () => TypedResults.Ok(new 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 +308,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 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 +330,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 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] - [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 +355,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 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] - [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 +380,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 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] - [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); @@ -510,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(AllowEmpty = true)] 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 @@ -519,8 +534,9 @@ 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(AllowEmpty = true)] Todo todo) => TypedResults.Ok(todo)){withFilter}"""; - var fromBodyNullableWithFilterSource = $"""app.MapPost("/", ([FromBody] 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 IResult postTodoWithDefault([FromBody] Todo todo = null) => TypedResults.Ok(todo); @@ -532,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 }, @@ -540,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 }, @@ -580,7 +600,7 @@ public async Task MapAction_ExplicitBodyParam_ComplexReturn_Snapshot() Name = "Test Item", IsComplete = false }; - var source = """ + var source = $""" app.MapPost("/fromBodyRequired", ([FromBody] Todo todo) => TypedResults.Ok(todo)); #pragma warning disable CS8622 app.MapPost("/fromBodyOptional", ([FromBody] Todo? todo) => TypedResults.Ok(todo)); @@ -615,6 +635,182 @@ 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("/", ([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 +string getHeaderWithDefault([FromHeader] 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[] { 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 }, + 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}", ([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 +string getRouteWithDefault([FromRoute] 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[] { 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 }, + 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}", ([FromRoute(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 +840,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 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([FromServices]{{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("/", ([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 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([FromServices]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 """; @@ -711,10 +907,10 @@ public async Task MapAction_ExplicitServiceParam_SimpleReturn(string source, boo [Fact] 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) => + 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(); @@ -756,6 +952,21 @@ 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", ([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); +"""; + 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..7f8776b8f80f 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs @@ -1,8 +1,22 @@ // 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 class CustomFromBodyAttribute : Attribute, IFromBodyMetadata +{ + public bool AllowEmpty { get; set; } +} diff --git a/src/Shared/RoslynUtils/SymbolExtensions.cs b/src/Shared/RoslynUtils/SymbolExtensions.cs index e80bb5054eff..1809be26edc7 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, out T? argumentValue) + { + argumentValue = default; + foreach (var namedArgument in attribute.NamedArguments) + { + if (string.Equals(namedArgument.Key, argumentName, StringComparison.Ordinal)) + { + var routeParameterNameConstant = namedArgument.Value; + argumentValue = (T?)routeParameterNameConstant.Value; + return true; + } + } + return false; + } } 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" };