Skip to main content

Overview

The Azure Blob Storage module provides a robust C# client wrapper for working with Azure Blob Storage in .NET applications. This module integrates the Azure.Storage.Blobs NuGet package and abstracts blob storage operations through a clean, easy-to-use interface. Azure Blob Storage is designed to store massive amounts of unstructured data such as text, binary data, documents, media files, and backups.

Installation

Intent.Azure.BlobStorage

The IBlobStorage Interface

The module generates an IBlobStorage interface that encapsulates common blob operations:
public interface IBlobStorage
{
    // Upload operations
    Task<Uri> UploadAsync(string containerName, string blobName, Stream content, CancellationToken cancellationToken = default);
    Task<Uri> UploadStringAsync(string containerName, string blobName, string content, CancellationToken cancellationToken = default);
    Task<Uri> UploadBytesAsync(string containerName, string blobName, byte[] content, CancellationToken cancellationToken = default);
    
    // Download operations
    Task<Stream> DownloadAsync(string containerName, string blobName, CancellationToken cancellationToken = default);
    Task<string> DownloadAsStringAsync(string containerName, string blobName, CancellationToken cancellationToken = default);
    Task<byte[]> DownloadAsBytesAsync(string containerName, string blobName, CancellationToken cancellationToken = default);
    
    // List operation
    IAsyncEnumerable<Uri> ListAsync(string containerName, CancellationToken cancellationToken = default);
    
    // Delete operation
    Task DeleteAsync(string containerName, string blobName, CancellationToken cancellationToken = default);
    
    // Existence check
    Task<bool> ExistsAsync(string containerName, string blobName, CancellationToken cancellationToken = default);
}

Usage Examples

Financial Reports Service

A complete example demonstrating blob storage in a business context:
public class FinancialReportService
{
    private readonly IBlobStorage _blobStorage;
    private readonly string _containerName = "financial-reports";

    public FinancialReportService(IBlobStorage blobStorage)
    {
        _blobStorage = blobStorage;
    }

    public async Task<Uri> SaveReportAsync(string reportName, string content)
    {
        var reportUri = await _blobStorage.UploadStringAsync(
            _containerName, 
            reportName, 
            content);
        return reportUri;
    }

    public async Task<string> GetReportAsync(string reportName)
    {
        var reportContent = await _blobStorage.DownloadAsStringAsync(
            _containerName, 
            reportName);
        return reportContent;
    }

    public async Task<IEnumerable<Uri>> ListReportsAsync()
    {
        var reports = new List<Uri>();
        await foreach (var uri in _blobStorage.ListAsync(_containerName))
        {
            reports.Add(uri);
        }
        return reports;
    }
    
    public async Task DeleteReportAsync(string reportName)
    {
        await _blobStorage.DeleteAsync(_containerName, reportName);
    }
}

Document Management

public class DocumentService
{
    private readonly IBlobStorage _blobStorage;
    private const string ContainerName = "documents";

    public async Task<string> UploadDocumentAsync(
        Stream fileStream, 
        string fileName,
        CancellationToken cancellationToken)
    {
        // Upload file
        var uri = await _blobStorage.UploadAsync(
            ContainerName,
            fileName,
            fileStream,
            cancellationToken);
            
        return uri.ToString();
    }
    
    public async Task<Stream> DownloadDocumentAsync(
        string fileName,
        CancellationToken cancellationToken)
    {
        return await _blobStorage.DownloadAsync(
            ContainerName,
            fileName,
            cancellationToken);
    }
    
    public async Task<bool> DocumentExistsAsync(
        string fileName,
        CancellationToken cancellationToken)
    {
        return await _blobStorage.ExistsAsync(
            ContainerName,
            fileName,
            cancellationToken);
    }
}

Image Processing

public class ImageService
{
    private readonly IBlobStorage _blobStorage;
    private readonly IImageProcessor _imageProcessor;

    public async Task<Uri> UploadProfileImageAsync(
        string userId, 
        byte[] imageData,
        CancellationToken cancellationToken)
    {
        // Process image (resize, compress)
        var processedImage = await _imageProcessor.ProcessAsync(imageData);
        
        // Upload to blob storage
        var blobName = $"profiles/{userId}/avatar.jpg";
        return await _blobStorage.UploadBytesAsync(
            "images",
            blobName,
            processedImage,
            cancellationToken);
    }
    
    public async Task<byte[]> GetProfileImageAsync(
        string userId,
        CancellationToken cancellationToken)
    {
        var blobName = $"profiles/{userId}/avatar.jpg";
        return await _blobStorage.DownloadAsBytesAsync(
            "images",
            blobName,
            cancellationToken);
    }
}

Configuration

Local Development (Azurite)

For local development, use Azurite to emulate Azure Blob Storage: appsettings.Development.json:
{
  "ConnectionStrings": {
    "AzureBlobStorage": "UseDevelopmentStorage=true"
  }
}

Production Connection String

appsettings.json:
{
  "ConnectionStrings": {
    "AzureBlobStorage": "DefaultEndpointsProtocol=https;AccountName=myaccountname;AccountKey=myaccountkey;EndpointSuffix=core.windows.net;"
  }
}

Getting Connection String from Azure Portal

  1. Navigate to your Storage Account in Azure Portal
  2. Click Access keys under Security + networking
  3. Copy the connection string from key1 or key2
appsettings.json:
{
  "AzureBlobStorage": {
    "ServiceUri": "https://myaccountname.blob.core.windows.net"
  }
}
Configure in code:
services.AddSingleton(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    var serviceUri = new Uri(config["AzureBlobStorage:ServiceUri"]);
    var credential = new DefaultAzureCredential();
    return new BlobServiceClient(serviceUri, credential);
});

Local Development Setup

Install Azurite

Option 1: NPM
npm install -g azurite
Run Azurite:
azurite
Option 2: Visual Studio Azurite is included with Visual Studio 2022+ and runs automatically with the Azure development workload. Option 3: Docker
docker run -p 10000:10000 -p 10001:10001 -p 10002:10002 \
  mcr.microsoft.com/azure-storage/azurite

Azure Storage Explorer

Connect to Azurite or production storage using Azure Storage Explorer:
  1. Download and install Storage Explorer
  2. Connect to:
    • Azurite: Use “Local Storage Emulator”
    • Azure: Add your Azure account or use connection string

Blob Types

Block Blobs (Default)

Optimized for uploading large amounts of data efficiently. Ideal for:
  • Documents
  • Media files (images, videos)
  • Backups
// Automatically uses block blob
await _blobStorage.UploadAsync("container", "document.pdf", stream);

Append Blobs

Optimized for append operations. Ideal for:
  • Log files
  • Audit trails
var appendBlobClient = containerClient.GetAppendBlobClient("logs/app.log");
await appendBlobClient.CreateIfNotExistsAsync();
await appendBlobClient.AppendBlockAsync(new MemoryStream(logData));

Page Blobs

Optimized for random read/write operations. Ideal for:
  • Virtual hard disks (VHDs)
  • Database files

Advanced Scenarios

Metadata

Attach custom metadata to blobs:
public async Task UploadWithMetadataAsync(
    string containerName,
    string blobName,
    Stream content,
    Dictionary<string, string> metadata)
{
    var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
    var blobClient = containerClient.GetBlobClient(blobName);
    
    var uploadOptions = new BlobUploadOptions
    {
        Metadata = metadata
    };
    
    await blobClient.UploadAsync(content, uploadOptions);
}

// Usage
await UploadWithMetadataAsync(
    "documents",
    "report.pdf",
    fileStream,
    new Dictionary<string, string>
    {
        { "Author", "John Doe" },
        { "Department", "Finance" },
        { "Year", "2024" }
    });

Blob Leasing

Implement distributed locking:
public async Task<bool> TryAcquireLeaseAsync(
    string containerName,
    string blobName,
    TimeSpan duration,
    CancellationToken cancellationToken)
{
    var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
    var blobClient = containerClient.GetBlobClient(blobName);
    
    try
    {
        var lease = await blobClient.GetBlobLeaseClient()
            .AcquireAsync(duration, cancellationToken: cancellationToken);
        return true;
    }
    catch (RequestFailedException ex) when (ex.Status == 409)
    {
        // Blob is already leased
        return false;
    }
}

Blob Versioning

Access previous versions:
public async Task<List<BlobItem>> GetBlobVersionsAsync(
    string containerName,
    string blobName)
{
    var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
    var versions = new List<BlobItem>();
    
    await foreach (var blobItem in containerClient.GetBlobsAsync(
        BlobTraits.All,
        BlobStates.Version,
        prefix: blobName))
    {
        versions.Add(blobItem);
    }
    
    return versions;
}

Blob Snapshots

Create point-in-time copies:
public async Task<BlobSnapshotInfo> CreateSnapshotAsync(
    string containerName,
    string blobName)
{
    var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
    var blobClient = containerClient.GetBlobClient(blobName);
    
    return await blobClient.CreateSnapshotAsync();
}

Shared Access Signatures (SAS)

Generate temporary access URLs:
public Uri GenerateSasUri(
    string containerName,
    string blobName,
    TimeSpan validFor)
{
    var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
    var blobClient = containerClient.GetBlobClient(blobName);
    
    var sasBuilder = new BlobSasBuilder
    {
        BlobContainerName = containerName,
        BlobName = blobName,
        Resource = "b",
        StartsOn = DateTimeOffset.UtcNow,
        ExpiresOn = DateTimeOffset.UtcNow.Add(validFor)
    };
    
    sasBuilder.SetPermissions(BlobSasPermissions.Read);
    
    return blobClient.GenerateSasUri(sasBuilder);
}

// Usage - generate 1-hour download link
var downloadUrl = GenerateSasUri("documents", "report.pdf", TimeSpan.FromHours(1));

Storage Tiers

Optimize costs with access tiers:
TierUse CaseCostAccess Time
HotFrequently accessed dataHighest storage costImmediate
CoolInfrequently accessed (30+ days)Lower storage costImmediate
ArchiveRarely accessed (180+ days)Lowest storage costHours
public async Task SetAccessTierAsync(
    string containerName,
    string blobName,
    AccessTier tier)
{
    var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
    var blobClient = containerClient.GetBlobClient(blobName);
    
    await blobClient.SetAccessTierAsync(tier);
}

// Archive old reports
await SetAccessTierAsync(
    "financial-reports", 
    "2020/annual-report.pdf", 
    AccessTier.Archive);

Best Practices

  • Use lowercase names
  • Avoid special characters except hyphens and underscores
  • Use hierarchical naming: department/year/month/file.pdf
  • Container names: 3-63 characters, alphanumeric and hyphens
  • Blob names: up to 1,024 characters
  • Use async methods for I/O operations
  • Consider parallel uploads for large files
  • Use BlobBatchClient for bulk operations
  • Enable CDN for frequently accessed content
  • Implement retry policies for transient failures
  • Use managed identities instead of connection strings
  • Implement SAS tokens with minimal permissions
  • Enable blob versioning for critical data
  • Use private endpoints for enhanced security
  • Enable soft delete for recovery
  • Use lifecycle management policies
  • Move old data to cool/archive tiers
  • Delete unnecessary snapshots and versions
  • Monitor storage usage and set alerts
  • Use block blob storage for better pricing

Lifecycle Management

Automate data management with lifecycle policies:
public class LifecyclePolicyExample
{
    // Configure in Azure Portal or via ARM template
    // Example policy:
    // - Move to Cool after 30 days of no modification
    // - Move to Archive after 90 days
    // - Delete after 365 days
}
Azure Portal Configuration:
  1. Storage Account → Lifecycle Management
  2. Add rule
  3. Define conditions and actions

Monitoring

Application Insights Integration

public class MonitoredBlobStorage : IBlobStorage
{
    private readonly IBlobStorage _inner;
    private readonly TelemetryClient _telemetry;

    public async Task<Uri> UploadAsync(
        string containerName,
        string blobName,
        Stream content,
        CancellationToken cancellationToken = default)
    {
        var stopwatch = Stopwatch.StartNew();
        try
        {
            var result = await _inner.UploadAsync(
                containerName, blobName, content, cancellationToken);
            
            _telemetry.TrackEvent("BlobUploaded", new Dictionary<string, string>
            {
                { "Container", containerName },
                { "BlobName", blobName },
                { "Size", content.Length.ToString() },
                { "Duration", stopwatch.ElapsedMilliseconds.ToString() }
            });
            
            return result;
        }
        catch (Exception ex)
        {
            _telemetry.TrackException(ex);
            throw;
        }
    }
}

Key Metrics

  • Upload/download success rate
  • Operation latency
  • Storage capacity used
  • Request count and throttling
  • Cost per operation

Common Patterns

Upload with Progress

public async Task UploadLargeFileAsync(
    string containerName,
    string blobName,
    Stream content,
    IProgress<long> progress)
{
    var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
    var blobClient = containerClient.GetBlobClient(blobName);
    
    var uploadOptions = new BlobUploadOptions
    {
        ProgressHandler = new Progress<long>(bytesTransferred =>
        {
            progress?.Report(bytesTransferred);
        })
    };
    
    await blobClient.UploadAsync(content, uploadOptions);
}

Conditional Operations

public async Task<bool> UploadIfNotExistsAsync(
    string containerName,
    string blobName,
    Stream content)
{
    var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
    var blobClient = containerClient.GetBlobClient(blobName);
    
    try
    {
        var conditions = new BlobRequestConditions
        {
            IfNoneMatch = ETag.All // Only upload if blob doesn't exist
        };
        
        await blobClient.UploadAsync(content, conditions: conditions);
        return true;
    }
    catch (RequestFailedException ex) when (ex.Status == 409)
    {
        return false; // Blob already exists
    }
}

Resources

Azure Blob Storage Docs

Official documentation

Best Practices

Performance and optimization

Azurite

Local development emulator

Storage Explorer

Browse and manage blobs

Build docs developers (and LLMs) love