Skip to content

Development Guide: ConnectSoft API Library Template

This guide provides comprehensive information for developers working with projects generated by the ConnectSoft API Library Template. It covers local development setup, building and testing, code organization, adding new API methods, and debugging tips.

Local Development Setup

Prerequisites

Ensure you have the following installed:

  • .NET SDK 8.0 (LTS) - Download
  • .NET SDK 9.0 (Preview, for multi-targeting) - Download
  • Visual Studio 2022 (v17.14+ for .slnx support) or JetBrains Rider
  • Git for version control

Opening the Solution

Visual Studio 2022

  1. Open Visual Studio 2022 (version 17.14 or later)
  2. File → Open → Project/Solution
  3. Select YourApiLibrary.slnx (recommended) or YourApiLibrary.sln
  4. Wait for solution to load and packages to restore

JetBrains Rider

  1. Open Rider
  2. File → Open
  3. Select YourApiLibrary.slnx or YourApiLibrary.sln
  4. Rider will automatically restore packages

Command Line

# Navigate to solution directory
cd YourApiLibrary

# Restore packages
dotnet restore YourApiLibrary.slnx

# Open in default editor (if configured)
code .  # VS Code
# or
start YourApiLibrary.slnx  # Visual Studio (Windows)

Building and Testing

Building the Solution

# Build in Debug configuration
dotnet build YourApiLibrary.slnx

# Build in Release configuration
dotnet build YourApiLibrary.slnx --configuration Release

# Build specific project
dotnet build src/YourApiLibrary/YourApiLibrary.csproj

Running Tests

# Run all tests
dotnet test YourApiLibrary.slnx

# Run tests with code coverage
dotnet test YourApiLibrary.slnx \
  --collect:"XPlat Code Coverage" \
  --settings YourApiLibrary.runsettings

# Run specific test class
dotnet test YourApiLibrary.slnx \
  --filter "FullyQualifiedName~YourServiceUnitTests"

# Run tests in debug mode
dotnet test YourApiLibrary.slnx --logger "console;verbosity=detailed"

Running Mock Server Tests

Mock server tests use WireMock.Net to simulate API responses:

# Run mock server tests
dotnet test YourApiLibrary.slnx \
  --filter "FullyQualifiedName~MockedServer"

Code Organization

Project Structure

The generated project follows a clear structure:

src/YourApiLibrary/
├── I{ServiceName}Service.cs          # Service interface
├── {ServiceName}Service.cs            # Service implementation
├── {ServiceName}ServiceException.cs   # Custom exceptions
├── {ServiceName}Response.cs           # Response models
├── {ServiceName}Result.cs             # Result models
├── {ServiceName}Error.cs              # Error models
├── {ServiceName}ServiceExtensions.cs  # DI extensions
├── Options/                           # Configuration options
├── Metrics/                          # Metrics (if UseMetrics=true)
├── Handlers/                         # Custom HTTP handlers (empty)
└── Resilience/                       # Custom resilience policies (empty)

Adding New API Methods

Step 1: Update the Interface

Add the method signature to the service interface:

public interface IMyService
{
    Task<MyServiceResult> GetDataAsync(CancellationToken cancellationToken = default);

    // Add new method
    Task<MyServiceResult> CreateItemAsync(CreateItemRequest request, CancellationToken cancellationToken = default);
}

Step 2: Implement the Method

Implement the method in the service class:

public async Task<MyServiceResult> CreateItemAsync(CreateItemRequest request, CancellationToken cancellationToken = default)
{
    MyServiceResponse? response = null;
    HttpResponseMessage httpResponse = null;

    try
    {
        var httpRequest = new HttpRequestMessage(HttpMethod.Post, "/api/items")
        {
            Content = JsonContent.Create(request)
        };

#if UseMetrics
        _metrics.IncrementRequestCounter();
        var stopwatch = Stopwatch.StartNew();
#endif

        httpResponse = await _httpClient
            .SendAsync(httpRequest, cancellationToken)
            .ConfigureAwait(false);

#if UseMetrics
        stopwatch.Stop();
        _metrics.RecordRequestProcessingTime(stopwatch.ElapsedMilliseconds);
#endif

        httpResponse.EnsureSuccessStatusCode();

        var responseStream = await httpResponse.Content
            .ReadAsStreamAsync(cancellationToken)
            .ConfigureAwait(false);

        if (responseStream != null)
        {
            response = await JsonSerializer
                .DeserializeAsync<MyServiceResponse>(
                    utf8Json: responseStream,
                    options: null,
                    cancellationToken: cancellationToken)
                .ConfigureAwait(false);
        }
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Error creating item");

#if UseMetrics
        _metrics.IncrementFailure();
        if (httpResponse != null)
        {
            _metrics.IncrementFailureByStatusCode((int)httpResponse.StatusCode);
        }
#endif

        throw new MyServiceException("Error creating item", ex);
    }

    return new MyServiceResult
    {
        // Map response to result
    };
}

Step 3: Create Request/Response Models

Create request and response models:

// CreateItemRequest.cs
public sealed class CreateItemRequest
{
    [JsonPropertyName("name")]
    required public string Name { get; set; }

    [JsonPropertyName("description")]
    public string? Description { get; set; }
}

// Update MyServiceResponse.cs if needed

Step 4: Add Unit Tests

Add tests for the new method:

[TestMethod]
public async Task CreateItemAsync_WithValidRequest_ReturnsSuccess()
{
    // Arrange
    var request = new CreateItemRequest { Name = "Test Item" };

    // Act
    var result = await _service!.CreateItemAsync(request);

    // Assert
    Assert.IsNotNull(result);
    // Additional assertions
}

Adding New Authentication Types

To add a new authentication type (beyond the supported ones):

  1. Create Authentication Options Class:

    // Options/CustomAuthenticationOptions.cs
    public sealed class CustomAuthenticationOptions
    {
        [Required]
        required public string Token { get; set; }
    }
    

  2. Update Service Options:

    // Add to MyServiceOptions.cs
    public CustomAuthenticationOptions? CustomAuthentication { get; set; }
    

  3. Add Authentication Header:

    // In MyServiceExtensions.cs
    private static void AddCustomAuthenticationHeader(HttpClient client)
    {
        if (options.CustomAuthentication != null)
        {
            client.DefaultRequestHeaders.Add(
                "X-Custom-Token",
                options.CustomAuthentication.Token);
        }
    }
    

  4. Call in Service Registration:

    configureClient: (provider, client) =>
    {
        // ... other configuration
        AddCustomAuthenticationHeader(client);
    }
    

Code Organization Best Practices

  1. One Class Per File: Each class in its own file with matching name.
  2. Namespace Organization: Group related classes in appropriate namespaces.
  3. Folder Structure: Use folders for logical grouping (Options, Metrics, etc.).
  4. Naming Conventions: Follow C# naming conventions and StyleCop rules.
  5. Documentation: Add XML documentation comments to public APIs.

Debugging

Debugging Service Methods

  1. Set Breakpoints: Set breakpoints in service methods.
  2. Run Tests in Debug Mode: Use your IDE's debug test feature.
  3. Attach to Process: Attach debugger to a running application using your library.

Debugging HTTP Requests

Enable HTTP Logging

services.AddLogging(builder =>
{
    builder.AddConsole();
    builder.SetMinimumLevel(LogLevel.Debug);
    builder.AddFilter("System.Net.Http.HttpClient", LogLevel.Trace);
});

Inspect HTTP Messages

Use a custom HTTP message handler to inspect requests/responses:

public class LoggingHandler : DelegatingHandler
{
    private readonly ILogger<LoggingHandler> _logger;

    public LoggingHandler(ILogger<LoggingHandler> logger)
    {
        _logger = logger;
    }

    protected override async Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        _logger.LogInformation("Request: {Method} {Uri}", 
            request.Method, request.RequestUri);

        var response = await base.SendAsync(request, cancellationToken);

        _logger.LogInformation("Response: {StatusCode}", response.StatusCode);

        return response;
    }
}

Register the handler:

services.AddHttpClient<IMyService, MyService>()
    .AddHttpMessageHandler<LoggingHandler>()
    // ... other configuration

Debugging Authentication Issues

  1. Check Configuration: Verify authentication options are correctly configured.
  2. Inspect Headers: Use logging or Fiddler/Postman to inspect request headers.
  3. Verify Credentials: Ensure API keys, tokens, or credentials are valid.
  4. Check Token Expiration: For OAuth2, verify tokens are being refreshed.

Debugging Resiliency Issues

  1. Enable Detailed Logging: Set logging level to Debug or Trace.
  2. Monitor Metrics: Check metrics for retry counts, circuit breaker state.
  3. Test with Chaos Injection: Enable chaos injection to test resiliency.
  4. Review Configuration: Verify resiliency configuration is correct.

Debugging Test Failures

  1. Check Test Configuration: Verify appsettings.UnitTests.json is correct.
  2. Mock Server Issues: Ensure WireMock server is started and configured.
  3. Assertion Failures: Review assertion messages for details.
  4. Exception Details: Check exception stack traces in test output.

Performance Considerations

Connection Pooling

The template uses HttpClientFactory which provides connection pooling:

  • Automatic: Connection pooling is handled automatically.
  • Configuration: Connection lifetime can be configured (default: 2 minutes).
  • Best Practice: Don't create HttpClient instances directly.

Request Optimization

  1. Use SendAsync: Prefer SendAsync over verb-specific methods for flexibility.
  2. Streaming: Use streaming for large responses to reduce memory usage.
  3. Cancellation Tokens: Always use cancellation tokens for async operations.
  4. ConfigureAwait: Use ConfigureAwait(false) in library code.

Memory Management

  1. Dispose Resources: Properly dispose of HttpResponseMessage and streams.
  2. Avoid Large Buffers: Use streaming instead of buffering large responses.
  3. Object Pooling: Consider object pooling for high-frequency scenarios.

Contributing Guidelines

Code Style

  • Follow StyleCop rules (enforced by analyzers).
  • Use meaningful variable and method names.
  • Add XML documentation comments to public APIs.
  • Keep methods focused and single-purpose.

Testing Requirements

  • Write unit tests for all new methods.
  • Add mock server tests for integration scenarios.
  • Maintain or improve code coverage.
  • Test error cases and edge conditions.

Pull Request Process

  1. Create Feature Branch: git checkout -b feature/my-feature
  2. Make Changes: Implement feature with tests
  3. Run Tests Locally: Ensure all tests pass
  4. Check Code Coverage: Verify coverage meets threshold
  5. Create Pull Request: Submit PR with description
  6. Address Review Comments: Make requested changes
  7. Merge to Master: After approval, merge triggers CI/CD

Common Development Tasks

Adding a New Dependency

  1. Add to Directory.Packages.props:

    <PackageVersion Include="Your.Package" Version="1.0.0" />
    

  2. Reference in Project:

    <PackageReference Include="Your.Package" />
    

  3. Restore Packages:

    dotnet restore YourApiLibrary.slnx
    

Updating Package Versions

  1. Update in Directory.Packages.props:

    <PackageVersion Include="Your.Package" Version="2.0.0" />
    

  2. Rebuild Solution:

    dotnet build YourApiLibrary.slnx
    

  3. Run Tests: Ensure compatibility with new version.

Configuring Static Analyzers

Static analyzers are configured in Directory.Build.props. To suppress warnings:

  1. Global Suppressions: Add to GlobalSuppressions.cs
  2. Local Suppressions: Use #pragma warning disable for specific cases
  3. Analyzer Configuration: Configure in .editorconfig or Directory.Build.props

Troubleshooting Development Issues

Build Failures

Problem: Solution fails to build.

Solutions:

  • Check for missing package references
  • Verify .NET SDK versions are installed
  • Review build errors for specific issues
  • Clean and rebuild: dotnet clean && dotnet build

Test Failures

Problem: Tests fail locally but pass in CI.

Solutions:

  • Check test configuration files
  • Verify mock server setup
  • Check for environment-specific issues
  • Review test logs for details

IntelliSense Not Working

Problem: IntelliSense doesn't show suggestions.

Solutions:

  • Restore packages: dotnet restore
  • Rebuild solution
  • Restart IDE
  • Check for project reference issues

Performance Issues

Problem: Slow builds or tests.

Solutions:

  • Use .slnx format for faster builds
  • Disable analyzers during development (not recommended)
  • Use incremental builds
  • Check for large test data sets

Best Practices

Code Quality

  1. Follow SOLID Principles: Write maintainable, testable code.
  2. Error Handling: Always handle exceptions appropriately.
  3. Logging: Use structured logging with appropriate log levels.
  4. Documentation: Document public APIs with XML comments.

Testing

  1. Test Coverage: Maintain high test coverage (>70%).
  2. Test Isolation: Each test should be independent.
  3. Mock External Dependencies: Use mocks for HTTP clients and external services.
  4. Test Edge Cases: Test error conditions, timeouts, and edge cases.

Configuration

  1. Environment-Specific: Use environment-specific configuration files.
  2. Secure Storage: Never commit secrets to source control.
  3. Validation: Always validate configuration at startup.
  4. Documentation: Document configuration options.

Additional Resources

Conclusion

This development guide provides the essential information needed to work effectively with projects generated by the ConnectSoft API Library Template. By following these guidelines and best practices, you can build robust, maintainable, and testable API client libraries.

For more information, see: