NetDiag .NET Client | Xakpc.NetDiag.Client NuGet Package

Official .NET SDK for NetDiag API. Run network diagnostics from C# applications with dependency injection, async/await, and .NET 8/9 support.

.NET
Xakpc.NetDiag.Client NuGet

Official .NET SDK for NetDiag API

Installation

dotnet add package Xakpc.NetDiag.Client

Or add to your project file:

<PackageReference Include="Xakpc.NetDiag.Client" Version="*" />

Requirements: .NET 8.0 or .NET 9.0


Quick Start

using Xakpc.NetDiag.Client;

// Create client with API key
using var client = new NetDiagClient("your-api-key");

// Run diagnostics on a target
var result = await client.CheckAsync("example.com");

Console.WriteLine(result.Status);        // Healthy, Warning, or Unhealthy
Console.WriteLine(result.Quorum);        // "3/3"
Console.WriteLine(result.Locations);     // Per-region results

For quick health checks:

// Simple boolean check
bool isUp = await client.IsHealthyAsync("example.com");

// Get status enum
Status status = await client.GetStatusAsync("example.com");

Dependency Injection

Register the client in your ASP.NET Core application:

// Program.cs or Startup.cs
builder.Services.AddNetDiagClient(options =>
{
    options.ApiKey = builder.Configuration["NetDiag:ApiKey"];
});

Then inject INetDiagClient:

public class HealthController : ControllerBase
{
    private readonly INetDiagClient _netDiag;

    public HealthController(INetDiagClient netDiag)
    {
        _netDiag = netDiag;
    }

    [HttpGet("check/{target}")]
    public async Task<IActionResult> Check(string target)
    {
        var result = await _netDiag.CheckAsync(target);
        return Ok(result);
    }
}

Configuration via appsettings.json

{
  "NetDiag": {
    "ApiKey": "your-api-key",
    "BaseUrl": "https://api.netdiag.dev",
    "Timeout": "00:00:30"
  }
}
builder.Services.AddNetDiagClient(
    builder.Configuration.GetSection("NetDiag")
);

API Reference

Constructors

// Default (no API key, rate-limited)
new NetDiagClient()

// With API key
new NetDiagClient(string apiKey)

// With full options
new NetDiagClient(NetDiagClientOptions options)

// For DI with custom HttpClient
new NetDiagClient(HttpClient httpClient, IOptions<NetDiagClientOptions> options)

Methods

CheckAsync

Run full diagnostics on a target.

Task<CheckResponse> CheckAsync(string target, CancellationToken ct = default)
Task<CheckResponse> CheckAsync(ChecksRequest request, CancellationToken ct = default)

Examples:

// Simple check with just hostname
var result = await client.CheckAsync("example.com");

// Check with options
var result = await client.CheckAsync(new ChecksRequest
{
    Target = "example.com",
    Port = 443,
    PingCount = 10,
    Regions = "us-east,eu-west,ap-southeast"
});

CheckPrometheusAsync

Get diagnostics results in Prometheus metrics format.

Task<string> CheckPrometheusAsync(string target, CancellationToken ct = default)
Task<string> CheckPrometheusAsync(ChecksRequest request, CancellationToken ct = default)

Example:

string metrics = await client.CheckPrometheusAsync("example.com");
// Returns Prometheus-formatted metrics string

IsHealthyAsync

Quick boolean health check.

Task<bool> IsHealthyAsync(string target, CancellationToken ct = default)

Example:

if (await client.IsHealthyAsync("api.example.com"))
{
    Console.WriteLine("Service is healthy!");
}

GetStatusAsync

Get the status enum for a target.

Task<Status> GetStatusAsync(string target, CancellationToken ct = default)

Example:

var status = await client.GetStatusAsync("example.com");

switch (status)
{
    case Status.Healthy:
        Console.WriteLine("All systems operational");
        break;
    case Status.Warning:
        Console.WriteLine("Degraded performance");
        break;
    case Status.Unhealthy:
        Console.WriteLine("Service is down");
        break;
}

Request Options

When using ChecksRequest:

Property Type Default Description
Target string required Hostname, IP address, or URL to check
Port int? 443 Port to check (80, 443, 8080, 8443)
PingCount int? 4 Number of ping packets (1-100)
PingTimeout int? 5 Ping timeout in seconds (1-30)
Dns string? null Custom DNS server to use
Regions string? null Comma-separated region codes

Available Regions: us-east, us-west, eu-west, eu-central, ap-southeast, ap-northeast


Response Types

CheckResponse

public record CheckResponse
{
    public string RunId { get; init; }
    public string Target { get; init; }
    public Status Status { get; init; }
    public string Quorum { get; init; }
    public string DnsPropagationStatus { get; init; }
    public DateTimeOffset StartedAt { get; init; }
    public DateTimeOffset CompletedAt { get; init; }
    public IReadOnlyList<LocationResult> Locations { get; init; }
}

LocationResult

public record LocationResult
{
    public string Region { get; init; }
    public Status Status { get; init; }
    public PingResult? Ping { get; init; }
    public DnsResult? Dns { get; init; }
    public TlsResult? Tls { get; init; }
    public HttpResult? Http { get; init; }
}

Diagnostic Results

public record PingResult
{
    public Status Status { get; init; }
    public double LatencyMs { get; init; }
    public double? MinRttMs { get; init; }
    public double? MaxRttMs { get; init; }
    public double? PacketLossPercent { get; init; }
    public bool TcpFallbackUsed { get; init; }
    public string? Message { get; init; }
    public ErrorInfo? Error { get; init; }
}
public record DnsResult
{
    public Status Status { get; init; }
    public IReadOnlyList<string> ResolvedAddresses { get; init; }
    public string? Message { get; init; }
    public ErrorInfo? Error { get; init; }
}
public record TlsResult
{
    public Status Status { get; init; }
    public bool CertificateValid { get; init; }
    public int? DaysUntilExpiry { get; init; }
    public DateTimeOffset? ExpiresAt { get; init; }
    public string? Subject { get; init; }
    public string? Issuer { get; init; }
    public string? Protocol { get; init; }
    public string? Message { get; init; }
    public ErrorInfo? Error { get; init; }
}
public record HttpResult
{
    public Status Status { get; init; }
    public int? StatusCode { get; init; }
    public string? ReasonPhrase { get; init; }
    public double? ResponseTimeMs { get; init; }
    public string? Message { get; init; }
    public ErrorInfo? Error { get; init; }
}

Error Handling

The client throws typed exceptions for different failure scenarios:

using Xakpc.NetDiag.Client;
using Xakpc.NetDiag.Client.Exceptions;

try
{
    var result = await client.CheckAsync("example.com");
}
catch (NetDiagRateLimitException ex)
{
    // Rate limited - wait and retry
    Console.WriteLine($"Retry after {ex.RetryAfterSeconds} seconds");
    await Task.Delay(TimeSpan.FromSeconds(ex.RetryAfterSeconds));
}
catch (NetDiagApiException ex)
{
    // API error (4xx/5xx)
    Console.Error.WriteLine($"API Error: {ex.StatusCode} - {ex.ReasonPhrase}");
}
catch (NetDiagException ex)
{
    // Other client errors
    Console.Error.WriteLine($"Client Error: {ex.Message}");
}

Exception Types

Exception Description
NetDiagException Base exception for all client errors
NetDiagApiException HTTP errors with StatusCode and ReasonPhrase
NetDiagRateLimitException Rate limit exceeded with RetryAfterSeconds

Polly Retry Example

using Polly;

var retryPolicy = Policy
    .Handle<NetDiagRateLimitException>()
    .WaitAndRetryAsync(
        retryCount: 3,
        sleepDurationProvider: (retryAttempt, exception, context) =>
        {
            if (exception is NetDiagRateLimitException rle)
                return TimeSpan.FromSeconds(rle.RetryAfterSeconds);
            return TimeSpan.FromSeconds(Math.Pow(2, retryAttempt));
        },
        onRetryAsync: (exception, timespan, retryAttempt, context) =>
        {
            Console.WriteLine($"Retry {retryAttempt} after {timespan}");
            return Task.CompletedTask;
        }
    );

var result = await retryPolicy.ExecuteAsync(
    () => client.CheckAsync("example.com")
);

Advanced Usage

Custom HttpClient

Provide your own HttpClient for advanced scenarios:

var httpClient = new HttpClient
{
    Timeout = TimeSpan.FromSeconds(60)
};

var options = Options.Create(new NetDiagClientOptions
{
    ApiKey = "your-api-key"
});

var client = new NetDiagClient(httpClient, options);

ASP.NET Core Health Checks Integration

public class NetDiagHealthCheck : IHealthCheck
{
    private readonly INetDiagClient _client;
    private readonly string _target;

    public NetDiagHealthCheck(INetDiagClient client, string target)
    {
        _client = client;
        _target = target;
    }

    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context,
        CancellationToken ct = default)
    {
        try
        {
            var status = await _client.GetStatusAsync(_target, ct);
            return status switch
            {
                Status.Healthy => HealthCheckResult.Healthy(),
                Status.Warning => HealthCheckResult.Degraded(),
                _ => HealthCheckResult.Unhealthy()
            };
        }
        catch (Exception ex)
        {
            return HealthCheckResult.Unhealthy(ex.Message);
        }
    }
}

Prometheus Metrics Endpoint

app.MapGet("/metrics", async (INetDiagClient client) =>
{
    var metrics = await client.CheckPrometheusAsync("your-service.com");
    return Results.Text(metrics, "text/plain");
});

IDisposable Pattern

The client implements IDisposable. When creating the client directly (not via DI), dispose it when done:

using var client = new NetDiagClient("your-api-key");
var result = await client.CheckAsync("example.com");
// Client is automatically disposed

Note: The client only disposes the HttpClient if it created one internally. When using DI or providing your own HttpClient, you're responsible for its lifecycle.


Related