Beginner ASP.NET Web API Interview Questions and Answers

๐Ÿ“‹ Table of Contents โ–พ
  1. Questions & Answers
  2. 📝 Knowledge Check

📡 Beginner ASP.NET Web API Interview Questions

This lesson covers the fundamental ASP.NET Core Web API concepts every .NET developer must know. Master controllers, routing, HTTP methods, model binding, validation, dependency injection, middleware, and the request pipeline. These questions reflect what interviewers ask at junior and entry-level .NET backend roles.

Questions & Answers

01 What is ASP.NET Core Web API and how does it differ from MVC?

Core ASP.NET Core Web API is a framework for building HTTP-based services โ€” RESTful APIs that return data (JSON, XML) rather than HTML views. It is part of the ASP.NET Core framework, which is a cross-platform, high-performance, open-source framework for building modern web applications on .NET.

Web API vs MVC:

  • MVC โ€” returns HTML views rendered on the server. Controllers return IActionResult with view templates. Designed for web applications that render UI server-side.
  • Web API โ€” returns data serialised as JSON or XML. Controllers inherit from ControllerBase (not Controller). Decorated with [ApiController]. Designed for headless services consumed by SPAs, mobile apps, or other services.
// Web API controller โ€” inherits ControllerBase (no view support)
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    public IActionResult GetAll() => Ok(new[] { "Widget", "Gadget" });
}

// MVC controller โ€” inherits Controller (has View() method)
public class HomeController : Controller
{
    public IActionResult Index() => View();   // renders HTML view
}

In ASP.NET Core, both share the same unified framework โ€” the distinction is whether you inherit from Controller or ControllerBase and whether you use the [ApiController] attribute.

02 What does the [ApiController] attribute do?

Core The [ApiController] attribute (introduced in ASP.NET Core 2.1) enables several API-specific conventions that reduce boilerplate:

  • Automatic model validation โ€” if the model is invalid, returns a 400 Bad Request with validation details automatically (no need to check ModelState.IsValid in every action).
  • Automatic binding source inference โ€” infers [FromBody] for complex types, [FromRoute] for route parameters, [FromQuery] for simple types โ€” no need to decorate every parameter.
  • Problem details responses โ€” returns RFC 7807 ProblemDetails JSON for 4xx/5xx errors.
  • Attribute routing required โ€” forces the use of attribute routing ([Route], [HttpGet], etc.) rather than conventional routing.
  • Multipart/form-data inference โ€” infers [FromForm] for IFormFile parameters.
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    [HttpPost]
    public IActionResult Create(OrderDto dto)  // [FromBody] inferred automatically
    {
        // No need to check ModelState.IsValid โ€” [ApiController] handles it
        // If dto is invalid, 400 is returned before this code runs
        return CreatedAtAction(nameof(GetById), new { id = 1 }, dto);
    }
}

03 How does routing work in ASP.NET Core Web API?

Routing ASP.NET Core Web API uses attribute routing โ€” routes are defined directly on controllers and actions using attributes.

[ApiController]
[Route("api/[controller]")]    // [controller] = "Products" (class name minus "Controller")
public class ProductsController : ControllerBase
{
    // GET api/products
    [HttpGet]
    public IActionResult GetAll() => Ok(_products);

    // GET api/products/42
    [HttpGet("{id:int}")]
    public IActionResult GetById(int id) => Ok(_products.FirstOrDefault(p => p.Id == id));

    // GET api/products/42/reviews
    [HttpGet("{id}/reviews")]
    public IActionResult GetReviews(int id) => Ok(_reviews[id]);

    // POST api/products
    [HttpPost]
    public IActionResult Create([FromBody] CreateProductDto dto)
        => CreatedAtAction(nameof(GetById), new { id = 1 }, dto);

    // PUT api/products/42
    [HttpPut("{id}")]
    public IActionResult Update(int id, [FromBody] UpdateProductDto dto) => Ok();

    // DELETE api/products/42
    [HttpDelete("{id}")]
    public IActionResult Delete(int id) => NoContent();
}

// Route constraints โ€” restrict type and format
[HttpGet("{id:int:min(1)}")]          // id must be int >= 1
[HttpGet("{name:alpha:minlength(2)}")] // name must be letters, min 2 chars
[HttpGet("{slug:regex(^[a-z-]+$)}")]   // slug matches regex

04 What are the HTTP action methods available in ASP.NET Core Web API?

HTTP

public class ItemsController : ControllerBase
{
    [HttpGet]               // Read all or filtered list
    [HttpGet("{id}")]       // Read one
    [HttpPost]              // Create
    [HttpPut("{id}")]       // Update โ€” full replacement
    [HttpPatch("{id}")]     // Update โ€” partial update
    [HttpDelete("{id}")]    // Delete
    [HttpHead("{id}")]      // Like GET but no response body (check existence)
    [HttpOptions]           // CORS preflight, discover allowed methods

    // Multiple methods on one action
    [AcceptVerbs("GET", "HEAD")]
    public IActionResult GetOrHead(int id) { ... }
}

// HTTP Semantics:
// GET    โ€” safe + idempotent (no side effects, repeatable)
// HEAD   โ€” safe + idempotent (like GET but empty body)
// PUT    โ€” idempotent (same result if called multiple times)
// DELETE โ€” idempotent
// POST   โ€” neither safe nor idempotent (creates new resource each call)
// PATCH  โ€” not idempotent in general (depends on implementation)

PUT vs PATCH: PUT replaces the entire resource โ€” the client must send all fields. PATCH applies a partial update โ€” only the fields being changed. Use JsonPatchDocument<T> (from Microsoft.AspNetCore.JsonPatch) for RFC 6902-compliant PATCH operations.

05 What are the IActionResult return types in ASP.NET Core?

Response

public class ProductsController : ControllerBase
{
    // Common response helpers
    public IActionResult Examples(int id)
    {
        return Ok(data);                              // 200 + body
        return Created("/api/items/1", data);         // 201 + Location header
        return CreatedAtAction(nameof(Get), new {id}, data); // 201 + Location
        return NoContent();                           // 204 (successful DELETE/PUT)
        return BadRequest("Invalid input");           // 400
        return BadRequest(ModelState);                // 400 + validation errors
        return Unauthorized();                        // 401
        return Forbid();                              // 403
        return NotFound();                            // 404
        return NotFound($"Product {id} not found");   // 404 + message
        return Conflict("Resource already exists");   // 409
        return UnprocessableEntity(ModelState);       // 422
        return StatusCode(503, "Service unavailable");// custom status code
        return Problem("Something went wrong", statusCode: 500); // RFC 7807
    }
}

// Strongly-typed return โ€” better for Swagger documentation
public ActionResult<Product> GetById(int id)
{
    var product = _repo.Find(id);
    if (product == null) return NotFound();
    return product;   // implicit Ok(product) โ€” no need to wrap
}

// Async versions
public async Task<ActionResult<Product>> GetByIdAsync(int id)
{
    var product = await _repo.FindAsync(id);
    return product == null ? NotFound() : Ok(product);
}

06 What is model binding in ASP.NET Core Web API?

Model Binding Model binding automatically maps incoming HTTP request data to action method parameters. The framework inspects request data from multiple sources and converts it to .NET types.

// Source attributes โ€” explicit binding source declaration
[HttpGet("{id}")]
public IActionResult Get(
    [FromRoute]  int    id,            // from URL segment: /items/42
    [FromQuery]  string sort = "name", // from query string: ?sort=price
    [FromHeader] string apiKey,        // from request header: X-Api-Key: abc
    [FromBody]   CreateItemDto dto,    // from request body (JSON)
    [FromForm]   IFormFile file        // from multipart form data
) { ... }

// Without [ApiController], binding inference is not automatic
// With [ApiController], inference works:
// - Complex types (classes) โ†’ [FromBody]
// - Simple types (int, string) that match route params โ†’ [FromRoute]
// - Other simple types โ†’ [FromQuery]

// Special types
[HttpPost("upload")]
public IActionResult Upload(
    [FromForm] string name,        // form field
    IFormFile  file,               // uploaded file (inferred [FromForm])
    IFormFileCollection files      // multiple files
) { ... }

// Bind from multiple sources with a custom model binder
// Or use [ModelBinder(typeof(MyBinder))] for custom binding logic

07 How does model validation work in ASP.NET Core?

Validation ASP.NET Core uses Data Annotations from System.ComponentModel.DataAnnotations for declarative model validation.

using System.ComponentModel.DataAnnotations;

public class CreateUserDto
{
    [Required(ErrorMessage = "Name is required")]
    [StringLength(100, MinimumLength = 2)]
    public string Name { get; set; } = "";

    [Required]
    [EmailAddress]
    public string Email { get; set; } = "";

    [Range(0, 120)]
    public int? Age { get; set; }

    [Phone]
    public string? Phone { get; set; }

    [Url]
    public string? Website { get; set; }

    [MinLength(8), MaxLength(50)]
    [RegularExpression(@"^(?=.*[A-Z])(?=.*\d).+$",
        ErrorMessage = "Password must have an uppercase letter and a digit")]
    public string Password { get; set; } = "";
}

// Without [ApiController] โ€” manual check required
[HttpPost]
public IActionResult Create(CreateUserDto dto)
{
    if (!ModelState.IsValid)
        return BadRequest(ModelState);  // returns validation errors
    // proceed...
}

// With [ApiController] โ€” automatic 400 + validation errors (no manual check needed)
[ApiController]
public class UsersController : ControllerBase
{
    [HttpPost]
    public IActionResult Create(CreateUserDto dto)
    {
        // Code here only runs if dto is valid
        return Ok();
    }
}

// Custom validation attribute
public class FutureDateAttribute : ValidationAttribute
{
    public override bool IsValid(object? value)
        => value is DateTime dt && dt > DateTime.UtcNow;
}

08 What is Dependency Injection (DI) in ASP.NET Core? How do you register services?

DI ASP.NET Core has a built-in IoC container. Services are registered in Program.cs and injected through constructor parameters. The container resolves and manages lifetimes automatically.

// Program.cs โ€” register services
var builder = WebApplication.CreateBuilder(args);

// Three lifetime options:
builder.Services.AddTransient<IEmailService, EmailService>();  // new instance every time
builder.Services.AddScoped<IOrderService, OrderService>();     // one per HTTP request
builder.Services.AddSingleton<ICacheService, RedisCacheService>(); // one for app lifetime

// Register with factory
builder.Services.AddScoped<IDbContext>(sp =>
    new AppDbContext(sp.GetRequiredService<DbContextOptions<AppDbContext>>()));

// Register all implementations of an interface
builder.Services.Scan(scan => scan
    .FromAssemblyOf<Program>()
    .AddClasses(c => c.AssignableTo<IRepository>())
    .AsImplementedInterfaces()
    .WithScopedLifetime());

// Constructor injection in a controller
public class OrdersController : ControllerBase
{
    private readonly IOrderService _orders;
    private readonly ILogger<OrdersController> _logger;

    public OrdersController(IOrderService orders, ILogger<OrdersController> logger)
    {
        _orders = orders;
        _logger = logger;
    }
}

// Inject directly into action (Minimal API style or with [FromServices])
[HttpGet]
public IActionResult Get([FromServices] IProductService svc) => Ok(svc.GetAll());

09 What is middleware in ASP.NET Core? How does the request pipeline work?

Middleware Middleware components form a pipeline โ€” each processes the HTTP request and can pass it to the next component or short-circuit the pipeline. Order matters: middleware registered first runs first for requests and last for responses.

var app = builder.Build();

// Middleware pipeline โ€” ORDER IS CRITICAL
app.UseExceptionHandler("/error");    // catch unhandled exceptions
app.UseHttpsRedirection();            // redirect HTTP โ†’ HTTPS
app.UseStaticFiles();                 // serve wwwroot files (short-circuit for static)
app.UseRouting();                     // match request to endpoint
app.UseCors("AllowAll");              // CORS headers
app.UseAuthentication();              // identify user (who are you?)
app.UseAuthorization();               // check permissions (are you allowed?)
app.UseRateLimiter();                 // enforce rate limits
app.MapControllers();                 // route to controllers

// Custom middleware โ€” inline
app.Use(async (context, next) =>
{
    context.Response.Headers.Add("X-Request-Id", Guid.NewGuid().ToString());
    await next(context);                      // call next middleware
    // After response: log timing, add headers
});

// Custom middleware โ€” class
public class TimingMiddleware(RequestDelegate next)
{
    public async Task InvokeAsync(HttpContext context)
    {
        var sw = Stopwatch.StartNew();
        await next(context);
        var ms = sw.ElapsedMilliseconds;
        context.Response.Headers.Add("X-Process-Time", $"{ms}ms");
    }
}
app.UseMiddleware<TimingMiddleware>();

10 What is the difference between AddScoped, AddTransient, and AddSingleton?

DI

  • Transient โ€” a new instance is created every time the service is requested. Use for lightweight, stateless services. Multiple injections in the same request get different instances.
  • Scoped โ€” one instance per HTTP request (scope). Multiple injections in the same request share the same instance. The most common lifetime for services that hold request-level state (DbContext, current user).
  • Singleton โ€” one instance for the entire application lifetime. Shared by all requests. Use for thread-safe, application-wide services (caches, configuration, HTTP clients). Never inject a Scoped service into a Singleton โ€” the Scoped service will never be released (captive dependency problem).
// Captive dependency โ€” WRONG: Singleton captures Scoped
builder.Services.AddSingleton<MyService>();   // singleton
builder.Services.AddScoped<IDbContext, AppDbContext>(); // scoped

// MyService constructor injects IDbContext:
// The SAME DbContext is reused for ALL requests forever โ€” connection leaks!

// Rule: Inject services with SHORTER or EQUAL lifetimes
// Singleton can inject Singleton only
// Scoped can inject Singleton or Scoped
// Transient can inject anything

// Detect captive dependencies:
builder.Services.AddScoped<IDbContext, AppDbContext>();
// In dev: configure validation
builder.Host.UseDefaultServiceProvider(options => {
    options.ValidateScopes = true;      // throws on captive dependencies
    options.ValidateOnBuild = true;     // validates at startup
});

11 How do you configure and read application settings in ASP.NET Core?

Config

// appsettings.json
{
  "ConnectionStrings": {
    "DefaultConnection": "Server=.;Database=MyDb;Trusted_Connection=true"
  },
  "Jwt": {
    "Key": "super-secret-key",
    "Issuer": "https://myapi.com",
    "ExpiresMinutes": 60
  },
  "FeatureFlags": {
    "EnableDarkMode": true
  }
}

// Strongly-typed settings class
public class JwtSettings
{
    public string Key { get; set; } = "";
    public string Issuer { get; set; } = "";
    public int ExpiresMinutes { get; set; } = 60;
}

// Register and bind in Program.cs
builder.Services.Configure<JwtSettings>(
    builder.Configuration.GetSection("Jwt"));

// Inject in a service
public class TokenService(IOptions<JwtSettings> options)
{
    private readonly JwtSettings _settings = options.Value;

    public string CreateToken() => _settings.Key; // use settings
}

// Direct configuration read (less preferred โ€” no type safety)
var connStr = builder.Configuration.GetConnectionString("DefaultConnection");
var key = builder.Configuration["Jwt:Key"];

// Environment-specific overrides: appsettings.Development.json
// Environment variables override appsettings (ASPNETCORE_ENVIRONMENT, Jwt__Key)

12 What is the difference between IActionResult and ActionResult<T>?

Response

  • IActionResult โ€” non-generic interface. Can return any action result (Ok, NotFound, BadRequest, etc.). No type information for Swagger โ€” the response schema is unknown without additional attributes.
  • ActionResult<T> โ€” generic wrapper introduced in ASP.NET Core 2.1. Can return either a typed value T or any IActionResult. The type T is used by Swagger to generate the response schema automatically. Strongly recommended for all API actions.
// IActionResult โ€” Swagger doesn't know the response type
[HttpGet("{id}")]
[ProducesResponseType(typeof(Product), 200)]  // must manually document
[ProducesResponseType(404)]
public IActionResult GetById(int id)
{
    var p = _repo.Find(id);
    return p == null ? NotFound() : Ok(p);
}

// ActionResult<T> โ€” Swagger auto-generates Product schema
[HttpGet("{id}")]
[ProducesResponseType(404)]  // still document non-T responses
public ActionResult<Product> GetById(int id)
{
    var p = _repo.Find(id);
    if (p == null) return NotFound();
    return p;              // implicit conversion to Ok(p) โ€” no wrapper needed!
}

// Async
public async Task<ActionResult<Product>> GetByIdAsync(int id)
{
    var p = await _repo.FindAsync(id);
    return p == null ? NotFound() : p;
}

13 What is Swagger / OpenAPI in ASP.NET Core and how do you enable it?

Docs Swagger (now standardised as OpenAPI) automatically generates interactive API documentation from your controller code. ASP.NET Core uses Swashbuckle or NSwag for this.

// Install: dotnet add package Swashbuckle.AspNetCore

// Program.cs
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(options =>
{
    options.SwaggerDoc("v1", new OpenApiInfo
    {
        Title   = "My API",
        Version = "v1",
        Description = "RESTful API built with ASP.NET Core"
    });

    // Include XML comments in docs
    var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
    options.IncludeXmlComments(xmlPath);

    // Add JWT auth to Swagger UI
    options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Type = SecuritySchemeType.Http, Scheme = "bearer"
    });
    options.AddSecurityRequirement(new OpenApiSecurityRequirement { ... });
});

var app = builder.Build();

// Only expose Swagger in development
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();       // serves /swagger/v1/swagger.json
    app.UseSwaggerUI();     // serves /swagger/index.html
}

// Document an action with XML summary comments
/// <summary>Get a product by its unique identifier.</summary>
/// <param name="id">The product ID</param>
/// <response code="200">Returns the product</response>
/// <response code="404">Product not found</response>
[HttpGet("{id}")]
public ActionResult<Product> GetById(int id) { ... }

14 How do you handle query string parameters and pagination in Web API?

Request

// GET /api/products?page=2&size=20&sort=price&order=desc&category=electronics
[HttpGet]
public async Task<ActionResult<PagedResult<ProductDto>>> GetAll(
    [FromQuery] int    page     = 1,
    [FromQuery] int    size     = 20,
    [FromQuery] string sort     = "name",
    [FromQuery] string order    = "asc",
    [FromQuery] string? category = null
)
{
    if (page < 1 || size < 1 || size > 100)
        return BadRequest("Invalid pagination parameters");

    var query = _db.Products.AsQueryable();

    if (category != null)
        query = query.Where(p => p.Category == category);

    var total = await query.CountAsync();
    var items = await query
        .OrderByDynamic(sort, order)
        .Skip((page - 1) * size)
        .Take(size)
        .Select(p => new ProductDto(p))
        .ToListAsync();

    return Ok(new PagedResult<ProductDto>
    {
        Items    = items,
        Page     = page,
        Size     = size,
        Total    = total,
        Pages    = (int)Math.Ceiling((double)total / size)
    });
}

// Bind query params into a POCO class (cleaner for many params)
public record ProductFilter(
    [FromQuery] int    Page     = 1,
    [FromQuery] int    Size     = 20,
    [FromQuery] string? Search  = null,
    [FromQuery] string? Category = null
);

[HttpGet]
public IActionResult GetAll([FromQuery] ProductFilter filter) { ... }

15 What is content negotiation in ASP.NET Core Web API?

HTTP Content negotiation is the process by which the server selects the best response format based on the client’s Accept header. By default, ASP.NET Core returns JSON.

// Enable XML output in addition to JSON
builder.Services.AddControllers()
    .AddXmlSerializerFormatters()   // uses XmlSerializer
    // OR
    .AddXmlDataContractSerializerFormatters(); // uses DataContractSerializer

// Client requests XML:
// Accept: application/xml
// โ†’ Server returns XML if formatter is registered

// Client requests JSON (default):
// Accept: application/json  OR  Accept: */*
// โ†’ Server returns JSON

// Force JSON only (ignore Accept header, always return JSON)
builder.Services.AddControllers(options =>
{
    options.RespectBrowserAcceptHeader = false;  // default
    options.ReturnHttpNotAcceptable    = true;   // return 406 if no matching format
});

// Restrict an action to specific formats
[HttpGet("{id}")]
[Produces("application/json")]                  // always JSON for this action
public ActionResult<Product> GetById(int id) { ... }

// Multiple formats
[HttpGet]
[Produces("application/json", "application/xml")]
public IEnumerable<Product> GetAll() { ... }

// Check negotiated format in the handler
[HttpGet]
public IActionResult GetAll()
{
    var accept = Request.Headers.Accept.ToString();
    // Usually handled automatically โ€” this is just for awareness
    return Ok(_products);
}

16 How do you return files and streams from a Web API action?

Response

public class FilesController : ControllerBase
{
    // Return a file from disk
    [HttpGet("download/{filename}")]
    public IActionResult DownloadFile(string filename)
    {
        var path = Path.Combine("uploads", filename);
        if (!System.IO.File.Exists(path))
            return NotFound();

        var bytes   = System.IO.File.ReadAllBytes(path);
        var mime    = "application/octet-stream";
        return File(bytes, mime, filename);   // triggers download
    }

    // Stream a large file (memory-efficient)
    [HttpGet("stream/{filename}")]
    public IActionResult StreamFile(string filename)
    {
        var path   = Path.Combine("uploads", filename);
        var stream = new FileStream(path, FileMode.Open, FileAccess.Read);
        return File(stream, "application/octet-stream", filename);
        // Stream is disposed after the response is sent
    }

    // Generate a file in memory (e.g., CSV export)
    [HttpGet("export/csv")]
    public IActionResult ExportCsv()
    {
        var sb = new StringBuilder("Id,Name,Price\n");
        foreach (var p in _products)
            sb.AppendLine($"{p.Id},{p.Name},{p.Price}");

        var bytes = Encoding.UTF8.GetBytes(sb.ToString());
        return File(bytes, "text/csv", "products.csv");
    }

    // Return PDF from a PhysicalFileResult
    [HttpGet("report")]
    public PhysicalFileResult DownloadReport()
        => PhysicalFile("/reports/annual.pdf", "application/pdf");
}

17 What is CORS and how do you configure it in ASP.NET Core?

Security

// Program.cs
builder.Services.AddCors(options =>
{
    // Named policy โ€” restrictive (production)
    options.AddPolicy("ProductionPolicy", policy =>
        policy
            .WithOrigins("https://myapp.com", "https://admin.myapp.com")
            .WithMethods("GET", "POST", "PUT", "PATCH", "DELETE")
            .WithHeaders("Content-Type", "Authorization")
            .AllowCredentials()   // allow cookies/auth headers
            .SetPreflightMaxAge(TimeSpan.FromHours(24))
    );

    // Allow all (development only โ€” never in production)
    options.AddPolicy("AllowAll", policy =>
        policy.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()
    );

    // Default policy
    options.AddDefaultPolicy(policy =>
        policy.WithOrigins("https://myapp.com").AllowAnyMethod().AllowAnyHeader()
    );
});

var app = builder.Build();
app.UseRouting();
app.UseCors("ProductionPolicy");  // must be BETWEEN UseRouting and UseAuthorization
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

// Per-controller or per-action policy override
[EnableCors("AllowAll")]
public class PublicController : ControllerBase { ... }

[DisableCors]  // disable CORS for this action
[HttpGet("internal")]
public IActionResult InternalEndpoint() { ... }

18 What are filters in ASP.NET Core Web API?

Filters Filters run code at specific stages of the action execution pipeline โ€” before or after model binding, action execution, or result execution. They separate cross-cutting concerns from action logic.

  • Authorization filters โ€” first to run; check if the user is authorised. (IAuthorizationFilter)
  • Resource filters โ€” wrap the entire pipeline after auth; useful for caching. (IResourceFilter)
  • Action filters โ€” run before and after the action method. Most commonly used. (IActionFilter)
  • Result filters โ€” run before and after the action result execution. (IResultFilter)
  • Exception filters โ€” handle unhandled exceptions thrown by action or other filters. (IExceptionFilter)
// Custom action filter โ€” log action execution time
public class TimingActionFilter : IActionFilter
{
    private Stopwatch _sw = new();

    public void OnActionExecuting(ActionExecutingContext context)
    {
        _sw.Start();  // before the action
    }

    public void OnActionExecuted(ActionExecutedContext context)
    {
        _sw.Stop();
        var logger = context.HttpContext.RequestServices
            .GetRequiredService<ILogger<TimingActionFilter>>();
        logger.LogInformation("{Action} took {Ms}ms",
            context.ActionDescriptor.DisplayName, _sw.ElapsedMilliseconds);
    }
}

// Register globally (applies to all controllers)
builder.Services.AddControllers(options =>
    options.Filters.Add<TimingActionFilter>()
);

// Or as attribute on a specific controller/action
[ServiceFilter(typeof(TimingActionFilter))]
public class OrdersController : ControllerBase { ... }

19 How do you handle exceptions globally in ASP.NET Core Web API?

Error Handling

// Option 1: UseExceptionHandler middleware (built-in)
app.UseExceptionHandler(errApp =>
{
    errApp.Run(async context =>
    {
        var feature = context.Features.Get<IExceptionHandlerFeature>();
        var ex      = feature?.Error;

        context.Response.StatusCode  = ex switch
        {
            NotFoundException     => 404,
            UnauthorizedException => 401,
            ValidationException   => 422,
            _                     => 500
        };
        context.Response.ContentType = "application/json";

        var problem = new ProblemDetails
        {
            Status = context.Response.StatusCode,
            Title  = ex?.Message ?? "An error occurred"
        };
        await context.Response.WriteAsJsonAsync(problem);
    });
});

// Option 2: IExceptionHandler (ASP.NET Core 8+, recommended)
public class GlobalExceptionHandler : IExceptionHandler
{
    public async ValueTask<bool> TryHandleAsync(
        HttpContext context, Exception exception, CancellationToken ct)
    {
        var (status, title) = exception switch
        {
            NotFoundException   => (404, exception.Message),
            ForbiddenException  => (403, "Access denied"),
            _                   => (500, "Internal server error")
        };
        context.Response.StatusCode = status;
        await context.Response.WriteAsJsonAsync(
            new ProblemDetails { Status = status, Title = title }, ct);
        return true;
    }
}

builder.Services.AddExceptionHandler<GlobalExceptionHandler>();
builder.Services.AddProblemDetails();
app.UseExceptionHandler();

20 What is the difference between .NET Framework Web API and ASP.NET Core Web API?

Core

  • Cross-platform โ€” ASP.NET Core runs on Windows, Linux, and macOS. .NET Framework runs on Windows only.
  • Performance โ€” ASP.NET Core is significantly faster (Kestrel web server, optimised pipeline, no System.Web overhead).
  • Unified framework โ€” ASP.NET Core merges Web API and MVC into one framework with shared routing, filters, and DI. The old .NET Framework had separate Web API (ApiController) and MVC (Controller) stacks.
  • Built-in DI โ€” ASP.NET Core has a built-in IoC container. The old framework required Unity, Autofac, or similar.
  • Hosting model โ€” ASP.NET Core uses Kestrel (can also sit behind Nginx/Apache). .NET Framework was IIS-only.
  • Side-by-side versioning โ€” multiple .NET versions can coexist on the same machine.
  • Open source โ€” ASP.NET Core is fully open source on GitHub.
  • Support โ€” .NET Framework Web API is in maintenance mode; ASP.NET Core is the future.
21 How do you create a minimal API in ASP.NET Core?

Minimal API Minimal APIs (introduced in .NET 6) allow you to build HTTP endpoints with minimal code โ€” no controllers, no classes required. Ideal for microservices, lightweight APIs, and serverless functions.

// Program.cs โ€” entire minimal API
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddScoped<IProductService, ProductService>();

var app = builder.Build();
app.UseSwagger();
app.UseSwaggerUI();

// Define endpoints directly on app
app.MapGet("/products", async (IProductService svc) =>
    await svc.GetAllAsync());

app.MapGet("/products/{id:int}", async (int id, IProductService svc) =>
{
    var p = await svc.GetByIdAsync(id);
    return p is null ? Results.NotFound() : Results.Ok(p);
});

app.MapPost("/products", async (CreateProductDto dto, IProductService svc) =>
{
    var created = await svc.CreateAsync(dto);
    return Results.CreatedAtRoute("GetProduct", new { id = created.Id }, created);
}).WithName("GetProduct");

app.MapPut("/products/{id}", async (int id, UpdateProductDto dto, IProductService svc) =>
{
    await svc.UpdateAsync(id, dto);
    return Results.NoContent();
});

app.MapDelete("/products/{id}", async (int id, IProductService svc) =>
{
    await svc.DeleteAsync(id);
    return Results.NoContent();
});

app.Run();

For larger applications, you can organise minimal API endpoints into extension methods and route groups (app.MapGroup("/api/v1")). Controllers remain the standard for complex applications; minimal APIs excel for simple, fast microservices.

22 What is the Program.cs entry point in ASP.NET Core 6+?

Core ASP.NET Core 6 introduced a simplified hosting model. Program.cs uses top-level statements โ€” the Startup.cs class and the separate service configuration/middleware configuration split are merged into a single file.

// Program.cs โ€” modern minimal hosting model (ASP.NET Core 6+)
var builder = WebApplication.CreateBuilder(args);

// 1. Configure services (was Startup.ConfigureServices)
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<AppDbContext>(opts =>
    opts.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
builder.Services.AddScoped<IUserService, UserService>();

var app = builder.Build();

// 2. Configure HTTP pipeline (was Startup.Configure)
if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
    app.UseDeveloperExceptionPage();
}
else
{
    app.UseExceptionHandler("/error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

app.Run();  // start listening

// For testing โ€” expose the implicit Program class
public partial class Program { }  // allows integration tests to use WebApplicationFactory

📝 Knowledge Check

Test your understanding of ASP.NET Core Web API fundamentals with these five questions.

🧠 Quiz Question 1 of 5

What does the [ApiController] attribute automatically do that plain ControllerBase does not?





🧠 Quiz Question 2 of 5

What is the difference between AddScoped and AddSingleton service lifetimes in ASP.NET Core DI?





🧠 Quiz Question 3 of 5

Why should you prefer ActionResult<T> over IActionResult as a return type for Web API actions?





🧠 Quiz Question 4 of 5

In the ASP.NET Core middleware pipeline, why must UseCors() be placed between UseRouting() and UseAuthentication()?





🧠 Quiz Question 5 of 5

What is the captive dependency problem in ASP.NET Core DI?