When starting to play around with Azure Functions, the lack of dependency injection support was pretty annoying. To overcome that issue I created a small library, Autofac On Functions, based on Azure Functions 1.x. Unfortunately it is not possible to generate a nuget package from these sources.
Azure On Function with nuget package available
Yesterday I started to play around with Azure Functions v2. It is available as beta version supporting dotnet standard and .net core. Updating was pretty relaxed beside the fact, that there had been some incompatible changes.
The good news is, packaging assemblies with reference to the Azure Functions SDK is now possible. So here it is:
This is a quite early state, but it is fully functional and provides exactly the functionality that is necessary. You can also download the sources on github.
What you need to get it running
What do you need to enjoy Azure Functions with dependency injection?
You can just download the sample also from Github here. Nuget package 0.1.5 is referenced. Make sure, you have the latest Azure Functions that supports .net standard on your machine.
Create an Azure Functions project
Simple open Visual Studio, and create a new project. Select “Azure Functions”.
Select type of Azure Function
Azure Functions v2 offers a little more infrastructure, also the dialog to add Azure Functions looks a little different. I didn’t select a storage account as for the sample it is simply not necessary. An http trigger function is going to be created on base of .net standard.
Add dependency to AutofacOnFunctions
Right click your project and select “manage nuget packages”. Search for AutofacOnFunctions under “browse”. Install current version. Again, be aware that it “only” supports .net standard.
Add a test service
With above steps, Visual Studio created an Azure Function. Nuget package is there. What do I need to do to use Dependency Injection now?
Create a simple service to be injected into your http triggered Azure Function and the interface:
using Microsoft.Extensions.Logging;
namespace AutofacOnFunctions.Samples.NetStandard.Services.Functions
{
internal class TestIt : ITestIt
{
private readonly ILogger<TestIt> _logger;
public TestIt(ILogger<TestIt> logger)
{
_logger = logger;
_logger.LogInformation("ctor");
}
public string Name { get; set; }
public string CallMe()
{
_logger.LogInformation("callme");
return "Dependency Injection Test method had been called.";
}
}
}
namespace AutofacOnFunctions.Samples.NetStandard.Services.Functions
{
public interface ITestIt
{
string CallMe();
}
}
Add a Bootstrapper
The bootstrapper will tell the framework which modules need to be loaded, thus which services shall be registered.
using Autofac;
using AutofacOnFunctions.Samples.NetStandard.Services.Modules;
using AutofacOnFunctions.Services.Ioc;
namespace AutofacOnFunctions.Samples.Bootstrap
{
public class Bootstrapper : IBootstrapper
{
public Module[] CreateModules()
{
return new Module[]
{
new ServicesModule()
};
}
}
}
Certainly, the “ServiceModule” contains your newly created test service:
using AutofacOnFunctions.Services.Ioc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
namespace AutofacOnFunctions.Samples.NetStandard.Services.Functions
{
public static class Function1
{
[FunctionName("Function1")]
public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
HttpRequest req, [Inject] ITestIt testit,
[Inject] ILogger logger)
{
logger.LogInformation("C# HTTP trigger function processed a request.");
return new OkObjectResult($"Hello. Dependency injection sample returns '{testit.CallMe()}'");
}
}
}
Inject your service in Azure Functions
Use the InjectAttribute to let your service being injected by Autofac.
using AutofacOnFunctions.Services.Ioc;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Azure.WebJobs.Host;
using Microsoft.Extensions.Logging;
namespace AutofacOnFunctions.Samples.NetStandard.Services.Functions
{
public static class Function1
{
[FunctionName("Function1")]
public static IActionResult Run([HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)]
HttpRequest req, [Inject] ITestIt testit,
[Inject] ILogger logger)
{
logger.LogInformation("C# HTTP trigger function processed a request.");
return new OkObjectResult($"Hello. Dependency injection sample returns '{testit.CallMe()}'");
}
}
}
See the Results
Just hit f5 and let’s see the results.
Custom provider for the InjectAttribute had been loaded. The provider will only be loaded, when there is at least one Inject attribute used. If an inject attribute is used, but there is no bootstrapper available, the framework will raise an exception as the Azure Function will not be able to start.
When you copy the marked url above and paste it in the browser of your choice, you should see this result:
Finally find all sources in this repository. Enjoy!
Final thoughts
I had an very interesting chat about property vs method injection within Azure Functions with Justin Yoo. You can follow the chat in his latest post Dependency Injection on Azure Functions. The actual question is: Does it make sense due to the static nature of Azure Functions to use method injection? Constructor injection certainly is not sensibly possible, would property injection be the better choice?
Actually I do like Justin’s approach with the FunctionFactory. To keep changes on method signatures low and therefore keep the effort in maintaining unit tests low, I would prefer to just inject the FunctionFactory instead of having a different approach.
I am going to set up a sample and let you guys know.
Update 24-09-2018:
- As promised I did setup a sample for the FunctionFactory approach. Actually I didn’t upload it anywhere as from my perspective this approach is not benefitial:
The function itself doesn’t define the dependencies anymore. Using the factory hides this important information - There is actually no need for an infrastructural solution with a static “FunctionFactory” from my point of view. If the function shall consume a Factory, the InjectAttribute can be used for doing so. Just using depedency injection to access services does not undercut the overall architecture which should be primarly dependency injection usage with in best case no exception.
- I do not like the fact, that the function itself follows a certain pattern to increase testability. A public static property that is used to change the behavior of the function is from my point of view not preferrable.
Just a sample to explain the approach. This is the code from Justin
public static class CoreGitHubRepositoriesHttpTrigger
{
public static IFunctionFactory Factory = new CoreFunctionFactory(new CoreAppModule());
[FunctionName("CoreGitHubRepositoriesHttpTrigger")]
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = "core/repositories")]HttpRequest req, ILogger log)
{
var result = await Factory.Create<IGitHubRepositoriesFunction>(log)
.InvokeAsync<HttpRequest, object>(req)
.ConfigureAwait(false);
return new OkObjectResult(result);
}
}
When set up like this, testing is quite straight. The only drawback is, that it is not possible to set up different container per function.
public static class CoreGitHubRepositoriesHttpTrigger
{
[FunctionName("CoreGitHubRepositoriesHttpTrigger")]
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = "core/repositories")]HttpRequest req, ILogger log,
IFunctionFactory factory)
{
var result = await factory.Create<IGitHubRepositoriesFunction>(log)
.InvokeAsync<HttpRequest, object>(req)
.ConfigureAwait(false);
return new OkObjectResult(result);
}
}
Hi Holger. When I run code from first example I have exception that ILogger is not registered so can’t be injected. Is there missing something?
Hi Krzysztof,
sorry for late answer. Can you please add some details?
->Which version do you use?
->Could you share some code?
Best
Holger
Hi Holger; any progress on the hybrid approach to using the FunctionFactory?
Will this work with Azure Durable functions too ?
Hi Muhammad,
actually good question, I didn’t give it a try. As durable functions is “just” orchestration, Azure functions should not be affected at all.
Would be interesting to see, if the InjectAttribute can be directly used also in the orchestration functions, actually that should just work out.
I will try it out and let you know.
Nice approach. Of the various ways I’ve seen folks attempting to solve Azure Functions, DI this one feels the most polished. Thanks!
Many thanks, Troy!