Skip to content

Configuration Guide: ConnectSoft API Library Template

The ConnectSoft API Library Template leverages the robust .NET Options pattern for managing configuration settings. This approach provides strongly-typed access to configuration values, integrates seamlessly with Dependency Injection (DI), and supports powerful validation mechanisms.

The Options Pattern Overview

The Options pattern provides a way to access configuration values as strongly-typed objects. This offers several benefits:

  • Strongly-Typed Access: Eliminates magic strings and provides compile-time safety
  • Validation: Allows for validation of configuration values, ensuring they meet specific criteria
  • Isolation: Configuration for a specific component is encapsulated in its own options class
  • DI Integration: Options classes are easily injected into services using IOptions<T>, IOptionsSnapshot<T>, or IOptionsMonitor<T>

Service Options

The main service options class ({ServiceName}ServiceOptions) contains all configuration for the API service.

Base Configuration

{
  "MyService": {
    "BaseUrl": "https://api.example.com",
    "DefaultTimeout": "00:00:30"
  }
}

Complete Service Options Structure

public sealed class MyServiceOptions
{
    [Required]
    [DataType(DataType.Url)]
    required public string BaseUrl { get; set; }

    [Required]
    [Range(typeof(TimeSpan), "00:00:01", "00:05:00")]
    required public TimeSpan DefaultTimeout { get; set; }

    [Required]
    [ValidateObjectMembers]
    required public WebProxyOptions WebProxy { get; set; }

    [Required]
    required public bool EnableHttpStandardResilience { get; set; }

    [Required]
    [ValidateObjectMembers]
    required public HttpStandardResilienceOptions HttpStandardResilience { get; set; }

    [Required]
    required public bool EnableHttpStandardHedgingResilience { get; set; }

    [Required]
    [ValidateObjectMembers]
    required public HttpStandardHedgingResilienceOptions HttpStandardHedgingResilience { get; set; }

    [Required]
    required public bool EnableChaosInjection { get; set; }

    [Required]
    [ValidateObjectMembers]
    required public ChaosInjectionOptions ChaosInjection { get; set; }

    // Authentication options (conditional based on AuthenticationType)
}

Authentication Options

API Key Authentication

{
  "MyService": {
    "ApiKeyAuthentication": {
      "ApiKey": "your-api-key-here",
      "HeaderName": "X-API-Key"
    }
  }
}

Basic Authentication

{
  "MyService": {
    "BasicAuthentication": {
      "Username": "your-username",
      "Password": "your-password"
    }
  }
}

OAuth2 Authentication

{
  "MyService": {
    "OAuth2Authentication": {
      "ClientName": "MyService",
      "OAuthHttpHandler": {
        "OAuth2Options": {
          "TokenEndpoint": "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token",
          "DefaultTimeout": "00:00:10",
          "ClientId": "your-client-id",
          "ClientSecret": "your-client-secret",
          "GrantType": "ClientCredentials",
          "CredentialsTransportMethod": "FormRequestBody",
          "TokenRequestContentType": "FormUrlEncoded",
          "Scope": [ "https://api.example.com/.default" ],
          "SetGrantTypeOnQueryString": false,
          "CredentialsKeyNames": {
            "client_id": "client_id",
            "client_secret": "client_secret",
            "grant_type": "grant_type"
          },
          "AccessTokenResponseOptions": {
            "KeyNames": {
              "AccessToken": "access_token",
              "TokenType": "token_type",
              "ExpiresIn": "expires_in"
            }
          }
        },
        "HttpClientFactoryEnabled": true,
        "IgnoreRefreshTokens": false
      }
    }
  }
}

OpenID Connect Authentication

OpenID Connect extends OAuth2 with validation settings:

{
  "MyService": {
    "OAuth2Authentication": {
      /* OAuth2 configuration as above */,
      "OpenIdConnect": {
        "Authority": "https://login.microsoftonline.com/{tenant-id}/v2.0",
        "ValidIssuers": [
          "https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token"
        ],
        "ValidAudiences": [ "your-client-id" ],
        "ValidateLifetime": true,
        "ValidateIssuerSigningKey": true,
        "ClockSkew": "00:05:00",
        "AdditionalTrustedSigningKeys": [],
        "RequireHttpsMetadata": true
      }
    }
  }
}

Resiliency Options

Standard Resilience Options

{
  "MyService": {
    "EnableHttpStandardResilience": true,
    "HttpStandardResilience": {
      "TotalRequestTimeout": {
        "Timeout": "00:00:30"
      },
      "Retry": {
        "MaxRetryAttempts": 3,
        "BackoffType": "Constant",
        "UseJitter": false,
        "Delay": "00:00:02",
        "MaxDelay": null
      },
      "CircuitBreaker": {
        "FailureRatio": 0.1,
        "MinimumThroughput": 2,
        "SamplingDuration": "00:00:30",
        "BreakDuration": "00:00:05"
      },
      "AttemptTimeout": {
        "Timeout": "00:00:10"
      },
      "RateLimiter": {
        "DefaultRateLimiterOptions": {
          "PermitLimit": 1000,
          "QueueLimit": 0,
          "QueueProcessingOrder": "OldestFirst"
        }
      }
    }
  }
}

Standard Hedging Resilience Options

{
  "MyService": {
    "EnableHttpStandardHedgingResilience": true,
    "HttpStandardHedgingResilience": {
      "TotalRequestTimeout": {
        "Timeout": "00:00:30"
      },
      "Hedging": {
        "Delay": "00:00:02",
        "MaxHedgedAttempts": 1
      },
      "Endpoint": {
        "RateLimiter": {
          "DefaultRateLimiterOptions": {
            "PermitLimit": 1000,
            "QueueLimit": 0
          }
        },
        "CircuitBreaker": {
          "FailureRatio": 0.1,
          "MinimumThroughput": 2,
          "SamplingDuration": "00:00:30",
          "BreakDuration": "00:00:05"
        },
        "Timeout": {
          "Timeout": "00:00:10"
        }
      }
    }
  }
}

Chaos Injection Options

{
  "MyService": {
    "EnableChaosInjection": true,
    "ChaosInjection": {
      "InjectionRate": 0.001,
      "Latency": "00:00:05"
    }
  }
}

Options Details

  • InjectionRate: Rate of injection (0.0 to 1.0). Default: 0.001 (0.1%)
  • Latency: Latency to inject. Default: 30 seconds

Web Proxy Options

{
  "MyService": {
    "WebProxy": {
      "UseProxy": true,
      "ProxyAddress": "https://proxy.example.com:8080"
    }
  }
}

Options Details

  • UseProxy: Whether to use a web proxy. Default: false
  • ProxyAddress: Proxy server address (optional, required if UseProxy=true)

Complete Configuration Examples

Example 1: API Key with Standard Resilience

{
  "MyService": {
    "BaseUrl": "https://api.example.com",
    "DefaultTimeout": "00:00:30",
    "WebProxy": {
      "UseProxy": false
    },
    "EnableHttpStandardResilience": true,
    "HttpStandardResilience": {
      "TotalRequestTimeout": { "Timeout": "00:00:30" },
      "Retry": {
        "MaxRetryAttempts": 3,
        "BackoffType": "Constant",
        "Delay": "00:00:02"
      },
      "CircuitBreaker": {
        "FailureRatio": 0.1,
        "MinimumThroughput": 2,
        "SamplingDuration": "00:00:30",
        "BreakDuration": "00:00:05"
      },
      "AttemptTimeout": { "Timeout": "00:00:10" },
      "RateLimiter": {
        "DefaultRateLimiterOptions": {
          "PermitLimit": 1000,
          "QueueLimit": 0
        }
      }
    },
    "EnableHttpStandardHedgingResilience": false,
    "HttpStandardHedgingResilience": {
      /* Not used when disabled */
    },
    "EnableChaosInjection": false,
    "ChaosInjection": {
      "InjectionRate": 0.001,
      "Latency": "00:00:05"
    },
    "ApiKeyAuthentication": {
      "ApiKey": "your-api-key-here",
      "HeaderName": "X-API-Key"
    }
  }
}

Example 2: OAuth2 with Hedging and Chaos

{
  "MyService": {
    "BaseUrl": "https://api.example.com",
    "DefaultTimeout": "00:00:30",
    "WebProxy": {
      "UseProxy": false
    },
    "EnableHttpStandardResilience": false,
    "EnableHttpStandardHedgingResilience": true,
    "HttpStandardHedgingResilience": {
      "TotalRequestTimeout": { "Timeout": "00:00:30" },
      "Hedging": {
        "Delay": "00:00:02",
        "MaxHedgedAttempts": 1
      },
      "Endpoint": {
        "RateLimiter": {
          "DefaultRateLimiterOptions": {
            "PermitLimit": 100,
            "QueueLimit": 10
          }
        },
        "CircuitBreaker": {
          "FailureRatio": 0.1,
          "MinimumThroughput": 5,
          "SamplingDuration": "00:01:00",
          "BreakDuration": "00:00:30"
        },
        "Timeout": { "Timeout": "00:00:10" }
      }
    },
    "EnableChaosInjection": true,
    "ChaosInjection": {
      "InjectionRate": 0.1,
      "Latency": "00:00:03"
    },
    "OAuth2Authentication": {
      "ClientName": "MyService",
      "OAuthHttpHandler": {
        "OAuth2Options": {
          "TokenEndpoint": "https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token",
          "ClientId": "your-client-id",
          "ClientSecret": "your-client-secret",
          "GrantType": "ClientCredentials",
          "Scope": [ "https://api.example.com/.default" ]
        }
      }
    }
  }
}

Configuration Binding

Service Registration

// Register options
services.AddMyServiceOptions(configuration);

// Register metrics (if UseMetrics=true)
services.AddMyServiceMetrics();

// Register service
services.AddMyServiceService();

Options Validation

The template includes automatic validation:

  1. DataAnnotations Validation: Required fields, data types, ranges
  2. Source-Generated Validators: Compile-time validation generation
  3. Startup Validation: Configuration validated at application startup

Validation Example

public sealed class MyServiceOptions
{
    [Required]
    [DataType(DataType.Url)]
    required public string BaseUrl { get; set; }

    [Required]
    [Range(typeof(TimeSpan), "00:00:01", "00:05:00")]
    required public TimeSpan DefaultTimeout { get; set; }
}

If validation fails, the application will throw an OptionsValidationException at startup.

Environment-Specific Configuration

appsettings.json (Base)

{
  "MyService": {
    "BaseUrl": "https://api.production.com",
    "DefaultTimeout": "00:00:30"
  }
}

appsettings.Development.json

{
  "MyService": {
    "BaseUrl": "https://api.dev.example.com",
    "DefaultTimeout": "00:01:00",
    "EnableChaosInjection": true,
    "ChaosInjection": {
      "InjectionRate": 0.1
    }
  }
}

appsettings.Production.json

{
  "MyService": {
    "BaseUrl": "https://api.production.com",
    "DefaultTimeout": "00:00:30",
    "EnableChaosInjection": false,
    "EnableHttpStandardResilience": true
  }
}

Using Environment Variables

You can override configuration using environment variables:

# Windows
set MyService__BaseUrl=https://api.example.com
set MyService__ApiKeyAuthentication__ApiKey=your-key

# Linux/Mac
export MyService__BaseUrl=https://api.example.com
export MyService__ApiKeyAuthentication__ApiKey=your-key

Environment Variable Naming

  • Use double underscore (__) to represent nested properties
  • Prefix with section name (MyService__)
  • Case-insensitive on Windows, case-sensitive on Linux/Mac

Secure Configuration

Azure Key Vault

builder.Configuration.AddAzureKeyVault(
    new Uri("https://your-keyvault.vault.azure.net/"),
    new DefaultAzureCredential());

User Secrets (Development)

dotnet user-secrets set "MyService:ApiKeyAuthentication:ApiKey" "your-key"

Configuration in appsettings.json

{
  "MyService": {
    "ApiKeyAuthentication": {
      "ApiKey": ""  // Will be overridden by environment variable or Key Vault
    }
  }
}

Metrics Configuration

When UseMetrics=true, metrics are automatically configured. No additional configuration is required in appsettings.json. Metrics are registered via:

services.AddMyServiceMetrics();

OpenTelemetry Integration

To export metrics to OpenTelemetry:

builder.Services.AddOpenTelemetry()
    .WithMetrics(builder =>
    {
        builder.AddAspNetCoreInstrumentation()
               .AddRuntimeInstrumentation()
               .AddMeter("ConnectSoft.MyService")
               .AddPrometheusExporter();
    });

Best Practices

Configuration Organization

  1. Group Related Settings: Keep related settings together in nested objects
  2. Use Descriptive Names: Clear, descriptive property names
  3. Document Defaults: Document default values in code comments

Security

  1. Never Commit Secrets: Use environment variables, Key Vault, or User Secrets
  2. Validate Configuration: Always enable validation
  3. Use HTTPS: Always use HTTPS for API endpoints

Environment Management

  1. Separate Configurations: Use environment-specific configuration files
  2. Override Sensitive Values: Override sensitive values per environment
  3. Test Configurations: Test configurations in each environment

Troubleshooting

Configuration Not Loading

Problem: Options are null or default values.

Solutions:

  • Verify section name matches (MyService)
  • Check JSON syntax is valid
  • Ensure configuration is added to IConfiguration

Validation Failures

Problem: OptionsValidationException at startup.

Solutions:

  • Check required fields are provided
  • Verify data types match (e.g., TimeSpan format)
  • Check range constraints are met

Environment Variables Not Working

Problem: Environment variables not overriding configuration.

Solutions:

  • Verify naming convention (double underscore for nesting)
  • Check environment variable is set correctly
  • Ensure AddEnvironmentVariables() is called

Conclusion

The ConnectSoft API Library Template provides a comprehensive configuration system using the Options pattern. By following the examples and best practices in this guide, you can configure your API library effectively and securely.

For more information, see: