Skip to content

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

Merged
kshyju merged 12 commits intomainfrom
shkr/2030_srcgen_func_executor_external_assemblies
Nov 29, 2023
Merged

Adding support for executing functions from referenced assemblies, to the source generated executor implementation.#2089
kshyju merged 12 commits intomainfrom
shkr/2030_srcgen_func_executor_external_assemblies

Conversation

@kshyju
Copy link
Copy Markdown
Member

@kshyju kshyju commented Nov 22, 2023

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

Fixes #2030

  • Excluding any non public methods when discovering valid azure functions (this change applies to the metadata provider as well).
  • Public methods defined inside internal classes from referenced assemblies are using existing reflection based default executor.

Sample code

When tested on a function app which has:

  • 1 http function (Function1) defined in the project.
  • Has a nuget reference to Microsoft.Azure.Functions.Worker.Extensions.OpenApi 1.5.1 package, which will add 4 functions.
  • Has a project reference which has 3 functions.
    - 1 Internal class with 2 functions (1 public method(FooLibraryInternalClassPublicMethod1) and 1 internal method(FooLibraryInternalClassInternalMethod2)
    - 1 Public class with 1 public method (FooLibraryPublicClassPublicMethod1).
internal class DirectFunctionExecutor : IFunctionExecutor
{
    private readonly IFunctionActivator _functionActivator;
    private Lazy<IFunctionExecutor> _defaultExecutor;
    private readonly Dictionary<string, Type> types = new()
    {
        { "FunctionApp172.Function1", Type.GetType("FunctionApp172.Function1, FunctionApp172, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")! },
        { "Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger", Type.GetType("Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger, Microsoft.Azure.Functions.Worker.Extensions.OpenApi, Version=1.0.0.0, Culture=neutral, PublicKeyToken=9475d07f10cb09df")! },
        { "ClassLibrary8.FooLibraryInternalClass", Type.GetType("ClassLibrary8.FooLibraryInternalClass, ClassLibrary8, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")! },
        { "ClassLibrary8.FooLibraryPublicClass", Type.GetType("ClassLibrary8.FooLibraryPublicClass, ClassLibrary8, 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, "FunctionApp172.Function1.Run", StringComparison.Ordinal))
        {
            var instanceType = types["FunctionApp172.Function1"];
            var i = _functionActivator.CreateInstance(instanceType, context) as global::FunctionApp172.Function1;
            context.GetInvocationResult().Value = i.Run((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0]);
        }
        else if (string.Equals(context.FunctionDefinition.EntryPoint, "Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger.RenderSwaggerDocument", StringComparison.Ordinal))
        {
            var instanceType = types["Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger"];
            var i = _functionActivator.CreateInstance(instanceType, context) as global::Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger;
            context.GetInvocationResult().Value = await i.RenderSwaggerDocument((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0], (string)inputArguments[1], (global::Microsoft.Azure.Functions.Worker.FunctionContext)inputArguments[2]);
        }
        else if (string.Equals(context.FunctionDefinition.EntryPoint, "Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger.RenderOpenApiDocument", StringComparison.Ordinal))
        {
            var instanceType = types["Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger"];
            var i = _functionActivator.CreateInstance(instanceType, context) as global::Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger;
            context.GetInvocationResult().Value = await i.RenderOpenApiDocument((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0], (string)inputArguments[1], (string)inputArguments[2], (global::Microsoft.Azure.Functions.Worker.FunctionContext)inputArguments[3]);
        }
        else if (string.Equals(context.FunctionDefinition.EntryPoint, "Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger.RenderSwaggerUI", StringComparison.Ordinal))
        {
            var instanceType = types["Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger"];
            var i = _functionActivator.CreateInstance(instanceType, context) as global::Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger;
            context.GetInvocationResult().Value = await i.RenderSwaggerUI((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0], (global::Microsoft.Azure.Functions.Worker.FunctionContext)inputArguments[1]);
        }
        else if (string.Equals(context.FunctionDefinition.EntryPoint, "Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger.RenderOAuth2Redirect", StringComparison.Ordinal))
        {
            var instanceType = types["Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger"];
            var i = _functionActivator.CreateInstance(instanceType, context) as global::Microsoft.Azure.Functions.Worker.Extensions.OpenApi.DefaultOpenApiHttpTrigger;
            context.GetInvocationResult().Value = await i.RenderOAuth2Redirect((global::Microsoft.Azure.Functions.Worker.Http.HttpRequestData)inputArguments[0], (global::Microsoft.Azure.Functions.Worker.FunctionContext)inputArguments[1]);
        }
        else if (string.Equals(context.FunctionDefinition.EntryPoint, "ClassLibrary8.FooLibraryInternalClass.Run1", StringComparison.Ordinal))
        {
            await _defaultExecutor.Value.ExecuteAsync(context);
        }
        else if (string.Equals(context.FunctionDefinition.EntryPoint, "ClassLibrary8.FooLibraryPublicClass.Run1", StringComparison.Ordinal))
        {
            var instanceType = types["ClassLibrary8.FooLibraryPublicClass"];
            var i = _functionActivator.CreateInstance(instanceType, context) as global::ClassLibrary8.FooLibraryPublicClass;
            context.GetInvocationResult().Value = i.Run1((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;
    }
}

Pull request checklist

  • My changes do not require documentation changes
    • Otherwise: Documentation issue linked to PR
  • My changes should not be added to the release notes for the next release
    • Otherwise: I've added my notes to release_notes.md
  • My changes do not need to be backported to a previous version
    • Otherwise: Backport tracked by issue/PR #issue_or_pr
  • I have added all required tests (Unit tests, E2E tests)

@kshyju kshyju requested review from fabiocav and satvu November 22, 2023 22:51
@kshyju kshyju marked this pull request as draft November 23, 2023 01:14
@kshyju kshyju removed request for fabiocav and satvu November 23, 2023 01:14
@kshyju kshyju force-pushed the shkr/2030_srcgen_func_executor_external_assemblies branch from 8eebd98 to 7395e12 Compare November 23, 2023 01:21
@kshyju kshyju marked this pull request as ready for review November 23, 2023 03:44
@kshyju kshyju requested review from fabiocav and satvu November 27, 2023 15:02
Comment thread sdk/Sdk.Generators/FunctionMethodVisibility.cs Outdated
Comment thread sdk/Sdk.Generators/FunctionMethodVisibility.cs Outdated
Comment thread sdk/Sdk.Generators/FunctionMethodVisibility.cs Outdated
Comment thread sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs Outdated
Comment thread sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.Emitter.cs Outdated
Comment thread sdk/Sdk.Generators/MethodVisibilityChecker.cs Outdated
Comment thread sdk/Sdk.Generators/FunctionMethodVisibility.cs
Comment thread sdk/Sdk.Generators/FunctionExecutor/FunctionExecutorGenerator.cs
Comment thread sdk/Sdk.Generators/FunctionMethodVisibility.cs
Comment thread sdk/release_notes.md
@kshyju kshyju merged commit 207c195 into main Nov 29, 2023
@kshyju kshyju deleted the shkr/2030_srcgen_func_executor_external_assemblies branch November 29, 2023 22:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Source gen function executor does not work when functions are defined in referenced assemblies

3 participants