0 7 75 min read en

Part 6: ASP.NET Core Web Development – C# / .NET Interview Questions and Answers

This chapter explores ASP.NET Core Web Development.

The Answers are split into sections: What πŸ‘Ό Junior, πŸŽ“ Middle, and πŸ‘‘ Senior .NET engineers should know about a particular topic.

Also, please take a look at other articles in the series: C# / .NET Interview Questions and Answers

Fundamentals

This section introduces its core concepts, architecture, and differences from older ASP.NET versions, setting the stage for deeper topics ahead.

Fundamentals

❓ What is ASP.NET Core, and how does it differ from the old ASP.NET (Framework)?

ASP.NET Core is a modern, cross-platform, open-source web framework that’s part of the unified .NET platform. Unlike ASP.NET (Framework), which is primarily hosted on IIS and is tied to Windows, ASP.NET Core runs on Kestrel across Windows, Linux, and macOSβ€”often behind a reverse proxy (IIS/NGINX/Apache) in production. Its modular architecture (no System.Web) and redesigned pipeline deliver notably higher throughput and lower allocations

FeatureASP.NET (Framework)ASP.NET Core
PlatformWindows-onlyCross-platform (Windows/Linux/macOS)
HostingIIS-onlyKestrel, IIS, Nginx, etc.
PerformanceSlower, more overheadHigh-performance, redesigned pipeline; supports Minimal APIs
ArchitectureTightly coupled system.webModular, no system.web
Dependency InjectionManual or third-party onlyBuilt-in DI
VersioningPart of .NET FrameworkPart of .NET (Core/5+)
Open SourceReference source available, but not fully community-drivenFully open source on GitHub

What .NET engineers should know:

  • πŸ‘Ό Junior: Should know that ASP.NET Core is the newer, cross-platform version and is recommended for new projects.
  • πŸŽ“ Middle: Expected to work fluently with ASP.NET Core, understand middleware, dependency injection, hosting models, and build REST APIs.
  • πŸ‘‘ Senior: Should know the architectural differences, migration paths from legacy ASP.NET, performance tuning in Core, and how to design scalable apps using modern hosting models (Kestrel + reverse proxy, containers, etc.).

πŸ“š Resources:

❓ What is Kestrel, and can ASP.NET Core run without IIS?

Kestrel is the default cross-platform web server for ASP.NET Core. It’s a lightweight, high-performance HTTP server built into the framework. Yes, ASP.NET Core apps can run without IIS – in fact, Kestrel is often used as the web server on all platforms. On Windows, production hosting is typically IIS (full) acting as a reverse proxy to Kestrel. On Linux, you can run Kestrel directly or place NGINX/Apache/Traefik in front; choose based on TLS/process mgmt/security needs. This cross-platform web server approach is a departure from the old ASP.NET, which was tightly integrated with IIS.

ASP.NET Core project templates use Kestrel by default when not hosted with IIS. In the following template-generated Program.cs, the WebApplication.CreateBuilder method calls UseKestrel internally:

var builder = WebApplication.CreateBuilder(args);
builder.WebHost.ConfigureKestrel(options =>
{
    options.Limits.MaxRequestBodySize = 10 * 1024 * 1024;
    options.AddServerHeader = false;
});

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.Run();

What .NET engineers should know about Kestrel:

  • πŸ‘Ό Junior: Should know Kestrel is the web server that runs ASP.NET Core apps, and it doesn't require IIS.
  • πŸŽ“ Middle: Should understand how to configure Kestrel, when to use IIS or Nginx as a reverse proxy, and how SSL/offloading works.
  • πŸ‘‘ Senior: Should architect production setups with Kestrel + reverse proxy, handle edge cases (timeouts, limits), and understand server tuning for high-performance APIs.

πŸ“š Resources:

❓How does the ASP.NET Core request processing pipeline work, and what is middleware?

In ASP.NET Core, HTTP requests flow through a sequence of middleware components, which together form the request pipeline. Each middleware can handle the request and/or response, and then optionally pass control to the next middleware in the pipeline. Middleware is essentially a modular handler that you configure in code (typically in Program.cs or Startup.cs). For example, there are built-in middleware options for routing, authentication, and serving static files, among others, and you can also insert custom ones. 

middleware C#
app.UseRouting();              // Sets up routing
app.UseAuthentication();       // Checks for auth
app.UseAuthorization();        // Checks access
app.MapControllers();          // Executes controller action

The pipeline is executed in order; a middleware can perform work before and after calling the next one.

middleware pipeline
ASP.NET Core Pipeline

What .NET engineers should know about middleware:

  • πŸ‘Ό Junior: Should understand that middleware is like a chain of handlers for HTTP requests and responses. Should know how to add and use existing middleware like UseRouting, UseAuthentication.
  • πŸŽ“ Middle: Should be able to write custom middleware, understand how early termination works, and manage orders for correct behavior (e.g., exception handling before routing).
  • πŸ‘‘ Senior: Should architect pipelines for cross-cutting concerns (logging, error handling, metrics), optimize middleware performance, and debug complex request flows.

πŸ“š Resources: ASP.NET Core Middleware

❓ How do you create a custom middleware in ASP.NET Core?

Creating your middleware in ASP.NET Core enables you to plug custom logic into the request pipeline, such as logging, timing, headers, or metrics.

There are two main ways to write middleware:

Option 1: Use inline in Program.cs

app.Use(async (context, next) =>
{
    Console.WriteLine($"Request: {context.Request.Method} {context.Request.Path}");
    await next(); // Call the next middleware
});

Option 2: Create a middleware class

namespace Middleware.Example;

public class MyCustomMiddleware
{
    private readonly RequestDelegate _next;

    public MyCustomMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    // IMessageWriter is injected into InvokeAsync
    public async Task InvokeAsync(HttpContext httpContext, IMessageWriter svc)
    {
        svc.Write(DateTime.Now.Ticks.ToString());
        await _next(httpContext);
    }
}

public static class MyCustomMiddlewareExtensions
{
    public static IApplicationBuilder UseMyCustomMiddleware(
        this IApplicationBuilder builder)
    {
        return builder.UseMiddleware<MyCustomMiddleware>();
    }
}
// in program.cs
app.UseMyCustomMiddleware();

What .NET engineers should know:

  • πŸ‘Ό Junior: Should know how to write a basic inline middleware using app.Use.
  • πŸŽ“ Middle: Should be able to build reusable middleware classes, manage execution order, and handle exceptions safely.
  • πŸ‘‘ Senior: Should be able to write cross-cutting middleware (correlation ID, telemetry, auth wrappers), handle edge cases (async disposal, scoped services), and understand performance implications.

❓ What’s the difference between app.Use(), app.Run(), and app.Map() in the middleware pipeline?

In ASP.NET Core, these three methods define how middleware behaves β€” each serves a different purpose in building the request pipeline.

app.Use()

  • Most commonly used.
  • Allows chaining middleware via the next() method.
  • You can perform actions before and after calling the next middleware.

app.Run()

  • Ends the pipeline. No next().
  • Think of it as a terminal middleware.
  • Use it for simple early termination.

app.Map()

  • Branching based on request path.
  • Only runs its pipeline if the path matches.
  • Helpful in splitting pipelines.
Difference between app.Use(), app.Run(), and app.Map() in the middleware pipeline
Difference between app.Use(), app.Run(), and app.Map() in the middleware pipeline

Summary:

MethodCalls next()?Ends pipeline?Conditional (by path)?
Useβœ… Yes❌ No❌ No
Run❌ Noβœ… Yes❌ No
Mapβœ… Inside branch❌ Noβœ… Yes

What .NET engineers should know:

  • πŸ‘Ό Junior: Should know that Use() chains middleware, Run() ends it, and Map() splits it by route.
  • πŸŽ“ Middle: Should structure pipelines properly using all three, avoid unreachable middleware, and debug execution order.
  • πŸ‘‘ Senior: Should be able to use MapWhen() and  Map() for branching, support multi-tenant or plugin-style routing, and isolate pipeline concerns like metrics, health checks, and APIs.

❓ What is routing in ASP.NET Core, and how is it configured?

Routing in ASP.NET Core is the system that maps incoming HTTP requests to the correct endpoint β€” usually a controller action, Razor page, or minimal API.

Routing options:

Minimal APIs / Program.cs:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/hello", () => "Hello World!");

app.Run();

With Controllers:

// Program.cs
builder.Services.AddControllers();
app.UseRouting();
app.UseAuthorization();
app.MapControllers();

Controller:

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetUser(int id) => Ok($"User #{id}");
}

How is routing configured?

Option 1: Attribute routing (most common)

Use [Route] and [HttpGet], [HttpPost], etc., to define paths directly on actions.

Option 2: Convention-based routing (less standard in Core)

app.UseEndpoints(endpoints =>
{
    endpoints.MapControllerRoute(
        name: "default",
        pattern: "{controller=Home}/{action=Index}/{id?}");
});

 Route patterns support:

  • {parameter} for capturing values
  • {parameter?} for optional values
  • Constraints like {id:int} or {slug:alpha}

What .NET engineers should know:

  • πŸ‘Ό Junior: Should understand how routes match URLs to controller actions or endpoints. Use [Route] and MapGet().
  • πŸŽ“ Middle: Should define flexible route patterns, support multiple routes per action, and apply route constraints or defaults.
  • πŸ‘‘ Senior: Should be aware of route versions (e.g., api/v1), build custom route conventions, apply endpoint filters/middleware, and debug routing issues in complex systems.

πŸ“š Resources:

❓ How does the ASP.NET Core request lifecycle work from request to response?

The ASP.NET Core request lifecycle is the journey an HTTP request takes through the framework, from the moment it enters Kestrel to when the response is sent back. Understanding it helps you decide where to put cross-cutting logic (middleware, endpoint filters, MVC filters) and how different components interact.

middleware pipeline
ASP.NET Core Pipeline

Key stages:

  1. Kestrel β€” The built-in web server receives the HTTP request and passes it to the hosting layer.
  2. Middleware pipeline β€” Executes in the order you register (app.Use...), can short-circuit, modify requests/responses, or delegate to the next. Global concerns like logging, exception handling, auth, and CORS live here.
  3. Routing β€” Matches the request URL/HTTP method to an endpoint, if none match -  404.
  4. Endpoint filters (.NET 7+) β€” Run after routing but before endpoint execution; work for minimal APIs and MVC endpoints. Useful for lightweight per-endpoint validation, shaping, or logging.
  5. MVC filter pipeline β€” If MVC is selected, filters run in this order:
    • Authorization filters: check user access before hitting the action.
    • Resource filters: run before/after model binding.
    • Action filters: wrap the action method execution.
    • Result filters: wrap the result execution (e.g., modify the response).
    • Exception filters: handle unhandled exceptions inside MVC.
  6. Action method execution β€” Your controller or page handler runs.
  7. Result execution β€” Response is prepared (JSON serialization, Razor view rendering, file streaming).
  8. Response pipeline β€” Middleware after the endpoint can still modify the outgoing response.
  9. Kestrel sends a response β€” Data goes back to the client.

What .NET engineers should know:

  • πŸ‘Ό Junior: Requests pass through middleware first, then endpoint filters (if any), then MVC filters/actions. Order matters.
  • πŸŽ“ Middle: Understand early termination; know which stage to hook into for logging, caching, auth, validation.
  • πŸ‘‘ Senior: Understand properly how the pipeline works, can operate efficiently with different elements from the pipeline, and choose correctly which part of the code should be placed in which pipeline stages.

πŸ“š Resources:

❓ How to host and deploy ASP.NET Core cross-platform?

ASP.NET Core runs on Windows, Linux, and macOS, giving you multiple hosting options:

  • Self-contained β€” app ships with .NET runtime, no need to install it on the server.
  • Framework-dependent β€” smaller publish, requires runtime installed.
  • Docker containers β€” portable, great for cloud deployments.
  • Reverse proxy β€” Nginx (Linux) or IIS (Windows) for SSL, load balancing.

What .NET engineers should know:

  • πŸ‘Ό Junior: Know .NET apps can run on different OSes without changing code.
  • πŸŽ“ Middle: Understand self-contained vs framework-dependent, and how to use Nginx/IIS as a reverse proxy.
  • πŸ‘‘ Senior: Design automated CI/CD pipelines, containerize for Kubernetes, and optimize cross-platform performance.

πŸ“š Resources:

Middleware & Routing

ASP.NET Core processes HTTP requests through a configurable middleware pipeline. Here, we explore how middleware works, how routing determines request destinations, and how to customize these behaviors.

Middleware & Routing

❓ How to implement global error handling, and why prefer UseExceptionHandler middleware over exception filters?

Global error handling should sit in the middleware pipeline so it catches exceptions from any endpoint (such as minimal APIs, MVC, Razor Pages, static files, gRPC negotiations, etc.). UseExceptionHandler runs early and consistently; MVC exception filters only run inside the MVC filter pipeline (after routing and only for controller/page actions). That’s why UseExceptionHandler is the recommended global approach.

// Program.cs β€” production-safe global handler with ProblemDetails
using Microsoft.AspNetCore.Diagnostics;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddProblemDetails();   // .NET 7/8 built-in ProblemDetails service
var app = builder.Build();

// 1) Global handler (before auth/authorization)
app.UseExceptionHandler("/error");

// 2) Optional: surface 4xx as text/json in non-dev
// app.UseStatusCodePages();

app.MapGet("/boom", () => throw new InvalidOperationException("Oops"));

// Centralized error endpoint β€” works for ANY exception in the app
app.Map("/error", (HttpContext http) =>
{
    var feature = http.Features.Get<IExceptionHandlerFeature>();
    var ex = feature?.Error;

    // log with your ILogger here if needed
    var traceId = http.TraceIdentifier;

    return Results.Problem(
        title: "Unhandled server error",
        detail: ex?.Message,
        statusCode: StatusCodes.Status500InternalServerError,
        instance: http.Request.Path,
        extensions: new Dictionary<string, object?> { ["traceId"] = traceId });
});

app.Run();

Why UseExceptionHandler over exception filters?

  • Scope: middleware wraps the whole app; filters only wrap MVC actions.
  • Consistency: handles failures that occur before MVC (model binding selection, endpoint selection for minimal APIs, file endpoints, etc.).
  • Simplicity: one place to produce ProblemDetails, set headers, and log.
  • Compatibility: works the same whether you use minimal APIs or MVC; filters don’t run for minimal APIs.
  • Security: easy to keep detailed errors off in prod (pair with DeveloperExceptionPage only in dev).

What .NET engineers should know:

  • πŸ‘Ό Junior: Able to put error handling in middleware so it catches everything;
  • πŸŽ“ Middle: Know how to log exceptions with correlation IDs, map known exceptions to proper status codes (e.g., 400/404), and keep messages safe (no PII).
  • πŸ‘‘ Senior: Able to handle errors across services, add exception-to-HTTP mapping, integrate with observability (trace/span IDs), and ensure idempotent error responses during retries.

πŸ“š Resources:

❓ How to serve static files in ASP.NET Core and configure caching headers for them?

Kestrel serves static files (JS, CSS, images) via the static-file middleware. By default, it serves from the wwwroot folder. You can add more file providers and set cache headers per path to control browser/CDN caching.

wwwroot static files

Example set Cache-Control headers globally for static files:

using Microsoft.Net.Http.Headers;

app.UseStaticFiles(new StaticFileOptions
{
    OnPrepareResponse = ctx =>
    {
        // Example: 7 days for all static files
        var headers = ctx.Context.Response.GetTypedHeaders();
        headers.CacheControl = new CacheControlHeaderValue
        {
            Public = true,
            MaxAge = TimeSpan.FromDays(7)
        };
    }
});

HTML:

<!-- Razor view tip β€” automatic cache-busting for files in wwwroot -->
<link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
<script src="~/js/site.js" asp-append-version="true"></script>

What .NET engineers should know:

  • πŸ‘Ό Junior: Should be able to put files in wwwroot, enable UseStaticFiles(), and set Cache-Control for better performance. Use asp-append-version to bust caches when files change.
  • πŸŽ“ Middle: Apply different cache policies by path; use long max-age + immutable for fingerprinted assets, short TTLs for others. Remember static middleware supports conditional requests via Last-Modified.
  • πŸ‘‘ Senior: Pair caching with a CDN, negotiate compression (ResponseCompression), and keep static files behind a reverse proxy (Nginx/Apache). Define environment-specific TTLs, ensure deterministic asset hashing in your build, and monitor cache hit ratios.

πŸ“š Resources:

❓ What is endpoint routing, and how does it improve performance compared to the old routing system?

Endpoint routing centralizes routing so that the framework selects the endpoint early in the middleware pipeline and attaches it to the HttpContext. This means middleware later in the pipeline already knows which endpoint will run and can make decisions accordingly.

In the old routing system (pre-2.2 MVC routing), routing happened inside MVC after the middleware pipeline ran, so that middleware couldn’t use route metadata without extra complexity.

Performance & flexibility benefits:

  • Early endpoint selection reduces work later in the pipeline and allows middleware to skip processing if the route doesn’t match.
  • Single unified routing for minimal APIs, MVC, Razor Pages, gRPC, SignalR, etc., no separate routing systems.
  • Metadata-driven middleware policies (auth, CORS, filters, rate limits, output caching) can run based on endpoint attributes.
  • Lower allocations compared to older MVC routing when using high-throughput endpoints.

Example:

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

// Endpoint routing
app.UseRouting();

app.UseAuthentication();
app.UseAuthorization();

app.UseEndpoints(endpoints =>
{
    endpoints.MapGet("/hello/{name}", (string name) => $"Hello {name}!");
    endpoints.MapControllers();
});

app.Run();

What .NET engineers should know:

  • πŸ‘Ό Junior: Should know that endpoint routing picks the route before MVC runs; this helps middleware see where the request is going.
  • πŸŽ“ Middle: Understand that endpoint metadata drives per-endpoint policies (auth, CORS, rate limits). It unifies routing for all frameworks in ASP.NET Core.
  • πŸ‘‘ Senior: Use endpoint metadata for performance tuning and fine-grained policies. Design pipelines where middleware can short-circuit early based on endpoint info, and avoid unnecessary work for unmatched routes.

πŸ“š Resources:

Hosting Models & Application Startup

This section explains hosting options, startup flow, and service registration.

Hosting Models & Application Startup

❓ What is the "minimal hosting model" introduced in .NET 6, and how does it differ from the previous Startup.cs model?

In .NET 6, Microsoft introduced the minimal hosting model β€” a more straightforward, cleaner way to build ASP.NET Core apps by ditching the old Startup.cs + Program.cs split.

Instead of multiple files and boilerplate, you configure everything in a single file: Program.cs.

 Minimal hosting model (in .NET 6+)

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddControllers();

var app = builder.Build();

app.MapControllers();

app.Run();

 Old model (before .NET 6, but still supported)

// Program.cs
public class Program
{
    public static void Main(string[] args)
        => CreateHostBuilder(args).Build().Run();

    public static IHostBuilder CreateHostBuilder(string[] args) =>
        Host.CreateDefaultBuilder(args)
            .ConfigureWebHostDefaults(webBuilder =>
            {
                webBuilder.UseStartup<Startup>();
            });
}

// Startup.cs
public class Startup
{
    public void ConfigureServices(IServiceCollection services) { }
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { }
}

 Key differences between minimal hosting and startup.cs:

 

FeatureMinimal Hosting (from .NET 6)Traditional Startup.cs
FilesSingle (Program.cs)Two (Program.cs + Startup.cs)
API styleFluent, top-level statementsStructured methods
FlexibilityMore dynamicMore structured
Learning curveEasier for small projectsBetter for large separation

What .NET engineers should know:

  • πŸ‘Ό Junior: Should understand the basic layout of a minimal API in Program.cs and how services and middleware are registered.
  • πŸŽ“ Middle: Expected to migrate apps from Startup.cs to minimal hosting, understand lifecycle, dependency injection, and modularization in the new style.
  • πŸ‘‘ Senior: Should design scalable app architectures using minimal APIs, and organize large projects without a Startup.cs, and decide when the classic pattern is a better fit (e.g., enterprise apps with strict layering).

πŸ“š Resources: Minimal APIs overview

❓ How do you configure services and the HTTP request pipeline in ASP.NET Core?

In ASP.NET Core, there are two phases in application startup:

  • Configure Services – this is where you register dependencies.
  • Configure the HTTP request pipeline – this is where you define how the app handles incoming requests (via middleware).

In the minimal hosting model (Program.cs)

var builder = WebApplication.CreateBuilder(args);

// Configure services (Dependency Injection)
builder.Services.AddControllers();
builder.Services.AddDbContext<AppDbContext>();
builder.Services.AddScoped<IMyService, MyService>();

var app = builder.Build();

// Configure HTTP request pipeline (Middleware)
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

 What's happening behind the scenes:

  • builder.Services is the built-in DI container. You register things here once, and they’re injected wherever needed.
  • app.UseXYZ() adds middleware components β€” logging, routing, auth, etc.
  • app.MapControllers() connects the pipeline to controller endpoints.

Typical pipeline order:

app.UseExceptionHandler();    // Global error handling
app.UseHttpsRedirection();   // Force HTTPS
app.UseRouting();            // Enable routing
app.UseAuthentication();     // Auth middleware
app.UseAuthorization();      // Permission checks
app.MapControllers();        // Endpoint execution

What .NET engineers should know:

  • πŸ‘Ό Junior: Should know the difference between builder.Services (setup) and app.UseXYZ() (request pipeline). Should be able to add basic services and middleware.
  • πŸŽ“ Middle: Should organize service registrations using extension methods, group middleware properly, and debug pipeline issues.
  • πŸ‘‘ Senior: Should design modular service configuration, apply cross-cutting concerns (logging, metrics, CORS), optimize middleware performance, and manage scopes/lifetimes correctly.

πŸ“š Resources:

❓ What is the difference between a Generic Host and a Web Host in ASP.NET Core?

The Generic Host (Host.CreateDefaultBuilder) is the unified hosting model introduced in ASP.NET Core 2.1+ that can host any .NET app β€” web apps, background services, worker services, etc. The Web Host (WebHost.CreateDefaultBuilder) was the original host for ASP.NET Core web apps only, and it’s now considered legacy for new apps.

AspectGeneric Host (IHost)Web Host (IWebHost)
ScopeAny .NET Core app (web, gRPC, background, etc.)Web apps only
AbstractionIHost, IHostBuilderIWebHost, IWebHostBuilder
Service containerUnified DI for all workloadsWeb DI only
Background tasksSupports IHostedService nativelyRequires hacks or separate services
Config & loggingUnified config/logging setup via HostBuilderWeb-focused config/logging
StatusRecommended for all new projectsLegacy for backward compatibility

Example β€” Generic Host (modern)

var builder = Host.CreateDefaultBuilder(args)
    .ConfigureServices((ctx, services) =>
    {
        services.AddHostedService<MyBackgroundService>();
    })
    .ConfigureWebHostDefaults(webBuilder =>
    {
        webBuilder.UseStartup<Startup>();
    });

var app = builder.Build();
await app.RunAsync();

What .NET engineers should know:

  • πŸ‘Ό Junior: Generic Host is newer and works for all app types; Web Host is old and only for web apps.
  • πŸŽ“ Middle: Generic Host adds flexibility β€” you can run web + background services together with the same DI container and config.
  • πŸ‘‘ Senior: Use Generic Host for future-proofing; know how to configure multiple hosted services, integrate non-web workloads, and migrate from Web Host if maintaining older apps.

πŸ“š Resources: .NET Generic Host in ASP.NET Core

❓ What are IHostedService and BackgroundService, and when should you use them?

IHostedService is the ASP.NET Core abstraction for long-running background tasks that start with the app and stop on shutdown. BackgroundService is an abstract base class implementing IHostedService that gives you a dedicated ExecuteAsync method, making it more straightforward to write background loops without boilerplate.

When to use:

  • For tasks that run alongside your app but aren’t triggered by HTTP requests.
  • Examples: message queue consumers, periodic cleanup jobs, cache warming, metrics pushers.
  • Use IHostedService For complete control (start/stop events); use BackgroundService for simpler continuous or periodic work.

Example β€” IHostedService (manual start/stop):

public class MyWorker : IHostedService
{
    private Timer? _timer;

    public Task StartAsync(CancellationToken ct)
    {
        _timer = new Timer(_ => Console.WriteLine("Tick"), null, TimeSpan.Zero, TimeSpan.FromSeconds(10));
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken ct)
    {
        _timer?.Dispose();
        return Task.CompletedTask;
    }
}

What .NET engineers should know:

  • πŸ‘Ό Junior: Both are for background work in ASP.NET Core. BackgroundService is a simpler base class for loops.
  • πŸŽ“ Middle: Pick IHostedService when you need fine-grained start/stop control; BackgroundService for continuous tasks. Always honor cancellation tokens for graceful shutdown.
  • πŸ‘‘ Senior: Use DI for dependencies, ensure idempotent operations, add error handling/retries, and consider external schedulers (Quartz.NET, Hangfire, Kubernetes CronJobs) for durable or critical jobs.

❓ IHostedService vs BackgroundService vs schedulers β€” when to choose what?

OptionWhat it isBest forProsConsTypical setup
IHostedServiceLow-level start/stop interface for background workPrecise lifecycle control, one-off startup/teardown tasksMinimal deps; full control over Start/StopMore boilerplate; you manage loops/timersservices.AddHostedService<MyWorker>()
BackgroundServiceBase class that gives you ExecuteAsync loopContinuous or periodic tasks in-processSimple loop model; honors cancellation; easy DIIn-process only; restarts lose in-memory stateclass MyWorker : BackgroundService { … }
Quartz.NETIn-process scheduler with cron, calendars, persistenceRich schedules, many jobs, clustering (DB-backed)Cron, misfire handling, durable storesExtra package; DB setup; operational overheadservices.AddQuartz().AddQuartzHostedService()
HangfireIn-process/out-of-process background jobs in DB queueFire-and-forget, delayed, recurring jobs with retriesDashboard; retries; persistence; scale-outRequires DB/Redis; licensing for some featuresservices.AddHangfire(...); app.UseHangfireDashboard()
Azure Functions (Timer)Serverless scheduled functionsCloud-native schedules, pay-per-useNo servers; easy ops; scaleAzure-only; cold starts; vendor lock-infunction.json with schedule
Kubernetes CronJobCluster-level scheduled podsHeavy jobs, infra tasks, language-agnosticStrong isolation; infra-grade reliabilityRequires k8s; separate deploymentCronJob manifest with cron

What .NET engineers should know about IHostedService vs BackgroundService:

  • πŸ‘Ό Junior: Should use BackgroundService for simple loops; IHostedService when you need a custom start/stop, for cron-like tasks, prefer a scheduler (Quartz/Hangfire).
  • πŸŽ“ Middle: Should be able to persist state for critical work; add retries/backoff; ensure graceful shutdown via cancellation tokens; consider Hangfire/Quartz when jobs must survive restarts.
  • πŸ‘‘ Senior: Know and be able to choose external schedulers (Hangfire/Quartz/Azure/K8s) when its needed for durability, observability, and scale-out. Make jobs idempotent, add metrics/tracing, and define SLAs and failure policies.

πŸ“š Resources:

Dependency Injection & Configuration

ASP.NET Core’s built-in dependency injection simplifies service management and testing. Here, we discuss DI fundamentals, advanced registration patterns, and configuration handling best practices.

Dependency Injection & Configuration

❓ How does built-in Dependency Injection (DI) work in ASP.NET Core, and what are service lifetimes?

ASP.NET Core has built-in dependency injection (DI) β€” no need for third-party containers. It’s how you manage object creation and pass dependencies automatically where needed.

You register services once in Program.cs, and the framework injects them into controllers, services, or middleware.

ASP.NET Core supports three built-in lifetimes:

LifetimeDescriptionExample use
SingletonOne instance for the entire app lifetimeConfiguration, caching
ScopedOne instance per HTTP requestRepositories, services
TransientA new instance every time it's requestedLightweight, stateless helpers

Image below describe how scopes and lifetime management happends for those 3:

ASP.NET Core DI Pipeline
flow of getting services from .net core service registry
flow of getting services from .net core service registry

What .NET engineers should know:

  • πŸ‘Ό Junior: Should know how to register and inject services using constructor injection. Understand the basics of Singleton, Scoped, and Transient.
  • πŸŽ“ Middle: Should pick the right lifetime for each use case, debug DI-related issues, and organize service registration using extension methods or modules.
  • πŸ‘‘ Senior: Should manage complex DI graphs, resolve lifetime mismatches, design for testability, and apply best practices like avoiding service locator patterns or over-injection.

πŸ“š Resources:

❓ Which DI antipatterns do you know in ASP.NET Core?

Here are common dependency injection antipatterns

❌ 1. Service Locator

Hides dependencies instead of injecting them explicitly. Breaks testability, readability, and maintainability.

var myService = serviceProvider.GetService<IMyService>();

πŸ’‘ Instead: use constructor injection.

❌ 2. Captive Dependency

A long-lived service (e.g., Singleton) depends on a short-lived one (e.g., Scoped). Causes bugs and scope-related memory leaks.

builder.Services.AddSingleton<ServiceA>();
builder.Services.AddScoped<IDbContext, DbContext>();

πŸ’‘ Rule: scoped should never be injected into a singleton.

❌ 3. Disposing of Transient Services Manually

ASP.NET Core disposes of services it creates β€” don’t manually dispose of them unless you own their lifecycle.

public class MyService : IDisposable
{
    public void Dispose() => Console.WriteLine("Disposing manually");
}

πŸ’‘ Let the container handle cleanup.

❌ 4. Async Factories in DI

Async constructors aren’t supported. Trying to use async setup during injection causes deadlocks or async void chaos.

public MyService()
{
    var result = AsyncMethod().GetAwaiter().GetResult(); // deadlock alert
}

πŸ’‘ Use IHostedService or init methods instead.

❌ 5. Service Injection into ViewModels or Entities

Don't inject services into models or entities β€” it couples business data to infrastructure logic.

❌ 6. Over-injection

Passing too many services into a single class is a smell. Often, it means the class is doing too much.

public class GodService(
    IUserService u, IEmailService e, ICache c, ILogger l, IMapper m) { }

❌ 7. Hidden DI in static classes

Static classes can't take advantage of DI and make unit testing a nightmare.

πŸ’‘ Prefer injecting dependencies into instance-based classes.

What .NET engineers should know:

  • πŸ‘Ό Junior: Should avoid service locator, prefer constructor injection, and understand basic lifetimes.
  • πŸŽ“ Middle: Expected to spot captive dependencies, avoid over-injection, and understand async pitfalls in DI.
  • πŸ‘‘ Senior: Should refactor to avoid DI smells, guide others away from hidden coupling, and architect scalable, testable service registration patterns.

πŸ“š Resources: Dependency injection guidelines

❓ Can you describe use cases for [FromKeyedServices] attribute in DI?

[FromKeyedServices] is a feature in .NET 8 that lets you inject named (keyed) services into minimal APIs, controllers, or other endpoints. It’s like telling the DI container:

 "Give me this specific version of the service by key."

It's perfect when you have multiple implementations of the same interface and want to resolve the correct one by name, not by type.

Example use case: Multiple payment providers

Imagine you have two implementations of IPaymentService:

builder.Services.AddKeyedScoped<IPaymentService, StripePaymentService>("stripe");
builder.Services.AddKeyedScoped<IPaymentService, PayPalPaymentService>("paypal");

Then, in your minimal API, you can select one like this:

app.MapPost("/pay/stripe", ([FromKeyedServices("stripe")] IPaymentService service) =>
{
    return service.Process();
});

app.MapPost("/pay/paypal", ([FromKeyedServices("paypal")] IPaymentService service) =>
{
    return service.Process();
});

When to use [FromKeyedServices]:

ScenarioWhy it fits
Multiple handlers for different strategiesInject by scenario-specific key
Multitenant support (per-tenant logic)Inject services per tenant key
Feature toggles or config-based wiringLoad services by config key
APIs that expose different behaviorsClean separation in endpoints

Manual resolution (non-attribute approach)

If you need to resolve manually:

var service = context.RequestServices.GetKeyedService<IPaymentService>("stripe");

But using [FromKeyedServices] is cleaner and more idiomatic for modern APIs.

What .NET engineers should know:

  • πŸ‘Ό Junior: Might not use [FromKeyedServices] in the right away, but should know it's useful when the same interface has multiple flavors.
  • πŸŽ“ Middle: Should be able to register and inject keyed services properly, using them for API branching or strategy patterns.
  • πŸ‘‘ Senior: Should design modular systems where keyed DI avoids bloated factories or service locators, and helps cleanly decouple behavior.

πŸ“š Resources: Keyed services in C#

❓ How is configuration handled in ASP.NET Core?

ASP.NET Core has a flexible configuration system that loads settings from multiple sources β€” JSON files, environment variables, command-line args, and secrets, then merges them into a single configuration object.

Below are the available options to handle configuration:

1. appsettings.json & environment-specific files

By default, the app loads:

  • appsettings.json (base settings)
  • appsettings.Development.json, appsettings.Production.json, etc. (environment overrides)
// appsettings.json
{
  "AppSettings": {
    "SiteName": "My Site",
    "EnableFeatureX": true
  }
}
var builder = WebApplication.CreateBuilder(args);
var siteName = builder.Configuration["AppSettings:SiteName"];

2. Environment-based configuration

The environment is set via DOTNET_ENVIRONMENT (or ASPNETCORE_ENVIRONMENT for web apps).
Common values: Development, Staging, Production.

if (builder.Environment.IsDevelopment())
{
    // Dev-only configuration
}

3. IOptions pattern (strongly typed settings)

For type-safe access to config sections:

Register the settings class:

builder.Services.Configure<AppSettings>(
    builder.Configuration.GetSection("AppSettings"));

Inject with IOptions:

public class HomeController : Controller
{
    private readonly AppSettings _settings;
    public HomeController(IOptions<AppSettings> options)
    {
        _settings = options.Value;
    }
}

4. Reloading configuration at runtime

If reloadOnChange: true (default), updating appsettings.json automatically updates values for services using IOptionsSnapshot.

5. Other configuration sources

  • Environment variables (highest precedence after code)
  • User secrets (for development only, via dotnet user-secrets)
  • Azure Key Vault or other providers
  • Command-line args (override everything else)

What .NET engineers should know:

  • πŸ‘Ό Junior: Should know how to read values from appsettings.json and use environment-specific overrides.
  • πŸŽ“ Middle: Should apply the IOptions pattern, understand IOptionsSnapshot vs IOptionsMonitor and structure the config cleanly.
  • πŸ‘‘ Senior: Should design multi-source configuration (secrets, vaults, env vars), manage precedence, and support dynamic reloads in production.

πŸ“š Resources:

❓ What advanced DI features exist (TryAdd, Replace, Remove), and when should you use them?

ASP.NET Core’s DI container includes handy β€œextensibility” helpers for safe defaults, swapping implementations, and pruning registrations.

  • TryAdd* β€” register only if nothing is registered yet (great for library defaults).
  • TryAddEnumerable β€” add another implementation to a multi-registration only if that exact pair isn’t already present.
  • Replace β€” swap an existing service descriptor with a new one (override package/library defaults).
  • RemoveAll β€” remove all registrations for a given service (clean the slate before custom wiring).

Example:

using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;

// 1) Safe defaults: TryAdd / TryAddSingleton|Scoped|Transient
services.TryAddSingleton<IFoo, DefaultFoo>();  // only if IFoo not already registered

// 2) Multi-impl lists: TryAddEnumerable (no accidental duplicates)
services.TryAddEnumerable(ServiceDescriptor.Transient<IBar, BarA>());
services.TryAddEnumerable(ServiceDescriptor.Transient<IBar, BarB>());
// If BarA added elsewhere, TryAddEnumerable won’t duplicate it

// 3) Replace an existing registration (override a default)
services.Replace(ServiceDescriptor.Scoped<IFoo, BetterFoo>());

// 4) Remove all implementations for a service (then re-add what you want)
services.RemoveAll<IFoo>();
services.AddScoped<IFoo, FinalFoo>();

When to use each 

  • Use TryAdd in reusable libraries/extensions to supply sensible defaults without blocking app authors who want to override.
  • Use TryAddEnumerable for pipeline-style or composite patterns (e.g., multiple IConfigureOptions<T>, validators, handlers) to avoid duplicate entries.
  • Use Replace when a package registers something you want to override globally (e.g., swap a clock, serializer, or strategy).
  • Use RemoveAll when you need a clean slate before applying your own set (e.g., wiping default handlers or test doubles in integration tests).

What .NET engineers should know about advanced DI features:

  • πŸ‘Ό Junior: Should know following: TryAdd avoids overwriting existing registrations; Replace overrides; RemoveAll clears.
  • πŸŽ“ Middle: Prefer TryAddEnumerable For additive pipelines, understand that the last registration typically winsβ€”these helpers make intent explicit.
  • πŸ‘‘ Senior: In shared libraries, always use TryAdd*. In apps, Replace/RemoveAll enable deterministic composition, but document and test overrides to prevent subtle behavior changes.

❓ How does the Options pattern work (IOptions, IOptionsSnapshot, IOptionsMonitor), and when to use each?

The Options pattern binds configuration to typed POCOs and injects them via DI.

  • IOptions<T>: a singleton snapshot created once (per app lifetime). No reloads.
  • IOptionsSnapshot<T>: scoped (new value per request/DI scope). Picks up file/config reloads.
  • IOptionsMonitor<T>: singleton with change notifications (OnChange) and always returns the latest value.

Example:

// Program.cs β€” bind config and enable reloads from appsettings.json
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

builder.Services.AddOptions<MyFeatureOptions>()
       .Bind(builder.Configuration.GetSection("MyFeature"))
       .ValidateDataAnnotations()
       .Validate(o => o.RetryCount >= 0, "RetryCount must be >= 0");

var app = builder.Build();
app.MapGet("/show",
    (Microsoft.Extensions.Options.IOptions<MyFeatureOptions> opts) => opts.Value); // singleton snapshot
app.Run();

public sealed class MyFeatureOptions
{
    public string Endpoint { get; set; } = "";
    public int RetryCount { get; set; } = 3;
}

// IOptionsSnapshot<T> β€” request-scoped, updates when config file changes
app.MapGet("/snapshot", (Microsoft.Extensions.Options.IOptionsSnapshot<MyFeatureOptions> snap) =>
{
    var o = snap.Value; // recomputed per request (scope)
    return Results.Ok(new { o.Endpoint, o.RetryCount });
});


// IOptionsMonitor<T> β€” singleton with change callback (great for background/singleton services)
public sealed class Worker : BackgroundService
{
    private MyFeatureOptions _current;
    public Worker(Microsoft.Extensions.Options.IOptionsMonitor<MyFeatureOptions> monitor)
    {
        _current = monitor.CurrentValue;
        monitor.OnChange(newVal => _current = newVal); // hot-reload
    }

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        while (!ct.IsCancellationRequested)
        {
            // always uses the latest options
            await Task.Delay(TimeSpan.FromSeconds(5), ct);
        }
    }
}

What .NET engineers should know:

  • πŸ‘Ό Junior: Use IOptions<T> for simple, static config; IOptionsSnapshot<T> updates per request; IOptionsMonitor<T> updates globally and can notify on changes.
  • πŸŽ“ Middle: Don’t inject IOptionsSnapshot<T> into singletons (it’s scoped). For background/singleton services, prefer IOptionsMonitor<T>. Enable reloadOnChange for live updates. Add validation (Validate, ValidateDataAnnotations).
  • πŸ‘‘ Senior: Centralize options binding/validation, fail fast on invalid config, and use OnChange carefully (debounce/guard) to avoid race conditions. For multi-tenant or named configs, use IOptionsMonitor<T>.Get("name") and ConfigureNamedOptions.

πŸ“š Resources: Options pattern in .NET

❓ What configuration providers are available (Azure Key Vault, AWS Secrets Manager, database)?

ASP.NET Core configuration is provider-based β€” each provider reads key/value pairs from a source and merges them into a single configuration tree. Beyond built-ins (JSON, INI, environment variables, command-line args), you can plug in cloud secret stores or databases.

Common providers:

ProviderPurposeWhen to use
JSON / INI / XML filesAppsettings, environment overridesDefault app config
Environment variablesDeployment-specific settings, container/k8s envsCloud-native deployments
Command-line argsCI/CD overrides, dev/test flagsQuick switches
User secrets (dotnet user-secrets)Local dev secretsNever commit secrets to source
Azure Key Vault (Azure.Extensions.AspNetCore.Configuration.Secrets)Secure cloud secret storeAzure-hosted apps
AWS Secrets Manager (Amazon.Extensions.Configuration.SecretsManager)Secure cloud secret storeAWS-hosted apps
AWS Parameter Store (Amazon.Extensions.Configuration.SystemsManager)Config/secret store in AWS SSMAWS apps with frequent updates
Database (custom provider)Centralized, DB-backed configMulti-tenant/shared config
Redis or Consul (community packages)Distributed config in memory or service registryReal-time config updates
In-memory (MemoryConfigurationProvider)Testing, overridesUnit/integration tests

Examples:

// Azure Key Vault
builder.Configuration.AddAzureKeyVault(
    new Uri($"https://{vaultName}.vault.azure.net/"),
    new DefaultAzureCredential());

// AWS Secrets Manager
builder.Configuration.AddSecretsManager(options =>
{
    options.SecretsManagerConfig.RegionEndpoint = RegionEndpoint.USEast1;
});

// Database-backed (custom)
builder.Configuration.Add(new DbConfigurationSource(connectionString));

What .NET engineers should know:

  • πŸ‘Ό Junior: Config can come from multiple sources; later providers override earlier ones.
  • πŸŽ“ Middle: Use cloud secret stores for sensitive data; prefer environment-specific overrides over hardcoding.
  • πŸ‘‘ Senior: Design provider ordering carefully, enable reloads where supported, and centralize config for distributed apps. Secure all secret sources with the least privilege.

πŸ“š Resources:

❓ What pitfalls exist when using scoped services in async background tasks?

Background workers (BackgroundService / IHostedService) are singletons. Injecting scoped services (e.g., EF Core DbContext) directly into them creates lifetime mismatches and disposal bugs. The safe pattern is: create a new scope per unit of work, resolve scoped services inside that scope, and dispose of the scope when done. Also, avoid long‑lived scopes, scope reuse across iterations, and any reliance on HttpContext (not available off the request pipeline).

βœ”οΈ Correct: create a scope per iteration or batch

public sealed class Worker : BackgroundService
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<Worker> _log;
    public Worker(IServiceScopeFactory scopeFactory, ILogger<Worker> log)
        { _scopeFactory = scopeFactory; _log = log; }

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        var timer = new PeriodicTimer(TimeSpan.FromSeconds(10));
        try
        {
            while (await timer.WaitForNextTickAsync(ct))
            {
                using var scope = _scopeFactory.CreateScope(); // or: await using var scope = _scopeFactory.CreateAsyncScope();
                var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
                await DoWorkAsync(db, ct);
            }
        }
        catch (OperationCanceledException) { } // graceful shutdown
        catch (Exception ex) { _log.LogError(ex, "Background failure"); }
    }
}

❌ Anti-pattern: captive dependency

public sealed class BadWorker : BackgroundService
{
    private readonly AppDbContext _db; // ❌ scoped in a singleton
    public BadWorker(AppDbContext db) => _db = db;
    // Will fail or behave incorrectly; DbContext may outlive its scope and is not thread-safe.
}

Common pitfalls 

  • Captive dependencies (singleton β†’ scoped)
    πŸ› οΈ Fix: inject IServiceScopeFactory; resolve scoped services in a short-lived scope.
  • Reusing the same scope across loop iterations
    πŸ› οΈ Fix: new scope per iteration/batch; dispose after use to free pooled connections and DI objects.
  • Long-running work inside a single scope
    πŸ› οΈ Fix: keep scopes small; for streams/queues, chunk work into batches, each with its scope.
  • Parallel processing with shared-scoped instances
    πŸ› οΈ Fix: never share a scoped service across threads; create independent scopes per parallel worker.
  • Not honoring CancellationToken
    πŸ› οΈ Fix: pass the token to I/O (DbContext, HTTP) and timers; exit promptly on shutdown to avoid β€œzombie” scopes.
  • Async background tasks using IOptionsSnapshot<T>
    πŸ› οΈ Fix: it’s scoped; inject IOptionsMonitor<T> instead (singleton-friendly, change notifications).
  • Assuming HttpContext / requesting services exist.
    πŸ› οΈ Fix: background code runs outside the request pipeline; resolve what you need from your scope.
  • Sync-over-async / async void work
    πŸ› οΈ Fix: keep everything async Task; wrap with retries/backoff; surface errors to logs/metrics.
  • Leaking EF Core contexts / thread-safety issues
    πŸ› οΈ Fix: one DbContext per scope; no cross-thread use; dispose with the scope; prefer AsNoTracking() for read paths.
  • Hidden transitive captive dependencies
    πŸ› οΈ Fix: review registrations and lifetimes; consider unit tests that resolve the container to catch lifetime mismatches early.

What .NET engineers should know:

  • πŸ‘Ό Junior: Background workers are singletons. Don’t inject DbContext directly; make a scope per job and resolve it inside.
  • πŸŽ“ Middle: Avoid long-lived or shared scopes; pass cancellation tokens; use IOptionsMonitor<T> (not IOptionsSnapshot<T>) in background code.
  • πŸ‘‘ Senior: Batch work with separate scopes, cap concurrency (e.g., SemaphoreSlim), ensure idempotency, add observability, and validate DI lifetimes in tests/CI.

πŸ“š Resources:

Web APIs, Controllers & UI Frameworks

This section covers controllers, model binding, validation, and how to choose between API styles.

Web APIs, Controllers & UI Frameworks

❓ What are Minimal APIs in ASP.NET Core, and how do they differ from using Controllers (MVC)?

Minimal APIs are a lightweight way to build HTTP endpoints in ASP.NET Core without the whole MVC framework.

They were introduced in .NET 6 to make small services, microservices, and quick prototypes faster to create.

 When to use

  • Minimal APIs: Small services, serverless apps, quick prototypes, simple CRUD without heavy validation.
  • Controllers: Enterprise apps, complex routing, heavy model binding, filters, versioning.

 Minimal API example

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

app.MapGet("/hello", () => "Hello World!");
app.MapPost("/users", (User user) => $"User {user.Name} created");

app.Run();

 Controller (MVC) example

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    [HttpGet("{id}")]
    public IActionResult GetUser(int id) => Ok($"User #{id}");
}

Program.cs

builder.Services.AddControllers();
app.MapControllers();

Key differences between the minimal API and the Controller:

FeatureMinimal APIsControllers (MVC)
SetupVery little boilerplateRequires AddControllers() and attributes
StructureInline route definitionsSeparate controller classes & attributes
Best forSmall APIs, microservices, PoCsLarge, structured applications
Filters/Model bindingLimited, simplerRich filtering, binding, validation
TestabilityFunctional, but less structuredMore test-friendly with separation
PerformanceSlightly faster for small APIsSlight overhead from MVC pipeline

What .NET engineers should know:

  • πŸ‘Ό Junior: Should know that Minimal APIs are just a lighter way to define endpoints compared to controllers.
  • πŸŽ“ Middle: Should choose Minimal APIs for small services and understand trade-offs (no full MVC features). Should be able to migrate Minimal API endpoints to Controllers if the app grows.
  • πŸ‘‘ Senior: Should the architects choose the right approach per module, mix Minimal APIs and Controllers if needed, and manage concerns like validation, auth, and versioning without MVC overhead

πŸ“š Resources: Minimal APIs quick reference

❓ What is model binding and model validation in ASP.NET Core MVC?

Model binding is the process where ASP.NET Core automatically takes incoming HTTP request data β€” from route values, query strings, form fields, headers, or JSON body β€” and maps it to method parameters or model objects in your controller or minimal API.

It saves you from manually parsing HttpRequest and converting strings to C# types.

What is model binding and model validation in ASP.NET Core MVC?

Example:

public class UserDto
{
    public string Name { get; set; }
    public int Age { get; set; }
}

[HttpPost("users")]
public IActionResult CreateUser(UserDto user)
{
    // "user" is populated automatically from JSON body
    return Ok(user);
}

If the request body is:

{ "name": "Alice", "age": 25 }

ASP.NET Core binds it to UserDto automatically.

Binding sources:

  1. [FromRoute] β†’ URL segments (/users/5)
  2. [FromQuery] β†’ Query string (?page=2)
  3. [FromBody] β†’ JSON/XML body
  4. [FromForm] β†’ Form fields
  5. [FromHeader] β†’ Request headers
  6. [FromServices] β†’ Injected services

If no attribute is specified:

  • For simple types (string, int, etc.), ASP.NET Core binds from the route or query string.
  • For complex types, it binds from the request body (in [ApiController]-decorated controllers).

What .NET engineers should know:

  • πŸ‘Ό Junior: Should know model binding automatically converts HTTP request data into C# parameters or models.
  • πŸŽ“ Middle: Should use binding attributes to control data sources and handle binding for complex objects.
  • πŸ‘‘ Senior: Should customize model binding for advanced scenarios (custom binders, performance tuning) and design APIs with clear binding rules.

πŸ“š Resources: Model Binding in ASP.NET Core

❓ What is model validation in ASP.NET Core MVC?

Model Validation occurs after binding. If you have validation attributes on your model properties (like [Required], [Range], [EmailAddress], etc.), the framework will automatically validate the model and record any errors. In ASP.NET Core, if you have the [ApiController] attribute on a controller, it even automatically returns a 400 Bad Request with validation errors if the model state is invalid (this saves you from manually checking ModelState.IsValid in API scenarios). In Razor Pages or MVC views, you often check ModelState or use tag helpers that display validation messages.

Model validaton

So in practice, When a request comes in:

  1. Routing picks the action and provides some route values.
  2. The model binder binds simple action parameters (e.g. int id from route or query) and complex types (like a ProductDto from the JSON body or posted form).
  3. The bound object is validated. You can also trigger manual validation (e.g. TryValidateModel) if needed.
  4. If errors exist, they are stored in ModelState. An ApiController will short-circuit to 400 with those errors in the response by default. In MVC views, you might re-display the form with error messages.

This system greatly simplifies input handling by abstracting the conversion of HTTP data to .NET objects and checking constraints, so you can assume in your action that the input model is already parsed and (hopefully) valid.

Example:

public class UserDto
{
    [Required]
    [StringLength(50)]
    public string Name { get; set; }

    [Range(18, 99)]
    public int Age { get; set; }
}

[HttpPost("users")]
public IActionResult CreateUser(UserDto user)
{
    if (!ModelState.IsValid) // In [ApiController], this check is automatic
        return BadRequest(ModelState);

    return Ok(user);
}

Request:

{ "age": 15 }

Response:

{
  "errors": {
    "Name": ["The Name field is required."],
    "Age": ["The field Age must be between 18 and 99."]
  }
}

Common validation sources

  • Data Annotations: [Required], [Range], [EmailAddress], etc.
  • Custom Validation Attributes: Your own rules via ValidationAttribute.
  • FluentValidation: A Popular external library for complex validation.

What .NET engineers should know about validations:

  • πŸ‘Ό Junior: Should know how to apply Data Annotations and check ModelState.IsValid.
  • πŸŽ“ Middle: Should use [ApiController] for automatic validation, create custom validation attributes, and return meaningful error responses.
  • πŸ‘‘ Senior: Should design complex validation workflows (e.g., conditional rules, async checks), integrate external validators like FluentValidation, and ensure performance for large models.

πŸ“š Resources: Model validation in ASP.NET Core MVC and Razor Pages

❓ What are filters in ASP.NET Core, and how do they differ from middleware?

Filters are part of the MVC framework (controllers and Razor Pages) that let you run code at specific points during action execution β€” before an action runs, after it runs, when results are being processed, or when exceptions occur. Types include Authorization filters, Resource filters, Action filters, Exception filters, and Result filters.

Middleware, by contrast, runs earlier in the request pipeline β€” before MVC routing β€” and operates globally for all requests, regardless of whether they hit MVC.

Endpoint filters (introduced in .NET 7) sit between middleware and MVC filters. They run after routing but before the selected endpoint executes, and work with both minimal APIs and MVC endpoints. This makes them great for cross-cutting concerns that are endpoint-specific but not tied to MVC’s full filter pipeline.

Filters run within the ASP.NET Core action invocation pipeline, sometimes referred to as the filter pipeline. The filter pipeline runs after ASP.NET Core selects the action to execute:
 
mvc pipeline

Here’s a breakdown of how they differ:

AspectMiddlewareEndpoint Filters (.NET 7+)MVC Filters
ScopeGlobal, all requestsPer endpoint (minimal API or MVC)Controller/action level
RunsBefore routingAfter routing, before endpoint executionAfter endpoint selection, inside MVC
Use casesAuth, CORS, logging, headersPer-endpoint validation, logging, shapingModel validation, authorization, result transformation
Configured viaapp.Use...()app.MapGet(...).AddEndpointFilter(...)Attributes or options.Filters.Add()

Example of a simple action filter that logs before and after an action runs:

public class LogActionFilter : IActionFilter
{
    public void OnActionExecuting(ActionExecutingContext context)
        => Console.WriteLine("Before action executes.");
    public void OnActionExecuted(ActionExecutedContext context)
        => Console.WriteLine("After action executes.");
}

You can register it globally in Startup.cs:

services.AddControllers(o => o.Filters.Add<LogActionFilter>());

Or apply it to a specific controller/action:

[ServiceFilter(typeof(LogActionFilter))]
public class MyController : Controller { }

Example β€” simple Endpoint Filter (.NET 7+ minimal API):

app.MapGet("/hello", () => "Hi")
   .AddEndpointFilter(async (context, next) =>
   {
       Console.WriteLine("Before endpoint");
       var result = await next(context);
       Console.WriteLine("After endpoint");
       return result;
   });

What .NET engineers should know:

  • πŸ‘Ό Junior: Should know that middleware is global and runs early, while filters apply to controllers and actions. Basic understanding of UseMiddleware vs [Authorize], [ValidateModel], etc.
  • πŸŽ“ Middle: Expected to create custom filters for logging, validation, and error handling. Understand the lifecycle differences and when to choose one over the other.
  • πŸ‘‘ Senior: Should design reusable, DI-enabled filters. Know how to balance concerns between middleware and filters for maintainability. Understand execution order and early termination logic.

πŸ“š Resources: Filters in ASP.NET Core

❓ What is the difference between Razor Pages and MVC in ASP.NET Core, and when might you use Razor Pages?

Razor Pages is page-focused β€” each page has its UI (.cshtml) and logic (PageModel) together. MVC splits logic into controllers and views, offering more flexibility but more boilerplate. Razor Pages is excellent for simple, page-centric apps; MVC suits complex apps with shared logic.

Example:

// Razor Page
public class IndexModel : PageModel
{
    public string Message { get; set; } = "Hello!";
}
@page
<h1>@Model.Message</h1>

What .NET engineers should know:

  • πŸ‘Ό Junior: Razor Pages keeps page logic and view together, MVC separates them.
  • πŸŽ“ Middle & πŸ‘‘ Senior: Understand routing, handlers, and when MVC is better for shared logic.

πŸ“š Resources:

❓ What is Blazor, and how do Blazor WebAssembly and Blazor Server differ?

Blazor is a framework for building interactive web UIs using C# instead of JavaScript. It runs .NET code directly in the browser (WebAssembly) or on the server (SignalR).

Blazor WebAssembly runs the app in the browser, downloading the .NET runtime and app files. It works offline but has a larger initial load. Blazor Server runs on the server, sending UI updates over a real-time connection β€” lighter to load but needs constant connectivity.

What .NET engineers should know:

  • πŸ‘Ό Junior: Should know that Blazor lets you write C# for the browser. WebAssembly runs client-side; the Server runs on the server.
  • πŸŽ“ Middle: Know trade-offs between WebAssembly and Blazor Server
  • πŸ‘‘ Senior: Architect Blazor solutions with hosting model choice based on performance, scalability, and deployment constraints; mix hosting models if required.

πŸ“š Resources:

❓ How to implement API versioning in ASP.NET Core (URL, query, header)?

API versioning in ASP.NET Core is handled via the Microsoft.AspNetCore.Mvc.Versioning package.

You can expose different versions through:

  • URL path β€” /api/v1/products
  • Query string β€” /api/products?api-version=1.0
  • Header β€” using a custom header like x-api-version: 1.0

Example using URL versioning:

builder.Services.AddApiVersioning(o =>
{
    o.AssumeDefaultVersionWhenUnspecified = true;
    o.DefaultApiVersion = new ApiVersion(1, 0);
    o.ReportApiVersions = true;
    o.ApiVersionReader = new UrlSegmentApiVersionReader();
});

Controller:

[ApiController]
[Route("api/v{version:apiVersion}/products")]
[ApiVersion("1.0")]
public class ProductsV1Controller : ControllerBase
{
    [HttpGet]
    public IActionResult Get() => Ok("Version 1");
}

What .NET engineers should know:

  • πŸ‘Ό Junior: Know that API versioning allows you to change APIs without breaking old clients.
  • πŸŽ“ Middle: Understand how to configure versioning via URL, query, and headers, and when to use each.
  • πŸ‘‘ Senior: Design a long-term versioning strategy, manage multiple versions in production, and ensure backward compatibility.

πŸ“š Resources: aspnet-api-versioning

❓ How to handle file uploads and large requests in ASP.NET Core?

ASP.NET Core supports file uploads via IFormFile (in-memory) or streaming to disk/other storage to avoid memory spikes. For large requests, increase MaxRequestBodySize and use streaming to prevent loading the whole file into memory.

Example (streaming to disk):

[HttpPost("upload")]
public async Task<IActionResult> Upload(IFormFile file)
{
    var path = Path.Combine("uploads", file.FileName);
    using var stream = new FileStream(path, FileMode.Create);
    await file.CopyToAsync(stream);
    return Ok("Uploaded");
}

Startup adjustments:

app.Use(async (context, next) =>
{
    context.Features.Get<IHttpMaxRequestBodySizeFeature>().MaxRequestBodySize = 1073741824; // 1 GB
    await next();
});

You can also configure Kestrel’s limits or use [RequestSizeLimit] attribute on specific controllers or actions.

What .NET engineers should know:

  • πŸ‘Ό Junior: Know IFormFile exists and can be used to upload files.
  • πŸŽ“ Middle: Understand the difference between in-memory and streamed uploads, and how to change request size limits.
  • πŸ‘‘ Senior: Implement secure large-file handling with streaming, virus scanning, resumable uploads, and cloud storage integration.

πŸ“š Resources: Upload large files with streaming

❓ How does content negotiation work, and how do you customize response formatting?

Content negotiation in ASP.NET Core picks the best response format (JSON, XML, plain text) based on the request’s Accept header and available formatters. By default, it uses JSON with System.Text.Json. You can add or customize formatters (e.g., XML) in AddControllers().

Example (adding XML formatter):

builder.Services.AddControllers()
    .AddXmlSerializerFormatters();

Example (forcing a specific format in action):

[HttpGet("{id}")]
[Produces("application/xml")]
public ActionResult<Product> Get(int id) => new Product { Id = id, Name = "Book" };

What .NET engineers should know:

  • πŸ‘Ό Junior: Know that ASP.NET Core chooses the output format based on the Accept header.
  • πŸŽ“ Middle: Understand how to add/remove formatters and override content negotiation in controllers or actions.
  • πŸ‘‘ Senior: Implement custom formatters, negotiate based on custom logic, and ensure consistent formatting across APIs.

πŸ“š Resources:

❓ What is gRPC in ASP.NET Core, and when should you use it over REST?

gRPC is a high-performance, contract-based RPC framework using HTTP/2 and Protocol Buffers. It’s great for internal service-to-service calls because it’s faster, strongly typed, and supports streaming.

Compared to REST, it’s less human-readable but more efficient over the wire.

Example service definition (.proto):

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply);
}
message HelloRequest { string name = 1; }
message HelloReply { string message = 1; }

What .NET engineers should know:

  • πŸ‘Ό Junior: Know gRPC is an alternative to REST for calling services, using .proto contracts.
  • πŸŽ“ Middle: Understand gRPC advantages like speed, streaming, and strong typing, and that it requires HTTP/2.
  • πŸ‘‘ Senior: Decide when to use gRPC (e.g., internal microservices) vs REST, design efficient contracts, and handle backward compatibility in .proto files.

πŸ“š Resources: Overview for gRPC on .NET

Security and Identity

Security is built into ASP.NET Core with features like authentication, authorization, and identity management. This section explains how to protect your applications using modern security practices.

Security and Identity

❓ What are common approaches to authentication in ASP.NET Core (cookie-based auth vs JWT bearer tokens)?

ASP.NET Core supports several authentication approaches, each fitting different app types:

  • Cookie-based authentication – Server issues a cookie after login; used for MVC/Razor Pages. Stores user session state on the server or in an encrypted cookie.
  • JWT bearer tokens – Client gets a signed token (often via OAuth 2.0 / OpenID Connect) and sends it in the Authorization header; ideal for APIs and SPAs.
  • OpenID Connect / OAuth 2.0 – Delegates authentication to identity providers (Azure AD, Auth0, Okta, IdentityServer). Usually returns cookies (web apps) or JWT (APIs).
  • Windows Authentication – Uses existing Active Directory/Kerberos/NTLM credentials; common in intranet apps.
  • Custom schemes – For integrating legacy or external auth systems (e.g., API keys, HMAC signatures).
ApproachBest forWhere identity livesStateful?ProsConsTypical setup
Cookie-based authMVC/Razor Pages web appsServer issues an auth cookie (ticket) to the browserStateful (or ticket-style)Simple sign-in flows, built-in anti-CSRF, works great with server-rendered UIHarder for APIs/mobile; session storage/affinity or ticket mgmtAddAuthentication().AddCookie()
JWT Bearer tokensPublic APIs, SPAs, mobileToken carried by client in Authorization: BearerStatelessScales horizontally, no server session store, easy cross-clientLarger tokens, rotation/expiration needed, careful client storageAddAuthentication().AddJwtBearer()
OpenID Connect (OIDC)Enterprise SSO, external IdPsIdP issues cookie (web) or JWT (API)Depends on flowDelegated auth, SSO, policy/compliance offloadedMore moving parts, needs IdP availabilityAddOpenIdConnect() / OIDC with cookie or JWT
OAuth 2.0 (Auth for APIs)Third-party API access, SPAs/mobileAccess/refresh tokensStatelessBroad ecosystem, scopes/consentRefresh flow complexity, token revocation strategyAddJwtBearer() + OAuth flows
Windows AuthenticationIntranets on Windows/ADKerberos/NTLM via browserStateless per requestZero passwords, seamless SSO in domainLimited to Windows/AD environmentsUseAuthentication() with Windows auth
API key / HMAC (custom)Service-to-service callsHeader or signature per requestStatelessSimple, fast, no cookie overheadKey distribution/rotation, coarse identityCustom AuthenticationHandler

What .NET engineers should know about authentication in ASP.NET Core:

  • πŸ‘Ό Junior: Cookies are best for server-rendered sites, JWT for APIs. Know that OAuth/OpenID lets you log in with external providers.
  • πŸŽ“ Middle: Understand token lifetimes, refresh tokens, CSRF vs XSS risks, and when to prefer stateful vs stateless auth.
  • πŸ‘‘ Senior: Design auth flows with security, scalability, and compliance in mind. Choose schemes per boundary, implement token rotation, secure storage, and proper validation.

πŸ“š Resources:

❓ What is policy-based authorization in ASP.NET Core, and how is it different from role-based authorization?

Role-based authorization checks if a user belongs to a specific role (e.g., Admin). Policy-based authorization lets you define richer rules (policies) that can check claims, values, or custom logic β€” roles can be part of a policy, but policies aren’t limited to roles.

Example of policy-based auth:

// Program.cs
builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("Over18", policy =>
        policy.RequireClaim("Age", "18", "19", "20"));
});

// Controller
[Authorize(Policy = "Over18")]
public IActionResult AdultsOnly() => View();

What .NET engineers should know:

  • πŸ‘Ό Junior: Roles are simple β€œlabels” for users; policies can use roles but can also check claims or custom conditions.
  • πŸŽ“ Middle: Know how to define and register policies, combine multiple requirements, and use custom IAuthorizationHandler for complex checks.
  • πŸ‘‘ Senior: Design a consistent authorization model mixing policies and roles, centralize security rules, and make them testable and maintainable.

❓ What is CORS, and how to configure CORS in an ASP.NET Core application?

CORS (Cross-Origin Resource Sharing) is a browser security feature that blocks requests to your API from different origins unless explicitly allowed. It’s essential when your frontend and backend run on other domains, ports, or protocols.

Common CORS misconfigurations (and fixes)

  1. AllowAnyOrigin() + AllowCredentials()
    Browsers disallow this combination; it’s insecure.
    πŸ› οΈ Fix: use WithOrigins("https://app.example.com") .AllowCredentials() or keep AllowAnyOrigin() without credentials.
  2. Wrong middleware order/preflight not handled
    If UseCors() runs too late, preflight (OPTIONS) fails.
    πŸ› οΈ Fix: place it after routing and before auth/authorization:
  3. Origin strings don’t match exactly (scheme/host/port)
    http://localhost:3000 β‰  https://localhost:3000.
    πŸ› οΈ Fix: list precise origins (no trailing slash), e.g.
    .WithOrigins("https://myapp.com","http://localhost:5173").
  4. Using wildcards when sending cookies/SignalR with credentials
    Credentials require a specific origin and AllowCredentials().
    πŸ› οΈ Fix: enumerate trusted origins; avoid *.
  5. Forgetting custom headers/methods or exposed headers
    Preflight fails if you send non-simple headers (e.g., Authorization) or need to read custom response headers.
    πŸ› οΈ Fix: .AllowHeaders("Authorization","Content-Type") .AllowMethods("GET","POST") .WithExposedHeaders("Location","X-Total-Count").
  6. Overly permissive defaults in production
    AllowAnyHeader().AllowAnyMethod() everywhere widen your blast radius.
    πŸ› οΈ Fix: narrow per environment or endpoint (named policies, [EnableCors]).
  7. Subdomain/tenant patterns not covered
    You need multiple origins (e.g., https://*.example.com)β€”WithOrigins doesn’t support wildcards.
    πŸ› οΈ Fix: use SetIsOriginAllowed(origin => /* validate pattern or registry */) with care.
  8. Trailing slash in origins
    WithOrigins("https://myapp.com/") won’t match.
    πŸ› οΈ Fix: remove the slash.
  9. Missing cache for preflight
    High traffic gets hammered by OPTIONS.
    πŸ› οΈ Fix: .SetPreflightMaxAge(TimeSpan.FromHours(1)) (tune to your risk posture).
  10. Mixing global and attribute CORS in conflicting ways
    Route-level [DisableCors] Or differently named policies can surprise you.
    πŸ› οΈ Fix: prefer a single global policy, add exceptions explicitly.

A safe baseline (production-minded)

builder.Services.AddCors(opt =>
{
    opt.AddPolicy("Frontend", p => p
        .WithOrigins("https://app.example.com","https://admin.example.com","http://localhost:5173")
        .WithMethods("GET","POST","PUT","DELETE")
        .WithHeaders("Authorization","Content-Type")
        .WithExposedHeaders("Location","X-Total-Count")
        .AllowCredentials()
        .SetPreflightMaxAge(TimeSpan.FromMinutes(30)));
});

What .NET engineers should know:

  • πŸ‘Ό Junior: Knows that CORS is a browser check; origins must match exactly; don’t mix * with credentials.
  • πŸŽ“ Middle: Aware that order matters; able to configure headers/methods/exposed headers; per-env policies; preflight caching.
  • πŸ‘‘ Senior: Treat CORS as part of your threat model; validate origins programmatically for multi-tenant; monitor preflight rates; least-privilege configs by endpoint.

πŸ“š Resources:

❓ What is ASP.NET Core Identity, and when should you use it?

ASP.NET Core Identity is a membership system for handling authentication, authorization, and user management (registration, login, roles, claims). It’s useful when you need built-in, customizable auth with minimal setup, without building everything from scratch.

Example (setup in Program.cs):

builder.Services.AddDbContext<AppDbContext>(...);
builder.Services.AddIdentity<IdentityUser, IdentityRole>()
    .AddEntityFrameworkStores<AppDbContext>();

What .NET engineers should know:

  • πŸ‘Ό Junior: Know Identity provides ready-to-use authentication and user management features.
  • πŸŽ“ Middle: Understand how to customize Identity (tables, password rules, roles) and integrate with external logins like Google or Microsoft.
  • πŸ‘‘ Senior: Decide when to use Identity vs custom auth, secure it with best practices, and integrate it into microservices or external identity providers.

πŸ“š Resources:

❓ What is the Data Protection API, and how does ASP.NET Core handle key management?

The Data Protection API in ASP.NET Core provides cryptographic services for encrypting and decrypting data (e.g., auth cookies, CSRF tokens). It uses a key ring to store and rotate encryption keys automatically. By default, keys are stored locally on disk, but you can persist them in Redis, Azure Key Vault, or a database for multi-instance apps.

Example (store keys in Redis):

builder.Services.AddDataProtection()
    .PersistKeysToStackExchangeRedis(ConnectionMultiplexer.Connect("localhost"), "DataProtection-Keys");

What .NET engineers should know:

  • πŸ‘Ό Junior: Know it encrypts sensitive data like cookies in ASP.NET Core.
  • πŸŽ“ Middle: Understand key lifetime, storage options, and why shared storage is needed in load-balanced environments.
  • πŸ‘‘ Senior: Design secure key storage strategies, integrate with cloud key vaults, and manage key rotation policies.

πŸ“š Resources:

❓ How to implement CSRF protection in ASP.NET Core?

ASP.NET Core automatically adds CSRF (anti-forgery) protection to Razor Pages and MVC by validating an anti-forgery token sent with each form POST. You generate the token with @Html.AntiForgeryToken() and validate it with [ValidateAntiForgeryToken] on actions. For APIs, use the IAntiforgery service if needed.

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Save(DataModel model) => Ok();

What .NET engineers should know:

  • πŸ‘Ό Junior: Know that CSRF is an attack where malicious sites trick users into making unwanted requests.
  • πŸŽ“ Middle: Understand how anti-forgery tokens work and when you need to apply them (non-GET requests from browsers).
  • πŸ‘‘ Senior: Implement CSRF protection for complex cases (SPAs, APIs), combine with same-site cookies, and balance security with usability.

πŸ“š Resources: Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core

❓ What is the SameSite cookie setting, and why does it matter for cross-domain scenarios?

The SameSite cookie setting controls whether a browser sends cookies with cross-site requests.

  • Strict only sent for same-site navigation
  • Lax  sent for top-level GET requests (default in many cases)
  • None  sent for all requests, but requires Secure flag

It matters because browsers block cross-site cookies by default, affecting scenarios like third-party logins, embedded iframes, or APIs on a different domain.

Example:

app.UseCookiePolicy(new CookiePolicyOptions
{
    MinimumSameSitePolicy = SameSiteMode.None,
    Secure = CookieSecurePolicy.Always
});

What .NET engineers should know:

  • πŸ‘Ό Junior: Know if cookies are sent in cross-site requests.
  • πŸŽ“ Middle: Understand the difference between Strict, Lax, and None, and their security trade-offs.
  • πŸ‘‘ Senior: Configure cookies correctly for SSO, third-party integrations, and ensure compliance with modern browser rules.

πŸ“š Resources: Work with SameSite cookies in ASP.NET Core

❓ What OWASP-recommended security features should be enabled in ASP.NET Core?

ASP.NET Core includes many built-in features that align with OWASP Top 10 recommendations:

  • HTTPS enforcement β€” app.UseHttpsRedirection()
  • HSTS β€” app.UseHsts() to force HTTPS for future requests
  • Secure cookies β€” set Secure, HttpOnly, and SameSite
  • Data Protection API β€” for encrypting sensitive data
  • Anti-forgery tokens β€” to prevent CSRF
  • Input validation & model binding β€” prevents injection attacks
  • Authentication & Authorization middleware β€” role/claims-based
  • Rate limiting & throttling β€” to prevent abuse (e.g., AspNetCoreRateLimit package)

What .NET engineers should know:

  • πŸ‘Ό Junior: Know the framework has built-in security middleware like HTTPS redirection, cookies, and CSRF protection.
  • πŸŽ“ Middle: Understand how to configure these features and why each is needed.
  • πŸ‘‘ Senior: Map OWASP Top 10 risks to ASP.NET Core protections, customize security policies and integrate security scanning into CI/CD.

πŸ“š Resources:

Performance, Monitoring & Background Processing

This section explores optimization techniques, monitoring tools, and strategies for running background tasks.

Performance, Monitoring & Background Processing

❓ What logging capabilities does ASP.NET Core provide, and how do you configure logging?

ASP.NET Core ships a unified logging abstraction (ILogger) with structured logging, scopes, filtering, and pluggable providers (Console, Debug, EventSource/EventLog, Azure App Insights, Serilog, etc.). You configure it via code and/or appsettings.json; for hot paths, use LoggerMessage/source-generated logging for fewer allocations and better throughput.

Program.cs

// Program.cs (minimal, production-friendly defaults)
var builder = WebApplication.CreateBuilder(args);

// 1) Configure providers &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp; filters
builder.Logging.ClearProviders();                    // start clean, then add what you need
builder.Logging.AddConsole();
builder.Logging.AddDebug();
// builder.Logging.AddEventSourceLogger();           // optional
// builder.Logging.AddEventLog();                    // Windows only
builder.Logging.AddFilter("Microsoft.AspNetCore", LogLevel.Warning); // reduce noise
builder.Logging.AddFilter("MyApp", LogLevel.Information);

// 2) Read levels from appsettings.json (logging section shown below is applied by default)
builder.Host.ConfigureLogging(logging => logging.AddConfiguration(builder.Configuration.GetSection("Logging")));

var app = builder.Build();

// 3) Use logging with scopes and structured data
app.MapGet("/health", (ILoggerFactory lf) =>
{
    var logger = lf.CreateLogger("MyApp.Health");
    using (logger.BeginScope(new Dictionary<string, object?> { ["correlationId"] = Guid.NewGuid() }))
    {
        logger.LogInformation("Health check OK at {utc}", DateTime.UtcNow);
    }
    return Results.Ok(new { status = "ok" });
});

app.Run();

appsettings.json 

// appsettings.json (typical logging section)
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  }
}

What .NET engineers should know:

  • πŸ‘Ό Junior: Use ILogger for structured logs; pick providers (Console, Debug, etc.); set levels in appsettings.json.
  • πŸŽ“ Middle: Filter by category, use scopes for correlation IDs, and tune noise from framework categories (e.g., Microsoft.AspNetCore  Warning).
  • πŸ‘‘ Senior: For hot paths, prefer LoggerMessage/source-generated logs; centralize sinks (e.g., App Insights/Serilog), define retention/PII policy, and standardize log schema across services.

πŸ“š Resources:

❓ How do you implement caching in ASP.NET Core, and what are the different types of caching available (in-memory, distributed, response/output caching)?

Caching is a key performance feature, and ASP.NET Core supports several kinds:

  • In-Memory Caching: This is caching data in the memory of the web server (for a single node).
// In-memory caching (fast, per-process)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache();
var app = builder.Build();

app.MapGet("/weather", async (IMemoryCache cache) =>
{
    return await cache.GetOrCreateAsync("wx:today", entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
        entry.SlidingExpiration = TimeSpan.FromMinutes(2);
        return Task.FromResult(new { temp = 23 });
    });
});

app.Run();
  • Distributed Caching: This is for scenarios where you have multiple servers or want the cache persisted externally (e.g., Redis or SQL Server).
// Distributed caching (shared across instances; e.g., Redis)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddStackExchangeRedisCache(o => o.Configuration = "localhost:6379");
var app = builder.Build();

app.MapGet("/profile/{id}", async (string id, IDistributedCache cache) =>
{
    var key = $"profile:{id}";
    var json = await cache.GetStringAsync(key);
    if (json is null)
    {
        var profile = new { id, name = "Ada" };             // load from DB in real life
        json = System.Text.Json.JsonSerializer.Serialize(profile);
        await cache.SetStringAsync(key, json, new DistributedCacheEntryOptions
        { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15) });
    }
    return Results.Text(json, "application/json");
});

app.Run();
  • Response Caching (HTTP caching): This is about caching at the HTTP response level, mostly to leverage browser/proxy caches.
// Response Caching (HTTP 1.1 semantics; respects Cache-Control)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddResponseCaching();
var app = builder.Build();

app.UseResponseCaching();
app.MapGet("/news", (HttpContext ctx) =>
{
    ctx.Response.GetTypedHeaders().CacheControl = new Microsoft.Net.Http.Headers.CacheControlHeaderValue
    { Public = true, MaxAge = TimeSpan.FromSeconds(30) };
    return Results.Text("cached for 30s");
});

app.Run();
  • Output Caching (new in .NET 7): This is a newer server-side output cache that can cache the actual output of controllers or pages in memory (separately from the above memory cache) and serve it directly on subsequent requests, bypassing executing the controller again. It’s like the old OutputCache in ASP.NET or like a built-in reverse-proxy cache. The difference from Response Caching is that Output Caching does not rely purely on HTTP cache headers – it’s configured in the app to decide what to cache.
// Output Caching (.NET 7+; smarter server-side response cache with policies)
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOutputCache();  // AddOutputCache(options => options.AddPolicy("anon", p => p.Expire(TimeSpan.FromMinutes(1))))
var app = builder.Build();

app.UseOutputCache();
app.MapGet("/products", () => Results.Ok(new[] { "A", "B" })).CacheOutput(p => p
    .Expire(TimeSpan.FromSeconds(60))
    .SetVaryByQuery("page","size")   // different cache entries per query
);
app.Run();

So, in practice:

If you want to cache data (like results of computations or DB calls), use MemoryCache or DistributedCache depending on your deployment. If you're going to cache entire responses for performance, use ResponseCache headers for client-side/proxy caching and/or the Output Caching middleware for server-side. They can be complementary: e.g., output cache your pages on the server for 30 sec, also allow the client to cache for that duration.

What .NET engineers should know about cache:

  • πŸ‘Ό Junior: Understand that in-memory is fastest but per-instance; distributed works across servers; response/output caching avoids recomputing complete responses.
  • πŸŽ“ Middle: Pick keys carefully, set absolute/sliding expirations, and invalidate on data changes. Use distributed cache for multi-node apps; prefer OutputCache over ResponseCaching for richer policies.
  • πŸ‘‘ Senior: Establish cache hierarchy (output > distributed > in-memory), define eviction/TTL strategy per data class, add cache-busting on writes, measure hit ratio, and protect against stampedes (locks, pre-warm, background refresh).

πŸ“š Resources:

❓ What is the rate-limiting middleware in ASP.NET Core, and why is it useful?

Rate-limiting middleware in ASP.NET Core controls how many requests a client can make in a given time window, protecting APIs from abuse, preventing resource exhaustion, and ensuring fair usage across clients. Introduced in .NET 7, it supports fixed window, sliding window, token bucket, and concurrency limiting. .NET 8 enhanced this with partitioned rate limiting (per-client limits, e.g., by IP or user) and global rate limiting policies for simplified configuration. For multi-instance apps (e.g., in Kubernetes), rate-limiting state can be shared via distributed stores like Redis, ensuring consistency across nodes.

Example:

// Program.cs
using System.Threading.RateLimiting;
using Microsoft.AspNetCore.RateLimiting;
using Microsoft.Extensions.Caching.StackExchangeRedis;

var builder = WebApplication.CreateBuilder(args);

// Add Redis for distributed rate limiting
builder.Services.AddStackExchangeRedisCache(o => o.Configuration = "localhost:6379");

// Configure rate limiting
builder.Services.AddRateLimiter(options =>
{
    // Global limiter (applies to all endpoints unless overridden)
    options.GlobalLimiter = PartitionedRateLimiter.CreateChained(
        PartitionedRateLimiter.Create<HttpContext, string>(resource =>
        {
            return new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
            {
                PermitLimit = 100, // Global limit: 100 requests per minute
                Window = TimeSpan.FromMinutes(1),
                QueueLimit = 0,
                AutoReplenishment = true
            });
        })
    );

    // Endpoint-specific partitioned limiter (per IP)
    options.AddFixedWindowLimiter("fixed-per-ip", new FixedWindowRateLimiterOptions
    {
        PermitLimit = 5, // 5 requests per 10 seconds per IP
        Window = TimeSpan.FromSeconds(10),
        QueueLimit = 0,
        AutoReplenishment = true
    }).WithPartitioner(ctx => ctx.Connection.RemoteIpAddress?.ToString() ?? "unknown");

    // Customize 429 response
    options.OnRejected = async (context, cancellationToken) =>
    {
        context.HttpContext.Response.StatusCode = StatusCodes.Status429TooManyRequests;
        var limiter = context.Lease.GetRateLimitMetadata();
        if (limiter?.RetryAfter != null)
        {
            context.HttpContext.Response.Headers.RetryAfter = ((int)limiter.RetryAfter.Value.TotalSeconds).ToString();
        }
        await context.HttpContext.Response.WriteAsync(
            $"Too many requests. Retry after {limiter?.RetryAfter?.TotalSeconds ?? 10}s", cancellationToken);
    };
});

var app = builder.Build();

app.UseRateLimiter();

// Apply rate limiting to endpoint
app.MapGet("/data", () => "OK").RequireRateLimiting("fixed-per-ip");

app.Run();

Common Rate Limiter Types in ASP.NET Core:

Limiter typeHow it worksBest forProsCons
Fixed windowResets counters at fixed intervals (e.g., 5 requests per 10 seconds).Simple APIs, predictable bursts.Easy to reason about, low overhead.Burst allowed at window edges (boundary problem).
Sliding windowTracks requests in rolling intervals, smoothing bursts.APIs needing more even traffic.Fairer distribution, avoids edge bursts.Slightly higher CPU/memory overhead.
Token bucketTokens added at a steady rate; requests consume tokens.Services needing burst capacity but steady refill.Flexible; supports bursts with controlled average rate.More complex tuning.
ConcurrencyLimits number of concurrent executions.Resource-heavy ops (file processing, DB calls).Protects from overloading resource pools.Doesn’t limit total request rate over time.
PartitionedApplies limits per key (e.g., IP, user ID).Multi-user APIs, fairness across clients.Granular control, scalable with distributed stores.Granular control, scalable with distributed stores.Requires careful key design, storage setup

What .NET engineers should know:

  • πŸ‘Ό Junior: Rate limiting controls how many requests a user can send in a period; prevents abuse.
  • πŸŽ“ Middle: Know limiter types (fixed/sliding window, token bucket, concurrency), set limits per route/policy, and return proper status codes (429).
  • πŸ‘‘ Senior: Design limits per API tier, use partitioned keys (IP, user, API key), monitor and tune thresholds, and integrate with distributed stores for multi-instance apps.

πŸ“š Resources: Rate limiting middleware in ASP.NET Core

❓ How would you implement health checks in ASP.NET Core, and what are they used for?

Health checks expose simple endpoints that report app and dependency health. They’re used by load balancers and orchestrators (Kubernetes liveness/readiness probes) to route traffic, restart stuck instances, and fail fast when a dependency is down.

// Program.cs β€” register checks with tags for liveness/readiness
using Microsoft.AspNetCore.Diagnostics.HealthChecks;
using Microsoft.Extensions.Diagnostics.HealthChecks;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddHealthChecks()
    .AddCheck("self", () => HealthCheckResult.Healthy(), tags: new[] { "live" })
    .AddSqlServer(builder.Configuration.GetConnectionString("Sql"), tags: new[] { "ready" })
    .AddRedis(builder.Configuration.GetConnectionString("Redis"), tags: new[] { "ready" });

var app = builder.Build();

// Map endpoints (separate for Kubernetes probes)
app.MapHealthChecks("/health/live", new HealthCheckOptions {
    Predicate = r => r.Tags.Contains("live"),
    ResponseWriter = HealthCheckResponseWriters.WriteMinimalJson // see custom writer below
});
app.MapHealthChecks("/health/ready", new HealthCheckOptions {
    Predicate = r => r.Tags.Contains("ready"),
    ResultStatusCodes =
    {
        [HealthStatus.Healthy] = StatusCodes.Status200OK,
        [HealthStatus.Degraded] = StatusCodes.Status200OK,
        [HealthStatus.Unhealthy] = StatusCodes.Status503ServiceUnavailable
    },
    ResponseWriter = HealthCheckResponseWriters.WriteMinimalJson
});

app.Run();

// Minimal JSON writer (keeps payload small for probes)
static class HealthCheckResponseWriters
{
    public static Task WriteMinimalJson(HttpContext ctx, HealthReport r)
    {
        ctx.Response.ContentType = "application/json";
        var summary = new {
            status = r.Status.ToString(),
            duration = r.TotalDuration.TotalMilliseconds,
            entries = r.Entries.ToDictionary(
                kv => kv.Key,
                kv => new { status = kv.Value.Status.ToString(), duration = kv.Value.Duration.TotalMilliseconds })
        };
        return ctx.Response.WriteAsJsonAsync(summary);
    }
}

Custom dependency check:

// Custom dependency check
using Microsoft.Extensions.Diagnostics.HealthChecks;

public sealed class S3PingHealthCheck : IHealthCheck
{
    public async Task<HealthCheckResult> CheckHealthAsync(
        HealthCheckContext context, CancellationToken token = default)
    {
        try
        {
            // ping your service/dependency here
            await Task.Delay(10, token);
            return HealthCheckResult.Healthy();
        }
        catch (Exception ex)
        {
            return HealthCheckResult.Unhealthy("S3 unreachable", ex);
        }
    }
}

What .NET engineers should know:

  • πŸ‘Ό Junior: Health checks are endpoints for liveness/readiness; add with AddHealthChecks() and map with MapHealthChecks.
  • πŸŽ“ Middle: Tag checks and split endpoints; return 503 for unhealthy; keep payloads tiny; avoid heavy work in checks.
  • πŸ‘‘ Senior: Make checks idempotent and fast (<100–300 ms), degrade instead of fail where possible, guard against cascades, and align probe timings (initialDelay/period/timeout) with real startup/dep latencies.

πŸ“š Resources:

❓ Why is using HttpClientFactory recommended for making HTTP calls in ASP.NET Core?

IHttpClientFactory manages HttpClient lifetimes and configuration. Creating HttpClient with new can cause socket exhaustion (too many ephemeral TCP ports in TIME_WAIT) or DNS stale entries because connections aren’t appropriately reused. HttpClientFactory pools HttpMessageHandler instances, handles DNS refresh, supports named/typed clients, and integrates Polly for retries, timeouts, and circuit breakers.

// Program.cs β€” register a named and typed client
builder.Services.AddHttpClient("github", c =>
{
    c.BaseAddress = new Uri("https://api.github.com/");
    c.DefaultRequestHeaders.UserAgent.ParseAdd("MyApp");
});

builder.Services.AddHttpClient<WeatherClient>(c =>
{
    c.BaseAddress = new Uri("https://api.weather.com/");
});

// Typed client
public class WeatherClient
{
    private readonly HttpClient _http;
    public WeatherClient(HttpClient http) => _http = http;
    public Task<string> GetForecastAsync() => _http.GetStringAsync("/forecast");
}

What .NET engineers should know:

  • πŸ‘Ό Junior: HttpClientFactory avoids socket exhaustion and makes HttpClient reuse safe.
  • πŸŽ“ Middle: Use named clients for per-service configs; typed clients for DI; add Polly handlers for retries/timeouts.
  • πŸ‘‘ Senior: Centralize outbound HTTP configs, track metrics, set handler lifetimes for DNS refresh, and secure outbound calls with auth tokens and resilience policies.

πŸ“š Resources:

❓ How can you run background tasks or scheduled jobs in ASP.NET Core?

ASP.NET Core offers two main paths: built-in hosted services for in-process work, and external schedulers for durable, cron-style jobs. Use BackgroundService/IHostedService for lightweight, app-lifecycle tasks; use Quartz.NET or Hangfire (or platform schedulers like Azure Functions Timer/Kubernetes CronJob) for persistent, retryable scheduling.

Periodic background task with BackgroundService + PeriodicTimer (.NET 6+)

public sealed class EmailDigestWorker : BackgroundService
{
    private readonly ILogger<EmailDigestWorker> _log;
    public EmailDigestWorker(ILogger<EmailDigestWorker> log) => _log = log;

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        var timer = new PeriodicTimer(TimeSpan.FromMinutes(5));
        try
        {
            while (await timer.WaitForNextTickAsync(ct))
            {
                // do work; honor ct; keep it short &amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp;amp; resilient
                _log.LogInformation("Sending email digests at {ts}", DateTimeOffset.UtcNow);
            }
        }
        catch (OperationCanceledException) { }
    }
}

// Program.cs
builder.Services.AddHostedService<EmailDigestWorker>();

Queued background work (producer/consumer) using Channel<T>


public interface IBackgroundQueue<T> { ValueTask EnqueueAsync(T item, CancellationToken ct); IAsyncEnumerable<T> DequeueAsync(CancellationToken ct); }

public sealed class BackgroundQueue<T> : IBackgroundQueue<T>
{
    private readonly Channel<T> _ch = Channel.CreateUnbounded<T>();
    public ValueTask EnqueueAsync(T item, CancellationToken ct) => _ch.Writer.WriteAsync(item, ct);
    public IAsyncEnumerable<T> DequeueAsync(CancellationToken ct) => _ch.Reader.ReadAllAsync(ct);
}

public sealed class QueueWorker : BackgroundService
{
    private readonly IBackgroundQueue<string> _q; private readonly ILogger<QueueWorker> _log;
    public QueueWorker(IBackgroundQueue<string> q, ILogger<QueueWorker> log) { _q = q; _log = log; }

    protected override async Task ExecuteAsync(CancellationToken ct)
    {
        await foreach (var job in _q.DequeueAsync(ct))
        {
            _log.LogInformation("Processing job {job}", job);
            // process…
        }
    }
}

// Program.cs
builder.Services.AddSingleton<IBackgroundQueue<string>, BackgroundQueue<string>>();
builder.Services.AddHostedService<QueueWorker>();

What .NET engineers should know:

  • πŸ‘Ό Junior: Use BackgroundService for periodic tasks; map intervals with PeriodicTimer; don’t block threads; honor cancellation tokens.
  • πŸŽ“ Middle: Can add retries/timeouts; isolate I/O errors; persist state if work must survive restarts.
  • πŸ‘‘ Senior: For production-ready scheduling, use Quartz.NET/Hangfire or platform schedulers (Azure Functions Timer, Kubernetes CronJob). Ensure idempotency, observability (metrics/traces), backoff strategies, and graceful shutdown.

πŸ“š Resources:

❓ What is SignalR in ASP.NET Core, and what are its typical use cases?

SignalR is a real-time communication library that lets server and client push messages instantly without polling. It uses WebSockets when possible, falling back to Server-Sent Events or Long Polling. ASP.NET Core supports typed hubs, automatic reconnect, scaling with backplanes, and streaming.

Typical use cases:

  • Live dashboards (metrics, IoT readings, analytics)
  • Chat/messaging apps
  • Notifications (e.g., order status updates)
  • Collaborative apps (whiteboards, document editing)
  • Multiplayer games

Example:

// Hub
public class ChatHub : Hub
{
    public Task SendMessage(string user, string message) =>
        Clients.All.SendAsync("ReceiveMessage", user, message);
}

// Program.cs
builder.Services.AddSignalR();
var app = builder.Build();
app.MapHub<ChatHub>("/chat");
app.Run();

What .NET engineers should know about SignalR:

  • πŸ‘Ό Junior: SignalR enables real-time server-to-client messaging; works over WebSockets or fallbacks.
  • πŸŽ“ Middle: Understand hubs, groups, scaling with Redis/Azure SignalR, and client SDKs (JavaScript, .NET, Java, etc.).
  • πŸ‘‘ Senior: Architect for scale (backplanes), manage connection lifecycles, handle reconnection logic, secure hubs with auth/authorization, and optimize message payloads.

πŸ“š Resources:

❓ What is the OutputCache middleware (.NET 7+), and how is it different from ResponseCaching?

OutputCache (introduced in .NET 7) It is the next-generation server-side response caching middleware. It stores generated responses in memory (or other stores in the future) and serves them directly on subsequent requests without re-executing the endpoint. Unlike the older ResponseCaching middleware, it:

  • Works regardless of HTTP cache headers (you control caching in code).
  • Supports rich policies (vary by query, route, headers, custom logic).
  • Integrates cleanly with minimal APIs, MVC, and Razor Pages.
  • Handles POST (and other non-GET) caching if you allow it.
  • Has more predictable eviction and expiration options.
OutputCache sits vs ResponseCaching
OutputCache sits vs ResponseCaching

Example:

// Program.cs β€” basic OutputCache
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOutputCache();
var app = builder.Build();

app.UseOutputCache();

// Minimal API with 60s cache
app.MapGet("/products", () => new[] { "A", "B", "C" })
   .CacheOutput(p => p.Expire(TimeSpan.FromSeconds(60))
                      .SetVaryByQuery("page", "size"));

app.Run();

Key differences β€” OutputCache vs ResponseCaching:

FeatureOutputCache (.NET 7+)ResponseCaching
TriggerControlled in code (.CacheOutput(...))Driven by HTTP headers (Cache-Control, Vary)
ScopeMinimal APIs, MVC, Razor PagesMostly MVC and Razor Pages
VaryingQuery, route, headers, custom keysLimited to Vary header
POST supportYes (if explicitly enabled)No (GET/HEAD only)
ExpirationPer-policy, absolute, slidingBased on cache headers
EvictionExplicit per policy / programmaticAutomatic when headers expire
CustomizationHigh β€” policies, predicates, programmaticLow β€” header-based only
Recommended for new appsβœ… Yes❌ Legacy, only if minimal caching needed

What .NET engineers should know:

  • πŸ‘Ό Junior: Understand the difference between OutputCache and ResponseCaching.
  • πŸŽ“ Middle: Use OutputCache for per-endpoint policies; ResponseCaching only if you want to honor browser/HTTP cache headers directly.
  • πŸ‘‘ Senior: Design cache keys carefully, combine with distributed caches for multi-instance scale-out, invalidate cache on data changes, and measure hit ratio to tune TTLs.

πŸ“š Resources:

❓ How to enable response compression in ASP.NET Core?

ASP.NET Core supports Gzip, Brotli, and custom compression providers to reduce payload size and improve performance. You add the compression middleware and configure supported MIME types.

Example:

builder.Services.AddResponseCompression(options =>
{
    options.EnableForHttps = true;
    options.Providers.Add<GzipCompressionProvider>();
    options.MimeTypes = ResponseCompressionDefaults.MimeTypes.Concat(new[] { "application/json" });
});

builder.Services.Configure<GzipCompressionProviderOptions>(o =>
    o.Level = CompressionLevel.Fastest);

app.UseResponseCompression();

What .NET engineers should know:

  • πŸ‘Ό Junior: Know compression reduces response size to speed up loading.
  • πŸŽ“ Middle: Understand how to enable Gzip/Brotli and choose MIME types to compress.
  • πŸ‘‘ Senior: Optimize compression settings, balance CPU cost vs performance gain, and monitor impact in production.

πŸ“š Resources: Response compression in ASP.NET Core

❓ How to integrate OpenTelemetry for distributed tracing and metrics in ASP.NET Core?

OpenTelemetry lets you collect traces, metrics, and logs across distributed systems. You install OpenTelemetry.Extensions.Hosting and configure tracing/metrics exporters (Jaeger, Zipkin, OTLP).

Example:

builder.Services.AddOpenTelemetry()
    .WithTracing(t =>
    {
        t.AddAspNetCoreInstrumentation()
         .AddHttpClientInstrumentation()
         .AddEntityFrameworkCoreInstrumentation()
         .AddOtlpExporter();
    })
    .WithMetrics(m =>
    {
        m.AddAspNetCoreInstrumentation()
         .AddRuntimeInstrumentation()
         .AddOtlpExporter();
    });

What .NET engineers should know:

  • πŸ‘Ό Junior: Know how OpenTelemetry collects and sends app performance data.
  • πŸŽ“ Middle: Understand how to instrument ASP.NET Core with tracing and metrics, and connect to a backend like Jaeger or Grafana.
  • πŸ‘‘ Senior: Design complete observability stacks, optimize sampling, correlate traces across microservices, and use telemetry for performance tuning.

πŸ“š Resources: OpenTelemetry for .NET

❓ How to scale SignalR with Redis backplane or Azure SignalR Service

SignalR works typically in a single server’s memory, so in multi-server setups, messages won’t reach all clients.
You solve this with:

  • Redis backplane β€” servers publish/subscribe messages via Redis.
  • Azure SignalR Service β€” fully managed scale-out without managing Redis.

Example (Redis backplane):

builder.Services.AddSignalR()
    .AddStackExchangeRedis("localhost:6379");

Example (Azure SignalR Service):

builder.Services.AddSignalR()
    .AddAzureSignalR("ConnectionString");

app.UseAzureSignalR(routes =>
{
    routes.MapHub<ChatHub>("/chat");
});

What .NET engineers should know:

  • πŸ‘Ό Junior: Know SignalR needs a backplane or service to work across multiple servers.
  • πŸŽ“ Middle: Understand Redis and Azure SignalR options, and when each is appropriate.
  • πŸ‘‘ Senior: Design scalable real-time systems, manage connection limits, and optimize performance for thousands of concurrent clients.

πŸ“š Resources:

❓ How to add Polly resilience policies (retry, circuit breaker) with HttpClientFactory?

ASP.NET Core integrates IHttpClientFactory With Polly, you can add retries, circuit breakers, and more directly to your HTTP clients.

Example:

builder.Services.AddHttpClient("MyClient")
    .AddTransientHttpErrorPolicy(p => 
        p.WaitAndRetryAsync(3, attempt => TimeSpan.FromSeconds(Math.Pow(2, attempt))))
    .AddTransientHttpErrorPolicy(p =>
        p.CircuitBreakerAsync(5, TimeSpan.FromSeconds(30)));

What .NET engineers should know:

  • πŸ‘Ό Junior: Know Polly helps handle transient HTTP failures with retries.
  • πŸŽ“ Middle: Understand how to configure retry and circuit breaker policies with HttpClientFactory.
  • πŸ‘‘ Senior: Design advanced resilience strategies with fallback, timeout, bulkhead isolation, and monitor their effectiveness in production.

πŸ“š Resources: Use Polly-based handlers

❓ How to benchmark and profile ASP.NET Core apps?

Its exist two common ways to measure benchmarks for .NET projects: 

  • dotnet-counters β€” real-time performance metrics (CPU, GC, requests/sec) for a running app.
  • BenchmarkDotNet β€” a micro-benchmarking framework for measuring the performance of specific code paths.

Example (dotnet-counters):

dotnet tool install --global dotnet-counters
dotnet-counters monitor --process-id 1234

Example (BenchmarkDotNet):

[MemoryDiagnoser]
public class MyBenchmarks
{
    [Benchmark]
    public void TestMethod() => Thread.Sleep(10);
}

BenchmarkRunner.Run<MyBenchmarks>();

What .NET engineers should know:

  • πŸ‘Ό Junior: Know you can measure performance with built-in and third-party tools.
  • πŸŽ“ Middle: Understand when to use runtime profiling (dotnet-counters) vs code benchmarking (BenchmarkDotNet).
  • πŸ‘‘ Senior: Profile production issues, analyze GC and thread usage, and design benchmarks that reflect real-world workloads.

πŸ“š Resources:

Misc

Here, we cover how to ensure your application is reliable, portable, and ready for global users.

Testing, Deployment & Globalization

❓ How to test ASP.NET Core apps with WebApplicationFactory for integration testing?

WebApplicationFactory<TEntryPoint> from Microsoft.AspNetCore.Mvc.Testing spins up a real in-memory server so you can test endpoints without deploying the app. It’s often used with HttpClient for end-to-end integration tests.

public class ProductsTests : IClassFixture<WebApplicationFactory<Program>>
{
    private readonly HttpClient _client;
    public ProductsTests(WebApplicationFactory<Program> factory)
    {
        _client = factory.CreateClient();
    }

    [Fact]
    public async Task GetProducts_ReturnsOk()
    {
        var response = await _client.GetAsync("/api/products");
        response.EnsureSuccessStatusCode();
    }
}

What .NET engineers should know:

  • πŸ‘Ό Junior: Know you can run real API tests without deploying using WebApplicationFactory.
  • πŸŽ“ Middle: Understand how to override configurations, mock dependencies, and reset state between tests.
  • πŸ‘‘ Senior: Build complex integration test setups with multiple services, databases, and authentication flows.

πŸ“š Resources: Integration tests in ASP.NET Core

❓ What are the best practices for deploying ASP.NET Core apps cross-platform?

ASP.NET Core runs on Windows, Linux, and macOS, so deployment is flexible.
Key practices:

  • Use a self-contained deployment if you don’t want to rely on the target machine’s .NET runtime.
  • Run in containers (Docker) for portability and consistent environments.
  • Reverse proxy with Nginx or Apache on Linux for SSL termination and load balancing.
  • Environment-specific configs via appsettings.{Environment}.json or environment variables.
  • CI/CD pipelines for automated build, test, and deployment.

Example (publish self-contained for Linux):

dotnet publish -c Release -r linux-x64 --self-contained true

What .NET engineers should know:

  • πŸ‘Ό Junior: Know .NET apps can run on multiple OSes without code changes.
  • πŸŽ“ Middle: Understand deployment options (framework-dependent vs self-contained, containers, reverse proxy).
  • πŸ‘‘ Senior: Design cross-platform deployment pipelines, automate scaling, and optimize for cloud-native environments.

❓ How to make ASP.NET Core apps scalable and cloud-ready?

For scalability in the cloud, design your app to handle multiple instances without depending on local state.
Key practices:

  • Statelessness β€” store session/data in a distributed cache (Redis, SQL), not in memory.
  • Health checks β€” expose /health endpoints for load balancers and orchestrators.
  • Distributed cache β€” share cache across all instances for consistency.
  • Horizontal scaling β€” run multiple app instances behind a load balancer.

Example (health checks + Redis cache):

builder.Services.AddHealthChecks()
    .AddRedis("localhost:6379");

builder.Services.AddStackExchangeRedisCache(o =>
    o.Configuration = "localhost:6379");

app.MapHealthChecks("/health");

What .NET engineers should know:

  • πŸ‘Ό Junior: Know that cloud apps should not store important data in local memory.
  • πŸŽ“ Middle: Understand how health checks and distributed caching help scaling.
  • πŸ‘‘ Senior: Architect fully stateless services, implement resilience patterns and integrate with Kubernetes or cloud PaaS scaling features.

πŸ“š Resources: Health checks in ASP.NET Core

❓  How to implement localization and globalization in ASP.NET Core (IStringLocalizer)?

Localization lets your app display content in multiple languages, while globalization ensures it works with different cultures (dates, numbers, currencies).
IStringLocalizer retrieves localized strings from .resx files without hardcoding them.

Example:

builder.Services.AddLocalization(o => o.ResourcesPath = "Resources");

app.UseRequestLocalization(new RequestLocalizationOptions
{
    SupportedCultures = new[] { new CultureInfo("en"), new CultureInfo("fr") },
    SupportedUICultures = new[] { new CultureInfo("en"), new CultureInfo("fr") },
    DefaultRequestCulture = new RequestCulture("en")
});

public class HomeController : Controller
{
    private readonly IStringLocalizer<HomeController> _localizer;
    public HomeController(IStringLocalizer<HomeController> localizer) => _localizer = localizer;

    public IActionResult Index() => Content(_localizer["Hello"]);
}

What .NET engineers should know:

  • πŸ‘Ό Junior: Know localization changes displayed text based on the user's language.
  • πŸŽ“ Middle: Understand how to configure IStringLocalizer with resource files and supported cultures.
  • πŸ‘‘ Senior: Build dynamic language-switching, manage translations at scale, and integrate with external localization platforms.

πŸ“š Resources: Globalization and localization in ASP.NET Core

Comments:

Please log in to be able add comments.