# .NET SDK

Official .NET SDK for KeyEnv.

Source: https://keyenv.dev/docs/sdks/dotnet/

Official .NET SDK for KeyEnv with .NET 6.0+ support and async/await patterns.

> **Warning**
>
>   **Beta Release**: This SDK is currently in development. APIs may change before the stable 1.0 release.

## Installation

### NuGet Package Manager

```bash
dotnet add package KeyEnv
```

### Package Manager Console

```powershell
Install-Package KeyEnv
```

### PackageReference

Add to your `.csproj`:

```xml
<PackageReference Include="KeyEnv" Version="1.1.0" />
```

## Quick Start

```csharp
using KeyEnv;

var client = KeyEnvClient.Create(Environment.GetEnvironmentVariable("KEYENV_TOKEN")!);

// Load secrets into environment variables
int count = await client.LoadEnvAsync("your-project-id", "production");
Console.WriteLine($"Loaded {count} secrets");

// Access secrets
Console.WriteLine(Environment.GetEnvironmentVariable("DATABASE_URL"));
```

## Initialize the Client

### Simple Initialization

```csharp
using KeyEnv;

var client = KeyEnvClient.Create(Environment.GetEnvironmentVariable("KEYENV_TOKEN")!);
```

### With Options

For more control, use `KeyEnvOptions`:

```csharp
using KeyEnv;

var client = KeyEnvClient.Create(new KeyEnvOptions
{
    Token = Environment.GetEnvironmentVariable("KEYENV_TOKEN")!,
    Timeout = TimeSpan.FromSeconds(60),
    CacheTtl = TimeSpan.FromMinutes(5)
});
```

## Loading Secrets

### Load into Environment Variables

The simplest way to use secrets in your application:

```csharp
int count = await client.LoadEnvAsync("project-id", "production");
Console.WriteLine($"Loaded {count} secrets");

// Access via Environment.GetEnvironmentVariable
var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
var apiKey = Environment.GetEnvironmentVariable("API_KEY");
```

### Get Secrets as Dictionary

Get secrets as a key-value dictionary:

```csharp
var secrets = await client.GetSecretsAsDictionaryAsync("project-id", "production");
Console.WriteLine(secrets["DATABASE_URL"]);
Console.WriteLine(secrets["API_KEY"]);
```

### Export as List

Get secrets with metadata:

```csharp
var secrets = await client.GetSecretsAsync("project-id", "production");
foreach (var secret in secrets)
{
    Console.WriteLine($"{secret.Key}={secret.Value}");
}
```

## Managing Secrets

### Get a Single Secret

```csharp
var secret = await client.GetSecretAsync("project-id", "production", "DATABASE_URL");
Console.WriteLine(secret.Value);
Console.WriteLine(secret.Description);
```

### Set a Secret

Creates or updates a secret:

```csharp
await client.SetSecretAsync("project-id", "production", "API_KEY", "sk_live_...");

// With description
await client.SetSecretAsync(
    "project-id",
    "production",
    "API_KEY",
    "sk_live_...",
    description: "Production API key"
);
```

### Delete a Secret

```csharp
await client.DeleteSecretAsync("project-id", "production", "OLD_KEY");
```

## Bulk Operations

### Bulk Import

Import multiple secrets at once:

```csharp
using KeyEnv.Types;

var secrets = new List<SecretInput>
{
    new SecretInput { Key = "DATABASE_URL", Value = "postgres://localhost/mydb" },
    new SecretInput { Key = "REDIS_URL", Value = "redis://localhost:6379" },
    new SecretInput { Key = "API_KEY", Value = "sk_test_...", Description = "Test API key" }
};

var result = await client.BulkImportAsync(
    "project-id",
    "development",
    secrets,
    new BulkImportOptions { Overwrite = true }
);

Console.WriteLine($"Created: {result.Created}, Updated: {result.Updated}");
```

### Generate .env File

```csharp
var envContent = await client.GenerateEnvFileAsync("project-id", "production");
await File.WriteAllTextAsync(".env", envContent);
```

## Projects & Environments

### List Projects

```csharp
var projects = await client.ListProjectsAsync();
foreach (var project in projects)
{
    Console.WriteLine($"{project.Name} ({project.Id})");
}
```

### Get Project Details

```csharp
var project = await client.GetProjectAsync("project-id");
Console.WriteLine($"Project: {project.Name}");
foreach (var env in project.Environments)
{
    Console.WriteLine($"  - {env.Name}");
}
```

### List Environments

```csharp
var environments = await client.ListEnvironmentsAsync("project-id");
foreach (var env in environments)
{
    Console.WriteLine(env.Name);
}
```

## Environment Permissions

### List Permissions

```csharp
var permissions = await client.ListPermissionsAsync("project-id", "production");
foreach (var perm in permissions)
{
    Console.WriteLine($"{perm.UserEmail}: {perm.Role}");
}
```

### Set Permission

```csharp
await client.SetPermissionAsync("project-id", "production", "user-id", "write");
```

### Get My Permissions

```csharp
var response = await client.GetMyPermissionsAsync("project-id");
foreach (var perm in response.Permissions)
{
    Console.WriteLine($"{perm.EnvironmentName}: {perm.Role} (CanWrite: {perm.CanWrite})");
}
Console.WriteLine($"Is Team Admin: {response.IsTeamAdmin}");
```

## Caching

Enable caching for better performance in high-throughput applications:

```csharp
var client = KeyEnvClient.Create(new KeyEnvOptions
{
    Token = Environment.GetEnvironmentVariable("KEYENV_TOKEN")!,
    CacheTtl = TimeSpan.FromMinutes(5)
});

// First call fetches from API
await client.GetSecretsAsync("project-id", "production");

// Subsequent calls within TTL use cache
await client.GetSecretsAsync("project-id", "production");

// Clear cache when needed
client.ClearCache("project-id", "production");
client.ClearAllCache();
```

## Error Handling

```csharp
using KeyEnv;

try
{
    var secret = await client.GetSecretAsync("project-id", "production", "MISSING_KEY");
}
catch (KeyEnvException ex)
{
    Console.WriteLine($"Error {ex.StatusCode}: {ex.Message}");

    if (ex.IsNotFound)
    {
        Console.WriteLine("Secret not found");
    }
    else if (ex.IsUnauthorized)
    {
        Console.WriteLine("Invalid or expired token");
    }
    else if (ex.IsForbidden)
    {
        Console.WriteLine("Access denied");
    }
}
```

## Cancellation Support

All async methods support cancellation tokens:

```csharp
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(10));

try
{
    var secrets = await client.GetSecretsAsync("project-id", "production", cts.Token);
}
catch (OperationCanceledException)
{
    Console.WriteLine("Operation was cancelled");
}
```

## IDisposable Pattern

The client implements `IDisposable` for proper resource cleanup:

```csharp
using (var client = KeyEnvClient.Create(token))
{
    await client.LoadEnvAsync("project-id", "production");
    // Client is disposed when leaving the using block
}

// Or with using declaration (C# 8+)
using var client = KeyEnvClient.Create(token);
await client.LoadEnvAsync("project-id", "production");
```

## API Reference

### Constructor Options

| Option | Type | Required | Default | Description |
|--------|------|----------|---------|-------------|
| `Token` | `string` | Yes | - | Service token |
| `Timeout` | `TimeSpan?` | No | `30s` | Request timeout |
| `CacheTtl` | `TimeSpan?` | No | `0` | Cache TTL (0 disables) |
| `BaseUrl` | `string?` | No | `https://api.keyenv.dev` | API base URL |

### Methods

| Method | Description |
|--------|-------------|
| `GetCurrentUserAsync()` | Get current user/token info |
| `ValidateTokenAsync()` | Validate token and get user info |
| `ListProjectsAsync()` | List all accessible projects |
| `GetProjectAsync(projectId)` | Get project with environments |
| `CreateProjectAsync(teamId, name)` | Create a new project |
| `DeleteProjectAsync(projectId)` | Delete a project |
| `ListEnvironmentsAsync(projectId)` | List environments in a project |
| `CreateEnvironmentAsync(projectId, name)` | Create a new environment |
| `DeleteEnvironmentAsync(projectId, env)` | Delete an environment |
| `ListSecretsAsync(projectId, env)` | List secret keys (no values) |
| `GetSecretsAsync(projectId, env)` | Export secrets with values |
| `GetSecretsAsDictionaryAsync(projectId, env)` | Export as dictionary |
| `GetSecretAsync(projectId, env, key)` | Get single secret |
| `SetSecretAsync(projectId, env, key, value)` | Create or update secret |
| `DeleteSecretAsync(projectId, env, key)` | Delete secret |
| `BulkImportAsync(projectId, env, secrets)` | Bulk import secrets |
| `LoadEnvAsync(projectId, env)` | Load secrets into environment |
| `GenerateEnvFileAsync(projectId, env)` | Generate .env file content |
| `GetSecretHistoryAsync(projectId, env, key)` | Get secret version history |
| `ListPermissionsAsync(projectId, env)` | List permissions |
| `SetPermissionAsync(projectId, env, userId, role)` | Set permission |
| `DeletePermissionAsync(projectId, env, userId)` | Delete permission |
| `GetMyPermissionsAsync(projectId)` | Get current user's permissions |
| `GetProjectDefaultsAsync(projectId)` | Get default permissions |
| `SetProjectDefaultsAsync(projectId, defaults)` | Set default permissions |
| `ClearCache(projectId, environment)` | Clear specific cache |
| `ClearAllCache()` | Clear all cached data |

## Examples

### ASP.NET Core Web API

```csharp
// Program.cs
using KeyEnv;

var builder = WebApplication.CreateBuilder(args);

// Load secrets at startup
var keyEnvToken = Environment.GetEnvironmentVariable("KEYENV_TOKEN");
if (!string.IsNullOrEmpty(keyEnvToken))
{
    using var client = KeyEnvClient.Create(keyEnvToken);
    await client.LoadEnvAsync(
        Environment.GetEnvironmentVariable("KEYENV_PROJECT")!,
        builder.Environment.EnvironmentName.ToLower()
    );
}

// Now secrets are available via Environment.GetEnvironmentVariable
builder.Services.AddControllers();
var app = builder.Build();

app.MapControllers();
app.Run();
```

### ASP.NET Core with Dependency Injection

```csharp
// Program.cs
using KeyEnv;

var builder = WebApplication.CreateBuilder(args);

// Register KeyEnvClient as a singleton
builder.Services.AddSingleton<KeyEnvClient>(sp =>
{
    return KeyEnvClient.Create(new KeyEnvOptions
    {
        Token = Environment.GetEnvironmentVariable("KEYENV_TOKEN")!,
        CacheTtl = TimeSpan.FromMinutes(5)
    });
});

var app = builder.Build();
app.Run();

// In a controller
[ApiController]
[Route("api/[controller]")]
public class SecretsController : ControllerBase
{
    private readonly KeyEnvClient _keyEnv;

    public SecretsController(KeyEnvClient keyEnv)
    {
        _keyEnv = keyEnv;
    }

    [HttpGet]
    public async Task<IActionResult> Get()
    {
        var secrets = await _keyEnv.GetSecretsAsDictionaryAsync("project-id", "production");
        return Ok(new { count = secrets.Count });
    }
}
```

### Console Application

```csharp
using KeyEnv;

class Program
{
    static async Task Main(string[] args)
    {
        var token = Environment.GetEnvironmentVariable("KEYENV_TOKEN")
            ?? throw new InvalidOperationException("KEYENV_TOKEN not set");

        using var client = KeyEnvClient.Create(token);

        // Load all secrets into environment
        var count = await client.LoadEnvAsync("my-project", "production");
        Console.WriteLine($"Loaded {count} secrets");

        // Use the secrets
        var dbUrl = Environment.GetEnvironmentVariable("DATABASE_URL");
        Console.WriteLine($"Database: {dbUrl}");
    }
}
```

### Azure Functions

```csharp
using KeyEnv;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.DependencyInjection;

var host = new HostBuilder()
    .ConfigureFunctionsWebApplication()
    .ConfigureServices(services =>
    {
        services.AddSingleton<KeyEnvClient>(sp =>
        {
            return KeyEnvClient.Create(new KeyEnvOptions
            {
                Token = Environment.GetEnvironmentVariable("KEYENV_TOKEN")!,
                CacheTtl = TimeSpan.FromMinutes(5)
            });
        });
    })
    .Build();

// Load secrets at startup
using (var scope = host.Services.CreateScope())
{
    var client = scope.ServiceProvider.GetRequiredService<KeyEnvClient>();
    await client.LoadEnvAsync(
        Environment.GetEnvironmentVariable("KEYENV_PROJECT")!,
        "production"
    );
}

host.Run();
```

### AWS Lambda with .NET

```csharp
using Amazon.Lambda.Core;
using KeyEnv;

[assembly: LambdaSerializer(typeof(Amazon.Lambda.Serialization.SystemTextJson.DefaultLambdaJsonSerializer))]

namespace MyLambda;

public class Function
{
    private static readonly KeyEnvClient _client;
    private static bool _secretsLoaded = false;

    static Function()
    {
        _client = KeyEnvClient.Create(new KeyEnvOptions
        {
            Token = Environment.GetEnvironmentVariable("KEYENV_TOKEN")!,
            CacheTtl = TimeSpan.FromMinutes(5)
        });
    }

    public async Task<string> FunctionHandler(object input, ILambdaContext context)
    {
        // Load secrets once per cold start
        if (!_secretsLoaded)
        {
            await _client.LoadEnvAsync(
                Environment.GetEnvironmentVariable("KEYENV_PROJECT")!,
                "production"
            );
            _secretsLoaded = true;
        }

        // Your handler logic here
        var apiKey = Environment.GetEnvironmentVariable("API_KEY");
        return $"Success with API key: {apiKey?.Substring(0, 10)}...";
    }
}
```

### Background Service

```csharp
using KeyEnv;

public class SecretsRefreshService : BackgroundService
{
    private readonly KeyEnvClient _client;
    private readonly ILogger<SecretsRefreshService> _logger;

    public SecretsRefreshService(KeyEnvClient client, ILogger<SecretsRefreshService> logger)
    {
        _client = client;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                var count = await _client.LoadEnvAsync("project-id", "production", stoppingToken);
                _logger.LogInformation("Refreshed {Count} secrets", count);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to refresh secrets");
            }

            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }
}
```
