DKNet.Svc.BlobStorage.AzureStorage 9.0.27

There is a newer version of this package available.
See the version list below for details.
dotnet add package DKNet.Svc.BlobStorage.AzureStorage --version 9.0.27
                    
NuGet\Install-Package DKNet.Svc.BlobStorage.AzureStorage -Version 9.0.27
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="DKNet.Svc.BlobStorage.AzureStorage" Version="9.0.27" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="DKNet.Svc.BlobStorage.AzureStorage" Version="9.0.27" />
                    
Directory.Packages.props
<PackageReference Include="DKNet.Svc.BlobStorage.AzureStorage" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add DKNet.Svc.BlobStorage.AzureStorage --version 9.0.27
                    
#r "nuget: DKNet.Svc.BlobStorage.AzureStorage, 9.0.27"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package DKNet.Svc.BlobStorage.AzureStorage@9.0.27
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=DKNet.Svc.BlobStorage.AzureStorage&version=9.0.27
                    
Install as a Cake Addin
#tool nuget:?package=DKNet.Svc.BlobStorage.AzureStorage&version=9.0.27
                    
Install as a Cake Tool

DKNet.Svc.BlobStorage.AzureStorage

NuGet NuGet Downloads .NET License

Azure Blob Storage implementation of the DKNet blob storage abstractions, providing seamless integration with Azure Storage services. This package offers production-ready blob storage capabilities with Azure-specific optimizations and features.

Features

  • Azure Blob Storage Integration: Direct integration with Azure Storage SDK
  • Full IBlobService Implementation: Complete implementation of DKNet blob storage abstractions
  • Connection String Support: Flexible connection string configuration
  • Container Management: Automatic container creation and management
  • Blob Metadata: Support for custom metadata and properties
  • Streaming Support: Efficient streaming for large file operations
  • Error Handling: Robust error handling with Azure-specific exceptions
  • Performance Optimized: Leverages Azure Storage SDK optimizations

Supported Frameworks

  • .NET 9.0+
  • Azure.Storage.Blobs 12.0+

Installation

Install via NuGet Package Manager:

dotnet add package DKNet.Svc.BlobStorage.AzureStorage

Or via Package Manager Console:

Install-Package DKNet.Svc.BlobStorage.AzureStorage

Quick Start

Configuration Setup

{
  "BlobService": {
    "AzureStorage": {
      "ConnectionString": "DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=mykey;EndpointSuffix=core.windows.net",
      "ContainerName": "mycontainer",
      "EnableMetadata": true,
      "MaxFileSize": 104857600,
      "GenerateUniqueNames": false,
      "PathPrefix": "uploads/"
    }
  }
}

Service Registration

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Configuration;

public void ConfigureServices(IServiceCollection services, IConfiguration configuration)
{
    // Add Azure Storage blob service
    services.AddAzureStorageAdapter(configuration);
    
    // Or configure manually
    services.Configure<AzureStorageOptions>(options =>
    {
        options.ConnectionString = "your-connection-string";
        options.ContainerName = "your-container";
        options.EnableMetadata = true;
        options.MaxFileSize = 100 * 1024 * 1024; // 100MB
    });
}

Basic Usage

using DKNet.Svc.BlobStorage.Abstractions;

public class DocumentService
{
    private readonly IBlobService _blobService;

    public DocumentService(IBlobService blobService)
    {
        _blobService = blobService;
    }

    public async Task<string> UploadDocumentAsync(IFormFile file)
    {
        using var stream = file.OpenReadStream();
        
        var blob = new BlobData
        {
            Name = $"documents/{Guid.NewGuid()}/{file.FileName}",
            ContentStream = stream,
            ContentType = file.ContentType,
            Metadata = new Dictionary<string, string>
            {
                ["OriginalFileName"] = file.FileName,
                ["UploadedAt"] = DateTime.UtcNow.ToString("O"),
                ["FileSize"] = file.Length.ToString()
            }
        };

        return await _blobService.SaveAsync(blob);
    }

    public async Task<Stream?> DownloadDocumentAsync(string blobName)
    {
        var request = new BlobRequest(blobName);
        var result = await _blobService.GetAsync(request);
        
        return result?.ContentStream;
    }
}

Configuration

Azure Storage Options

public class AzureStorageOptions : BlobServiceOptions
{
    public string ConnectionString { get; set; } = null!;
    public string ContainerName { get; set; } = null!;
    
    // Inherited from BlobServiceOptions
    public bool EnableMetadata { get; set; } = true;
    public long MaxFileSize { get; set; } = 50 * 1024 * 1024; // 50MB
    public string[] AllowedExtensions { get; set; } = Array.Empty<string>();
    public string[] BlockedExtensions { get; set; } = { ".exe", ".bat", ".cmd" };
    public bool GenerateUniqueNames { get; set; } = false;
    public string PathPrefix { get; set; } = string.Empty;
}

Connection String Formats

// Storage account connection string
"DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=mykey;EndpointSuffix=core.windows.net"

// With custom endpoint
"DefaultEndpointsProtocol=https;AccountName=myaccount;AccountKey=mykey;BlobEndpoint=https://myaccount.blob.core.windows.net/"

// Using SAS token
"BlobEndpoint=https://myaccount.blob.core.windows.net/;SharedAccessSignature=sv=2020-02-10&ss=b&srt=sco&sp=rwdlacx&se=2021-12-31T23:59:59Z&st=2021-01-01T00:00:00Z&spr=https&sig=signature"

// Development (Azurite emulator)
"UseDevelopmentStorage=true"

Environment-Specific Configuration

// Production configuration
public void ConfigureProductionServices(IServiceCollection services, IConfiguration configuration)
{
    services.Configure<AzureStorageOptions>(options =>
    {
        options.ConnectionString = configuration.GetConnectionString("AzureStorage");
        options.ContainerName = "production-files";
        options.EnableMetadata = true;
        options.MaxFileSize = 500 * 1024 * 1024; // 500MB for production
        options.AllowedExtensions = new[] { ".pdf", ".jpg", ".png", ".docx" };
    });
    
    services.AddAzureStorageAdapter(configuration);
}

// Development configuration
public void ConfigureDevelopmentServices(IServiceCollection services, IConfiguration configuration)
{
    services.Configure<AzureStorageOptions>(options =>
    {
        options.ConnectionString = "UseDevelopmentStorage=true"; // Azurite
        options.ContainerName = "dev-files";
        options.EnableMetadata = true;
        options.MaxFileSize = 10 * 1024 * 1024; // 10MB for development
    });
    
    services.AddAzureStorageAdapter(configuration);
}

API Reference

AzureStorageBlobService

Implements IBlobService with Azure Blob Storage backend:

  • SaveAsync(BlobData, CancellationToken) - Upload blob to Azure Storage
  • GetAsync(BlobRequest, CancellationToken) - Download blob from Azure Storage
  • GetItemAsync(BlobRequest, CancellationToken) - Get blob metadata only
  • ListItemsAsync(BlobRequest, CancellationToken) - List blobs in container
  • DeleteAsync(BlobRequest, CancellationToken) - Delete blob from Azure Storage
  • ExistsAsync(BlobRequest, CancellationToken) - Check if blob exists

AzureStorageOptions

Configuration class extending BlobServiceOptions:

  • ConnectionString - Azure Storage connection string
  • ContainerName - Target container name
  • Plus all base blob service options

Setup Extensions

  • AddAzureStorageAdapter(IConfiguration) - Register Azure Storage implementation

Advanced Usage

Custom Metadata and Properties

public class ImageService
{
    private readonly IBlobService _blobService;

    public ImageService(IBlobService blobService)
    {
        _blobService = blobService;
    }

    public async Task<string> UploadImageAsync(byte[] imageData, string fileName, ImageMetadata metadata)
    {
        var blob = new BlobData
        {
            Name = $"images/{DateTime.UtcNow:yyyy/MM/dd}/{fileName}",
            Content = imageData,
            ContentType = "image/jpeg",
            Metadata = new Dictionary<string, string>
            {
                ["Width"] = metadata.Width.ToString(),
                ["Height"] = metadata.Height.ToString(),
                ["Format"] = metadata.Format,
                ["Quality"] = metadata.Quality.ToString(),
                ["Camera"] = metadata.CameraModel ?? "Unknown",
                ["Location"] = metadata.GpsLocation ?? "Unknown"
            }
        };

        return await _blobService.SaveAsync(blob);
    }

    public async Task<ImageInfo?> GetImageInfoAsync(string imageName)
    {
        var request = new BlobRequest(imageName);
        var result = await _blobService.GetItemAsync(request);

        if (result?.Details == null)
            return null;

        return new ImageInfo
        {
            Name = result.Name,
            Size = result.Details.ContentLength,
            ContentType = result.Details.ContentType,
            LastModified = result.Details.LastModified,
            Width = GetMetadataValue(result.Metadata, "Width"),
            Height = GetMetadataValue(result.Metadata, "Height"),
            Format = GetMetadataValue(result.Metadata, "Format")
        };
    }

    private static string GetMetadataValue(IDictionary<string, string>? metadata, string key)
    {
        return metadata?.TryGetValue(key, out var value) == true ? value : "Unknown";
    }
}

Blob Lifecycle Management

public class BlobLifecycleService
{
    private readonly IBlobService _blobService;
    private readonly ILogger<BlobLifecycleService> _logger;

    public BlobLifecycleService(IBlobService blobService, ILogger<BlobLifecycleService> logger)
    {
        _blobService = blobService;
        _logger = logger;
    }

    public async Task ArchiveOldBlobsAsync(TimeSpan maxAge)
    {
        var cutoffDate = DateTime.UtcNow - maxAge;
        var request = new BlobRequest("") { Type = BlobTypes.Directory };

        await foreach (var blob in _blobService.ListItemsAsync(request))
        {
            if (blob.Details?.LastModified < cutoffDate)
            {
                _logger.LogInformation("Archiving old blob: {BlobName}", blob.Name);
                
                // Move to archive container or delete
                await _blobService.DeleteAsync(new BlobRequest(blob.Name));
            }
        }
    }

    public async Task<long> CalculateStorageUsageAsync()
    {
        long totalSize = 0;
        var request = new BlobRequest("") { Type = BlobTypes.Directory };

        await foreach (var blob in _blobService.ListItemsAsync(request))
        {
            totalSize += blob.Details?.ContentLength ?? 0;
        }

        return totalSize;
    }
}

Batch Operations

public class BatchBlobService
{
    private readonly IBlobService _blobService;
    private readonly ILogger<BatchBlobService> _logger;

    public BatchBlobService(IBlobService blobService, ILogger<BatchBlobService> logger)
    {
        _blobService = blobService;
        _logger = logger;
    }

    public async Task<List<string>> UploadMultipleFilesAsync(IEnumerable<IFormFile> files)
    {
        var uploadTasks = files.Select(async file =>
        {
            try
            {
                using var stream = file.OpenReadStream();
                var blob = new BlobData
                {
                    Name = $"batch-upload/{Guid.NewGuid()}/{file.FileName}",
                    ContentStream = stream,
                    ContentType = file.ContentType
                };

                var location = await _blobService.SaveAsync(blob);
                _logger.LogDebug("Successfully uploaded {FileName} to {Location}", file.FileName, location);
                return location;
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to upload {FileName}", file.FileName);
                return null;
            }
        });

        var results = await Task.WhenAll(uploadTasks);
        return results.Where(r => r != null).Cast<string>().ToList();
    }

    public async Task DeleteBlobsByPrefixAsync(string prefix)
    {
        var request = new BlobRequest(prefix) { Type = BlobTypes.Directory };
        var deleteTasks = new List<Task>();

        await foreach (var blob in _blobService.ListItemsAsync(request))
        {
            var deleteTask = DeleteSafelyAsync(blob.Name);
            deleteTasks.Add(deleteTask);

            // Process in batches to avoid overwhelming the service
            if (deleteTasks.Count >= 10)
            {
                await Task.WhenAll(deleteTasks);
                deleteTasks.Clear();
            }
        }

        if (deleteTasks.Any())
        {
            await Task.WhenAll(deleteTasks);
        }
    }

    private async Task DeleteSafelyAsync(string blobName)
    {
        try
        {
            await _blobService.DeleteAsync(new BlobRequest(blobName));
            _logger.LogDebug("Successfully deleted {BlobName}", blobName);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to delete {BlobName}", blobName);
        }
    }
}

Integration with ASP.NET Core

[ApiController]
[Route("api/[controller]")]
public class FilesController : ControllerBase
{
    private readonly IBlobService _blobService;
    private readonly ILogger<FilesController> _logger;

    public FilesController(IBlobService blobService, ILogger<FilesController> logger)
    {
        _blobService = blobService;
        _logger = logger;
    }

    [HttpPost("upload")]
    public async Task<IActionResult> UploadFile([FromForm] FileUploadRequest request)
    {
        if (request.File == null || request.File.Length == 0)
            return BadRequest("No file uploaded");

        try
        {
            using var stream = request.File.OpenReadStream();
            var blob = new BlobData
            {
                Name = $"uploads/{DateTime.UtcNow:yyyy/MM/dd}/{Guid.NewGuid()}/{request.File.FileName}",
                ContentStream = stream,
                ContentType = request.File.ContentType,
                Metadata = new Dictionary<string, string>
                {
                    ["OriginalFileName"] = request.File.FileName,
                    ["UploadedBy"] = User.Identity?.Name ?? "Anonymous",
                    ["Category"] = request.Category ?? "General"
                }
            };

            var location = await _blobService.SaveAsync(blob);
            
            return Ok(new { 
                Location = location, 
                FileName = request.File.FileName,
                Size = request.File.Length
            });
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to upload file {FileName}", request.File.FileName);
            return StatusCode(500, "Upload failed");
        }
    }

    [HttpGet("download/{*blobName}")]
    public async Task<IActionResult> DownloadFile(string blobName)
    {
        try
        {
            var request = new BlobRequest(blobName);
            var result = await _blobService.GetAsync(request);

            if (result == null)
                return NotFound();

            var fileName = Path.GetFileName(blobName);
            return File(result.ContentStream, result.ContentType ?? "application/octet-stream", fileName);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to download file {BlobName}", blobName);
            return StatusCode(500, "Download failed");
        }
    }

    [HttpGet("list")]
    public async Task<IActionResult> ListFiles([FromQuery] string? prefix = null)
    {
        try
        {
            var request = new BlobRequest(prefix ?? "") { Type = BlobTypes.Directory };
            var files = new List<FileInfo>();

            await foreach (var blob in _blobService.ListItemsAsync(request))
            {
                files.Add(new FileInfo
                {
                    Name = blob.Name,
                    Size = blob.Details?.ContentLength ?? 0,
                    ContentType = blob.Details?.ContentType ?? "unknown",
                    LastModified = blob.Details?.LastModified ?? DateTime.MinValue
                });
            }

            return Ok(files);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to list files with prefix {Prefix}", prefix);
            return StatusCode(500, "List operation failed");
        }
    }
}

public class FileUploadRequest
{
    public IFormFile File { get; set; } = null!;
    public string? Category { get; set; }
}

public class FileInfo
{
    public string Name { get; set; } = null!;
    public long Size { get; set; }
    public string ContentType { get; set; } = null!;
    public DateTime LastModified { get; set; }
}

Error Handling

public class RobustBlobService
{
    private readonly IBlobService _blobService;
    private readonly ILogger<RobustBlobService> _logger;

    public RobustBlobService(IBlobService blobService, ILogger<RobustBlobService> logger)
    {
        _blobService = blobService;
        _logger = logger;
    }

    public async Task<string?> SafeUploadAsync(BlobData blob, int maxRetries = 3)
    {
        for (int attempt = 1; attempt <= maxRetries; attempt++)
        {
            try
            {
                return await _blobService.SaveAsync(blob);
            }
            catch (Azure.RequestFailedException ex) when (ex.Status == 429) // Too Many Requests
            {
                if (attempt == maxRetries)
                    throw;

                var delay = TimeSpan.FromSeconds(Math.Pow(2, attempt)); // Exponential backoff
                _logger.LogWarning("Upload rate limited, retrying in {Delay}s (attempt {Attempt}/{MaxRetries})", 
                    delay.TotalSeconds, attempt, maxRetries);
                
                await Task.Delay(delay);
            }
            catch (Azure.RequestFailedException ex) when (ex.Status >= 500) // Server errors
            {
                if (attempt == maxRetries)
                    throw;

                _logger.LogWarning(ex, "Server error during upload, retrying (attempt {Attempt}/{MaxRetries})", 
                    attempt, maxRetries);
                
                await Task.Delay(TimeSpan.FromSeconds(attempt));
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Upload failed for blob {BlobName}", blob.Name);
                throw;
            }
        }

        return null;
    }
}

Performance Considerations

  • Connection Pooling: Azure Storage SDK handles connection pooling automatically
  • Parallel Operations: Use Task.WhenAll for concurrent operations
  • Stream Usage: Always use streams for large files to avoid memory issues
  • Metadata: Minimize metadata size for better performance
  • Container Access: Container access is cached within the service

Security Considerations

  • Connection Strings: Store connection strings securely (Azure Key Vault, environment variables)
  • SAS Tokens: Use SAS tokens for limited access scenarios
  • Container Permissions: Set appropriate container access levels
  • HTTPS: Always use HTTPS endpoints in production
  • Access Logging: Enable Azure Storage logging for audit trails

Thread Safety

  • Azure Storage SDK is thread-safe
  • Service instance can be used concurrently
  • Stream operations should not share streams between threads
  • Container client is cached and thread-safe

Contributing

See the main CONTRIBUTING.md for guidelines on how to contribute to this project.

License

This project is licensed under the MIT License.


Part of the DKNet Framework - A comprehensive .NET framework for building modern, scalable applications.

Product Compatible and additional computed target framework versions.
.NET net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
9.0.31 117 9/19/2025
9.0.30 128 9/18/2025
9.0.29 127 9/18/2025
9.0.28 135 9/17/2025
9.0.27 135 9/17/2025
9.0.26 153 9/16/2025
9.0.25 151 9/15/2025
9.0.24 151 9/15/2025
9.0.23 68 9/6/2025
9.0.22 139 9/3/2025
9.0.21 129 9/1/2025
9.0.20 144 7/15/2025
9.0.19 139 7/14/2025
9.0.18 138 7/14/2025
9.0.17 142 7/14/2025
9.0.16 118 7/11/2025
9.0.15 120 7/11/2025
9.0.14 121 7/11/2025
9.0.13 129 7/11/2025
9.0.12 144 7/8/2025
9.0.11 140 7/8/2025
9.0.10 139 7/7/2025
9.0.9 139 7/2/2025
9.0.8 146 7/2/2025
9.0.7 145 7/1/2025
9.0.5 143 6/24/2025
9.0.4 145 6/24/2025
9.0.3 141 6/23/2025
9.0.2 142 6/23/2025
9.0.1 145 6/23/2025