C# Language Interview Q&A

C# Language Interview Q&A

Explore c# language interview q&a code snippets and tutorials

C# Language Interview Q&A

ASP.NET Core background work: BackgroundService, PeriodicTimer and scoped DbContext

A BackgroundService is a singleton, so scoped services (DbContext) must be resolved only from a new scope.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// ======================================================================
// Q: How do you run background work in ASP.NET Core?
//
// A BackgroundService is a singleton, so scoped services (DbContext) must be
// resolved only from a new scope. For heavy or critical jobs: a separate worker
// / Azure Function / Hangfire, plus a message queue for reliability.
// ======================================================================

public class PriceSyncWorker(IServiceScopeFactory scopes, ILogger<PriceSyncWorker> log)
    : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
        while (await timer.WaitForNextTickAsync(stoppingToken))
        {
            try
            {
                await using var scope = scopes.CreateAsyncScope();   // new DbContext every time
                var sync = scope.ServiceProvider.GetRequiredService<PriceSync>();
                await sync.RunAsync(stoppingToken);
            }
            catch (Exception ex) when (ex is not OperationCanceledException)
            {
                log.LogError(ex, "Price sync failed");    // the worker keeps running
            }
        }
    }
}
builder.Services.AddHostedService<PriceSyncWorker>();
C# Language Interview Q&A

.NET structured logging with ILogger and source-generated LoggerMessage

ILogger<T> via DI, log levels per namespace in configuration, and never passwords, tokens or personal data in logs.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// ======================================================================
// Q: How do you log properly in .NET?
//
// ILogger<T> via DI, log levels per namespace in configuration, and never
// passwords, tokens or personal data in logs. Export to Application Insights /
// OpenTelemetry (Q33).
// ======================================================================

// ✓ structured logging: OrderId / CustomerId become searchable fields
logger.LogInformation("Order {OrderId} placed by {CustomerId}", order.Id, customerId);

// ✗ interpolation: the fields are lost and the string is built even if the level is off
logger.LogInformation($"Order {order.Id} placed by {customerId}");

// For hot paths: source-generated logging, without allocations
[LoggerMessage(Level = LogLevel.Warning, Message = "ERP rejected {OrderNumber}: {Reason}")]
static partial void LogErpRejected(ILogger logger, string orderNumber, string reason);
C# Language Interview Q&A

Why use IHttpClientFactory instead of new HttpClient() in .NET

new HttpClient() on every call: socket exhaustion (sockets stay in TIME_WAIT).

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// ======================================================================
// Q: Why IHttpClientFactory instead of new HttpClient()?
//
// - new HttpClient() on every call: socket exhaustion (sockets stay in
//   TIME_WAIT).
// - One static HttpClient forever: it does not see DNS changes.
// - IHttpClientFactory / typed clients: handler pooling and recycling, central
//   configuration (base address, headers), resilience (Q11), logging.
// ======================================================================

builder.Services.AddHttpClient<IErpClient, ErpClient>(c => c.BaseAddress = new(erpUrl))
                .AddStandardResilienceHandler();
C# Language Interview Q&A

ASP.NET Core configuration: IOptions vs IOptionsSnapshot vs IOptionsMonitor

Sources in order (the last one wins): appsettings.json → appsettings.{Environment}.json → User Secrets (Development only) → environment variables → command line.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// ======================================================================
// Q: How does configuration work? IOptions vs IOptionsSnapshot vs
// IOptionsMonitor?
//
// Sources in order (the last one wins): appsettings.json →
// appsettings.{Environment}.json → User Secrets (Development only) → environment
// variables → command line. On Azure: Key Vault / App Configuration.
// - IOptions<T>: Singleton lifetime. No runtime changes: read once.
// - IOptionsSnapshot<T>: Scoped lifetime. Picks up changes per request. Not
//   usable in singletons.
// - IOptionsMonitor<T>: Singleton lifetime. Picks up changes, via CurrentValue
//   and OnChange.
// ======================================================================

builder.Services.AddOptions<ErpOptions>()
    .Bind(builder.Configuration.GetSection("Erp"))
    .ValidateDataAnnotations()
    .ValidateOnStart();      // bad config = the app does not start, instead of blowing up later
C# Language Interview Q&A

ASP.NET Core middleware vs MVC filters vs endpoint filters

Middleware: runs for every request, generic (logging, exception handling, headers).

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
// ======================================================================
// Q: Middleware vs filters?
//
// - Middleware: runs for every request, generic (logging, exception handling,
//   headers). It does not know which action will run.
// - Filters (MVC): inside the MVC pipeline, with access to action arguments,
//   ModelState and the result. Order: Authorization → Resource → Action →
//   Exception → Result.
// - Endpoint filters: the equivalent for Minimal APIs.
// ======================================================================

public class TimingMiddleware(RequestDelegate next, ILogger<TimingMiddleware> log)
{
    public async Task InvokeAsync(HttpContext ctx)
    {
        var sw = Stopwatch.StartNew();
        await next(ctx);                       // next in the chain
        log.LogInformation("{Path} took {Ms} ms", ctx.Request.Path, sw.ElapsedMilliseconds);
    }
}