This document describes the architecture and design decisions of the UptimeRobot .NET Client Library.
- Overview
- Project Structure
- Core Components
- Design Patterns
- Data Flow
- Key Design Decisions
- Extension Points
The UptimeRobot .NET Client Library is a strongly-typed, modern .NET client for the UptimeRobot API. It provides:
- Type Safety: Strongly-typed models with nullable reference types
- Ease of Use: Fluent API with automatic pagination
- Flexibility: Support for multiple .NET versions (9.0, 8.0, 6.0, Standard 2.0)
- Reliability: Comprehensive error handling and logging support
- Testability: Dependency injection support and mockable interfaces
uptimerobot-dotnet/
├── src/
│ ├── Apis/ # API endpoint implementations
│ │ ├── ApiMonitors.cs
│ │ ├── ApiAlertContacts.cs
│ │ ├── ApiMaintenanceWindows.cs
│ │ └── ApiStatusPages.cs
│ ├── Exceptions/ # Custom exception types
│ │ ├── UptimeRobotException.cs
│ │ ├── UptimeRobotApiException.cs
│ │ └── UptimeRobotValidationException.cs
│ ├── Models/ # Data models
│ │ ├── Monitor.cs
│ │ ├── AlertContact.cs
│ │ ├── MaintenanceWindow.cs
│ │ ├── StatusPage.cs
│ │ ├── Parameters.cs
│ │ ├── Responses.cs
│ │ ├── Enums.cs
│ │ ├── BaseModel.cs
│ │ └── Interfaces.cs
│ ├── UptimeRobotClient.cs # Main client class
│ ├── UptimeRobotClientBase.cs # Base HTTP functionality
│ ├── UptimeRobotClientFactory.cs
│ └── UtrFormUrlEncodedContent.cs
├── test/
│ └── UptimeRobotDotNetTests/
│ ├── Monitors/
│ ├── AlertContacts/
│ ├── MaintenanceWindows/
│ ├── StatusPages/
│ └── Core/
└── docs/
├── API_REFERENCE.md
├── ARCHITECTURE.md
└── CONTRIBUTING.md
Location: src/UptimeRobotClient.cs
Purpose: Main entry point for the library. Partial class that combines all API implementations.
Responsibilities:
- Stores API key
- Inherits base HTTP functionality
- Aggregates all API endpoint methods
public partial class UptimeRobotClient : UptimeRobotClientBase
{
private readonly string _apiKey;
public UptimeRobotClient(HttpClient httpClient, string apiKey, string apiVersion, ILogger logger)
: base(httpClient, apiVersion, logger)
{
_apiKey = apiKey ?? throw new ArgumentNullException(nameof(apiKey));
}
}Design Note: Uses partial class to separate API implementations into logical files (one per API section).
Location: src/UptimeRobotClientBase.cs
Purpose: Provides core HTTP communication functionality.
Responsibilities:
- HTTP client management
- Request serialization (form-urlencoded)
- Response deserialization (JSON)
- Error detection and parsing
- Logging HTTP operations
Key Methods:
protected async Task<T> PostAsync<T>(Uri path, IContentModel content, CancellationToken cancellationToken)Design Note: Separates HTTP concerns from API-specific logic.
Location: src/UptimeRobotClientFactory.cs
Purpose: Factory for creating client instances with proper configuration.
Responsibilities:
- Create and configure
HttpClient - Set default headers (User-Agent)
- Provide singleton
HttpClientinstance - Support custom
HttpClientinjection
Usage:
// Simple creation
var client = UptimeRobotClientFactory.Create("api-key");
// With custom HttpClient
var httpClient = new HttpClient();
var client = UptimeRobotClientFactory.Create(httpClient, "api-key");
// With logging
var client = UptimeRobotClientFactory.Create("api-key", logger: logger);Design Note: Follows the factory pattern to encapsulate client creation complexity.
Location: src/Apis/Api*.cs
Purpose: Implement specific API endpoints as partial classes of UptimeRobotClient.
Structure:
namespace UptimeRobotDotnet
{
public partial class UptimeRobotClient
{
// Endpoint path constants
public const string MonitorsGetPath = "getMonitors";
public const string MonitorsCreatePath = "newMonitor";
// API methods
public async Task<UtrResponse> GetMonitorsAsync(...)
public async IAsyncEnumerable<Monitor> GetAllMonitorsAsync(...)
public async Task<UtrResponse> CreateMonitorAsync(...)
}
}Design Note: Each API section (Monitors, Alert Contacts, etc.) is in its own file for maintainability.
Location: src/Models/
Purpose: Data Transfer Objects (DTOs) for API requests and responses.
Categories:
Monitor.cs: Monitor entityAlertContact.cs: Alert contact entityMaintenanceWindow.cs: Maintenance window entityStatusPage.cs: Status page entity
MonitorSearchParameters: Query parameters for searching monitorsMonitorCreateParameters: Parameters for creating monitorsMonitorUpdateParameters: Parameters for updating monitors- Similar for other APIs
UtrResponse: Standard API response wrapperUtrPagination: Pagination informationUtrError: Error detailsUtrResponseActionMonitor: Action response with monitor ID
Key Features:
- JSON serialization attributes (
[JsonPropertyName]) - Validation attributes (
[Required],[Range]) - Nullable reference types
- Strongly-typed enums
Location: src/Exceptions/
Purpose: Provide detailed error information for different failure scenarios.
Exception
└── UptimeRobotException (base for all library exceptions)
├── UptimeRobotApiException (API returned error response)
└── UptimeRobotValidationException (client-side validation failed)
Usage:
try
{
await client.CreateMonitorAsync(parameters);
}
catch (UptimeRobotValidationException ex)
{
// Handle validation errors
foreach (var error in ex.ValidationResults)
{
Console.WriteLine(error.ErrorMessage);
}
}
catch (UptimeRobotApiException ex)
{
// Handle API errors
Console.WriteLine($"API Error: {ex.ErrorResponse?.Error?.Message}");
}
catch (UptimeRobotException ex)
{
// Handle other errors
Console.WriteLine($"Error: {ex.Message}");
}Component: UptimeRobotClientFactory
Purpose: Encapsulate client creation and configuration.
Benefits:
- Centralized configuration
- Reusable
HttpClientinstances - Easy to mock for testing
Component: UptimeRobotClient + API implementations
Purpose: Separate concerns while maintaining single class interface.
Benefits:
- Logical file organization
- Easy to navigate
- Maintains encapsulation
Component: UptimeRobotClientBase.PostAsync<T>()
Purpose: Define skeleton of HTTP request/response handling.
Benefits:
- Consistent error handling
- Centralized logging
- Reusable serialization logic
Component: Parameter classes
Purpose: Provide intuitive API for constructing requests.
Example:
var parameters = new MonitorCreateParameters
{
FriendlyName = "My Monitor",
Url = "https://example.com",
Type = MonitorType.HTTP,
Interval = 300
};Component: IAsyncEnumerable<T> pagination methods
Purpose: Abstract pagination complexity.
Example:
await foreach (var monitor in client.GetAllMonitorsAsync())
{
// Handles pagination automatically
}┌─────────────┐
│ Client │
│ Code │
└──────┬──────┘
│ 1. Call API method
▼
┌─────────────────────┐
│ UptimeRobotClient │
│ (API implementation)│
└──────┬──────────────┘
│ 2. Prepare parameters
▼
┌─────────────────────┐
│ BaseModel │
│ GetContentForRequest│
└──────┬──────────────┘
│ 3. Serialize to Dictionary
▼
┌─────────────────────────┐
│ UptimeRobotClientBase │
│ PostAsync<T> │
└──────┬──────────────────┘
│ 4. Create form-urlencoded content
▼
┌─────────────────────────────┐
│ UtrFormUrlEncodedContent │
│ (HTTP request body) │
└──────┬──────────────────────┘
│ 5. HTTP POST
▼
┌─────────────────┐
│ HttpClient │
└──────┬──────────┘
│ 6. Send request
▼
┌─────────────────┐
│ UptimeRobot API │
└─────────────────┘
┌─────────────────┐
│ UptimeRobot API │
└──────┬──────────┘
│ 1. JSON response
▼
┌─────────────────┐
│ HttpClient │
└──────┬──────────┘
│ 2. HTTP response
▼
┌─────────────────────────┐
│ UptimeRobotClientBase │
│ PostAsync<T> │
└──────┬──────────────────┘
│ 3. Check for "stat: fail"
├─ Yes → Throw UptimeRobotApiException
│
│ 4. Check HTTP status
├─ Error → Throw UptimeRobotException
│
│ 5. Deserialize JSON
▼
┌─────────────────────┐
│ System.Text.Json │
└──────┬──────────────┘
│ 6. Create typed object
▼
┌─────────────────────┐
│ Response Model │
│ (e.g., Monitor) │
└──────┬──────────────┘
│ 7. Return to caller
▼
┌─────────────┐
│ Client │
│ Code │
└─────────────┘
// Automatic pagination with IAsyncEnumerable
public async IAsyncEnumerable<Monitor> GetAllMonitorsAsync(
MonitorSearchParameters? parameters = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
parameters ??= new MonitorSearchParameters { ApiKey = _apiKey };
parameters.Limit ??= 50;
var currentPage = 0;
var total = 0;
do
{
parameters.Offset = currentPage * parameters.Limit;
var response = await GetMonitorsAsync(parameters, cancellationToken);
if (response?.Monitors != null)
{
foreach (var monitor in response.Monitors)
{
yield return monitor; // Stream results
}
}
total = response?.Pagination?.Total ?? 0;
currentPage++;
} while (currentPage * parameters.Limit < total && !cancellationToken.IsCancellationRequested);
}Decision: Enable nullable reference types project-wide.
Rationale:
- Improved null-safety
- Better IDE support
- Prevents null reference exceptions
Impact:
- All properties explicitly marked as nullable or non-nullable
- Compiler warnings for potential null issues
- Breaking change from v1.x
Decision: Use PascalCase for all C# properties with [JsonPropertyName] for snake_case API fields.
Rationale:
- Follow .NET naming conventions
- Better developer experience
- Consistent with .NET ecosystem
Example:
[JsonPropertyName("friendly_name")]
public string? FriendlyName { get; set; }Decision: Replace integer types with enum types.
Rationale:
- Type safety
- IntelliSense support
- Self-documenting code
Example:
// Before (v1.x)
Type = 1
// After (v2.0)
Type = MonitorType.HTTPDecision: Use form-urlencoded for requests (as per API requirement).
Rationale:
- UptimeRobot API requires form-urlencoded
- Simpler than multipart/form-data
- Custom implementation in
UtrFormUrlEncodedContent
Decision: Use IAsyncEnumerable<T> for automatic pagination.
Rationale:
- Streams results efficiently
- Memory-efficient for large datasets
- Modern async/await pattern
- Supports cancellation
Decision: Split API implementations across multiple files using partial classes.
Rationale:
- Logical organization
- Easier navigation
- Maintains single public interface
- Reduces file size
Decision: Create custom exception types instead of using generic exceptions.
Rationale:
- Detailed error information
- Easier exception handling
- API-specific error context
- Better debugging
Decision: Cache reflection results in GetContentForRequest().
Rationale:
- Performance optimization
- Reduce reflection overhead
- One-time reflection per type
- Significant improvement for repeated calls
Users can provide their own HttpClient for custom configuration:
var httpClient = new HttpClient
{
Timeout = TimeSpan.FromSeconds(60)
};
httpClient.DefaultRequestHeaders.Add("X-Custom-Header", "value");
var client = UptimeRobotClientFactory.Create(httpClient, apiKey);Support for ILogger allows integration with any logging framework:
var loggerFactory = LoggerFactory.Create(builder =>
{
builder.AddConsole();
builder.AddApplicationInsights();
});
var logger = loggerFactory.CreateLogger<UptimeRobotClient>();
var client = UptimeRobotClientFactory.Create(apiKey, logger: logger);Users can extend parameter classes with additional validation:
public class CustomMonitorParameters : MonitorCreateParameters
{
[CustomValidation(typeof(MyValidator), "ValidateUrl")]
public new string Url { get; set; }
}Users can add retry policies to the HttpClient:
var retryPolicy = Policy
.Handle<HttpRequestException>()
.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt)));
var httpClient = new HttpClient(new PolicyHttpMessageHandler(retryPolicy));
var client = UptimeRobotClientFactory.Create(httpClient, apiKey);- WireMock.Net: Mock HTTP responses
- NUnit: Test framework
- Coverage: >95% code coverage
Structure:
test/UptimeRobotDotNetTests/
├── BaseTest.cs # Base test class with logging
├── BaseHttpClientTest.cs # Base for HTTP mocking tests
├── Monitors/
│ ├── MonitorsTests.cs # Unit tests
│ └── MonitorsManualTests.cs # Integration tests
├── AlertContacts/
├── MaintenanceWindows/
├── StatusPages/
└── Core/
├── BaseModelTests.cs
└── UtrFormUrlEncodedContentTests.cs
- Arrange-Act-Assert
- Mock HTTP responses with WireMock
- Manual integration tests marked
[Explicit] - Parameterized tests with
[TestCase]
- HttpClient Reuse: Singleton
HttpClientin factory - Reflection Caching: Cache property info in
BaseModel - Streaming Results:
IAsyncEnumerablefor pagination - Async/Await: Fully asynchronous API
- ConfigureAwait(false): Used throughout for library code
- Source Generators: Replace reflection with compile-time generation
- Polly Integration: Built-in retry and circuit breaker patterns
- Response Caching: Optional caching layer
- GraphQL Support: If UptimeRobot adds GraphQL API
- Real-time Updates: WebSocket support if available
The UptimeRobot .NET Client Library is designed with:
- Separation of Concerns: Clear boundaries between HTTP, API, and domain logic
- Extensibility: Multiple extension points for customization
- Type Safety: Nullable types, enums, and validation
- Performance: Caching, streaming, and async patterns
- Testability: Dependency injection and mockable interfaces
- Maintainability: Organized structure and clear patterns
These architectural decisions ensure the library is robust, performant, and easy to use while maintaining flexibility for advanced scenarios.