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.
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