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.
To compile OpenDAL .NET binding from source code, you need:
net8.0 or net10.0.cargo build
dotnet build
dotnet test
using OpenDAL; 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.
using OpenDAL; using var fs = new Operator("fs", new Dictionary<string, string> { ["root"] = "/tmp/opendal", });
using OpenDAL; using OpenDAL.ServiceConfig; using var fs = new Operator(new FsServiceConfig { Root = "/tmp/opendal", });
using for Operator, Executor, and stream instances.Executor alive for the full lifetime of operations using it.Executor or Operator too early causes ObjectDisposedException.Operator provides sync and async APIs for common object operations:
Info for scheme/root/name/capabilitiesDuplicate() to create a new handle to the same backend configurationusing OpenDAL; using OpenDAL.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/");
Use options types in OpenDAL.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 OpenDAL.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);
OpenDAL exposes .NET Stream wrappers:
OpenReadStream(path, readOptions, executor) returns OperatorInputStream.OpenWriteStream(path, writeOptions, bufferSize, executor) returns OperatorOutputStream.using OpenDAL; 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:
ReadAsync / WriteAsync on these streams are synchronous wrappers that still honor cancellation checks before execution.Generate presigned HTTP requests with expiration:
using OpenDAL; 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:
PresignReadAsyncPresignWriteAsyncPresignStatAsyncPresignDeleteAsyncApply middleware-like behavior with WithLayer:
using OpenDAL; using OpenDAL.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), });
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)); }
a/b/c.txt).CreateDir, directory Stat, List roots), prefer trailing slash paths such as logs/.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.