Introduction

As part of the last post we looked at how we can use the Keyed dependency functionality in Isolated .NET 6 Azure Functions. The implementation required a service abstraction which took the Keyed dependencies and then the Azure Function took a dependency on the service. This is a valid way of implementing this functionality with minimal fuss.

However as this requires an additional service class to be defined it could be seen as additional complexity to use the functionality. So is it possible to do it directly in an Azure Function without the need for an extra class?

In short; yes. Although as we will see moving through there are different hoops which need to be jumped through instead to make it work.

Show me the code

The example code for this can be found in the named-directly-in-function branch.

What is the issue?

Let’s take a step back and determine what the underlying problem is. We have an Azure Function definition which is defined as a Http end point due to the usage of the HttpTriggerAttribute in the function parameter list. The worker runtime needs to find all of the functions and then instantiate them with their dependency tree when requested. This is done through an IFunctionActivator implementation and out of the box this is done by the DefaultFunctionActivator source here.

internal class DefaultFunctionActivator : IFunctionActivator
{
    public object? CreateInstance(Type instanceType, FunctionContext context)
    {
        if (instanceType is null)
        {
            throw new ArgumentNullException(nameof(instanceType));
        }

        if (context is null)
        {
            throw new ArgumentNullException(nameof(context));
        }

        return ActivatorUtilities.CreateInstance(context.InstanceServices, instanceType, Array.Empty<object>());
    }
}

As you can see from the source it all comes down to the ActivatorUtilities.CreateInstance call to create the instance of the function. This is a special entry point to resolve the dependency tree of the target instanceType. This in our case is the target Azure function class. Due to the nature of this it uses the default Ioc container which in our context is not Autofac.

So how do we solve this?

ServiceBasedFunctionActivator

As part of the Dotnet Worker functionality it is all based on interfaces which are in most cases public. This allows for customizations as required by the use case. The functionality, as we can see above in the DefaultFunctionActivator, has default implementations but we can swap them as required.

So what do we need to swap out and what do we need to implement?

Well actually after navigating around the Dotnet Worker repo linked above this isn’t as complicated as I was expecting. There are concepts of function invokers, function executors, function invoker factories etc. but after debugging through the code what it comes down to is implementing a different IFunctionActivator version and replacing the default version at application start up.

public class ServiceBasedFunctionActivator : IFunctionActivator
{
    public object? CreateInstance(Type instanceType, FunctionContext context)
    {
        _ = instanceType ?? throw new ArgumentNullException(nameof(instanceType));
        _ = context ?? throw new ArgumentNullException(nameof(context));
        
        return context.InstanceServices.GetService(instanceType);
    }
}

As we can see from the DefaultFunctionActivator implementation we already have access to the InstanceServices property on the FunctionContext instance passed in. This is used in the original implementation. What isn’t obvious from the name of the property is that it is the IServiceProvider instance where as on first glace I had mistakenly taken it for the IServiceCollection registrations. This makes a lot more sense and makes it easier to implement.

As we saw in “Using AutoFac with .NET 6 Isolated Azure Functions” once the Autofac Service Provider factory has been registered with the host builder the working implementation of IServiceProvider is the Autofac one.

var host = new HostBuilder()
            .UseServiceProviderFactory(new AutofacServiceProviderFactory())

As we have access to the service provider we can request the target service type, the actual Azure function class in this case, directly from the provider in our new ServiceBasedFunctionActivator implementation.

context.InstanceServices.GetService(instanceType);

Registering Dependencies

Now we have the new ServiceBasedFunctionActivator implementation we now need to register it with the Ioc container so that it is used instead of the default version.

This is done as part of the ConfigureService registration process in the Host Builder setup.

.ConfigureServices(services =>
{
    services.Replace(ServiceDescriptor.Singleton<IFunctionActivator, ServiceBasedFunctionActivator>());
}

The Replace() method is quite handy in that it will look at the target service type, IFunctionActivator in this case, and it will find the first implementation of that type in the services collection and replace it with the specified service descriptor provided. This is very handy when wanting to do this type of replace default functionality without having to iterate over the collection manually etc.

Registering Azure Function with Autofac Container

This isn’t the end of the story. If you now ran the application it would still not resolve the Azure Function class as it would still not know about how to resolve the configured Keyed depencencies. So what are we missing?

ASP.NET Core has a similar issue with resolving Controllers. To resolve this issue there is an extension method AddControllersAsServices on the IMvcBuilder which is called at Startup. This method tells the builder to register all the controller classes with the Ioc container as transient services. Once this is done a service based controller activitor is used to resolve the controllers through the IServiceProvider like any other dependency.

This is the same for Azure Functions however there is no helper extension method, and I’ve not figured out yet how it is possible to auto discover the functions, to register them. So for now we have to do this manually.

.ConfigureContainer<ContainerBuilder>(builder =>
{
    builder.RegisterType<HelloService>().Keyed<IGreeting>("hello");
    builder.RegisterType<GoodByeService>().Keyed<IGreeting>("goodbye");
    builder.RegisterType<GetWelcome>().WithAttributeFiltering();
})

As in the earlier posts this registers the Keyed IGreeting implementations and now also registers the Azure Function implementation with the Autofac builder and applies the WithAttributeFiltering() extension method to the registration as we previously did with the MyService registration previously.

Full Code

Program.cs

public class Program
{
    public static void Main()
    {
        var host = new HostBuilder()
            .UseServiceProviderFactory(new AutofacServiceProviderFactory())
            .ConfigureFunctionsWorkerDefaults()
            .ConfigureContainer<ContainerBuilder>(builder =>
            {
                builder.RegisterType<HelloService>().Keyed<IGreeting>("hello");
                builder.RegisterType<GoodByeService>().Keyed<IGreeting>("goodbye");
                builder.RegisterType<GetWelcome>().WithAttributeFiltering();
            })
            .ConfigureServices(services =>
            {
                services.Replace(ServiceDescriptor.Singleton<IFunctionActivator, ServiceBasedFunctionActivator>());
            })
            .Build();

        host.Run();
    }
}

Azure Function

public class GetWelcome
{
    private readonly IGreeting _hello;
    private readonly IGreeting _goodbye;
    private readonly ILogger _logger;
        
    public GetWelcome(
        [KeyFilter("hello")] IGreeting hello,
        [KeyFilter("goodbye")] IGreeting goodbye,
        ILoggerFactory loggerFactory)
    {
        _hello = hello;
        _goodbye = goodbye;
        _logger = loggerFactory.CreateLogger<GetWelcome>();
    }

    [Function("GetWelcome")]
    public HttpResponseData Run([HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req)
    {
        _logger.LogInformation("C# HTTP trigger function processed a request.");

        var response = req.CreateResponse(HttpStatusCode.OK);
        response.Headers.Add("Content-Type", "text/plain; charset=utf-8");

        response.WriteString($"{_hello.Speak()} and then {_goodbye.Speak()}");

        return response;
    }

Conclusion

In this post we have looked at how we can use Autofac’s Keyed dependencies directly in an Azure Function. We have looked at how this is possible by creating a custom implementation of IFunctionActivator and how to register it.

I personally think Autofac is a brilliant Ioc container which is easy to plug into Azure Functions v4 Isolated model as well as ASP.NET Core. This allows for ease of customization for complex scenarios. It also allows for using the same common code structures and concepts between Azure Functions and ASP.NET Core applications if that is your thing.

Due to how easy it was to implement a ServiceBasedFunctionActivator and swap it in as part of the service registration process I can see why it’s not part of the out of the box experience. I would like to see if a similar concept to AddControllersAsServices is possible in the future though and this is something I want to investigate. I think an AddFunctionsAsServices extension method would be very helpful.

What are you thoughts about using a different container than the built in one in Azure Functions? Is it overhead or welcomed configurability? Let me know on Twitter @WestDiscGolf as I’d be interested in hearing your thoughts.