Azure Functions V2 supports ASP.NET Core like dependency injection. It is specially good if we write functions that are wrappers for shared libraries and components we are also using in web and other applications of our solution. This blog post shows how to use dependency injection in Azure Functions.
To get started with code check out Azure Functions with DI Github repository by @MikaBerglund. It’s simple and minimal Visual Studio solution without any overhead.
Preparing the code
Let’s start with simple classes I previously used in blog post ASP.NET Core: Inject all instances of interface. There’s interface for alerts service and we use here service implementation that sends alert to e-mail. Service uses customer class to get customer e-mail.
public class Customer
{
public string Name { get; set; }
public string Email { get; set; }
public string Mobile { get; set; }
}
public interface IAlertService
{
void SendAlert(Customer c, string title, string body);
}
public class EmailAlertService : IAlertService
{
public void SendAlert(Customer c, string title, string body)
{
if (string.IsNullOrEmpty(c.Email))
{
return;
}
Debug.WriteLine($"Sending e-mail to {c.Email} for {c.Name}");
}
}
Azure Functions startup class
There’s startup class supported by Azure Functions V2. It’s similar to one we know from ASP.NET Core but there are differences. To make dependency injection work we must add reference Microsoft.Azure.Functions.Extensions to our Azure Functions project.
After namespaces section in Startup class we have to define FunctionsStartup attribute for assembly.
[assembly: FunctionsStartup(typeof(AzureFunctionsDI.Startup))]
Our functions startup class extends FunctionsStartup class.
public abstract class FunctionsStartup : IWebJobsStartup
{
protected FunctionsStartup();
public abstract void Configure(IFunctionsHostBuilder builder);
public void Configure(IWebJobsBuilder builder);
}
We have to define at least Configure() method that is called with functions host builder.
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddScoped<IAlertService, EmailAlertService>();
}
}
We can add also other service registrations in this method. It seems like functions are on the half-way to Startup class we know from ASP.NET Core.
Injecting instances to Azure Functions
With Azure Functions V2 we can use dependency injection similar to ASP.NET Core. Minimal default HTTP-triggered function is shown here.
public static class Function1
{
[FunctionName("Function1")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
return await Task.FromResult(new OkObjectResult("OK"));
}
}
To make it work with dependency injection we have to move from static scope to instance scope and use constructor injection like shown here.
public class Function1
{
private readonly IAlertService _alertService;
public Function1(IAlertService alertService)
{
_alertService = alertService;
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var customer = new Customer { Name = "John", Email = "john@example.com" };
var title = "Emergy alert";
var body = "The roof is on fire!";
_alertService.SendAlert(customer, title, body);
return await Task.FromResult(new OkObjectResult("OK"));
}
}
Running the functions project and setting break point to line in constructor where IAlertService is assigned allows us see if instance is injected to functions class.
No errors, function is running and we have instance of IAlertService injected to our function.
Injecting all instances of interface
Similar to ASP.NET Core we can also inject all instances of interface to function. I added new implementation of IAlertService to project.
public class SmsAlertService : IAlertService
{
public void SendAlert(Customer c, string title, string body)
{
if (string.IsNullOrEmpty(c.Mobile))
{
return;
}
Debug.WriteLine($"Sending SMS to {c.Mobile} for {c.Name}");
}
}
Then I registered it to dependency injection in functions Startup class.
public class Startup : FunctionsStartup
{
public override void Configure(IFunctionsHostBuilder builder)
{
builder.Services.AddScoped<IAlertService, EmailAlertService>();
builder.Services.AddScoped<IAlertService, SmsAlertService>();
}
}
Here is how my function looks with injection of all IAlertService instances.
public class Function1
{
private readonly IEnumerable<IAlertService> _alertServices;
public Function1(IEnumerable<IAlertService> alertServices)
{
_alertServices = alertServices;
}
[FunctionName("Function1")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
var customer = new Customer { Name = "John", Email = "john@example.com" };
var title = "Emergy alert";
var body = "The roof is on fire!";
foreach (var alertService in _alertServices)
{
alertService.SendAlert(customer, title, body);
}
return await Task.FromResult(new OkObjectResult("OK"));
}
}
Running the function with breakpoint same way as before shows that both registered instances of IAlertService class are injected to function.
Dependency injection with Azure Functions V2 works like dependency injection in ASP.NET Core.
Wrapping up
With Azure Functions V2 we can use same dependency injection mechanism as in ASP.NET Core. There are differences like no support for direct injection through function arguments (ASP.NET Core supports controller action injection) but the basics are all the same. Support for dependency injection makes it easier to re-use same components and services we are using in web applications through dependency injection.
View Comments (7)
What if you want the service to make use of ILogger?
Just declare the ILogger in the service's constructor and DI will do the rest?
That works in the world of APIs/Controllers, but doesn't work for Azure Functions.
There are things that ASP.NET Core adds automatically to DI. If you are setting up DI stuff for something else (like functions or console apps) then you have to register loggers by yourself.
Hi Gunnar,
How do you Unit Test this type of App, especially the Run method but not calling it directly but via the HostBuilder and starting the app in memory?
Hi Gunnar,
Thanks for detailed explanation on implementing DI to Azure Function.
I can get it to work on my local machine, but when deployed to Azure environment, it seems to fail. Have you been able to deploy and execute in Azure sever.
Thanks in advance.
Ajay
Yes, it works on Azure too. If you run into issues then open function in Azure portal and open log window. Then run function and see the output of it. If you have Application Insights enabled for function the errors go there with details.
Thanks Gunnar.
From the Application Insights, i was able to locate my mistake. Incidentally, didn't realise the level of information that is available.
From the way i had configured my code, my SQL server connection string should have been recorded in 'Application Settings' rather than 'Connection strings' in Azure Function settings.
How do you resolve a dependency at runtime? I know about constructor injection, but what if the dependency is not always required and it too requires constructor injection of dependencies? Some say that this approach is an anti-pattern, but if the constructor of the required object has injection parameters I don't see how you can get around it.