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¶
- Open Visual Studio 2022 (version 17.14 or later)
- File → Open → Project/Solution
- Select
YourApiLibrary.slnx(recommended) orYourApiLibrary.sln - Wait for solution to load and packages to restore
JetBrains Rider¶
- Open Rider
- File → Open
- Select
YourApiLibrary.slnxorYourApiLibrary.sln - 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):
-
Create Authentication Options Class:
-
Update Service Options:
-
Add Authentication Header:
-
Call in Service Registration:
Code Organization Best Practices¶
- One Class Per File: Each class in its own file with matching name.
- Namespace Organization: Group related classes in appropriate namespaces.
- Folder Structure: Use folders for logical grouping (Options, Metrics, etc.).
- Naming Conventions: Follow C# naming conventions and StyleCop rules.
- Documentation: Add XML documentation comments to public APIs.
Debugging¶
Debugging Service Methods¶
- Set Breakpoints: Set breakpoints in service methods.
- Run Tests in Debug Mode: Use your IDE's debug test feature.
- 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¶
- Check Configuration: Verify authentication options are correctly configured.
- Inspect Headers: Use logging or Fiddler/Postman to inspect request headers.
- Verify Credentials: Ensure API keys, tokens, or credentials are valid.
- Check Token Expiration: For OAuth2, verify tokens are being refreshed.
Debugging Resiliency Issues¶
- Enable Detailed Logging: Set logging level to
DebugorTrace. - Monitor Metrics: Check metrics for retry counts, circuit breaker state.
- Test with Chaos Injection: Enable chaos injection to test resiliency.
- Review Configuration: Verify resiliency configuration is correct.
Debugging Test Failures¶
- Check Test Configuration: Verify
appsettings.UnitTests.jsonis correct. - Mock Server Issues: Ensure WireMock server is started and configured.
- Assertion Failures: Review assertion messages for details.
- 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
HttpClientinstances directly.
Request Optimization¶
- Use SendAsync: Prefer
SendAsyncover verb-specific methods for flexibility. - Streaming: Use streaming for large responses to reduce memory usage.
- Cancellation Tokens: Always use cancellation tokens for async operations.
- ConfigureAwait: Use
ConfigureAwait(false)in library code.
Memory Management¶
- Dispose Resources: Properly dispose of
HttpResponseMessageand streams. - Avoid Large Buffers: Use streaming instead of buffering large responses.
- 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¶
- Create Feature Branch:
git checkout -b feature/my-feature - Make Changes: Implement feature with tests
- Run Tests Locally: Ensure all tests pass
- Check Code Coverage: Verify coverage meets threshold
- Create Pull Request: Submit PR with description
- Address Review Comments: Make requested changes
- Merge to Master: After approval, merge triggers CI/CD
Common Development Tasks¶
Adding a New Dependency¶
-
Add to Directory.Packages.props:
-
Reference in Project:
-
Restore Packages:
Updating Package Versions¶
-
Update in Directory.Packages.props:
-
Rebuild Solution:
-
Run Tests: Ensure compatibility with new version.
Configuring Static Analyzers¶
Static analyzers are configured in Directory.Build.props. To suppress warnings:
- Global Suppressions: Add to
GlobalSuppressions.cs - Local Suppressions: Use
#pragma warning disablefor specific cases - Analyzer Configuration: Configure in
.editorconfigorDirectory.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
.slnxformat for faster builds - Disable analyzers during development (not recommended)
- Use incremental builds
- Check for large test data sets
Best Practices¶
Code Quality¶
- Follow SOLID Principles: Write maintainable, testable code.
- Error Handling: Always handle exceptions appropriately.
- Logging: Use structured logging with appropriate log levels.
- Documentation: Document public APIs with XML comments.
Testing¶
- Test Coverage: Maintain high test coverage (>70%).
- Test Isolation: Each test should be independent.
- Mock External Dependencies: Use mocks for HTTP clients and external services.
- Test Edge Cases: Test error conditions, timeouts, and edge cases.
Configuration¶
- Environment-Specific: Use environment-specific configuration files.
- Secure Storage: Never commit secrets to source control.
- Validation: Always validate configuration at startup.
- 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:
- Overview - Template overview and quick start
- Architecture - Design principles and patterns
- Testing Guide - Testing strategies and mock server
- Runbook - CI/CD operations