Adventures with Azure Functions: Dependency Injection

In this article we will look at how to do Dependency Injection with Azure Functions as described in the article at
https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection.

The code for this article can be found at
https://github.com/mattruma/SampleDependencyInjectionFunctionApp.

To make the most of this article, it will be helpful to be familiar with:

This application supports three different ways to add a ToDo item using a specified Data Store.

The data store could write to Azure Table Storage, Azure Cosmos DB, Azure SQL or any data storage for that matter.

For this article, the data stores do not write to any data storage.

Let’s look at the code.

Open the solution in Visual Studio.

We have two classes, ToDoSample1DataStore and ToDoSample2DataStore, each implementing the IToDoDataStore interface.

The IToDoDataStore has a single method, Add, which accepts a ToDo object and an ILogger and return a ToDo object.

Each of the implementations of our Add methods, in the ToDoSample1DataStore and ToDoSample2DataStore, will output the data store that handled the add and apply the name of the data store to the ToDo object.

using Microsoft.Extensions.Logging;

namespace SampleDependencyInjectionFunctionApp.Data
{
    public class ToDoSample1DataStore : IToDoDataStore
    {
        public ToDo Add(
            ToDo toDo,
            ILogger logger)
        {
            logger.LogInformation($"Adding ToDo using {nameof(ToDoSample1DataStore)}...");
            logger.LogInformation("ToDo added.");

            toDo.DataStore = nameof(ToDoSample1DataStore);

            return toDo;
        }
    }
}

Let’s move on to the functions.

We have three Azure Functions, wonderfully named, Function1, Function2 and Function3.

Each function will call the Add method on the appropriate data store.

  • Function1 makes use of the ToDoSample1DataStore
  • Function2 makes use of the ToDoSample2DataStore
  • Function3 depends on dependency injection which which will determine if ToDoSample1DataStore or ToDoSample2DataStore will be used.

Function1 is the standard boiler plate HttpTrigger function, except for one little change, instead of taking an HttpRequest object, it will take a ToDo object.

The function automatically converts the HttpRequst to a ToDo object for us, pretty sweet!

Function1 creates a concrete instance of the ToDoSample1DataStore and adds the passed ToDo object.

using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using SampleDependencyInjectionFunctionApp.Data;

namespace SampleDependencyInjectionFunctionApp
{
    public static class Function1
    {
        [FunctionName("Function1")]
        public static IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] ToDo toDo,
            ILogger logger)
        {
            logger.LogInformation("Function1 function processed a request.");

            if (toDo == null)
            {
                return new BadRequestResult();
            }

            var toDoDataStore =
                new ToDoSample1DataStore();

            toDo =
                toDoDataStore.Add(
                    toDo,
                    logger);

            return new OkObjectResult(toDo);
        }
    }
}

If we call Function1 from PostMan we can see our ToDo object’s DataStore property is stamped with the value ToDoSample1DataStore, letting us know which IToDoDataStore‘s Add method was called.

Function2 is almost exactly like Function1, except it creates an instance of ToDoSample2DataStore to handle adding ToDo objects, which will stamp the ToDo object’s DataStore property with a value of ToDoSample2DataStore.`

Now for the real fun! Let’s look at Function3!

using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using SampleDependencyInjectionFunctionApp.Data;

namespace SampleDependencyInjectionFunctionApp
{
    public class Function3
    {
        private readonly IToDoDataStore _toDoDataStore;
        public Function3(
            IToDoDataStore toDoDataStore)
        {
            _toDoDataStore = toDoDataStore;
        }

        [FunctionName("Function3")]
        public IActionResult Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] ToDo toDo,
            ILogger logger)
        {
            logger.LogInformation("Function3 function processed a request.");

            if (toDo == null)
            {
                return new BadRequestResult();
            }

            toDo =
                _toDoDataStore.Add(
                    toDo,
                    logger);

            return new OkObjectResult(toDo);
        }
    }
}

Some things to point out in Function3 that are different than Function1 and Function2:

  • The class is no longer static.
  • The Run method is no longer static.
  • The class has a bona fide constructor.

This looks a lot like what we see in a Controller for a .NET Web App or Web Api.

So how does Function3 know where to get an IToDoDataStore from?

Just like in a .NET Web App or Web Api it all happens in Startup.cs.

using Microsoft.Azure.Functions.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
using SampleDependencyInjectionFunctionApp.Data;

// https://docs.microsoft.com/en-us/azure/azure-functions/functions-dotnet-dependency-injection

[assembly: FunctionsStartup(typeof(SampleDependencyInjectionFunctionApp.Startup))]
namespace SampleDependencyInjectionFunctionApp
{
    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddTransient<IToDoDataStore, ToDoSample1DataStore>();

            // TODO: Uncomment the below, and comment the above to use ToDoSample2DataStore as the default implement of IToDoDataStore
            // builder.Services.AddTransient<IToDoDataStore, ToDoSample2DataStore>();
        }
    }
}

Note: I apologize if the “<” signs are not showing correctly, it is something with EnlighterJS.

If we want a ToDoSample1DataStore to be provided for each IToDoDataStore we simply need to add it to the services, using the AddTransient method.

For more information service lifetimes, Transient, Scoped and Singleton, see
https://docs.microsoft.com/en-us/aspnet/core/fundamentals/dependency-injection?view=aspnetcore-2.2#service-lifetimes.

Let’s call Function3 from PostMan.

You can see that the ToDo object’s DataStore property was stamped with the ToDoSample1DataStore data store.

Let’s make it use ToDoSample2DataStore.

Back in the Startup.cs file, comment the line using ToDoSample1DataStore and uncomment the line using ToDoSample2DataStore.

One more time, let’s call Function3 from PostMan.

Note the DataStore property of the ToDo object contains the value ToDoSample2DataStore.

While a simplistic example of Azure Functions and Dependency Injection, it should be enough to get you started!

Leave a Reply

Your email address will not be published.