Adding support for executing functions from referenced assemblies, to the source generated executor implementation. (#2089)

* Adding support for executing functions from referenced assemblies, to the source generated executor implementaion.

* Release note and version bump

* Picking only public functions as valid functions for metadata generation and executor.

* Minor cleanup

* Get full type name including assembly identity for all types.

* Minor cleanup

* Switching initializing the defaultExecutor to a Lazy approach.

* A bit more cleanup.

* PR feedback fixes

* PR feedback fixes
This commit is contained in:
Shyju Krishnankutty 2023-11-29 14:52:47 -08:00 коммит произвёл GitHub
Родитель 0733bbdcb1
Коммит 207c19533a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 439 добавлений и 104 удалений

Просмотреть файл

@ -0,0 +1,43 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using Microsoft.CodeAnalysis;
namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
{
internal static class IMethodSymbolExtensions
{
/// <summary>
/// Determines the visibility of an azure function method.
/// The visibility is determined by the following rules:
/// 1. If the method is public, and all containing types are public, return Public
/// 2. If the method is public, but one or more containing types are not public, return PublicButContainingTypeNotVisible
/// 3. If the method is not public, return NotPublic
/// </summary>
/// <param name="methodSymbol">The <see cref="IMethodSymbol"/> instance representing an azure function method.</param>
/// <returns><see cref="FunctionMethodVisibility"/></returns>
internal static FunctionMethodVisibility GetVisibility(this IMethodSymbol methodSymbol)
{
// Check if the symbol itself is public
if (methodSymbol.DeclaredAccessibility == Accessibility.Public)
{
// Check if any containing type is not public
INamedTypeSymbol containingType = methodSymbol.ContainingType;
while (containingType != null)
{
if (containingType.DeclaredAccessibility != Accessibility.Public)
{
return FunctionMethodVisibility.PublicButContainingTypeNotVisible;
}
containingType = containingType.ContainingType;
}
// If both the symbol and all containing types are public, return PublicAndVisible
return FunctionMethodVisibility.Public;
}
// If the symbol itself is not public, return NotPublic
return FunctionMethodVisibility.NotPublic;
}
}
}

Просмотреть файл

@ -13,8 +13,12 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
{
internal static class Emitter
{
internal static string Emit(GeneratorExecutionContext context, IEnumerable<ExecutableFunction> functions, bool includeAutoRegistrationCode)
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/>
@ -31,7 +35,7 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]
internal class DirectFunctionExecutor : IFunctionExecutor
{
private readonly IFunctionActivator _functionActivator;
private readonly IFunctionActivator _functionActivator;{{(defaultExecutorNeeded ? $"{Environment.NewLine} private Lazy<IFunctionExecutor> _defaultExecutor;" : string.Empty)}}
{{GetTypesDictionary(functions)}}
public DirectFunctionExecutor(IFunctionActivator functionActivator)
{
@ -41,8 +45,8 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
/// <inheritdoc/>
public async ValueTask ExecuteAsync(FunctionContext context)
{
{{GetMethodBody(functions)}}
}
{{GetMethodBody(functions, defaultExecutorNeeded)}}
}{{(defaultExecutorNeeded ? $"{Environment.NewLine}{EmitCreateDefaultExecutorMethod(context)}" : string.Empty)}}
}
/// <summary>
@ -67,20 +71,40 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
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 IFunctionExecutor CreateDefaultExecutorInstance(FunctionContext context)
{
var defaultExecutorFullName = "Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor, {{assemblyIdentity}}";
var defaultExecutorType = Type.GetType(defaultExecutorFullName);
return ActivatorUtilities.CreateInstance(context.InstanceServices, defaultExecutorType) as IFunctionExecutor;
}
""";
}
private static string GetTypesDictionary(IEnumerable<ExecutableFunction> functions)
{
var classNames = functions.Where(f => !f.IsStatic).Select(f => f.ParentFunctionClassName).Distinct();
if (!classNames.Any())
{
return """
// 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()
{
{{string.Join($",{Environment.NewLine} ", classNames.Select(c => $$""" { "{{c}}", Type.GetType("{{c}}")! }"""))}}
{{string.Join($",{Environment.NewLine} ", typesDict.Select(c => $$""" { "{{c.Key}}", Type.GetType("{{c.Key}}, {{c.Value}}")! }"""))}}
};
""";
@ -114,64 +138,84 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
return "";
}
private static string GetMethodBody(IEnumerable<ExecutableFunction> functions)
private static string GetMethodBody(IEnumerable<ExecutableFunction> functions, bool anyDefaultExecutor)
{
var sb = new StringBuilder();
sb.Append(
"""
$$"""
var inputBindingFeature = context.Features.Get<IFunctionInputBindingFeature>()!;
var inputBindingResult = await inputBindingFeature.BindFunctionInputAsync(context)!;
var inputArguments = inputBindingResult.Values;
{{(anyDefaultExecutor ? $" _defaultExecutor = new Lazy<IFunctionExecutor>(() => CreateDefaultExecutorInstance(context));{Environment.NewLine}" : string.Empty)}}
""");
bool first = true;
foreach (ExecutableFunction function in functions)
{
var fast = function.Visibility == FunctionMethodVisibility.Public;
sb.Append($$"""
{{(first ? string.Empty : "else ")}}if (string.Equals(context.FunctionDefinition.EntryPoint, "{{function.EntryPoint}}", StringComparison.Ordinal))
{
{{(fast ? EmitFastPath(function) : EmitSlowPath())}}
}
""");
first = false;
int functionParamCounter = 0;
var functionParamList = new List<string>();
foreach (var argumentTypeName in function.ParameterTypeNames)
{
functionParamList.Add($"({argumentTypeName})inputArguments[{functionParamCounter++}]");
}
var methodParamsStr = string.Join(", ", functionParamList);
if (!function.IsStatic)
{
sb.Append($$"""
var instanceType = types["{{function.ParentFunctionClassName}}"];
var i = _functionActivator.CreateInstance(instanceType, context) as {{function.ParentFunctionFullyQualifiedClassName}};
""");
}
sb.Append(@"
");
if (function.IsReturnValueAssignable)
{
sb.Append(@$"context.GetInvocationResult().Value = ");
}
if (function.ShouldAwait)
{
sb.Append("await ");
}
sb.Append(function.IsStatic
? @$"{function.ParentFunctionFullyQualifiedClassName}.{function.MethodName}({methodParamsStr});
}}"
: $@"i.{function.MethodName}({methodParamsStr});
}}");
}
return sb.ToString();
}
private static string EmitFastPath(ExecutableFunction function)
{
var sb = new StringBuilder();
int functionParamCounter = 0;
var functionParamList = new List<string>();
foreach (var argumentTypeName in function.ParameterTypeNames)
{
functionParamList.Add($"({argumentTypeName})inputArguments[{functionParamCounter++}]");
}
var methodParamsStr = string.Join(", ", functionParamList);
if (!function.IsStatic)
{
sb.Append($$"""
var instanceType = types["{{function.ParentFunctionClassName}}"];
var i = _functionActivator.CreateInstance(instanceType, context) as {{function.ParentFunctionFullyQualifiedClassName}};
""");
}
if (!function.IsStatic)
{
sb.Append(@"
");
}
else
{
sb.Append(" ");
}
if (function.IsReturnValueAssignable)
{
sb.Append("context.GetInvocationResult().Value = ");
}
if (function.ShouldAwait)
{
sb.Append("await ");
}
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);";
}
}
}
}

Просмотреть файл

@ -52,5 +52,16 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
/// A collection of fully qualified type names of the parameters of the function.
/// </summary>
internal IEnumerable<string> ParameterTypeNames { set; get; } = Enumerable.Empty<string>();
/// <summary>
/// Get a value indicating the visibility of the executable function.
/// </summary>
internal FunctionMethodVisibility Visibility { get; set; }
/// <summary>
/// Gets the assembly identity of the function.
/// ex: FooAssembly, Version=1.2.3.4, Culture=neutral, PublicKeyToken=9475d07f10cb09df
/// </summary>
internal string AssemblyIdentity { get; set; } = null!;
}
}

Просмотреть файл

@ -2,9 +2,8 @@
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Collections.Generic;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
{
@ -23,48 +22,38 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
private Compilation Compilation => _context.Compilation;
internal ICollection<ExecutableFunction> GetFunctions(List<MethodDeclarationSyntax> methods)
internal ICollection<ExecutableFunction> GetFunctions(IEnumerable<IMethodSymbol> methods)
{
var functionList = new List<ExecutableFunction>();
foreach (MethodDeclarationSyntax method in methods)
foreach (IMethodSymbol method in methods.Where(m=>m.DeclaredAccessibility == Accessibility.Public))
{
_context.CancellationToken.ThrowIfCancellationRequested();
var model = Compilation.GetSemanticModel(method.SyntaxTree);
if (!FunctionsUtil.IsValidFunctionMethod(_context, Compilation, model, method))
var methodName = method.Name;
var methodParameterList = new List<string>();
foreach (IParameterSymbol parameterSymbol in method.Parameters)
{
continue;
}
var methodName = method.Identifier.Text;
var methodParameterList = new List<string>(method.ParameterList.Parameters.Count);
foreach (var methodParam in method.ParameterList.Parameters)
{
if (model.GetDeclaredSymbol(methodParam) is not IParameterSymbol parameterSymbol)
{
continue;
}
var fullyQualifiedTypeName = parameterSymbol.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
methodParameterList.Add(fullyQualifiedTypeName);
}
var methodSymbol = model.GetDeclaredSymbol(method)!;
var defaultFormatClassName = methodSymbol.ContainingSymbol.ToDisplayString();
var fullyQualifiedClassName = methodSymbol.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var defaultFormatClassName = method.ContainingSymbol.ToDisplayString();
var fullyQualifiedClassName = method.ContainingSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat);
var function = new ExecutableFunction
{
EntryPoint = $"{defaultFormatClassName}.{method.Identifier.ValueText}",
EntryPoint = $"{defaultFormatClassName}.{method.Name}",
ParameterTypeNames = methodParameterList,
MethodName = methodName,
ShouldAwait = IsTaskType(methodSymbol.ReturnType),
IsReturnValueAssignable = IsReturnValueAssignable(methodSymbol),
IsStatic = method.Modifiers.Any(SyntaxKind.StaticKeyword),
ShouldAwait = IsTaskType(method.ReturnType),
IsReturnValueAssignable = IsReturnValueAssignable(method),
IsStatic = method.IsStatic,
ParentFunctionClassName = defaultFormatClassName,
ParentFunctionFullyQualifiedClassName = fullyQualifiedClassName
ParentFunctionFullyQualifiedClassName = fullyQualifiedClassName,
Visibility = method.GetVisibility(),
AssemblyIdentity = method.ContainingAssembly.Identity.GetDisplayName(),
};
functionList.Add(function);

Просмотреть файл

@ -1,8 +1,11 @@
// 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;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;
using FunctionMethodSyntaxReceiver = Microsoft.Azure.Functions.Worker.Sdk.Generators.FunctionMetadataProviderGenerator.FunctionMethodSyntaxReceiver;
@ -23,25 +26,53 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
return;
}
if (context.SyntaxReceiver is not FunctionMethodSyntaxReceiver receiver || receiver.CandidateMethods.Count == 0)
if (context.SyntaxReceiver is not FunctionMethodSyntaxReceiver receiver)
{
return;
}
var entryAssemblyFuncs = GetSymbolsMethodSyntaxes(receiver.CandidateMethods, context);
var dependentFuncs = GetDependentAssemblyFunctionsSymbols(context);
var allMethods = entryAssemblyFuncs.Concat(dependentFuncs);
if (!allMethods.Any())
{
return;
}
var parser = new Parser(context);
var functions = parser.GetFunctions(receiver.CandidateMethods);
if (functions.Count == 0)
{
return;
}
var functions = parser.GetFunctions(allMethods);
var shouldIncludeAutoGeneratedAttributes = ShouldIncludeAutoGeneratedAttributes(context);
var text = Emitter.Emit(context, functions, shouldIncludeAutoGeneratedAttributes);
context.AddSource(Constants.FileNames.GeneratedFunctionExecutor, SourceText.From(text, Encoding.UTF8));
}
private IEnumerable<IMethodSymbol> GetSymbolsMethodSyntaxes(List<MethodDeclarationSyntax> methods, GeneratorExecutionContext context)
{
foreach (MethodDeclarationSyntax method in methods)
{
var model = context.Compilation.GetSemanticModel(method.SyntaxTree);
if (FunctionsUtil.IsValidFunctionMethod(context, context.Compilation, model, method))
{
IMethodSymbol? methodSymbol = (IMethodSymbol)model.GetDeclaredSymbol(method)!;
yield return methodSymbol;
}
}
}
/// <summary>
/// Collect methods with Function attributes on them from dependent/referenced assemblies.
/// </summary>
private static IEnumerable<IMethodSymbol> GetDependentAssemblyFunctionsSymbols(GeneratorExecutionContext context)
{
var visitor = new ReferencedAssemblyMethodVisitor(context.Compilation);
visitor.Visit(context.Compilation.SourceModule);
return visitor.FunctionMethods;
}
private static bool ShouldIncludeAutoGeneratedAttributes(GeneratorExecutionContext context)
{
if (!context.AnalyzerConfigOptions.GlobalOptions.TryGetValue(

Просмотреть файл

@ -45,8 +45,8 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
{
var result = ImmutableArray.CreateBuilder<GeneratorFunctionMetadata>();
// Loop through the candidate methods (methods with any attribute associated with them)
foreach (IMethodSymbol method in methods)
// Loop through the candidate methods (methods with any attribute associated with them) which are public.
foreach (IMethodSymbol method in methods.Where(m => m.DeclaredAccessibility == Accessibility.Public))
{
CancellationToken.ThrowIfCancellationRequested();
@ -129,7 +129,7 @@ namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
/// <summary>
/// Checks for and returns any OutputBinding attributes associated with the method.
/// </summary>
private bool TryGetMethodOutputBinding(IMethodSymbol method,out bool hasMethodOutputBinding, out GeneratorRetryOptions? retryOptions, out IList<IDictionary<string, object>>? bindingsList)
private bool TryGetMethodOutputBinding(IMethodSymbol method, out bool hasMethodOutputBinding, out GeneratorRetryOptions? retryOptions, out IList<IDictionary<string, object>>? bindingsList)
{
var attributes = method!.GetAttributes(); // methodSymbol is not null here because it's checked in IsValidAzureFunction which is called before bindings are collected/created

Просмотреть файл

@ -0,0 +1,26 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
namespace Microsoft.Azure.Functions.Worker.Sdk.Generators
{
/// <summary>
/// Represents the visibility of an "azure function" method and its parent classes.
/// </summary>
internal enum FunctionMethodVisibility
{
/// <summary>
/// The method and it's parent classes are public & visible.
/// </summary>
Public,
/// <summary>
/// The method is public, but one or more of its parent classes are not public.
/// </summary>
PublicButContainingTypeNotVisible,
/// <summary>
/// The method is not public.
/// </summary>
NotPublic
}
}

Просмотреть файл

@ -10,7 +10,7 @@
<IncludeBuildOutput>false</IncludeBuildOutput>
<EmitCompilerGeneratedFiles>true</EmitCompilerGeneratedFiles>
<MinorProductVersion>1</MinorProductVersion>
<PatchProductVersion>4</PatchProductVersion>
<PatchProductVersion>5</PatchProductVersion>
<VersionSuffix></VersionSuffix>
<IsRoslynComponent>true</IsRoslynComponent>
</PropertyGroup>

Просмотреть файл

@ -38,8 +38,7 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
<FunctionsEnableWorkerIndexing Condition="$(FunctionsEnableWorkerIndexing) == '' Or $(FunctionsEnableWorkerIndexing)">true</FunctionsEnableWorkerIndexing>
<FunctionsEnableMetadataSourceGen>$(FunctionsEnableWorkerIndexing)</FunctionsEnableMetadataSourceGen>
<FunctionsAutoRegisterGeneratedMetadataProvider>$(FunctionsEnableWorkerIndexing)</FunctionsAutoRegisterGeneratedMetadataProvider>
<FunctionsEnableExecutorSourceGen Condition="$(FunctionsEnableExecutorSourceGen) == ''">false</FunctionsEnableExecutorSourceGen>
<FunctionsEnableExecutorSourceGen Condition="$(FunctionsEnableExecutorSourceGen) == '' Or $(FunctionsEnableExecutorSourceGen)">true</FunctionsEnableExecutorSourceGen>
<FunctionsAutoRegisterGeneratedFunctionsExecutor Condition="$(FunctionsAutoRegisterGeneratedFunctionsExecutor) == ''">true</FunctionsAutoRegisterGeneratedFunctionsExecutor>
<FunctionsAutoRegisterGeneratedFunctionsExecutor Condition="$(FunctionsAutoRegisterGeneratedFunctionsExecutor)">true</FunctionsAutoRegisterGeneratedFunctionsExecutor>
<FunctionsGeneratedCodeNamespace Condition="$(FunctionsGeneratedCodeNamespace) == ''">$(RootNamespace.Replace("-", "_"))</FunctionsGeneratedCodeNamespace>

Просмотреть файл

@ -4,12 +4,15 @@
- My change description (#PR/#issue)
-->
### Microsoft.Azure.Functions.Worker.Sdk 1.16.4 (meta package)
### Microsoft.Azure.Functions.Worker.Sdk 1.16.3 (meta package)
- Update worker.config generation to accurate worker executable name (#1053)
- Default to optimized function executor.
- Update Microsoft.Azure.Functions.Worker.Sdk.Generators dependency to 1.1.5
### Microsoft.Azure.Functions.Worker.Sdk.Generators 1.1.5
- Adding support for executing functions from referenced assemblies in the optimized function executor (#2089)
- Update worker.config generation to accurate worker executable name (#1053)
- Fix incorrect value of `ScriptFile` property in function metadata for .Net Framework function apps (#2103)
- Generate valid namespace when root namespace contains `-` (#2097)

Просмотреть файл

@ -1,4 +1,7 @@
using Microsoft.Azure.Functions.Worker;
// (c).NET Foundation.All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
namespace DependentAssemblyWithFunctions
@ -11,5 +14,11 @@ namespace DependentAssemblyWithFunctions
{
throw new NotImplementedException();
}
[Function("ThisShouldBeSkippedBecauseMethodNotPublic")]
internal static HttpResponseData Run2([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequestData req)
{
throw new NotImplementedException();
}
}
}

Просмотреть файл

@ -0,0 +1,150 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Azure.Functions.Worker.Sdk.Generators;
using Xunit;
namespace Microsoft.Azure.Functions.SdkGeneratorTests
{
public partial class FunctionExecutorGeneratorTests
{
public class DependentAssemblyTest
{
private readonly Assembly[] _referencedAssemblies;
public DependentAssemblyTest()
{
var abstractionsExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Abstractions.dll");
var httpExtension = Assembly.LoadFrom("Microsoft.Azure.Functions.Worker.Extensions.Http.dll");
var hostingExtension = Assembly.LoadFrom("Microsoft.Extensions.Hosting.dll");
var diExtension = Assembly.LoadFrom("Microsoft.Extensions.DependencyInjection.dll");
var hostingAbExtension = Assembly.LoadFrom("Microsoft.Extensions.Hosting.Abstractions.dll");
var diAbExtension = Assembly.LoadFrom("Microsoft.Extensions.DependencyInjection.Abstractions.dll");
var dependentAssembly = Assembly.LoadFrom("DependentAssemblyWithFunctions.dll");
_referencedAssemblies = new[]
{
abstractionsExtension,
httpExtension,
hostingExtension,
hostingAbExtension,
diExtension,
diAbExtension,
dependentAssembly
};
}
[Fact]
public async Task FunctionsFromDependentAssembly()
{
const string inputSourceCode = """
using System;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
namespace MyCompany
{
public class MyHttpTriggers
{
[Function("FunctionA")]
public HttpResponseData Foo([HttpTrigger(AuthorizationLevel.User, "get")] HttpRequestData r, FunctionContext c)
{
return r.CreateResponse(System.Net.HttpStatusCode.OK);
}
}
}
""";
var expected = $$"""
// <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 TestProject
{
[global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Never)]
internal class DirectFunctionExecutor : IFunctionExecutor
{
private readonly IFunctionActivator _functionActivator;
private Lazy<IFunctionExecutor> _defaultExecutor;
private readonly Dictionary<string, Type> types = new()
{
{ "MyCompany.MyHttpTriggers", Type.GetType("MyCompany.MyHttpTriggers, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null")! },
{ "DependentAssemblyWithFunctions.DependencyFunction", Type.GetType("DependentAssemblyWithFunctions.DependencyFunction, DependentAssemblyWithFunctions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")! },
{ "MyCompany.MyProduct.MyApp.HttpFunctions", Type.GetType("MyCompany.MyProduct.MyApp.HttpFunctions, DependentAssemblyWithFunctions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")! },
{ "MyCompany.MyProduct.MyApp.Foo.Bar", Type.GetType("MyCompany.MyProduct.MyApp.Foo.Bar, DependentAssemblyWithFunctions, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")! }
};
public DirectFunctionExecutor(IFunctionActivator functionActivator)
{
_functionActivator = functionActivator ?? throw new ArgumentNullException(nameof(functionActivator));
}
/// <inheritdoc/>
public async ValueTask ExecuteAsync(FunctionContext context)
{
var inputBindingFeature = context.Features.Get<IFunctionInputBindingFeature>()!;
var inputBindingResult = await inputBindingFeature.BindFunctionInputAsync(context)!;
var inputArguments = inputBindingResult.Values;
_defaultExecutor = new Lazy<IFunctionExecutor>(() => CreateDefaultExecutorInstance(context));
if (string.Equals(context.FunctionDefinition.EntryPoint, "MyCompany.MyHttpTriggers.Foo", StringComparison.Ordinal))
{
var instanceType = types["MyCompany.MyHttpTriggers"];
var i = _functionActivator.CreateInstance(instanceType, context) as global::MyCompany.MyHttpTriggers;
context.GetInvocationResult().Value = i.Foo((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0], (global::Microsoft.Azure.Functions.Worker.FunctionContext)inputArguments[1]);
}
else if (string.Equals(context.FunctionDefinition.EntryPoint, "DependentAssemblyWithFunctions.DependencyFunction.Run", StringComparison.Ordinal))
{
var instanceType = types["DependentAssemblyWithFunctions.DependencyFunction"];
var i = _functionActivator.CreateInstance(instanceType, context) as global::DependentAssemblyWithFunctions.DependencyFunction;
context.GetInvocationResult().Value = i.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0]);
}
else if (string.Equals(context.FunctionDefinition.EntryPoint, "DependentAssemblyWithFunctions.InternalFunction.Run", StringComparison.Ordinal))
{
await _defaultExecutor.Value.ExecuteAsync(context);
}
else if (string.Equals(context.FunctionDefinition.EntryPoint, "DependentAssemblyWithFunctions.StaticFunction.Run", StringComparison.Ordinal))
{
context.GetInvocationResult().Value = global::DependentAssemblyWithFunctions.StaticFunction.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0], (global::Microsoft.Azure.Functions.Worker.FunctionContext)inputArguments[1]);
}
else if (string.Equals(context.FunctionDefinition.EntryPoint, "MyCompany.MyProduct.MyApp.HttpFunctions.Run", StringComparison.Ordinal))
{
var instanceType = types["MyCompany.MyProduct.MyApp.HttpFunctions"];
var i = _functionActivator.CreateInstance(instanceType, context) as global::MyCompany.MyProduct.MyApp.HttpFunctions;
context.GetInvocationResult().Value = i.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0]);
}
else if (string.Equals(context.FunctionDefinition.EntryPoint, "MyCompany.MyProduct.MyApp.Foo.Bar.Run", StringComparison.Ordinal))
{
var instanceType = types["MyCompany.MyProduct.MyApp.Foo.Bar"];
var i = _functionActivator.CreateInstance(instanceType, context) as global::MyCompany.MyProduct.MyApp.Foo.Bar;
context.GetInvocationResult().Value = i.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0]);
}
}
private IFunctionExecutor CreateDefaultExecutorInstance(FunctionContext context)
{
var defaultExecutorFullName = "Microsoft.Azure.Functions.Worker.Invocation.DefaultFunctionExecutor, Microsoft.Azure.Functions.Worker.Core, Version=1.16.0.0, Culture=neutral, PublicKeyToken=551316b6919f366c";
var defaultExecutorType = Type.GetType(defaultExecutorFullName);
return ActivatorUtilities.CreateInstance(context.InstanceServices, defaultExecutorType) as IFunctionExecutor;
}
}
{{GetExpectedExtensionMethodCode()}}
}
""".Replace("'", "\"");
await TestHelpers.RunTestAsync<FunctionExecutorGenerator>(
_referencedAssemblies,
inputSourceCode,
Constants.FileNames.GeneratedFunctionExecutor,
expected);
}
}
}
}

Просмотреть файл

@ -16,7 +16,7 @@ using Xunit;
namespace Microsoft.Azure.Functions.SdkGeneratorTests
{
public class FunctionExecutorGeneratorTests
public partial class FunctionExecutorGeneratorTests
{
// A super set of assemblies we need for all tests in the file.
private readonly Assembly[] _referencedAssemblies = new[]
@ -116,9 +116,9 @@ namespace TestProject
private readonly IFunctionActivator _functionActivator;
private readonly Dictionary<string, Type> types = new()
{{
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers"")! }},
{{ ""MyCompany.MyHttpTriggers2"", Type.GetType(""MyCompany.MyHttpTriggers2"")! }},
{{ ""MyCompany.QueueTriggers"", Type.GetType(""MyCompany.QueueTriggers"")! }}
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }},
{{ ""MyCompany.MyHttpTriggers2"", Type.GetType(""MyCompany.MyHttpTriggers2, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }},
{{ ""MyCompany.QueueTriggers"", Type.GetType(""MyCompany.QueueTriggers, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }}
}};
public DirectFunctionExecutor(IFunctionActivator functionActivator)
@ -225,7 +225,7 @@ namespace MyCompany.MyProject.MyApp
private readonly IFunctionActivator _functionActivator;
private readonly Dictionary<string, Type> types = new()
{{
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers"")! }}
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }}
}};
public DirectFunctionExecutor(IFunctionActivator functionActivator)
@ -471,7 +471,7 @@ namespace TestProject
private readonly IFunctionActivator _functionActivator;
private readonly Dictionary<string, Type> types = new()
{{
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers"")! }}
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }}
}};
public DirectFunctionExecutor(IFunctionActivator functionActivator)
@ -556,7 +556,7 @@ namespace TestProject
private readonly IFunctionActivator _functionActivator;
private readonly Dictionary<string, Type> types = new()
{{
{{ ""TestProject.TestProject"", Type.GetType(""TestProject.TestProject"")! }}
{{ ""TestProject.TestProject"", Type.GetType(""TestProject.TestProject, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }}
}};
public DirectFunctionExecutor(IFunctionActivator functionActivator)
@ -639,7 +639,7 @@ namespace TestProject
private readonly IFunctionActivator _functionActivator;
private readonly Dictionary<string, Type> types = new()
{{
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers"")! }}
{{ ""MyCompany.MyHttpTriggers"", Type.GetType(""MyCompany.MyHttpTriggers, TestProject, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null"")! }}
}};
public DirectFunctionExecutor(IFunctionActivator functionActivator)

Просмотреть файл

@ -15,6 +15,8 @@ using Microsoft.CodeAnalysis.Testing.Verifiers;
using Microsoft.Azure.Functions.Worker.Core;
using System.Reflection;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Versioning;
namespace Microsoft.Azure.Functions.SdkGeneratorTests
{
@ -87,11 +89,16 @@ namespace Microsoft.Azure.Functions.SdkGeneratorTests
{
public Test()
{
// See https://www.nuget.org/packages/Microsoft.NETCore.App.Ref/6.0.0
var targetFrameworkAttribute = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(TargetFrameworkAttribute), false)
.SingleOrDefault() as TargetFrameworkAttribute;
string targetFramework = targetFrameworkAttribute!.FrameworkName;
var tfm = ConvertFrameworkMonikerToTfm(targetFramework);
this.ReferenceAssemblies = new ReferenceAssemblies(
targetFramework: "net6.0",
referenceAssemblyPackage: new PackageIdentity("Microsoft.NETCore.App.Ref", "6.0.0"),
referenceAssemblyPath: Path.Combine("ref", "net6.0"));
targetFramework: tfm,
referenceAssemblyPackage: new PackageIdentity("Microsoft.NETCore.App.Ref", Environment.Version.ToString()),
referenceAssemblyPath: Path.Combine("ref", tfm));
}
public LanguageVersion LanguageVersion { get; set; } = LanguageVersion.CSharp9;
@ -119,6 +126,29 @@ namespace Microsoft.Azure.Functions.SdkGeneratorTests
{
return ((CSharpParseOptions)base.CreateParseOptions()).WithLanguageVersion(this.LanguageVersion);
}
/// <summary>
/// Example input: .NETCoreApp,Version=v7.0
/// Example output: net7.0
/// </summary>
private static string ConvertFrameworkMonikerToTfm(string frameworkMoniker)
{
var parts = frameworkMoniker.Split(',');
var identifier = parts[0];
var version = parts[1].Split('=')[1];
switch (identifier)
{
case ".NETCoreApp":
return $"net{version.Substring(1)}";
case ".NETFramework":
return $"net{version.Replace(".", "")}";
case ".NETStandard":
return $"netstandard{version.Substring(1)}";
default:
throw new NotSupportedException($"Unknown framework identifier: {identifier}");
}
}
}
}
}