39

I have some class libraries that I use in my ASP.NET Web API app that handle all my backend stuff e.g. CRUD operations to multiple databases like Azure SQL Database, Cosmos DB, etc.

I don't want to re-invent the wheel and able to use them in a new Azure Functions that I'm creating in Visual Studio 2017. All my repository methods use an interface. So, how will I implement dependency injection in my new Azure function?

I'm not seeing any support for DI but I'm a bit confused. It appears Azure Functions are based on the same SDK as WebJobs and I think last year Microsoft had started supporting DI in WebJobs - I know for sure because I implemented it using Ninject.

Is there way around this so that I can use my existing libraries in my new Azure Functions project?

Anass Kartit
  • 2,017
  • 15
  • 22
Sam
  • 26,817
  • 58
  • 206
  • 383
  • Possible duplicate of [Autofac Dependency Injection in Azure Function](https://stackoverflow.com/questions/44397407/autofac-dependency-injection-in-azure-function) – Mikhail Shilkov Aug 28 '17 at 07:09
  • 1
    DI came to WebJobs more than two years ago. I'm kind of amazed it still isn't available in Functions. (Then again, I'm also perplexed that Functions are `static`-based.) This answer illustrates the simplicity of WebJobs DI (`BuildServiceProvider` would be the "myContainer" equivalent for Core DI) -- https://stackoverflow.com/a/30454556/152997 – McGuireV10 Feb 01 '18 at 10:59
  • Vote for it here: https://feedback.azure.com/forums/355860-azure-functions/suggestions/15642447-enable-dependency-injection-in-c-functions – Aaron Hudon Feb 05 '19 at 04:28
  • See: [Create .NET 6 Azure Function App with Dependency Injection and the Key Vault as Configuration Provider](https://www.forestbrook.net/docs/azure/functionwithkeyvaultanddi.html) – Marcel Wolterbeek Jan 20 '22 at 16:59

11 Answers11

29

I see these two techniques in addition to the service locator (anti)pattern. I asked the Azure Functions team for their comments as well.

https://blog.wille-zone.de/post/azure-functions-dependency-injection/

https://blog.wille-zone.de/post/azure-functions-proper-dependency-injection/

Sam
  • 26,817
  • 58
  • 206
  • 383
  • 1
    Here's my two cents. The links in this answer is using the method injection. There is another approach - property injection that uses a `static` property. I wrote a blog post about this, for V1, https://devkimchi.com/2017/11/16/azure-functions-with-ioc-container/ and, for V2, https://blog.mexia.com.au/dependency-injections-on-azure-functions-v2 – justinyoo Apr 14 '18 at 10:33
  • 4
    DI is now supported out of the box by Azure Functions v2: https://learn.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection – quervernetzt Nov 18 '19 at 09:04
8

There is an open feature request on the GitHub pages for Azure Functions concerning this matter.

However, the way I'm approaching this is using some kind of 'wrapper' entry point, resolve this using the service locator and and start the function from there.

This looks a bit like this (simplified)

var builder = new ContainerBuilder();
//register my types

var container = builder.Build();

using(var scope = container.BeginLifetimeScope())
{
  var functionLogic = scope.Resolve<IMyFunctionLogic>();

  functionLogic.Execute();
}

This is a bit hacky of course, but it's the best there is until there is at the moment (to my knowledge).

Jan_V
  • 4,244
  • 1
  • 40
  • 64
5

I've seen the willie-zone blog mentioned a lot when it comes to this topic, but you don't need to go that route to use DI with Azure functions.

If you are using Version2 you can make your Azure functions non-static. Then you can add a public constructor for injecting your dependencies. The next step is to add an IWebJobsStartup class. In your startup class you will be able to register your services like you would for any other .Net Core project.

I have a public repo that is using this approach here: https://github.com/jedi91/MovieSearch/tree/master/MovieSearch

Here is a direct link to the startup class: https://github.com/jedi91/MovieSearch/blob/master/MovieSearch/Startup.cs

And here is the function: https://github.com/jedi91/MovieSearch/blob/master/MovieSearch/Functions/Search.cs

Hope this approach helps. If you are wanting to keep your Azure Functions static then the willie-zone approach should work, but I really like this approach and it doesn't require any third party libraries.

One thing to note is the Directory.Build.target file. This file will copy your extensions over in the host file so that DI will work once the function is deployed to Azure. Running the function locally does not require this file.

  • Tried your approach. Looks like my Startup code isn't getting called, I did add the assembly attribute at the top. My function class can not be instantiated because of a missing dependency. This dependency should be setup in Startup.cs. Any pointers? – ngruson Mar 10 '19 at 14:44
  • Fixed after updating to Microsoft.NET.Sdk.Functions 1.0.26. – ngruson Mar 10 '19 at 15:48
  • Glad updating the SDK fixed your issue. I have had issues when using netcore 2.1 and above for the target version. At first I just used 2.0, but then learned that I just needed the latest version of the Webjobs extensions nuget package. I'm guessing that updating to the latest SDK brought in that nuget package as a dependency. – Michael Kovacevich Mar 11 '19 at 15:41
  • 1
    This functionality is now broken on the most recent version of Azure Functions. In order to get it working in Azure you need to specify the version to be 2.0.12342.0, other versions may work as well. There is currently an open issue on the GitHub repo to address the problem: https://github.com/Azure/azure-functions-host/issues/4203 – Michael Kovacevich Mar 26 '19 at 01:35
  • I had the same issue. Upgrading the Microsoft.NET.Sdk.Functions pacakge to 1.0.26 didn't help. I also had to upgrade the Microsoft.Azure.WebJobs NuGet package to the latest version (3.0.6 in that case). – Robin Güldenpfennig Apr 25 '19 at 07:17
5

Azure Functions Depdendency Injection was announced at MSBuild 2019. Here's an example on how to do it:

[assembly: FunctionsStartup(typeof(MyNamespace.Startup))]

namespace MyNamespace
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddHttpClient();
            builder.Services.AddSingleton((s) => {
                return new CosmosClient(Environment.GetEnvironmentVariable("COSMOSDB_CONNECTIONSTRING"));
            });
            builder.Services.AddSingleton<ILoggerProvider, MyLoggerProvider>();
        }
    }
}
mwilson
  • 12,295
  • 7
  • 55
  • 95
5

As stated above, it was just announced at Build 2019. It can now be setup almost exactly like you would in an ASP .Net Core app.

Microsoft Documentation

Short Blog I Wrote

3

Actually there is a much nicer and simpler way provided out of the box by Microsoft. It is a bit hard to find though. You simply create a start up class and add all required services here, and then you can use constructor injection like in regular web apps and web apis.

This is all you need to do.

First I create my start up class, I call mine Startup.cs to be consistent with Razor web apps, although this is for Azure Functions, but still it's the Microsoft way.

using System;
using com.paypal;
using dk.commentor.bl.command;
using dk.commentor.logger;
using dk.commentor.sl;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using org.openerp;

[assembly:Microsoft.Azure.WebJobs.Hosting.WebJobsStartup(typeof(dk.commentor.starterproject.api.Startup))]
namespace dk.commentor.starterproject.api
{

    public class Startup : IWebJobsStartup
    {

        public void Configure(IWebJobsBuilder builder)
        {
            builder.Services.AddSingleton<ILogger, CommentorLogger>();
            builder.Services.AddSingleton<IPaymentService, PayPalService>();
            builder.Services.AddSingleton<IOrderService, OpenERPService>();
            builder.Services.AddSingleton<ProcessOrderCommand>();
            Console.WriteLine("Host started!");
        }
    }
}

Next I change the method call in the function from static to non-static, and I add a constructor to the class (which is now also non-static). In this constructor I simply add the services I require as constructor parameters.

using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using dk.commentor.bl.command;

namespace dk.commentor.starterproject.api
{
    public class ProcessOrder
    {
        private ProcessOrderCommand processOrderCommand;

        public ProcessOrder(ProcessOrderCommand processOrderCommand) {
            this.processOrderCommand = processOrderCommand;
        }

        [FunctionName("ProcessOrder")]
        public async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req, ILogger log)
        {
            log.LogInformation("C# HTTP trigger ProcessOrder called!");
            log.LogInformation(System.Environment.StackTrace);

            string jsonRequestData = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic requestData = JsonConvert.DeserializeObject(jsonRequestData);

            if(requestData?.orderId != null)
                return (ActionResult)new OkObjectResult($"Processing order with id {requestData.orderId}");
            else
                return new BadRequestObjectResult("Please pass an orderId in the request body");
        }
    }
}

Hopes this helps.

Michael
  • 397
  • 2
  • 10
2

I would like to add my 2 cents to it. I used the technique that it's used by Host injecting ILogger. If you look at the Startup project I created GenericBindingProvider that implements IBindingProvider. Then for each type I want to be injected I register it as follow:

builder.Services.AddTransient<IWelcomeService, WelcomeService>();
builder.Services.AddSingleton<IBindingProvider, GenericBindingProvider<IWelcomeService>>();

The downside is that you need to register the type you want to be injected into the function twice.

Sample code:

Azure Functions V2 Dependency Injection sample

1

I have been using SimpleInjector perfectly fine in Azure Functions. Just create a class (let's call it IoCConfig) that has the registrations and make a static instance of that class in function class so that each instance will use the existing instance.

public interface IIoCConfig
{
    T GetInstance<T>() where T : class;
}

public class IoCConfig : IIoCConfig
{
    internal Container Container;

    public IoCConfig(ExecutionContext executionContext, ILogger logger)
    {
        var configurationRoot = new ConfigurationBuilder()
            .SetBasePath(executionContext.FunctionAppDirectory)
            .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
            .AddEnvironmentVariables()
            .Build();

        Container = new Container();
        Configure(configurationRoot, logger);
    }

    public IoCConfig(IConfigurationRoot configurationRoot, ILogger logger)
    {
        Container = new Container();
        Configure(configurationRoot, logger);
    }

    private void Configure(IConfigurationRoot configurationRoot, ILogger logger)
    {
        Container.RegisterInstance(typeof(IConfigurationRoot), configurationRoot);
        Container.Register<ISomeType, SomeType>();
    }

    public T GetInstance<T>() where T : class
    {
        return Container.GetInstance<T>();
    }
}

Then in root:

   public static class SomeFunction
{
    public static IIoCConfig IoCConfig;

    [FunctionName("SomeFunction")]
    public static async Task Run(
        [ServiceBusTrigger("some-topic", "%SUBSCRIPTION_NAME%", Connection = "AZURE_SERVICEBUS_CONNECTIONSTRING")]
        SomeEvent msg,
        ILogger log,
        ExecutionContext executionContext)
    {
        Ensure.That(msg).IsNotNull();

        if (IoCConfig == null)
        {
            IoCConfig = new IoCConfig(executionContext, log);
        }

        var someType = IoCConfig.GetInstance<ISomeType>();
        await someType.Handle(msg);
    }
}
tubakaya
  • 457
  • 4
  • 15
0

AzureFunctions.Autofac is very easy to use.

Just add a config file:

public class DIConfig
{
    public DIConfig(string functionName)
    {
        DependencyInjection.Initialize(builder =>
        {
            builder.RegisterType<Sample>().As<ISample>();
            ...
        }, functionName);
    }
}

Add the DependencyInjectionConfig attribute then inject:

[DependencyInjectionConfig(typeof(DIConfig))]
public class MyFunction
{
    [FunctionName("MyFunction")]
    public static HttpResponseMessage Run([HttpTrigger(AuthorizationLevel.Function, "get", Route = null)]HttpRequestMessage request, 
                                          TraceWriter log, 
                                          [Inject]ISample sample)
    {

https://github.com/introtocomputerscience/azure-function-autofac-dependency-injection

Nate
  • 35
  • 2
  • 1
    While this link may answer the question, it is better to include the essential parts of the answer here and provide the link for reference. Link-only answers can become invalid if the linked page changes. - [From Review](/review/low-quality-posts/22464876) – Ram Ghadiyaram Mar 14 '19 at 14:26
  • Thank you for the feedback. I have edited my post to include the essentials parts of the answer. – Nate Mar 15 '19 at 12:26
  • Using Autofac this way creates a separate container for every class that uses the DependencyInjectionConfig attribute as each class is static and has the attribute once for every class. This means that on startup every static class will run the same registration which is slow. Autofac makes Azure Function startup really slow. One way to use Autofac with only one container for your whole function app is to use one partial class that has every function in your entire project split into classes for organisation that are actually only one static class. Autofac for DI has serious issues scaling. – Aran Mulholland Jan 23 '20 at 09:47
  • When Autofac is used because you have seperate containers for every static class you can run into issues with socket exceptions as making each container share an IHttpClientFactory etc is really hacky. Choose Microsoft DI using version 2 functions or your app will suffer, badly. – Aran Mulholland Jan 23 '20 at 09:49
0

I think this is a better solution:

https://github.com/junalmeida/autofac-azurefunctions https://www.nuget.org/packages/Autofac.Extensions.DependencyInjection.AzureFunctions

Install the NuGet in your project and then make a Startup.cs and put this in it:

[assembly: FunctionsStartup(typeof(Startup))]

public class Startup
{
    public override void Configure(IFunctionsHostBuilder builder)
    {
        builder
            .UseAppSettings() // this is optional, this will bind IConfiguration in the container.
            .UseAutofacServiceProviderFactory(ConfigureContainer);
    }

    private void ConfigureContainer(ContainerBuilder builder)
    {
         // do DI registration against Autofac like normal! (builder is just the normal ContainerBuilder from Autofac)
    }
    ...

Then in your function code you can do normal constructor injection via DI:

public class Function1 : Disposable
{
    public Function1(IService1 service1, ILogger logger)
    {
        // logger and service1 injected via autofac like normal
        // ...
    }

    [FunctionName(nameof(Function1))]
    public async Task Run([QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")]string myQueueItem)
    {
        //...
Drew Delano
  • 1,421
  • 16
  • 21
0

Support for Dependency injection begins with Azure Functions 2.x which means Dependency Injection in Azure function can now leverage .NET Core Dependency Injection features.

Before you can use dependency injection, you must install the following NuGet packages:

  • Microsoft.Azure.Functions.Extensions
  • Microsoft.NET.Sdk.Functions

Having Dependency Injection eases things like DBContext, Http client usage (Httpclienfactory), Iloggerfactory, cache support etc.

Firstly, update the Startup class as shown below

namespace DemoApp
{
    public class Startup: FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddScoped<IHelloWorld, HelloWorld>();

            // Registering Serilog provider
            var logger = new LoggerConfiguration()
                .WriteTo.Console()
                .CreateLogger();
            builder.Services.AddLogging(lb => lb.AddSerilog(logger));
            //Reading configuration section can be added here etc.
        }
    }
}

Secondly, Removal of Static keyword in Function class and method level

public class DemoFunction
{
    private readonly IHelloWorld _helloWorld;
    public DemoFunction(IHelloWorld helloWorld)
    {
        _helloWorld = helloWorld;
    }

    [FunctionName("HttpDemoFunction")]
    public async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a request.");
    }

If we look into above e.g. IHelloWorld is injected using .NET Core DI

**Note:**In-spite of having latest version of Azure function v3 for Dependency Injection to enable few steps are manual as shown above

Sample code on github can be found here

Raju Rh
  • 127
  • 1
  • 9