Dependency Injections on Azure Functions is not that quite intuitive. I've written many blog posts about dependency management on Azure Functions to improve testability and this was my latest one. However, they are mostly about V1, which supports .NET Framework. Azure Functions V2 is now on public preview and I'm going to write another post for DI on Azure Functions V2, by taking advantage of the simple dependency injection feature that ASP.NET Core provides out-of-the-box.
Due to the
static nature of Azure Function triggers, it's not that easy to manage dependencies. If we can inject an IoC container itself, when an Azure Function instance is being loaded, this will be ideal. I am pretty sure that Azure Functions Team at Microsoft currently works hard to make this happen. In the meantime, we need to find out a workaround. One of the easiest and most popular workarounds is to use a
static property on each trigger. This
static property is basically an instance of an IoC container. Once the property gets instantiated, each function trigger resolves dependencies within the function.
The Workaround – IoC Container from ASP.NET Core
When an ASP.NET Core application is up and running, it bootstraps all dependencies at first within the
StartUp class using the
IServiceCollection instance. This instance also has some DI functions like
AddSingleton(). As Azure Functions V2 comes with ASP.NET Core, we can directly make use of this feature. The only difference is that, in Azure Functions, we have to bootstrap dependencies by ourselves. Let's have a look.
The source code used in this post can be found here.
I am asked to write an Azure Function code, given a username or organisation name on GitHub, to return the list of repositories. Let's simplify the user story here:
AS a user, when I give a GitHub username or organisation name, I WANT TO see the list of GitHub repositories
In order to achieve this user story, I need to write an HTTP trigger function to send an HTTP request to a GitHub API. OK, first things first.
HTTP Trigger Function
This is the HTTP trigger function, with all dependencies inside.
No good. I am not happy with that because the
HttpClient should be injected from outside and there are a few things to be injected outside. This needs to be refactored. Let's change it. First of all, I need to add a static property of
IServiceProvider to the trigger, which acts as an IoC container. By the way, the
IServiceProvider should be instantiated by
IServiceCollection. Therefore, it's a good idea to create a
ContainerBuilder class to build it.
This is the interface design. It accepts a module from outside,
RegisterModule(), which contains all dependencies then build a container,
Build(), to return
Therefore, its implementation creates a new
IServiceCollection instance, loads all dependencies from the
IModule, then builds
IServiceProvider instance, like below:
Then, how does the
IModule works? From one trigger to another, they don't have the same dependencies at all. In order to keep the collection of dependencies as light as possible, modularising dependencies is a better practice. Let's have a look. The
IModule interface defines one method,
Load() and it takes one parameter of
Module class implements
IModule and does nothing but works as a placeholder. This is a sort of base module which can be used, in case there is no suitable module found.
Another implementation of
CoreAppModule, which loads an instance of
HttpClient as a singleton. The reason why it should be registered as a singleton can be found here.
I've created the
ContainerBuilder class above and it's ready to play. Let's refactor the existing trigger.
HTTP Trigger Function – Refactored #1
The trigger function now needs to have a static property of
Now, I can inject
HttpClient instance into the trigger, which has become more testable. But I'm still not happy with the result. Why? Let's see the
requestUrl variable. It's hard-coded. What if the endpoint URL is changed for some reason? It should be configurable by reading from either an environment variable or a separate settings files like
appsettings.json which is a similar way to how an ASP.NET Core application does.
I have a
config.json file that looks like:
Its corresponding POCO class looks like:
Module – Refactored #1
ASP.NET Core supports a configuration builder OOTB, so I can just use it in the module class.
In this example, I just use the
AddJsonFile() method, but other methods like
AddEnvironmentVariables() can be used depending on your preferences. Even I can use YAML file for configuration settings. Now, the GitHub endpoint URL is all configurable.
HTTP Trigger Function – Refactored #2
With this in mind, let's do another refactoring and this is the result:
For now it's a sort of working code with full testability. Therefore, the test code for this function might look like:
As you can see above, the static property has got a mocked instance, and the function parameters also have received the mocked ones for testing. This is how dependency injection approach is used for Azure Function triggers.
However, this approach still imposes an issue – Service Provider Pattern, which is known as an anti-pattern. In the function trigger code refactored above, I explicitly resolved two instances.
From the caller's point of view, the function trigger in this case, it's not necessary to know which dependencies I need to resolve, but just run them. With this point, the function trigger needs more refactoring to hide dependency resolutions. This is also a good practice for encapsulation of features that should be hidden.
Let's have a look at the interface design of
It returns a function implementing the
IFunction interface, which is also registered into the IoC container. What does
IFunction do? All logics in the function trigger move into there. For example,
As you can see, all the logics resided in the function trigger has moved into the
CoreGitHubRepositoriesFunction class. Now the implementation of the
IFunctionFactory might look like:
This factory class firstly loads dependencies, then resolves a function with the given type when it's called.
Module – Refactored #2
Now, I need to update the
CoreAppModule class to register the
By doing so, all necessary dependencies have been registered into the IoC container.
HTTP Trigger Function – Refactored #3
With these changes, let's refactor the function trigger again. Instead of directly using the
IServiceProvider as a static property, it uses the
IFunctionFactory this time.
What the function trigger needs to do is to pass parameters and invoke the function that contains all the logics. It doesn't have to know what's going on inside the function. Testing the function trigger gets much easier.
Of course, all the logics also need to be tested, but it's much easier because they are NOT
static classes any longer. I'm not going to show how to test the rest here. Instead, I'll let you test them.
More Complex Dependency Injection Scenarios
Someone having hawk's eyes might have been wondering why I used
IGitHubRepositoriesFunction, instead of
IFunction. The dependency injection feature that ASP.NET Core provides is very simple. There is no control over multiple implementations with a same interface. For example, there might be multiple functions implementing the same
IFunction interface like:
If I need to differentiate them from each other, the current workaround is to create another interfaces like
IFunction and pass them, instead of directly using
IFunction. Alternatively, I can write a custom logic around them.
There is another scenario. Functions tend to live in a same assembly, ie. a same
.dll file. If I can scan a .dll file and automatically register all functions, it would be much easier. Unfortunately, this is not supported by ASP.NET Core either. If you really want to use those features, a 3rd-party library like Autofac needs to be considered. However, also unfortunately, it doesn't seem to get along with V2 yet.
Therefore, here's the suggestion. If you want to use the IoC container from the 3rd-party library, stay on V1. If you want to use the IoC container provided by ASP.NET Core, use V2.
So far, I've walked through how dependency injections are working on Azure Functions V2, with ASP.NET Core's DI feature. Obviously this is not an ideal solution yet and I know Azure Functions Team works really hard to enable this feature sooner rather than later. I hope this feature is released soon.
ACKNOWLEDGEMENT: This post has originally been posted at Mexia blog.