AzureFunction.Isolated.HostConfigurator 1.0.3

dotnet add package AzureFunction.Isolated.HostConfigurator --version 1.0.3                
NuGet\Install-Package AzureFunction.Isolated.HostConfigurator -Version 1.0.3                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="AzureFunction.Isolated.HostConfigurator" Version="1.0.3" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add AzureFunction.Isolated.HostConfigurator --version 1.0.3                
#r "nuget: AzureFunction.Isolated.HostConfigurator, 1.0.3"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install AzureFunction.Isolated.HostConfigurator as a Cake Addin
#addin nuget:?package=AzureFunction.Isolated.HostConfigurator&version=1.0.3

// Install AzureFunction.Isolated.HostConfigurator as a Cake Tool
#tool nuget:?package=AzureFunction.Isolated.HostConfigurator&version=1.0.3                

🪲 Problem

The new dotnet-isolated hosting model of Azure Functions doesn't allow you to configure the connection for triggers or binding the same way it does in the In-Process model. This is a problem when you want to use a connection string from Key Vault, for example. The problem is that the host process needs the connection before it calls into your code (i.e. before your Program.cs is called) so you only have a couple of ways to configure it:

  • Use an environment variable
  • Use a local.settings.json file

Both of these options may not be sufficient to cover your scenario so this package helps you work around this limitation that will hopefully be resolved soon on the Azure Functions side. See this issue and this issue for context.

⚒️ Workaround

Azure Functions using the dotnet-isolated hosting model will load the extensions that your project references and will initialize the extensions before trying to resolve the connection strings.

This step happens inside the AddScriptHost method of the ScriptHostBuilderExtensions when the host calls the HasExternalConfigurationStartups() method. (the code I'm talking about here is in the azure-function-host project on GitHub here.

So this package essentially hooks into that functionality to allow you to customize the host configuration before it's used.

This package implements a WebJob extension that will delegate the configuration to a type that you have to implement and specify in your project using the assembly attribute [HostConfiguratorAttribute(typeof(TheTypeToBeInvoked))].

You need to specify a type that implements the interface IWebJobsConfigurationStartup from the package Microsoft.Azure.WebJobs in the attribute, this type will then be invoked by this extension allowing you to add additional configuration sources to the host configuration.

🚀 Usage

  1. Install the package AzureFunction.Isolated.HostConfigurator into your Azure Functions dotnet-isolated project or in a separate class library project that targets net6.0 if your function project targets net7.0.
dotnet add package AzureFunction.Isolated.HostConfigurator
  1. Create a class and implement the interface IWebJobsConfigurationStartup from the package Microsoft.Azure.WebJobs in your project. This is where you will add your custom configuration sources.
internal class StartupExtension : IWebJobsConfigurationStartup
{
    public void Configure(WebJobsBuilderContext context, IWebJobsConfigurationBuilder builder)
    {
        // Please note the usage of the context.ApplicationRootPath to get the path to the application root, where we can find our configuration files
        builder.ConfigurationBuilder
            .AddJsonFile(Path.Combine(context.ApplicationRootPath, "appsettings.json"), optional: false, reloadOnChange: false)
            .AddJsonFile(Path.Combine(context.ApplicationRootPath, $"appsettings.{context.EnvironmentName}.json"), optional: false, reloadOnChange: false)
            .AddEnvironmentVariables();
    }
}
  1. Add the attribute [HostConfiguratorAttribute(typeof(TheTypeToBeInvoked))] to your project.
[assembly: HostConfiguratorAttribute(typeof(StartupExtension))]
  1. Set the ConfiguratorAssemblyName configuration with the name of the assembly that contains the [assembly: HostConfiguratorAttribute] attribute. Such configuration can be added to either the host.json, the appsettings.json or as an environment variable

host.json example

{
  "version": "2.0",
  "logging": {
    "applicationInsights": {
      "samplingSettings": {
        "isEnabled": true,
        "excludedTypes": "Request"
      }
    }
  },
  "ConfiguratorAssemblyName": "FunctionApp1.dll"
}

⚠️ Caveats

This package provides a workaround for an unsupported functionality loading an assembly that you specify into the function host process, this comes with a set of limitations and constraints as you can see below.

  • If you need to call AddJsonFile('appsettings.json'), make sure to call Path.Combine(context.ApplicationRootPath, "appsettings.json") because the host will not be running from the same directory your code is running from.

  • If you use a single assembly approach, you're forced to target net6.0 and your assembly will be loaded twice, once in the host process and once by the Azure Functions host. If you have any static initialization code, it may potentially be called twice. To prevent this issue you should define a standalone assembly that contains the configuration code instead of adding the type in your functions code directly.

  • If you need an assembly reference in your configuration class, you have to make sure it can be loaded in the host process (the function host) that targets .NET 6.0. Keep the code in the host configurator class as small as possible, and if your function needs to target net7.0, place your custom configuration class in a separate assembly and target net6.0.

  • Since local.settings.json is not published during deployment, I decided not to include that file in the assembly name lookup here. If you add the appsettings.json file, make sure to set the Copy to Output Directory property to Copy always or Copy if newer so that the file is copied to the output directory during the build process. Please note that you're also constrained about the version of the packages that you're using, for KeyVault and App Configuration, you can copy that from the relative samples.

  • You may get a warning telling you that: The Functions scale controller may not scale the following functions correctly because some configuration values were modified in an external startup class. This is caused by a validation that checks if the configuration values have been modified by an external startup class, which is the case here since the original configuration has no means to read from the additional configuration sources we want to add. This warning can be safely ignored since it's the problem this library tries to solve. If you want to dig deeper, this behavior is implemented in the ExternalConfigurationStartupValidator in the azure-functions-host project.

Note that if you use managed identities with e.g. Azure Service Bus you won't get the warning because the configuration value is null but not the nested configuration called fullyQualifiedNamespace and the service only checks for top level ones.

  • You can't set a breakpoint in the code loaded by the host configurator and if you do so, you will realize that the breakpoint won't be hit. If you need to debug the code, you can use the Debugger.Launch() method to attach a debugger to the process.

💡 Samples

Check the /samples folder for some sample projects.

This has been tested on Visual Studio 17.6.5 and via the func cli 4.0.5085 both of which can successfully run a project as well as deployed in Azure Function.

Product Compatible and additional computed target framework versions.
.NET net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last updated
1.0.3 425 8/18/2023
1.0.1 167 7/31/2023
1.0.0 173 7/28/2023