ASP.NET Core Interview Q&A

ASP.NET Core Interview Q&A

Explore asp.net core interview q&a code snippets and tutorials

ASP.NET Core Interview Q&A

ASP.NET Core DI lifetimes and captive dependencies: scopes in a C# BackgroundService

Transient: a new instance every time.

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: Explain DI lifetimes. What's a captive dependency?
//
// - Transient: a new instance every time.
// - Scoped: one instance per request. This is how DbContext is registered.
// - Singleton: one instance for the whole application.
// A captive dependency is when a singleton holds a scoped service. A DbContext
// then gets shared across threads, even though it is not thread-safe. In
// background services you create your own scope.
// ======================================================================

// BackgroundService that creates its own scope per run
public class PriceSyncWorker(IServiceScopeFactory scopes, ILogger<PriceSyncWorker> log)
    : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        using var timer = new PeriodicTimer(TimeSpan.FromMinutes(15));
        while (await timer.WaitForNextTickAsync(ct))
        {
            await using var scope = scopes.CreateAsyncScope();
            var sync = scope.ServiceProvider.GetRequiredService<IPriceSyncService>();
            try { await sync.RunAsync(ct); }
            catch (Exception ex) when (ex is not OperationCanceledException)
            {
                log.LogError(ex, "Price sync failed"); // the worker must not die
            }
        }
    }
}
ASP.NET Core Interview Q&A

C# async pitfalls in ASP.NET Core: .Result sync-over-async and async void

.Result / .Wait() = sync-over-async: it blocks a thread pool thread and under load causes thread pool starvation.

csharp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
// ======================================================================
// Q: What's wrong here?
//
// - .Result / .Wait() = sync-over-async: it blocks a thread pool thread and
//   under load causes thread pool starvation. In legacy ASP.NET or a UI context
//   it can also deadlock.
// - async void: exceptions cannot be caught and can crash the whole process. Use
//   it only for event handlers; this should be async Task.
// - The CancellationToken is missing.
// ======================================================================

public IActionResult Sync()
{
    var result = _erpClient.GetPricesAsync().Result;
    return Ok(result);
}

public async void OnOrderCreated(Order o) => await _bus.PublishAsync(o);
ASP.NET Core Interview Q&A

EF Core race condition: fixing check-then-act stock reservation (atomic update, RowVersion)

It has a check-then-act race condition.

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
31
32
33
34
35
36
37
38
39
// ======================================================================
// Q: Spot the bug (also written by AI)
//
// It has a check-then-act race condition. Two concurrent orders both read
// Available = 5 and each reserves 5 units.
// Two fixes: an atomic update with ExecuteUpdateAsync (the best option here), or
// optimistic concurrency with a row version (catch DbUpdateConcurrencyException
// and retry or return false).
// ======================================================================

// AI-generated code with the bug
public async Task<bool> ReserveStock(string sku, int qty)
{
    var item = await _db.Stock.FirstAsync(s => s.Sku == sku);
    if (item.Available >= qty)
    {
        item.Available -= qty;
        await _db.SaveChangesAsync();
        return true;
    }
    return false;
}

// Fix 1: atomic update (best option here)
var affected = await _db.Stock
    .Where(s => s.Sku == sku && s.Available >= qty)
    .ExecuteUpdateAsync(s => s.SetProperty(x => x.Available, x => x.Available - qty), ct);
return affected == 1;

// Fix 2: optimistic concurrency
public class StockItem
{
    public string Sku { get; set; } = default!;
    public int Available { get; set; }
    [Timestamp] public byte[] RowVersion { get; set; } = default!; // SQL Server
    // PostgreSQL: modelBuilder.Entity<StockItem>().Property<uint>("xmin").IsRowVersion();
}
// ...
catch (DbUpdateConcurrencyException) { /* retry or return false */ }
ASP.NET Core Interview Q&A

ASP.NET Core code review: fixing an AI-generated GetOrder endpoint (IDOR, DTOs, 404)

No.

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// ======================================================================
// Q: Claude Code generated this endpoint. Would you merge it?
//
// No. The problems:
// - IDOR / BOLA: there is no [Authorize] and nothing checks that the order
//   belongs to the user's tenant or customer. Change the id and you see other
//   companies' orders, which is critical in B2B.
// - Data exposure: it returns the EF entity directly. The whole Customer comes
//   along (e.g. billing details, credit limit) and there is a risk of circular
//   references.
// - Null handling: it returns 200 with null instead of 404.
// - CancellationToken and AsNoTracking() are missing.
// Return 404 rather than 403, so an attacker does not learn that the id exists.
// ======================================================================

// AI-generated endpoint under review
[HttpGet("api/orders/{id}")]
public async Task<IActionResult> GetOrder(int id)
{
    var order = await _db.Orders
        .Include(o => o.Customer)
        .Include(o => o.Lines)
        .FirstOrDefaultAsync(o => o.Id == id);

    return Ok(order);
}

// Fixed version: [Authorize], ownership filter, DTO projection, 404
[Authorize]
[HttpGet("api/orders/{id:int}")]
public async Task<ActionResult<OrderDto>> GetOrder(int id, CancellationToken ct)
{
    var customerId = User.GetCustomerId(); // from claims, NEVER from the request

    var order = await _db.Orders
        .AsNoTracking()
        .Where(o => o.Id == id && o.CustomerId == customerId)
        .Select(o => new OrderDto(
            o.Id, o.OrderNumber, o.Status, o.TotalNet,
            o.Lines.Select(l => new OrderLineDto(l.Sku, l.Quantity, l.UnitPrice)).ToList()))
        .FirstOrDefaultAsync(ct);

    return order is null ? NotFound() : Ok(order);
}