Master these 31 carefully curated interview questions to ace your next Dotnet core interview.
.NET Core is cross-platform, open-source, and modular; .NET Framework is Windows-only with a larger but monolithic library.
.NET Core: cross-platform, high performance, Docker support, side-by-side versioning. .NET Framework: Windows-only, WinForms/WPF/ASP.NET WebForms. .NET 5+ unified both. Migration: use .NET Upgrade Assistant. Choose .NET 8+ for all new projects.
Middleware are components in the request pipeline that handle requests/responses: auth, logging, error handling, routing.
Pipeline: app.Use()/app.Map()/app.Run(). Built-in: UseAuthentication, UseAuthorization, UseStaticFiles, UseCors. Custom: RequestDelegate, next() pattern. Order matters: exception handling first, auth before authorization. Short-circuit: don't call next() to stop pipeline.
SignalR enables real-time web communication: WebSockets, Server-Sent Events, and long polling with automatic fallback.
Hubs: server-side classes clients connect to. Clients: JavaScript, .NET, Java. Features: group messaging, connection management, streaming (IAsyncEnumerable). Scale-out: Redis backplane, Azure SignalR Service. Use for: chat, notifications, live dashboards, collaborative editing.
Blazor builds interactive web UIs with C# instead of JavaScript, running on server (SignalR) or client (WebAssembly).
Blazor Server: C# runs on server, UI updates via SignalR. Blazor WASM: C# compiled to WebAssembly, runs in browser. Blazor United (.NET 8): combines both with auto mode. Components: .razor files with C# and HTML. Benefits: share code between client/server, use .NET ecosystem, type safety.
CQRS separates read (query) and write (command) models using MediatR for dispatching commands and queries to handlers.
Command: CreateOrderCommand → handler validates, saves to write DB. Query: GetOrderQuery → handler reads from optimized read model. MediatR: mediator pattern library. Benefits: independent scaling, optimized read models, event sourcing compatibility. Use when: complex domains, different read/write patterns. Overkill for simple CRUD.
Containerize with Docker, use CI/CD (GitHub Actions/Azure DevOps), deploy to Kubernetes or Azure App Service.
Steps: (1) Dockerfile with multi-stage build. (2) CI/CD pipeline: build, test, publish. (3) Deploy: Azure App Service, AWS ECS, Kubernetes. (4) Configuration: environment variables, Azure Key Vault. (5) Health checks endpoints. (6) Logging: Serilog + Seq/Application Insights. (7) Monitoring: Prometheus/Grafana.
Native AOT compiles .NET directly to native machine code, eliminating JIT, reducing startup time and memory usage.
Benefits: instant startup, smaller deployment size, no JIT overhead. Tradeoffs: no runtime code generation, limited reflection, longer build time. Ideal for: serverless/Lambda functions, CLI tools, microservices with fast startup requirements. Not all libraries compatible yet. PublishAot property in project file.
ASP.NET Core is a cross-platform, high-performance rewrite of ASP.NET with built-in DI, middleware pipeline, and modular design.
ASP.NET: Windows-only, IIS-dependent, System.Web monolith. ASP.NET Core: cross-platform (Windows/Linux/macOS), Kestrel web server, modular middleware pipeline, built-in DI, unified MVC + Web API, Razor Pages, Minimal APIs. Performance: ASP.NET Core is 10-100x faster than ASP.NET. Hosting: IIS, Nginx, Docker, Azure. .NET 8 LTS is current. Features: Native AOT, gRPC, Blazor, SignalR, Health Checks. Migration: incremental via YARP reverse proxy or System.Web adapters.
Kestrel is ASP.NET Core's default cross-platform web server, built on libuv/IO completion ports for high-performance HTTP handling.
Kestrel: default web server, cross-platform, supports HTTP/1.1, HTTP/2, HTTP/3. Can run standalone or behind reverse proxy (Nginx, IIS, Apache). Configuration: endpoints, TLS certificates, connection limits in appsettings.json or code. Performance: one of fastest web servers in TechEmpower benchmarks. Behind reverse proxy for: SSL termination, load balancing, static files, URL rewriting. Named pipes, Unix sockets supported. HTTPS: developer certificate for dev, Let's Encrypt or commercial for production.
Transient creates new instance each injection; Scoped creates one per HTTP request; Singleton creates one for the application lifetime.
AddTransient<T>: new instance every time resolved — lightweight, stateless services. AddScoped<T>: one instance per scope (HTTP request in web) — DbContext, unit of work, per-request state. AddSingleton<T>: one instance for app lifetime — configuration, caches, HttpClient factory. Captive dependency trap: Singleton depending on Scoped/Transient captures shorter-lived service. ValidateScopes in Development catches this. Rule: long-lived services should not depend on short-lived ones. IServiceScopeFactory creates manual scopes in singletons.
ASP.NET Core uses endpoint routing with route templates, attribute routing, and conventional routing for matching URLs to handlers.
Endpoint routing: UseRouting() builds route table, UseEndpoints() dispatches. Attribute routing: [Route('api/[controller]')], [HttpGet('{id}')]. Convention: MapControllerRoute('default', '{controller=Home}/{action=Index}/{id?}'). Route constraints: {id:int}, {name:alpha}, {age:range(18,99)}. Route parameters: required vs optional (?). MapGet/MapPost: Minimal API routing. Route groups: MapGroup('/api').MapGet('/users', handler). Link generation: LinkGenerator for URL building. Area routing for modular apps.
Options pattern binds configuration sections to strongly-typed classes, supporting validation, reload, and named instances.
Setup: services.Configure<SmtpOptions>(config.GetSection('Smtp')). Inject: IOptions<T> (singleton, read once), IOptionsSnapshot<T> (scoped, reloads per request), IOptionsMonitor<T> (singleton, change notifications via OnChange). Validation: [Required], [Range] attributes + ValidateDataAnnotations(). ValidateOnStart() catches errors at startup. Named options: services.Configure<DbOptions>('primary', section) — IOptionsSnapshot<T>.Get('primary'). Post-configure: PostConfigure<T> for computed values. Prefer over manual IConfiguration.GetValue — type safety, testability, validation.
ASP.NET Core has built-in ILogger with providers for console, debug, EventSource, plus third-party like Serilog.
ILogger<T>: category-based logging. Log levels: Trace, Debug, Information, Warning, Error, Critical. Structured logging: logger.LogInformation('User {UserId} logged in', userId) — properties preserved for querying. Configuration: appsettings.json log levels per category. Serilog: structured logging with sinks (console, file, Seq, Elasticsearch). Enrichers: add context (machine name, thread ID). Scopes: using logger.BeginScope for correlation. High-performance: LoggerMessage.Define for hot-path logging (avoids boxing). OpenTelemetry integration for distributed tracing.
Native AOT compiles .NET to native machine code at build time, enabling fast startup, small footprint, but no runtime JIT.
Ahead-of-Time compilation: PublishAot=true in csproj. Benefits: ~10x faster startup, smaller memory footprint, no JIT warm-up, single-file deployment, no .NET runtime dependency. Limitations: no dynamic code (Reflection.Emit, dynamic loading), trimming removes unused code (may break reflection-heavy libraries), platform-specific binaries. Supported: Minimal APIs, gRPC, console apps. Not supported: Blazor Server, SignalR, MVC with views. Ideal for: microservices, serverless functions, CLI tools, containers. Source generators replace runtime reflection for AOT compatibility.
Model binding maps HTTP request data to action parameters; validation uses data annotations and ModelState for error reporting.
Binding sources: [FromBody] (JSON), [FromQuery], [FromRoute], [FromForm], [FromHeader], [FromServices]. Automatic: complex types from body, simple types from route/query. Data annotations: [Required], [StringLength], [Range], [EmailAddress], [RegularExpression], [Compare]. Custom: IValidatableObject for cross-property validation. FluentValidation: complex rules with readable syntax. ModelState.IsValid in controllers. Minimal APIs: use filters for validation. [ApiController]: automatic 400 for invalid models. Problem details response format.
Mediator pattern decouples request senders from handlers; MediatR implements CQRS with IRequest/IRequestHandler and pipeline behaviors.
MediatR: mediator.Send(new GetUserQuery(id)) → GetUserQueryHandler.Handle(). IRequest<T>/IRequestHandler<TRequest, TResponse> for queries/commands. INotification/INotificationHandler for events (multiple handlers). Pipeline behaviors: logging, validation, transaction management — cross-cutting concerns as middleware. CQRS: separate Command (write) and Query (read) models. Benefits: clean architecture, testable handlers, loose coupling. Criticism: over-engineering for simple CRUD, hides dependencies, adds indirection. Consider: Wolverine as alternative, or simple service classes.
Build self-contained or framework-dependent, deploy to IIS/Nginx/Docker/Azure with health checks, logging, and monitoring.
Modes: framework-dependent (smaller, needs runtime installed) vs self-contained (includes runtime, larger). Docker: multi-stage build with SDK image for build, runtime image for deploy. Azure: App Service, Container Apps, AKS. IIS: AspNetCoreModuleV2 (in-process recommended). Nginx: reverse proxy with proxy_pass. Health checks: MapHealthChecks for load balancer. Environment: ASPNETCORE_ENVIRONMENT variable. HTTPS: Let's Encrypt, Azure managed certificates. CI/CD: GitHub Actions, Azure DevOps. Monitoring: Application Insights, Prometheus + Grafana.
Use xUnit/NUnit with Moq for mocking, WebApplicationFactory for integration tests, and test data builders.
Unit tests: xUnit + Moq/NSubstitute. Arrange-Act-Assert pattern. Mock interfaces: var mock = new Mock<IUserService>(); mock.Setup(x => x.GetUser(1)).ReturnsAsync(user). Integration tests: WebApplicationFactory<Program> creates in-memory test server. services.RemoveAll<IDbContext>(); add in-memory DB. HttpClient for API testing. FluentAssertions: result.Should().BeOfType<User>().Which.Name.Should().Be('John'). Test patterns: test builders, fixture classes, shared context. Coverage: dotnet test --collect:'XPlat Code Coverage'. Snapshot testing: Verify library.
Channels provide high-performance async producer-consumer queues for passing data between tasks without external dependencies.
Channel.CreateBounded<T>(capacity) / Channel.CreateUnbounded<T>(). Writer: await channel.Writer.WriteAsync(item). Reader: await foreach (var item in channel.Reader.ReadAllAsync()). Bounded channels: backpressure when full (BoundedChannelFullMode: Wait, DropOldest, DropNewest). Use cases: request queuing in web apps, data processing pipelines, rate limiting, background task queues. vs BlockingCollection: Channels are async-native, better performance. vs Queue: Channels are thread-safe and async. Architecture: HTTP handler writes to channel, BackgroundService reads and processes.
Global usings apply imports project-wide (one declaration); file-scoped namespaces remove nesting for the entire file.
Global using (C# 10): global using System.Linq; — applies to all files in project. Place in GlobalUsings.cs. ImplicitUsings in csproj auto-adds common namespaces. File-scoped namespace (C# 10): namespace MyApp.Models; (no braces) — reduces indentation by one level. Entire file belongs to namespace. Benefits: less boilerplate, cleaner files, fewer merge conflicts. Combined with top-level statements and minimal APIs: extremely concise code. Best practices: use GlobalUsings.cs for project-wide imports, file-scoped namespaces in all files.
Short-circuiting stops the middleware pipeline early by not calling next(), returning a response immediately without reaching later middleware.
Normal flow: each middleware calls await next() to pass to the next. Short-circuit: skip next() call, write response directly. Examples: CORS preflight returns 204 without reaching controllers. Authentication fails returns 401. Rate limiting returns 429. Static files serves file without reaching MVC. Custom: health check endpoint at /health. UseWhen: conditional middleware (only runs for matching requests). Map: branches pipeline for path prefix. Terminal middleware (app.Run): always short-circuits, runs last. Performance benefit: avoid unnecessary processing.
Use EF Core migrations with dotnet ef commands, apply in CI/CD, and use idempotent scripts for production deployments.
Commands: dotnet ef migrations add CreateUsers, dotnet ef database update. Migration files: Up() applies changes, Down() reverses. Production: dotnet ef migrations script --idempotent generates SQL script. DbContext.Database.Migrate() in code (use for simple deployments). Multiple contexts: --context flag. Data seeding: HasData() in model configuration. Best practices: small focused migrations, test Down() method, never edit applied migrations, squash old migrations. CI/CD: generate migration script in build, apply in deployment. Alternatives: FluentMigrator, DbUp for raw SQL migrations.
Azure Functions is serverless compute running .NET code triggered by HTTP, timers, queues, blobs, and other events.
Triggers: HTTP, Timer (cron), Queue (Azure Storage, Service Bus), Blob, Cosmos DB, Event Grid, SignalR. Isolated worker model (.NET 8): separate process, full middleware support, DI. Durable Functions: stateful orchestrations (function chaining, fan-out/fan-in, human interaction). Cold start: mitigated with Premium plan or always-ready instances. .NET 8 supports Native AOT for faster cold starts. Testing: mock trigger data, test function classes directly. Pricing: consumption (pay per execution) or premium (pre-warmed). Deploy: Azure CLI, GitHub Actions, VS Code.
Repository abstracts data access behind an interface; Unit of Work groups multiple repository operations into a single transaction.
Repository: IUserRepository with GetById, Add, Update, Delete. Implementation wraps DbContext/DbSet. Benefits: testable (mock interface), swappable data source, encapsulates query logic. Unit of Work: coordinates multiple repositories sharing same DbContext, commits/rolls back together. SaveChangesAsync() is the unit of work commit. Controversy: EF Core DbContext already implements repository + UoW patterns. Generic repository (IRepository<T>) is often an anti-pattern — too abstract. Prefer specific repositories with domain-relevant methods.
IDistributedCache interface with Redis, SQL Server, or NCache implementations for shared caching across multiple app instances.
IDistributedCache: GetAsync, SetAsync, RemoveAsync with DistributedCacheEntryOptions (absolute/sliding expiration). Redis: AddStackExchangeRedisCache — most popular, fast, supports pub/sub. SQL Server: AddDistributedSqlServerCache — uses database table. HybridCache (.NET 9): combines L1 (in-memory) + L2 (distributed), stampede protection, tag-based invalidation. Cache-aside pattern: check cache → miss → fetch from DB → store in cache. Output caching: [OutputCache] caches entire HTTP responses. Strategy: cache frequently read, rarely changed data. Invalidation: event-driven or TTL-based.
Use JWT authentication, HTTPS, CORS, input validation, rate limiting, and follow OWASP security practices.
Authentication: JWT Bearer tokens with AddJwtBearer(). Generate tokens with symmetric/asymmetric keys. Authorization: [Authorize], policies, resource-based. CORS: AddCors with specific origins (not *). HTTPS: UseHsts, UseHttpsRedirection. Input: model validation, parameterized queries (EF prevents SQL injection). Headers: HSTS, X-Content-Type-Options, X-Frame-Options. Rate limiting: AddRateLimiter (fixed window, sliding window, token bucket). Secrets: Azure Key Vault, user-secrets for development. CSRF: anti-forgery tokens for forms. Dependency scanning: dotnet list package --vulnerable.
Health checks verify application dependencies (DB, cache, external services) and report status to load balancers and monitoring tools.
Setup: services.AddHealthChecks().AddSqlServer(connString).AddRedis(redisConn). MapHealthChecks('/health'). Custom: implement IHealthCheck.CheckHealthAsync — return Healthy, Degraded, or Unhealthy. UI: HealthChecksUI dashboard. Kubernetes: liveness and readiness probes to /health/live and /health/ready. Load balancer: unhealthy instances removed from rotation. Tags: group checks (ready vs live). Timeout: per-check timeout prevents blocking. AspNetCore.Diagnostics.HealthChecks NuGet: pre-built checks for SQL, Redis, RabbitMQ, Azure services, HTTP endpoints.
REPR organizes APIs as individual endpoint classes with request/response models, alternative to traditional MVC controllers.
Libraries: FastEndpoints, Carter, or Minimal APIs with organization. Each endpoint: one class per HTTP operation with typed request/response. Structure: Features/Users/GetUser/GetUserEndpoint.cs, GetUserRequest.cs, GetUserResponse.cs. Benefits: vertical slice architecture, easier to navigate than fat controllers, clear request/response contracts, easier testing. FastEndpoints: class GetUser : Endpoint<GetUserRequest, GetUserResponse>, auto-validation, pre/post processors. vs Controllers: REPR scales better for large APIs, avoids 1000-line controllers. Combines well with CQRS/MediatR.
API controllers use MVC conventions with attributes and model binding; Minimal APIs use lambda-based routing with less ceremony.
Controllers: [ApiController] + ControllerBase, attribute routing, automatic model validation, filters (action/exception/resource), conventions. Minimal APIs: app.MapGet('/api/users', handler), direct parameter injection, endpoint filters, route groups. Controllers better for: large APIs needing organization, complex model binding, existing codebases. Minimal APIs better for: microservices, simple endpoints, performance (slightly faster), new projects, AOT compatibility. Both support: DI, auth, OpenAPI, middleware. Hybrid: use both in same project. MinimalApis.Extensions helps bridge.
.NET Aspire is a cloud-ready application framework providing service defaults, orchestration, and pre-configured integrations for distributed apps.
.NET Aspire (2024): opinionated patterns for cloud-native .NET apps. Service defaults: health checks, OpenTelemetry, resilience configured by default. App host: orchestrates multi-project solutions — define all services, databases, caches in C#. Components: pre-configured Redis, PostgreSQL, RabbitMQ, Azure services with health checks and telemetry. Dashboard: local development dashboard showing logs, traces, metrics across all services. vs Docker Compose: .NET Aspire manages service discovery, connection strings, environment variables automatically. Deployment: Azure Container Apps, Kubernetes via manifest generation.
Ready to master Dotnet core?
Start learning with our comprehensive course and practice these questions.