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
- Testing, Diagnostics (BenchmarkDotNet, Analyzers), Logging, OpenTelemetry
- Security & Reliability (Validation, Injection Prevention, Idempotency)
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
Deterministic disposal
using var stream = File.OpenRead(path); // dispose at scope end
await using var connection = new SomeAsyncDisposable(); // IAsyncDisposable for async cleanup
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);
}
Guidelines
- Do not rely on finalizers; they’re non-deterministic.
- Dispose streams, DB connections, sockets promptly.
- Propagate
CancellationTokenfrom 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);
}
}
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();
}
}
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" });
}
}
}
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...
Observability pipeline (diagram):
App code --> ILogger<T> --> Log sink --> Central log store
\-> ActivitySource -> OTel exporter -> Tracing backend
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!;
}
Prevent injection
- SQL parameterization (ORMs handle this; use
FromSqlInterpolatedcarefully). - 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");
}
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)