Skip to main content

.NET SDK

The official .NET SDK for Rexec provides a modern, cross-platform interface for Terminal as a Service.

Requirements

  • .NET 8.0 or later

Installation

dotnet add package Rexec

Quick Start

using Rexec;

// Create client
var client = new RexecClient("https://your-instance.com", "your-api-token");

// Create a container
var container = await client.Containers.CreateAsync("ubuntu:24.04");
Console.WriteLine($"Created: {container.Id}");

// Start it
await client.Containers.StartAsync(container.Id);

// Execute a command
var result = await client.Containers.ExecAsync(container.Id, "echo 'Hello from .NET!'");
Console.WriteLine(result.Stdout);

// Clean up
await client.Containers.DeleteAsync(container.Id);

Client Initialization

Basic Client

using Rexec;

var client = new RexecClient(
    "https://your-instance.com",
    "your-api-token"
);
The RexecClient class is defined at /home/daytona/workspace/source/sdk/dotnet/RexecClient.cs:18.

With Dependency Injection

using Microsoft.Extensions.DependencyInjection;
using Rexec;

services.AddSingleton<RexecClient>(sp =>
    new RexecClient(
        "https://your-instance.com",
        "your-api-token"
    )
);

With Disposal

using var client = new RexecClient(baseUrl, token);
// Client is automatically disposed

Container Operations

The Container service provides methods for managing sandboxed environments.

List Containers

var containers = await client.Containers.ListAsync();

foreach (var c in containers)
{
    Console.WriteLine($"{c.Name}: {c.Status}");
}

Get Container

var container = await client.Containers.GetAsync(containerId);
Console.WriteLine($"Container {container.Name} is {container.Status}");

Create Container

// Simple creation
var container = await client.Containers.CreateAsync("ubuntu:24.04");

// With options using fluent API
var container = await client.Containers.CreateAsync(
    new CreateContainerRequest("python:3.12")
        .WithName("my-python-sandbox")
        .WithEnv("PYTHONPATH", "/app")
        .WithEnv("DEBUG", "true")
        .WithLabel("project", "demo")
);

Console.WriteLine($"Created: {container.Id}");

Start Container

await client.Containers.StartAsync(containerId);

Stop Container

await client.Containers.StopAsync(containerId);

Delete Container

await client.Containers.DeleteAsync(containerId);

Execute Commands

var result = await client.Containers.ExecAsync(
    containerId,
    "python --version"
);

if (result.IsSuccess)
{
    Console.WriteLine(result.Stdout);
}
else
{
    Console.WriteLine($"Error: {result.Stderr}");
}

File Operations

Manage files and directories within containers.

List Files

var files = await client.Files.ListAsync(containerId, "/app");

foreach (var file in files)
{
    var type = file.IsDir ? "DIR" : $"{file.Size} bytes";
    Console.WriteLine($"{file.Name} - {type}");
}

Read File

var content = await client.Files.ReadStringAsync(containerId, "/etc/hostname");
Console.WriteLine($"Hostname: {content}");

// Or read as bytes
var bytes = await client.Files.ReadBytesAsync(containerId, "/path/to/file");

Write File

await client.Files.WriteAsync(
    containerId,
    "/app/script.py",
    "print('Hello!')"
);

// Or write bytes
await client.Files.WriteBytesAsync(
    containerId,
    "/path/to/file",
    byteArray
);

Delete File

await client.Files.DeleteAsync(containerId, "/tmp/scratch.txt");

Interactive Terminal

Connect to containers via WebSocket for real-time terminal access.

Basic Terminal Usage

await using var terminal = await client.Terminal.ConnectAsync(containerId);

// Set up event handlers
terminal.OnData += data => Console.Write(data);
terminal.OnClose += () => Console.WriteLine("Disconnected");
terminal.OnError += ex => Console.WriteLine($"Error: {ex.Message}");

// Send commands
await terminal.WriteAsync("ls -la\n");
await terminal.WriteAsync("cd /app && python main.py\n");

// Resize terminal
await terminal.ResizeAsync(120, 40);

// The using statement handles cleanup automatically

Terminal with Custom Size

await using var terminal = await client.Terminal.ConnectAsync(
    containerId,
    cols: 120,
    rows: 40
);

Advanced Examples

Run Script and Capture Output

async Task<string> RunScriptAsync(
    RexecClient client,
    string containerId,
    string script)
{
    var output = new StringBuilder();
    
    await using var terminal = await client.Terminal.ConnectAsync(containerId);
    
    terminal.OnData += data => output.Append(data);
    
    var tcs = new TaskCompletionSource<bool>();
    terminal.OnClose += () => tcs.SetResult(true);
    
    await terminal.WriteAsync($"{script}\nexit\n");
    await tcs.Task;
    
    return output.ToString();
}

// Usage
var result = await RunScriptAsync(
    client,
    container.Id,
    "apt update && apt install -y curl"
);
Console.WriteLine(result);

Parallel Container Creation

async Task<List<Container>> CreateBatchAsync(
    RexecClient client,
    int count)
{
    var tasks = Enumerable.Range(0, count)
        .Select(i => client.Containers.CreateAsync(
            new CreateContainerRequest("ubuntu:24.04")
                .WithName($"worker-{i}")
        ));
    
    return (await Task.WhenAll(tasks)).ToList();
}

// Create 5 containers in parallel
var containers = await CreateBatchAsync(client, 5);
Console.WriteLine($"Created {containers.Count} containers");

File Upload

async Task UploadFileAsync(
    RexecClient client,
    string containerId,
    string localPath,
    string remotePath)
{
    var content = await File.ReadAllTextAsync(localPath);
    await client.Files.WriteAsync(containerId, remotePath, content);
}

// Usage
await UploadFileAsync(
    client,
    container.Id,
    "./local-script.sh",
    "/home/script.sh"
);

Directory Sync

async Task SyncDirectoryAsync(
    RexecClient client,
    string containerId,
    string localDir,
    string remoteDir)
{
    await client.Files.MkdirAsync(containerId, remoteDir);
    
    var files = Directory.GetFiles(localDir, "*", SearchOption.AllDirectories);
    
    foreach (var file in files)
    {
        var relativePath = Path.GetRelativePath(localDir, file);
        var remotePath = Path.Combine(remoteDir, relativePath)
            .Replace("\\", "/");
        
        var content = await File.ReadAllBytesAsync(file);
        await client.Files.WriteBytesAsync(containerId, remotePath, content);
        
        Console.WriteLine($"Uploaded: {remotePath}");
    }
}

Real-time Log Streaming

async Task StreamLogsAsync(
    RexecClient client,
    string containerId,
    string command,
    CancellationToken cancellationToken = default)
{
    await using var terminal = await client.Terminal.ConnectAsync(containerId);
    
    terminal.OnData += data =>
    {
        Console.Write(data);
        
        // Could also save to file
        // File.AppendAllText("logs.txt", data);
    };
    
    await terminal.WriteAsync($"{command}\n");
    
    // Keep connection open until cancelled
    await Task.Delay(Timeout.Infinite, cancellationToken);
}

// Usage with cancellation
using var cts = new CancellationTokenSource();
var task = StreamLogsAsync(client, container.Id, "tail -f /var/log/app.log", cts.Token);

// Cancel after 30 seconds
cts.CancelAfter(TimeSpan.FromSeconds(30));
await task;

Error Handling

using Rexec;

try
{
    var container = await client.Containers.GetAsync("invalid-id");
}
catch (RexecException ex) when (ex.IsApiError)
{
    Console.WriteLine($"API error {ex.StatusCode}: {ex.Message}");
}
catch (RexecException ex)
{
    Console.WriteLine($"Network error: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"Unexpected error: {ex.Message}");
}

Async/Await and Cancellation

All methods support cancellation tokens:
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(30));

try
{
    var containers = await client.Containers.ListAsync(cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Operation timed out");
}

LINQ Integration

Use LINQ with SDK results:
var containers = await client.Containers.ListAsync();

// Filter containers
var running = containers
    .Where(c => c.Status == "running")
    .ToList();

// Group by image
var grouped = containers
    .GroupBy(c => c.Image)
    .Select(g => new { Image = g.Key, Count = g.Count() });

// Order by creation time
var sorted = containers
    .OrderByDescending(c => c.CreatedAt);

Dependency Injection Example

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Rexec;

var builder = Host.CreateApplicationBuilder(args);

// Register RexecClient
builder.Services.AddSingleton<RexecClient>(sp =>
    new RexecClient(
        builder.Configuration["Rexec:BaseUrl"],
        builder.Configuration["Rexec:Token"]
    )
);

// Register your services
builder.Services.AddScoped<IContainerService, ContainerService>();

var host = builder.Build();
await host.RunAsync();

Source Code

View the full source code on GitHub:

License

MIT License - see LICENSE for details.

Build docs developers (and LLMs) love