DEV Community

Seigo Kitamura
Seigo Kitamura

Posted on

Part 3 - Production-Ready C#: Resource Management, Testing & Telemetry, Security & Reliability

Excerpt

This post focuses on the practices that keep C# services healthy in production: proper disposal (IDisposable/IAsyncDisposable), timeouts and cancellation, tests/benchmarks/diagnostics, structured logging with tracing, and security/reliability measures (validation, injection prevention, idempotency).


Production-Ready Practices


Resource Management: IDisposable, IAsyncDisposable, Timeouts/Cancellation

Why it matters:
Proper resource management prevents leaks, improves reliability, and ensures predictable cleanup.

Scope-based disposal (diagram):

Acquire resource --> Using scope --> Work with resource --> Scope ends --> Dispose deterministically
Enter fullscreen mode Exit fullscreen mode

Deterministic disposal

using var stream = File.OpenRead(path); // dispose at scope end
await using var connection = new SomeAsyncDisposable(); // IAsyncDisposable for async cleanup
Enter fullscreen mode Exit fullscreen mode

Timeouts + cancellation

public async Task<string> FetchAsync(HttpClient http, string url, CancellationToken ct) {
    using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
    cts.CancelAfter(TimeSpan.FromSeconds(3));
    return await http.GetStringAsync(url, cts.Token).ConfigureAwait(false);
}
Enter fullscreen mode Exit fullscreen mode

Guidelines

  • Do not rely on finalizers; they’re non-deterministic.
  • Dispose streams, DB connections, sockets promptly.
  • Propagate CancellationToken from controller → service → repository.

Testing, Diagnostics (BenchmarkDotNet, Analyzers), Logging, OpenTelemetry

Why it matters:
Testing and diagnostics ensure correctness and performance. Logging and tracing provide observability and help diagnose issues in production.

Unit & integration tests

// xUnit unit test
public class MathTests {
    [Fact]
    public void Sum_Works() => Assert.Equal(7, 3 + 4);
}

// ASP.NET Core integration test with WebApplicationFactory
public class ApiTests : IClassFixture<WebApplicationFactory<Program>> {
    private readonly HttpClient _client;
    public ApiTests(WebApplicationFactory<Program> factory) => _client = factory.CreateClient();

    [Fact]
    public async Task GetUsers_ReturnsOk() {
        var res = await _client.GetAsync("/api/users");
        Assert.True(res.IsSuccessStatusCode);
    }
}
Enter fullscreen mode Exit fullscreen mode

Benchmarking (evidence-based decisions)

[MemoryDiagnoser]
public class ConcatBench {
    [Benchmark]
    public string PlusConcat() {
        var s = "";
        for (int i = 0; i < 1000; i++) s += i;
        return s;
    }

    [Benchmark]
    public string StringBuilderConcat() {
        var sb = new StringBuilder();
        for (int i = 0; i < 1000; i++) sb.Append(i);
        return sb.ToString();
    }
}
Enter fullscreen mode Exit fullscreen mode

Static analysis & null-safety

  • Enable nullable reference types: #nullable enable
  • Use Roslyn analyzers, StyleCop/FxCop rules to catch maintainability issues.

Structured logging & correlation

public class UsersController : ControllerBase {
    private readonly ILogger<UsersController> _logger;
    public UsersController(ILogger<UsersController> logger) => _logger = logger;

    [HttpGet]
    public IActionResult Get() {
        using (_logger.BeginScope(new Dictionary<string, object> { ["CorrelationId"] = Guid.NewGuid() })) {
            _logger.LogInformation("Fetching users");
            return Ok(new[] { "alice", "bob" });
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Tracing with OpenTelemetry (high-level)

// Program.cs
builder.Services.AddOpenTelemetry()
    .WithTracing(t => t
        .AddAspNetCoreInstrumentation()
        .AddHttpClientInstrumentation()
        .AddSource("MyCompany.MyService"))
    .StartWithHost();

// Use ActivitySource in code
var activitySource = new ActivitySource("MyCompany.MyService");
using var activity = activitySource.StartActivity("ProcessOrder");
// add tags/events...
Enter fullscreen mode Exit fullscreen mode

Observability pipeline (diagram):

App code --> ILogger<T> --> Log sink --> Central log store
        \-> ActivitySource -> OTel exporter -> Tracing backend
Enter fullscreen mode Exit fullscreen mode

Security & Reliability (Validation, Injection Prevention, Idempotency)

Why it matters:
Security and reliability are critical for protecting data, preventing attacks, and ensuring robust service behavior.

Validation

public class CreateUserRequest {
    [Required, EmailAddress] public string Email { get; set; } = default!;
    [Required, MinLength(8)] public string Password { get; set; } = default!;
}
Enter fullscreen mode Exit fullscreen mode

Prevent injection

  • SQL parameterization (ORMs handle this; use FromSqlInterpolated carefully).
  • Encode/escape output in HTML contexts.

Idempotency & retries

  • Make external calls idempotent; use idempotency keys or PUT semantics where possible.
  • Use timeouts and circuit-breakers for resiliency; apply retries where safe.

Timeout + retry sketch

public async Task<string> SafeFetchAsync(HttpClient http, string url, CancellationToken ct) {
    using var cts = CancellationTokenSource.CreateLinkedTokenSource(ct);
    cts.CancelAfter(TimeSpan.FromSeconds(2));
    for (int attempt = 0; attempt < 3; attempt++) {
        try {
            return await http.GetStringAsync(url, cts.Token);
        } catch (TaskCanceledException) when (!ct.IsCancellationRequested) {
            // timeout -> retry with backoff
            await Task.Delay(TimeSpan.FromMilliseconds(100 * (attempt + 1)), ct);
        }
    }
    throw new TimeoutException("Exceeded retry attempts");
}
Enter fullscreen mode Exit fullscreen mode

CTA

This wraps up the series. Consider packaging these posts with a downloadable PDF as a single reference for your team or future reference. Share your production lessons learned—what’s your favorite reliability design pattern?


Series Navigation

Top comments (0)