Dotnet π§
Apache OpenDALβ’ .NET Binding
Note: This binding has its own independent version number, which may differ from the Rust core version. When checking for updates or compatibility, always refer to this binding's version rather than the core version.
Buildβ
To compile OpenDAL .NET binding from source code, you need:
- .NET SDK for
net8.0ornet10.0. - Rust toolchain for building the native library.
cargo build
dotnet build
dotnet test
Quickstartβ
using DotOpenDAL;
using System.Text;
using var executor = new Executor(2);
using var op = new Operator("memory");
await op.WriteAsync("demo.txt", Encoding.UTF8.GetBytes("hello"), executor);
var bytes = await op.ReadAsync("demo.txt", executor);
var text = Encoding.UTF8.GetString(bytes);
var meta = await op.StatAsync("demo.txt", executor: executor);
var entries = await op.ListAsync("", executor: executor);
await op.DeleteAsync("demo.txt", executor);
If you don't pass an Executor, OpenDAL uses the default executor.
Creating an Operatorβ
With scheme + dictionary optionsβ
using DotOpenDAL;
using var fs = new Operator("fs", new Dictionary<string, string>
{
["root"] = "/tmp/opendal",
});
With typed service configβ
using DotOpenDAL;
using DotOpenDAL.ServiceConfig;
using var fs = new Operator(new FsServiceConfig
{
Root = "/tmp/opendal",
});
Executor and Lifetimeβ
- Prefer
usingforOperator,Executor, and stream instances. - Keep an
Executoralive for the full lifetime of operations using it. - Disposing an
ExecutororOperatortoo early causesObjectDisposedException. - For async calls, ensure disposal happens after awaited operations complete.
Core Operationsβ
Operator provides sync and async APIs for common object operations:
- Read / Write
- Stat / List
- Delete / CreateDir
- Copy / Rename / RemoveAll
- PresignRead / PresignWrite / PresignStat / PresignDelete (async)
Infofor scheme/root/name/capabilitiesDuplicate()to create a new handle to the same backend configuration
using DotOpenDAL;
using DotOpenDAL.Options;
using System.Text;
using var op = new Operator("memory");
op.CreateDir("logs/");
op.Write("logs/a.txt", Encoding.UTF8.GetBytes("v1"));
op.Copy("logs/a.txt", "logs/b.txt");
op.Rename("logs/b.txt", "logs/c.txt");
var read = op.Read("logs/c.txt", new ReadOptions { Offset = 0, Length = 2 });
var list = op.List("logs/", new ListOptions { Recursive = true });
op.RemoveAll("logs/");
Optionsβ
Use options types in DotOpenDAL.Options to pass operation-specific parameters.
ReadOptions: range, conditions, concurrency, response-header overrides.WriteOptions: append, content headers, conditions, concurrency/chunk, user metadata.StatOptions: conditional headers and response-header overrides.ListOptions: recursive, limit, start-after, versions, deleted.
using DotOpenDAL.Options;
var writeOptions = new WriteOptions
{
ContentType = "text/plain",
CacheControl = "no-cache",
IfNotExists = true,
};
await op.WriteAsync("docs/readme.txt", content, writeOptions);
var listOptions = new ListOptions
{
Recursive = true,
Limit = 100,
};
var entries = await op.ListAsync("docs/", listOptions);
Streamsβ
OpenDAL exposes .NET Stream wrappers:
OpenReadStream(path, readOptions, executor)returnsOperatorInputStream.OpenWriteStream(path, writeOptions, bufferSize, executor)returnsOperatorOutputStream.
using DotOpenDAL;
using System.Text;
using var op = new Operator("memory");
await using (var writer = op.OpenWriteStream("stream.txt"))
{
var payload = Encoding.UTF8.GetBytes("stream-data");
await writer.WriteAsync(payload, 0, payload.Length);
await writer.FlushAsync();
}
using var reader = op.OpenReadStream("stream.txt");
using var ms = new MemoryStream();
reader.CopyTo(ms);
Notes:
- Dispose write streams to flush/close native resources deterministically.
ReadAsync/WriteAsyncon these streams are synchronous wrappers that still honor cancellation checks before execution.
Presignβ
Generate presigned HTTP requests with expiration:
using DotOpenDAL;
using var op = new Operator("s3", new Dictionary<string, string>
{
["bucket"] = "my-bucket",
["region"] = "us-east-1",
["access_key_id"] = "<ak>",
["secret_access_key"] = "<sk>",
});
var request = await op.PresignReadAsync("data/file.txt", TimeSpan.FromMinutes(10));
Console.WriteLine(request.Method);
Console.WriteLine(request.Uri);
foreach (var header in request.Headers)
{
Console.WriteLine($"{header.Key}: {header.Value}");
}
Available APIs:
PresignReadAsyncPresignWriteAsyncPresignStatAsyncPresignDeleteAsync
Layersβ
Apply middleware-like behavior with WithLayer:
using DotOpenDAL;
using DotOpenDAL.Layer;
using var baseOp = new Operator("memory");
using var op = baseOp
.WithLayer(new ConcurrentLimitLayer(64))
.WithLayer(new RetryLayer
{
MaxTimes = 5,
MinDelay = TimeSpan.FromMilliseconds(100),
MaxDelay = TimeSpan.FromSeconds(5),
Factor = 2f,
Jitter = true,
})
.WithLayer(new TimeoutLayer
{
Timeout = TimeSpan.FromSeconds(30),
IoTimeout = TimeSpan.FromSeconds(5),
});
Error Handling and Capability Checksβ
Most failures are surfaced as OpenDALException with a typed Code (ErrorCode).
try
{
await op.ReadAsync("missing.txt");
}
catch (OpenDALException ex) when (ex.Code == ErrorCode.NotFound)
{
// Handle missing object
}
Some features depend on backend capability. Check op.Info.FullCapability before using optional options/features:
var cap = op.Info.FullCapability;
if (cap.PresignRead)
{
var req = await op.PresignReadAsync("a.txt", TimeSpan.FromMinutes(5));
}
Path Conventionsβ
- Use backend-native object keys (for example,
a/b/c.txt). - For directory-like operations (
CreateDir, directoryStat,Listroots), prefer trailing slash paths such aslogs/.
License and Trademarksβ
Licensed under the Apache License, Version 2.0: http://www.apache.org/licenses/LICENSE-2.0
Apache OpenDAL, OpenDAL, and Apache are either registered trademarks or trademarks of the Apache Software Foundation.