Recently, Microsoft unveiled a new Azure .NET SDK, it's not a simple version upgrade, but a major change in the programming experience. While it may take a while to adapt to the new SDK, it is worth learning. The new SDK is more powerful, more flexible and easier to use.

Current Status of Azure .NET SDKs


Currently, the official packages for .NET SDKs for Azure are confusing and unfriendly to new users, and if you're new to Azure, I'll give you a summary of what's going on:

Ancient Days: WindowsAzure.*

Microsoft cloud was initially named "Windows Azure", and the first version of the Azure SDKs are also named with a prefix "WindowsAzure". These packages stopped updating one year ago, and they were replaced by the next version of SDK. So, please DO NOT use the WindowsAzure.* packages for your new project.

Active Troops: Microsoft.Azure.*

After rebranding to Microsoft Azure, the SDKs are also renamed with Microsoft.Azure.* prefix. New features are delivered through these packages, and they are also split into more individual small packages comparing to the previous version. My blog is also using this version of the Azure SDK to connect with Azure. It covers the most complete feature set of Azure today, you can still use this version of Azure SDK to develop your current App.

The Future is Now: Azure.*

Although Microsoft.Azure* is good enough for daily usage, it still has its problems. For instance, the API design wasn't simple enough. Code today looks like this:

AzureServiceTokenProvider tokenProvider = new AzureServiceTokenProvider();
string token = tokenProvider.GetAccessTokenAsync("https://storage.azure.com/").GetAwaiter().GetResult();
TokenCredential tokenCredential = new TokenCredential(token);
StorageCredentials storageCredentials = new StorageCredentials(tokenCredential);
CloudBlobClient blobClient = new CloudBlobClient(new Uri(Configuration["BlobServiceUri"]), storageCredentials);
services.AddSingleton(blobClient);

Can be replaced by very straight forward code in the new SDK like this:

services.AddAzureClients(builder => builder.AddBlobServiceClient(new Uri(Configuration["BlobServiceUri"])));

And the old Azure SDKs have language gaps, for example, a feature exists in .NET SDK may not be found in the Python SDK. Normally, a lot of enterprise systems aren't designed with a single programming language. By using the new SDK, unified API surface and usage are cross languages, which means you can spend less time working across the enterprise system.

Finally, .NET dependencies like managing different versions of Newtonsoft.Json that are referenced by different Azure SDK packages can be tough. Thew new unified SDKs will solve these problems once for all.

However, the Azure service coverage of the new SDK is not complete. So you will need to check whether your Azure feature is supported before switching to the new SDK.

You can find more details about the new Azure SDK for .NET here: https://github.com/Azure/azure-sdk-for-net

Migrate to the new Azure SDK


I migrated a simple project to the new Azure.* SDK these days. It is a tool for syncing Azure Blob Storage that I wrote years ago: https://github.com/EdiWang/Azure-Blob-Backup

Replace NuGet Packages

Replace Microsoft.Azure.Storage.Blob, Microsoft.Azure.KeyVault.Core with Azure.Storage.Blobs

<PackageReference Include="Azure.Storage.Blobs" Version="12.0.0" />
Modify Code

Old code for Azure Blob Container

public static CloudBlobContainer BlobContainer { get; set; }
...
private static CloudBlobContainer GetBlobContainer()
{
    CloudStorageAccount storageAccount = new CloudStorageAccount(new StorageCredentials(Options.AccountName, Options.AccountKey), true);
    CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
    CloudBlobContainer container = blobClient.GetContainerReference(Options.ContainerName);
    return container;
}

New code

public static BlobContainerClient BlobContainer { get; set; }
...
private static BlobContainerClient GetBlobContainer()
{
    var container = new BlobContainerClient(Options.ConnectionString, Options.ContainerName);
    return container;
}

And also, get the blob file list, old code like this:

var blobs = await BlobContainer.ListBlobsSegmentedAsync(null);
var cloudFiles = (from item in blobs.Results
                  where item.GetType() == typeof(CloudBlockBlob)
                  select (CloudBlockBlob)item
                  into blob
                  select new FileSyncInfo()
                  {
                      FileName = blob.Name,
                      Length = blob.Properties.Length
                  }).ToList();

Is replaced by simpler code like this:

var cloudFiles = new List<FileSyncInfo>();
await foreach (var blobItem in BlobContainer.GetBlobsAsync())
{
    var fsi = new FileSyncInfo
    {
        FileName = blobItem.Name,
        Length = blobItem.Properties.ContentLength
    };
    cloudFiles.Add(fsi);
}

As you can see, the new SDK can do similar operations with less code. For the complete migration code change, you can refer to my commit on GitHub: https://github.com/EdiWang/Azure-Blob-Backup/commit/48222f740d6752ba00eee992625a69226b15c836

What About My Blog's Code?


My blog is also using the Azure SDK to talk to Blobs. It is a bit tricky for me to migrate the code.

Setting ContentType

Old Code

var blockBlob = _container.GetBlockBlobReference(fileName);

// Why .NET Core doesn't have MimeMapping.GetMimeMapping()
string extension = Path.GetExtension(blockBlob.Uri.AbsoluteUri);
switch (extension.ToLower())
{
    case ".jpg":
    case ".jpeg":
        blockBlob.Properties.ContentType = "image/jpeg";
        break;
    case ".png":
        blockBlob.Properties.ContentType = "image/png";
        break;
    case ".gif":
        blockBlob.Properties.ContentType = "image/gif";
        break;
    default:
        break;
}

await using (var fileStream = new MemoryStream(imageBytes))
{
    await blockBlob.UploadFromStreamAsync(fileStream);
}

New Code

BlobClient blob = _container.GetBlobClient(fileName);

// Why .NET Core doesn't have MimeMapping.GetMimeMapping()
var blobHttpHeader = new BlobHttpHeaders();
string extension = Path.GetExtension(blob.Uri.AbsoluteUri);
switch (extension.ToLower())
{
    case ".jpg":
    case ".jpeg":
        blobHttpHeader.ContentType = "image/jpeg";
        break;
    case ".png":
        blobHttpHeader.ContentType = "image/png";
        break;
    case ".gif":
        blobHttpHeader.ContentType = "image/gif";
        break;
    default:
        break;
}

await using (var fileStream = new MemoryStream(imageBytes))
{
    var uploadedBlob = await blob.UploadAsync(fileStream, blobHttpHeader);
}
Downloading a Single File

Old Code

public async Task<Response<ImageInfo>> GetAsync(string fileName)
{
    var blockBlob = _container.GetBlockBlobReference(fileName);
    await using var memoryStream = new MemoryStream();
    var extension = Path.GetExtension(fileName);
    if (string.IsNullOrWhiteSpace(extension))
    {
        return new FailedResponse<ImageInfo>((int)ResponseFailureCode.ExtensionNameIsNull);
    }

    var exists = await blockBlob.ExistsAsync();
    if (!exists)
    {
        _logger.LogWarning($"Blob {fileName} not exist.");
        return new FailedResponse<ImageInfo>((int)ResponseFailureCode.ImageNotExistInAzureBlob);
    }

    await blockBlob.DownloadToStreamAsync(memoryStream);
    var arr = memoryStream.ToArray();

    var fileType = extension.Replace(".", string.Empty);
    var imageInfo = new ImageInfo
    {
        ImageBytes = arr,
        ImageExtensionName = fileType
    };

    return new SuccessResponse<ImageInfo>(imageInfo);
}

New Code

public async Task<Response<ImageInfo>> GetAsync(string fileName)
{
    var blobClient = _container.GetBlobClient(fileName);
    await using var memoryStream = new MemoryStream();
    var extension = Path.GetExtension(fileName);
    if (string.IsNullOrWhiteSpace(extension))
    {
        return new FailedResponse<ImageInfo>((int)ResponseFailureCode.ExtensionNameIsNull);
    }

    // This needs to try-catch 404 status now :(
    // See https://github.com/Azure/azure-sdk-for-net/issues/8952
    //var exists = await blobClient.ExistsAsync();
    //if (!exists)
    //{
    //    _logger.LogWarning($"Blob {fileName} not exist.");
    //    return new FailedResponse<ImageInfo>((int)ResponseFailureCode.ImageNotExistInAzureBlob);
    //}

    try
    {
        await blobClient.DownloadToAsync(memoryStream);
        var arr = memoryStream.ToArray();

        var fileType = extension.Replace(".", string.Empty);
        var imageInfo = new ImageInfo
        {
            ImageBytes = arr,
            ImageExtensionName = fileType
        };

        return new SuccessResponse<ImageInfo>(imageInfo);
    }
    catch (Azure.RequestFailedException e)
    {
        if (e.Status == 404)
        {
            return new FailedResponse<ImageInfo>((int)ResponseFailureCode.ImageNotExistInAzureBlob);
        }

        throw;
    }
}
Reference

Azure SDK for .NET Document

Exploring the new Azure .NET SDKs for .NET

Azure SDK for .NET Samples

Azure SDK Design Guidelines for .NET