Benday.BlobStorage
1.0.1-alpha
dotnet add package Benday.BlobStorage --version 1.0.1-alpha
NuGet\Install-Package Benday.BlobStorage -Version 1.0.1-alpha
<PackageReference Include="Benday.BlobStorage" Version="1.0.1-alpha" />
<PackageVersion Include="Benday.BlobStorage" Version="1.0.1-alpha" />
<PackageReference Include="Benday.BlobStorage" />
paket add Benday.BlobStorage --version 1.0.1-alpha
#r "nuget: Benday.BlobStorage, 1.0.1-alpha"
#:package Benday.BlobStorage@1.0.1-alpha
#addin nuget:?package=Benday.BlobStorage&version=1.0.1-alpha&prerelease
#tool nuget:?package=Benday.BlobStorage&version=1.0.1-alpha&prerelease
Benday.BlobStorage
Connects entities implementing IBlobOwner to blob attachments in Azure Storage. Works with any storage backend -- Cosmos DB, Table Storage, EF Core, or anything else -- as long as the entity implements IBlobOwner.
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.BlobStorage --version 1.0.0-alpha
This package depends on Benday.AzureStorage which will be installed automatically.
How It Works
BlobBridge<T> wraps an IBlobRepository and prefixes all blob paths with the entity's GetBlobPrefix() value. This means each entity instance gets its own isolated folder of blob attachments.
Getting Started
1. Implement IBlobOwner on Your Entity
IBlobOwner requires a single method that returns the blob path prefix for that entity:
using Benday.Common.Interfaces;
// This entity can be stored anywhere -- Cosmos DB, EF Core, Table Storage, etc.
public class Invoice : IBlobOwner
{
public string TenantId { get; set; } = string.Empty;
public string InvoiceId { get; set; } = string.Empty;
// All blobs for this entity will be stored under this prefix.
// For example: "tenant-42/invoices/inv-123/"
public string GetBlobPrefix() => $"{TenantId}/invoices/{InvoiceId}/";
}
2. Register Azure Storage Services
In Program.cs, register the core services and a blob container:
builder.Services.AddBendayAzureStorage(builder.Configuration);
builder.Services.AddBlobRepository("my-container");
3. Use BlobBridge to Manage Attachments
using Benday.BlobStorage;
public class InvoiceAttachmentService
{
private readonly BlobBridge<Invoice> _bridge;
public InvoiceAttachmentService(IBlobRepository blobRepository)
{
_bridge = new BlobBridge<Invoice>(blobRepository);
}
// Upload from a stream
public async Task<string> AttachFileAsync(Invoice invoice, string filename, Stream content)
{
// Stores at "tenant-42/invoices/inv-123/receipt.pdf"
return await _bridge.AttachAsync(invoice, filename, content,
metadata: new Dictionary<string, string>
{
["uploaded-by"] = "system"
});
}
// Upload from a local file path
public async Task<string> AttachLocalFileAsync(Invoice invoice, string localPath)
{
var filename = Path.GetFileName(localPath);
return await _bridge.AttachFileAsync(invoice, filename, localPath);
}
// Upload from a byte array
public async Task<string> AttachBytesAsync(Invoice invoice, string filename, byte[] data)
{
return await _bridge.AttachBytesAsync(invoice, filename, data);
}
// List all attachments for an entity
public async IAsyncEnumerable<string> ListAttachmentNamesAsync(Invoice invoice)
{
await foreach (var blob in _bridge.ListAttachmentsAsync(invoice))
{
yield return blob.Name;
}
}
// Download an attachment as bytes
public async Task<byte[]> DownloadAttachmentAsync(Invoice invoice, string filename)
{
return await _bridge.DownloadAttachmentBytesAsync(invoice, filename);
}
// Download an attachment to a local file
public async Task DownloadToFileAsync(Invoice invoice, string filename, string localPath)
{
await _bridge.DownloadAttachmentToFileAsync(invoice, filename, localPath);
}
// Get a time-limited download URL
public Uri GetDownloadUrl(Invoice invoice, string filename)
{
return _bridge.GetAttachmentSasUri(
invoice,
filename,
expiry: TimeSpan.FromMinutes(15),
downloadFilename: filename); // Sets Content-Disposition for browser downloads
}
// Check if an attachment exists
public async Task<bool> HasAttachmentAsync(Invoice invoice, string filename)
{
return await _bridge.AttachmentExistsAsync(invoice, filename);
}
// Delete a specific attachment
public async Task DeleteAttachmentAsync(Invoice invoice, string filename)
{
await _bridge.DeleteAttachmentAsync(invoice, filename);
}
// Delete all attachments for an entity
public async Task DeleteAllAttachmentsAsync(Invoice invoice)
{
await _bridge.DeleteAttachmentsAsync(invoice, maxConcurrency: 25);
}
}
Combining with Table Storage
An entity can implement both IBlobOwner and ITableEntity to store its data in Table Storage while keeping file attachments in Blob Storage:
using Azure;
using Azure.Data.Tables;
using Benday.Common.Interfaces;
public class ProjectDocument : ITableEntity, IEntityIdentity<string>, IBlobOwner
{
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; }
public string ProjectId { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty;
public string GetBlobPrefix() => $"projects/{ProjectId}/docs/{RowKey}/";
}
public class ProjectDocumentService
{
private readonly ITableRepository<ProjectDocument> _tableRepo;
private readonly BlobBridge<ProjectDocument> _blobBridge;
public ProjectDocumentService(
ITableRepository<ProjectDocument> tableRepo,
IBlobRepository blobRepo)
{
_tableRepo = tableRepo;
_blobBridge = new BlobBridge<ProjectDocument>(blobRepo);
}
public async Task<ProjectDocument> CreateDocumentAsync(
string projectId, string title, string filename, Stream fileContent)
{
var doc = new ProjectDocument
{
Id = Guid.NewGuid().ToString(),
RowKey = Guid.NewGuid().ToString(),
PartitionKey = projectId,
ProjectId = projectId,
Title = title
};
// Save metadata to Table Storage
await _tableRepo.SaveAsync(doc);
// Attach the file to Blob Storage
await _blobBridge.AttachAsync(doc, filename, fileContent);
return doc;
}
public async Task DeleteDocumentAsync(string projectId, string documentId)
{
var doc = await _tableRepo.GetByIdAsync(projectId, documentId);
if (doc != null)
{
// Delete blobs first, then the table row
await _blobBridge.DeleteAttachmentsAsync(doc);
await _tableRepo.DeleteAsync(projectId, documentId);
}
}
}
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
- Benday.AzureStorage (>= 1.0.1-alpha)
- Benday.Common.Interfaces (>= 1.0.0-alpha)
-
net8.0
- Benday.AzureStorage (>= 1.0.1-alpha)
- Benday.Common.Interfaces (>= 1.0.0-alpha)
-
net9.0
- Benday.AzureStorage (>= 1.0.1-alpha)
- Benday.Common.Interfaces (>= 1.0.0-alpha)
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 |
|---|---|---|
| 1.0.1-alpha | 50 | 4/4/2026 |
| 1.0.0-alpha | 50 | 4/3/2026 |
Initial alpha release. BlobBridge for connecting IBlobOwner entities to blob attachments in Azure Storage.