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