ASP.NET Core Web

 

ASP.NET Core Web API Development

Table of Contents

  1. RESTful API Design Principles
  2. Content Negotiation
  3. API Versioning Strategies
  4. OpenAPI/Swagger Integration
  5. Interview Questions

RESTful API Design Principles

What is REST?

REST (Representational State Transfer) is an architectural style for designing networked applications. It relies on stateless, client-server communication, typically over HTTP.

Core REST Principles

1. Stateless Communication

  • Each request from client to server must contain all the information needed to understand and process the request
  • Server doesn't store client state between requests
  • Improves scalability and reliability
// Stateless example - each request includes authentication
[HttpGet("users/{id}")]
[Authorize]
public async Task<IActionResult> GetUser(int id)
{
    var user = await _userService.GetUserByIdAsync(id);
    return Ok(user);
}

2. Client-Server Architecture

  • Clear separation between client and server concerns
  • Client handles user interface, server handles data storage and business logic
  • Allows independent evolution of client and server

3. Uniform Interface

  • Identification of resources (URIs)
  • Manipulation of resources through representations
  • Self-descriptive messages
  • Hypermedia as the engine of application state (HATEOAS)

4. Resource-Based Design

  • Resources are identified by URIs
  • Resources have representations (JSON, XML, etc.)
  • Resources can have multiple representations
// Resource-based endpoints
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
    [HttpGet] // GET /api/products
    public async Task<IActionResult> GetProducts() { ... }

    [HttpGet("{id}")] // GET /api/products/123
    public async Task<IActionResult> GetProduct(int id) { ... }

    [HttpPost] // POST /api/products
    public async Task<IActionResult> CreateProduct([FromBody] ProductDto product) { ... }

    [HttpPut("{id}")] // PUT /api/products/123
    public async Task<IActionResult> UpdateProduct(int id, [FromBody] ProductDto product) { ... }

    [HttpDelete("{id}")] // DELETE /api/products/123
    public async Task<IActionResult> DeleteProduct(int id) { ... }
}

5. HTTP Methods and Status Codes

  • GET: Retrieve resource(s)
  • POST: Create new resource
  • PUT: Update entire resource
  • PATCH: Partial update
  • DELETE: Remove resource

Common HTTP Status Codes:

  • 200 OK - Success
  • 201 Created - Resource created
  • 204 No Content - Success, no response body
  • 400 Bad Request - Invalid request
  • 401 Unauthorized - Authentication required
  • 403 Forbidden - Authorization failed
  • 404 Not Found - Resource doesn't exist
  • 409 Conflict - Resource conflict
  • 422 Unprocessable Entity - Validation errors
  • 500 Internal Server Error - Server error

6. Idempotency

  • GET, PUT, DELETE should be idempotent (multiple identical requests = same result)
  • POST is not idempotent (creates new resources)

7. HATEOAS (Hypermedia as the Engine of Application State)

  • API responses include links to related resources
  • Clients discover available actions through links
public class ProductResponse
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public List<Link> Links { get; set; } = new();

    public ProductResponse(Product product)
    {
        Id = product.Id;
        Name = product.Name;
        Price = product.Price;

        Links.Add(new Link { Rel = "self", Href = $"/api/products/{Id}", Method = "GET" });
        Links.Add(new Link { Rel = "update", Href = $"/api/products/{Id}", Method = "PUT" });
        Links.Add(new Link { Rel = "delete", Href = $"/api/products/{Id}", Method = "DELETE" });
    }
}

public class Link
{
    public string Rel { get; set; }
    public string Href { get; set; }
    public string Method { get; set; }
}

Best Practices for RESTful APIs

URL Design

  • Use nouns, not verbs: /products not /getProducts
  • Use plural nouns: /products not /product
  • Use lowercase with hyphens: /product-categories
  • Avoid deep nesting: prefer /products/123/reviews over /products/categories/electronics/items/123/reviews

Filtering, Sorting, Pagination

// GET /api/products?category=electronics&sort=price&order=asc&page=1&limit=10
[HttpGet]
public async Task<IActionResult> GetProducts(
    [FromQuery] string category,
    [FromQuery] string sort = "name",
    [FromQuery] string order = "asc",
    [FromQuery] int page = 1,
    [FromQuery] int limit = 10)
{
    var query = _context.Products.AsQueryable();

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

    query = order.ToLower() == "desc"
        ? query.OrderByDescending(GetSortExpression(sort))
        : query.OrderBy(GetSortExpression(sort));

    var totalCount = await query.CountAsync();
    var products = await query
        .Skip((page - 1) * limit)
        .Take(limit)
        .ToListAsync();

    var result = new
    {
        Data = products,
        Pagination = new
        {
            Page = page,
            Limit = limit,
            TotalCount = totalCount,
            TotalPages = (int)Math.Ceiling(totalCount / (double)limit)
        }
    };

    return Ok(result);
}

private Expression<Func<Product, object>> GetSortExpression(string sortBy)
{
    return sortBy.ToLower() switch
    {
        "price" => p => p.Price,
        "name" => p => p.Name,
        _ => p => p.Id
    };
}

Error Handling

public class ErrorResponse
{
    public string Type { get; set; }
    public string Title { get; set; }
    public int Status { get; set; }
    public string Detail { get; set; }
    public Dictionary<string, string[]> Errors { get; set; }
}

[ApiController]
public class BaseController : ControllerBase
{
    protected IActionResult BadRequestWithErrors(Dictionary<string, string[]> errors)
    {
        var errorResponse = new ErrorResponse
        {
            Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
            Title = "Bad Request",
            Status = 400,
            Detail = "One or more validation errors occurred.",
            Errors = errors
        };

        return BadRequest(errorResponse);
    }
}

Content Negotiation

What is Content Negotiation?

Content negotiation is the process of selecting the best representation for a given response when there are multiple representations available. It allows clients to specify their preferred format for request/response bodies.

Content Types in ASP.NET Core

1. Input Formatters

Handle incoming request bodies (deserialization)

2. Output Formatters

Handle outgoing response bodies (serialization)

Configuring Content Negotiation

// Program.cs or Startup.cs
builder.Services.AddControllers(options =>
{
    // Clear default formatters
    options.InputFormatters.Clear();
    options.OutputFormatters.Clear();

    // Add JSON formatter
    options.InputFormatters.Add(new SystemTextJsonInputFormatter(
        new JsonOptions
        {
            PropertyNameCaseInsensitive = true,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase
        }));

    options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(
        new JsonOptions
        {
            PropertyNameCaseInsensitive = true,
            PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
            WriteIndented = false
        }));

    // Add XML formatter
    options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter(
        new XmlOptions { UseXmlSerializer = false }));

    options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
});

Accept Header Negotiation

Clients can specify preferred content types using the Accept header:

Accept: application/json, application/xml;q=0.9, */*;q=0.8

Custom Content Negotiation

public class CustomContentNegotiator : IContentNegotiator
{
    public ContentNegotiationResult Negotiate(
        ContentNegotiationContext context)
    {
        var acceptHeader = context.Request.Headers.Accept.ToString();

        if (acceptHeader.Contains("application/vnd.api+json"))
        {
            return new ContentNegotiationResult(
                new MediaTypeHeaderValue("application/vnd.api+json"),
                context.ConnegContext.FirstFormatter);
        }

        // Default to JSON
        return new ContentNegotiationResult(
            new MediaTypeHeaderValue("application/json"),
            context.ConnegContext.FirstFormatter);
    }
}

// Register custom negotiator
builder.Services.AddControllers(options =>
{
    options.RespectBrowserAcceptHeader = true;
    options.ReturnHttpNotAcceptable = true;
}).AddXmlDataContractSerializerFormatters();

Format-Specific Actions

[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
    [HttpGet("export")]
    [Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")]
    public async Task<IActionResult> ExportToExcel()
    {
        var products = await _productService.GetAllProductsAsync();
        var excelData = GenerateExcel(products);

        return File(excelData, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "products.xlsx");
    }

    [HttpGet("v2")]
    [Produces("application/vnd.api.v2+json")]
    public async Task<IActionResult> GetProductsV2()
    {
        var products = await _productService.GetAllProductsAsync();
        return Ok(new { data = products, version = "2.0" });
    }
}

Custom Media Types

// Custom media type for API versioning
public static class MediaTypes
{
    public const string Json = "application/json";
    public const string Xml = "application/xml";
    public const string VndApiJson = "application/vnd.api+json";
    public const string VndApiV1Json = "application/vnd.api.v1+json";
    public const string VndApiV2Json = "application/vnd.api.v2+json";
}

[HttpGet]
[Produces(MediaTypes.VndApiV1Json, MediaTypes.Json)]
public async Task<IActionResult> GetProducts()
{
    var products = await _productService.GetAllProductsAsync();
    return Ok(products);
}

Content Negotiation Best Practices

  1. Default to JSON - Most modern APIs use JSON as the default format
  2. Support XML - For legacy system compatibility
  3. Use Vendor Media Types - For API versioning (application/vnd.api.v1+json)
  4. Respect Accept Headers - Honor client preferences
  5. Provide Format Overrides - Allow query parameters like ?format=xml
  6. Document Supported Formats - Clearly document available content types

API Versioning Strategies

Why API Versioning?

APIs evolve over time. Versioning ensures backward compatibility while allowing new features.

Common Versioning Strategies

1. URI Versioning

Include version in the URL path.

Pros:

  • Explicit and clear
  • Easy to understand
  • Caching friendly

Cons:

  • Violates REST principles (version is not a resource)
  • Requires routing changes
// URI Versioning
[Route("api/v1/[controller]")]
[ApiController]
public class ProductsV1Controller : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetProducts() { ... }
}

[Route("api/v2/[controller]")]
[ApiController]
public class ProductsV2Controller : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetProducts() { ... }
}

// Routing configuration
app.MapControllers();

2. Query Parameter Versioning

Pass version as query parameter.

Pros:

  • Single endpoint
  • Easy to implement

Cons:

  • Less visible in URLs
  • Can be forgotten
  • Not cache-friendly
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetProducts([FromQuery] string version = "1")
    {
        if (version == "2")
        {
            // V2 logic
            return await GetProductsV2();
        }

        // V1 logic
        return await GetProductsV1();
    }
}

3. Header Versioning

Use custom headers for versioning.

Pros:

  • Keeps URLs clean
  • Follows REST principles
  • Good for content negotiation

Cons:

  • Less discoverable
  • Requires documentation
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> GetProducts()
    {
        var version = Request.Headers["X-API-Version"].ToString();

        if (version == "2")
        {
            // V2 logic
            return await GetProductsV2();
        }

        // V1 logic
        return await GetProductsV1();
    }
}

4. Media Type Versioning

Use Accept header with vendor media types.

Pros:

  • REST-compliant
  • Leverages content negotiation
  • Supports multiple representations

Cons:

  • Complex to implement
  • Less intuitive
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
    [HttpGet]
    [Produces("application/vnd.api.v1+json", "application/vnd.api.v2+json")]
    public async Task<IActionResult> GetProducts()
    {
        var acceptHeader = Request.Headers.Accept.ToString();

        if (acceptHeader.Contains("vnd.api.v2"))
        {
            // V2 response
            Response.ContentType = "application/vnd.api.v2+json";
            return await GetProductsV2();
        }

        // V1 response
        Response.ContentType = "application/vnd.api.v1+json";
        return await GetProductsV1();
    }
}

ASP.NET Core API Versioning Package

Microsoft provides a comprehensive API versioning package.

// Program.cs
builder.Services.AddApiVersioning(options =>
{
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.ReportApiVersions = true;
    options.ApiVersionReader = ApiVersionReader.Combine(
        new UrlSegmentApiVersionReader(),
        new HeaderApiVersionReader("X-API-Version"),
        new MediaTypeApiVersionReader("version"));
});

// Controller
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    [MapToApiVersion("1.0")]
    public async Task<IActionResult> GetProductsV1() { ... }

    [HttpGet]
    [MapToApiVersion("2.0")]
    public async Task<IActionResult> GetProductsV2() { ... }
}

Versioning Best Practices

  1. Plan for Versioning from Day One
  2. Use Semantic Versioning (Major.Minor.Patch)
  3. Document Version Changes clearly
  4. Support Multiple Versions simultaneously
  5. Deprecation Policy - warn users before removing versions
  6. Version Headers in responses
  7. Test All Versions in CI/CD

Handling Breaking Changes

[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    [MapToApiVersion("1.0")]
    public async Task<IActionResult> GetProductsV1()
    {
        var products = await _productService.GetAllProductsAsync();
        return Ok(products.Select(p => new ProductV1Dto
        {
            Id = p.Id,
            Name = p.Name,
            Price = p.Price
        }));
    }

    [HttpGet]
    [MapToApiVersion("2.0")]
    public async Task<IActionResult> GetProductsV2()
    {
        var products = await _productService.GetAllProductsAsync();
        return Ok(products.Select(p => new ProductV2Dto
        {
            Id = p.Id,
            Name = p.Name,
            Price = p.Price,
            Category = p.Category,
            InStock = p.Quantity > 0
        }));
    }
}

OpenAPI/Swagger Integration

What is OpenAPI?

OpenAPI Specification (formerly Swagger) is a standard for describing REST APIs. It allows tools to generate documentation, client libraries, and server stubs.

Setting up Swashbuckle.AspNetCore

// Program.cs
builder.Services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo
    {
        Title = "My API",
        Version = "v1",
        Description = "A sample API",
        Contact = new OpenApiContact
        {
            Name = "API Support",
            Email = "support@example.com"
        }
    });

    c.SwaggerDoc("v2", new OpenApiInfo
    {
        Title = "My API",
        Version = "v2",
        Description = "A sample API v2"
    });

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

    // Enable JWT Bearer token support
    c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
    {
        Description = "JWT Authorization header using the Bearer scheme",
        Name = "Authorization",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
        Scheme = "Bearer"
    });

    c.AddSecurityRequirement(new OpenApiSecurityRequirement
    {
        {
            new OpenApiSecurityScheme
            {
                Reference = new OpenApiReference
                {
                    Type = ReferenceType.SecurityScheme,
                    Id = "Bearer"
                }
            },
            Array.Empty<string>()
        }
    });
});

app.UseSwagger();
app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1");
    c.SwaggerEndpoint("/swagger/v2/swagger.json", "My API v2");
});

Documenting Controllers and Actions

/// <summary>
/// Manages product operations
/// </summary>
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase
{
    /// <summary>
    /// Gets all products
    /// </summary>
    /// <param name="category">Optional category filter</param>
    /// <param name="page">Page number (default: 1)</param>
    /// <param name="limit">Items per page (default: 10)</param>
    /// <returns>List of products</returns>
    /// <response code="200">Returns the list of products</response>
    /// <response code="400">If parameters are invalid</response>
    [HttpGet]
    [ProducesResponseType(typeof(IEnumerable<ProductDto>), 200)]
    [ProducesResponseType(400)]
    public async Task<IActionResult> GetProducts(
        [FromQuery] string category,
        [FromQuery, Range(1, int.MaxValue)] int page = 1,
        [FromQuery, Range(1, 100)] int limit = 10)
    {
        // Implementation
    }

    /// <summary>
    /// Creates a new product
    /// </summary>
    /// <param name="product">Product data</param>
    /// <returns>Created product</returns>
    /// <response code="201">Returns the created product</response>
    /// <response code="400">If product data is invalid</response>
    [HttpPost]
    [ProducesResponseType(typeof(ProductDto), 201)]
    [ProducesResponseType(400)]
    public async Task<IActionResult> CreateProduct([FromBody] CreateProductDto product)
    {
        // Implementation
    }
}

Custom Schema Examples

builder.Services.AddSwaggerGen(c =>
{
    c.MapType<ProductDto>(() => new OpenApiSchema
    {
        Example = new OpenApiObject
        {
            ["id"] = new OpenApiInteger(1),
            ["name"] = new OpenApiString("Sample Product"),
            ["price"] = new OpenApiDouble(29.99),
            ["category"] = new OpenApiString("Electronics")
        }
    });
});

API Versioning with Swagger

builder.Services.AddSwaggerGen(c =>
{
    c.DocInclusionPredicate((version, desc) =>
    {
        if (!desc.TryGetMethodInfo(out var methodInfo)) return false;

        var versions = methodInfo.DeclaringType?
            .GetCustomAttributes(true)
            .OfType<ApiVersionAttribute>()
            .SelectMany(attr => attr.Versions);

        var maps = methodInfo.GetCustomAttributes(true)
            .OfType<MapToApiVersionAttribute>()
            .SelectMany(attr => attr.Versions)
            .ToArray();

        return versions?.Any(v => $"v{v}" == version) == true &&
               (!maps.Any() || maps.Any(v => $"v{v}" == version));
    });
});

Advanced Swagger Configuration

Custom Operation Filters

public class AddRequiredHeaderParameter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        if (operation.Parameters == null)
            operation.Parameters = new List<OpenApiParameter>();

        operation.Parameters.Add(new OpenApiParameter
        {
            Name = "X-API-Key",
            In = ParameterLocation.Header,
            Required = true,
            Schema = new OpenApiSchema { Type = "string" }
        });
    }
}

// Register filter
builder.Services.AddSwaggerGen(c =>
{
    c.OperationFilter<AddRequiredHeaderParameter>();
});

Custom Document Filters

public class AddVersionParameter : IDocumentFilter
{
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        swaggerDoc.Info.Version = "1.0.0";
        swaggerDoc.Info.Title = "My API";
    }
}

Testing APIs with Swagger UI

Swagger UI provides an interactive interface for testing API endpoints directly from the documentation.

Best Practices for OpenAPI Documentation

  1. Comprehensive Descriptions - Document all endpoints, parameters, and responses
  2. Examples - Provide request/response examples
  3. Error Responses - Document all possible error codes
  4. Authentication - Show how to authenticate
  5. Versioning - Document version differences
  6. Deprecation Notices - Mark deprecated endpoints
  7. Rate Limiting - Document rate limits if applicable

Interview Questions

Basic Level (1-2 years experience)

Q1. What is REST and what are its key principles?

Answer: REST (Representational State Transfer) is an architectural style for designing networked applications. Key principles include:

  • Stateless: Each request contains all necessary information
  • Client-Server: Clear separation of concerns
  • Uniform Interface: Consistent way to interact with resources
  • Resource-Based: Everything is a resource with unique URIs
  • HTTP Methods: GET, POST, PUT, DELETE for CRUD operations
  • Stateless Communication: No client state stored on server

Q2. What are the main HTTP methods used in REST APIs?

Answer:

  • GET: Retrieve resource(s) - safe and idempotent
  • POST: Create new resource - not idempotent
  • PUT: Update entire resource - idempotent
  • PATCH: Partial update - not necessarily idempotent
  • DELETE: Remove resource - idempotent

Q3. What is content negotiation in Web APIs?

Answer: Content negotiation is the process of selecting the best representation for a response when multiple formats are available. It allows clients to specify preferred content types via Accept headers, and servers respond with appropriate format (JSON, XML, etc.).

Q4. What are the common API versioning strategies?

Answer:

  1. URI Versioning/api/v1/products
  2. Query Parameters/api/products?version=1
  3. HeadersX-API-Version: 1
  4. Media TypesAccept: application/vnd.api.v1+json

Q5. What is Swagger/OpenAPI?

Answer: OpenAPI (formerly Swagger) is a specification for describing REST APIs. It provides a standard way to document API endpoints, request/response formats, authentication, etc. Tools can generate documentation, client libraries, and server stubs from OpenAPI definitions.


Intermediate Level (2-3 years experience)

Q6. How do you implement content negotiation in ASP.NET Core?

Answer:

// In Program.cs
builder.Services.AddControllers(options =>
{
    options.RespectBrowserAcceptHeader = true;
    options.ReturnHttpNotAcceptable = true;
}).AddXmlDataContractSerializerFormatters();

// In controller
[HttpGet]
[Produces("application/json", "application/xml")]
public IActionResult GetData()
{
    var data = GetData();
    return Ok(data);
}

Q7. What are the pros and cons of different API versioning approaches?

Answer: URI Versioning:

  • Pros: Explicit, cache-friendly, easy to understand
  • Cons: Violates REST, requires routing changes

Header Versioning:

  • Pros: REST-compliant, clean URLs
  • Cons: Less discoverable, requires documentation

Query Parameter:

  • Pros: Simple to implement
  • Cons: Clutters URLs, not cache-friendly

Media Type:

  • Pros: REST-compliant, supports content negotiation
  • Cons: Complex, less intuitive

Q8. How do you handle API versioning with the Microsoft.AspNetCore.Mvc.Versioning package?

Answer:

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

[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase
{
    [HttpGet]
    [MapToApiVersion("1.0")]
    public IActionResult GetV1() { ... }

    [HttpGet]
    [MapToApiVersion("2.0")]
    public IActionResult GetV2() { ... }
}

Q9. What is HATEOAS and why is it important?

Answer: HATEOAS (Hypermedia as the Engine of Application State) means that API responses include links to related resources and available actions. This allows clients to discover API capabilities dynamically rather than hardcoding URLs.

Example response:

{
  "id": 1,
  "name": "Product A",
  "_links": {
    "self": { "href": "/api/products/1" },
    "update": { "href": "/api/products/1", "method": "PUT" },
    "delete": { "href": "/api/products/1", "method": "DELETE" }
  }
}

Q10. How do you implement global error handling in ASP.NET Core Web API?

Answer:

// Custom exception filter
public class ApiExceptionFilter : IExceptionFilter
{
    public void OnException(ExceptionContext context)
    {
        var error = new ErrorResponse
        {
            Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1",
            Title = "Internal Server Error",
            Status = 500,
            Detail = "An unexpected error occurred."
        };

        context.Result = new ObjectResult(error)
        {
            StatusCode = 500
        };
        context.ExceptionHandled = true;
    }
}

// Register filter
builder.Services.AddControllers(options =>
{
    options.Filters.Add<ApiExceptionFilter>();
});

Advanced Level (3+ years experience)

Q11. How would you design a RESTful API for a complex domain with nested resources?

Answer: For complex domains, consider:

  1. Resource Modeling: Identify main entities and relationships
  2. URL Design: Use shallow nesting, avoid deep hierarchies
  3. Sub-resources/users/123/posts vs /posts?userId=123
  4. Actions: Use proper HTTP methods, avoid verbs in URLs
  5. Filtering/Sorting: Query parameters for complex queries
  6. Pagination: Consistent pagination across all list endpoints

Example:

// Good design
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    [HttpGet("{orderId}/items")]
    public IActionResult GetOrderItems(int orderId) { ... }

    [HttpPost("{orderId}/items")]
    public IActionResult AddOrderItem(int orderId, [FromBody] OrderItem item) { ... }
}

// Avoid deep nesting
// Bad: /api/users/123/orders/456/items/789
// Good: /api/order-items/789?orderId=456&userId=123

Q12. How do you implement API rate limiting and throttling?

Answer:

// Using AspNetCoreRateLimit package
builder.Services.AddMemoryCache();
builder.Services.Configure<IpRateLimitOptions>(options =>
{
    options.GeneralRules = new List<RateLimitRule>
    {
        new RateLimitRule
        {
            Endpoint = "*",
            Limit = 100,
            Period = "1m"
        }
    };
});

builder.Services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
builder.Services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();

app.UseIpRateLimiting();

Q13. What strategies do you use for API security beyond authentication?

Answer:

  1. Input Validation: Model validation, custom validators
  2. Output Encoding: Prevent XSS in responses
  3. Rate Limiting: Prevent abuse
  4. CORS: Control cross-origin requests
  5. HTTPS Only: Enforce secure connections
  6. API Keys: For client identification
  7. Request Signing: Verify request integrity
  8. Audit Logging: Track all API access

Q14. How do you handle long-running operations in Web APIs?

Answer:

  1. Asynchronous Processing: Return 202 Accepted with status URL
  2. Webhooks: Notify clients when operation completes
  3. Polling: Provide status endpoint for clients to check
  4. WebSockets/SignalR: Real-time updates

Example:

[HttpPost("reports/generate")]
public async Task<IActionResult> GenerateReport([FromBody] ReportRequest request)
{
    var operationId = await _reportService.StartReportGenerationAsync(request);

    var result = new
    {
        OperationId = operationId,
        Status = "Processing",
        StatusUrl = Url.Action("GetReportStatus", new { operationId })
    };

    return Accepted(result);
}

[HttpGet("reports/status/{operationId}")]
public async Task<IActionResult> GetReportStatus(string operationId)
{
    var status = await _reportService.GetReportStatusAsync(operationId);

    if (status.IsCompleted)
    {
        return Ok(new
        {
            Status = "Completed",
            DownloadUrl = Url.Action("DownloadReport", new { operationId })
        });
    }

    return Ok(new { Status = "Processing" });
}

Q15. How do you implement API analytics and monitoring?

Answer:

// Middleware for API analytics
public class ApiAnalyticsMiddleware
{
    private readonly RequestDelegate _next;

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

    public async Task InvokeAsync(HttpContext context)
    {
        var startTime = DateTime.UtcNow;
        var originalBodyStream = context.Response.Body;

        using (var responseBody = new MemoryStream())
        {
            context.Response.Body = responseBody;

            await _next(context);

            var endTime = DateTime.UtcNow;
            var duration = endTime - startTime;

            // Log analytics
            await _analyticsService.LogRequestAsync(new ApiRequestLog
            {
                Method = context.Request.Method,
                Path = context.Request.Path,
                StatusCode = context.Response.StatusCode,
                Duration = duration,
                UserAgent = context.Request.Headers["User-Agent"],
                IpAddress = context.Connection.RemoteIpAddress?.ToString()
            });

            await responseBody.CopyToAsync(originalBodyStream);
        }
    }
}

Q16. What are the challenges of API versioning and how do you mitigate them?

Answer: Challenges:

  1. Backward Compatibility: Breaking changes affect existing clients
  2. Maintenance Burden: Supporting multiple versions
  3. Documentation Complexity: Keeping docs for all versions
  4. Testing: Testing all supported versions

Mitigation Strategies:

  1. Semantic Versioning: Clear rules for breaking changes
  2. Deprecation Policy: Warn clients before removing versions
  3. Feature Flags: Gradual rollout of new features
  4. Contract Testing: Ensure versions meet their contracts
  5. Automated Testing: Test all versions in CI/CD
  6. Monitoring: Track usage of different versions

Q17. How do you design APIs for mobile applications?

Answer:

  1. Efficient Data Transfer: Minimize payload size
  2. Pagination: Essential for large datasets
  3. Conditional Requests: ETags, Last-Modified headers
  4. Offline Support: Sync endpoints for offline-first apps
  5. Push Notifications: Webhooks or Firebase for real-time updates
  6. Version Compatibility: Support older app versions longer
  7. Error Handling: Clear error messages for mobile UX

Q18. What is GraphQL and how does it compare to REST?

Answer: GraphQL is a query language for APIs that allows clients to request exactly the data they need.

Comparison:

  • REST: Fixed endpoints return fixed data structures
  • GraphQL: Single endpoint, clients specify exact data needs
  • Over-fetching: REST can return unnecessary data, GraphQL avoids this
  • Under-fetching: REST may require multiple calls, GraphQL can do in one
  • Versioning: GraphQL schemas evolve without versioning
  • Caching: REST easier to cache, GraphQL more complex

Q19. How do you implement API caching strategies?

Answer:

  1. HTTP Caching: Cache-Control, ETags, Last-Modified
  2. Application-Level Caching: In-memory, distributed cache
  3. Database Query Caching: Cache expensive queries
  4. Response Caching: Cache entire responses
[HttpGet]
[ResponseCache(Duration = 300, Location = ResponseCacheLocation.Any)]
public async Task<IActionResult> GetProducts()
{
    var products = await _cache.GetOrCreateAsync("products", async entry =>
    {
        entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
        return await _productService.GetAllProductsAsync();
    });

    return Ok(products);
}

Q20. How do you handle database concurrency in Web APIs?

Answer:

[HttpPut("{id}")]
public async Task<IActionResult> UpdateProduct(int id, [FromBody] UpdateProductDto dto)
{
    try
    {
        var product = await _context.Products.FindAsync(id);
        if (product == null) return NotFound();

        // Check ETag for optimistic concurrency
        var etag = Request.Headers["If-Match"].ToString();
        if (!string.IsNullOrEmpty(etag) && etag != $"\"{product.RowVersion}\"")
        {
            return StatusCode(412, "Resource has been modified");
        }

        // Update entity
        _mapper.Map(dto, product);
        product.RowVersion = Guid.NewGuid().ToString();

        await _context.SaveChangesAsync();

        // Return updated entity with new ETag
        Response.Headers["ETag"] = $"\"{product.RowVersion}\"";
        return Ok(product);
    }
    catch (DbUpdateConcurrencyException)
    {
        return Conflict("Resource was modified by another request");
    }
}

Additional Interview Tips

API Design Best Practices

  • Use Consistent Naming: camelCase for JSON, PascalCase for .NET
  • Handle Errors Gracefully: Meaningful error messages and appropriate status codes
  • Document Everything: Use OpenAPI/Swagger for comprehensive docs
  • Version from Day One: Plan for evolution
  • Security First: Authentication, authorization, input validation
  • Performance Matters: Caching, pagination, async operations

Common Anti-Patterns to Avoid

  • Over-Engineering: Keep it simple, don't add unnecessary complexity
  • Tight Coupling: Design for change, use dependency injection
  • Ignoring HTTP Semantics: Use correct status codes and methods
  • Poor Error Handling: Don't expose internal errors to clients
  • No Pagination: Large datasets without pagination cause performance issues
  • Inconsistent Responses: Standardize response formats across the API

Testing Strategies

  • Unit Tests: Test business logic in isolation
  • Integration Tests: Test API endpoints with real dependencies
  • Contract Tests: Ensure API contracts are maintained
  • Performance Tests: Load testing for scalability
  • Security Tests: Penetration testing and vulnerability scanning

Deployment Considerations

  • Environment Parity: Similar environments for dev/staging/prod
  • Blue-Green Deployments: Zero-downtime deployments
  • Feature Toggles: Enable/disable features without redeployment
  • Monitoring: Track API usage, errors, and performance
  • Rollback Strategy: Quick rollback plans for failed deployments

Comments

Popular posts from this blog