S3mphony 2026.1.18.400
dotnet add package S3mphony --version 2026.1.18.400
NuGet\Install-Package S3mphony -Version 2026.1.18.400
<PackageReference Include="S3mphony" Version="2026.1.18.400" />
<PackageVersion Include="S3mphony" Version="2026.1.18.400" />
<PackageReference Include="S3mphony" />
paket add S3mphony --version 2026.1.18.400
#r "nuget: S3mphony, 2026.1.18.400"
#:package S3mphony@2026.1.18.400
#addin nuget:?package=S3mphony&version=2026.1.18.400
#tool nuget:?package=S3mphony&version=2026.1.18.400
S3mphony
AWS S3 Abstraction & Infrastructure Utility
S3mphony is a lightweight, generic, developer-friendly library that simplifies Amazon S3 interactions for APIs and services.
Designed for keeping code clean and efficient,
octet-streamsupport for uploading ML models, providingJSONdeserialization/serialization simplifications, and upload/download utilities for specific use cases.Includes
CsvHelperfor reading and writing large CSV files via streaming as opposed to typical uploads and downloads.Built-in support with Microsoft's
IMemoryCacherequest-level concurrency gating, avoid cache pollution from empty results, and keep applications responsive under load.Includes a semaphore lock to prevent data corruption and fault tolerance. In a distributed environment, connecting to the same bucket, this is an important inclusion.
- This is distinct from 'S3 Object Lock', designed to prevent objects from being deleted or overwritten.
From a developer’s perspective, everything is simplified: intuitive methods, sensible defaults, and a storage model that feels effortless while staying efficient behind the scenes.
Ideal for APIs, background workers, dashboards, and ML-ops that need resilient, low-cost, cache-aware access to S3.
It turns S3 into a database, in an odd way.
Update to support parallel downloads from S3 while deserializing.
Changed:
foreach (string? key in filteredKeys) {
byte[]? data = await DownloadBytesAsync(key, ct);
T? value = JsonSerializer.Deserialize<T>(data, jsonOptions);
if (value != null) {
results.Add(value);
}
}
To this:
// Concurrent download and deserialize all matching objects
T?[] values = await Task.WhenAll(
filteredKeys.Select(async key =>
JsonSerializer.Deserialize<T>(
await DownloadBytesAsync(key, ct),
_jsonOptions()))
);
Getting Started
AWS deployment example with appsettings.json configuration for configuration:
{
"AWS": {
"Profile: "development",
"Region: "ca-central-1",
"S3": {
"AccessKeyId": "...",
"SecretAccessKey: "...",
"BucketName: "my-bucket-name"
}
}
}
}
Azure deployments
- Use double underscores to separate the nested configuration sections.
- For example,
AWS__S3__AccessKeyId,AWS__S3__SecretAccessKey, andAWS__S3__BucketName.
Example installation and setup:
- Included is
S3Options, a C# class to hold S3 configuration options. - Then, in your
Startup.csorProgram.cs, wherever you configure services, add the following:
S3Options s3Options = builder.Configuration["AWS:S3"];
builder.Configuration.Bind(s3Options);
services.Configure<S3Options>(config.GetSection("AWS:S3"));
services.AddSingleton<S3Settings>(s3Options);
services.AddSingleton<S3StorageUtility>(s3Options);
services.AddSingleton<S3Channel>();
Additional service registrations for caching.
builder.Services.AddOutputCache();
builder.Services.AddMemoryCache();
- This allows you to inject
S3StorageUtilitywherever needed in your application to interact with S3 storage. - You can also inject the
S3Settingsdirectly if you need access to the configuration values. - Make sure to replace the placeholder values in
appsettings.jsonwith your actual AWS S3 credentials and bucket information.
Example usage of S3StorageUtility:
ImportantBusinessDocumentData meetingNotes = new()
{
Id = Guid.NewGuid(),
Text = meetingNotes,
MeetingDate = DateTime.Now
};
var createdKey = await _s3Channel.PutStructureAsync(
meetingNotes,
prefix: "meeting-notes/,
overwrite: false,
ct: ct);
Example retrieval of data for displaying in an application:
IEnumerable<ImportantBusinessDocumentData>? meetingNotes = Cache.Get<IEnumerable<ImportantBusinessDocumentData>>(_recentMeetingNotesCached) ?? null;
if (meetingNotes == null || !meetingNotes.Any()) {
meetingNotes = await s3Client.GetFromJsonAsync<IEnumerable<ImportantBusinessDocumentData>>("meeting-notes/") ?? null;
if (meetingNotes != null && meetingNotes.Any())
{
Cache.Set(_recentMeetingNotesCached, meetingNotes, _memoryCacheEntryOptions);
}
}
Then you've got your cool designed UI with whatever library. A simple example using FluentUI for Blazor:
@using Microsoft.Extensions.Caching.Memory
@using Microsoft.FluentUI.AspNetCore.Components
@inject IMemoryCache Cache
@inject IS3Channel S3Channel
<FluentCard style="width: 100%; padding: 16px;">
<div style="display:flex; align-items:center; gap:12px; justify-content:space-between;">
<div>
<FluentText Typography="Typography.PaneHeader">Meeting Notes</FluentText>
<FluentText Typography="Typography.Body" style="opacity:.8;">
Recent notes from S3, cached locally when non-empty.
</FluentText>
</div>
<div style="display:flex; gap:8px; align-items:center;">
<FluentTextField @bind-Value="_query"
Placeholder="Search text…"
Style="min-width: 260px;"
Immediate="true" />
<FluentButton Appearance="Appearance.Accent" OnClick="RefreshAsync" Disabled="@_busy">
@(_busy ? "Loading…" : "Refresh")
</FluentButton>
</div>
</div>
<div style="margin-top: 14px;">
@if (_busy && _notes.Count == 0)
{
<FluentProgressRing />
}
else if (_notes.Count == 0)
{
<FluentMessageBar Intent="MessageIntent.Info">
No meeting notes yet. Upload one and it’ll appear here.
</FluentMessageBar>
}
else
{
<FluentDataGrid Items="@FilteredNotes"
ResizableColumns="true"
GridTemplateColumns="1fr 170px 120px"
RowClass="row-hover"
Style="width: 100%;">
<PropertyColumn Title="Summary" Property="@(n => Preview(n.Text))" />
<PropertyColumn Title="Meeting Date" Property="@(n => n.MeetingDate.ToString("yyyy-MM-dd HH:mm"))" />
<TemplateColumn Title="">
<FluentButton Appearance="Appearance.Stealth" OnClick="@(async () => OpenAsync(context))">
View
</FluentButton>
</TemplateColumn>
</FluentDataGrid>
}
</div>
</FluentCard>
<FluentDialog @bind-Visible="_dialogOpen" Modal="true" Style="width: min(900px, 92vw);">
<DialogTitle>
<FluentText Typography="Typography.Title">Meeting Note</FluentText>
</DialogTitle>
<DialogContent>
@if (_selected is not null)
{
<FluentText Typography="Typography.Subtitle">
@_selected.MeetingDate.ToString("f")
</FluentText>
<FluentDivider />
<div style="white-space: pre-wrap; line-height: 1.35; margin-top: 10px;">
@_selected.Text
</div>
}
</DialogContent>
<DialogActions>
<FluentButton Appearance="Appearance.Outline" OnClick="@(() => _dialogOpen = false)">Close</FluentButton>
</DialogActions>
</FluentDialog>
@code {
private const string Prefix = "meeting-notes/"; // no leading slash is usually cleaner for S3 keys
private const string CacheKey = "recent:meeting-notes"; // local memory cache key
private static readonly MemoryCacheEntryOptions CacheOptions = new()
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10),
Priority = CacheItemPriority.High
};
private readonly List<ImportantBusinessDocumentData> _notes = new();
private ImportantBusinessDocumentData? _selected;
private bool _busy;
private bool _dialogOpen;
private string _query = "";
protected override async Task OnInitializedAsync()
=> await LoadAsync(forceRefresh: false);
private IEnumerable<ImportantBusinessDocumentData> FilteredNotes =>
string.IsNullOrWhiteSpace(_query)
? _notes
: _notes.Where(n => (n.Text ?? "").Contains(_query, StringComparison.OrdinalIgnoreCase));
private async Task RefreshAsync()
=> await LoadAsync(forceRefresh: true);
private async Task LoadAsync(bool forceRefresh)
{
_busy = true;
try
{
if (!forceRefresh &&
Cache.TryGetValue(CacheKey, out List<ImportantBusinessDocumentData>? cached) &&
cached is { Count: > 0 })
{
_notes.Clear();
_notes.AddRange(cached.OrderByDescending(n => n.MeetingDate));
return;
}
// Pull most recent notes from S3 (your library)
var fromS3 = await S3Channel.GetRecentStructuresAsync<ImportantBusinessDocumentData>(
prefix: Prefix,
takeMostRecent: 100,
ct: CancellationToken.None);
_notes.Clear();
_notes.AddRange(fromS3.OrderByDescending(n => n.MeetingDate));
// Cache only if signal
if (_notes.Count > 0)
Cache.Set(CacheKey, _notes.ToList(), CacheOptions);
}
finally
{
_busy = false;
}
}
private Task OpenAsync(ImportantBusinessDocumentData note)
{
_selected = note;
_dialogOpen = true;
return Task.CompletedTask;
}
private static string Preview(string? text)
{
if (string.IsNullOrWhiteSpace(text)) return "(empty)";
text = text.Trim().Replace("\r", " ").Replace("\n", " ");
return text.Length <= 90 ? text : text[..90] + "…";
}
}
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | 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
- Amazon.Lambda.Annotations (>= 1.8.0)
- Amazon.Lambda.Core (>= 2.8.0)
- Amazon.Lambda.Serialization.SystemTextJson (>= 2.4.4)
- Amazon.Lambda.SQSEvents (>= 2.2.0)
- AWS.Messaging.Lambda (>= 1.0.2)
- AWSSDK.Core (>= 4.0.3.6)
- AWSSDK.Extensions.NETCore.Setup (>= 4.0.3.17)
- AWSSDK.S3 (>= 4.0.16)
- CsvHelper (>= 33.1.0)
- Microsoft.AspNetCore.OpenApi (>= 10.0.1)
- Microsoft.Extensions.Hosting (>= 10.0.1)
- Microsoft.ML (>= 5.0.0)
- Microsoft.ML.AutoML (>= 0.23.0)
- Microsoft.ML.Recommender (>= 0.23.0)
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 |
|---|---|---|
| 2026.1.18.400 | 82 | 1/18/2026 |
| 2026.1.7.401 | 91 | 1/7/2026 |
| 2026.1.7.359 | 97 | 1/7/2026 |
| 2026.1.7.351 | 95 | 1/7/2026 |
| 2026.1.3.238 | 105 | 1/3/2026 |
| 2026.1.3.232 | 97 | 1/3/2026 |
| 2026.1.2.1928 | 95 | 1/2/2026 |
| 2025.12.27.1627 | 89 | 12/27/2025 |
| 2025.12.27.943 | 87 | 12/27/2025 |
| 2025.12.27.445 | 85 | 12/27/2025 |
| 2025.12.26.2048 | 103 | 12/26/2025 |
| 2025.12.26.436 | 281 | 12/26/2025 |
| 2025.12.26.122 | 162 | 12/26/2025 |
| 2025.12.26.113 | 165 | 12/26/2025 |
| 1.0.0 | 176 | 12/25/2025 |