Benday.AzureStorage
1.2.0
dotnet add package Benday.AzureStorage --version 1.2.0
NuGet\Install-Package Benday.AzureStorage -Version 1.2.0
<PackageReference Include="Benday.AzureStorage" Version="1.2.0" />
<PackageVersion Include="Benday.AzureStorage" Version="1.2.0" />
<PackageReference Include="Benday.AzureStorage" />
paket add Benday.AzureStorage --version 1.2.0
#r "nuget: Benday.AzureStorage, 1.2.0"
#:package Benday.AzureStorage@1.2.0
#addin nuget:?package=Benday.AzureStorage&version=1.2.0
#tool nuget:?package=Benday.AzureStorage&version=1.2.0
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.21.0)
- Azure.Storage.Blobs (>= 12.29.1)
- Azure.Storage.Queues (>= 12.27.1)
- Benday.Common.Interfaces (>= 1.0.3)
- Microsoft.AspNetCore.StaticFiles (>= 2.3.11)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.9)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
-
net8.0
- Azure.Data.Tables (>= 12.11.0)
- Azure.Identity (>= 1.21.0)
- Azure.Storage.Blobs (>= 12.29.1)
- Azure.Storage.Queues (>= 12.27.1)
- Benday.Common.Interfaces (>= 1.0.3)
- Microsoft.AspNetCore.StaticFiles (>= 2.3.11)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.9)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
-
net9.0
- Azure.Data.Tables (>= 12.11.0)
- Azure.Identity (>= 1.21.0)
- Azure.Storage.Blobs (>= 12.29.1)
- Azure.Storage.Queues (>= 12.27.1)
- Benday.Common.Interfaces (>= 1.0.3)
- Microsoft.AspNetCore.StaticFiles (>= 2.3.11)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.9)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.9)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.9)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.9)
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.2.0 | 39 | 7/3/2026 |
| 1.1.0-alpha | 73 | 5/15/2026 |
| 1.0.1-alpha | 86 | 4/4/2026 |
| 1.0.0-alpha | 111 | 4/3/2026 |
Merged the former Benday.BlobStorage package into Benday.AzureStorage. BlobBridge (for connecting IBlobOwner entities to blob attachments) now lives in the Benday.AzureStorage.Blobs namespace. Also includes blob repositories, Table Storage repositories, typed queue clients, queue health checks, and DI helpers.