sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs (185 lines of code) (raw):

// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the MIT License. See License.txt in the project root for license information. using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; namespace Microsoft.Azure.Functions.Worker.Sdk.Generators { public partial class FunctionExecutorGenerator { internal static class Emitter { private const string WorkerCoreAssemblyName = "Microsoft.Azure.Functions.Worker.Core"; internal static string Emit(GeneratorExecutionContext context, IEnumerable<ExecutableFunction> executableFunctions, bool includeAutoRegistrationCode) { var functions = executableFunctions.ToList(); var defaultExecutorNeeded = functions.Any(f => f.Visibility == FunctionMethodVisibility.PublicButContainingTypeNotVisible); string result = $$""" // <auto-generated/> using System; using System.Threading.Tasks; using System.Collections.Generic; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Azure.Functions.Worker; using Microsoft.Azure.Functions.Worker.Context.Features; using Microsoft.Azure.Functions.Worker.Invocation; namespace {{FunctionsUtil.GetNamespaceForGeneratedCode(context)}} { [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] {{Constants.GeneratedCodeAttribute}} internal class DirectFunctionExecutor : global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor { private readonly global::Microsoft.Azure.Functions.Worker.IFunctionActivator _functionActivator;{{(defaultExecutorNeeded ? $"{Constants.NewLine} private Lazy<global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor> _defaultExecutor;" : string.Empty)}} {{GetTypesDictionary(functions)}} public DirectFunctionExecutor(global::Microsoft.Azure.Functions.Worker.IFunctionActivator functionActivator) { _functionActivator = functionActivator ?? throw new global::System.ArgumentNullException(nameof(functionActivator)); } /// <inheritdoc/> public async global::System.Threading.Tasks.ValueTask ExecuteAsync(global::Microsoft.Azure.Functions.Worker.FunctionContext context) { {{GetMethodBody(functions, defaultExecutorNeeded)}} }{{(defaultExecutorNeeded ? $"{Constants.NewLine}{EmitCreateDefaultExecutorMethod(context)}" : string.Empty)}} } /// <summary> /// Extension methods to enable registration of the custom <see cref="IFunctionExecutor"/> implementation generated for the current worker. /// </summary> {{Constants.GeneratedCodeAttribute}} public static class FunctionExecutorHostBuilderExtensions { ///<summary> /// Configures an optimized function executor to the invocation pipeline. ///</summary> public static IHostBuilder ConfigureGeneratedFunctionExecutor(this IHostBuilder builder) { return builder.ConfigureServices(s => { s.AddSingleton<global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor, DirectFunctionExecutor>(); }); } }{{GetAutoConfigureStartupClass(includeAutoRegistrationCode)}} } """; return result; } private static string EmitCreateDefaultExecutorMethod(GeneratorExecutionContext context) { var workerCoreAssembly = context.Compilation.SourceModule.ReferencedAssemblySymbols.Single(a => a.Name == WorkerCoreAssemblyName); var assemblyIdentity = workerCoreAssembly.Identity; return $$""" private global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor CreateDefaultExecutorInstance(global::Microsoft.Azure.Functions.Worker.FunctionContext context) { var defaultExecutorFullName = "Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor, {{assemblyIdentity}}"; var defaultExecutorType = global::System.Type.GetType(defaultExecutorFullName); return ActivatorUtilities.CreateInstance(context.InstanceServices, defaultExecutorType) as global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor; } """; } private static string GetTypesDictionary(IEnumerable<ExecutableFunction> functions) { // Build a dictionary of type names and its full qualified names (including assembly identity) var typesDict = functions .Where(f => !f.IsStatic) .GroupBy(f => f.ParentFunctionClassName) .ToDictionary(k => k.First().ParentFunctionClassName, v => v.First().AssemblyIdentity); if (typesDict.Count == 0) { return ""; } return $$""" private readonly Dictionary<string, Type> types = new Dictionary<string, Type>() { {{string.Join($",{Constants.NewLine} ", typesDict.Select(c => $$""" { "{{c.Key}}", Type.GetType("{{c.Key}}, {{c.Value}}") }"""))}} }; """; } private static string GetAutoConfigureStartupClass(bool includeAutoRegistrationCode) { if (includeAutoRegistrationCode) { string result = $$""" /// <summary> /// Auto startup class to register the custom <see cref="IFunctionExecutor"/> implementation generated for the current worker. /// </summary> [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)] {{Constants.GeneratedCodeAttribute}} public class FunctionExecutorAutoStartup : global::Microsoft.Azure.Functions.Worker.IAutoConfigureStartup { /// <summary> /// Configures the <see cref="IHostBuilder"/> to use the custom <see cref="IFunctionExecutor"/> implementation generated for the current worker. /// </summary> /// <param name="hostBuilder">The <see cref="IHostBuilder"/> instance to use for service registration.</param> public void Configure(IHostBuilder hostBuilder) { hostBuilder.ConfigureGeneratedFunctionExecutor(); } } """; return result; } return ""; } private static string GetMethodBody(IEnumerable<ExecutableFunction> functions, bool anyDefaultExecutor) { var sb = new StringBuilder(); sb.Append( $""" var inputBindingFeature = context.Features.Get<global::Microsoft.Azure.Functions.Worker.Context.Features.IFunctionInputBindingFeature>(); var inputBindingResult = await inputBindingFeature.BindFunctionInputAsync(context); var inputArguments = inputBindingResult.Values; {(anyDefaultExecutor ? $" _defaultExecutor = new Lazy<global::Microsoft.Azure.Functions.Worker.Invocation.IFunctionExecutor>(() => CreateDefaultExecutorInstance(context));{Constants.NewLine}" : string.Empty)} """); foreach (ExecutableFunction function in functions) { var fast = function.Visibility == FunctionMethodVisibility.Public; sb.Append($$""" if (string.Equals(context.FunctionDefinition.EntryPoint, "{{function.EntryPoint}}", StringComparison.Ordinal)) { {{(fast ? EmitFastPath(function) : EmitSlowPath())}} return; } """); } return sb.ToString(); } private static string EmitFastPath(ExecutableFunction function) { var sb = new StringBuilder(); if (!function.IsStatic) { sb.Append($""" var instanceType = types["{function.ParentFunctionClassName}"]; var i = _functionActivator.CreateInstance(instanceType, context) as {function.ParentFunctionFullyQualifiedClassName}; """); } sb.Append(!function.IsStatic ? """ """ : " "); if (function.IsReturnValueAssignable) { sb.Append("context.GetInvocationResult().Value = "); } if (function.ShouldAwait) { sb.Append("await "); } // Parameters int functionParamCounter = 0; var functionParamList = new List<string>(); foreach (var argumentTypeName in function.ParameterTypeNames) { functionParamList.Add($"({argumentTypeName})inputArguments[{functionParamCounter++}]"); } var methodParamsStr = string.Join(", ", functionParamList); sb.Append(function.IsStatic ? $"{function.ParentFunctionFullyQualifiedClassName}.{function.MethodName}({methodParamsStr});" : $"i.{function.MethodName}({methodParamsStr});"); return sb.ToString(); } private static string EmitSlowPath() { return " await _defaultExecutor.Value.ExecuteAsync(context);"; } } } }