Azure Functions - Extension resolution and loading.

Implementing IExtensionConfigProvider in a Function App is a common way to run code at startup of the Function App. It is also the place to register the components of your extension. In this post I will describe how Azure Functions finds and instantiates IExtensionConfigProvider implementations.

There are several ways how an IExtensionConfigProvider implementation is found.

Auto detection

The auto detection works in Azure Functions V1 and V2.

Auto detection of IExtensionConfigProvider implementation starts when the ScriptHost gets initialized.

  1. The ScriptHost loads all function metadata from your function.json files.
  2. The ExtensionLoader examines all parameters with a BindingAttribute of each function and gathers the Assembly where the BindingAttribute is defined!
  3. The ExtensionLoader retrieves all exported types of the assembly
  4. The ExtensionLoader checks each exported type if it implements IExtensionConfigProvider
  5. The ExtensionLoader uses Activator.CreateInstance (with no further arguments) to instantiate the IExtensionConfigProvider

Pseudo code

    var functionMetadata = LoadAllFunctionMetadata();
    foreach (var function in functionMetadata) 
    {
        var attributes = ExtensionLoader.GetAllReferencedBindingAttributes(function);
        foreach (var attribute in attributes) 
        {
            var assembly = GetAssemblyWhereAttributeIsDefined(attribute);
            var types = assembly.GetExportedTypes();
            foreach (var type in types) 
            {
                if (type implements IExtensionConfigProvider)
                {
                    Activator.CreateInstance(type);
                }
            }
        }
    }

While the auto detection is very convenient it also only works if we define and use a custom BindingAttribute.

V1 - Auto detect from extension path

This only works in Azure Functions V1.

Azure Functions can also load additional extensions from a given path. For this you need to set AzureWebJobs_ExtensionsPath either in your local.settings.json or in Azure in the AppSettings to a path where your extensions are located.

Example

{
  "Values": {
    "AzureWebJobs_ExtensionsPath": "."
  }
}

Within this folder the ExtensionLoader looks for .dll files, that have extension in their filename. It then retrieves all exported types of the assembly and checks if they implement IExtensionConfigProvider. If so it will instantiate the type.

V2 - Manual via config file

This only works in Azure Functions V2.

Beside the auto detection Azure Functions also can instantiate an IExtensionConfigProvider via a config file. This is useful if you want to run code at startup without having a custom BindingAttribute.

After the ScriptHost auto detected IExtensionConfigProvider implementation it checks if the Function App contains an extensions.json file. The file must be placed in the bin folder of the output directory! In the extensions.json we can define which additional IExtensionConfigProvider implementation should be loaded. The json file must contain an object with a property named extensions. The property must contain an array of objects with a property named TypeName.

Example:

{
  "extensions": [
    {
      "TypeName": "MyFunctionApp.MyExtensionConfigProvider, MyFunctionApp"
    }
  ]
}

If you want to have your extensions.json copied into the bin after each build, you can adjust your .csproj file.

<Target AfterTargets="Build" Name="CopyExtensions">
    <Copy SourceFiles="extensions.json" DestinationFolder="$(OutputPath)\bin" ContinueOnError="true" />
</Target>