dependency-injectionazure-functionsazure-blob-storageconfigblobstorage

How to use Dependency Injection in Azure Function .NET 6?


I'm trying to upload files to an Azure Blob Storage. I want a C# Azure Function to be triggered by HTTP and then execute method UploadAsync().

In order to connect to my storage account I need to create a BlobContainerClient and therefore I need to connect through a connection string. To not expose my connection string and to make my functionality mockable for testing, I want to setup my connection up with dependency injection.

enter image description here

However, I end up with the same error each time...

public interface IMyBlobService
{
    Task<bool> UploadImageAsync(Stream stream, string blobName);
}

public class MyBlobService : IMyBlobService
{
    private readonly BlobContainerClient _containerClient;

    public MyBlobService(BlobContainerClient blobContainerClient)
    {
        _containerClient = blobContainerClient;
    }

    public async Task<bool> UploadImageAsync(Stream stream, string blobName)
    {
        BlobClient blobClient = _containerClient.GetBlobClient(blobName);
        var result = await blobClient.UploadAsync(stream);  
        // Do something with the result
    }

    public class MyBlobHandler
    {
        private readonly IMyBlobService _myBlobService;

        public MyBlobHandler(IMyBlobService myBlobService)
        {
            _myBlobService = myBlobService;
        }

        public async Task<bool> Handle(Stream stream, string blobName)
        {
            return await _myBlobService.UploadImageAsync(stream, blobName);
        }
    }

Startup.cs

    internal class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            var configuration = BuildConfiguration(builder.GetContext().ApplicationRootPath);
            builder.Services.AddAppConfiguration(configuration);

            builder.Services.AddSingleton(x =>
            {
                var connectionString = builder.GetContext().Configuration.GetConnectionString("Storage:ConnectionString");
                return new BlobContainerClient(connectionString, "pictures");
            });

            builder.Services.AddScoped<IMyBlobService, MyBlobService>();
        }

        private IConfiguration BuildConfiguration(string applicationRootPath)
        {
            var config =
                new ConfigurationBuilder()
                    .SetBasePath(applicationRootPath)
                    .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true)
                    .AddJsonFile("settings.json", optional: true, reloadOnChange: true)
                    .AddEnvironmentVariables()
                    .Build();
            return config;
        }
    }
    internal static class ConfigurationServiceCollectionExtensions
    {
        public static IServiceCollection AddAppConfiguration(this IServiceCollection services, IConfiguration config)
        {
            services.Configure<StorageSettings>(config.GetSection(nameof(StorageSettings)));
            return services;
        }
    }

I figured out (with commenting other stuff out) that:

var configuration = buildConfiguration(builder.GetContext().ApplicationRootPath);

is causing the error: Value cannot be null. (Parameter 'provider') How do I fix this?

appsettings.Development.json

{
  "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet"
  },
  "StorageSettings": {
    "ConnectionString": "DefaultEndpointsProtocol... etc etc"
  }
}

I cannot figure out a way to make this work properly.

It seems like dependency injection works differently for Azure Functions?

Documentation about DI like (e.g.): https://damienaicheh.github.io/azure/azure-functions/dotnet/2022/05/10/use-settings-json-azure-function-en.html got me started but I have spent more than 6 hours trying now.

Can someone with more expertise share some thoughts?

UPDATE: My system doesnt know my settings file and therefor throws a nullexception. How can I add this to iniate my BlobContainerClient properly?


Solution

  • Try this configuration, the BlobContainerClient should be added as a singleton, you didn't added the IMyBlobService in the DI container

    public class Startup : FunctionsStartup
    {
        public override void Configure(IFunctionsHostBuilder builder)
        {
            builder.Services.AddSingleton(x =>
            {
                var connectionString = builder.GetContext().Configuration.GetConnectionString("MyBlobStorage");
                return new BlobContainerClient(connectionString, "mycontainer");
            });
    
            builder.Services.AddScoped<IMyBlobService, MyBlobService>();
        }
    }