Rystem.Content.Abstractions
10.0.7
dotnet add package Rystem.Content.Abstractions --version 10.0.7
NuGet\Install-Package Rystem.Content.Abstractions -Version 10.0.7
<PackageReference Include="Rystem.Content.Abstractions" Version="10.0.7" />
<PackageVersion Include="Rystem.Content.Abstractions" Version="10.0.7" />
<PackageReference Include="Rystem.Content.Abstractions" />
paket add Rystem.Content.Abstractions --version 10.0.7
#r "nuget: Rystem.Content.Abstractions, 10.0.7"
#:package Rystem.Content.Abstractions@10.0.7
#addin nuget:?package=Rystem.Content.Abstractions&version=10.0.7
#tool nuget:?package=Rystem.Content.Abstractions&version=10.0.7
Rystem.Content.Abstractions
Rystem.Content.Abstractions contains the shared content API, the registration builder, and the cross-provider migration service.
It does not implement storage by itself. Real storage arrives through provider packages such as Blob, File Share, SharePoint, or InMemory.
Installation
dotnet add package Rystem.Content.Abstractions
What this package adds
This package defines:
IContentRepositoryIContentRepositoryBuilderContentRepositoryOptionsContentRepositoryResultContentRepositoryDownloadResultContentInformationTypeIContentMigration
At DI level, AddContentRepository() currently registers:
IContentMigrationasTransient
Provider registrations are added later through With...Integration(...) extension methods.
Architecture
The core model is intentionally small:
IContentRepositoryis the only storage contractAddContentRepository()returns anIContentRepositoryBuilder- providers register named implementations through the shared factory system
- migrations resolve source and destination repositories from
IFactory<IContentRepository>
In the current source tree and tests, named resolution is done with IFactory<IContentRepository>, not an IContentRepositoryFactory abstraction.
Registration API
IContentRepositoryBuilder exposes three registration shapes:
| Method | Use when | Default lifetime |
|---|---|---|
WithIntegration<TRepository>(name, lifetime) |
repository has no options object | Transient |
WithIntegration<TRepository, TOptions>(options, name, lifetime) |
repository needs synchronous options wiring | Transient |
WithIntegrationAsync<TRepository, TOptions, TConnection>(options, name, lifetime) |
repository setup is asynchronous and builds a connection wrapper | Transient |
The built-in Blob, File, and SharePoint providers sit on top of WithIntegrationAsync(...). The InMemory provider uses WithIntegration(...) and overrides the lifetime to Singleton.
The provider-specific WithBlobStorageIntegration(...), WithFileStorageIntegration(...), WithSharepointIntegration(...), and WithInMemoryIntegration(...) extensions are defined by the provider packages, not by this package itself.
Minimal setup
var repositories = services.AddContentRepository();
repositories.WithInMemoryIntegration("inmemory");
When you need a provider with async setup:
var repositories = services.AddContentRepository();
await repositories.WithBlobStorageIntegrationAsync(options =>
{
options.ContainerName = "supertest";
options.ConnectionString = configuration["ConnectionString:Storage"];
}, "blobstorage");
Consuming named repositories
The content tests resolve repositories like this:
public sealed class ContentService
{
private readonly IContentRepository _contentRepository;
public ContentService(IFactory<IContentRepository> factory)
=> _contentRepository = factory.Create("blobstorage");
}
IContentRepository contract
| Method | Return type | Purpose |
|---|---|---|
ListAsync(prefix, downloadContent, informationRetrieve) |
IAsyncEnumerable<ContentRepositoryDownloadResult> |
Enumerate files with optional prefix filtering |
DownloadAsync(path, informationRetrieve) |
Task<ContentRepositoryDownloadResult?> |
Download bytes and optional metadata |
GetPropertiesAsync(path, informationRetrieve) |
Task<ContentRepositoryResult?> |
Read metadata without downloading bytes |
UploadAsync(path, data, options, overwrite) |
ValueTask<bool> |
Create or replace a file |
SetPropertiesAsync(path, options) |
ValueTask<bool> |
Update headers, metadata, or tags |
DeleteAsync(path) |
ValueTask<bool> |
Delete a file |
ExistAsync(path) |
ValueTask<bool> |
Check whether a file exists |
Important caveat: overwrite is part of the shared interface, but not every provider enforces it the same way.
Shared models
ContentRepositoryOptions
var options = new ContentRepositoryOptions
{
HttpHeaders = new ContentRepositoryHttpHeaders
{
ContentType = "image/png",
CacheControl = "max-age=3600",
ContentDisposition = "attachment; filename=image.png"
},
Metadata = new Dictionary<string, string>
{
["author"] = "alice"
},
Tags = new Dictionary<string, string>
{
["version"] = "1"
}
};
ContentInformationType
| Value | Meaning |
|---|---|
None |
no extra metadata |
HttpHeaders |
include content headers |
Metadata |
include metadata dictionary |
Tags |
include tags when supported |
All |
HttpHeaders | Metadata | Tags |
Result models
| Type | Properties |
|---|---|
ContentRepositoryResult |
Path, Uri, Options |
ContentRepositoryDownloadResult |
everything in ContentRepositoryResult plus Data |
Path and Uri are provider-defined. Do not assume they have identical semantics across Blob, File Share, SharePoint, and InMemory.
Migration service
IContentMigration copies content between named providers.
This example matches the usage style in src/Content/Rystem.Content.Tests/Rystem.Content.UnitTest/Integrations/AllStorageTest.cs.
ContentMigrationResult result = await contentMigration.MigrateAsync(
sourceName: "inmemory",
destinationName: "filestorage",
settings: options =>
{
options.Prefix = "Test/Folder1/";
options.OverwriteIfExists = true;
options.Predicate = item => item.Path?.Contains("fileName6") != true;
options.ModifyDestinationPath = path => path.Replace("Folder2", "Folder3");
});
Available settings:
| Property | Default | Meaning |
|---|---|---|
Prefix |
null |
restrict source enumeration |
Predicate |
null |
skip items that do not match |
OverwriteIfExists |
false |
pass overwrite intent to destination upload |
OnErrorContinue |
true |
keep going after per-file errors |
ModifyDestinationPath |
null |
rewrite the output path |
Migration behavior notes
The current implementation is a straightforward copy loop:
- it resolves both repositories through
IFactory<IContentRepository> - it enumerates source items with
ListAsync(..., downloadContent: false, ContentInformationType.None) - it downloads each matched item with
ContentInformationType.All - it uploads the downloaded bytes plus options to the destination repository
Important caveats from the source:
NotMigratedPathsexists onContentMigrationResult, but the current implementation never fills it- when an upload returns
false, the result currently lands inNotContentPaths - there is no provider-native server-side copy or batching logic
Writing a custom provider
If you want your own backend, implement IContentRepository and register it through the builder.
internal sealed class MyContentRepository : IContentRepository
{
public IAsyncEnumerable<ContentRepositoryDownloadResult> ListAsync(
string? prefix = null,
bool downloadContent = false,
ContentInformationType informationRetrieve = ContentInformationType.None,
CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
public Task<ContentRepositoryDownloadResult?> DownloadAsync(
string path,
ContentInformationType informationRetrieve = ContentInformationType.None,
CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
public Task<ContentRepositoryResult?> GetPropertiesAsync(
string path,
ContentInformationType informationRetrieve = ContentInformationType.All,
CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
public ValueTask<bool> UploadAsync(
string path,
byte[] data,
ContentRepositoryOptions? options = null,
bool overwrite = true,
CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
public ValueTask<bool> SetPropertiesAsync(
string path,
ContentRepositoryOptions? options = null,
CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
public ValueTask<bool> DeleteAsync(string path, CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
public ValueTask<bool> ExistAsync(string path, CancellationToken cancellationToken = default)
=> throw new NotImplementedException();
}
Then register it:
services
.AddContentRepository()
.WithIntegration<MyContentRepository>("custom");
Related packages
Rystem.Content.Infrastructure.Storage.BlobRystem.Content.Infrastructure.Storage.FileRystem.Content.Infrastructure.M365.SharepointRystem.Content.Infrastructure.InMemory
Use this package when you want the contract and migration layer; add a provider package when you want real storage.
| 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
- Rystem.DependencyInjection (>= 10.0.7)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on Rystem.Content.Abstractions:
| Package | Downloads |
|---|---|
|
Rystem.Content.Infrastructure.M365.Sharepoint
Rystem.Content helps you to integrate with azure services or to create an abstraction layer among your infrastructure and your business. |
|
|
Rystem.Content.Infrastructure.Storage.Blob
Rystem.Content helps you to integrate with azure services or to create an abstraction layer among your infrastructure and your business. |
|
|
Rystem.Content.Infrastructure.InMemory
Rystem.Content helps you to integrate with azure services or to create an abstraction layer among your infrastructure and your business. |
|
|
Rystem.Content.Infrastructure.Storage.File
Rystem.Content helps you to integrate with azure services or to create an abstraction layer among your infrastructure and your business. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 10.0.7 | 80 | 3/26/2026 |
| 10.0.6 | 170,728 | 3/3/2026 |
| 10.0.5 | 198 | 2/22/2026 |
| 10.0.4 | 213 | 2/9/2026 |
| 10.0.3 | 147,975 | 1/28/2026 |
| 10.0.1 | 209,176 | 11/12/2025 |
| 9.1.3 | 434 | 9/2/2025 |
| 9.1.2 | 764,549 | 5/29/2025 |
| 9.1.1 | 97,902 | 5/2/2025 |
| 9.0.32 | 186,762 | 4/15/2025 |
| 9.0.31 | 5,880 | 4/2/2025 |
| 9.0.30 | 88,930 | 3/26/2025 |
| 9.0.29 | 9,061 | 3/18/2025 |
| 9.0.28 | 299 | 3/17/2025 |
| 9.0.27 | 302 | 3/16/2025 |
| 9.0.26 | 321 | 3/13/2025 |
| 9.0.25 | 52,189 | 3/9/2025 |
| 9.0.23 | 275 | 3/9/2025 |
| 9.0.21 | 404 | 3/6/2025 |
| 9.0.20 | 19,619 | 3/6/2025 |