> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/trailbaseio/trailbase/llms.txt
> Use this file to discover all available pages before exploring further.

# C#/.NET SDK

> Official C#/.NET client for TrailBase

The official C#/.NET SDK for TrailBase provides a fully-typed, async client for accessing your TrailBase backend from .NET applications.

## Installation

Install the TrailBase NuGet package:

```bash theme={null}
dotnet add package TrailBase
```

Or via Package Manager Console:

```powershell theme={null}
Install-Package TrailBase
```

## Requirements

* .NET 8.0 or .NET 9.0
* Compatible with Native AOT compilation

## Initialization

### Basic Client

```csharp theme={null}
using TrailBase;

var client = new Client("https://your-server.trailbase.io", null);
```

### Client with Tokens

```csharp theme={null}
using TrailBase;

var tokens = new Tokens(
    auth_token: "your-auth-token",
    refresh_token: "your-refresh-token",
    csrf_token: "your-csrf-token"
);

var client = new Client("https://your-server.trailbase.io", tokens);
```

## Authentication

### Login

```csharp theme={null}
try
{
    var tokens = await client.Login("user@example.com", "password");
    Console.WriteLine($"Auth token: {tokens.auth_token}");
    
    var user = client.User();
    if (user != null)
    {
        Console.WriteLine($"Logged in as: {user.email}");
    }
}
catch (Exception ex)
{
    Console.WriteLine($"Login failed: {ex.Message}");
}
```

### Logout

```csharp theme={null}
await client.Logout();
```

### Current User

```csharp theme={null}
var user = client.User();
if (user != null)
{
    Console.WriteLine($"User ID: {user.sub}");
    Console.WriteLine($"Email: {user.email}");
}
```

### Access Tokens

```csharp theme={null}
var tokens = client.Tokens();
if (tokens != null)
{
    // Persist tokens for later use
    var json = System.Text.Json.JsonSerializer.Serialize(tokens);
    await File.WriteAllTextAsync("tokens.json", json);
}
```

### Refresh Token

```csharp theme={null}
await client.RefreshAuthToken();
```

## Record API

### Define Your Record Types

```csharp theme={null}
using System.Text.Json.Serialization;

public class Post
{
    [JsonPropertyName("id")]
    public string Id { get; set; } = "";
    
    [JsonPropertyName("title")]
    public string Title { get; set; } = "";
    
    [JsonPropertyName("content")]
    public string Content { get; set; } = "";
    
    [JsonPropertyName("author_id")]
    public string AuthorId { get; set; } = "";
    
    [JsonPropertyName("created_at")]
    public long CreatedAt { get; set; }
}

public class NewPost
{
    [JsonPropertyName("title")]
    public string Title { get; set; } = "";
    
    [JsonPropertyName("content")]
    public string Content { get; set; } = "";
}
```

### List Records

```csharp theme={null}
var posts = client.Records("posts");

var response = await posts.List<Post>(
    limit: 10,
    offset: 0,
    order: new[] { "-created_at" },
    count: true
);

Console.WriteLine($"Records: {response.records.Count}");
Console.WriteLine($"Total count: {response.total_count}");
Console.WriteLine($"Next cursor: {response.cursor}");
```

### Read a Record

```csharp theme={null}
// String ID
var post = await posts.Read<Post>("post-id");

// Integer ID
var post = await posts.Read<Post>(123);

// With expanded relationships
var postWithAuthor = await posts.Read<Post>(
    "post-id",
    expand: new[] { "author" }
);

Console.WriteLine($"Title: {post.Title}");
```

### Create a Record

```csharp theme={null}
var newPost = new NewPost
{
    Title = "Hello World",
    Content = "My first post from C#"
};

var postId = await posts.Create(newPost);
Console.WriteLine($"Created post with ID: {postId}");
```

### Create Multiple Records

```csharp theme={null}
var newPosts = new[]
{
    new NewPost { Title = "Post 1", Content = "Content 1" },
    new NewPost { Title = "Post 2", Content = "Content 2" }
};

var ids = await posts.CreateBulk(newPosts);
Console.WriteLine($"Created {ids.Count} posts");
```

### Update a Record

```csharp theme={null}
var updates = new Dictionary<string, object>
{
    { "title", "Updated Title" }
};

await posts.Update("post-id", updates);
```

### Delete a Record

```csharp theme={null}
await posts.Delete("post-id");
```

## Filtering

```csharp theme={null}
using TrailBase;

// Simple equality filter
var filters = new[]
{
    new Filter(column: "author_id", value: userId)
};

var response = await posts.List<Post>(filters: filters);

// With comparison operators
var weekAgo = DateTimeOffset.UtcNow.AddDays(-7).ToUnixTimeSeconds();
var recentPosts = await posts.List<Post>(
    filters: new[]
    {
        new Filter(
            column: "created_at",
            op: CompareOp.GreaterThan,
            value: weekAgo.ToString()
        )
    }
);

// LIKE operator for text search
var searchResults = await posts.List<Post>(
    filters: new[]
    {
        new Filter(
            column: "title",
            op: CompareOp.Like,
            value: "%search%"
        )
    }
);

// AND composite filter
var filtered = await posts.List<Post>(
    filters: new[]
    {
        new And(new[]
        {
            new Filter(column: "status", value: "published"),
            new Filter(column: "author_id", value: userId)
        })
    }
);

// OR composite filter
var filtered = await posts.List<Post>(
    filters: new[]
    {
        new Or(new[]
        {
            new Filter(column: "category", value: "tech"),
            new Filter(column: "category", value: "science")
        })
    }
);
```

### Available Comparison Operators

```csharp theme={null}
public enum CompareOp
{
    Equal,
    NotEqual,
    LessThan,
    LessThanEqual,
    GreaterThan,
    GreaterThanEqual,
    Like,
    Regexp,
    StWithin,      // Geospatial
    StIntersects,  // Geospatial
    StContains     // Geospatial
}
```

## Real-time Subscriptions

Subscribe to record changes using Server-Sent Events:

```csharp theme={null}
// Subscribe to a single record
await foreach (var @event in posts.Subscribe("post-id"))
{
    if (@event.Insert != null)
    {
        Console.WriteLine($"Record inserted: {@event.Insert}");
    }
    else if (@event.Update != null)
    {
        Console.WriteLine($"Record updated: {@event.Update}");
    }
    else if (@event.Delete != null)
    {
        Console.WriteLine($"Record deleted: {@event.Delete}");
    }
    else if (@event.Error != null)
    {
        Console.WriteLine($"Error: {@event.Error}");
    }
}
```

## Error Handling

```csharp theme={null}
using System.Net.Http;

try
{
    var post = await posts.Read<Post>("post-id");
}
catch (HttpRequestException ex)
{
    Console.WriteLine($"HTTP error: {ex.StatusCode}");
    Console.WriteLine($"Message: {ex.Message}");
}
catch (Exception ex)
{
    Console.WriteLine($"Error: {ex.Message}");
}
```

## Advanced Usage

### Custom Fetch

```csharp theme={null}
var response = await client.Fetch(
    "api/custom/endpoint",
    HttpMethod.Post,
    new StringContent(
        System.Text.Json.JsonSerializer.Serialize(new { key = "value" }),
        System.Text.Encoding.UTF8,
        "application/json"
    )
);

var data = await response.Content.ReadAsStringAsync();
```

### Pagination

```csharp theme={null}
var pagination = new Pagination
{
    Limit = 20,
    Offset = 0
};

var response = await posts.List<Post>(pagination: pagination);

// Cursor-based pagination
var nextPage = await posts.List<Post>(
    pagination: new Pagination { Cursor = response.cursor }
);
```

## Type Definitions

### User

```csharp theme={null}
public class User
{
    public string sub { get; }
    public string email { get; }
    
    public User(string sub, string email)
    {
        this.sub = sub;
        this.email = email;
    }
}
```

### Tokens

```csharp theme={null}
public class Tokens
{
    public string auth_token { get; }
    public string? refresh_token { get; }
    public string? csrf_token { get; }
    
    public Tokens(string auth_token, string? refresh_token, string? csrf_token)
    {
        this.auth_token = auth_token;
        this.refresh_token = refresh_token;
        this.csrf_token = csrf_token;
    }
}
```

### ListResponse

```csharp theme={null}
public class ListResponse<T>
{
    public string? cursor { get; set; }
    public int? total_count { get; set; }
    public List<T> records { get; set; }
}
```

### Pagination

```csharp theme={null}
public class Pagination
{
    public string? Cursor { get; set; }
    public int? Limit { get; set; }
    public int? Offset { get; set; }
}
```

## Best Practices

<Note>
  Use nullable reference types (enabled by default in modern .NET) for better null safety.
</Note>

<Warning>
  Store tokens securely using Windows Credential Manager, Azure Key Vault, or similar secure storage.
</Warning>

<Info>
  The SDK is AOT-compatible and works with Native AOT compilation for improved startup time and reduced memory usage.
</Info>

## Example Application

```csharp theme={null}
using TrailBase;
using System.Text.Json.Serialization;

public class Post
{
    [JsonPropertyName("id")]
    public string Id { get; set; } = "";
    
    [JsonPropertyName("title")]
    public string Title { get; set; } = "";
    
    [JsonPropertyName("content")]
    public string Content { get; set; } = "";
    
    [JsonPropertyName("published")]
    public bool Published { get; set; }
}

class Program
{
    static async Task Main(string[] args)
    {
        // Initialize client
        var url = Environment.GetEnvironmentVariable("TRAILBASE_URL") 
            ?? "http://localhost:4000";
        var client = new Client(url, null);
        
        // Login
        try
        {
            await client.Login(
                Environment.GetEnvironmentVariable("TRAILBASE_EMAIL")!,
                Environment.GetEnvironmentVariable("TRAILBASE_PASSWORD")!
            );
            
            var user = client.User();
            Console.WriteLine($"Logged in as: {user?.email}");
        }
        catch (Exception ex)
        {
            Console.WriteLine($"Login failed: {ex.Message}");
            return;
        }
        
        // List posts
        var posts = client.Records("posts");
        var response = await posts.List<Post>(
            order: new[] { "-created_at" },
            limit: 10,
            filters: new[]
            {
                new Filter(column: "published", value: "true")
            }
        );
        
        Console.WriteLine($"\nFound {response.records.Count} posts:");
        foreach (var post in response.records)
        {
            Console.WriteLine($"- {post.Title}");
        }
        
        // Create a new post
        var newPost = new Dictionary<string, object>
        {
            { "title", "Hello from C#" },
            { "content", "This post was created using the TrailBase C# SDK" },
            { "published", true }
        };
        
        var newPostId = await posts.Create(newPost);
        Console.WriteLine($"\nCreated new post with ID: {newPostId}");
        
        // Read the post
        var post = await posts.Read<Post>(newPostId);
        Console.WriteLine($"Post title: {post.Title}");
        
        // Logout
        await client.Logout();
        Console.WriteLine("\nLogged out");
    }
}
```
