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 5e6cbf836ff8..83c09bcf59a2 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Microsoft.AspNetCore.App.Analyzers.csproj +++ b/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Microsoft.AspNetCore.App.Analyzers.csproj @@ -25,6 +25,8 @@ + + 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 918ef3c78c1a..b791dcb5efd8 100644 --- a/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.Generators.csproj +++ b/src/Http/Http.Extensions/gen/Microsoft.AspNetCore.Http.Generators.csproj @@ -21,7 +21,9 @@ + + diff --git a/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs b/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs index 77fca5f07d52..6f4432d9af41 100644 --- a/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs +++ b/src/Http/Http.Extensions/gen/RequestDelegateGenerator.cs @@ -120,7 +120,7 @@ public void Initialize(IncrementalGeneratorInitializationContext context) lineNumber); } """); - } + } return code.ToString(); }); diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs new file mode 100644 index 000000000000..f0a2c48b28d5 --- /dev/null +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointEmitter.cs @@ -0,0 +1,43 @@ +// 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.Http.Generators.StaticRouteHandlerModel.Emitters; +internal static class EndpointEmitter +{ + internal static string EmitParameterPreparation(this Endpoint endpoint) + { + var parameterPreparationBuilder = new StringBuilder(); + + for (var parameterIndex = 0; parameterIndex < endpoint.Parameters.Length; parameterIndex++) + { + var parameter = endpoint.Parameters[parameterIndex]; + + var parameterPreparationCode = parameter switch + { + { + Source: EndpointParameterSource.SpecialType + } => parameter.EmitSpecialParameterPreparation(), + { + Source: EndpointParameterSource.Query, + } => parameter.EmitQueryParameterPreparation(), + _ => throw new Exception("Unreachable!") + }; + + // To avoid having two newlines after the block of parameter handling code. + if (parameterIndex < endpoint.Parameters.Length - 1) + { + parameterPreparationBuilder.AppendLine(parameterPreparationCode); + } + else + { + parameterPreparationBuilder.Append(parameterPreparationCode); + } + } + + return parameterPreparationBuilder.ToString(); + } +} diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs new file mode 100644 index 000000000000..23a7f4611ff5 --- /dev/null +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Emitters/EndpointParameterEmitter.cs @@ -0,0 +1,56 @@ +// 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.Http.Generators.StaticRouteHandlerModel.Emitters; +internal static class EndpointParameterEmitter +{ + internal static string EmitSpecialParameterPreparation(this EndpointParameter endpointParameter) + { + return $""" + var {endpointParameter.Name}_local = {endpointParameter.AssigningCode}; +"""; + } + + internal static string EmitQueryParameterPreparation(this EndpointParameter endpointParameter) + { + var builder = new StringBuilder(); + + // Preamble for diagnostics purposes. + builder.AppendLine($$""" + // Endpoint Parameter: {{endpointParameter.Name}} (Type = {{endpointParameter.Type}}, IsOptional = {{endpointParameter.IsOptional}}, Source = {{endpointParameter.Source}}) +"""); + + // Grab raw input from HttpContext. + builder.AppendLine($$""" + var {{endpointParameter.Name}}_raw = {{endpointParameter.AssigningCode}}; +"""); + + // If we are not optional, then at this point we can just assign the string value to the handler argument, + // otherwise we need to detect whether no value is provided and set the handler argument to null to + // preserve consistency with RDF behavior. We don't want to emit the conditional block to avoid + // compiler errors around null handling. + if (endpointParameter.IsOptional) + { + builder.AppendLine($$""" + var {{endpointParameter.HandlerArgument}} = {{endpointParameter.Name}}_raw.Count > 0 ? {{endpointParameter.Name}}_raw.ToString() : null; +"""); + } + else + { + builder.AppendLine($$""" + if (StringValues.IsNullOrEmpty({{endpointParameter.Name}}_raw)) + { + httpContext.Response.StatusCode = 400; + return Task.CompletedTask; + } + var {{endpointParameter.HandlerArgument}} = {{endpointParameter.Name}}_raw.ToString(); +"""); + } + + return builder.ToString(); + } +} diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs index c698758755bc..e863dfedf78b 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/Endpoint.cs @@ -88,7 +88,7 @@ public static bool SignatureEquals(Endpoint a, Endpoint b) for (var i = 0; i < a.Parameters.Length; i++) { - if (a.Parameters[i].Equals(b.Parameters[i])) + if (!a.Parameters[i].Equals(b.Parameters[i])) { return false; } diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs index 72a6f1f0a948..0c820a0ee4ec 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/EndpointParameter.cs @@ -3,8 +3,10 @@ using System; using Microsoft.AspNetCore.App.Analyzers.Infrastructure; +using Microsoft.AspNetCore.Analyzers.RouteEmbeddedLanguage.Infrastructure; using Microsoft.CodeAnalysis; using WellKnownType = Microsoft.AspNetCore.App.Analyzers.Infrastructure.WellKnownTypeData.WellKnownType; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel; @@ -15,11 +17,25 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp Type = parameter.Type; Name = parameter.Name; Source = EndpointParameterSource.Unknown; + HandlerArgument = $"{parameter.Name}_local"; - if (GetSpecialTypeCallingCode(Type, wellKnownTypes) is string callingCode) + var fromQueryMetadataInterfaceType = wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_Metadata_IFromQueryMetadata); + + if (GetSpecialTypeAssigningCode(Type, wellKnownTypes) is string assigningCode) { Source = EndpointParameterSource.SpecialType; - CallingCode = callingCode; + AssigningCode = assigningCode; + } + else if (parameter.HasAttributeImplementingInterface(fromQueryMetadataInterfaceType)) + { + Source = EndpointParameterSource.Query; + AssigningCode = $"httpContext.Request.Query[\"{parameter.Name}\"]"; + IsOptional = parameter.Type is INamedTypeSymbol parameterType && parameterType.NullableAnnotation == NullableAnnotation.Annotated; + } + else + { + // TODO: Inferencing rules go here - but for now: + Source = EndpointParameterSource.Unknown; } } @@ -28,23 +44,17 @@ public EndpointParameter(IParameterSymbol parameter, WellKnownTypes wellKnownTyp // TODO: If the parameter has [FromRoute("AnotherName")] or similar, prefer that. public string Name { get; } - public string? CallingCode { get; } + public string? AssigningCode { get; } + public string HandlerArgument { get; } + public bool IsOptional { get; } public string EmitArgument() { - switch (Source) - { - case EndpointParameterSource.SpecialType: - return CallingCode!; - default: - // Eventually there should be know unknown parameter sources, but in the meantime we don't expect them to get this far. - // The netstandard2.0 target means there is no UnreachableException. - throw new Exception("Unreachable!"); - } + return HandlerArgument; } // TODO: Handle special form types like IFormFileCollection that need special body-reading logic. - private static string? GetSpecialTypeCallingCode(ITypeSymbol type, WellKnownTypes wellKnownTypes) + private static string? GetSpecialTypeAssigningCode(ITypeSymbol type, WellKnownTypes wellKnownTypes) { if (SymbolEqualityComparer.Default.Equals(type, wellKnownTypes.Get(WellKnownType.Microsoft_AspNetCore_Http_HttpContext))) { diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs index 4ca73fb4c1d4..6d7369e51afd 100644 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs +++ b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/StaticRouteHandlerModel.Emitter.cs @@ -2,9 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; using System.Linq; +using System.Net.Cache; using System.Text; +using Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel.Emitters; using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Microsoft.AspNetCore.Http.Generators.StaticRouteHandlerModel; @@ -61,14 +65,18 @@ public static string EmitRequestHandler(this Endpoint endpoint) var resultAssignment = endpoint.Response.IsVoid ? string.Empty : "var result = "; var awaitHandler = endpoint.Response.IsAwaitable ? "await " : string.Empty; var setContentType = endpoint.Response.IsVoid ? string.Empty : $@"httpContext.Response.ContentType ??= ""{endpoint.Response.ContentType}"";"; - return $$""" + + var requestHandlerSource = $$""" {{handlerSignature}} { +{{endpoint.EmitParameterPreparation()}} {{setContentType}} {{resultAssignment}}{{awaitHandler}}handler({{endpoint.EmitArgumentList()}}); {{(endpoint.Response.IsVoid ? "return Task.CompletedTask;" : endpoint.EmitResponseWritingCall())}} } """; + + return requestHandlerSource; } private static string EmitResponseWritingCall(this Endpoint endpoint) @@ -108,14 +116,12 @@ private static string EmitResponseWritingCall(this Endpoint endpoint) * can be used to reduce the boxing that happens at runtime when constructing * the context object. */ - public static string EmitFilteredRequestHandler(this Endpoint endpoint) + public static string EmitFilteredRequestHandler(this Endpoint _) { - var argumentList = endpoint.Parameters.Length == 0 ? string.Empty : $", {endpoint.EmitArgumentList()}"; - return $$""" async Task RequestHandlerFiltered(HttpContext httpContext) { - var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext{{argumentList}})); + var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } """; diff --git a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/WellKnownTypeData.cs b/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/WellKnownTypeData.cs deleted file mode 100644 index 9b26b8c76e37..000000000000 --- a/src/Http/Http.Extensions/gen/StaticRouteHandlerModel/WellKnownTypeData.cs +++ /dev/null @@ -1,45 +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.App.Analyzers.Infrastructure; - -internal static class WellKnownTypeData -{ - public enum WellKnownType - { - Microsoft_AspNetCore_Http_HttpContext, - Microsoft_AspNetCore_Http_HttpRequest, - Microsoft_AspNetCore_Http_HttpResponse, - Microsoft_AspNetCore_Http_IFormCollection, - Microsoft_AspNetCore_Http_IFormFileCollection, - Microsoft_AspNetCore_Http_IFormFile, - Microsoft_AspNetCore_Http_IResult, - System_IO_Pipelines_PipeReader, - System_IO_Stream, - System_Security_Claims_ClaimsPrincipal, - System_Threading_CancellationToken, - System_Threading_Tasks_Task, - System_Threading_Tasks_Task_T, - System_Threading_Tasks_ValueTask, - System_Threading_Tasks_ValueTask_T, - } - - public static readonly string[] WellKnownTypeNames = new[] - { - "Microsoft.AspNetCore.Http.HttpContext", - "Microsoft.AspNetCore.Http.HttpRequest", - "Microsoft.AspNetCore.Http.HttpResponse", - "Microsoft.AspNetCore.Http.IFormCollection", - "Microsoft.AspNetCore.Http.IFormFileCollection", - "Microsoft.AspNetCore.Http.IFormFile", - "Microsoft.AspNetCore.Http.IResult", - "System.IO.Pipelines.PipeReader", - "System.IO.Stream", - "System.Security.Claims.ClaimsPrincipal", - "System.Threading.CancellationToken", - "System.Threading.Tasks.Task", - "System.Threading.Tasks.Task`1", - "System.Threading.Tasks.ValueTask", - "System.Threading.Tasks.ValueTask`1", - }; -} 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 09a381ba791e..b6a0988e5a9f 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 @@ -16,6 +16,7 @@ + diff --git a/src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs b/src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs index 43435af6e8c5..4cbb1e8ff8f2 100644 --- a/src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs +++ b/src/Http/Http.Extensions/test/ProblemDetailsDefaultWriterTest.cs @@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Http.Json; using System.Text.Json.Serialization; using Microsoft.CodeAnalysis; +using JsonOptions = Microsoft.AspNetCore.Http.Json.JsonOptions; namespace Microsoft.AspNetCore.Http.Extensions.Tests; diff --git a/src/Http/Http.Extensions/test/ProblemDetailsServiceCollectionExtensionsTest.cs b/src/Http/Http.Extensions/test/ProblemDetailsServiceCollectionExtensionsTest.cs index bb0d16c9b039..7b4e3ab437f2 100644 --- a/src/Http/Http.Extensions/test/ProblemDetailsServiceCollectionExtensionsTest.cs +++ b/src/Http/Http.Extensions/test/ProblemDetailsServiceCollectionExtensionsTest.cs @@ -8,6 +8,7 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.Options; using Moq; +using JsonOptions = Microsoft.AspNetCore.Http.Json.JsonOptions; namespace Microsoft.AspNetCore.Http.Extensions.Tests; 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 new file mode 100644 index 000000000000..b9a14be5a3b0 --- /dev/null +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleSpecialTypeParam_StringReturn.generated.txt @@ -0,0 +1,179 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable + +namespace Microsoft.AspNetCore.Builder +{ + %GENERATEDCODEATTRIBUTE% + internal class SourceKey + { + public string Path { get; init; } + public int Line { get; init; } + + public SourceKey(string path, int line) + { + Path = path; + Line = line; + } + } + + // This class needs to be internal so that the compiled application + // has access to the strongly-typed endpoint definitions that are + // generated by the compiler so that they will be favored by + // overload resolution and opt the runtime in to the code generated + // implementation produced here. + %GENERATEDCODEATTRIBUTE% + internal static class GenerateRouteBuilderEndpoints + { + private static readonly string[] GetVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Get }; + private static readonly string[] PostVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Post }; + private static readonly string[] PutVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Put }; + private static readonly string[] DeleteVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Delete }; + private static readonly string[] PatchVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Patch }; + + internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet( + this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, + [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern, + global::System.Func handler, + [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "", + [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0) + { + return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore( + endpoints, + pattern, + handler, + GetVerb, + filePath, + lineNumber); + } + + } +} + +namespace Microsoft.AspNetCore.Http.Generated +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using System.IO; + using Microsoft.AspNetCore.Routing; + using Microsoft.AspNetCore.Routing.Patterns; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Metadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.FileProviders; + using Microsoft.Extensions.Primitives; + + using MetadataPopulator = System.Func; + using RequestDelegateFactoryFunc = System.Func; + + file static class GeneratedRouteBuilderExtensionsCore + { + + private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() + { + [(@"TestMapActions.cs", 15)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 15)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (System.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); + } + + Task RequestHandler(HttpContext httpContext) + { + var req_local = httpContext.Request; + var res_local = httpContext.Response; + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(req_local, res_local); + return httpContext.Response.WriteAsync(result); + } + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext)); + 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); + } + } + } +} 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 new file mode 100644 index 000000000000..2f9da29b3cb0 --- /dev/null +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_MultipleStringParam_StringReturn.generated.txt @@ -0,0 +1,195 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable + +namespace Microsoft.AspNetCore.Builder +{ + %GENERATEDCODEATTRIBUTE% + internal class SourceKey + { + public string Path { get; init; } + public int Line { get; init; } + + public SourceKey(string path, int line) + { + Path = path; + Line = line; + } + } + + // This class needs to be internal so that the compiled application + // has access to the strongly-typed endpoint definitions that are + // generated by the compiler so that they will be favored by + // overload resolution and opt the runtime in to the code generated + // implementation produced here. + %GENERATEDCODEATTRIBUTE% + internal static class GenerateRouteBuilderEndpoints + { + private static readonly string[] GetVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Get }; + private static readonly string[] PostVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Post }; + private static readonly string[] PutVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Put }; + private static readonly string[] DeleteVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Delete }; + private static readonly string[] PatchVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Patch }; + + internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet( + this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, + [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern, + global::System.Func handler, + [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "", + [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0) + { + return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore( + endpoints, + pattern, + handler, + GetVerb, + filePath, + lineNumber); + } + + } +} + +namespace Microsoft.AspNetCore.Http.Generated +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using System.IO; + using Microsoft.AspNetCore.Routing; + using Microsoft.AspNetCore.Routing.Patterns; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Metadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.FileProviders; + using Microsoft.Extensions.Primitives; + + using MetadataPopulator = System.Func; + using RequestDelegateFactoryFunc = System.Func; + + file static class GeneratedRouteBuilderExtensionsCore + { + + private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() + { + [(@"TestMapActions.cs", 15)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 15)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (System.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); + } + + Task RequestHandler(HttpContext httpContext) + { + // Endpoint Parameter: p1 (Type = string, IsOptional = False, Source = Query) + var p1_raw = httpContext.Request.Query["p1"]; + if (StringValues.IsNullOrEmpty(p1_raw)) + { + httpContext.Response.StatusCode = 400; + return Task.CompletedTask; + } + var p1_local = p1_raw.ToString(); + + // Endpoint Parameter: p2 (Type = string, IsOptional = False, Source = Query) + var p2_raw = httpContext.Request.Query["p2"]; + if (StringValues.IsNullOrEmpty(p2_raw)) + { + httpContext.Response.StatusCode = 400; + return Task.CompletedTask; + } + var p2_local = p2_raw.ToString(); + + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(p1_local, p2_local); + return httpContext.Response.WriteAsync(result); + } + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext)); + 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); + } + } + } +} 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 7f9a7f9be477..1fb1a55b7354 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 @@ -112,6 +112,7 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { + httpContext.Response.ContentType ??= "text/plain"; var result = handler(); return httpContext.Response.WriteAsync(result); 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 new file mode 100644 index 000000000000..43ad0d849bf5 --- /dev/null +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn.generated.txt @@ -0,0 +1,181 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable + +namespace Microsoft.AspNetCore.Builder +{ + %GENERATEDCODEATTRIBUTE% + internal class SourceKey + { + public string Path { get; init; } + public int Line { get; init; } + + public SourceKey(string path, int line) + { + Path = path; + Line = line; + } + } + + // This class needs to be internal so that the compiled application + // has access to the strongly-typed endpoint definitions that are + // generated by the compiler so that they will be favored by + // overload resolution and opt the runtime in to the code generated + // implementation produced here. + %GENERATEDCODEATTRIBUTE% + internal static class GenerateRouteBuilderEndpoints + { + private static readonly string[] GetVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Get }; + private static readonly string[] PostVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Post }; + private static readonly string[] PutVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Put }; + private static readonly string[] DeleteVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Delete }; + private static readonly string[] PatchVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Patch }; + + internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet( + this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, + [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern, + global::System.Func handler, + [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "", + [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0) + { + return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore( + endpoints, + pattern, + handler, + GetVerb, + filePath, + lineNumber); + } + + } +} + +namespace Microsoft.AspNetCore.Http.Generated +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using System.IO; + using Microsoft.AspNetCore.Routing; + using Microsoft.AspNetCore.Routing.Patterns; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Metadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.FileProviders; + using Microsoft.Extensions.Primitives; + + using MetadataPopulator = System.Func; + using RequestDelegateFactoryFunc = System.Func; + + file static class GeneratedRouteBuilderExtensionsCore + { + + private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() + { + [(@"TestMapActions.cs", 15)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 15)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (System.Func)del; + EndpointFilterDelegate? filteredInvocation = null; + + if (options?.EndpointBuilder?.FilterFactories.Count > 0) + { + filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic => + { + if (ic.HttpContext.Response.StatusCode == 400) + { + return ValueTask.FromResult(Results.Empty); + } + return ValueTask.FromResult(handler(ic.GetArgument(0))); + }, + options.EndpointBuilder, + handler.Method); + } + + Task RequestHandler(HttpContext httpContext) + { + // Endpoint Parameter: p (Type = string?, IsOptional = True, Source = Query) + var p_raw = httpContext.Request.Query["p"]; + var p_local = p_raw.Count > 0 ? p_raw.ToString() : null; + + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(p_local); + return httpContext.Response.WriteAsync(result); + } + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext)); + 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); + } + } + } +} diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn.generated.txt new file mode 100644 index 000000000000..43ad0d849bf5 --- /dev/null +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn.generated.txt @@ -0,0 +1,181 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable + +namespace Microsoft.AspNetCore.Builder +{ + %GENERATEDCODEATTRIBUTE% + internal class SourceKey + { + public string Path { get; init; } + public int Line { get; init; } + + public SourceKey(string path, int line) + { + Path = path; + Line = line; + } + } + + // This class needs to be internal so that the compiled application + // has access to the strongly-typed endpoint definitions that are + // generated by the compiler so that they will be favored by + // overload resolution and opt the runtime in to the code generated + // implementation produced here. + %GENERATEDCODEATTRIBUTE% + internal static class GenerateRouteBuilderEndpoints + { + private static readonly string[] GetVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Get }; + private static readonly string[] PostVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Post }; + private static readonly string[] PutVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Put }; + private static readonly string[] DeleteVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Delete }; + private static readonly string[] PatchVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Patch }; + + internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet( + this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, + [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern, + global::System.Func handler, + [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "", + [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0) + { + return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore( + endpoints, + pattern, + handler, + GetVerb, + filePath, + lineNumber); + } + + } +} + +namespace Microsoft.AspNetCore.Http.Generated +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using System.IO; + using Microsoft.AspNetCore.Routing; + using Microsoft.AspNetCore.Routing.Patterns; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Metadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.FileProviders; + using Microsoft.Extensions.Primitives; + + using MetadataPopulator = System.Func; + using RequestDelegateFactoryFunc = System.Func; + + file static class GeneratedRouteBuilderExtensionsCore + { + + private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() + { + [(@"TestMapActions.cs", 15)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 15)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (System.Func)del; + EndpointFilterDelegate? filteredInvocation = null; + + if (options?.EndpointBuilder?.FilterFactories.Count > 0) + { + filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic => + { + if (ic.HttpContext.Response.StatusCode == 400) + { + return ValueTask.FromResult(Results.Empty); + } + return ValueTask.FromResult(handler(ic.GetArgument(0))); + }, + options.EndpointBuilder, + handler.Method); + } + + Task RequestHandler(HttpContext httpContext) + { + // Endpoint Parameter: p (Type = string?, IsOptional = True, Source = Query) + var p_raw = httpContext.Request.Query["p"]; + var p_local = p_raw.Count > 0 ? p_raw.ToString() : null; + + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(p_local); + return httpContext.Response.WriteAsync(result); + } + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext)); + 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); + } + } + } +} diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn.generated.txt new file mode 100644 index 000000000000..43ad0d849bf5 --- /dev/null +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn.generated.txt @@ -0,0 +1,181 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable + +namespace Microsoft.AspNetCore.Builder +{ + %GENERATEDCODEATTRIBUTE% + internal class SourceKey + { + public string Path { get; init; } + public int Line { get; init; } + + public SourceKey(string path, int line) + { + Path = path; + Line = line; + } + } + + // This class needs to be internal so that the compiled application + // has access to the strongly-typed endpoint definitions that are + // generated by the compiler so that they will be favored by + // overload resolution and opt the runtime in to the code generated + // implementation produced here. + %GENERATEDCODEATTRIBUTE% + internal static class GenerateRouteBuilderEndpoints + { + private static readonly string[] GetVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Get }; + private static readonly string[] PostVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Post }; + private static readonly string[] PutVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Put }; + private static readonly string[] DeleteVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Delete }; + private static readonly string[] PatchVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Patch }; + + internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet( + this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, + [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern, + global::System.Func handler, + [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "", + [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0) + { + return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore( + endpoints, + pattern, + handler, + GetVerb, + filePath, + lineNumber); + } + + } +} + +namespace Microsoft.AspNetCore.Http.Generated +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using System.IO; + using Microsoft.AspNetCore.Routing; + using Microsoft.AspNetCore.Routing.Patterns; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Metadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.FileProviders; + using Microsoft.Extensions.Primitives; + + using MetadataPopulator = System.Func; + using RequestDelegateFactoryFunc = System.Func; + + file static class GeneratedRouteBuilderExtensionsCore + { + + private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() + { + [(@"TestMapActions.cs", 15)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 15)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (System.Func)del; + EndpointFilterDelegate? filteredInvocation = null; + + if (options?.EndpointBuilder?.FilterFactories.Count > 0) + { + filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic => + { + if (ic.HttpContext.Response.StatusCode == 400) + { + return ValueTask.FromResult(Results.Empty); + } + return ValueTask.FromResult(handler(ic.GetArgument(0))); + }, + options.EndpointBuilder, + handler.Method); + } + + Task RequestHandler(HttpContext httpContext) + { + // Endpoint Parameter: p (Type = string?, IsOptional = True, Source = Query) + var p_raw = httpContext.Request.Query["p"]; + var p_local = p_raw.Count > 0 ? p_raw.ToString() : null; + + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(p_local); + return httpContext.Response.WriteAsync(result); + } + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext)); + 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); + } + } + } +} diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleStringParam_StringReturn.generated.txt b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleStringParam_StringReturn.generated.txt new file mode 100644 index 000000000000..5c141fbe34e3 --- /dev/null +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/Baselines/MapAction_SingleStringParam_StringReturn.generated.txt @@ -0,0 +1,186 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ +#nullable enable + +namespace Microsoft.AspNetCore.Builder +{ + %GENERATEDCODEATTRIBUTE% + internal class SourceKey + { + public string Path { get; init; } + public int Line { get; init; } + + public SourceKey(string path, int line) + { + Path = path; + Line = line; + } + } + + // This class needs to be internal so that the compiled application + // has access to the strongly-typed endpoint definitions that are + // generated by the compiler so that they will be favored by + // overload resolution and opt the runtime in to the code generated + // implementation produced here. + %GENERATEDCODEATTRIBUTE% + internal static class GenerateRouteBuilderEndpoints + { + private static readonly string[] GetVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Get }; + private static readonly string[] PostVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Post }; + private static readonly string[] PutVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Put }; + private static readonly string[] DeleteVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Delete }; + private static readonly string[] PatchVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Patch }; + + internal static global::Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet( + this global::Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, + [global::System.Diagnostics.CodeAnalysis.StringSyntax("Route")] string pattern, + global::System.Func handler, + [global::System.Runtime.CompilerServices.CallerFilePath] string filePath = "", + [global::System.Runtime.CompilerServices.CallerLineNumber]int lineNumber = 0) + { + return global::Microsoft.AspNetCore.Http.Generated.GeneratedRouteBuilderExtensionsCore.MapCore( + endpoints, + pattern, + handler, + GetVerb, + filePath, + lineNumber); + } + + } +} + +namespace Microsoft.AspNetCore.Http.Generated +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Collections.ObjectModel; + using System.Diagnostics; + using System.Linq; + using System.Reflection; + using System.Threading.Tasks; + using System.IO; + using Microsoft.AspNetCore.Routing; + using Microsoft.AspNetCore.Routing.Patterns; + using Microsoft.AspNetCore.Builder; + using Microsoft.AspNetCore.Http; + using Microsoft.AspNetCore.Http.Metadata; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.FileProviders; + using Microsoft.Extensions.Primitives; + + using MetadataPopulator = System.Func; + using RequestDelegateFactoryFunc = System.Func; + + file static class GeneratedRouteBuilderExtensionsCore + { + + private static readonly Dictionary<(string, int), (MetadataPopulator, RequestDelegateFactoryFunc)> map = new() + { + [(@"TestMapActions.cs", 15)] = ( + (methodInfo, options) => + { + Debug.Assert(options?.EndpointBuilder != null, "EndpointBuilder not found."); + options.EndpointBuilder.Metadata.Add(new SourceKey(@"TestMapActions.cs", 15)); + return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() }; + }, + (del, options, inferredMetadataResult) => + { + var handler = (System.Func)del; + EndpointFilterDelegate? filteredInvocation = null; + + if (options?.EndpointBuilder?.FilterFactories.Count > 0) + { + filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic => + { + if (ic.HttpContext.Response.StatusCode == 400) + { + return ValueTask.FromResult(Results.Empty); + } + return ValueTask.FromResult(handler(ic.GetArgument(0))); + }, + options.EndpointBuilder, + handler.Method); + } + + Task RequestHandler(HttpContext httpContext) + { + // Endpoint Parameter: p (Type = string, IsOptional = False, Source = Query) + var p_raw = httpContext.Request.Query["p"]; + if (StringValues.IsNullOrEmpty(p_raw)) + { + httpContext.Response.StatusCode = 400; + return Task.CompletedTask; + } + var p_local = p_raw.ToString(); + + httpContext.Response.ContentType ??= "text/plain"; + var result = handler(p_local); + return httpContext.Response.WriteAsync(result); + } + async Task RequestHandlerFiltered(HttpContext httpContext) + { + var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext)); + 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); + } + } + } +} 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 35f739c6d74d..9c7c47871b50 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 @@ -142,6 +142,7 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { + httpContext.Response.ContentType ??= "text/plain"; var result = handler(); return httpContext.Response.WriteAsync(result); @@ -184,6 +185,7 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { + httpContext.Response.ContentType ??= "text/plain"; var result = handler(); return httpContext.Response.WriteAsync(result); @@ -226,6 +228,7 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandler(HttpContext httpContext) { + httpContext.Response.ContentType ??= "application/json"; var result = await handler(); await httpContext.Response.WriteAsync(result); @@ -268,6 +271,7 @@ namespace Microsoft.AspNetCore.Http.Generated async Task RequestHandler(HttpContext httpContext) { + httpContext.Response.ContentType ??= "application/json"; var result = await handler(); await httpContext.Response.WriteAsync(result); 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 1b85f761bb5d..58989e50724e 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 @@ -10,7 +10,7 @@ namespace Microsoft.AspNetCore.Builder { - [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.Http.Generators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + %GENERATEDCODEATTRIBUTE% internal class SourceKey { public string Path { get; init; } @@ -28,7 +28,7 @@ namespace Microsoft.AspNetCore.Builder // 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. - [System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.AspNetCore.Http.Generators, Version=42.42.42.42, Culture=neutral, PublicKeyToken=adb9793829ddae60", "42.42.42.42")] + %GENERATEDCODEATTRIBUTE% internal static class GenerateRouteBuilderEndpoints { private static readonly string[] GetVerb = new[] { global::Microsoft.AspNetCore.Http.HttpMethods.Get }; @@ -142,13 +142,14 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { + var req_local = httpContext.Request; httpContext.Response.ContentType ??= "text/plain"; - var result = handler(httpContext.Request); + var result = handler(req_local); return httpContext.Response.WriteAsync(result); } async Task RequestHandlerFiltered(HttpContext httpContext) { - var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext, httpContext.Request)); + var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -184,13 +185,14 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { + var res_local = httpContext.Response; httpContext.Response.ContentType ??= "text/plain"; - var result = handler(httpContext.Response); + var result = handler(res_local); return httpContext.Response.WriteAsync(result); } async Task RequestHandlerFiltered(HttpContext httpContext) { - var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext, httpContext.Response)); + var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } @@ -226,13 +228,15 @@ namespace Microsoft.AspNetCore.Http.Generated Task RequestHandler(HttpContext httpContext) { + var req_local = httpContext.Request; + var res_local = httpContext.Response; httpContext.Response.ContentType ??= "text/plain"; - var result = handler(httpContext.Request, httpContext.Response); + var result = handler(req_local, res_local); return httpContext.Response.WriteAsync(result); } async Task RequestHandlerFiltered(HttpContext httpContext) { - var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext, httpContext.Request, httpContext.Response)); + var result = await filteredInvocation(new DefaultEndpointFilterInvocationContext(httpContext)); await GeneratedRouteBuilderExtensionsCore.ExecuteObjectResult(result, httpContext); } diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs index f3143dd790ef..4b078282f3cb 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTestBase.cs @@ -237,14 +237,15 @@ private static Task CreateCompilationAsync(string sources) internal async Task VerifyAgainstBaselineUsingFile(Compilation compilation, [CallerMemberName] string callerName = "") { var baselineFilePath = Path.Combine("RequestDelegateGenerator", "Baselines", $"{callerName}.generated.txt"); - var generatedCode = compilation.SyntaxTrees.Last(); + var generatedSyntaxTree = compilation.SyntaxTrees.Last(); + var generatedCode = generatedSyntaxTree.GetText(); var baseline = await File.ReadAllTextAsync(baselineFilePath); var expectedLines = baseline .TrimEnd() // Trim newlines added by autoformat .Replace("%GENERATEDCODEATTRIBUTE%", RequestDelegateGeneratorSources.GeneratedCodeAttribute) .Split(Environment.NewLine); - Assert.True(CompareLines(expectedLines, generatedCode.GetText(), out var errorMessage), errorMessage); + Assert.True(CompareLines(expectedLines, generatedCode, out var errorMessage), errorMessage); } private bool CompareLines(string[] expectedLines, SourceText sourceText, out string message) diff --git a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs index 1a38c9c24f1b..ced3df263e8c 100644 --- a/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs +++ b/src/Http/Http.Extensions/test/RequestDelegateGenerator/RequestDelegateGeneratorTests.cs @@ -31,6 +31,122 @@ public async Task MapAction_NoParam_StringReturn(string source, string httpMetho await VerifyResponseBodyAsync(httpContext, expectedBody); } + [Fact] + public async Task MapAction_SingleStringParam_StringReturn() + { + var (results, compilation) = await RunGeneratorAsync(""" +app.MapGet("/hello", ([FromQuery]string p) => p); +"""); + + var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); + var endpoint = GetEndpointFromCompilation(compilation); + + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + var p = Assert.Single(endpointModel.Parameters); + Assert.Equal(EndpointParameterSource.Query, p.Source); + Assert.Equal("p", p.Name); + + var httpContext = CreateHttpContext(); + httpContext.Request.QueryString = new QueryString("?p=Hello%20world!"); + + await endpoint.RequestDelegate(httpContext); + await VerifyResponseBodyAsync(httpContext, "Hello world!"); + await VerifyAgainstBaselineUsingFile(compilation); + } + + [Fact] + public async Task MapAction_SingleNullableStringParam_WithQueryStringValueProvided_StringReturn() + { + var (results, compilation) = await RunGeneratorAsync(""" +app.MapGet("/hello", ([FromQuery]string? p) => p ?? "Error!"); +"""); + + var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); + var endpoint = GetEndpointFromCompilation(compilation); + + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + var p = Assert.Single(endpointModel.Parameters); + Assert.Equal(EndpointParameterSource.Query, p.Source); + Assert.Equal("p", p.Name); + + var httpContext = CreateHttpContext(); + httpContext.Request.QueryString = new QueryString("?p=Hello%20world!"); + + await endpoint.RequestDelegate(httpContext); + await VerifyResponseBodyAsync(httpContext, "Hello world!"); + await VerifyAgainstBaselineUsingFile(compilation); + } + + [Fact] + public async Task MapAction_SingleNullableStringParam_WithoutQueryStringValueProvided_StringReturn() + { + var (results, compilation) = await RunGeneratorAsync(""" +app.MapGet("/hello", ([FromQuery]string? p) => p ?? "Was null!"); +"""); + + var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); + var endpoint = GetEndpointFromCompilation(compilation); + + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + var p = Assert.Single(endpointModel.Parameters); + Assert.Equal(EndpointParameterSource.Query, p.Source); + Assert.Equal("p", p.Name); + + var httpContext = CreateHttpContext(); + + await endpoint.RequestDelegate(httpContext); + await VerifyResponseBodyAsync(httpContext, "Was null!"); + await VerifyAgainstBaselineUsingFile(compilation); + } + + [Fact] + public async Task MapAction_SingleNullableStringParam_WithEmptyQueryStringValueProvided_StringReturn() + { + var (results, compilation) = await RunGeneratorAsync(""" +app.MapGet("/hello", ([FromQuery]string? p) => p == string.Empty ? "No value, but not null!" : "Was null!"); +"""); + + var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); + var endpoint = GetEndpointFromCompilation(compilation); + + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + var p = Assert.Single(endpointModel.Parameters); + Assert.Equal(EndpointParameterSource.Query, p.Source); + Assert.Equal("p", p.Name); + + var httpContext = CreateHttpContext(); + httpContext.Request.QueryString = new QueryString("?p="); + + await endpoint.RequestDelegate(httpContext); + await VerifyResponseBodyAsync(httpContext, "No value, but not null!"); + await VerifyAgainstBaselineUsingFile(compilation); + } + + [Fact] + public async Task MapAction_MultipleStringParam_StringReturn() + { + var (results, compilation) = await RunGeneratorAsync(""" +app.MapGet("/hello", ([FromQuery]string p1, [FromQuery]string p2) => $"{p1} {p2}"); +"""); + + var endpointModel = GetStaticEndpoint(results, GeneratorSteps.EndpointModelStep); + var endpoint = GetEndpointFromCompilation(compilation); + + Assert.Equal("/hello", endpointModel.RoutePattern); + Assert.Equal("MapGet", endpointModel.HttpMethod); + + var httpContext = CreateHttpContext(); + httpContext.Request.QueryString = new QueryString("?p1=Hello&p2=world!"); + + await endpoint.RequestDelegate(httpContext); + await VerifyResponseBodyAsync(httpContext, "Hello world!"); + await VerifyAgainstBaselineUsingFile(compilation); + } + [Theory] [InlineData("HttpContext")] [InlineData("HttpRequest")] @@ -87,6 +203,7 @@ public async Task MapAction_MultipleSpecialTypeParam_StringReturn() var httpContext = CreateHttpContext(); await endpoint.RequestDelegate(httpContext); await VerifyResponseBodyAsync(httpContext, "Hello world!"); + await VerifyAgainstBaselineUsingFile(compilation); } [Fact] diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/SymbolExtensions.cs b/src/Shared/RoslynUtils/SymbolExtensions.cs similarity index 100% rename from src/Framework/AspNetCoreAnalyzers/src/Analyzers/RouteEmbeddedLanguage/Infrastructure/SymbolExtensions.cs rename to src/Shared/RoslynUtils/SymbolExtensions.cs diff --git a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/WellKnownTypeData.cs b/src/Shared/RoslynUtils/WellKnownTypeData.cs similarity index 98% rename from src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/WellKnownTypeData.cs rename to src/Shared/RoslynUtils/WellKnownTypeData.cs index d22c74e2d029..ba1732b5e45b 100644 --- a/src/Framework/AspNetCoreAnalyzers/src/Analyzers/Infrastructure/WellKnownTypeData.cs +++ b/src/Shared/RoslynUtils/WellKnownTypeData.cs @@ -48,7 +48,9 @@ public enum WellKnownType Microsoft_AspNetCore_Mvc_IActionResult, Microsoft_AspNetCore_Mvc_Infrastructure_IConvertToActionResult, Microsoft_AspNetCore_Http_RequestDelegate, + System_Threading_Tasks_Task, System_Threading_Tasks_Task_T, + System_Threading_Tasks_ValueTask, System_Threading_Tasks_ValueTask_T, System_Reflection_ParameterInfo, Microsoft_AspNetCore_Http_IBindableFromHttpContext_T, @@ -146,7 +148,9 @@ public enum WellKnownType "Microsoft.AspNetCore.Mvc.IActionResult", "Microsoft.AspNetCore.Mvc.Infrastructure.IConvertToActionResult", "Microsoft.AspNetCore.Http.RequestDelegate", + "System.Threading.Tasks.Task", "System.Threading.Tasks.Task`1", + "System.Threading.Tasks.ValueTask", "System.Threading.Tasks.ValueTask`1", "System.Reflection.ParameterInfo", "Microsoft.AspNetCore.Http.IBindableFromHttpContext`1",