Storage Utility
A unified, dependency injection-friendly storage abstraction library for ASP.NET Core applications that supports multiple cloud storage providers through a single, consistent API.
๐ฏ Purposeโ
The Storage utility provides a powerful abstraction layer over various cloud storage providers, eliminating the need to build complex connection strings manually. It wraps the excellent FluentStorage library with a clean, strongly-typed configuration interface that integrates seamlessly with ASP.NET Core's dependency injection system.
๐ฆ Package Structureโ
๐ Installationโ
dotnet add package MasLazu.AspNet.Storage
โ๏ธ Configurationโ
1. Service Registrationโ
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add storage with configuration
builder.Services.AddMasLazuStorage(builder.Configuration);
var app = builder.Build();
2. Configuration Optionsโ
The library supports 13+ storage providers, each with strongly-typed configuration:
Amazon S3โ
{
"Storage": {
"Provider": "S3",
"S3": {
"AccessKey": "your-access-key",
"SecretKey": "your-secret-key",
"BucketName": "your-bucket-name",
"Region": "us-east-1",
"ServiceUrl": "https://s3.amazonaws.com"
}
}
}
MinIO (S3-Compatible)โ
{
"Storage": {
"Provider": "MinIO",
"MinIO": {
"AccessKey": "your-access-key",
"SecretKey": "your-secret-key",
"BucketName": "your-bucket-name",
"Endpoint": "http://localhost:9000"
}
}
}
DigitalOcean Spacesโ
{
"Storage": {
"Provider": "DigitalOceanSpaces",
"DigitalOceanSpaces": {
"AccessKey": "your-access-key",
"SecretKey": "your-secret-key",
"BucketName": "your-bucket-name",
"Region": "nyc3"
}
}
}
Azure Blob Storageโ
{
"Storage": {
"Provider": "AzureBlob",
"AzureBlob": {
"ConnectionString": "AccountName=youraccount;AccountKey=yourkey;",
"ContainerName": "your-container",
"BlobServiceUrl": "https://youraccount.blob.core.windows.net"
}
}
}
Azure Data Lake Gen2โ
{
"Storage": {
"Provider": "AzureDataLake",
"AzureDataLake": {
"AccountName": "youraccount",
"AccountKey": "yourkey",
"FileSystem": "your-filesystem"
}
}
}
Google Cloud Storageโ
{
"Storage": {
"Provider": "GoogleCloud",
"GoogleCloud": {
"BucketName": "your-bucket-name",
"CredentialsJson": "{ \"type\": \"service_account\", \"project_id\": \"your-project\", ... }",
"ProjectId": "your-project-id"
}
}
}
Azure Key Vaultโ
{
"Storage": {
"Provider": "AzureKeyVault",
"AzureKeyVault": {
"VaultName": "your-vault-name",
"ClientId": "your-client-id",
"ClientSecret": "your-client-secret",
"TenantId": "your-tenant-id"
}
}
}
Local File Systemโ
{
"Storage": {
"Provider": "Local",
"Local": {
"RootPath": "/path/to/storage/directory"
}
}
}
In-Memory Storageโ
{
"Storage": {
"Provider": "InMemory",
"InMemory": {
"Name": "my-memory-storage"
}
}
}
FTPโ
{
"Storage": {
"Provider": "Ftp",
"Ftp": {
"Host": "ftp.example.com",
"Port": 21,
"Username": "your-username",
"Password": "your-password",
"RootPath": "/optional/root/path"
}
}
}
SFTPโ
{
"Storage": {
"Provider": "Sftp",
"Sftp": {
"Host": "sftp.example.com",
"Port": 22,
"Username": "your-username",
"Password": "your-password",
"PrivateKeyPath": "/path/to/private/key",
"RootPath": "/optional/root/path"
}
}
}
ZIP Archiveโ
{
"Storage": {
"Provider": "Zip",
"Zip": {
"Path": "/path/to/archive.zip"
}
}
}
Databricks File Systemโ
{
"Storage": {
"Provider": "Databricks",
"Databricks": {
"WorkspaceUrl": "https://your-workspace.cloud.databricks.com",
"Token": "your-databricks-token",
"ClusterId": "your-cluster-id"
}
}
}
๐ง Usage Examplesโ
1. Basic File Operationsโ
public class FileStorageService
{
private readonly IBlobStorage _storage;
public FileStorageService(IBlobStorage storage)
{
_storage = storage;
}
// Upload a file
public async Task UploadFileAsync(string fileName, Stream content)
{
await _storage.WriteAsync(fileName, content);
}
// Download a file
public async Task<Stream> DownloadFileAsync(string fileName)
{
return await _storage.OpenReadAsync(fileName);
}
// Check if file exists
public async Task<bool> FileExistsAsync(string fileName)
{
return await _storage.ExistsAsync(fileName);
}
// Delete a file
public async Task DeleteFileAsync(string fileName)
{
await _storage.DeleteAsync(fileName);
}
}
2. Advanced Operationsโ
public class AdvancedStorageService
{
private readonly IBlobStorage _storage;
public AdvancedStorageService(IBlobStorage storage)
{
_storage = storage;
}
// List files with pagination
public async Task<List<string>> ListFilesAsync(string prefix = "", int maxResults = 100)
{
var blobs = await _storage.ListAsync(new ListOptions
{
Prefix = prefix,
MaxResults = maxResults
});
return blobs.Select(b => b.FullPath).ToList();
}
// Upload with metadata
public async Task UploadWithMetadataAsync(string fileName, Stream content, Dictionary<string, string> metadata)
{
var blob = new Blob(fileName, content);
blob.Metadata = metadata;
await _storage.WriteAsync(blob);
}
// Copy files between locations
public async Task CopyFileAsync(string sourcePath, string destinationPath)
{
await _storage.CopyAsync(sourcePath, destinationPath);
}
// Move/rename files
public async Task MoveFileAsync(string sourcePath, string destinationPath)
{
await _storage.MoveAsync(sourcePath, destinationPath);
}
}
3. File Upload Controllerโ
[ApiController]
[Route("api/files")]
public class FileController : ControllerBase
{
private readonly IBlobStorage _storage;
public FileController(IBlobStorage storage)
{
_storage = storage;
}
[HttpPost("upload")]
public async Task<IActionResult> UploadFile(IFormFile file)
{
if (file == null || file.Length == 0)
return BadRequest("No file uploaded");
var fileName = $"{Guid.NewGuid()}_{file.FileName}";
var filePath = $"uploads/{fileName}";
using var stream = file.OpenReadStream();
await _storage.WriteAsync(filePath, stream);
return Ok(new { FileName = fileName, Path = filePath });
}
[HttpGet("download/{fileName}")]
public async Task<IActionResult> DownloadFile(string fileName)
{
var filePath = $"uploads/{fileName}";
if (!await _storage.ExistsAsync(filePath))
return NotFound();
var stream = await _storage.OpenReadAsync(filePath);
return File(stream, "application/octet-stream", fileName);
}
[HttpDelete("{fileName}")]
public async Task<IActionResult> DeleteFile(string fileName)
{
var filePath = $"uploads/{fileName}";
await _storage.DeleteAsync(filePath);
return NoContent();
}
}
4. Image Processing Serviceโ
public class ImageStorageService
{
private readonly IBlobStorage _storage;
public ImageStorageService(IBlobStorage storage)
{
_storage = storage;
}
public async Task<string> UploadAndResizeImageAsync(IFormFile image, int maxWidth = 800, int maxHeight = 600)
{
// Generate unique filename
var extension = Path.GetExtension(image.FileName);
var fileName = $"images/{Guid.NewGuid()}{extension}";
// Process image (resize, optimize, etc.)
using var originalStream = image.OpenReadStream();
using var processedStream = await ProcessImageAsync(originalStream, maxWidth, maxHeight);
// Upload processed image
await _storage.WriteAsync(fileName, processedStream);
return fileName;
}
private async Task<Stream> ProcessImageAsync(Stream inputStream, int maxWidth, int maxHeight)
{
// Image processing logic (using ImageSharp, SkiaSharp, etc.)
// This is a simplified example
var outputStream = new MemoryStream();
// Process image here...
outputStream.Position = 0;
return outputStream;
}
}
5. Backup Serviceโ
public class BackupService
{
private readonly IBlobStorage _primaryStorage;
private readonly IBlobStorage _backupStorage;
public BackupService(IBlobStorage primaryStorage, IBlobStorage backupStorage)
{
_primaryStorage = primaryStorage;
_backupStorage = backupStorage;
}
public async Task BackupFilesAsync(string prefix = "")
{
var files = await _primaryStorage.ListAsync(new ListOptions { Prefix = prefix });
foreach (var file in files)
{
using var stream = await _primaryStorage.OpenReadAsync(file.FullPath);
await _backupStorage.WriteAsync($"backup/{file.FullPath}", stream);
}
}
public async Task RestoreFilesAsync(string prefix = "")
{
var backupFiles = await _backupStorage.ListAsync(new ListOptions { Prefix = $"backup/{prefix}" });
foreach (var file in backupFiles)
{
var originalPath = file.FullPath.Replace("backup/", "");
using var stream = await _backupStorage.OpenReadAsync(file.FullPath);
await _primaryStorage.WriteAsync(originalPath, stream);
}
}
}
๐๏ธ Architecture Detailsโ
Configuration Classesโ
public class StorageConfiguration
{
public string Provider { get; set; } = string.Empty;
public S3Configuration? S3 { get; set; }
public MinIOConfiguration? MinIO { get; set; }
public DigitalOceanSpacesConfiguration? DigitalOceanSpaces { get; set; }
public AzureBlobConfiguration? AzureBlob { get; set; }
public AzureDataLakeConfiguration? AzureDataLake { get; set; }
public AzureServiceFabricConfiguration? AzureServiceFabric { get; set; }
public GoogleCloudConfiguration? GoogleCloud { get; set; }
public AzureKeyVaultConfiguration? AzureKeyVault { get; set; }
public DatabricksConfiguration? Databricks { get; set; }
public LocalConfiguration? Local { get; set; }
public InMemoryConfiguration? InMemory { get; set; }
public ZipConfiguration? Zip { get; set; }
public FtpConfiguration? Ftp { get; set; }
public SftpConfiguration? Sftp { get; set; }
}
Service Registrationโ
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMasLazuStorage(this IServiceCollection services, IConfiguration configuration)
{
// FluentValidation integration
services.AddFluentValidationAutoValidation();
services.AddValidatorsFromAssemblyContaining<StorageConfigurationValidator>();
// Configuration binding with validation
services.AddOptions<StorageConfiguration>()
.Configure(o => configuration.GetSection("Storage").Bind(o))
.ValidateOnStart();
// Provider-specific storage factory
services.AddSingleton(sp =>
{
var config = sp.GetRequiredService<IOptions<StorageConfiguration>>().Value;
return config.Provider switch
{
"S3" => CreateS3Storage(config.S3!),
"MinIO" => CreateMinIOStorage(config.MinIO!),
"AzureBlob" => CreateAzureStorage(config.AzureBlob!),
// ... other providers
_ => throw new InvalidOperationException($"Unsupported storage provider: {config.Provider}")
};
});
return services;
}
}
๐งช Testing Supportโ
1. In-Memory Storage for Testingโ
{
"Storage": {
"Provider": "InMemory",
"InMemory": {
"Name": "test-storage"
}
}
}
2. Local File System for Developmentโ
{
"Storage": {
"Provider": "Local",
"Local": {
"RootPath": "./test-storage"
}
}
}
3. Unit Testing Exampleโ
public class FileStorageServiceTests
{
private readonly IBlobStorage _storage;
private readonly FileStorageService _service;
public FileStorageServiceTests()
{
// Use in-memory storage for testing
_storage = StorageFactory.Blobs.InMemory("test");
_service = new FileStorageService(_storage);
}
[Fact]
public async Task UploadFileAsync_ValidFile_StoresSuccessfully()
{
// Arrange
var fileName = "test.txt";
var content = "Hello, World!"u8.ToArray();
using var stream = new MemoryStream(content);
// Act
await _service.UploadFileAsync(fileName, stream);
// Assert
Assert.True(await _storage.ExistsAsync(fileName));
}
}
๐ Security Considerationsโ
1. Credential Managementโ
- Store credentials securely (Azure Key Vault, AWS Secrets Manager, etc.)
- Use managed identities when possible
- Rotate credentials regularly
- Never commit credentials to source control
2. Access Controlโ
- Implement proper authorization checks
- Use signed URLs for temporary access
- Validate file types and sizes
- Implement rate limiting
3. Data Protectionโ
- Encrypt sensitive files at rest
- Use HTTPS for all storage operations
- Implement proper backup strategies
- Monitor access patterns
๐ Performance Optimizationโ
1. Connection Poolingโ
// Configure connection pooling for better performance
builder.Services.Configure<StorageConfiguration>(config =>
{
// Provider-specific optimizations
});
2. Caching Strategyโ
public class CachedStorageService
{
private readonly IBlobStorage _storage;
private readonly IMemoryCache _cache;
public async Task<Stream> GetFileWithCacheAsync(string fileName)
{
if (_cache.TryGetValue(fileName, out Stream cachedStream))
{
return cachedStream;
}
var stream = await _storage.OpenReadAsync(fileName);
_cache.Set(fileName, stream, TimeSpan.FromMinutes(30));
return stream;
}
}
3. Batch Operationsโ
public async Task BatchUploadAsync(Dictionary<string, Stream> files)
{
var tasks = files.Select(kvp =>
_storage.WriteAsync(kvp.Key, kvp.Value));
await Task.WhenAll(tasks);
}
๐ง Advanced Configurationโ
1. Custom Storage Factoryโ
public static IServiceCollection AddCustomStorage(this IServiceCollection services, Func<IServiceProvider, IBlobStorage> factory)
{
services.AddSingleton(factory);
return services;
}
// Usage
builder.Services.AddCustomStorage(sp =>
{
var config = sp.GetRequiredService<IOptions<StorageConfiguration>>().Value;
// Custom storage creation logic
return StorageFactory.Blobs.InMemory("custom");
});
2. Multiple Storage Providersโ
public class MultiStorageService
{
private readonly IBlobStorage _primaryStorage;
private readonly IBlobStorage _backupStorage;
private readonly IBlobStorage _archiveStorage;
public MultiStorageService(
IBlobStorage primaryStorage,
IBlobStorage backupStorage,
IBlobStorage archiveStorage)
{
_primaryStorage = primaryStorage;
_backupStorage = backupStorage;
_archiveStorage = archiveStorage;
}
public async Task UploadWithBackupAsync(string fileName, Stream content)
{
// Upload to primary storage
await _primaryStorage.WriteAsync(fileName, content);
// Backup to secondary storage
await _backupStorage.WriteAsync($"backup/{fileName}", content);
// Archive old versions
await _archiveStorage.WriteAsync($"archive/{DateTime.UtcNow:yyyy/MM/dd}/{fileName}", content);
}
}
3. Storage Monitoringโ
public class StorageMonitorService : BackgroundService
{
private readonly IBlobStorage _storage;
private readonly ILogger<StorageMonitorService> _logger;
public StorageMonitorService(IBlobStorage storage, ILogger<StorageMonitorService> logger)
{
_storage = storage;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
var files = await _storage.ListAsync();
_logger.LogInformation($"Storage contains {files.Count} files");
// Additional monitoring logic
// - Check storage usage
// - Validate file integrity
// - Send alerts if needed
await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error monitoring storage");
}
}
}
}
๐ Project Structureโ
MasLazu.AspNet.Storage/
โโโ src/
โ โโโ MasLazu.AspNet.Storage/
โ โโโ Configurations/
โ โ โโโ StorageConfiguration.cs
โ โ โโโ S3Configuration.cs
โ โ โโโ AzureBlobConfiguration.cs
โ โ โโโ GoogleCloudConfiguration.cs
โ โ โโโ ... (other provider configs)
โ โโโ Extensions/
โ โ โโโ ServiceCollectionExtensions.cs
โ โโโ Validators/
โ โ โโโ StorageConfigurationValidator.cs
โ โโโ MasLazu.AspNet.Storage.csproj
โโโ test/
โ โโโ MasLazu.AspNet.Storage.Tests/
โโโ README.md
โ Best Practicesโ
โ Configuration Do'sโ
- Use strongly-typed configuration classes
- Validate configuration on startup
- Store sensitive data securely
- Use environment-specific settings
โ Configuration Don'tsโ
- Don't hardcode connection strings
- Don't store credentials in source control
- Don't skip configuration validation
- Don't use development settings in production
โ Usage Do'sโ
- Use dependency injection for storage services
- Implement proper error handling
- Validate file types and sizes
- Use async/await for all operations
- Implement logging and monitoring
โ Usage Don'tsโ
- Don't block on async operations
- Don't trust user-provided filenames
- Don't store large files in memory
- Don't skip authorization checks
- Don't ignore storage operation errors
๐ Integration Examplesโ
With File Upload APIโ
[ApiController]
[Route("api/files")]
public class FileUploadController : ControllerBase
{
private readonly IBlobStorage _storage;
[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file)
{
var fileName = $"{Guid.NewGuid()}{Path.GetExtension(file.FileName)}";
using var stream = file.OpenReadStream();
await _storage.WriteAsync(fileName, stream);
return Ok(new { FileName = fileName });
}
}
With User Avatar Serviceโ
public class UserAvatarService
{
private readonly IBlobStorage _storage;
private readonly IUserService _userService;
public async Task<string> UploadAvatarAsync(Guid userId, IFormFile avatar)
{
var fileName = $"avatars/{userId}.jpg";
using var stream = avatar.OpenReadStream();
// Process image (resize, crop, etc.)
var processedStream = await ProcessAvatarImageAsync(stream);
await _storage.WriteAsync(fileName, processedStream);
// Update user profile
await _userService.UpdateAvatarAsync(userId, fileName);
return fileName;
}
}
With Document Managementโ
public class DocumentService
{
private readonly IBlobStorage _storage;
private readonly IDocumentRepository _repository;
public async Task<Document> UploadDocumentAsync(DocumentUploadRequest request)
{
var document = new Document
{
Id = Guid.NewGuid(),
Name = request.Name,
FileName = $"{Guid.NewGuid()}{Path.GetExtension(request.File.FileName)}",
ContentType = request.File.ContentType,
Size = request.File.Length,
UploadedAt = DateTime.UtcNow
};
using var stream = request.File.OpenReadStream();
await _storage.WriteAsync(document.FileName, stream);
await _repository.AddAsync(document);
return document;
}
}
๐ฏ Next Stepsโ
- EmailSender Utility - Professional email delivery
- Framework Overview - Core framework concepts
- Examples - Complete implementation examples
๐ Related Documentationโ
- Utilities Overview - All available utilities
- EmailSender Utility - Email delivery solution
- Framework Application Layer - Base service patterns
- Configuration Best Practices - Configuration guidelines