diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs index ef2bef8f2240..8dfd0c7e93e4 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/DiagnosticDescriptors.cs @@ -161,10 +161,10 @@ internal static class DiagnosticDescriptors isEnabledByDefault: true, helpLinkUri: "https://aka.ms/aspnet/analyzers"); - internal static readonly DiagnosticDescriptor RouteParameterComplexTypeIsNotParsableOrBindable = new( + internal static readonly DiagnosticDescriptor RouteParameterComplexTypeIsNotParsable = new( "ASP0020", "Complex types referenced by route parameters must be parsable", - "Parameter '{0}' of type {1} should define a bool TryParse(string, IFormatProvider, out {1}) method, or implement IParsable<{1}>, or define a ValueTask<{1}> BindAsync(HttpContext), or implement IBindableFromHttpContext<{1}>.", + "Parameter '{0}' of type {1} should define a bool TryParse(string, IFormatProvider, out {1}) method, or implement IParsable<{1}>.", "Usage", DiagnosticSeverity.Error, isEnabledByDefault: true, diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Microsoft.AspNetCore.App.Analyzers.csproj b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Microsoft.AspNetCore.App.Analyzers.csproj index e68fcbf8ccac..0e632da24044 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Microsoft.AspNetCore.App.Analyzers.csproj +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Microsoft.AspNetCore.App.Analyzers.csproj @@ -27,10 +27,7 @@ - - - - + diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs index ce65f2d1e42f..1d5f8550a1a5 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/DisallowNonParsableComplexTypesOnParameters.cs @@ -68,14 +68,11 @@ private static void DisallowNonParsableComplexTypesOnParameters( if (IsRouteParameter(routeUsage, handlerDelegateParameter)) { var parsability = ParsabilityHelper.GetParsability(parameterTypeSymbol, wellKnownTypes); - var bindability = ParsabilityHelper.GetBindability(parameterTypeSymbol, wellKnownTypes); - if (!(parsability == Parsability.Parsable || bindability == Bindability.Bindable)) + if (parsability != Parsability.Parsable) { - var descriptor = SelectDescriptor(parsability, bindability); - context.ReportDiagnostic(Diagnostic.Create( - descriptor, + DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable, location, handlerDelegateParameter.Name, parameterTypeSymbol.Name @@ -103,10 +100,8 @@ static bool ReportFromAttributeDiagnostic(OperationAnalysisContext context, Well var parsability = ParsabilityHelper.GetParsability(parameterTypeSymbol, wellKnownTypes); if (parameter.HasAttributeImplementingInterface(fromMetadataInterfaceTypeSymbol) && parsability != Parsability.Parsable) { - var descriptor = SelectDescriptor(parsability, Bindability.NotBindable); - context.ReportDiagnostic(Diagnostic.Create( - descriptor, + DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable, location, parameter.Name, parameterTypeSymbol.Name @@ -140,16 +135,5 @@ static bool ReportFromAttributeDiagnostic(OperationAnalysisContext context, Well return parameterTypeSymbol; } - - static DiagnosticDescriptor SelectDescriptor(Parsability parsability, Bindability bindability) - { - // This abomination is used to take the parsability and bindability and together figure - // out what the most optimal diagnostic message is to give to our plucky user. - return (parsability, bindability) switch - { - { parsability: Parsability.NotParsable, bindability: Bindability.InvalidReturnType } => DiagnosticDescriptors.BindAsyncSignatureMustReturnValueTaskOfT, - _ => DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsableOrBindable - }; - } } } diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/RouteHandlerAnalyzer.cs b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/RouteHandlerAnalyzer.cs index c5dd2f0a9d8c..274bf3d4d4ce 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/RouteHandlerAnalyzer.cs +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteHandlers/RouteHandlerAnalyzer.cs @@ -25,7 +25,7 @@ public partial class RouteHandlerAnalyzer : DiagnosticAnalyzer DiagnosticDescriptors.DoNotReturnActionResultsFromRouteHandlers, DiagnosticDescriptors.DetectMisplacedLambdaAttribute, DiagnosticDescriptors.DetectMismatchedParameterOptionality, - DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsableOrBindable, + DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable, DiagnosticDescriptors.BindAsyncSignatureMustReturnValueTaskOfT, DiagnosticDescriptors.AmbiguousRouteHandlerRoute, DiagnosticDescriptors.AtMostOneFromBodyAttribute diff --git a/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs b/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs index a4bffcbc3a0f..aaa80f6db2c1 100644 --- a/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs +++ b/src/Framework/AspNetCoreAnalyzers/test/RouteHandlers/DisallowNonParsableComplexTypesOnParametersTest.cs @@ -195,7 +195,7 @@ public class Customer } """; - var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsableOrBindable) + var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable) .WithArguments("customer", "Customer") .WithLocation(0); @@ -218,7 +218,7 @@ public class Customer } """; - var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsableOrBindable) + var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable) .WithArguments("customer", "Customer") .WithLocation(0); @@ -289,7 +289,7 @@ public static bool TryParse(string s, IFormatProvider provider, out Customer res } [Fact] - public async Task Route_Parameter_withBindAsyncMethodThatReturnsTask_of_T_Fails() + public async Task Route_Parameter_withBindAsyncMethod_Fails() { // Arrange var source = $$""" @@ -301,14 +301,14 @@ public async Task Route_Parameter_withBindAsyncMethodThatReturnsTask_of_T_Fails( public class Customer { - public async static Task BindAsync(HttpContext context) + public async static ValueTask BindAsync(HttpContext context) { return new Customer(); } } """; - var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.BindAsyncSignatureMustReturnValueTaskOfT) + var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable) .WithArguments("customer", "Customer") .WithLocation(0); @@ -422,7 +422,7 @@ public static bool TryParse(string s, IFormatProvider provider, out Customer res } [Fact] - public async Task Route_Parameter_withHttpContextBindableComplexType_viaImplicitIBindableFromHttp_Works() + public async Task Route_Parameter_withHttpContextBindableComplexType_viaImplicitIBindableFromHttp_Fails() { // Arrange var source = $$""" @@ -433,7 +433,7 @@ public async Task Route_Parameter_withHttpContextBindableComplexType_viaImplicit using Microsoft.AspNetCore.Http; var webApp = WebApplication.Create(); -webApp.MapGet("/customers/{customer}", (Customer customer) => {}); +webApp.MapGet("/customers/{customer}", ({|#0:Customer customer|}) => {}); public class Customer : IBindableFromHttpContext { @@ -444,12 +444,16 @@ public class Customer : IBindableFromHttpContext } """; + var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable) + .WithArguments("customer", "Customer") + .WithLocation(0); + // Act - await VerifyCS.VerifyAnalyzerAsync(source); + await VerifyCS.VerifyAnalyzerAsync(source, expectedDiagnostic); } [Fact] - public async Task Route_Parameter_withHttpContextBindableComplexType_viaExplicitIBindableFromHttp_Works() + public async Task Route_Parameter_withHttpContextBindableComplexType_viaExplicitIBindableFromHttp_Fails() { // Arrange var source = $$""" @@ -460,7 +464,7 @@ public async Task Route_Parameter_withHttpContextBindableComplexType_viaExplicit using Microsoft.AspNetCore.Http; var webApp = WebApplication.Create(); -webApp.MapGet("/customers/{customer}", (Customer customer) => {}); +webApp.MapGet("/customers/{customer}", ({|#0:Customer customer|}) => {}); public class Customer : IBindableFromHttpContext { @@ -471,8 +475,12 @@ public class Customer : IBindableFromHttpContext } """; + var expectedDiagnostic = new DiagnosticResult(DiagnosticDescriptors.RouteParameterComplexTypeIsNotParsable) + .WithArguments("customer", "Customer") + .WithLocation(0); + // Act - await VerifyCS.VerifyAnalyzerAsync(source); + await VerifyCS.VerifyAnalyzerAsync(source, expectedDiagnostic); } [Fact] diff --git a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.Generators.csproj b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.Generators.csproj index 8b051b325eb1..f339d4fc2351 100644 --- a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.Generators.csproj +++ b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.Generators.csproj @@ -24,10 +24,7 @@ - - - - + diff --git a/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs b/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs index 13d303d40c3b..ebb981855057 100644 --- a/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs +++ b/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs @@ -81,6 +81,10 @@ public void Initialize(IncrementalGeneratorInitializationContext context) codeWriter.WriteLine("EndpointFilterDelegate? filteredInvocation = null;"); endpoint.EmitRouteOrQueryResolver(codeWriter); endpoint.EmitJsonPreparation(codeWriter); + if (endpoint.NeedsParameterArray) + { + codeWriter.WriteLine("var parameters = del.Method.GetParameters();"); + } codeWriter.WriteLineNoTabs(string.Empty); codeWriter.WriteLine("if (options?.EndpointBuilder?.FilterFactories.Count > 0)"); codeWriter.StartBlock(); diff --git a/src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs b/src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs index 0fd4b4162192..b8bcc470c8dd 100644 --- a/src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs +++ b/src/Http/Http.Extensions/gen/RequestDelegateGeneratorSources.cs @@ -158,6 +158,12 @@ private static Task WriteToResponseAsync(T? value, HttpContext httpContext, J } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } """; diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs index 54030a9a8656..3fe38552bd69 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs @@ -1,11 +1,9 @@ // 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.Linq; using System.Globalization; using System.IO; -using System.Text; +using System.Linq; namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters; internal static class EndpointEmitter @@ -17,24 +15,28 @@ internal static string EmitParameterPreparation(this Endpoint endpoint, int base foreach (var parameter in endpoint.Parameters) { - switch (parameter) + switch (parameter.Source) { - case { Source: EndpointParameterSource.SpecialType }: + case EndpointParameterSource.SpecialType: parameter.EmitSpecialParameterPreparation(parameterPreparationBuilder); break; - case { Source: EndpointParameterSource.Query or EndpointParameterSource.Header }: + case EndpointParameterSource.Query: + case EndpointParameterSource.Header: parameter.EmitQueryOrHeaderParameterPreparation(parameterPreparationBuilder); break; - case { Source: EndpointParameterSource.Route }: + case EndpointParameterSource.Route: parameter.EmitRouteParameterPreparation(parameterPreparationBuilder); break; - case { Source: EndpointParameterSource.RouteOrQuery }: + case EndpointParameterSource.RouteOrQuery: parameter.EmitRouteOrQueryParameterPreparation(parameterPreparationBuilder); break; - case { Source: EndpointParameterSource.JsonBody }: + case EndpointParameterSource.BindAsync: + parameter.EmitBindAsyncPreparation(parameterPreparationBuilder); + break; + case EndpointParameterSource.JsonBody: parameter.EmitJsonBodyParameterPreparationString(parameterPreparationBuilder); break; - case { Source: EndpointParameterSource.Service }: + case EndpointParameterSource.Service: parameter.EmitServiceParameterPreparation(parameterPreparationBuilder); break; } diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs index 89a7bc1622b5..6d841471c314 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs @@ -2,10 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Text; +using Microsoft.AspNetCore.Analyzers.Infrastructure; +using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; using Microsoft.CodeAnalysis; namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters; + internal static class EndpointParameterEmitter { internal static void EmitSpecialParameterPreparation(this EndpointParameter endpointParameter, CodeWriter codeWriter) @@ -26,7 +28,7 @@ internal static void EmitQueryOrHeaderParameterPreparation(this EndpointParamete // compiler errors around null handling. if (endpointParameter.IsOptional) { - codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = {endpointParameter.EmitAssigningCodeResult()}.Count > 0 ? {endpointParameter.EmitAssigningCodeResult()}.ToString() : null;"); + codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = {endpointParameter.EmitAssigningCodeResult()}.Count > 0 ? (string?){endpointParameter.EmitAssigningCodeResult()} : null;"); } else { @@ -34,7 +36,7 @@ internal static void EmitQueryOrHeaderParameterPreparation(this EndpointParamete codeWriter.StartBlock(); codeWriter.WriteLine("wasParamCheckFailure = true;"); codeWriter.EndBlock(); - codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = {endpointParameter.EmitAssigningCodeResult()}.ToString();"); + codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = (string?){endpointParameter.EmitAssigningCodeResult()};"); } endpointParameter.EmitParsingBlock(codeWriter); @@ -64,7 +66,7 @@ internal static void EmitRouteParameterPreparation(this EndpointParameter endpoi codeWriter.WriteLine($$"""throw new InvalidOperationException($"'{{endpointParameter.Name}}' is not a route parameter.");"""); codeWriter.EndBlock(); - var assigningCode = $"httpContext.Request.RouteValues[\"{endpointParameter.Name}\"]?.ToString()"; + var assigningCode = $"(string?)httpContext.Request.RouteValues[\"{endpointParameter.Name}\"]"; codeWriter.WriteLine($"var {endpointParameter.EmitAssigningCodeResult()} = {assigningCode};"); if (!endpointParameter.IsOptional) @@ -75,7 +77,7 @@ internal static void EmitRouteParameterPreparation(this EndpointParameter endpoi codeWriter.EndBlock(); } - codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = {endpointParameter.EmitAssigningCodeResult()}?.ToString();"); + codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = (string?){endpointParameter.EmitAssigningCodeResult()};"); endpointParameter.EmitParsingBlock(codeWriter); } @@ -94,7 +96,8 @@ internal static void EmitRouteOrQueryParameterPreparation(this EndpointParameter codeWriter.EndBlock(); } - codeWriter.WriteLine($"var {endpointParameter.EmitHandlerArgument()} = {endpointParameter.EmitAssigningCodeResult()};"); + codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = (string?){endpointParameter.EmitAssigningCodeResult()};"); + endpointParameter.EmitParsingBlock(codeWriter); } internal static void EmitJsonBodyParameterPreparationString(this EndpointParameter endpointParameter, CodeWriter codeWriter) @@ -116,6 +119,47 @@ internal static void EmitJsonBodyParameterPreparationString(this EndpointParamet codeWriter.EndBlock(); } + internal static void EmitBindAsyncPreparation(this EndpointParameter endpointParameter, CodeWriter codeWriter) + { + var unwrappedType = endpointParameter.Type.UnwrapTypeSymbol(); + var unwrappedTypeString = unwrappedType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat); + + switch (endpointParameter.BindMethod) + { + case BindabilityMethod.IBindableFromHttpContext: + codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = await BindAsync<{unwrappedTypeString}>(httpContext, parameters[{endpointParameter.Ordinal}]);"); + break; + case BindabilityMethod.BindAsyncWithParameter: + codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = await {unwrappedTypeString}.BindAsync(httpContext, parameters[{endpointParameter.Ordinal}]);"); + break; + case BindabilityMethod.BindAsync: + codeWriter.WriteLine($"var {endpointParameter.EmitTempArgument()} = await {unwrappedTypeString}.BindAsync(httpContext);"); + break; + default: + throw new Exception("Unreachable!"); + } + + // TODO: Generate more compact code if the type is a reference type and/or the BindAsync return nullability matches the handler parameter type. + if (endpointParameter.IsOptional) + { + codeWriter.WriteLine($"var {endpointParameter.EmitHandlerArgument()} = ({unwrappedTypeString}?){endpointParameter.EmitTempArgument()};"); + } + else + { + codeWriter.WriteLine($"{unwrappedTypeString} {endpointParameter.EmitHandlerArgument()};"); + + codeWriter.WriteLine($"if ((object?){endpointParameter.EmitTempArgument()} == null)"); + codeWriter.StartBlock(); + codeWriter.WriteLine("wasParamCheckFailure = true;"); + codeWriter.WriteLine($"{endpointParameter.EmitHandlerArgument()} = default!;"); + codeWriter.EndBlock(); + codeWriter.WriteLine("else"); + codeWriter.StartBlock(); + codeWriter.WriteLine($"{endpointParameter.EmitHandlerArgument()} = ({unwrappedTypeString}){endpointParameter.EmitTempArgument()};"); + codeWriter.EndBlock(); + } + } + internal static void EmitServiceParameterPreparation(this EndpointParameter endpointParameter, CodeWriter codeWriter) { codeWriter.WriteLine(endpointParameter.EmitParameterDiagnosticComment()); diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs index 83cf8bf65bdb..73d25920e9cf 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs @@ -3,7 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; +using Microsoft.AspNetCore.Analyzers.Infrastructure; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using Microsoft.CodeAnalysis.CSharp.Syntax; @@ -47,21 +47,36 @@ public Endpoint(IInvocationOperation operation, WellKnownTypes wellKnownTypes) { var parameter = new EndpointParameter(method.Parameters[i], wellKnownTypes); - if (parameter.Source == EndpointParameterSource.Unknown) + switch (parameter.Source) { - Diagnostics.Add(DiagnosticDescriptors.GetUnableToResolveParameterDescriptor(parameter.Name)); - return; + case EndpointParameterSource.BindAsync: + IsAwaitable = true; + switch (parameter.BindMethod) + { + case BindabilityMethod.IBindableFromHttpContext: + case BindabilityMethod.BindAsyncWithParameter: + NeedsParameterArray = true; + break; + } + break; + case EndpointParameterSource.JsonBody: + case EndpointParameterSource.JsonBodyOrService: + IsAwaitable = true; + break; + case EndpointParameterSource.Unknown: + Diagnostics.Add(DiagnosticDescriptors.GetUnableToResolveParameterDescriptor(parameter.Name)); + break; } parameters[i] = parameter; } Parameters = parameters; - IsAwaitable |= parameters.Any(parameter => parameter.Source == EndpointParameterSource.JsonBody); } public string HttpMethod { get; } - public bool IsAwaitable { get; set; } + public bool IsAwaitable { get; } + public bool NeedsParameterArray { get; } public string? RoutePattern { get; } public EndpointResponse? Response { get; } public EndpointParameter[] Parameters { get; } = Array.Empty(); diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs index 0f6a01c6cde1..d1a12e428d1d 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs @@ -3,13 +3,11 @@ using System; using System.Diagnostics.CodeAnalysis; -using Microsoft.AspNetCore.App.Analyzers.Infrastructure; +using Microsoft.AspNetCore.Analyzers.Infrastructure; using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; +using Microsoft.AspNetCore.App.Analyzers.Infrastructure; using Microsoft.CodeAnalysis; using WellKnownType = Microsoft.AspNetCore.App.Analyzers.Infrastructure.WellKnownTypeData.WellKnownType; -using Microsoft.AspNetCore.Analyzers.Infrastructure; -using System.Linq; -using System.Globalization; namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel; @@ -19,35 +17,28 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp { Type = parameter.Type; Name = parameter.Name; + Ordinal = parameter.Ordinal; Source = EndpointParameterSource.Unknown; + IsOptional = parameter.IsOptional(); - 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(fromRouteMetadataInterfaceType, out var fromRouteAttribute)) + if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromRouteMetadata), out var fromRouteAttribute)) { Source = EndpointParameterSource.Route; Name = GetParameterName(fromRouteAttribute, parameter.Name); - IsOptional = parameter.IsOptional(); IsParsable = TryGetParsability(parameter, wellKnownTypes, out var parsingBlockEmitter); ParsingBlockEmitter = parsingBlockEmitter; } - else if (parameter.HasAttributeImplementingInterface(fromQueryMetadataInterfaceType, out var fromQueryAttribute)) + else if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromQueryMetadata), out var fromQueryAttribute)) { Source = EndpointParameterSource.Query; Name = GetParameterName(fromQueryAttribute, parameter.Name); - IsOptional = parameter.IsOptional(); - AssigningCode = $"httpContext.Request.Query[\"{parameter.Name}\"]"; IsParsable = TryGetParsability(parameter, wellKnownTypes, out var parsingBlockEmitter); ParsingBlockEmitter = parsingBlockEmitter; } - else if (parameter.HasAttributeImplementingInterface(fromHeaderMetadataInterfaceType, out var fromHeaderAttribute)) + else if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromHeaderMetadata), out var fromHeaderAttribute)) { Source = EndpointParameterSource.Header; Name = GetParameterName(fromHeaderAttribute, parameter.Name); - IsOptional = parameter.IsOptional(); IsParsable = TryGetParsability(parameter, wellKnownTypes, out var parsingBlockEmitter); ParsingBlockEmitter = parsingBlockEmitter; } @@ -56,7 +47,7 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp Source = EndpointParameterSource.JsonBody; IsOptional = isOptional; } - else if (parameter.HasAttributeImplementingInterface(fromServiceMetadataInterfaceType)) + else if (parameter.HasAttributeImplementingInterface(wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromServiceMetadata))) { Source = EndpointParameterSource.Service; IsOptional = parameter.Type is INamedTypeSymbol { NullableAnnotation: NullableAnnotation.Annotated } || parameter.HasExplicitDefaultValue; @@ -66,10 +57,20 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp Source = EndpointParameterSource.SpecialType; AssigningCode = specialTypeAssigningCode; } + else if (HasBindAsync(parameter, wellKnownTypes, out var bindMethod)) + { + Source = EndpointParameterSource.BindAsync; + BindMethod = bindMethod; + } else if (parameter.Type.SpecialType == SpecialType.System_String) { Source = EndpointParameterSource.RouteOrQuery; - IsOptional = parameter.IsOptional(); + } + else if (TryGetParsability(parameter, wellKnownTypes, out var parsingBlockEmitter)) + { + Source = EndpointParameterSource.RouteOrQuery; + IsParsable = true; + ParsingBlockEmitter = parsingBlockEmitter; } else { @@ -78,23 +79,46 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp } } - private bool TryGetParsability(IParameterSymbol parameter, WellKnownTypes wellKnownTypes, [NotNullWhen(true)]out Action? parsingBlockEmitter) + public ITypeSymbol Type { get; } + public string Name { get; } + public int Ordinal { get; } + public bool IsOptional { get; } + + public EndpointParameterSource Source { get; } + + // Only used for SpecialType parameters that need + // to be resolved by a specific WellKnownType + public string? AssigningCode { get; } + + [MemberNotNullWhen(true, nameof(ParsingBlockEmitter))] + public bool IsParsable { get; } + public Action? ParsingBlockEmitter { get; } + + public BindabilityMethod? BindMethod { get; } + + private static bool HasBindAsync(IParameterSymbol parameter, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out BindabilityMethod? bindMethod) + { + var parameterType = parameter.Type.UnwrapTypeSymbol(); + return ParsabilityHelper.GetBindability(parameterType, wellKnownTypes, out bindMethod) == Bindability.Bindable; + } + + private bool TryGetParsability(IParameterSymbol parameter, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out Action? parsingBlockEmitter) { var parameterType = parameter.Type.UnwrapTypeSymbol(); // ParsabilityHelper returns a single enumeration with a Parsable/NonParsable enumeration result. We use this already // in the analyzers to determine whether we need to warn on whether a type needs to implement TryParse/IParsable. To - // support usage in the code generator an optiona out parameter has been added to hint at what variant of the various + // support usage in the code generator an optional out parameter has been added to hint at what variant of the various // TryParse methods should be used (this implies that the preferences are baked into ParsabilityHelper). If we aren't // parsable at all we bail. - if (ParsabilityHelper.GetParsability(parameterType, wellKnownTypes, out var parsabilityMethod) == Parsability.NotParsable) + if (ParsabilityHelper.GetParsability(parameterType, wellKnownTypes, out var parsabilityMethod) != Parsability.Parsable) { parsingBlockEmitter = null; return false; } // If we are parsable we need to emit code based on the enumeration ParsabilityMethod which has a bunch of members - // which spell out the preferred TryParse uage. This swtich statement makes slight variations to them based on + // which spell out the preferred TryParse usage. This switch statement makes slight variations to them based on // which method was encountered. Func? preferredTryParseInvocation = parsabilityMethod switch { @@ -104,7 +128,7 @@ private bool TryGetParsability(IParameterSymbol parameter, WellKnownTypes wellKn ParsabilityMethod.Enum => (string inputArgument, string outputArgument) => $$"""Enum.TryParse<{{parameterType.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)}}>({{inputArgument}}!, out var {{outputArgument}})""", ParsabilityMethod.Uri => (string inputArgument, string outputArgument) => $$"""Uri.TryCreate({{inputArgument}}!, UriKind.RelativeOrAbsolute, out var {{outputArgument}})""", ParsabilityMethod.String => null, // string parameters don't require parsing - _ => null + _ => throw new Exception("Unreachable!"), }; // Special case handling for specific types @@ -166,18 +190,6 @@ private bool TryGetParsability(IParameterSymbol parameter, WellKnownTypes wellKn return true; } - public ITypeSymbol Type { get; } - public EndpointParameterSource Source { get; } - - // Only used for SpecialType parameters that need - // to be resolved by a specific WellKnownType - internal string? AssigningCode { get; set; } - public string Name { get; } - public bool IsOptional { get; } - [MemberNotNull("ParsingBlockEmitter")] - public bool IsParsable { get; } - public Action ParsingBlockEmitter { get; } - // 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) { @@ -245,6 +257,8 @@ public override bool Equals(object obj) => obj is EndpointParameter other && other.Source == Source && other.Name == Name && + other.Ordinal == Ordinal && + other.IsOptional == IsOptional && SymbolEqualityComparer.Default.Equals(other.Type, Type); public bool SignatureEquals(object obj) => diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs index f0a605a077af..878c5a6361c5 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs @@ -2,9 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.CodeDom.Compiler; -using System.Globalization; -using System.IO; using System.Linq; using System.Text; using Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters; @@ -110,7 +107,14 @@ public static void EmitRequestHandler(this Endpoint endpoint, CodeWriter codeWri codeWriter.Write("await "); } codeWriter.WriteLine($"handler({endpoint.EmitArgumentList()});"); - codeWriter.WriteLine(endpoint.Response.IsVoid ? "return Task.CompletedTask;" : endpoint.EmitResponseWritingCall()); + if (!endpoint.Response.IsVoid) + { + codeWriter.WriteLine(endpoint.EmitResponseWritingCall()); + } + else if (!endpoint.IsAwaitable) + { + codeWriter.WriteLine("return Task.CompletedTask;"); + } codeWriter.EndBlock(); // End handler method block } diff --git a/src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj b/src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj index b6a0988e5a9f..01cfa420bd6d 100644 --- a/src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj +++ b/src/Http/Http.Extensions/test/Microsoft.AspNetCore.Http.Extensions.Tests.csproj @@ -29,7 +29,7 @@ - PreserveNewest + Always diff --git a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs index 5695c4e92df6..782c42fe5b83 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateFactoryTests.cs @@ -628,12 +628,6 @@ public static bool TryParse(string? value, out MyTryParseRecord? result) } } - private class MyBindAsyncTypeThatThrows - { - public static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) => - throw new InvalidOperationException("BindAsync failed"); - } - private record MyBindAsyncRecord(Uri Uri) { public static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) @@ -1123,79 +1117,6 @@ public async Task RequestDelegatePopulatesUnattributedTryParsableParametersFromR Assert.Equal(42, httpContext.Items["tryParsable"]); } - [Fact] - public async Task RequestDelegatePrefersBindAsyncOverTryParse() - { - var httpContext = CreateHttpContext(); - - httpContext.Request.Headers.Referer = "https://example.org"; - - var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncRecord myBindAsyncRecord) => - { - httpContext.Items["myBindAsyncRecord"] = myBindAsyncRecord; - }); - - var requestDelegate = resultFactory.RequestDelegate; - - await requestDelegate(httpContext); - - Assert.Equal(new MyBindAsyncRecord(new Uri("https://example.org")), httpContext.Items["myBindAsyncRecord"]); - } - - [Fact] - public async Task RequestDelegatePrefersBindAsyncOverTryParseForNonNullableStruct() - { - var httpContext = CreateHttpContext(); - - httpContext.Request.Headers.Referer = "https://example.org"; - - var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncStruct myBindAsyncStruct) => - { - httpContext.Items["myBindAsyncStruct"] = myBindAsyncStruct; - }); - - var requestDelegate = resultFactory.RequestDelegate; - await requestDelegate(httpContext); - - Assert.Equal(new MyBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myBindAsyncStruct"]); - } - - [Fact] - public async Task RequestDelegateUsesBindAsyncOverTryParseGivenNullableStruct() - { - var httpContext = CreateHttpContext(); - - httpContext.Request.Headers.Referer = "https://example.org"; - - var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBindAsyncStruct? myBindAsyncStruct) => - { - httpContext.Items["myBindAsyncStruct"] = myBindAsyncStruct; - }); - - var requestDelegate = resultFactory.RequestDelegate; - await requestDelegate(httpContext); - - Assert.Equal(new MyBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myBindAsyncStruct"]); - } - - [Fact] - public async Task RequestDelegateUsesParameterInfoBindAsyncOverOtherBindAsync() - { - var httpContext = CreateHttpContext(); - - httpContext.Request.Headers.Referer = "https://example.org"; - - var resultFactory = RequestDelegateFactory.Create((HttpContext httpContext, MyBothBindAsyncStruct? myBothBindAsyncStruct) => - { - httpContext.Items["myBothBindAsyncStruct"] = myBothBindAsyncStruct; - }); - - var requestDelegate = resultFactory.RequestDelegate; - await requestDelegate(httpContext); - - Assert.Equal(new MyBothBindAsyncStruct(new Uri("https://example.org")), httpContext.Items["myBothBindAsyncStruct"]); - } - [Fact] public async Task RequestDelegateUsesTryParseOverBindAsyncGivenExplicitAttribute() { @@ -1554,19 +1475,6 @@ public async Task RequestDelegateThrowsForSingleArgBindAsyncFailuresIfThrowOnBad Assert.Equal(400, badHttpRequestException.StatusCode); } - [Fact] - public async Task BindAsyncExceptionsAreUncaught() - { - var httpContext = CreateHttpContext(); - - var factoryResult = RequestDelegateFactory.Create((MyBindAsyncTypeThatThrows arg1) => { }); - - var requestDelegate = factoryResult.RequestDelegate; - - var ex = await Assert.ThrowsAsync(() => requestDelegate(httpContext)); - Assert.Equal("BindAsync failed", ex.Message); - } - [Fact] public async Task BindAsyncWithBodyArgument() { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_Snapshot.generated.txt new file mode 100644 index 000000000000..26d1d5942c52 --- /dev/null +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_BindAsync_Snapshot.generated.txt @@ -0,0 +1,1726 @@ +//------------------------------------------------------------------------------ +// +// 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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); + } + 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.Diagnostics.CodeAnalysis; + using System.Globalization; + using System.Linq; + using System.Reflection; + using System.Text.Json; + using System.Text.Json.Serialization.Metadata; + 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.Json; + using Microsoft.AspNetCore.Http.Metadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.FileProviders; + using Microsoft.Extensions.Primitives; + using Microsoft.Extensions.Options; + + using MetadataPopulator = System.Func; + using RequestDelegateFactoryFunc = System.Func; + + file static class GeneratedRouteBuilderExtensionsCore + { + + private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() + { + [(@"TestMapActions.cs", 24)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + var parameters = del.Method.GetParameters(); + + 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), ic.GetArgument(1))); + }, + options.EndpointBuilder, + handler.Method); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord.BindAsync(httpContext, parameters[1]); + global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(httpContext_local, myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord.BindAsync(httpContext, parameters[1]); + global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, httpContext_local, myBindAsyncParam_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", 25)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + var parameters = del.Method.GetParameters(); + + 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); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord.BindAsync(httpContext, parameters[0]); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord.BindAsync(httpContext, parameters[0]); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncRecord?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, myBindAsyncParam_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", 26)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + var parameters = del.Method.GetParameters(); + + 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), ic.GetArgument(1))); + }, + options.EndpointBuilder, + handler.Method); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct.BindAsync(httpContext, parameters[1]); + global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(httpContext_local, myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct.BindAsync(httpContext, parameters[1]); + global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, httpContext_local, myBindAsyncParam_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", 27)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 27)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + var parameters = del.Method.GetParameters(); + + 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); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct.BindAsync(httpContext, parameters[0]); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct.BindAsync(httpContext, parameters[0]); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBindAsyncStruct?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, myBindAsyncParam_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", 28)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 28)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + var parameters = del.Method.GetParameters(); + + 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), ic.GetArgument(1))); + }, + options.EndpointBuilder, + handler.Method); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct.BindAsync(httpContext, parameters[1]); + global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(httpContext_local, myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct.BindAsync(httpContext, parameters[1]); + global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, httpContext_local, myBindAsyncParam_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", 29)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 29)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + var parameters = del.Method.GetParameters(); + + 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); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct.BindAsync(httpContext, parameters[0]); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct.BindAsync(httpContext, parameters[0]); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyNullableBindAsyncStruct?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, myBindAsyncParam_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", 30)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 30)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + var parameters = del.Method.GetParameters(); + + 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), ic.GetArgument(1))); + }, + options.EndpointBuilder, + handler.Method); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct.BindAsync(httpContext, parameters[1]); + global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(httpContext_local, myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct.BindAsync(httpContext, parameters[1]); + global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, httpContext_local, myBindAsyncParam_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", 31)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 31)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + var parameters = del.Method.GetParameters(); + + 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); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct.BindAsync(httpContext, parameters[0]); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct.BindAsync(httpContext, parameters[0]); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MyBothBindAsyncStruct?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, myBindAsyncParam_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", 32)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 32)); + 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), ic.GetArgument(1))); + }, + options.EndpointBuilder, + handler.Method); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord.BindAsync(httpContext); + global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(httpContext_local, myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord.BindAsync(httpContext); + global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, httpContext_local, myBindAsyncParam_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", 33)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 33)); + 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); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord.BindAsync(httpContext); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord.BindAsync(httpContext); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncRecord?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, myBindAsyncParam_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", 34)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 34)); + 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), ic.GetArgument(1))); + }, + options.EndpointBuilder, + handler.Method); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct.BindAsync(httpContext); + global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(httpContext_local, myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct.BindAsync(httpContext); + global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, httpContext_local, myBindAsyncParam_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", 35)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 35)); + 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); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct.BindAsync(httpContext); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct.BindAsync(httpContext); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.MySimpleBindAsyncStruct?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, myBindAsyncParam_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", 36)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 36)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + var parameters = del.Method.GetParameters(); + + 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), ic.GetArgument(1))); + }, + options.EndpointBuilder, + handler.Method); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await BindAsync(httpContext, parameters[1]); + global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(httpContext_local, myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await BindAsync(httpContext, parameters[1]); + global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, httpContext_local, myBindAsyncParam_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", 37)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 37)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + var parameters = del.Method.GetParameters(); + + 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); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await BindAsync(httpContext, parameters[0]); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await BindAsync(httpContext, parameters[0]); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromImplicitStaticAbstractInterface?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, myBindAsyncParam_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", 38)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 38)); + 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), ic.GetArgument(1))); + }, + options.EndpointBuilder, + handler.Method); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync.BindAsync(httpContext); + global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(httpContext_local, myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync.BindAsync(httpContext); + global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, httpContext_local, myBindAsyncParam_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", 39)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 39)); + 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); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync.BindAsync(httpContext); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync.BindAsync(httpContext); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.InheritBindAsync?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, myBindAsyncParam_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", 40)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 40)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + var parameters = del.Method.GetParameters(); + + 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), ic.GetArgument(1))); + }, + options.EndpointBuilder, + handler.Method); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await BindAsync(httpContext, parameters[1]); + global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(httpContext_local, myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var httpContext_local = httpContext; + var myBindAsyncParam_temp = await BindAsync(httpContext, parameters[1]); + global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface myBindAsyncParam_local; + if ((object?)myBindAsyncParam_temp == null) + { + wasParamCheckFailure = true; + myBindAsyncParam_local = default!; + } + else + { + myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface)myBindAsyncParam_temp; + } + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, httpContext_local, myBindAsyncParam_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", 41)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 41)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (Func)del; + EndpointFilterDelegate? filteredInvocation = null; + var parameters = del.Method.GetParameters(); + + 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); + } + + async Task RequestHandler(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await BindAsync(httpContext, parameters[0]); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + return; + } + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(myBindAsyncParam_local); + await httpContext.Response.WriteAsync(result); + } + + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var wasParamCheckFailure = false; + var myBindAsyncParam_temp = await BindAsync(httpContext, parameters[0]); + var myBindAsyncParam_local = (global::Microsoft.AspNetCore.Http.Generators.Tests.BindAsyncFromExplicitStaticAbstractInterface?)myBindAsyncParam_temp; + + if (wasParamCheckFailure) + { + httpContext.Response.StatusCode = 400; + } + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, myBindAsyncParam_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 Func ResolveFromRouteOrQuery(string parameterName, IEnumerable? routeParameterNames) + { + return routeParameterNames?.Contains(parameterName, StringComparer.OrdinalIgnoreCase) == true + ? (httpContext) => new StringValues((string?)httpContext.Request.RouteValues[parameterName]) + : (httpContext) => httpContext.Request.Query[parameterName]; + } + + private static Task WriteToResponseAsync(T? value, HttpContext httpContext, JsonTypeInfo jsonTypeInfo, JsonSerializerOptions options) + { + var runtimeType = value?.GetType(); + if (runtimeType is null || jsonTypeInfo.Type == runtimeType || jsonTypeInfo.PolymorphismOptions is not null) + { + return httpContext.Response.WriteAsJsonAsync(value!, jsonTypeInfo); + } + + return httpContext.Response.WriteAsJsonAsync(value!, options.GetTypeInfo(runtimeType)); + } + + private static async ValueTask<(bool, T?)> TryResolveBody(HttpContext httpContext, bool allowEmpty) + { + var feature = httpContext.Features.Get(); + + if (feature?.CanHaveBody == true) + { + if (!httpContext.Request.HasJsonContentType()) + { + httpContext.Response.StatusCode = StatusCodes.Status415UnsupportedMediaType; + return (false, default); + } + try + { + var bodyValue = await httpContext.Request.ReadFromJsonAsync(); + if (!allowEmpty && bodyValue == null) + { + httpContext.Response.StatusCode = StatusCodes.Status400BadRequest; + return (false, bodyValue); + } + return (true, bodyValue); + } + catch (IOException) + { + return (false, default); + } + catch (System.Text.Json.JsonException) + { + httpContext.Response.StatusCode = StatusCodes.Status400BadRequest; + return (false, default); + } + } + return (false, default); + } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } + } +} 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 bba725e95098..6b55279be2e7 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) { @@ -90,16 +90,16 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 23)] = ( + [(@"TestMapActions.cs", 24)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); 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) @@ -110,7 +110,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); @@ -119,8 +119,8 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: todo (Type = Todo, IsOptional = False, IsParsable = False, Source = JsonBody) - var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, false); + // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, IsParsable = False, Source = JsonBody) + var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, false); if (!isSuccessful) { return; @@ -139,8 +139,8 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: todo (Type = Todo, IsOptional = False, IsParsable = False, Source = JsonBody) - var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, false); + // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, IsParsable = False, Source = JsonBody) + var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, false); if (!isSuccessful) { return; @@ -150,7 +150,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, todo_local!)); + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, todo_local!)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -158,16 +158,16 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 25)] = ( + [(@"TestMapActions.cs", 26)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26)); 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) @@ -178,7 +178,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); @@ -187,8 +187,8 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: todo (Type = Todo?, IsOptional = True, IsParsable = False, Source = JsonBody) - var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, true); + // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo?, IsOptional = True, IsParsable = False, Source = JsonBody) + var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, true); if (!isSuccessful) { return; @@ -207,8 +207,8 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: todo (Type = Todo?, IsOptional = True, IsParsable = False, Source = JsonBody) - var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, true); + // Endpoint Parameter: todo (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo?, IsOptional = True, IsParsable = False, Source = JsonBody) + var (isSuccessful, todo_local) = await GeneratedRouteBuilderExtensionsCore.TryResolveBody(httpContext, true); if (!isSuccessful) { return; @@ -218,7 +218,7 @@ namespace Microsoft.AspNetCore.Http.Generated { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, todo_local)); + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, todo_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -325,5 +325,11 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } 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 297eb16cd30c..e746fcb7b9c5 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 @@ -120,11 +120,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 23)] = ( + [(@"TestMapActions.cs", 24)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -180,11 +180,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 24)] = ( + [(@"TestMapActions.cs", 25)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -240,11 +240,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 25)] = ( + [(@"TestMapActions.cs", 26)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -403,5 +403,11 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt index 188d6bfaf82d..e7cbce34da82 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_ExplicitSource_SimpleReturn_Snapshot.generated.txt @@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 23)] = ( + [(@"TestMapActions.cs", 24)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -125,7 +125,7 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var queryValue_temp = queryValue_raw.ToString(); + var queryValue_temp = (string?)queryValue_raw; string queryValue_local = queryValue_temp!; if (wasParamCheckFailure) @@ -147,7 +147,7 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var queryValue_temp = queryValue_raw.ToString(); + var queryValue_temp = (string?)queryValue_raw; string queryValue_local = queryValue_temp!; if (wasParamCheckFailure) @@ -162,11 +162,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 24)] = ( + [(@"TestMapActions.cs", 25)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -197,7 +197,7 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var headerValue_temp = headerValue_raw.ToString(); + var headerValue_temp = (string?)headerValue_raw; string headerValue_local = headerValue_temp!; if (wasParamCheckFailure) @@ -219,7 +219,7 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var headerValue_temp = headerValue_raw.ToString(); + var headerValue_temp = (string?)headerValue_raw; string headerValue_local = headerValue_temp!; if (wasParamCheckFailure) @@ -234,11 +234,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 25)] = ( + [(@"TestMapActions.cs", 26)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -268,12 +268,12 @@ namespace Microsoft.AspNetCore.Http.Generated { throw new InvalidOperationException($"'routeValue' is not a route parameter."); } - var routeValue_raw = httpContext.Request.RouteValues["routeValue"]?.ToString(); + var routeValue_raw = (string?)httpContext.Request.RouteValues["routeValue"]; if (routeValue_raw == null) { wasParamCheckFailure = true; } - var routeValue_temp = routeValue_raw?.ToString(); + var routeValue_temp = (string?)routeValue_raw; string routeValue_local = routeValue_temp!; if (wasParamCheckFailure) @@ -294,12 +294,12 @@ namespace Microsoft.AspNetCore.Http.Generated { throw new InvalidOperationException($"'routeValue' is not a route parameter."); } - var routeValue_raw = httpContext.Request.RouteValues["routeValue"]?.ToString(); + var routeValue_raw = (string?)httpContext.Request.RouteValues["routeValue"]; if (routeValue_raw == null) { wasParamCheckFailure = true; } - var routeValue_temp = routeValue_raw?.ToString(); + var routeValue_temp = (string?)routeValue_raw; string routeValue_local = routeValue_temp!; if (wasParamCheckFailure) @@ -314,11 +314,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 26)] = ( + [(@"TestMapActions.cs", 27)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 27)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -350,7 +350,8 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var value_local = value_raw; + var value_temp = (string?)value_raw; + string value_local = value_temp!; if (wasParamCheckFailure) { @@ -371,7 +372,8 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var value_local = value_raw; + var value_temp = (string?)value_raw; + string value_local = value_temp!; if (wasParamCheckFailure) { @@ -385,11 +387,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 27)] = ( + [(@"TestMapActions.cs", 28)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 27)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 28)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -421,7 +423,8 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var value_local = value_raw; + var value_temp = (string?)value_raw; + string value_local = value_temp!; if (wasParamCheckFailure) { @@ -442,7 +445,8 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var value_local = value_raw; + var value_temp = (string?)value_raw; + string value_local = value_temp!; if (wasParamCheckFailure) { @@ -555,5 +559,11 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } 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 efcfb5a03aab..7dc73e294ebc 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 @@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 23)] = ( + [(@"TestMapActions.cs", 24)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -249,5 +249,11 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } 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 d26bd1f2d783..eb12665e119f 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 @@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 23)] = ( + [(@"TestMapActions.cs", 24)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -125,7 +125,7 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var p1_temp = p1_raw.ToString(); + var p1_temp = (string?)p1_raw; string p1_local = p1_temp!; // Endpoint Parameter: p2 (Type = string, IsOptional = False, IsParsable = False, Source = Query) var p2_raw = httpContext.Request.Query["p2"]; @@ -133,7 +133,7 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var p2_temp = p2_raw.ToString(); + var p2_temp = (string?)p2_raw; string p2_local = p2_temp!; if (wasParamCheckFailure) @@ -155,7 +155,7 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var p1_temp = p1_raw.ToString(); + var p1_temp = (string?)p1_raw; string p1_local = p1_temp!; // Endpoint Parameter: p2 (Type = string, IsOptional = False, IsParsable = False, Source = Query) var p2_raw = httpContext.Request.Query["p2"]; @@ -163,7 +163,7 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var p2_temp = p2_raw.ToString(); + var p2_temp = (string?)p2_raw; string p2_local = p2_temp!; if (wasParamCheckFailure) @@ -277,5 +277,11 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } 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 3705677f0c91..a81bd52385e2 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 @@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 23)] = ( + [(@"TestMapActions.cs", 24)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -243,5 +243,11 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt index 1395067abb94..1396cdf01789 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleComplexTypeParam_StringReturn.generated.txt @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Builder internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet( this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern, - global::System.Func handler, + global::System.Func handler, [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "", [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0) { @@ -90,16 +90,16 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 23)] = ( + [(@"TestMapActions.cs", 24)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); 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) @@ -110,7 +110,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); @@ -119,18 +119,18 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: p (Type = Todo, IsOptional = False, IsParsable = True, Source = Query) + // Endpoint Parameter: p (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, IsParsable = True, Source = Query) var p_raw = httpContext.Request.Query["p"]; if (StringValues.IsNullOrEmpty(p_raw)) { wasParamCheckFailure = true; } - var p_temp = p_raw.ToString(); - if (!global::Todo.TryParse(p_temp!, out var p_parsed_temp)) + var p_temp = (string?)p_raw; + if (!global::Microsoft.AspNetCore.Http.Generators.Tests.Todo.TryParse(p_temp!, out var p_parsed_temp)) { wasParamCheckFailure = true; } - global::Todo p_local = p_parsed_temp!; + global::Microsoft.AspNetCore.Http.Generators.Tests.Todo p_local = p_parsed_temp!; if (wasParamCheckFailure) { @@ -145,24 +145,24 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: p (Type = Todo, IsOptional = False, IsParsable = True, Source = Query) + // Endpoint Parameter: p (Type = Microsoft.AspNetCore.Http.Generators.Tests.Todo, IsOptional = False, IsParsable = True, Source = Query) var p_raw = httpContext.Request.Query["p"]; if (StringValues.IsNullOrEmpty(p_raw)) { wasParamCheckFailure = true; } - var p_temp = p_raw.ToString(); - if (!global::Todo.TryParse(p_temp!, out var p_parsed_temp)) + var p_temp = (string?)p_raw; + if (!global::Microsoft.AspNetCore.Http.Generators.Tests.Todo.TryParse(p_temp!, out var p_parsed_temp)) { wasParamCheckFailure = true; } - global::Todo p_local = p_parsed_temp!; + global::Microsoft.AspNetCore.Http.Generators.Tests.Todo p_local = p_parsed_temp!; if (wasParamCheckFailure) { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, p_local)); + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, p_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -269,5 +269,11 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt index db093970a50d..b158ab8c65d2 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleEnumParam_StringReturn.generated.txt @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Builder internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet( this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern, - global::System.Func handler, + global::System.Func handler, [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "", [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0) { @@ -90,16 +90,16 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 23)] = ( + [(@"TestMapActions.cs", 24)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); 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) @@ -110,7 +110,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); @@ -119,18 +119,18 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: p (Type = TodoStatus, IsOptional = False, IsParsable = True, Source = Query) + // Endpoint Parameter: p (Type = Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus, IsOptional = False, IsParsable = True, Source = Query) var p_raw = httpContext.Request.Query["p"]; if (StringValues.IsNullOrEmpty(p_raw)) { wasParamCheckFailure = true; } - var p_temp = p_raw.ToString(); - if (!Enum.TryParse(p_temp!, out var p_parsed_temp)) + var p_temp = (string?)p_raw; + if (!Enum.TryParse(p_temp!, out var p_parsed_temp)) { wasParamCheckFailure = true; } - global::TodoStatus p_local = p_parsed_temp!; + global::Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus p_local = p_parsed_temp!; if (wasParamCheckFailure) { @@ -145,24 +145,24 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandlerFiltered(HttpContext httpContext) { var wasParamCheckFailure = false; - // Endpoint Parameter: p (Type = TodoStatus, IsOptional = False, IsParsable = True, Source = Query) + // Endpoint Parameter: p (Type = Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus, IsOptional = False, IsParsable = True, Source = Query) var p_raw = httpContext.Request.Query["p"]; if (StringValues.IsNullOrEmpty(p_raw)) { wasParamCheckFailure = true; } - var p_temp = p_raw.ToString(); - if (!Enum.TryParse(p_temp!, out var p_parsed_temp)) + var p_temp = (string?)p_raw; + if (!Enum.TryParse(p_temp!, out var p_parsed_temp)) { wasParamCheckFailure = true; } - global::TodoStatus p_local = p_parsed_temp!; + global::Microsoft.AspNetCore.Http.Generators.Tests.TodoStatus p_local = p_parsed_temp!; if (wasParamCheckFailure) { httpContext.Response.StatusCode = 400; } - var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, p_local)); + var result = await filteredInvocation(EndpointFilterInvocationContext.Create(httpContext, p_local)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -269,5 +269,11 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } 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 bb7c3c1e728b..16e4f40abd61 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 @@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 23)] = ( + [(@"TestMapActions.cs", 24)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -121,7 +121,7 @@ namespace Microsoft.AspNetCore.Http.Generated var wasParamCheckFailure = false; // Endpoint Parameter: p (Type = string?, IsOptional = True, IsParsable = False, Source = Query) var p_raw = httpContext.Request.Query["p"]; - var p_temp = p_raw.Count > 0 ? p_raw.ToString() : null; + var p_temp = p_raw.Count > 0 ? (string?)p_raw : null; string p_local = p_temp!; if (wasParamCheckFailure) @@ -139,7 +139,7 @@ namespace Microsoft.AspNetCore.Http.Generated var wasParamCheckFailure = false; // Endpoint Parameter: p (Type = string?, IsOptional = True, IsParsable = False, Source = Query) var p_raw = httpContext.Request.Query["p"]; - var p_temp = p_raw.Count > 0 ? p_raw.ToString() : null; + var p_temp = p_raw.Count > 0 ? (string?)p_raw : null; string p_local = p_temp!; if (wasParamCheckFailure) @@ -253,5 +253,11 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleTimeOnlyParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleTimeOnlyParam_StringReturn.generated.txt index 29594257279b..28d64a9a57ef 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleTimeOnlyParam_StringReturn.generated.txt +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleTimeOnlyParam_StringReturn.generated.txt @@ -90,11 +90,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 23)] = ( + [(@"TestMapActions.cs", 24)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -125,7 +125,7 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var p_temp = p_raw.ToString(); + var p_temp = (string?)p_raw; if (!global::System.TimeOnly.TryParse(p_temp!, CultureInfo.InvariantCulture, out var p_parsed_temp)) { wasParamCheckFailure = true; @@ -151,7 +151,7 @@ namespace Microsoft.AspNetCore.Http.Generated { wasParamCheckFailure = true; } - var p_temp = p_raw.ToString(); + var p_temp = (string?)p_raw; if (!global::System.TimeOnly.TryParse(p_temp!, CultureInfo.InvariantCulture, out var p_parsed_temp)) { wasParamCheckFailure = true; @@ -269,5 +269,11 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } 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 fb30dd615c5f..d4582f7849d1 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 @@ -120,11 +120,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 23)] = ( + [(@"TestMapActions.cs", 24)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -174,11 +174,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 24)] = ( + [(@"TestMapActions.cs", 25)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -228,11 +228,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 25)] = ( + [(@"TestMapActions.cs", 26)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -282,11 +282,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 26)] = ( + [(@"TestMapActions.cs", 27)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 27)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -435,5 +435,11 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } 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 35ad5f1a8dfb..3608bfd2f5c5 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 @@ -120,11 +120,11 @@ namespace Microsoft.AspNetCore.Http.Generated private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() { - [(@"TestMapActions.cs", 23)] = ( + [(@"TestMapActions.cs", 24)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 23)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -178,11 +178,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 24)] = ( + [(@"TestMapActions.cs", 25)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 24)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -236,11 +236,11 @@ namespace Microsoft.AspNetCore.Http.Generated var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection.Empty; return new RequestDelegateResult(targetDelegate, metadata); }), - [(@"TestMapActions.cs", 25)] = ( + [(@"TestMapActions.cs", 26)] = ( (methodInfo, options) => { Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); - options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 25)); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 26)); return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; }, (del, options, inferredMetadataResult) => @@ -395,5 +395,11 @@ namespace Microsoft.AspNetCore.Http.Generated } return (false, default); } + + private static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + where T : class, IBindableFromHttpContext + { + return T.BindAsync(context, parameter); + } } } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs index 8ae55bc0d565..608593ce07a0 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs @@ -25,6 +25,10 @@ namespace Microsoft.AspNetCore.Http.Generators.Tests; public abstract class RequestDelegateGeneratorTestBase : LoggedTest { + // Change this to true and run tests in development to regenerate baseline files. + // Then: cp artifacts/bin/Microsoft.AspNetCore.Http.Extensions.Tests/Debug/net8.0/RequestDelegateGenerator/Baselines/* src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines + public bool RegenerateBaselines => false; + protected abstract bool IsGeneratorEnabled { get; } internal async Task<(GeneratorRunResult?, Compilation)> RunGeneratorAsync(string sources, params string[] updatedSources) @@ -215,6 +219,7 @@ private static string GetMapActionString(string sources) => $$""" #nullable enable using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Numerics; using System.Reflection; @@ -238,109 +243,6 @@ public static IEndpointRouteBuilder MapTestEndpoints(this IEndpointRouteBuilder return app; } } - -public enum TodoStatus -{ - Trap, // A trap for Enum.TryParse! - Done, - InProgress, - NotDone -} - -public interface ITodo -{ - public int Id { get; } - public string? Name { get; } - public bool IsComplete { get; } - public TodoStatus Status { get; } -} - -public class PrecedenceCheckTodo -{ - public PrecedenceCheckTodo(int magicValue) - { - MagicValue = magicValue; - } - public int MagicValue { get; } - public static bool TryParse(string? input, IFormatProvider? provider, out PrecedenceCheckTodo result) - { - result = new PrecedenceCheckTodo(42); - return true; - } - public static bool TryParse(string? input, out PrecedenceCheckTodo result) - { - result = new PrecedenceCheckTodo(24); - return true; - } -} - -public class PrecedenceCheckTodoWithoutFormat -{ - public PrecedenceCheckTodoWithoutFormat(int magicValue) - { - MagicValue = magicValue; - } - public int MagicValue { get; } - public static bool TryParse(string? input, out PrecedenceCheckTodoWithoutFormat result) - { - result = new PrecedenceCheckTodoWithoutFormat(24); - return true; - } -} - -public class ParsableTodo : IParsable -{ - public int Id { get; set; } - public string? Name { get; set; } = "Todo"; - public bool IsComplete { get; set; } - public static ParsableTodo Parse(string s, IFormatProvider? provider) - { - return new ParsableTodo(); - } - public static bool TryParse(string? input, IFormatProvider? provider, out ParsableTodo result) - { - if (input == "1") - { - result = new ParsableTodo - { - Id = 1, - Name = "Knit kitten mittens.", - IsComplete = false - }; - return true; - } - else - { - result = null!; - return false; - } - } -} - -public class Todo -{ - public int Id { get; set; } - public string? Name { get; set; } = "Todo"; - public bool IsComplete { get; set; } - public static bool TryParse(string input, out Todo? result) - { - if (input == "1") - { - result = new Todo - { - Id = 1, - Name = "Knit kitten mittens.", - IsComplete = false - }; - return true; - } - else - { - result = null; - return false; - } - } -} """; private static Task CreateCompilationAsync(string sources) { @@ -383,9 +285,20 @@ internal async Task VerifyAgainstBaselineUsingFile(Compilation compilation, [Cal { return; } + var baselineFilePath = Path.Combine("RequestDelegateGenerator", "Baselines", $"{callerName}.generated.txt"); var generatedSyntaxTree = compilation.SyntaxTrees.Last(); var generatedCode = await generatedSyntaxTree.GetTextAsync(); + + if (RegenerateBaselines) + { + var newSource = generatedCode.ToString() + .Replace(RequestDelegateGeneratorSources.GeneratedCodeAttribute, "%GENERATEDCODEATTRIBUTE%") + + Environment.NewLine; + await File.WriteAllTextAsync(baselineFilePath, newSource); + Assert.Fail("RegenerateBaselines=true. Do not merge PRs with this set."); + } + var baseline = await File.ReadAllTextAsync(baselineFilePath); var expectedLines = baseline .TrimEnd() // Trim newlines added by autoformat @@ -395,7 +308,7 @@ internal async Task VerifyAgainstBaselineUsingFile(Compilation compilation, [Cal Assert.True(CompareLines(expectedLines, generatedCode, out var errorMessage), errorMessage); } - private bool CompareLines(string[] expectedLines, SourceText sourceText, out string message) + private static bool CompareLines(string[] expectedLines, SourceText sourceText, out string message) { if (expectedLines.Length != sourceText.Lines.Count) { diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs index eb59e451bf7c..c309887cdf28 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs @@ -1,23 +1,19 @@ // 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; using System.Globalization; -using System.Net.Sockets; using System.Net; +using System.Net.Sockets; using System.Numerics; -using System.Reflection.Metadata; using System.Reflection; +using System.Reflection.Metadata; +using System.Text; +using System.Text.Encodings.Web; 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; -using System.Text.Encodings.Web; -using Microsoft.AspNetCore.WebUtilities; -using Microsoft.Extensions.WebEncoders.Testing; +using Microsoft.Extensions.Primitives; namespace Microsoft.AspNetCore.Http.Generators.Tests; @@ -559,7 +555,7 @@ public async Task MapAction_NoParam_ComplexReturn(string source) await VerifyResponseBodyAsync(httpContext, expectedBody); } - public static IEnumerable MapAction_NoParam_TaskOfTReturn_Data => new List() + 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}""" }, @@ -1182,4 +1178,219 @@ public async Task CanApplyFiltersOnHandlerWithVariousArguments(string handlerMet await VerifyResponseBodyAsync(httpContext, expectedBody); } + + [Fact] + public async Task MapAction_InferredTryParse_NonOptional_Provided() + { + var source = """ +app.MapGet("/", (HttpContext httpContext, int id) => +{ + httpContext.Items["id"] = id; +}); +"""; + var (_, compilation) = await RunGeneratorAsync(source); + var endpoint = GetEndpointFromCompilation(compilation); + + var httpContext = CreateHttpContext(); + httpContext.Request.Query = new QueryCollection(new Dictionary + { + ["id"] = "42", + }); + + httpContext.Request.Headers.Referer = "https://example.org"; + await endpoint.RequestDelegate(httpContext); + + Assert.Equal(42, httpContext.Items["id"]); + Assert.Equal(200, httpContext.Response.StatusCode); + } + + public static object[][] BindAsyncUriTypesAndOptionalitySupport = new object[][] + { + new object[] { "MyBindAsyncRecord", false }, + new object[] { "MyBindAsyncStruct", true }, + new object[] { "MyNullableBindAsyncStruct", false }, + new object[] { "MyBothBindAsyncStruct", true }, + new object[] { "MySimpleBindAsyncRecord", false, }, + new object[] { "MySimpleBindAsyncStruct", true }, + new object[] { "BindAsyncFromImplicitStaticAbstractInterface", false }, + new object[] { "InheritBindAsync", false }, + new object[] { "BindAsyncFromExplicitStaticAbstractInterface", false }, + // TODO: Fix this + //new object[] { "MyBindAsyncFromInterfaceRecord", false }, + }; + + public static IEnumerable BindAsyncUriTypes => + BindAsyncUriTypesAndOptionalitySupport.Select(x => new[] { x[0] }); + + [Theory] + [MemberData(nameof(BindAsyncUriTypes))] + public async Task MapAction_BindAsync_Optional_Provided(string bindAsyncType) + { + var source = $$""" +app.MapGet("/", (HttpContext httpContext, {{bindAsyncType}}? myBindAsyncParam) => +{ + httpContext.Items["uri"] = myBindAsyncParam?.Uri; +}); +"""; + var (_, compilation) = await RunGeneratorAsync(source); + var endpoint = GetEndpointFromCompilation(compilation); + + var httpContext = CreateHttpContext(); + httpContext.Request.Headers.Referer = "https://example.org"; + await endpoint.RequestDelegate(httpContext); + + Assert.Equal(new Uri("https://example.org"), httpContext.Items["uri"]); + Assert.Equal(200, httpContext.Response.StatusCode); + } + + [Theory] + [MemberData(nameof(BindAsyncUriTypes))] + public async Task MapAction_BindAsync_NonOptional_Provided(string bindAsyncType) + { + var source = $$""" +app.MapGet("/", (HttpContext httpContext, {{bindAsyncType}} myBindAsyncParam) => +{ + httpContext.Items["uri"] = myBindAsyncParam.Uri; +}); +"""; + var (_, compilation) = await RunGeneratorAsync(source); + var endpoint = GetEndpointFromCompilation(compilation); + + var httpContext = CreateHttpContext(); + httpContext.Request.Headers.Referer = "https://example.org"; + await endpoint.RequestDelegate(httpContext); + + Assert.Equal(new Uri("https://example.org"), httpContext.Items["uri"]); + Assert.Equal(200, httpContext.Response.StatusCode); + } + + [Theory] + [MemberData(nameof(BindAsyncUriTypesAndOptionalitySupport))] + public async Task MapAction_BindAsync_Optional_NotProvided(string bindAsyncType, bool expectException) + { + var source = $$""" +app.MapGet("/", (HttpContext httpContext, {{bindAsyncType}}? myBindAsyncParam) => +{ + httpContext.Items["uri"] = myBindAsyncParam?.Uri; +}); +"""; + var (_, compilation) = await RunGeneratorAsync(source); + var endpoint = GetEndpointFromCompilation(compilation); + + var httpContext = CreateHttpContext(); + + if (expectException) + { + // These types simply don't support optional parameters since they cannot return null. + var ex = await Assert.ThrowsAsync(() => endpoint.RequestDelegate(httpContext)); + Assert.Equal("The request is missing the required Referer header.", ex.Message); + } + else + { + await endpoint.RequestDelegate(httpContext); + + Assert.Null(httpContext.Items["uri"]); + Assert.Equal(200, httpContext.Response.StatusCode); + } + } + + [Theory] + [MemberData(nameof(BindAsyncUriTypesAndOptionalitySupport))] + public async Task MapAction_BindAsync_NonOptional_NotProvided(string bindAsyncType, bool expectException) + { + var source = $$""" +app.MapGet("/", (HttpContext httpContext, {{bindAsyncType}} myBindAsyncParam) => +{ + httpContext.Items["uri"] = myBindAsyncParam.Uri; +}); +"""; + var (_, compilation) = await RunGeneratorAsync(source); + var endpoint = GetEndpointFromCompilation(compilation); + + var httpContext = CreateHttpContext(); + + if (expectException) + { + var ex = await Assert.ThrowsAsync(() => endpoint.RequestDelegate(httpContext)); + Assert.Equal("The request is missing the required Referer header.", ex.Message); + } + else + { + await endpoint.RequestDelegate(httpContext); + + Assert.Null(httpContext.Items["uri"]); + Assert.Equal(400, httpContext.Response.StatusCode); + } + } + + [Fact] + public async Task MapAction_BindAsync_Snapshot() + { + var source = new StringBuilder(); + + var i = 0; + while (i < BindAsyncUriTypesAndOptionalitySupport.Length * 2) + { + var bindAsyncType = BindAsyncUriTypesAndOptionalitySupport[i / 2][0]; + source.AppendLine(CultureInfo.InvariantCulture, $$"""app.MapGet("/{{i}}", (HttpContext httpContext, {{bindAsyncType}} myBindAsyncParam) => "Hello world! {{i}}");"""); + i++; + source.AppendLine(CultureInfo.InvariantCulture, $$"""app.MapGet("/{{i}}", ({{bindAsyncType}}? myBindAsyncParam) => "Hello world! {{i}}");"""); + i++; + } + + var (_, compilation) = await RunGeneratorAsync(source.ToString()); + + await VerifyAgainstBaselineUsingFile(compilation); + + var endpoints = GetEndpointsFromCompilation(compilation); + Assert.Equal(BindAsyncUriTypesAndOptionalitySupport.Length * 2, endpoints.Length); + + for (i = 0; i < BindAsyncUriTypesAndOptionalitySupport.Length * 2; i++) + { + var httpContext = CreateHttpContext(); + // Set a referrer header so BindAsync always succeeds and the route handler is always called optional or not. + httpContext.Request.Headers.Referer = "https://example.org"; + + await endpoints[i].RequestDelegate(httpContext); + await VerifyResponseBodyAsync(httpContext, $"Hello world! {i}"); + } + } + + [Fact] + public async Task MapAction_BindAsync_ExceptionsAreUncaught() + { + var source = """ +app.MapGet("/", (HttpContext httpContext, MyBindAsyncTypeThatThrows myBindAsyncParam) => { }); +"""; + var (_, compilation) = await RunGeneratorAsync(source); + var endpoint = GetEndpointFromCompilation(compilation); + + var httpContext = CreateHttpContext(); + httpContext.Request.Headers.Referer = "https://example.org"; + + var ex = await Assert.ThrowsAsync(() => endpoint.RequestDelegate(httpContext)); + Assert.Equal("BindAsync failed", ex.Message); + } + + [Theory] + [InlineData("BindAsyncWrongType")] + [InlineData("BindAsyncFromStaticAbstractInterfaceWrongType")] + [InlineData("InheritBindAsyncWrongType")] + public async Task MapAction_BindAsync_WithWrongType_IsNotUsed(string bindAsyncType) + { + var source = $$""" +app.MapGet("/", ({{bindAsyncType}} myNotBindAsyncParam) => { }); +"""; + var (result, compilation) = await RunGeneratorAsync(source); + + VerifyStaticEndpointModel(result, endpointModel => + { + var parameter = Assert.Single(endpointModel.Parameters); + Assert.Equal("myNotBindAsyncParam", parameter.Name); + Assert.NotEqual(EndpointParameterSource.BindAsync, parameter.Source); + }); + + var ex = Assert.Throws(() => GetEndpointFromCompilation(compilation)); + Assert.StartsWith($"BindAsync method found on {bindAsyncType} with incorrect format.", ex.Message); + } } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs index 7f8776b8f80f..1a2bcd501abb 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/SharedTypes.cs @@ -1,9 +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.Diagnostics; +using System.Reflection; using Microsoft.AspNetCore.Http.Metadata; namespace Microsoft.AspNetCore.Http.Generators.Tests; +#nullable enable + public class TestService { public string TestServiceMethod() => "Produced from service!"; @@ -12,11 +16,367 @@ public class TestService public class Todo { public int Id { get; set; } - public string Name { get; set; } = "Todo"; + public string? Name { get; set; } = "Todo"; public bool IsComplete { get; set; } + public static bool TryParse(string input, out Todo? result) + { + if (input == "1") + { + result = new Todo + { + Id = 1, + Name = "Knit kitten mittens.", + IsComplete = false + }; + return true; + } + else + { + result = null; + return false; + } + } } public class CustomFromBodyAttribute : Attribute, IFromBodyMetadata { public bool AllowEmpty { get; set; } } + +public enum TodoStatus +{ + Trap, // A trap for Enum.TryParse! + Done, + InProgress, + NotDone +} + +public interface ITodo +{ + public int Id { get; } + public string? Name { get; } + public bool IsComplete { get; } + public TodoStatus Status { get; } +} + +public class PrecedenceCheckTodo +{ + public PrecedenceCheckTodo(int magicValue) + { + MagicValue = magicValue; + } + public int MagicValue { get; } + public static bool TryParse(string? input, IFormatProvider? provider, out PrecedenceCheckTodo result) + { + result = new PrecedenceCheckTodo(42); + return true; + } + public static bool TryParse(string? input, out PrecedenceCheckTodo result) + { + result = new PrecedenceCheckTodo(24); + return true; + } +} + +public class PrecedenceCheckTodoWithoutFormat +{ + public PrecedenceCheckTodoWithoutFormat(int magicValue) + { + MagicValue = magicValue; + } + public int MagicValue { get; } + public static bool TryParse(string? input, out PrecedenceCheckTodoWithoutFormat result) + { + result = new PrecedenceCheckTodoWithoutFormat(24); + return true; + } +} + +public class ParsableTodo : IParsable +{ + public int Id { get; set; } + public string? Name { get; set; } = "Todo"; + public bool IsComplete { get; set; } + public static ParsableTodo Parse(string s, IFormatProvider? provider) + { + return new ParsableTodo(); + } + public static bool TryParse(string? input, IFormatProvider? provider, out ParsableTodo result) + { + if (input == "1") + { + result = new ParsableTodo + { + Id = 1, + Name = "Knit kitten mittens.", + IsComplete = false + }; + return true; + } + else + { + result = null!; + return false; + } + } +} + +public record MyBindAsyncRecord(Uri Uri) +{ + public static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + { + if (parameter.ParameterType != typeof(MyBindAsyncRecord)) + { + throw new UnreachableException($"Unexpected parameter type: {parameter.ParameterType}"); + } + if (parameter.Name != "myBindAsyncParam") + { + throw new UnreachableException("Unexpected parameter name"); + } + + if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri)) + { + return new(result: null); + } + + return new(result: new(uri)); + } + + // BindAsync(HttpContext, ParameterInfo) should be preferred over TryParse(string, ...) if there's + // no [FromRoute] or [FromQuery] attributes. + public static bool TryParse(string? value, out MyBindAsyncRecord? result) => + throw new NotImplementedException(); +} + +public record struct MyNullableBindAsyncStruct(Uri Uri) +{ + public static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + { + if (parameter.ParameterType != typeof(MyNullableBindAsyncStruct) && parameter.ParameterType != typeof(MyNullableBindAsyncStruct?)) + { + throw new UnreachableException("Unexpected parameter type"); + } + if (parameter.Name != "myBindAsyncParam") + { + throw new UnreachableException("Unexpected parameter name"); + } + + if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri)) + { + return new(result: null); + } + + return new(result: new(uri)); + } + + public static bool TryParse(string? value, out MyNullableBindAsyncStruct? result) => + throw new NotImplementedException(); +} + +public record struct MyBindAsyncStruct(Uri Uri) +{ + public static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + { + if (parameter.ParameterType != typeof(MyBindAsyncStruct) && parameter.ParameterType != typeof(MyBindAsyncStruct?)) + { + throw new UnreachableException("Unexpected parameter type"); + } + if (parameter.Name != "myBindAsyncParam") + { + throw new UnreachableException("Unexpected parameter name"); + } + + if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri)) + { + throw new BadHttpRequestException("The request is missing the required Referer header."); + } + + return new(result: new(uri)); + } + + // BindAsync(HttpContext, ParameterInfo) should be preferred over TryParse(string, ...) if there's + // no [FromRoute] or [FromQuery] attributes. + public static bool TryParse(string? value, out MyBindAsyncStruct result) => + throw new NotImplementedException(); +} + +public record struct MyBothBindAsyncStruct(Uri Uri) +{ + public static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + { + if (parameter.ParameterType != typeof(MyBothBindAsyncStruct) && parameter.ParameterType != typeof(MyBothBindAsyncStruct?)) + { + throw new UnreachableException("Unexpected parameter type"); + } + if (parameter.Name != "myBindAsyncParam") + { + throw new UnreachableException("Unexpected parameter name"); + } + + if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri)) + { + throw new BadHttpRequestException("The request is missing the required Referer header."); + } + + return new(result: new(uri)); + } + + // BindAsync with ParameterInfo is preferred + public static ValueTask BindAsync(HttpContext context) => + throw new NotImplementedException(); +} + +public record struct MySimpleBindAsyncStruct(Uri Uri) +{ + public static ValueTask BindAsync(HttpContext context) + { + if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri)) + { + throw new BadHttpRequestException("The request is missing the required Referer header."); + } + + return new(result: new(uri)); + } + + public static bool TryParse(string? value, out MySimpleBindAsyncStruct result) => + throw new NotImplementedException(); +} + +public record MySimpleBindAsyncRecord(Uri Uri) +{ + public static ValueTask BindAsync(HttpContext context) + { + if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri)) + { + return new(result: null); + } + + return new(result: new(uri)); + } + + public static bool TryParse(string? value, out MySimpleBindAsyncRecord? result) => + throw new NotImplementedException(); +} + +public interface IBindAsync +{ + static ValueTask BindAsync(HttpContext context) + { + if (typeof(T) != typeof(MyBindAsyncFromInterfaceRecord)) + { + throw new UnreachableException("Unexpected parameter type"); + } + + if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri)) + { + return new(default(T)); + } + + return new(result: (T)(object)new MyBindAsyncFromInterfaceRecord(uri)); + } +} + +public class BindAsyncWrongType +{ + public static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) => + throw new UnreachableException("We shouldn't bind from the wrong type!"); +} + +public record MyBindAsyncFromInterfaceRecord(Uri Uri) : IBindAsync +{ +} + +public interface IHaveUri +{ + Uri? Uri { get; set; } +} + +public class BaseBindAsync where T : IHaveUri, new() +{ + public static ValueTask BindAsync(HttpContext context) + { + if (typeof(T) != typeof(InheritBindAsync)) + { + throw new UnreachableException("Unexpected parameter type"); + } + + if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri)) + { + return new(default(T)); + } + + return new(result: new() { Uri = uri }); + } +} + +public class InheritBindAsync : BaseBindAsync, IHaveUri +{ + public Uri? Uri { get; set; } +} + +// Using wrong T on purpose +public class InheritBindAsyncWrongType : BaseBindAsync +{ +} + +public class BindAsyncFromImplicitStaticAbstractInterface : IBindableFromHttpContext +{ + public Uri? Uri { get; init; } + + public static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) + { + if (parameter.ParameterType != typeof(BindAsyncFromImplicitStaticAbstractInterface)) + { + throw new UnreachableException("Unexpected parameter type"); + } + if (parameter.Name != "myBindAsyncParam") + { + throw new UnreachableException("Unexpected parameter name"); + } + + if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri)) + { + return new(result: null); + } + + return new(result: new() { Uri = uri }); + } +} + +public class BindAsyncFromExplicitStaticAbstractInterface : IBindableFromHttpContext +{ + public Uri? Uri { get; init; } + + static ValueTask IBindableFromHttpContext.BindAsync(HttpContext context, ParameterInfo parameter) + { + if (parameter.ParameterType != typeof(BindAsyncFromExplicitStaticAbstractInterface)) + { + throw new UnreachableException("Unexpected parameter type"); + } + if (parameter.Name != "myBindAsyncParam") + { + throw new UnreachableException("Unexpected parameter name"); + } + + if (!Uri.TryCreate(context.Request.Headers.Referer, UriKind.Absolute, out var uri)) + { + return new(result: null); + } + + return new(result: new() { Uri = uri }); + } +} + +public class BindAsyncFromStaticAbstractInterfaceWrongType : IBindableFromHttpContext +{ + public static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) => + throw new UnreachableException("We shouldn't bind from the wrong interface type!"); +} + +public class MyBindAsyncTypeThatThrows +{ + public static ValueTask BindAsync(HttpContext context, ParameterInfo parameter) => + throw new InvalidOperationException("BindAsync failed"); +} + +#nullable restore diff --git a/src/Shared/RoslynUtils/Bindability.cs b/src/Shared/RoslynUtils/Bindability.cs deleted file mode 100644 index 32c75ed33825..000000000000 --- a/src/Shared/RoslynUtils/Bindability.cs +++ /dev/null @@ -1,14 +0,0 @@ -// 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.Collections.Generic; -using System.Text; - -namespace Microsoft.AspNetCore.Analyzers.Infrastructure; -internal enum Bindability -{ - Bindable, - NotBindable, - InvalidReturnType -} diff --git a/src/Shared/RoslynUtils/Parsability.cs b/src/Shared/RoslynUtils/Parsability.cs index 80840e2755dd..6535ca530d12 100644 --- a/src/Shared/RoslynUtils/Parsability.cs +++ b/src/Shared/RoslynUtils/Parsability.cs @@ -6,8 +6,3 @@ using System.Text; namespace Microsoft.AspNetCore.Analyzers.Infrastructure; -internal enum Parsability -{ - Parsable, - NotParsable -} diff --git a/src/Shared/RoslynUtils/ParsabilityHelper.cs b/src/Shared/RoslynUtils/ParsabilityHelper.cs index c23425b8e224..3864e2adc650 100644 --- a/src/Shared/RoslynUtils/ParsabilityHelper.cs +++ b/src/Shared/RoslynUtils/ParsabilityHelper.cs @@ -17,7 +17,7 @@ namespace Microsoft.AspNetCore.Analyzers.Infrastructure; internal static class ParsabilityHelper { - private static bool IsTypeAlwaysParsableOrBindable(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out ParsabilityMethod? parsabilityMethod) + private static bool IsTypeAlwaysParsable(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, [NotNullWhen(true)] out ParsabilityMethod? parsabilityMethod) { // Any enum is valid. if (typeSymbol.TypeKind == TypeKind.Enum) @@ -49,9 +49,9 @@ internal static Parsability GetParsability(ITypeSymbol typeSymbol, WellKnownType return GetParsability(typeSymbol, wellKnownTypes, out var _); } - internal static Parsability GetParsability(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, out ParsabilityMethod? parsabilityMethod) + internal static Parsability GetParsability(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, [NotNullWhen(false)] out ParsabilityMethod? parsabilityMethod) { - if (IsTypeAlwaysParsableOrBindable(typeSymbol, wellKnownTypes, out parsabilityMethod)) + if (IsTypeAlwaysParsable(typeSymbol, wellKnownTypes, out parsabilityMethod)) { return Parsability.Parsable; } @@ -64,7 +64,9 @@ internal static Parsability GetParsability(ITypeSymbol typeSymbol, WellKnownType } // Check if the parameter type has a public static TryParse method. - var tryParseMethods = typeSymbol.GetMembers("TryParse").OfType(); + var tryParseMethods = typeSymbol.GetThisAndBaseTypes() + .SelectMany(t => t.GetMembers("TryParse")) + .OfType(); if (tryParseMethods.Any(m => IsTryParseWithFormat(m, wellKnownTypes))) { @@ -114,13 +116,14 @@ internal static bool IsParsableViaIParsable(ITypeSymbol typeSymbol, WellKnownTyp private static bool IsBindableViaIBindableFromHttpContext(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes) { var iBindableFromHttpContextTypeSymbol = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_IBindableFromHttpContext_T); - var implementsIBindableFromHttpContext = typeSymbol.AllInterfaces.Any( + var constructedTypeSymbol = typeSymbol.AllInterfaces.FirstOrDefault( i => SymbolEqualityComparer.Default.Equals(i.ConstructedFrom, iBindableFromHttpContextTypeSymbol) ); - return implementsIBindableFromHttpContext; + return constructedTypeSymbol != null && + SymbolEqualityComparer.Default.Equals(constructedTypeSymbol.TypeArguments[0].UnwrapTypeSymbol(), typeSymbol); } - private static bool IsBindAsync(IMethodSymbol methodSymbol, INamedTypeSymbol typeSymbol, WellKnownTypes wellKnownTypes) + private static bool IsBindAsync(IMethodSymbol methodSymbol, ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes) { return methodSymbol.DeclaredAccessibility == Accessibility.Public && methodSymbol.IsStatic && @@ -131,7 +134,7 @@ methodSymbol.ReturnType is INamedTypeSymbol returnType && SymbolEqualityComparer.Default.Equals(returnType.TypeArguments[0], typeSymbol); } - private static bool IsBindAsyncWithParameter(IMethodSymbol methodSymbol, INamedTypeSymbol typeSymbol, WellKnownTypes wellKnownTypes) + private static bool IsBindAsyncWithParameter(IMethodSymbol methodSymbol, ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes) { return methodSymbol.DeclaredAccessibility == Accessibility.Public && methodSymbol.IsStatic && @@ -139,49 +142,91 @@ private static bool IsBindAsyncWithParameter(IMethodSymbol methodSymbol, INamedT SymbolEqualityComparer.Default.Equals(methodSymbol.Parameters[0].Type, wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_HttpContext)) && SymbolEqualityComparer.Default.Equals(methodSymbol.Parameters[1].Type, wellKnownTypes.Get(WellKnownType.System_Reflection_ParameterInfo)) && methodSymbol.ReturnType is INamedTypeSymbol returnType && - IsReturningValueTaskOfT(returnType, typeSymbol, wellKnownTypes); + IsReturningValueTaskOfTOrNullableT(returnType, typeSymbol, wellKnownTypes); } - private static bool IsReturningValueTaskOfT(INamedTypeSymbol returnType, INamedTypeSymbol containingType, WellKnownTypes wellKnownTypes) + private static bool IsReturningValueTaskOfTOrNullableT(INamedTypeSymbol returnType, ITypeSymbol containingType, WellKnownTypes wellKnownTypes) { return SymbolEqualityComparer.Default.Equals(returnType.ConstructedFrom, wellKnownTypes.Get(WellKnownType.System_Threading_Tasks_ValueTask_T)) && - SymbolEqualityComparer.Default.Equals(returnType.TypeArguments[0], containingType); + SymbolEqualityComparer.Default.Equals(returnType.TypeArguments[0].UnwrapTypeSymbol(), containingType); } - internal static Bindability GetBindability(INamedTypeSymbol typeSymbol, WellKnownTypes wellKnownTypes) + internal static Bindability GetBindability(ITypeSymbol typeSymbol, WellKnownTypes wellKnownTypes, out BindabilityMethod? bindabilityMethod) { - if (IsTypeAlwaysParsableOrBindable(typeSymbol, wellKnownTypes, out var _)) - { - return Bindability.Bindable; - } + bindabilityMethod = null; if (IsBindableViaIBindableFromHttpContext(typeSymbol, wellKnownTypes)) { + bindabilityMethod = BindabilityMethod.IBindableFromHttpContext; return Bindability.Bindable; } - var bindAsyncMethods = typeSymbol.GetMembers("BindAsync").OfType(); + // TODO: Search interfaces too. See MyBindAsyncFromInterfaceRecord test as an example. + // It's easy to find, but we need to flow the interface back to the emitter to call it. + // With parent types, we can continue to pretend we're calling a method directly on the child. + var bindAsyncMethods = typeSymbol.GetThisAndBaseTypes() + .SelectMany(t => t.GetMembers("BindAsync")) + .OfType(); + foreach (var methodSymbol in bindAsyncMethods) { - if (IsBindAsync(methodSymbol, typeSymbol, wellKnownTypes) || IsBindAsyncWithParameter(methodSymbol, typeSymbol, wellKnownTypes)) + if (IsBindAsyncWithParameter(methodSymbol, typeSymbol, wellKnownTypes)) + { + bindabilityMethod = BindabilityMethod.BindAsyncWithParameter; + break; + } + if (IsBindAsync(methodSymbol, typeSymbol, wellKnownTypes)) { - return Bindability.Bindable; + bindabilityMethod = BindabilityMethod.BindAsync; } } + if (bindabilityMethod is not null) + { + return Bindability.Bindable; + } + // See if we can give better guidance on why the BindAsync method is no good. if (bindAsyncMethods.Count() == 1) { var bindAsyncMethod = bindAsyncMethods.Single(); - if (bindAsyncMethod.ReturnType is INamedTypeSymbol returnType && !IsReturningValueTaskOfT(returnType, typeSymbol, wellKnownTypes)) + if (bindAsyncMethod.ReturnType is INamedTypeSymbol returnType && !IsReturningValueTaskOfTOrNullableT(returnType, typeSymbol, wellKnownTypes)) { return Bindability.InvalidReturnType; } - } return Bindability.NotBindable; } } +internal enum Parsability +{ + Parsable, + NotParsable, +} + +internal enum ParsabilityMethod +{ + String, + IParsable, + Enum, + TryParse, + TryParseWithFormatProvider, + Uri, +} + +internal enum Bindability +{ + Bindable, + NotBindable, + InvalidReturnType, +} + +internal enum BindabilityMethod +{ + IBindableFromHttpContext, + BindAsync, + BindAsyncWithParameter, +} diff --git a/src/Shared/RoslynUtils/ParsabilityMethod.cs b/src/Shared/RoslynUtils/ParsabilityMethod.cs deleted file mode 100644 index e4b9fbe23064..000000000000 --- a/src/Shared/RoslynUtils/ParsabilityMethod.cs +++ /dev/null @@ -1,14 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -namespace Microsoft.AspNetCore.Analyzers.Infrastructure; - -internal enum ParsabilityMethod -{ - String, - IParsable, - Enum, - TryParse, - TryParseWithFormatProvider, - Uri -} diff --git a/src/Shared/RoslynUtils/SymbolExtensions.cs b/src/Shared/RoslynUtils/SymbolExtensions.cs index 7c73a703a73a..e4f81d5c1a15 100644 --- a/src/Shared/RoslynUtils/SymbolExtensions.cs +++ b/src/Shared/RoslynUtils/SymbolExtensions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics.CodeAnalysis; using System.Linq; @@ -11,12 +12,12 @@ namespace Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; internal static class SymbolExtensions { - public static INamedTypeSymbol? UnwrapTypeSymbol(this ITypeSymbol typeSymbol) + public static ITypeSymbol UnwrapTypeSymbol(this ITypeSymbol typeSymbol, bool unwrapArray = false) { INamedTypeSymbol? unwrappedTypeSymbol = null; - // If it is an array, unwrap it. - if (typeSymbol is IArrayTypeSymbol arrayTypeSymbol) + // If it is an array, and unwrapArray = true, unwrap it before unwrapping nullable. + if (unwrapArray && typeSymbol is IArrayTypeSymbol arrayTypeSymbol) { unwrappedTypeSymbol = arrayTypeSymbol.ElementType as INamedTypeSymbol; } @@ -31,7 +32,17 @@ internal static class SymbolExtensions unwrappedTypeSymbol = unwrappedTypeSymbol.TypeArguments[0] as INamedTypeSymbol; } - return unwrappedTypeSymbol; + return unwrappedTypeSymbol ?? typeSymbol; + } + + public static IEnumerable GetThisAndBaseTypes(this ITypeSymbol? type) + { + var current = type; + while (current != null) + { + yield return current; + current = current.BaseType; + } } public static bool HasAttribute(this ISymbol symbol, INamedTypeSymbol attributeType)