Date:

Share:

How To Setup Dependency Injection With Azure Functions ⚡

Related Articles

January 6, 20225 Reading minutes

Dependency injection (DI) is a technique for achieving a reversal of control (also known as IoC) between departments and their dependence.

Azure Functions supports dependent injection molding.

With DI, you can separate responsibilities into different departments and inject them into your primary function department. DI helps in loosely linked code and improves maintenance, testing and reuse.

If you’re new to building Azure Functions, check out my Azure Functions ⚡ for the .NET Developer post to get started quickly. I will continue to use the sample function I used there and expand it to support dependency injection.

Setting up a dependency injection

We wrote Azure Functions previous logs for the console. Suppose we want to perform a business-related processing every time we receive a message in line.

Let’s add a new grade, MessageProcessor, To process this message. He implements the IMessageProcessor Interface, where there is the Process A function that receives the string message and does a certain processing.

The application is currently checking to see if the message contains an ‘exception’ and throws an exception code. This may make testing and business processes more valuable and relevant in your real-world applications.

public interface IMessageProcessor

    void Process(string message);


public class MessageProcessor : IMessageProcessor

    public void Process(string message)
    
        if (message.Contains("exception")) 
                throw new Exception("Exception found in message");
    

The default Function class created by the Visual Studio template (in the previous example) is a static method in a static class.

To support injection injection through the constructor, we need the class to be operated with a constructor. So let’s update the Function1 From the previous example being non-static. Let’s also update the class name to be ProcessWeatherDataFunction.

public class ProcessWeatherDataFunction

    private readonly IMessageProcessor messageProcessor;

    public ProcessWeatherDataFunction(IMessageProcessor messageProcessor)
    
        this.messageProcessor = messageProcessor;
    

    [FunctionName("ProcessWeatherData")]
    public void Run(
      [QueueTrigger("add-weatherdata", Connection = "WeatherDataQueue")]string myQueueItem,
      ILogger log)
    
        messageProcessor.Process(myQueueItem);
        log.LogInformation($"C# Queue trigger function processed: myQueueItem");
    

The Function department, in addition to registration, also processes the message using the MessageProcessor status.

Now that we have the Azure function using an injected dependency, we need to ensure that instance of MessageProcessor Injected through the constructor when the runtime of Azure Functions performs the function.

Registration services

If you know Inject dependency on ASP NET Core applications, We use Startup.cs Department to register services for ServiceCollection.

Similarly, in Azure Functions, we can create a Startup.cs Class to record the dependency. The class name is intended solely for consistency and can be different.

To set up the Startup class, we need to make sure,

The class below is a sample startup class that also registers the IMessageProcessor Into the ServiceCollection used for functions.

[assembly: FunctionsStartup(typeof(WeatherDataIngestor.Startup))]
namespace WeatherDataIngestor

    public class Startup : FunctionsStartup
    
        public override void Configure(IFunctionsHostBuilder builder)
        
            builder.Services.AddTransient<IMessageProcessor, MessageProcessor>();
        
    

Launch the application and release a message to Azure Queue to make sure it is collected and processed properly.

God Startup The class operates as soon as the Azure Functions host is activated and builds the ServiceCollection which is used to resolve dependencies whenever a new message is processed.

Set up registration

God Run Method in our Azure function, ProcessWeatherDataFunction there is ILogger Gets injection by run time. This is done automatically and does not use the ServiceCollection we set up in Startup.

However, we can infiltrate an ILogger instance through the Builder using the ServiceCollection we defined earlier. To do this, we need to move ILogger to Builder.

public ProcessWeatherDataFunction(
    IMessageProcessor messageProcessor, 
    ILogger<ProcessWeatherDataFunction> log)

    this.messageProcessor = messageProcessor;
    this.log = log;

The code above injects in the instance of the ILogger<ProcessWeatherDataFunction> Where the generic class type represents the category name used when registering.

To register services related to registration, in Startup Class, run the AddLogging Method as shown below.

public override void Configure(IFunctionsHostBuilder builder)

    builder.Services.AddTransient<IMessageProcessor, MessageProcessor>();
    builder.Services.AddLogging();

Service life

Depending on the lifespan used to register the services, each execution of a function gets identical or different instances of dependency.

as Mentioned hereAzure Functions like ASP NET support three service lives

  • Transient → A new show is created whenever a show is requested
  • Scoped → A new instance is created once for each function performed.
  • Singleton ← A new instance created at the boot of the host and reused for the entire execution of the functions.

To understand this through an example, let’s create three separate classes for use with different life periods. The following is an example of a transient life span.

It has Write A defined method, which simply takes a logger instance. A random guide is defined in the classroom when it is constructed. This is documented when the writing method is turned on.

The value of the guide will help us determine if it is the same instance or new instance that enters it when an instance of that class is requested.

public interface ITransientService

    void Write(string message);


public class TransientService : ITransientService

    private readonly ILogger<TransientService> logger;
    public TransientService(ILogger<TransientService> logger)
    
        Random = Guid.NewGuid().ToString();
        this.logger = logger;
    
    public string Random  get; 
    public void Write(string message)
    
        logger.LogInformation("Transient - message, Random", message, Random);
    

Similar to the above, I also added the IScopedService and ISingletonService Accompanying interface and implementations. its Very similar to the above Except for a change in the registered message.

Update MessageProcessor Grade and use Write Method to enter the console. As shown below, the process method calls the writing method of each specific interface for life. It specifies the message as a ‘message processor’ to indicate the source class from which it is practicing.

public MessageProcessor(
    ITransientService transientService, 
    ISingletonService singletonService,
    IScopedService scopedService,
    IOptions<MyConfigOptions> configOptions)

    this.transientService = transientService;
    this.singletonService = singletonService;
    this.scopedService = scopedService;
    this.configOptions = configOptions;

public void Process(string message)

    if (message.Contains("exception")) 
        throw new Exception("Exception found in message");

    transientService.Write("Message Processor");
    scopedService.Write("Message Processor");
    singletonService.Write("Message Processor");

To fully understand Lifetimes, we need more dependencies that use the same services. This will help us understand how new dependencies are created when multiple instances of dependency are requested from the DI container. Let’s add AnotherDependency To simulate this.

public class AnotherDependency : IAnotherDependency

    private readonly ITransientService transientService;
    private readonly ISingletonService singletonService;
    private readonly IScopedService scopedService;

    public AnotherDependency(
      ITransientService transientService,
      ISingletonService singletonService,
      IScopedService scopedService)
    
        this.transientService = transientService;
        this.singletonService = singletonService;
        this.scopedService = scopedService;
    

    public void Process(string message)
    
        transientService.Write("Another Dependency");
        scopedService.Write("Another Dependency");
        singletonService.Write("Another Dependency");
    

Update the Function class to take the AnotherDependency In the constructor.

public ProcessWeatherDataFunction(
    IMessageProcessor messageProcessor, 
    IAnotherDependency anotherDependency,
    ILogger<ProcessWeatherDataFunction> log)
  ... 

[FunctionName("ProcessWeatherData")]
public void Run(
    [QueueTrigger("add-weatherdata", Connection = "WeatherDataQueue")]string myQueueItem)

    messageProcessor.Process(myQueueItem);
    anotherDependency.Process(myQueueItem);

    log.LogInformation($"C# Queue trigger function processed: myQueueItem");

With all the new dependencies connected, let’s update Startup Grade to connect the new interfaces. Use the appropriate expansion methods – AddTransient, AddScoped, AddSingleton – To list the new dependencies as shown below.

builder.Services.AddTransient<ITransientService, TransientService>();
builder.Services.AddScoped<IScopedService, ScopedService>();
builder.Services.AddSingleton<ISingletonService, SingletonService>();
builder.Services.AddTransient<IAnotherDependency, AnotherDependency>();

Launch both applications. When the Azure Function host (the terminal application running on the local computer) is started, it will start the Startup Grade and write down all the dependencies. The methods in Startup The class is called only once.

When a new message is emitted in the queue, and an instance of the Azure function is created all relevant dependencies are injected inside. Below is a sample log output for two newly processed messages.


The Transient Scoped service receives a different GUID for each case. The GUID is the same as scoped in single message processing, so both MessageProcessor and AnotherDependency Get the same show. The instance is different for another message because it receives a separate instance. For Singleton, the GUID is the same for all messages processed by the same host. In this case, our terminal app is the host.

App settings

By default, the template for Azure Functions is not supported appsettings.json Files. However, you can update the configuration to support configuration files.

To list different configuration sources, we can bypass ConfigureAppConfiguration M e FunctionsStartup That we inherited from us Startup status.

public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder)

    base.ConfigureAppConfiguration(builder);

    var context = builder.GetContext();

    builder.ConfigurationBuilder
        .AddJsonFile(
            Path.Combine(context.ApplicationRootPath, "appsettings.json"),
            optional: true, reloadOnChange: false)
        .AddJsonFile(
            Path.Combine(context.ApplicationRootPath, $"appsettings.context.EnvironmentName.json"),
            optional: true, reloadOnChange: false)
        .AddEnvironmentVariables();

The workaround defines ConfigurationBuilder to use appssettings.json And an environment-specific application settings file. You can also update it to use other configuration source providers available in .NET. If you want to learn more about configuration, check out the video below.

Once set up, we can also use Pattern options Within our Azure functions. We can create a custom class to represent the configuration data, and dependency infuse into our function and dependency classes for a configuration as below.


  "MyConfig": 
    "Url": "http://testapi.com",
    "Secret": ""
  

Code below at Startup The class, connects the class MyConfigOptions Into the collection of services and makes it available for dependency injection as shown below in MessageProcessor as IOptions<MyConfigOptions>

public class MyConfigOptions

    public string Url  get; set; 
    public string Secret  get; set; 


...

builder.Services.AddOptions<MyConfigOptions>()
    .Configure<IConfiguration>((settings, configuration) =>
    
        configuration.GetSection("MyConfig").Bind(settings);
    );

...

public MessageProcessor(IOptions<MyConfigOptions> configOptions) ...

Azure Functions by default also supports Managing Secrets If you want to store sensitive information in local development environment machines. You can right-click (in Visual Studio) on the project, enable User Secrets Management in the menu, and configure the sensitive configuration in the secrets.json file.

I hope this helps you understand how dependency injection can be used when building Azure functions and different use cases.

Buy me coffee

Source

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Popular Articles