I have a couple of public projects (this blog, and the code camp website) that both use Azure Blob storage to store images for the sites. I’ve felt guilty that I’ve copy/pasted the code between them for a while. I decided to fix it.

While the code was pretty simple, I did need to change it a bit to generalize it. But then I wondered, would it be useful for anyone else? I decided to just make it a Nuget package. But should I have? We’ll get to that in a minute, let’s talk about how I generalized it first.

Generalizing the Code

Originally, the code was a simple class that did all the work in a single method. But to generalize it I needed to move code that was specific to my implementations out. First to go was the IConfiguration usage. In the old code, I was simply importing configuration (as AppSettings):

public ImageStorageService(IOptions<AppSettings> settings, 
  ILogger<ImageStorageService> logger)	
{	
  _settings = settings;	
  _logger = logger;	
}

Then using the app settings to create the credential into Azure Blob Storage:

var creds = new StorageSharedKeyCredential(_settings.Value.BlobStorage.Account,   
                                           _settings.Value.BlobStorage.Key);	
var client = new BlobServiceClient(new Uri(_settings.Value.BlobStorage.StorageUrl), creds);

I didn’t like this. To generalize this, I needed to derive the credentials to store the extra piece of information (the StorageUrl):

public class AzureImageStorageCredentials : StorageSharedKeyCredential
{
  public AzureImageStorageCredentials(string accountName, string accountKey, string url)
    :base(accountName, accountKey)
  {
    AccountUrl = url;
  }

  public string AccountUrl { get; private set; }

}

This way we could just inject it into the service as needed:

public AzureImageStorageService(ILogger<AzureImageStorageService> logger, 
  AzureImageStorageServiceClient client)
{
  _logger = logger;
  _client = client;
}

FInally, to clean up the code, I hid the service registration in an extension method:

public static IServiceCollection AddAzureImageStorageService(this IServiceCollection coll, 
  [NotNull] string azureAccountName,
  [NotNull] string azureAccountKey,
  [NotNull] string azureStorageUrl)
{

  coll.AddScoped(coll => new AzureImageStorageCredentials(azureAccountName, 
                                                          azureAccountKey, 
                                                          azureStorageUrl));
  coll.AddScoped<AzureImageStorageServiceClient>();
  coll.AddTransient<IAzureImageStorageService, AzureImageStorageService>();

  return coll;
}

This seemed all good, but now that i’ve generalized it with the derived credential, the dummy credentials that I used during development simply broke. I needed to verify that the key was Base64 encoded:

public static IServiceCollection AddAzureImageStorageService(this IServiceCollection coll, 
  [NotNull] string azureAccountName,
  [NotNull] string azureAccountKey,
  [NotNull] string azureStorageUrl)
{
  // Test for valid Base64 Key
  Span<byte> buffer = new Span<byte>(new byte[azureAccountKey.Length]);
  if (!Convert.TryFromBase64String(azureAccountKey, buffer, out int bytesParsed))
  {
    throw new InvalidOperationException("Azure Account Key must be a Base64 Encoded String. If running in development, mock the IImageStorageService for development instead.");
  }

  coll.AddScoped(coll => new AzureImageStorageCredentials(azureAccountName, azureAccountKey, azureStorageUrl));
  coll.AddScoped<AzureImageStorageServiceClient>();
  coll.AddTransient<IAzureImageStorageService, AzureImageStorageService>();

  return coll;
}

That’s all and well…but…

#c# #nuget #packages #dependency injection

Should You Always Create a Nuget Package? Asking for a Friend...
1.15 GEEK