---
title: Complete .NET Example
slug: devicepark/sdk-documentation/examples/csharp-quickstart
description: Run a complete .NET allocation and session lifecycle.
docTags: 
createdAt: 2026-10-02T08:00:00.000Z
---

This example lists a device, creates an allocation, starts and stops one session, then releases the allocation. It is a complete console application and always attempts cleanup.

## Prerequisites

```bash
dotnet new console -o device-park-quickstart
cd device-park-quickstart
dotnet add package Testinium.DevicePark

export DEVICEPARK_CLIENT_ID=your-client-id
export DEVICEPARK_CLIENT_SECRET=your-client-secret
```

Replace the generated `Program.cs` with the following:

```csharp
using Testinium.DevicePark;
using Testinium.DevicePark.Errors;
using Testinium.DevicePark.Models;

using var client = DeviceParkApiClient.Builder()
    .Url("https://devicepark.testinium.io")
    .Credentials(Credentials.FromEnvironment())
    .Timeout(60)
    .Build();

string? allocationId = null;
string? sessionId = null;

try
{
    var devices = await client.Devices.ListAsync(new ListDevicesRequest.Builder()
        .Page(0)
        .Size(20)
        .AddFilter(DeviceFilter.STATE, "HEALTHY", SearchOperation.EQUAL)
        .Build());

    var serial = devices.Data.FirstOrDefault(candidate => candidate.Serial is not null)?.Serial;
    if (serial is null)
    {
        throw new InvalidOperationException("No visible device has a serial number");
    }

    var allocation = await client.Allocations.CreateAsync(new DeviceAllocationRequest.Builder()
        .Serial(serial)
        .Priority(3)
        .Build());

    allocationId = allocation.AllocationId
        ?? throw new InvalidOperationException("Allocation response does not contain allocationId");

    if (allocation.DeviceSerial is null)
    {
        throw new InvalidOperationException(
            $"Allocation {allocationId} is queued at position {allocation.Position?.ToString() ?? "unknown"}");
    }

    var session = await client.Sessions.StartAsync(new DeviceStartSessionRequest.Builder()
        .AllocationId(allocationId)
        .UserId(42)
        .UserEmail("qa@example.com")
        .CompanyId(7)
        .CompanyName("Example Company")
        .VideoRecording(true)
        .Build());

    sessionId = session.SessionId
        ?? throw new InvalidOperationException("Session response does not contain sessionId");

    Console.WriteLine($"allocationId={allocationId} sessionId={sessionId} deviceSerial={session.DeviceSerial}");

    // Run Appium automation here while the session is active.
}
catch (DeviceParkHttpException error)
{
    Console.Error.WriteLine($"Device Park request failed: {error.Status} {error.ErrorCode} {error.ErrorMessage}");
    throw;
}
finally
{
    if (sessionId is not null)
    {
        try
        {
            await client.Sessions.StopAsync(sessionId);
        }
        catch (DeviceParkException error)
        {
            Console.Error.WriteLine($"Could not stop session {sessionId}: {error.Message}");
        }
    }

    if (allocationId is not null)
    {
        try
        {
            await client.Allocations.DeleteAsync(allocationId);
        }
        catch (DeviceParkException error)
        {
            Console.Error.WriteLine($"Could not release allocation {allocationId}: {error.Message}");
        }
    }
}
```

Run it:

```bash
dotnet run
```

The client implements `IDisposable`, so the `using` declaration releases it when `Main` returns. There is no explicit close call.

The outer `catch` logs the error detail and rethrows, so the process exits with a non-zero code and CI treats the run as failed. The inner `catch` blocks inside `finally` deliberately swallow cleanup errors so that a failure while stopping the session does not replace the original test failure.

If the allocation enters the queue, this minimal example reports its position and releases it during cleanup. A production runner should keep the same `allocationId`, inspect [List Allocations](docId\:GGobQpsUr2aKNz0jfCNYD) until `DeviceSerial` is assigned, and enforce its own maximum wait time.

:::hint{type="warning"}
A device must belong to a pool before it can be allocated. A device that appears in List Devices and reports a healthy state is not necessarily allocatable; if it belongs to no pool, allocation fails with `ALLOCATION_ERR003`. Add the device to a pool first, or ask a Device Park administrator to do so.
:::

## Target Frameworks

The SDK ships `netstandard2.0`, `net472` and `net8.0` assets and has no package dependencies, so this example also compiles unchanged in an older project. Replace the top-level statements with a `Main` method when targeting .NET Framework.

| Project target              | Asset used       |
| --------------------------- | ---------------- |
| .NET Framework 4.7.2+       | `net472`         |
| .NET Core 3.1, .NET 5, 6, 7 | `netstandard2.0` |
| .NET 8, 9, 10 and later     | `net8.0`         |

## Continue

- [Understand allocation targeting and queue behavior](docId\:sPz_N5rutGOmDKrBjbb0L)
- [Review .NET error types](docId\:nZrw0_UkMOapMnZtgQtMI)
- [Review exact method signatures](docId\:OoaKfd3Ua6tGD-fnw_WuH)
