Skip to main content

Resumable Session Stores

Vali-Blob tracks resumable upload sessions using a pluggable IResumableSessionStore. The session store records each in-progress upload's state: upload ID, target path, total file size, bytes received, and expiry time. This state is needed to resume interrupted uploads and to assemble chunks in the correct order.


IResumableSessionStore Interface​

public interface IResumableSessionStore
{
Task SaveAsync(ResumableUploadSession session, CancellationToken ct = default);
Task<ResumableUploadSession?> GetAsync(string uploadId, CancellationToken ct = default);
Task UpdateAsync(ResumableUploadSession session, CancellationToken ct = default);
Task DeleteAsync(string uploadId, CancellationToken ct = default);
}
MethodWhen called
SaveAsyncStartResumableUploadAsync — creates the session record
GetAsyncUploadChunkAsync, CompleteResumableUploadAsync, GetResumableUploadStatusAsync — retrieve session state
UpdateAsyncAfter each chunk is successfully stored — advances UploadedBytes
DeleteAsyncCompleteResumableUploadAsync and AbortResumableUploadAsync — clean up

ResumableUploadSession Model​

public class ResumableUploadSession
{
public string UploadId { get; set; } = default!;
public string Path { get; set; } = default!;
public long TotalSize { get; set; }
public long UploadedBytes { get; set; }
public string? ContentType { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
public ResumableUploadStatus Status { get; set; }
}

public enum ResumableUploadStatus
{
Pending,
InProgress,
Complete,
Aborted
}

Available Session Stores​

InMemory (Default)​

The in-memory store uses a ConcurrentDictionary<string, ResumableUploadSession> held in process memory. No external dependencies required.

// InMemory is the default — no additional registration needed
builder.Services
.AddVali-Blob(o => o.DefaultProvider = "aws")
.AddProvider<AWSS3Provider>("aws", o => { /* ... */ });

// Explicit registration (optional, same effect)
builder.Services.AddVali-Blob().AddInMemorySessionStore();

Characteristics:

PropertyValue
Persistence across restartsNo
Multi-instance safeNo
Automatic TTLNo
Setup complexityNone
Latency~0 ms
Best forDevelopment, single-instance apps, tests

Limitation: If your application runs multiple instances (containers, pods), each instance has its own dictionary. A chunk upload routed to a different instance than the StartResumableUploadAsync call will fail with a session-not-found error. Use Redis or EF Core for multi-instance deployments.


Redis​

Vali-Blob.Redis provides RedisResumableSessionStore backed by StackExchange.Redis. Sessions are serialized to JSON and stored as Redis string keys with a sliding TTL.

See Redis Store for full configuration.


EF Core​

Vali-Blob.EFCore provides EfCoreResumableSessionStore, which persists sessions in a relational database table via Entity Framework Core.

See EF Core Store for full configuration.


Comparison Table​

FeatureInMemoryRedisEF Core
Persistence across restartsNoYesYes
Multi-instance safeNoYesYes (with care)
Automatic TTL / expiryNoYes (sliding)Optional (background job)
Query sessions by pathManual scanNo (key-based)Yes (LINQ)
Setup complexityNoneLowMedium (migration)
External dependencyNoneRedis serverSQL database
Latency~0 ms~0.5–2 ms~1–10 ms
Best forDev / TestProduction (stateless)Production (SQL-heavy)

Choosing a Session Store​

Is this for development or testing only?
YES → Use InMemory (zero setup, zero dependencies).

Do you already run Redis in your infrastructure?
YES → Use Redis (lowest overhead, automatic TTL, dead-simple setup).

Do you prefer a SQL database and already use EF Core?
YES → Use EF Core (queryable sessions, integrates with existing schema).

Single-instance app with restart-safe sessions, no Redis?
→ Use EF Core with SQLite for a lightweight embedded option.

Session Lifecycle​

loading...

If neither Complete nor Abort is ever called — because the client disconnected permanently — the session remains in the store until:

  • Redis TTL expires (Redis store), or
  • A background cleanup job removes it (InMemory / EF Core stores).

Cleaning Up Stale Sessions​

For InMemory and EF Core stores, add a background cleanup service:

public class StaleSessionCleanupService(
IResumableSessionStore store,
IStorageFactory factory,
ILogger<StaleSessionCleanupService> logger) : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
await Task.Delay(TimeSpan.FromHours(1), ct);

try
{
// EF Core store supports GetExpiredAsync — InMemory does not
if (store is EfCoreResumableSessionStore efStore)
{
var expired = await efStore.GetExpiredAsync(ct);

foreach (var session in expired)
{
var provider = (IResumableUploadProvider)factory.Create();
await provider.AbortResumableUploadAsync(session.UploadId, ct);
logger.LogInformation("Cleaned up stale session {UploadId}", session.UploadId);
}
}
}
catch (Exception ex)
{
logger.LogError(ex, "Error during stale session cleanup");
}
}
}
}

// Register
builder.Services.AddHostedService<StaleSessionCleanupService>();

Redis handles TTL-based expiry automatically — no cleanup service needed.


Implementing a Custom Session Store​

Any type that implements IResumableSessionStore and is registered in DI will be picked up by Vali-Blob automatically. See the DynamoDB example in the full session stores documentation.

// Register your custom store
builder.Services.AddSingleton<IResumableSessionStore, DynamoDbSessionStore>();

builder.Services
.AddVali-Blob(o => o.DefaultProvider = "aws")
.AddProvider<AWSS3Provider>("aws", o => { /* ... */ });
// Vali-Blob detects and uses your registered IResumableSessionStore automatically