|
// 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.Immutable;
using System.Globalization;
using System.IO;
using System.Linq;
using Microsoft.AspNetCore.Analyzers.Infrastructure;
using Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerModel;
using Microsoft.AspNetCore.Http.RequestDelegateGenerator.StaticRouteHandlerModel.Emitters;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
namespace Microsoft.AspNetCore.Http.RequestDelegateGenerator;
public sealed partial class RequestDelegateGenerator : IIncrementalGenerator
{
internal static string EmitInterceptorDefinition((Endpoint Source, int Index, ImmutableArray<InterceptableLocation> Elements) endpointWithLocations)
{
var endpoint = endpointWithLocations.Source;
using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
using var codeWriter = new CodeWriter(stringWriter, baseIndent: 2);
foreach (var location in endpointWithLocations.Elements)
{
#pragma warning disable RSEXPERIMENTAL002 // Experimental interceptable location API
codeWriter.WriteLine(location.GetInterceptsLocationAttributeSyntax());
#pragma warning restore RSEXPERIMENTAL002
}
codeWriter.WriteLine($"internal static RouteHandlerBuilder {endpoint.HttpMethod}{endpointWithLocations.Index}(");
codeWriter.Indent++;
codeWriter.WriteLine("this IEndpointRouteBuilder endpoints,");
// MapFallback overloads that only take a delegate do not need a pattern argument
if (endpoint.HttpMethod != "MapFallback" || endpoint.Operation.Arguments.Length != 2)
{
codeWriter.WriteLine(@"[StringSyntax(""Route"")] string pattern,");
}
// MapMethods overloads define an additional `httpMethods` parameter
if (endpoint.HttpMethod == "MapMethods")
{
codeWriter.WriteLine("IEnumerable<string> httpMethods,");
}
codeWriter.WriteLine("Delegate handler)");
codeWriter.Indent--;
codeWriter.StartBlock();
codeWriter.WriteLine("MetadataPopulator populateMetadata = (methodInfo, options) =>");
codeWriter.StartBlock();
codeWriter.WriteLine(@"Debug.Assert(options != null, ""RequestDelegateFactoryOptions not found."");");
codeWriter.WriteLine(@"Debug.Assert(options.EndpointBuilder != null, ""EndpointBuilder not found."");");
codeWriter.WriteLine($"options.EndpointBuilder.Metadata.Add(new {RequestDelegateGeneratorSources.GeneratedCodeConstructor});");
endpoint.EmitEndpointMetadataPopulation(codeWriter);
codeWriter.WriteLine("return new RequestDelegateMetadataResult { EndpointMetadata = options.EndpointBuilder.Metadata.AsReadOnly() };");
codeWriter.EndBlockWithSemicolon();
codeWriter.WriteLine("RequestDelegateFactoryFunc createRequestDelegate = (del, options, inferredMetadataResult) =>");
codeWriter.StartBlock();
codeWriter.WriteLine(@"Debug.Assert(options != null, ""RequestDelegateFactoryOptions not found."");");
codeWriter.WriteLine(@"Debug.Assert(options.EndpointBuilder != null, ""EndpointBuilder not found."");");
codeWriter.WriteLine(@"Debug.Assert(options.EndpointBuilder.ApplicationServices != null, ""ApplicationServices not found."");");
codeWriter.WriteLine(@"Debug.Assert(options.EndpointBuilder.FilterFactories != null, ""FilterFactories not found."");");
codeWriter.WriteLine($"var handler = Cast(del, {endpoint.EmitHandlerDelegateType()} => throw null!);");
codeWriter.WriteLine("EndpointFilterDelegate? filteredInvocation = null;");
codeWriter.WriteLine("var serviceProvider = options.ServiceProvider ?? options.EndpointBuilder.ApplicationServices;");
endpoint.EmitLoggingPreamble(codeWriter);
endpoint.EmitJsonPreparation(codeWriter);
endpoint.EmitRouteOrQueryResolver(codeWriter);
endpoint.EmitJsonBodyOrServiceResolver(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();
codeWriter.WriteLine(endpoint.Response?.IsAwaitable == true
? "filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(async ic =>"
: "filteredInvocation = GeneratedRouteBuilderExtensionsCore.BuildFilterDelegate(ic =>");
codeWriter.StartBlock();
codeWriter.WriteLine("if (ic.HttpContext.Response.StatusCode == 400)");
codeWriter.StartBlock();
codeWriter.WriteLine(endpoint.Response?.IsAwaitable == true
? "return (object?)Results.Empty;"
: "return ValueTask.FromResult<object?>(Results.Empty);");
codeWriter.EndBlock();
endpoint.EmitFilteredInvocation(codeWriter);
codeWriter.EndBlockWithComma();
codeWriter.WriteLine("options.EndpointBuilder,");
codeWriter.WriteLine("handler.Method);");
codeWriter.EndBlock();
codeWriter.WriteLineNoTabs(string.Empty);
endpoint.EmitRequestHandler(codeWriter);
codeWriter.WriteLineNoTabs(string.Empty);
endpoint.EmitFilteredRequestHandler(codeWriter);
codeWriter.WriteLineNoTabs(string.Empty);
codeWriter.WriteLine("RequestDelegate targetDelegate = filteredInvocation is null ? RequestHandler : RequestHandlerFiltered;");
codeWriter.WriteLine("var metadata = inferredMetadataResult?.EndpointMetadata ?? ReadOnlyCollection<object>.Empty;");
codeWriter.WriteLine("return new RequestDelegateResult(targetDelegate, metadata);");
codeWriter.EndBlockWithSemicolon();
codeWriter.WriteLine($"var castHandler = Cast(handler, {endpoint.EmitHandlerDelegateType()} => throw null!);");
codeWriter.WriteLine("return MapCore(");
codeWriter.Indent++;
codeWriter.WriteLine("endpoints,");
// For `MapFallback` overloads that only take a delegate, provide the assumed default
// Otherwise, pass the pattern provided from the MapX invocation
if (endpoint.HttpMethod != "MapFallback" && endpoint.Operation.Arguments.Length != 2)
{
codeWriter.WriteLine("pattern,");
}
else
{
codeWriter.WriteLine($"{SymbolDisplay.FormatLiteral("{*path:nonfile}", true)},");
}
codeWriter.WriteLine("handler,");
codeWriter.WriteLine($"{endpoint.EmitVerb()},");
codeWriter.WriteLine("populateMetadata,");
codeWriter.WriteLine("createRequestDelegate,");
codeWriter.WriteLine("castHandler.Method);");
codeWriter.Indent--;
codeWriter.EndBlock();
return stringWriter.ToString();
}
internal static ImmutableHashSet<string> EmitHttpVerbs(ImmutableArray<Endpoint> endpoints)
{
return endpoints
.Distinct(EndpointHttpMethodComparer.Instance)
.Select(endpoint => endpoint.EmitterContext.HttpMethod!)
.Where(verb => verb is not null)
.ToImmutableHashSet();
}
internal static string EmitEndpointHelpers(ImmutableArray<Endpoint> endpoints)
{
var hasJsonBodyOrService = endpoints.Any(endpoint => endpoint.EmitterContext.HasJsonBodyOrService);
var hasJsonBodyOrQuery = endpoints.Any(endpoint => endpoint.EmitterContext.HasJsonBodyOrQuery);
var hasJsonBody = endpoints.Any(endpoint => endpoint.EmitterContext.HasJsonBody);
var hasFormBody = endpoints.Any(endpoint => endpoint.EmitterContext.HasFormBody);
var hasRouteOrQuery = endpoints.Any(endpoint => endpoint.EmitterContext.HasRouteOrQuery);
var hasBindAsync = endpoints.Any(endpoint => endpoint.EmitterContext.HasBindAsync);
var hasParsable = endpoints.Any(endpoint => endpoint.EmitterContext.HasParsable);
var hasEndpointMetadataProvider = endpoints.Any(endpoint => endpoint.EmitterContext.HasEndpointMetadataProvider);
var hasEndpointParameterMetadataProvider = endpoints.Any(endpoint => endpoint.EmitterContext.HasEndpointParameterMetadataProvider);
var hasIResult = endpoints.Any(endpoint => endpoint.Response?.IsIResult == true);
using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
using var codeWriter = new CodeWriter(stringWriter, baseIndent: 0);
if (hasRouteOrQuery)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.ResolveFromRouteOrQueryMethod);
}
if (hasJsonBody || hasJsonBodyOrService || hasJsonBodyOrQuery)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.TryResolveBodyAsyncMethod);
}
if (hasFormBody)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.TryResolveFormAsyncMethod);
}
if (hasBindAsync)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.BindAsyncMethod);
}
if (hasJsonBodyOrService)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.ResolveJsonBodyOrServiceMethod);
}
if (hasParsable)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.TryParseExplicitMethod);
}
if (hasIResult)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.ExecuteAsyncExplicitMethod);
}
if (hasEndpointMetadataProvider)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.PopulateEndpointMetadataMethod);
}
if (hasEndpointParameterMetadataProvider)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.PopulateEndpointParameterMetadataMethod);
}
return stringWriter.ToString();
}
internal static string EmitHelperTypes(ImmutableArray<Endpoint> endpoints)
{
var hasFormBody = endpoints.Any(endpoint => endpoint.EmitterContext.HasFormBody);
var hasJsonBody = endpoints.Any(endpoint => endpoint.EmitterContext.HasJsonBody || endpoint.EmitterContext.HasJsonBodyOrService || endpoint.EmitterContext.HasJsonBodyOrQuery);
var hasResponseMetadata = endpoints.Any(endpoint => endpoint.EmitterContext.HasResponseMetadata);
var requiresPropertyAsParameterInfo = endpoints.Any(endpoint => endpoint.EmitterContext.RequiresPropertyAsParameterInfo);
var requiresParameterBindingMetadataClass = endpoints.Any(endpoint => endpoint.EmitterContext.RequiresParameterBindingMetadataClass);
using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
using var codeWriter = new CodeWriter(stringWriter, baseIndent: 0);
if (hasFormBody)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.AntiforgeryMetadataClass);
}
if (hasJsonBody || hasResponseMetadata)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.DisableCookieRedirectMetadataClass);
}
if (hasFormBody || hasJsonBody || hasResponseMetadata)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.ContentTypeConstantsType);
}
if (requiresPropertyAsParameterInfo)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.PropertyAsParameterInfoClass);
}
if (requiresParameterBindingMetadataClass)
{
codeWriter.WriteLine(RequestDelegateGeneratorSources.ParameterBindingMetadataClass);
}
return stringWriter.ToString();
}
internal static void Emit(
SourceProductionContext context,
ImmutableArray<string> endpointsCode,
string helperMethods,
ImmutableHashSet<string> httpVerbs,
string helperTypes)
{
if (endpointsCode.IsDefaultOrEmpty)
{
return;
}
using var stringWriter = new StringWriter(CultureInfo.InvariantCulture);
using var codeWriter = new CodeWriter(stringWriter, baseIndent: 0);
foreach (var endpoint in endpointsCode)
{
codeWriter.WriteLine(endpoint);
}
var code = RequestDelegateGeneratorSources.GetGeneratedRouteBuilderExtensionsSource(
endpoints: stringWriter.ToString(),
helperMethods: helperMethods ?? string.Empty,
helperTypes: helperTypes ?? string.Empty,
verbs: httpVerbs);
context.AddSource("GeneratedRouteBuilderExtensions.g.cs", code);
}
}
|