Interview Handbook by Sayed Elmahdy العربية

Topic 07 · Web & APIs

ASP.NET Core & MVC

MVC, Kestrel, middleware, routing, model binding, filters, DI, configuration, CORS.

24 questionsJunior · 8Mid · 10Senior · 612 quiz

Concepts

What you will learn: Follow a request through from arrival to response, locating routing, , dependency injection and configuration. The goal is to choose an application model and place each responsibility appropriately, while seeing how step order affects a real request.

Before you start: is how a client sends a request and a server returns a response. An endpoint is the address and handler that responds. No prior framework experience is required, but reviewing C# methods and classes in topic 02 will make the examples easier.

Select a term with a dotted underline for its meaning, then continue reading from the same place.

#MVC and choosing an application model

A controller that queries , calculates discounts and constructs is hard to change and test. separates coordination, business behavior and rendering.

In a store, the controller receives a request and coordinates its handling; the model represents sales data and behavior; the view presents the result. A model is not synonymous with a database table, and controllers should not hold every business rule.

StyleOutput and use
MVCControllers and Razor views for HTML pages
Web controllersControllerBase for data, commonly
Razor PagesA page with handlers for page-oriented workflows
Minimal APIsRoute handlers and endpoint groups without controllers; suitable for feature-organized APIs
BlazorC# components; render modes determine execution and interactivity
// IProductService and ProductViewModel are application types.
public sealed class ProductsController(IProductService products) : Controller
{
    public async Task<IActionResult> Details(int id, CancellationToken ct)
    {
        var product = await products.GetAsync(id, ct);       // (1)
        if (product is null) return NotFound();
        return View(product);                              // (2)
    }
}
🧾 Code walkthrough
  1. (1) The action coordinates a service call and cancellation; the service owns retrieval details.
  2. (2) MVC convention selects Views/Products/Details.cshtml. An API returns a instead of a view.

Choose according to team needs, UI and conventions. Minimal APIs need not live entirely in Program.cs, and MVC is not a complete application architecture.

#Kestrel, IIS and reverse proxies

The application needs an server; public hosting also requires , routing and limit decisions. Kestrel is a cross-platform server that can run at the edge or behind a proxy such as Nginx or .

Client -> optional CDN/WAF -> reverse proxy -> Kestrel -> pipeline

IIS in-process:     IIS -> ASP.NET Core Module -> IIS HTTP Server -> app
IIS out-of-process: IIS -> ASP.NET Core Module -> Kestrel -> app
using Microsoft.AspNetCore.HttpOverrides;
using System.Net;

// Before Build; replace this address with the trusted proxy's address.
builder.Services.Configure<ForwardedHeadersOptions>(options =>
{
    options.ForwardedHeaders = ForwardedHeaders.XForwardedFor
        | ForwardedHeaders.XForwardedProto;                // (1)
    options.KnownProxies.Add(IPAddress.Parse("10.0.0.10")); // (2)
});
// Early in the pipeline, before HTTPS redirection:
app.UseForwardedHeaders();                                 // (3)
🧾 Code walkthrough
  1. (1) Reads original client and scheme from trusted forwarded headers.
  2. (2) Trusts a specific proxy; replace the example address and configure proxy chains to match the network.
  3. (3) Corrects request information before redirects and authentication. Do not trust arbitrary client-supplied forwarding headers.

in-process hosting does not use Kestrel to receive requests. A helps centralize TLS and load balancing but is not mandatory for a correctly configured Kestrel deployment. Align body limits and timeouts across layers.

#Pipeline order, custom middleware, Use, Run and Map

A worked example
  1. A request passes through start logging, authentication, then an orders endpoint. can perform work before and after the next step.
  2. If an authentication step rejects and completes the response, the endpoint is not executed. A logger timing before and after next measures the downstream pipeline.
  3. Ordering therefore changes behavior: a step requiring an established identity must run after identity establishment. the request and identify every possible early exit.

Middleware can run before and after next or short-circuit the pipeline. Ordering creates nesting: responses unwind in reverse order, and an exception handler can catch downstream failures only.

Forwarded headers -> exception handler -> HSTS/HTTPS -> static files
  -> routing -> CORS -> authentication -> rate limiter -> authorization
  -> endpoint -> response unwinds through executed middleware

Use: may call next; Run(RequestDelegate): terminal handler
Map("/path", branch): branch pipeline
MapGet / MapControllers: register endpoints, not the same branching API
public sealed class RequestLogMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(HttpContext context,
        ILogger<RequestLogMiddleware> log)
    {
        var started = Stopwatch.GetTimestamp();             // (1)
        try { await next(context); }                        // (2)
        finally
        {
            double ms = Stopwatch.GetElapsedTime(started).TotalMilliseconds;
            log.LogInformation("Request {TraceId} ended after {ElapsedMs} ms",
                context.TraceIdentifier, ms);               // (3)
        }
    }
}

// Required CORS, authentication, authorization and limiter services are registered.
app.UseExceptionHandler();
app.UseMiddleware<RequestLogMiddleware>();
if (!app.Environment.IsDevelopment()) app.UseHsts();
app.UseHttpsRedirection();
app.UseRouting();
app.UseCors("storefront");
app.UseAuthentication();
app.UseRateLimiter();                                       // (4)
app.UseAuthorization();
app.MapControllers();
🧾 Code walkthrough
  1. (1) Stopwatch from System.Diagnostics measures elapsed time rather than wall-clock time.
  2. (2) next execution; finally records elapsed time even on failure.
  3. (3) Structured logging excludes bodies and secrets. A propagating exception may not yet have its final status; this is a timing log, not a success verdict.
  4. (4) Place endpoint-aware limiting after routing, and after authentication when partitioning by user. Forwarded headers belong early according to hosting.

Conventional middleware constructors are long-lived; inject scoped services into InvokeAsync or use suitably registered IMiddleware. Parameterless app.Run() starts the host; Run(handler) is terminal middleware. Static files served before authorization are public; protect private content through an appropriate path.

#Routing and binding: destination and input sources

Routing selects an endpoint; binding converts request values into parameters. An int route constraint restricts matching rather than validating user input with a field error.

// Conventional routing for controllers with views:
app.MapControllerRoute("default", "{controller=Home}/{action=Index}/{id?}");

// Attribute routing; IOrderService and UpdateOrder are application types.
[ApiController]
[Route("api/orders")]
public sealed class OrdersController(IOrderService orders) : ControllerBase
{
    [HttpPut("{id:int}")]
    public async Task<IActionResult> Update(
        [FromRoute] int id,                              // (1)
        [FromQuery] bool notify,
        [FromHeader(Name = "Idempotency-Key")] string? key,
        [FromBody] UpdateOrder request,                  // (2)
        CancellationToken ct)
    {
        await orders.UpdateAsync(id, request, notify, key, ct);
        return NoContent();
    }
}

// Minimal API grouping:
var group = app.MapGroup("/api/products").RequireAuthorization();
group.MapGet("/{id:int}", (int id) => TypedResults.Ok(new { Id = id }));
🧾 Code walkthrough
  1. (1) Route identifies the resource, query carries options and headers carry metadata. FromServices resolves ; FromForm binds form fields and uploads.
  2. (2) Bind one body to a request . Avoid exposing entity properties to overposting.

ApiController infers sources from types, routes and DI registration; explicit attributes help when the source matters. Minimal have their own rules, including TryParse and BindAsync, rather than model binders. /orders/abc does not match {id:int}, commonly producing 404; conversion failure after a match may produce 400. Constraints do not replace range checks.

#Validation, filters and .NET 10 Minimal APIs

Binding asks whether input can be read, validation whether its shape is acceptable, and the domain whether the operation is currently allowed. Stock availability needs enforcement with a safe update, not merely a request annotation.

// .NET 10 Web project; package: Microsoft.Extensions.Validation.
using System.ComponentModel.DataAnnotations;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddValidation();                         // (1)
var app = builder.Build();
app.MapPost("/products", (CreateProduct request) =>
    TypedResults.Created("/products/1", request));         // (2) illustrative id
app.Run();

public sealed class CreateProduct
{
    [Required, StringLength(200)]
    public string Name { get; set; } = "";
    [Range(0.01, 1_000_000)]
    public decimal Price { get; set; }
}
🧾 Code walkthrough
  1. (1) 10 AddValidation enables automatic validation of supported Minimal parameters using generated metadata. The example is in the same assembly.
  2. (2) Failed annotations return 400 before the handler runs. A real creation endpoint persists the resource and uses its actual Id in Location.
ChoiceScope
Data annotationsSimple input rules; ApiController returns automatic 400 for invalid ModelState
FluentValidationComplex, testable rules; explicitly call ValidateAsync for async rules and integrate errors with responses
MVC filtersAuthorization, resource, action, exception and result stages; not every filter wraps every stage
Endpoint filtersMinimal API handler arguments and results, distinct from MVC action filters

Action filters see bound arguments; resource filters run before binding. Exception filters do not cover all or response-writing failures. Middleware usually suits -wide logging or global errors. See the validation reference for metadata discovery across assemblies.

#Results, status codes and response contracts

An response includes status and headers as well as . Use return types that reveal success and failure possibilities rather than returning 200 for every outcome.

using Microsoft.AspNetCore.Http.HttpResults;

// IOrderReader and OrderDto are application types registered with DI.
app.MapGet("/orders/{id:int}",
    async Task<Results<Ok<OrderDto>, NotFound>>
    (int id, IOrderReader reader, CancellationToken ct) =>
    {
        var order = await reader.GetAsync(id, ct);          // (1)
        return order is null
            ? TypedResults.NotFound()
            : TypedResults.Ok(order);                      // (2)
    });
🧾 Code walkthrough
  1. (1) Null represents expected absence rather than an invented exception.
  2. (2) The Results union constrains return types and supplies OpenAPI metadata. This is a Minimal result, not 's IActionResult.

IActionResult supports varied MVC results; ActionResult<T> adds a success type. Document failure responses too, and avoid serializing entities directly.

StatusMeaning
200 / 201 / 202 / 204Success with data; creation with Location; accepted but unfinished work; success without a body
400 / 401 / 403 / 404Invalid input; missing or invalid credentials; known identity without permission; absent resource
409 / 422 / 429 / 500State conflict; understood but unprocessable content under the contract; rate limit; unexpected failure

202 does not claim completion. A 401 generally challenges with WWW-Authenticate according to the scheme; 403 forbids. Define a consistent business-error policy instead of memorizing a status for every exception type.

#DI lifetimes, captive dependencies and keyed services

A worked example
  1. A reporting service receives a . Scoped lifetime means one instance per scope; a typical web request supplies such a scope.
  2. Making the reporting service a singleton while retaining that context extends the dependency's intended lifetime and shares it across requests. This is a captive dependency, potentially causing validation errors, concurrency faults or stale state.
  3. Choose lifetimes around retained state and inspect dependency lifetimes too. A BackgroundService needing scoped services creates an appropriate scope per unit of work rather than retaining them for its entire lifetime.

handles ownership and lifetime as well as construction. A long-lived service retaining a short-lived dependency can accidentally share state across requests.

LifetimeMeaningExample
TransientAn instance per resolutionA lightweight service without shared state
ScopedAn instance per scope, commonly a requestDbContext
SingletonAn instance per root service providerA cache or shared service requiring thread safety
// Application types: IPaymentProvider, CardProvider and WalletProvider.
builder.Services.AddKeyedScoped<IPaymentProvider, CardProvider>("card");
builder.Services.AddKeyedScoped<IPaymentProvider, WalletProvider>("wallet"); // (1)

app.MapPost("/payments/card",
    ([FromKeyedServices("card")] IPaymentProvider provider) =>
        TypedResults.Ok(provider.Name));                   // (2)

// A singleton's unit of work, not a singleton-held DbContext:
await using var scope = scopeFactory.CreateAsyncScope();   // (3)
var work = scope.ServiceProvider.GetRequiredService<IOrderWork>();
await work.ExecuteAsync(ct);
🧾 Code walkthrough
  1. (1) Keyed DI, introduced in 8, distinguishes implementations of one interface.
  2. (2) Declarative key selection avoids spreading IServiceProvider through business classes.
  3. (3) A fresh scope owns each unit of work and ends after its awaited work. Do not let its dependencies escape.

A singleton retaining a scoped DbContext is a captive dependency. ValidateScopes detects many violations when enabled; exercise startup validation in tests too. A captured transient can effectively live as long as its singleton owner. Let the container dispose what it owns; avoid arbitrary disposal of injected services or calling BuildServiceProvider inside registrations.

#Configuration, options, environments and secrets

Deployment differences should not produce scattered settings or committed passwords. Bind typed options and fail early when required values are invalid.

Default application configuration, low -> high priority:
appsettings.json -> appsettings.{Environment}.json
  -> User Secrets in Development -> environment variables -> command-line

Later added providers can override earlier values.
Payments__ApiKey maps to Payments:ApiKey.
public sealed class PaymentOptions
{
    [Required] public string ApiKey { get; set; } = "";
    [Range(1, 60)] public int TimeoutSeconds { get; set; } = 10;
}

// Before Build; DataAnnotations attributes need their namespace.
builder.Services.AddOptions<PaymentOptions>()
    .BindConfiguration("Payments")                          // (1)
    .ValidateDataAnnotations()
    .ValidateOnStart();                                     // (2)
🧾 Code walkthrough
  1. (1) Binds one section instead of scattering string lookups.
  2. (2) Validates during host startup rather than the first payment request. Keep ApiKey out of diagnostics and logs.
InterfaceLifetime and changes
IOptions<T>Singleton wrapper with cached values, without reload tracking
IOptionsSnapshot<T>Scoped; computes options on demand per scope, unsuitable for singleton injection
IOptionsMonitor<T>Singleton with CurrentValue and OnChange when the provider supports reload

Development, Staging and Production select settings and debugging behavior, not a security boundary. With WebApplicationBuilder, DOTNET_ENVIRONMENT takes precedence if ASPNETCORE_ENVIRONMENT is also set; avoid conflicting values. Development User Secrets stay outside Git but are not an encrypted vault. Use an appropriate production secret store or injection mechanism; environment variables are not encryption.

#IExceptionHandler, ProblemDetails and structured logging

Repeated action-level try/catch creates inconsistent errors and can expose stack . Represent expected outcomes as results and centralize unexpected failures with a trace identifier linking the response to internal logs.

using Microsoft.AspNetCore.Diagnostics;
using Microsoft.AspNetCore.Mvc;

// Before Build:
builder.Services.AddProblemDetails();
builder.Services.AddExceptionHandler<GlobalErrors>();       // (1)
// After Build, early: app.UseExceptionHandler();

public sealed class GlobalErrors(ILogger<GlobalErrors> log) : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(HttpContext context,
        Exception exception, CancellationToken ct)
    {
        log.LogError(exception, "Unhandled request {TraceId}",
            context.TraceIdentifier);                     // (2)
        context.Response.StatusCode = 500;
        await context.Response.WriteAsJsonAsync(new ProblemDetails
        {
            Status = 500,
            Title = "An unexpected error occurred.",
            Extensions = { ["traceId"] = context.TraceIdentifier }
        }, contentType: "application/problem+json", cancellationToken: ct); // (3)
        return true;                                      // (4)
    }
}
🧾 Code walkthrough
  1. (1) Registers an IExceptionHandler, available from 8, with ProblemDetails services; multiple handlers run in registration order.
  2. (2) Full exceptions go to an access-controlled internal sink. The placeholder makes TraceId searchable.
  3. (3) Clients receive a stable message and identifier, without database internals or secrets.
  4. (4) true marks the exception handled; false allows another handler to try. Handlers are long-lived, so respect dependency lifetimes.

Once a response starts, rewriting its status or body may be impossible. Treat request cancellation according to its expected semantics. In .NET 10, default diagnostics for exceptions handled with true are suppressed by default; deliberately log what is needed without duplication. Levels are Trace, Debug, Information, Warning, Error and Critical. Exclude and card data. See the error-handling reference.

#CORS, preflight, rate limits and health checks

A browser needs permission to read cross-origin responses; an also needs traffic limits and readiness signals. These are distinct concerns, none replacing authentication.

using System.Threading.RateLimiting;
using Microsoft.AspNetCore.Diagnostics.HealthChecks;

builder.Services.AddCors(o => o.AddPolicy("storefront", p => p
    .WithOrigins("https://shop.example.com")
    .WithMethods("GET", "POST")
    .WithHeaders("Content-Type", "Authorization")));       // (1)
builder.Services.AddRateLimiter(o =>
{
    o.RejectionStatusCode = StatusCodes.Status429TooManyRequests;
    o.AddFixedWindowLimiter("browse", p =>
    {
        p.PermitLimit = 100;
        p.Window = TimeSpan.FromMinutes(1);
        p.QueueLimit = 0;
    });                                                    // (2)
});
builder.Services.AddHealthChecks();                        // (3)

// After Build; install CORS and rate-limiting middleware as shown earlier.
app.MapGet("/catalog", () => TypedResults.Ok("Catalog"))
    .RequireRateLimiting("browse");
app.MapHealthChecks("/health/live", new HealthCheckOptions
{
    Predicate = _ => false                                 // (4)
});
app.MapHealthChecks("/health/ready");
🧾 Code walkthrough
  1. (1) Allow specific origins. or Authorization headers commonly trigger an OPTIONS preflight; handles it before authorization.
  2. (2) This policy shares one quota in an instance, not automatically per user. Use partitioning for user or client quotas.
  3. (3) Registration alone adds no dependency checks. Add real readiness checks for required dependencies or the endpoint will report Healthy without inspecting them.
  4. (4) Liveness checks -host responsiveness without dependency checks; readiness runs registered checks.

CORS is a browser policy and does not restrict curl or prevent . For credentialed cookie requests, use AllowCredentials with explicit origins rather than a wildcard. In-process limiting is neither a distributed quota nor complete DDoS protection; enforce shared limits at a gateway where needed. Avoid restart loops from dependency-based liveness and keep detailed health output private.

#HttpClient and resilience: time budgets and retries

Creating a new client and handler for every call loses connection reuse and can exhaust sockets. The factory separates client and handler lifetimes and centralizes configuration and resilience.

// Package: Microsoft.Extensions.Http.Resilience.
using Microsoft.Extensions.Http.Resilience;

builder.Services.AddHttpClient<CatalogClient>(http =>
{
    http.BaseAddress = new Uri("https://catalog.example.com/"); // (1)
}).AddStandardResilienceHandler(options =>
{
    options.Retry.DisableForUnsafeHttpMethods();             // (2)
});

public sealed class CatalogClient(HttpClient http)
{
    public Task<ProductDto?> GetAsync(int id, CancellationToken ct) =>
        http.GetFromJsonAsync<ProductDto>($"products/{id}", ct); // (3)
}
🧾 Code walkthrough
  1. (1) Centralizes the base address; the factory manages pooled handlers.
  2. (2) The standard handler adds timeouts, retries, a circuit breaker and other strategies. Unsafe-method retries are disabled until an contract supports them.
  3. (3) Cancellation flows into the call. Nullable output alone does not map a 404: GetFromJsonAsync throws on non-success responses, so define error mapping explicitly.

A long-lived client with SocketsHttpHandler and PooledConnectionLifetime is also valid. Avoid capturing a short-lived typed client in a singleton without reviewing and lifetime behavior. Tune total budgets, per-attempt timeouts and retries to the dependency. Retrying a payment may duplicate an operation that succeeded before its response was lost.

#BackgroundService, durable jobs and deployment

Cleanup or reconciliation should not be fire-and-forget work tied to a request scope. Use an in-process loop for repeatable work and a durable scheduler or queue when losing work on restart is unacceptable.

// IOrderMaintenance is a scoped application service.
public sealed class OrderCleaner(IServiceScopeFactory scopes,
    ILogger<OrderCleaner> log) : BackgroundService
{
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        using var timer = new PeriodicTimer(TimeSpan.FromMinutes(10)); // (1)
        try
        {
            while (await timer.WaitForNextTickAsync(stoppingToken))
            {
                try
                {
                    await using var scope = scopes.CreateAsyncScope(); // (2)
                    var work = scope.ServiceProvider
                        .GetRequiredService<IOrderMaintenance>();
                    await work.CleanAsync(stoppingToken);
                }
                catch (Exception ex) when (ex is not OperationCanceledException)
                {
                    log.LogError(ex, "Order cleanup failed");        // (3)
                }
            }
        }
        catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
        {
            // Normal shutdown.
        }
    }
}
// Register before Build:
// builder.Services.AddHostedService<OrderCleaner>();
🧾 Code walkthrough
  1. (1) One loop awaits ticks without overlapping its own work; each replica still runs its own loop.
  2. (2) A hosted service is long-lived, so each iteration creates a scope instead of retaining a .
  3. (3) Iteration failures are logged; the next tick is another opportunity, not a durable retry policy.

Unhandled ExecuteAsync exceptions stop the host by default from 6. In .NET 10 all of ExecuteAsync runs in the background; do not use its pre-await portion as a startup barrier. Hangfire offers persisted jobs, retries and a dashboard. Quartz specializes in scheduling, with persistence and clustering when configured. Neither guarantees exactly-once side effects; design jobs.

  1. Publish Release as framework-dependent or self-contained and promote a tested, fixed artifact.
  2. Use images for container builds and ASP.NET runtime images for execution; patch both runtime and .
  3. Inject configuration and secrets per environment and configure trusted proxies and .
  4. Run migrations as a controlled step with backward-compatible changes for rolling or blue-green deployments.
  5. Gate traffic on readiness and support graceful shutdown and request draining; deployment strategy alone does not guarantee zero downtime.

Interview questions

Check your reasoning

Why does this choice fit? When would it fail? What changes under higher load or a dependency failure?

7.1Explain MVC and the responsibility of a controller.Junior
Model answer

I describe as separating the model's data and behavior, the view's presentation and the controller's request coordination. Routing selects an action, which binds input, calls application behavior and chooses a response. I keep business rules out of controllers so they can be tested and reused independently of . The model is not limited to an entity; a view model can contain exactly what a page needs without exposing persistence details.

7.2When would you choose controllers, Razor Pages or Minimal APIs?Junior
Model answer

I use controllers when the application benefits from conventions, action filters or an existing controller organization. Razor Pages fits page-oriented workflows. Minimal suit endpoints organized around features with explicit route handlers and endpoint groups. Blazor addresses component-based rather than merely replacing an API style. I base the choice on team conventions, rendering needs and supported capabilities, and keep application logic separate so the hosting style does not dominate the design.

7.3What is Kestrel, and is it always behind IIS?Junior
Model answer

Kestrel is 's cross-platform server. It can run directly or behind a such as Nginx, or . A proxy may centralize , routing and load balancing, but IIS is not required. On Windows I distinguish IIS in-process hosting, which uses the IIS server integration, from out-of-process hosting that forwards to Kestrel. I also configure forwarding trust so application code sees the correct client address and scheme.

7.4What is middleware and how does next work?Junior
Model answer

forms the processing pipeline. A component can inspect or change the request, call next to run downstream components, and do more work as the response unwinds. It can also short-circuit, for example by rejecting a request without calling next. Ordering therefore determines both what runs and what context is available. I place broad exception handling early enough to wrap the relevant components and authentication before authorization that needs the user identity.

7.5What is the difference between Use, Run and Map?Junior
Model answer

I use Use for that may call the next component. Run with a request adds terminal middleware. Map with a path branches the pipeline for matching requests. I distinguish that branching from endpoint registration such as MapGet and MapControllers, which uses endpoint routing. There is also a separate parameterless app.Run call that starts the host. Mixing these meanings is a common reason an endpoint becomes unreachable behind an earlier terminal handler.

7.6How do conventional and attribute routing differ?Junior
Model answer

Conventional routing defines patterns centrally, such as controller, action and optional identifier, which works well for views. Attribute routing places route templates and methods on controllers and actions, making contracts explicit. Constraints such as int or guid narrow route matching. I do not use constraints as input validation: a failed constraint commonly means no matching endpoint and a 404, whereas an invalid bound value can produce a 400 response.

7.7Where can action parameters be bound from?Junior
Model answer

Parameters can come from route values, the query string, headers, a body, form data or dependency injection. I use explicit source attributes when they clarify the contract and separate request from persistence entities to avoid overposting. ApiController can infer many sources, but I do not assume every framework style has identical rules. Minimal have their own binding conventions, including TryParse and BindAsync, and one request body should represent one coherent input model.

7.8How do IActionResult, ActionResult<T> and TypedResults differ?Junior
Model answer

IActionResult gives actions flexibility to return different result kinds. ActionResult of T combines a declared success payload with other action results. In Minimal , TypedResults returns concrete result types, and a Results union can declare the permitted alternatives, such as Ok of OrderDto and NotFound. That helps compile-time checking and OpenAPI metadata. I still document errors and headers deliberately, because a success type alone does not define the entire contract.

7.9Why does middleware order matter for routing, authentication and limiting?Mid
Model answer

Each depends on information established earlier. Routing selects endpoint metadata used by , authorization and endpoint-specific rate limits. Authentication establishes the user, so I place a user-partitioned limiter after it and authorization after identity is available. Exception handling needs to wrap the components whose failures it should handle. I make the order explicit when these dependencies matter, even though minimal hosting can insert some middleware automatically in simpler configurations.

7.10When should you use a filter instead of middleware?Mid
Model answer

I use for -wide behavior such as timing, correlation and global error handling. I use an filter when the concern depends on action context, bound arguments, ModelState or action results. Middleware after routing can inspect endpoint metadata, so it is inaccurate to say it knows nothing about endpoints, but it does not automatically get bound action arguments. For Minimal , endpoint filters provide a separate handler-level mechanism rather than MVC action filters.

7.11How does validation differ between API controllers and .NET 10 Minimal APIs?Mid
Model answer

With ApiController, binding and model validation populate ModelState, and invalid input normally produces an automatic 400 response. 10 Minimal can enable built-in validation through AddValidation and the validation package, using generated metadata for supported parameter types. I verify discovery when models live in other assemblies. For complex asynchronous rules I explicitly integrate a validator such as FluentValidation. In both cases, request validation does not replace transactional enforcement of business .

7.12What are the three DI lifetimes, and what is a captive dependency?Mid
Model answer

Transient creates an instance per resolution, scoped shares one within a scope, and singleton shares one within the root service provider. A request normally supplies a scope. A captive dependency occurs when a longer-lived service retains something intended to be shorter-lived, such as a singleton capturing a scoped . That can share unsafe state across requests. I enable scope validation and create explicit scopes for singleton background units of work, keeping scoped objects inside those scopes.

7.13What do keyed services solve?Mid
Model answer

Keyed services let the container register multiple implementations under one interface with distinct keys, available from 8. A payment endpoint can request the card provider explicitly without ambiguous registration. When the key comes from runtime input, I usually put selection behind a small factory or strategy boundary instead of injecting IServiceProvider everywhere. I still validate the allowed key and respect the registered lifetime; keys do not change ownership or make a provider thread-safe.

7.14How do configuration precedence and environments work?Mid
Model answer

Default application configuration layers the base settings file, environment-specific settings, development User Secrets, environment variables and command-line arguments, with later values overriding earlier ones. Added providers participate according to registration order. The environment name selects files and development behavior, but is not a security boundary. I avoid conflicting environment variables and keep secrets outside source control. User Secrets are a development convenience rather than an encrypted production vault, so production needs an appropriate secret-management mechanism.

7.15When would you use IOptions, IOptionsSnapshot or IOptionsMonitor?Mid
Model answer

I use IOptions for stable cached configuration, IOptionsSnapshot for a value computed on demand within each scope, and IOptionsMonitor for a singleton-friendly current value with change notifications. Reload depends on the underlying provider; it is not guaranteed for every source. Snapshot is scoped and should not be captured by a singleton. I bind a typed options class and use validation with ValidateOnStart so missing keys or invalid timeouts fail during startup rather than during a payment.

7.16How do you handle unexpected errors without leaking internals?Mid
Model answer

I centralize unexpected failures with IExceptionHandler and exception-handler , returning a ProblemDetails response with a stable message and identifier. Detailed exceptions stay in internal logs. Expected outcomes such as a missing resource usually return explicit results instead of exceptions. I handle cancellation according to its context and remember that an already-started response cannot always be rewritten. In 10, handled exceptions suppress default diagnostics, so I choose application logging deliberately to avoid missing or duplicate records.

7.17What is CORS preflight, and does CORS secure an API?Mid
Model answer

A browser sends a preflight OPTIONS request for cross-origin requests that need permission for their method or headers, such as requests with Authorization. answers according to the allowed origins, methods and headers. It is a browser-enforced sharing policy, not server authentication, authorization or protection, and tools such as curl do not enforce it. I allow explicit origins and never combine wildcard origins with credentialed access.

7.18Why use IHttpClientFactory, and what is a valid alternative?Mid
Model answer

IHttpClientFactory centralizes configuration and pools handlers so short-lived clients can reuse connections without rebuilding a connection pool for every call. Typed clients also give a focused application boundary. A long-lived HttpClient using SocketsHttpHandler with a suitable PooledConnectionLifetime is a valid alternative. I avoid claiming that every static client is wrong. I check refresh, cancellation, timeouts and lifetime capture, especially when a singleton holds a typed client intended to be short-lived.

7.19How do you safely configure retries for outbound HTTP calls?Senior
Model answer

I define a total time budget, per-attempt limits, bounded retries and which failures are transient. Backoff and jitter reduce synchronized retry bursts, while a circuit breaker limits repeated calls to an unhealthy dependency. I disable retries for unsafe operations unless an contract makes repetition safe. A timeout does not prove a payment failed; the response may simply have been lost. I also prevent stacked retry layers from multiplying attempts and verify behavior under dependency failure.

7.20What are the limits of in-process rate limiting?Senior
Model answer

An in-process limiter enforces quotas within that application instance. With several replicas, a client can receive separate allowances unless a shared gateway or distributed mechanism coordinates them. I choose partition keys deliberately, authenticate before user-based partitioning and trust forwarded addresses only from configured proxies. I return an appropriate rejection response and consider queueing and retry guidance. Rate limiting protects capacity, but it does not replace authentication or upstream protection against large-scale traffic attacks.

7.21How do liveness and readiness checks differ?Senior
Model answer

Liveness answers whether the host is responsive enough to keep running; readiness answers whether it should receive traffic. I avoid making every dependency outage fail liveness because that can restart healthy processes continuously. Readiness checks include only dependencies needed for serving the intended traffic, with bounded checks and limited public detail. I also remember that AddHealthChecks alone does not inspect a database: real checks must be registered and selected for the appropriate endpoint.

7.22When would you choose BackgroundService, Hangfire or Quartz?Senior
Model answer

I use BackgroundService for simple in-process loops whose work can be repeated or recovered elsewhere. I create a scope per unit of work and honor shutdown cancellation. For durable queued jobs with retries and operational visibility, Hangfire can fit. Quartz suits scheduling requirements, with persistence and clustering when configured. I do not equate either tool with exactly-once side effects. Jobs need and recovery because a crash can occur after an external effect but before completion is recorded.

7.23What changed for BackgroundService execution in .NET 10?Senior
Model answer

In 10, the entire ExecuteAsync method runs in the background. I cannot rely on code before its first await to block startup as it could previously. If initialization must finish before requests are accepted, I use an explicit startup mechanism and readiness policy instead of an incidental scheduling behavior. I still handle operational failures deliberately, create scopes for scoped dependencies and honor cancellation; background execution does not make jobs durable or exceptions harmless.

7.24What makes an ASP.NET Core deployment safe for rolling upgrades?Senior
Model answer

I promote the same tested artifact, inject environment-specific configuration and secrets, and verify runtime and patching responsibilities. Readiness gates traffic and graceful shutdown drains active work. The important constraint is compatibility while old and new versions overlap, including changes, messages and contracts. I run migrations as a controlled deployment step and use expand-and-contract changes when needed. Rolling or blue-green deployment helps availability, but without compatibility and recovery planning it cannot guarantee zero downtime.

Test yourself

Quiz answers and completed-topic marks are saved in this browser; they do not sync across devices.

Score: 0 / 0

Which server receives requests in IIS in-process hosting?

out-of-process hosting proxies to Kestrel.

Where should a user-partitioned endpoint rate limiter run?

It needs endpoint metadata and the authenticated user.

A request does not match {id:int} because its segment is abc. Likely result?

A route constraint controls matching rather than input error formatting.

Which .NET 10 registration enables built-in Minimal API validation?

Annotations need the validation pipeline and suitable metadata.

A singleton directly retains a scoped DbContext. What is the problem?

A scope per unit of work prevents retaining the context for the service lifetime.

Which options abstraction is scoped?

Snapshot should not be captured by a singleton constructor.

What does CORS prevent by itself?

Authentication, authorization and defenses have separate roles.

What does returning true from IExceptionHandler mean?

10 suppresses default diagnostics for handled exceptions by default.

Which claim about HTTP retry is correct?

The response can be lost after the operation succeeds.

What should a readiness endpoint with no registered dependency checks imply?

AddHealthChecks alone does not add database checks.

What is required for scoped services inside BackgroundService?

A hosted service is long-lived, while its dependencies may be scoped.

Which statement about durable schedulers is correct?

A crash between a side effect and completion recording can cause the work to retry.