Benday.AzureStorage
1.0.1-alpha
dotnet add package Benday.AzureStorage --version 1.0.1-alpha
NuGet\Install-Package Benday.AzureStorage -Version 1.0.1-alpha
<PackageReference Include="Benday.AzureStorage" Version="1.0.1-alpha" />
<PackageVersion Include="Benday.AzureStorage" Version="1.0.1-alpha" />
<PackageReference Include="Benday.AzureStorage" />
paket add Benday.AzureStorage --version 1.0.1-alpha
#r "nuget: Benday.AzureStorage, 1.0.1-alpha"
#:package Benday.AzureStorage@1.0.1-alpha
#addin nuget:?package=Benday.AzureStorage&version=1.0.1-alpha&prerelease
#tool nuget:?package=Benday.AzureStorage&version=1.0.1-alpha&prerelease
Benday.AzureStorage
Simplified Azure Storage access for .NET -- blob repositories, Table Storage repositories, typed queue clients, queue health checks, and DI helpers.
Targets .NET 8.0, 9.0, and 10.0.
Written by Benjamin Day Pluralsight Author | Microsoft MVP https://www.benday.com https://www.honestcheetah.com info@benday.com YouTube: https://www.youtube.com/@_benday
Installation
dotnet add package Benday.AzureStorage --version 1.0.0-alpha
Configuration
Add an AzureStorage section to your appsettings.json:
{
"AzureStorage": {
"ConnectionString": "your-connection-string",
"CreateStructures": true
}
}
For local development with Azurite:
{
"AzureStorage": {
"UseDevelopmentStorage": true,
"CreateStructures": true
}
}
For Managed Identity (DefaultAzureCredential):
{
"AzureStorage": {
"AccountName": "mystorageaccount",
"UseDefaultAzureCredential": true,
"CreateStructures": true
}
}
Setting CreateStructures to true automatically creates containers, tables, and queues if they don't exist.
Service Registration
Register core Azure Storage services in Program.cs:
builder.Services.AddBendayAzureStorage(builder.Configuration);
This registers AzureStorageConfig, BlobServiceClient, TableServiceClient, QueueClientFactory, and IMimeTypeUtil as singletons.
Then register the specific resources you need:
// Blob container
builder.Services.AddBlobRepository("my-container");
// Table Storage repository
builder.Services.AddTableRepository<CustomerRepository, CustomerEntity>();
// Typed queue
builder.Services.AddTypedQueue<OrderMessage>("order-queue", opts =>
{
opts.MaxDequeueCount = 3;
});
Table Storage
Define an Entity
Table Storage entities must implement both ITableEntity (from Azure.Data.Tables) and IEntityIdentity<string> (from Benday.Common.Interfaces):
using Azure;
using Azure.Data.Tables;
using Benday.Common.Interfaces;
public class CustomerEntity : ITableEntity, IEntityIdentity<string>
{
public string Id { get; set; } = string.Empty;
public string PartitionKey { get; set; } = "default";
public string RowKey { get; set; } = string.Empty;
public DateTimeOffset? Timestamp { get; set; }
public ETag ETag { get; set; }
// Your custom properties
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
public string Region { get; set; } = string.Empty;
}
Create a Repository
Extend TableRepository<T> and provide the table name:
using Benday.AzureStorage.Tables;
public class CustomerRepository : TableRepository<CustomerEntity>
{
public CustomerRepository(
TableServiceClient serviceClient,
AzureStorageConfig config,
ILogger<CustomerRepository> logger)
: base(serviceClient, config, logger)
{
}
protected override string TableName => "customers";
// Optional: override the default partition key
protected override string DefaultPartitionKey => "default";
}
Use the Repository
public class CustomerService
{
private readonly ITableRepository<CustomerEntity> _repo;
public CustomerService(ITableRepository<CustomerEntity> repo)
{
_repo = repo;
}
public async Task CreateCustomerAsync(string name, string email, string region)
{
var customer = new CustomerEntity
{
Id = Guid.NewGuid().ToString(),
RowKey = Guid.NewGuid().ToString(),
PartitionKey = region,
Name = name,
Email = email,
Region = region
};
await _repo.SaveAsync(customer);
}
public async Task<CustomerEntity?> GetCustomerAsync(string region, string id)
{
return await _repo.GetByIdAsync(partitionKey: region, rowKey: id);
}
public async Task<IList<CustomerEntity>> GetCustomersByRegionAsync(string region)
{
return await _repo.GetAllAsync(partitionKey: region);
}
public async Task<IList<CustomerEntity>> SearchByNameAsync(string namePrefix)
{
return await _repo.QueryAsync(c => c.Name.CompareTo(namePrefix) >= 0);
}
public async Task DeleteCustomerAsync(string region, string id)
{
await _repo.DeleteAsync(partitionKey: region, rowKey: id);
}
}
The overloads that take only a string id (no partition key) use the DefaultPartitionKey value automatically.
Blob Repository
public class ReportService
{
private readonly IBlobRepository _blobs;
public ReportService(IBlobRepository blobs)
{
_blobs = blobs;
}
public async Task<string> UploadReportAsync(string path, Stream content)
{
return await _blobs.UploadAsync(path, content);
}
public async Task<byte[]> DownloadReportAsync(string path)
{
return await _blobs.DownloadBytesAsync(path);
}
public async Task<bool> ReportExistsAsync(string path)
{
return await _blobs.ExistsAsync(path);
}
public Uri GetReportUrl(string path)
{
return _blobs.GetSasUri(path, expiry: TimeSpan.FromHours(1));
}
}
Typed Queues
public class OrderMessage
{
public string OrderId { get; set; } = string.Empty;
public string CustomerId { get; set; } = string.Empty;
public decimal Total { get; set; }
}
public class OrderQueueService
{
private readonly TypedQueueClient<OrderMessage> _queue;
public OrderQueueService(TypedQueueClient<OrderMessage> queue)
{
_queue = queue;
}
public async Task EnqueueOrderAsync(string orderId, string customerId, decimal total)
{
await _queue.SendAsync(new OrderMessage
{
OrderId = orderId,
CustomerId = customerId,
Total = total
});
}
public async Task ProcessNextAsync()
{
var message = await _queue.ReceiveAsync(visibilityTimeout: TimeSpan.FromMinutes(5));
if (message == null)
return; // Queue is empty
// Check for poison messages first
if (await _queue.MoveToPoisonIfNeededAsync(message))
return; // Moved to poison queue
try
{
Console.WriteLine($"Processing order {message.Body!.OrderId}");
await _queue.CompleteAsync(message);
}
catch
{
// Release back to queue for retry
await _queue.ReleaseAsync(message, visibilityDelay: TimeSpan.FromSeconds(30));
throw;
}
}
}
Messages are JSON-serialized and Base64-encoded automatically. Messages that exceed MaxDequeueCount are moved to a poison queue (default name: {queueName}-poison).
Health Checks
Monitor queue depth with ASP.NET Core health checks:
builder.Services.AddHealthChecks()
.AddAzureQueueCheck("order-queue-health", opts =>
{
opts.QueueName = "order-queue";
opts.DegradedThreshold = 50; // Degraded when >= 50 messages
opts.UnhealthyThreshold = 200; // Unhealthy when >= 200 messages
opts.PoisonIsUnhealthy = true; // Any poison messages = unhealthy
});
License
MIT
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. 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 is compatible. 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. |
-
net10.0
- Azure.Data.Tables (>= 12.11.0)
- Azure.Identity (>= 1.20.0)
- Azure.Storage.Blobs (>= 12.27.0)
- Azure.Storage.Queues (>= 12.25.0)
- Benday.Common.Interfaces (>= 1.0.0-alpha)
- Microsoft.AspNetCore.StaticFiles (>= 2.3.9)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
-
net8.0
- Azure.Data.Tables (>= 12.11.0)
- Azure.Identity (>= 1.20.0)
- Azure.Storage.Blobs (>= 12.27.0)
- Azure.Storage.Queues (>= 12.25.0)
- Benday.Common.Interfaces (>= 1.0.0-alpha)
- Microsoft.AspNetCore.StaticFiles (>= 2.3.9)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
-
net9.0
- Azure.Data.Tables (>= 12.11.0)
- Azure.Identity (>= 1.20.0)
- Azure.Storage.Blobs (>= 12.27.0)
- Azure.Storage.Queues (>= 12.25.0)
- Benday.Common.Interfaces (>= 1.0.0-alpha)
- Microsoft.AspNetCore.StaticFiles (>= 2.3.9)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.5)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.5)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.5)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.5)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on Benday.AzureStorage:
| Package | Downloads |
|---|---|
|
Benday.BlobStorage
Connects entities with blob attachments in Azure Storage. Works with any storage backend — Cosmos DB, Table Storage, EF Core — as long as the entity implements IBlobOwner. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.1-alpha | 30 | 4/4/2026 |
| 1.0.0-alpha | 37 | 4/3/2026 |
Initial alpha release. Blob repositories, Table Storage repositories, typed queue clients, queue health checks, and DI helpers for Azure Storage.