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.
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);
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?
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>();
}
}