In my last post you learned how to implement dependency injection in Azure Functions on function level. While this works for services registered as transient or singleton, it does not work with services registered as scoped. In this post you will learn how we can implement proper dependency injection with scopes!

Inject attribute

We again start with the Inject attribute. Since we are going to change how to resolve bindings, the attribute can now be empty.

Binding provider

The binding provider is used to create bindings. The method TryCreateAsync will be called for each instance of the InjectAttribute.
There we get additional informations, e.g. the parameter on which the attribute is used. Our binding provider also receives the IServiceProvider instance which will be passed to the binding.

Inject binding

The binding class holds the information of the binding. In this case we pass the IServiceProvider instance and Type the attribute is bound to. The method BindAsync will be called, when ever a binding is resolved which happens when a function will be executed. We resolve the service from the IServiceProvider and pass it to a IValueProvider.

The InjectValueProvider holds the value (the instance of the requested service).

Adding support for scoped services

In order to support scoped services we need to change some of the methods. First we need to create and store an IServiceScope instance for each function invocation, so that we can share the same scope for the same function invocation. Luckily each function invocation gets a unique id, which will be passed our InjectBinding. First we add a ConcurrentDictionary to the InjectBindingProvider to store the scopes.

Now we change the BindAsync method in the InjectBinding. The BindingContext contains the FunctionInstanceId which is the unique id, created for each function invocation. So for each invocation we create a new scope and store the scope in the ConcurrentDictionary. We then resolve the service from the scope.

At this point we are already able to resolve scoped services, BUT we do not clean up and destroy the scope, so all resolved service will life forever. In order to cleanup the scopes, we implement an IFunctionInvocationFilter and IFunctionExceptionFilter which are called whenever a function execution ended or when an exception within the function occurred.

Conclusion

Although Azure Functions does not come with dependency injection for you functions, it can be added quite easy. It is still a “hacky” solution, but it works :-)
You can find the full example on GitHub.