# .NET SDK

<figure><img src="https://762006384-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F-MJXOXEifAmpyvNVL1to%2Fuploads%2FCatD9Fz7a1mtDPXCYrur%2FKSM%20%2B%20Net%20Icon.png?alt=media&#x26;token=00f4fa03-b694-4e64-a1bd-ec4cfa7c21e3" alt=""><figcaption></figcaption></figure>

## Download and Installation

### Prerequisites

> `dotnet add package Keeper.SecretsManager`

For more information, see <https://www.nuget.org/packages/Keeper.SecretsManager/>

### Source Code

Find the .Net source code in the [GitHub repository](https://github.com/Keeper-Security/secrets-manager/tree/master/sdk/dotNet)

## Using the SDK

### Initialize Storage

{% hint style="info" %}
Using token only to generate a new config (for later usage) requires at least one read operation to bind the token and fully populate `config.json`
{% endhint %}

In order to retrieve secrets, you must first initialize the local storage on your machine.

```csharp
SecretsManagerClient.InitializeStorage(storage: IKeyValueStorage, oneTimeToken: string, hostName: string = null)
```

<table data-header-hidden><thead><tr><th width="162">Parameter</th><th width="186">Type</th><th width="150">Required</th><th width="150">Default</th><th>Description</th></tr></thead><tbody><tr><td>Parameter</td><td>Type</td><td>Required</td><td>Default</td><td>Description</td></tr><tr><td><code>storage</code></td><td><code>IKeyValueStorage</code></td><td>Yes</td><td></td><td>Storage implementation for persisting config</td></tr><tr><td><code>oneTimeToken</code></td><td><code>string</code></td><td>Yes</td><td></td><td>One Time Access Token used to initialize the config</td></tr><tr><td><code>hostName</code></td><td><code>string</code></td><td>Optional</td><td>null</td><td>Keeper server hostname (auto-detected from token if omitted)</td></tr></tbody></table>

#### **Example Usage**

```csharp
var storage = new LocalConfigStorage("ksm-config.json");
SecretsManagerClient.InitializeStorage(storage, "[One Time Access Token]");
// Using token only to generate a config (for later usage)
// requires at least one access operation to bind the token
var options = new SecretsManagerOptions(storage);
await SecretsManagerClient.GetSecrets(options);
```

{% hint style="success" %}
**Automatic File Security** (v17.1.0+)

The .NET SDK automatically secures configuration and cache files:

* **Unix/macOS**: Sets `chmod 600` (owner read/write only)
* **Windows**: Restricts ACLs to current user only

Files protected:

* `ksm-config.json` (or your custom config file name)
* `cache.dat` (if using caching)

No additional configuration needed - the SDK handles this automatically.
{% endhint %}

#### Secrets Manager Options

```csharp
SecretsManagerOptions(
        IKeyValueStorage storage, 
        QueryFunction queryFunction = null, 
        bool allowUnverifiedCertificate = false, 
        string proxyUrl = null
)
```

<table data-header-hidden><thead><tr><th width="256.1796875">Parameter</th><th width="186">Type</th><th width="150">Required</th><th width="150">Default</th><th data-hidden>Description</th></tr></thead><tbody><tr><td>Parameter</td><td>Type</td><td>Required</td><td>Default</td><td>Description</td></tr><tr><td><code>storage</code></td><td><code>IKeyValueStorage</code></td><td>Yes</td><td></td><td></td></tr><tr><td><code>queryFunction</code></td><td><code>QueryFunction</code></td><td>Optional</td><td>null</td><td></td></tr><tr><td><code>allowUnverifiedCertificate</code></td><td><code>bool</code></td><td>Optional</td><td>false</td><td></td></tr><tr><td><code>proxyUrl</code></td><td><code>string</code></td><td>Optional</td><td>null</td><td></td></tr></tbody></table>

### Retrieve Secrets

```csharp
GetSecrets(options: SecretsManagerOptions, recordsFilter: string[] = null): Task<KeeperSecrets>
```

| Parameter       | Type                    | Required | Default | Description                     |
| --------------- | ----------------------- | -------- | ------- | ------------------------------- |
| `options`       | `SecretsManagerOptions` | Yes      |         | Storage and query configuration |
| `recordsFilter` | `string[]`              | Optional | `null`  | Record search filters           |

**Response**

Type: `KeeperSecrets`

Object containing all Keeper records, or records that match the given filter criteria

**Example Usage**

Retrieve all Secrets

```csharp
var options = new SecretsManagerOptions(storage, testPostFunction);
var secrets = await SecretsManagerClient.GetSecrets(options);
```

### Retrieve Secrets by Title

```csharp
// get all matching records
async Task<IEnumerable<KeeperRecord>> GetSecretsByTitle(SecretsManagerOptions options, string recordTitle)

// get only the first matching record
async Task<KeeperRecord> GetSecretByTitle(SecretsManagerOptions options, string recordTitle)
```

<table><thead><tr><th width="178.10385756676556">Parameter</th><th width="226.46137991694417">Type</th><th width="150">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>options</code></td><td>SecretsManagerOptions</td><td>Yes</td><td>Preconfigured options</td></tr><tr><td><code>recordTitle</code></td><td>string</td><td>Yes</td><td>Record title to search for</td></tr></tbody></table>

**Example Usage**

```csharp
using System;
using System.Threading.Tasks;
using SecretsManager;

private static async Task getOneIndividualSecret()
{
    var storage = new LocalConfigStorage("ksm-config.json");
    var options = new SecretsManagerOptions(storage);
    var records = await SecretsManagerClient.GetSecretsByTitle(
            options, "My Credentials");
    foreach (var record in records)
    {
        Console.WriteLine(record.RecordUid + " - " + record.Data.title);
        foreach (var field in record.Data.fields)
        {
            Console.WriteLine("\t" + field.label + " (" + field.type + "): [" + String.Join(", ", field.value) + "]");
        }
    }
}
```

### Retrieve Values from a Secret

**Retrieve a Password**

{% tabs %}
{% tab title="Get Password" %}

```csharp
secret.FieldValue("password")
```

{% endtab %}

{% tab title="Example Usage" %}

```csharp
var storage = new LocalConfigStorage(configName);
Console.WriteLine($"Local Config Storage opened from the file {configName}");
if (clientKey != null)
    SecretsManagerClient.InitializeStorage(storage, "<One Time Access Token>");

var options = new SecretsManagerOptions(storage);
//get secrets
var secrets= (await SecretsManagerClient.GetSecrets(options)).Records;

// get the password from the first secret
var firstSecret= secrets[0];
var password = firstSecret.FieldValue("password").ToString();
```

{% endtab %}
{% endtabs %}

Field types are based on the Keeper [Record Type](https://docs.keeper.io/enterprise-guide/record-types). For a detailed list of available fields based on the Keeper Record Type, see the [record-type-info](https://docs.keeper.io/en/keeperpam/commander-cli/command-reference/record-commands/record-type-commands#record-type-info-command) command in Keeper Commander.

**Retrieve Other Fields Using Keeper Notation**

{% tabs %}
{% tab title="Get Value" %}

```csharp
GetValue(KeeperSecrets secrets, string notation)
```

{% endtab %}

{% tab title="Example Usage" %}

```csharp
var storage = new LocalConfigStorage(configName);
Console.WriteLine($"Local Config Storage opened from the file {configName}");
if (clientKey != null)
    SecretsManagerClient.InitializeStorage(storage, "<One Time Access Token>");

var options = new SecretsManagerOptions(storage);
//get secrets
var secrets = (await SecretsManagerClient.GetSecrets(options)).Records;

// get login field value using dot notation
var password = Notation.GetValue(secrets, "BediNKCMG21ztm5xGYgNww/field/login");
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
See [Keeper Notation documentation](https://docs.keeper.io/en/keeperpam/secrets-manager/about/keeper-notation) to learn about Keeper Notation format and capabilities
{% endhint %}

<table data-header-hidden><thead><tr><th>Parameter</th><th>Type</th><th width="200">Required</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td>Parameter</td><td>Type</td><td>Required</td><td>Default</td><td>Description</td></tr><tr><td><code>secrets</code></td><td><code>KeeperSecrets</code></td><td>Yes</td><td></td><td>Secrets to query</td></tr><tr><td><code>notation</code></td><td><code>string</code></td><td>Yes</td><td></td><td>Field query in dot notation format</td></tr></tbody></table>

**Retrieve TOTP Code**

{% tabs %}
{% tab title="Get TOTP Code" %}

```csharp
CryptoUtils.GetTotpCode(string url)
```

{% endtab %}

{% tab title="Example Usage" %}

```csharp
var storage = new LocalConfigStorage(configName);
Console.WriteLine($"Local Config Storage opened from the file {configName}");
if (clientKey != null)
    SecretsManagerClient.InitializeStorage(storage, "<One Time Access Token>");

var options = new SecretsManagerOptions(storage);
//get secrets
var secrets = (await SecretsManagerClient.GetSecrets(options)).Records;

// get TOTP url from a record
var url = Notation.GetValue(secrets, "BediNKCMG21ztm5xGYgNww/field/OneTimeCode");

// get TOTP code
var totp = CryptoUtils.GetTotpCode(url);
Console.WriteLine(totp.Code);
```

{% endtab %}
{% endtabs %}

<table data-header-hidden><thead><tr><th>Parameter</th><th>Type</th><th width="200">Required</th><th>Default</th><th>Description</th></tr></thead><tbody><tr><td>Parameter</td><td>Type</td><td>Required</td><td>Default</td><td>Description</td></tr><tr><td><code>url</code></td><td><code>string</code></td><td>Yes</td><td></td><td>TOTP Url</td></tr></tbody></table>

### Update Values in a Secret

{% hint style="warning" %}
Record update commands don't update local record data on success *(esp. updated record revision)* so any consecutive updates to an already updated record will fail due to **revision** mismatch. Make sure to reload all updated records after each update batch.
{% endhint %}

#### Save Changes to a Secret

{% tabs %}
{% tab title="Update Secret" %}

```csharp
UpdateSecret(options: SecretsManagerOptions, record: KeeperRecord, transactionType: UpdateTransactionType? = null);
```

{% endtab %}

{% tab title="Example Usage" %}

```csharp
var options = new SecretsManagerOptions(storage, testPostFunction);
await SecretsManagerClient.UpdateSecret(options, secret);
```

{% endtab %}

{% tab title="Transactional updates" %}

```csharp
var storage = new LocalConfigStorage(configName);
var options = new SecretsManagerOptions(storage);

//get secrets
var secrets = (await SecretsManagerClient.GetSecrets(options)).Records;

// get the first secret
var secret = secrets[0];

// rotate password on the record
secret.UpdateFieldValue("password", "MyNewPassword");
//start a transaction
await SecretsManagerClient.UpdateSecret(options, secret, UpdateTransactionType.Rotation);
// rotate password on remote host
success = rotateRemoteSshPassword("MyNewPassword");
// complete the transaction - commit or rollback
await SecretsManagerClient.CompleteTransaction(options, secret.RecordUid, rollback: !success);
```

{% endtab %}
{% endtabs %}

<table data-header-hidden><thead><tr><th width="162">Parameter</th><th width="243">Type</th><th width="103">Required</th><th width="89">Default</th><th>Description</th></tr></thead><tbody><tr><td>Parameter</td><td>Type</td><td>Required</td><td>Default</td><td>Description</td></tr><tr><td><code>options</code></td><td><code>SecretsManagerOptions</code></td><td>Yes</td><td></td><td>Storage and query configuration</td></tr><tr><td><code>record</code></td><td><code>KeeperRecord</code></td><td>Yes</td><td></td><td>The record with updated field values</td></tr><tr><td><code>transactionType</code></td><td><code>UpdateTransactionType?</code></td><td>Optional</td><td>null</td><td>Set to <code>Rotation</code> for transactional (two-phase) updates</td></tr></tbody></table>

Use UpdateSecret to save changes made to a secret record. Changes will not be reflected in the Keeper Vault until UpdateSecret is performed.

**Update a Field Value**

{% tabs %}
{% tab title="Update Field Value" %}

```csharp
UpdateFieldValue(string fieldType, object value)
```

{% endtab %}

{% tab title="Example Usage" %}

```csharp
var storage = new LocalConfigStorage(configName);
Console.WriteLine($"Local Config Storage opened from the file {configName}");
if (clientKey != null)
    SecretsManagerClient.InitializeStorage(storage, "<One Time Access Token>");

var options = new SecretsManagerOptions(storage);
//get secrets
var secrets= (await SecretsManagerClient.GetSecrets(options)).Records;

// get the password from the first secret
var firstSecret= secrets[0];

// update the login field
firstSecret.UpdateFieldValue("login", "My New Login");

// update title and notes
firstSecret.Data.title = "New Title";
firstSecret.Data.notes = "New Notes";

// save changes
await SecretsManagerClient.UpdateSecret(options, firstSecret);
```

{% endtab %}
{% endtabs %}

| Parameter   | Type     | Required | Default | Description               |
| ----------- | -------- | -------- | ------- | ------------------------- |
| `fieldType` | `string` | Yes      |         | The field to update       |
| `value`     | `object` | Yes      |         | Value to set the field to |

#### Generate a Random Password

{% tabs %}
{% tab title="Generate Password" %}

```csharp
CryptoUtils.GeneratePassword(
    int minLength = 32, 
    int? lowercase = null, 
    int? uppercase = null, 
    int? digits = null, 
    int? specialCharacters = null, 
    string specialCharacterSet = AsciiSpecialCharacters
)
```

{% endtab %}

{% tab title="Example Usage" %}

```csharp
// generate a 32-character password with balanced character distribution
var password = CryptoUtils.GeneratePassword();

// generate a 64-character password with at least 10 digits and 5 special characters
var strongPassword = CryptoUtils.GeneratePassword(minLength: 64, digits: 10, specialCharacters: 5);

// generate a 16-character password using only alphanumeric characters (no special chars)
var alphanumeric = CryptoUtils.GeneratePassword(minLength: 16, specialCharacters: 0);

// update a record with the new password
firstRecord.UpdateFieldValue("password", password);
await SecretsManagerClient.UpdateSecret(options, firstRecord);
```

{% endtab %}
{% endtabs %}

<table><thead><tr><th width="203.7142857142857">Parameter</th><th width="150">Type</th><th width="150">Required</th><th>Default</th></tr></thead><tbody><tr><td>minLength</td><td><code>int</code></td><td>Optional</td><td>32</td></tr><tr><td>lowercase</td><td><code>int?</code></td><td>Optional</td><td>null</td></tr><tr><td>uppercase</td><td><code>int?</code></td><td>Optional</td><td>null</td></tr><tr><td>digits</td><td><code>int?</code></td><td>Optional</td><td>null</td></tr><tr><td>specialCharacters</td><td><code>int?</code></td><td>Optional</td><td>null</td></tr><tr><td>specialCharacterSet</td><td><code>string</code></td><td>Optional</td><td><code>"!@#$%()+;&#x3C;>=?[]{}^.,</code></td></tr></tbody></table>

Each parameter indicates the minimum number of that character type to include. When `null`, the generator uses a balanced distribution across all character categories. The `specialCharacterSet` parameter controls which special characters are eligible for inclusion.

### Download a File

```csharp
DownloadFile(file: KeeperFile): ByteArray
```

| Parameter | Type         | Required | Default | Description      |
| --------- | ------------ | -------- | ------- | ---------------- |
| `file`    | `KeeperFile` | Yes      |         | File to download |

**Response**

Type: `ByteArray`

ByteArray of file for download

### Download a **Thumbnail**

```csharp
DownloadThumbnail(file: KeeperFile): ByteArray
```

| Parameter | Type         | Required | Default | Description                     |
| --------- | ------------ | -------- | ------- | ------------------------------- |
| `file`    | `KeeperFile` | Yes      |         | File with thumbnail to download |

**Response**

Type: `ByteArray`

ByteArray of thumbnail for download

### Upload a File

Upload File:

```csharp
UploadFile(SecretsManagerOptions options, KeeperRecord ownerRecord, KeeperFileUpload file)
```

<table><thead><tr><th width="184.14763231197773">Parameter</th><th width="224.99431983802478">Type</th><th width="150">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>options</code></td><td>SecretsManagerOptions</td><td>Yes</td><td>Storage and query configuration</td></tr><tr><td><code>ownerRecord</code></td><td>KeeperRecord</td><td>Yes</td><td>The record to attach the uploaded file to</td></tr><tr><td><code>file</code></td><td>KeeperFileUpload</td><td>Yes</td><td>The File to upload</td></tr></tbody></table>

Creating the Keeper File Upload Object:

```csharp
KeeperFileUpload(string name, string title, string type, byte[] data)
```

<table><thead><tr><th width="169">Parameter</th><th width="150">Type</th><th width="150">Required</th><th>Description</th></tr></thead><tbody><tr><td><code>name</code></td><td>string</td><td>Yes</td><td>What the name of the file will be in Keeper once uploaded</td></tr><tr><td><code>title</code></td><td>string</td><td>Yes</td><td>What the title of the file will be in Keeper once uploaded</td></tr><tr><td><code>type</code></td><td>string</td><td>Yes</td><td>The mime type of data in the file. 'application/octet-stream' for example</td></tr><tr><td><code>data</code></td><td>byte[]</td><td>Yes</td><td>File data as bytes</td></tr></tbody></table>

**Example Usage**

```csharp
using System;
using System.Threading.Tasks;
using SecretsManager;

private static async Task uploadFile()
{
    // initalize storage and options
    var storage = new LocalConfigStorage("ksm-config.json");
    var options = new SecretsManagerOptions(storage);
    
    // get a record to attach the file to
    var records = (await SecretsManagerClient.GetSecrets(
            options, new[] { "XXX" })
        ).Records;
        
    var ownerRecord = records[0];
    
    // get file data to upload
    var bytes = await File.ReadAllBytesAsync("my-file.json");
    var myFile = new KeeperFileUpload(
        "my-file1.json", 
        "My File", 
        null, 
        bytes
    );
       
    // upload file to selected record                     
    await SecretsManagerClient.UploadFile(options, ownerRecord, myFile);
    
}
```

### Create a Secret

#### Prerequisites:

* Shared folder UID
  * Shared folder must be accessible by the Secrets Manager Application
  * You and the Secrets Manager application must have edit permission
  * There must be at least one record in the shared folder
* Created records and record fields must be formatted correctly
  * See the [documentation](https://docs.keeper.io/enterprise-guide/record-types) for expected field formats for each record type
* TOTP fields accept only URL generated outside of the KSM SDK
* After record creation, you can upload file attachments using [UploadFile](#upload-a-file)

{% tabs %}
{% tab title="Create a Record" %}

```csharp
SecretsManagerClient.CreateSecret(options, folderUid, record)
```

<table><thead><tr><th width="168">Parameter</th><th width="261">Type</th><th>Required</th><th>Default</th></tr></thead><tbody><tr><td><code>options</code></td><td><code>SecretsManagerOptions</code></td><td>Yes</td><td></td></tr><tr><td><code>folderUid</code></td><td><code>string</code></td><td>Yes</td><td></td></tr><tr><td><code>record</code></td><td><code>KeeperRecordData</code></td><td>Yes</td><td></td></tr></tbody></table>
{% endtab %}

{% tab title="Create Record in Sub-folder" %}

```csharp
SecretsManagerClient.CreateSecret2(options, createOptions, record, folders)
```

<table><thead><tr><th width="168">Parameter</th><th width="261">Type</th><th>Required</th><th>Default</th></tr></thead><tbody><tr><td><code>options</code></td><td><code>SecretsManagerOptions</code></td><td>Yes</td><td></td></tr><tr><td><code>createOptions</code></td><td><code>CreateOptions</code></td><td>Yes</td><td></td></tr><tr><td><code>record</code></td><td><code>KeeperRecordData</code></td><td>Yes</td><td></td></tr><tr><td><code>folders</code></td><td><code>KeeperFolder[]</code></td><td>No</td><td></td></tr></tbody></table>
{% endtab %}

{% tab title="Login Record Example" %}
This example creates a login type record with a login value and a generated password.

{% hint style="info" %}
Replace '`[FOLDER UID]`' in the example with the UID of a shared folder that your Secrets Manager has access to.
{% endhint %}

```csharp
var newRecord = new KeeperRecordData{type = "login", title = "Sample KSM Record: C#"};

newRecord.fields = new[]
{
    new KeeperRecordField { type = "login", value = new[] { "My Username" } },
    new KeeperRecordField { type = "password", value = new[] { CryptoUtils.GeneratePassword() } },
};
newRecord.notes = "This is a C# record creation example";

var recordUid = await SecretsManagerClient.CreateSecret(options, folderUid, newRecord);
```

{% endtab %}

{% tab title="Custom Type Example" %}
This example creates a record with a custom record type.

{% hint style="info" %}
Replace '`[FOLDER UID]`' in the example with the UID of a shared folder that your Secrets Manager has access to.
{% endhint %}

```csharp
var newRecord = new KeeperRecordData();
newRecord.type = "Custom Login";
newRecord.title = "Sample Custom Type KSM Record: C#";
newRecord.fields = new[]
{
    new KeeperRecordField { 
        type = "host", 
        value = new[] 
        {
            new Dictionary<string, string>
            {
                { "hostName", "127.0.0.1"},
                { "port", "8080"}
            }
        },
        label = "My Custom Host lbl",
        required = true
    },
    
    new KeeperRecordField { 
        type = "login", 
        value = new[] { "login@email.com" }, 
        required = true, 
        label = "My Custom Login lbl"
    },
    
    new KeeperRecordField
    {
        type = "password", 
        value = new[] { CryptoUtils.GeneratePassword() }, 
        required = true, 
        label = "My Custom Password lbl"
    },
    
    new KeeperRecordField
    {
        type = "url",
        value = new[] { "http://localhost:8080/login" },
        label = "My Login Page",
        required = true
    },
    
    new KeeperRecordField { 
        type = "securityQuestion", 
        value = new[]
        {
            new Dictionary<string, string>
            {
                {"question", "What is one plus one (write just a number)"}, 
                { "answer", "2" }
            }
        }, 
        label = "My Question 1",
        required = true
    },
    
    new KeeperRecordField { 
        type = "phone", 
        value = new[]
        {
            new Dictionary<string,string>
            {
                { "region", "US" },
                { "number", "510-444-3333" },
                { "ext", "2345" },
                { "type", "Mobile" }
            }
        }, 
        label = "My Private Phone", 
        privacyScreen = true
    },

    new KeeperRecordField
    {
        type = "date", 
        value = new[] {(object) 1641934793000 }, 
        label = "My Date Lbl"
    },
    
    new KeeperRecordField { 
        type = "name", 
        value = new[]
        {
            new Dictionary<string, string>
            {
                {"first", "John"},
                {"middle", "Patrick"},
                {"last", "Smith"}
            }
        },
        required = true
    },
    new KeeperRecordField
    {
        type = "oneTimeCode", 
        value = new[]
        {
            "otpauth://totp/Example:alice@google.com?secret=JBSWY3DPEHPK3PXP&issuer=Example"
        }, 
        label = "My TOTP",
        required = true
    }
};
newRecord.notes = "\tThis custom type record was created\n\tvia .NET SDK copied from https://docs.keeper.io/secrets-manager/secrets-manager/developer-sdk-library/.net-sdk";

var recordUid = await SecretsManagerClient.CreateSecret(options, "[FOLDER_UID]", newRecord);

```

{% endtab %}
{% endtabs %}

### Delete a Secret

The .Net KSM SDK can delete records in the Keeper Vault.

{% tabs %}
{% tab title="Delete Secret" %}

```csharp
DeleteSecret(smOptions, recordsUids);
```

<table><thead><tr><th>Parameter</th><th width="250.66666666666666">Type</th><th align="center">Required</th></tr></thead><tbody><tr><td><code>smOptions</code></td><td><code>SecretsManagerOptions</code></td><td align="center">Yes</td></tr><tr><td><code>recordsUids</code></td><td><code>string[]</code></td><td align="center">Yes</td></tr></tbody></table>
{% endtab %}

{% tab title="Example" %}

<pre class="language-csharp"><code class="lang-csharp">using SecretsManager;

// setup secrets manager
var storage = new LocalConfigStorage("ksm-config.json");
//SecretsManagerClient.InitializeStorage(storage, "&#x3C;One Time Access Token>");
var smOptions = new SecretsManagerOptions(storage);

// delete a specific secret by record UID
<strong>await SecretsManagerClient.DeleteSecret(smOptions, new string[] {"EG6KdJaaLG7esRZbMnfbFA"});
</strong></code></pre>

{% endtab %}
{% endtabs %}

### Caching

To protect against losing access to your secrets when network access is lost, the .Net SDK allows caching of secrets to the local machine in an encrypted file.

**Setup and Configure Cache**

In order to setup caching in the .Net SDK, include a caching post function as the second argument when instantiating a`SecretsManagerOptions` object.

The .Net SDK includes a default caching function `cachingPostFunction` which stores cached queries to a file.

```csharp
var options = new SecretsManagerOptions(storage, SecretsManagerClient.CachingPostFunction);
var secrets = await SecretsManagerClient.GetSecrets(options);
```

### Folders

Folders have full CRUD support - create, read, update and delete operations.

### Read Folders

Downloads full folder hierarchy.

```csharp
Task<KeeperFolder[]> GetFolders(SecretsManagerOptions options)
```

**Response**

Type: `KeeperFolder[]`

**Example Usage**

```csharp
using SecretsManager;

var options = new SecretsManagerOptions(new LocalConfigStorage("ksm-config.json"));
var folders = await SecretsManagerClient.GetFolders(options);
```

### Create a Folder

Requires `CreateOptions` and folder name to be provided. The folder UID parameter in `CreateOptions` is required - UID of a shared folder, while sub-folder UID is optional and if missing, a new regular folder is created directly under the parent (shared folder). There's no requirement for the sub-folder to be a direct descendant of the parent shared folder - it could be many levels deep.

```csharp
Task<string> CreateFolder(SecretsManagerOptions options, CreateOptions createOptions, string folderName, KeeperFolder[] folders = null)
```

<table><thead><tr><th width="181.85161529417937">Parameter</th><th width="242.99431983802478">Type</th><th width="102">Required</th><th width="87">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>options</code></td><td><code>SecretsManagerOptions</code></td><td>Yes</td><td></td><td>Preconfigured options</td></tr><tr><td><code>createOptions</code></td><td><code>CreateOptions</code></td><td>Yes</td><td></td><td>The parent and sub-folder UIDs</td></tr><tr><td><code>folderName</code></td><td><code>string</code></td><td>Yes</td><td></td><td>The Folder name</td></tr><tr><td><code>folders</code></td><td><code>KeeperFolder[]</code></td><td>No</td><td><code>null</code></td><td>List of folders to use in the search for parent and sub-folder from CreateOptions</td></tr></tbody></table>

```csharp
public class CreateOptions {
    public string FolderUid { get; }
    public string SubFolderUid { get; }
}
```

```csharp
public class KeeperFolder {
        public byte[] FolderKey { get; }
        public string FolderUid { get; }
        public string ParentUid { get; }
        public string Name { get; }
}
```

**Example Usage**

```csharp
using SecretsManager;

var options = new SecretsManagerOptions(new LocalConfigStorage("ksm-config.json"));
var co = new CreateOptions("[PARENT_SHARED_FOLDER_UID]");
var folderUid = await SecretsManagerClient.CreateFolder(options, co, "new_folder");
```

### Update a Folder

Updates the folder metadata - currently folder name only.

```csharp
Task UpdateFolder(SecretsManagerOptions options, string folderUid, string folderName, KeeperFolder[] folders = null)
```

<table><thead><tr><th width="154.85161529417937">Parameter</th><th width="243.99431983802478">Type</th><th width="100">Required</th><th width="88">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>options</code></td><td><code>SecretsManagerOptions</code></td><td>Yes</td><td></td><td>Preconfigured options</td></tr><tr><td><code>folderUid</code></td><td><code>string</code></td><td>Yes</td><td></td><td>The folder UID</td></tr><tr><td><code>folderName</code></td><td><code>string</code></td><td>Yes</td><td></td><td>The new folder name</td></tr><tr><td><code>folders</code></td><td><code>KeeperFolder[]</code></td><td>No</td><td><code>null</code></td><td>List of folders to use in the search for parent folder</td></tr></tbody></table>

**Example Usage**

```csharp
using SecretsManager;

var options = new SecretsManagerOptions(new LocalConfigStorage("ksm-config.json"));
await SecretsManagerClient.UpdateFolder(options, "[FOLDER_UID]", "new_folder_name");
```

### Delete Folders

Removes a list of folders. Use `forceDeletion` flag to remove non-empty folders.

{% hint style="info" %}
When using forceDeletion avoid sending parent with its children folder UIDs. Depending on the delete order you may get an error - *ex.* if parent force-deleted child first. There's no guarantee that list will always be processed in FIFO order.
{% endhint %}

{% hint style="info" %}
Any folders UIDs missing from the vault or not shared to the KSM Application will not result in error.
{% endhint %}

```csharp
Task DeleteFolder(SecretsManagerOptions options, string[] folderUids, bool forceDeletion = false)
```

<table><thead><tr><th width="180.85161529417937">Parameter</th><th width="242.99431983802478">Type</th><th width="100">Required</th><th width="89">Default</th><th>Description</th></tr></thead><tbody><tr><td><code>options</code></td><td><code>SecretsManagerOptions</code></td><td>Yes</td><td></td><td>Preconfigured options</td></tr><tr><td><code>folderUids</code></td><td><code>string[]</code></td><td>Yes</td><td></td><td>The folder UID list</td></tr><tr><td><code>forceDeletion</code></td><td><code>bool</code></td><td>No</td><td><code>false</code></td><td>Force deletion of non-empty folders</td></tr></tbody></table>

**Example Usage**

```csharp
using SecretsManager;

var options = new SecretsManagerOptions(new LocalConfigStorage("ksm-config.json"));
await SecretsManagerClient.DeleteFolder(options, new string[]{"[FOLDER_UID1]", "[FOLDER_UID2]"}, true);
```

### Proxy support

.NET SDK supports setting proxy through environment variables and passing proxy url to `SecretsManagerOptions` directly

{% hint style="info" %}
Keeper recommends using **environment variables** for proxy settings. This approach keeps configuration details out of code, ensures consistency across tools and environments, and simplifies deployment and maintenance
{% endhint %}

**Example usage through environment variables**

```
HTTPS_PROXY=http://user:password@127.0.0.1:3128 dotnet run
```

Example usage passing url to `SecretsManagerOptions`

```csharp
var options = new SecretsManagerOptions(storage, null, false, "http://user:password@127.0.0.1:3128");
```
