100 REST API Interview Questions and Answers for Developers
Interview preparation · Technical guide
REST Web API Interview Questions and Answers
REST, HTTP, security, observability, testing, and integration topics. HTTP semantics and client-side security controls are described with current standards in mind.
Examples are independent teaching snippets and may require application types, imports, packages, schema, and configuration. Framework behavior is version-dependent. Corrections address identified issues; the complete source code collection has not been compiled or integration-tested.
1. Explain the REST architectural style and its principles
REST (Representational State Transfer) is an architectural style for distributed hypermedia systems, not a protocol or standard.
Core Principles:
- Stateless: Each request contains all information needed
- Client-Server: Separation of concerns
- Cacheable: Responses must be cacheable
- Uniform Interface: Consistent interface across resources
- Layered System: Hierarchical architecture
- Code on Demand: Optional execution of code on client
C# Example - RESTful Controller:
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserService _userService;
public UsersController(IUserService userService)
{
_userService = userService;
}
// GET api/users - Stateless, cacheable
[HttpGet]
[ResponseCache(Duration = 300)] // Cacheable
public async Task<ActionResult<IEnumerable<UserDto>>> GetUsers()
{
var users = await _userService.GetAllAsync();
return Ok(users); // Uniform interface
}
// GET api/users/{id} - Resource-based
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var user = await _userService.GetByIdAsync(id);
if (user == null)
return NotFound(); // Standard HTTP status
return Ok(user);
}
}
2. What is the difference between REST and SOAP?
| Aspect | REST | SOAP |
|---|---|---|
| Protocol | HTTP-based | XML-based protocol |
| Data Format | JSON, XML, Text | XML only |
| State | Stateless | Can be stateful |
| Performance | Lightweight | Heavyweight |
| Caching | Built-in | Limited |
C# Example - SOAP vs REST:
// SOAP Service Contract
[ServiceContract]
public interface IUserService
{
[OperationContract]
User GetUser(int userId);
}
// SOAP Implementation
public class UserService : IUserService
{
public User GetUser(int userId)
{
// Complex XML serialization
return new User { Id = userId, Name = "John" };
}
}
// REST Implementation (same as above controller)
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
// Simple JSON response
return Ok(new UserDto { Id = id, Name = "John" });
}
3. Explain RESTful resource naming conventions
Best Practices:
- Use nouns, not verbs
- Use plural nouns for collections
- Use lowercase with hyphens
- Hierarchical relationships with forward slashes
C# Example:
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
// ✅ Good: /api/orders
[HttpGet]
public async Task<ActionResult<IEnumerable<OrderDto>>> GetOrders()
// ✅ Good: /api/orders/123
[HttpGet("{id}")]
public async Task<ActionResult<OrderDto>> GetOrder(int id)
// ✅ Good: /api/orders/123/items
[HttpGet("{orderId}/items")]
public async Task<ActionResult<IEnumerable<OrderItemDto>>> GetOrderItems(int orderId)
// ✅ Good: /api/orders/123/items/456
[HttpGet("{orderId}/items/{itemId}")]
public async Task<ActionResult<OrderItemDto>> GetOrderItem(int orderId, int itemId)
// ❌ Bad: /api/getOrders
// ❌ Bad: /api/order_list
// ❌ Bad: /api/Orders
}
4. What are the differences between REST and GraphQL?
| Aspect | REST | GraphQL |
|---|---|---|
| Data Fetching | Multiple endpoints | Single endpoint |
| Over-fetching | Common problem | Avoided |
| Under-fetching | Common problem | Avoided |
| Versioning | URL versioning | Schema evolution |
| Caching | HTTP caching | Custom caching |
C# Example - GraphQL vs REST:
// REST - Multiple endpoints
[HttpGet("users/{id}")]
public async Task<ActionResult<UserDto>> GetUser(int id)
[HttpGet("users/{id}/orders")]
public async Task<ActionResult<IEnumerable<OrderDto>>> GetUserOrders(int id)
[HttpGet("users/{id}/profile")]
public async Task<ActionResult<ProfileDto>> GetUserProfile(int id)
// GraphQL - Single endpoint
[HttpPost("graphql")]
public async Task<IActionResult> GraphQL([FromBody] GraphQLRequest request)
{
var schema = new Schema
{
Query = new UserQuery()
};
var result = await new DocumentExecuter().ExecuteAsync(_ =>
{
_.Schema = schema;
_.Query = request.Query;
_.Variables = request.Variables;
});
return Ok(result);
}
5. Explain HTTP methods and their semantics
HTTP method semantics matter: GET, HEAD, OPTIONS, and TRACE are safe; PUT and DELETE are defined as idempotent; POST is not generally idempotent. “Idempotent” means repeating the same request has the same intended server effect, even if status codes or logs differ. A GET must not change application state.
6. What is the difference between GET and POST?
| Aspect | GET | POST |
|---|---|---|
| Purpose | Retrieve data | Create/submit data |
| Safety | Safe (no side effects) | Not safe |
| Idempotency | Idempotent | Not idempotent |
| Caching | Cacheable | Not cacheable |
| Data in URL | Parameters in URL | Data in body |
| Data Size | Limited by URL length | No limit |
C# Example:
// GET - Safe, idempotent, cacheable
[HttpGet("search")]
public async Task<ActionResult<IEnumerable<ProductDto>>> SearchProducts(
[FromQuery] string name,
[FromQuery] decimal? minPrice,
[FromQuery] decimal? maxPrice)
{
// Can be cached, no side effects
var products = await _productService.SearchAsync(name, minPrice, maxPrice);
return Ok(products);
}
// POST - Not safe, not idempotent, not cacheable
[HttpPost("orders")]
public async Task<ActionResult<OrderDto>> CreateOrder([FromBody] CreateOrderDto dto)
{
// Creates new resource, has side effects
var order = await _orderService.CreateAsync(dto);
return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}
7. Explain REST status codes and their categories
Status Code Categories:
- 1xx (Informational): Request processing
- 2xx (Success): Request successful
- 3xx (Redirection): Further action needed
- 4xx (Client Error): Client made error
- 5xx (Server Error): Server error
C# Example:
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<OrderDto>> CreateOrder([FromBody] CreateOrderDto dto)
{
try
{
var order = await _orderService.CreateAsync(dto);
// 201 Created - Resource created successfully
return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}
catch (ValidationException ex)
{
// 400 Bad Request - Client error
return BadRequest(ex.Message);
}
catch (UnauthorizedAccessException)
{
// 401 Unauthorized - Authentication required
return Unauthorized();
}
catch (ForbiddenException)
{
// 403 Forbidden - Not authorized
return Forbid();
}
catch (NotFoundException)
{
// 404 Not Found - Resource not found
return NotFound();
}
catch (ConflictException)
{
// 409 Conflict - Resource conflict
return Conflict();
}
catch (Exception)
{
// 500 Internal Server Error - Server error
return StatusCode(500, "An unexpected error occurred");
}
}
}
8. What are the differences between 2xx, 4xx, and 5xx status codes?
2xx (Success):
- 200 OK: Request successful
- 201 Created: Resource created
- 204 No Content: Success but no content
4xx (Client Error):
- 400 Bad Request: Invalid request
- 401 Unauthorized: Authentication required
- 403 Forbidden: Not authorized
- 404 Not Found: Resource not found
5xx (Server Error):
- 500 Internal Server Error: Server error
- 502 Bad Gateway: Gateway error
- 503 Service Unavailable: Service unavailable
C# Example:
public class ApiResponse<T>
{
public int StatusCode { get; set; }
public string Message { get; set; }
public T Data { get; set; }
public bool IsSuccess { get; set; }
}
[ApiController]
public class ProductsController : ControllerBase
{
[HttpGet("{id}")]
public async Task<ActionResult<ApiResponse<ProductDto>>> GetProduct(int id)
{
try
{
var product = await _productService.GetByIdAsync(id);
if (product == null)
{
// 4xx - Client error (resource not found)
return NotFound(new ApiResponse<ProductDto>
{
StatusCode = 404,
Message = "Product not found",
IsSuccess = false
});
}
// 2xx - Success
return Ok(new ApiResponse<ProductDto>
{
StatusCode = 200,
Message = "Product retrieved successfully",
Data = product,
IsSuccess = true
});
}
catch (Exception ex)
{
// 5xx - Server error
return StatusCode(500, new ApiResponse<ProductDto>
{
StatusCode = 500,
Message = "Internal server error",
IsSuccess = false
});
}
}
}
9. Explain REST constraints and their importance
REST Constraints:
- Client-Server: Separation of concerns
- Stateless: No client context stored
- Cacheable: Responses must be cacheable
- Uniform Interface: Consistent interface
- Layered System: Hierarchical architecture
- Code on Demand: Optional client-side code
C# Example:
// Stateless - No session state
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
private readonly IOrderService _orderService;
private readonly ICacheService _cacheService;
public OrdersController(IOrderService orderService, ICacheService cacheService)
{
_orderService = orderService;
_cacheService = cacheService;
}
// Stateless - Each request is independent
[HttpGet("{id}")]
public async Task<ActionResult<OrderDto>> GetOrder(int id)
{
// No dependency on previous requests
var cacheKey = $"order:{id}";
var order = await _cacheService.GetAsync<OrderDto>(cacheKey);
if (order == null)
{
order = await _orderService.GetByIdAsync(id);
await _cacheService.SetAsync(cacheKey, order, TimeSpan.FromMinutes(30));
}
return Ok(order);
}
// Uniform Interface - Consistent HTTP methods
[HttpPost]
public async Task<ActionResult<OrderDto>> CreateOrder([FromBody] CreateOrderDto dto)
[HttpPut("{id}")]
public async Task<ActionResult<OrderDto>> UpdateOrder(int id, [FromBody] UpdateOrderDto dto)
[HttpDelete("{id}")]
public async Task<ActionResult> DeleteOrder(int id)
}
10. What is the difference between stateless and stateful APIs?
Stateless vs Stateful:
| Aspect | Stateless | Stateful |
|---|---|---|
| Session | No session state | Maintains session state |
| Scalability | Highly scalable | Limited scalability |
| Memory | No server memory | Uses server memory |
| Load Balancing | Easy | Complex |
C# Example:
// Stateless API
[ApiController]
[Route("api/[controller]")]
public class StatelessController : ControllerBase
{
[HttpPost("process")]
public async Task<ActionResult<ProcessResultDto>> ProcessData([FromBody] ProcessDataDto dto)
{
// All data comes from request - no server state
var result = await _processor.ProcessAsync(dto);
return Ok(result);
}
}
// Stateful API (Session-based)
[ApiController]
[Route("api/[controller]")]
public class StatefulController : ControllerBase
{
[HttpPost("login")]
public async Task<ActionResult> Login([FromBody] LoginDto dto)
{
var user = await _authService.AuthenticateAsync(dto);
// Store state in session
HttpContext.Session.SetString("UserId", user.Id.ToString());
HttpContext.Session.SetString("UserRole", user.Role);
return Ok(new { message = "Logged in successfully" });
}
[HttpGet("profile")]
public async Task<ActionResult<UserDto>> GetProfile()
{
// Retrieve state from session
var userId = HttpContext.Session.GetString("UserId");
if (string.IsNullOrEmpty(userId))
return Unauthorized();
var user = await _userService.GetByIdAsync(int.Parse(userId));
return Ok(user);
}
}
11. Explain HTTP request and response structure
HTTP Request Structure:
GET /api/users/123 HTTP/1.1
Host: api.example.com
Authorization: Bearer token123
Content-Type: application/json
Accept: application/json
{
"name": "John Doe"
}
HTTP Response Structure:
HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: max-age=3600
ETag: "abc123"
{
"id": 123,
"name": "John Doe",
"email": "john@example.com"
}
C# Example:
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<UserDto>> CreateUser([FromBody] CreateUserDto dto)
{
// Request headers available
var authHeader = Request.Headers["Authorization"].FirstOrDefault();
var contentType = Request.ContentType;
var userAgent = Request.Headers["User-Agent"].FirstOrDefault();
var user = await _userService.CreateAsync(dto);
// Set response headers
Response.Headers.Add("X-User-Id", user.Id.ToString());
Response.Headers.Add("X-Created-At", DateTime.UtcNow.ToString("R"));
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var user = await _userService.GetByIdAsync(id);
if (user == null)
return NotFound();
// Set caching headers
Response.Headers.Add("Cache-Control", "max-age=3600");
Response.Headers.Add("ETag", $"\"{user.GetHashCode()}\"");
return Ok(user);
}
}
12. What are the differences between HTTP/1.1 and HTTP/2?
| Aspect | HTTP/1.1 | HTTP/2 |
|---|---|---|
| Multiplexing | No | Yes |
| Header Compression | No | HPACK |
| Server Push | No | Yes |
| Binary Protocol | No | Yes |
| Connection Reuse | Limited | Efficient |
C# Example - HTTP/2 Configuration:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// Configure HTTP/2
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(5000, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps();
});
});
// HTTP/2 Server Push (if needed)
[HttpGet("page")]
public async Task<ActionResult> GetPage()
{
// HTTP/2 server push headers
Response.Headers.Add("Link", "</styles.css>; rel=preload; as=style");
Response.Headers.Add("Link", "</script.js>; rel=preload; as=script");
return Ok(new { content = "Page content" });
}
13. Explain HTTP headers and their types
Common HTTP Headers:
Request Headers:
- Authorization: Authentication
- Content-Type: Data format
- Accept: Expected response format
- User-Agent: Client information
Response Headers:
- Content-Type: Response format
- Cache-Control: Caching behavior
- ETag: Entity tag for caching
- Location: Redirect location
C# Example:
[ApiController]
[Route("api/[controller]")]
public class FilesController : ControllerBase
{
[HttpGet("{id}")]
public async Task<IActionResult> GetFile(int id)
{
var file = await _fileService.GetByIdAsync(id);
if (file == null)
return NotFound();
// Set response headers
Response.Headers.Add("Content-Disposition", $"attachment; filename=\"{file.Name}\"");
Response.Headers.Add("Content-Length", file.Size.ToString());
Response.Headers.Add("Last-Modified", file.ModifiedDate.ToString("R"));
Response.Headers.Add("ETag", $"\"{file.GetHashCode()}\"");
return File(file.Content, file.ContentType);
}
[HttpPost]
public async Task<ActionResult<FileDto>> UploadFile(IFormFile file)
{
// Check request headers
var contentType = Request.ContentType;
var contentLength = Request.ContentLength;
var userAgent = Request.Headers["User-Agent"].FirstOrDefault();
if (contentLength > 10 * 1024 * 1024) // 10MB limit
return BadRequest("File too large");
var uploadedFile = await _fileService.UploadAsync(file);
// Set response headers
Response.Headers.Add("X-File-Id", uploadedFile.Id.ToString());
Response.Headers.Add("X-Upload-Date", DateTime.UtcNow.ToString("R"));
return CreatedAtAction(nameof(GetFile), new { id = uploadedFile.Id }, uploadedFile);
}
}
14. What is the difference between request and response headers?
Request Headers:
- Sent by client to server
- Describe the request
- Include authentication, content type, etc.
Response Headers:
- Sent by server to client
- Describe the response
- Include caching, content type, etc.
C# Example:
[ApiController]
[Route("api/[controller]")]
public class HeadersController : ControllerBase
{
[HttpGet("info")]
public ActionResult<HeadersInfo> GetHeadersInfo()
{
// Request headers (from client)
var requestHeaders = new Dictionary<string, string>
{
["Authorization"] = Request.Headers["Authorization"].FirstOrDefault(),
["User-Agent"] = Request.Headers["User-Agent"].FirstOrDefault(),
["Accept"] = Request.Headers["Accept"].FirstOrDefault(),
["Content-Type"] = Request.ContentType,
["Host"] = Request.Headers["Host"].FirstOrDefault()
};
// Set response headers (to client)
Response.Headers.Add("X-Request-Id", Guid.NewGuid().ToString());
Response.Headers.Add("X-Server-Time", DateTime.UtcNow.ToString("R"));
Response.Headers.Add("Cache-Control", "no-cache");
return Ok(new HeadersInfo
{
RequestHeaders = requestHeaders,
ResponseHeaders = Response.Headers.ToDictionary(h => h.Key, h => h.Value.ToString())
});
}
}
public class HeadersInfo
{
public Dictionary<string, string> RequestHeaders { get; set; }
public Dictionary<string, string> ResponseHeaders { get; set; }
}
15. Explain HTTP status codes in detail
Detailed Status Codes:
2xx Success:
- 200 OK: Request successful
- 201 Created: Resource created
- 202 Accepted: Request accepted for processing
- 204 No Content: Success but no content
3xx Redirection:
- 301 Moved Permanently: Resource moved permanently
- 302 Found: Resource temporarily moved
- 304 Not Modified: Resource not modified
4xx Client Errors:
- 400 Bad Request: Invalid request
- 401 Unauthorized: Authentication required
- 403 Forbidden: Not authorized
- 404 Not Found: Resource not found
- 409 Conflict: Resource conflict
- 422 Unprocessable Entity: Validation error
5xx Server Errors:
- 500 Internal Server Error: Server error
- 502 Bad Gateway: Gateway error
- 503 Service Unavailable: Service unavailable
C# Example:
[ApiController]
[Route("api/[controller]")]
public class StatusCodesController : ControllerBase
{
[HttpPost("orders")]
public async Task<ActionResult<OrderDto>> CreateOrder([FromBody] CreateOrderDto dto)
{
try
{
// Validation
if (!ModelState.IsValid)
return BadRequest(ModelState); // 400
var order = await _orderService.CreateAsync(dto);
// 201 Created with location header
return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}
catch (ValidationException ex)
{
// 422 Unprocessable Entity
return UnprocessableEntity(new { errors = ex.Errors });
}
catch (ConflictException ex)
{
// 409 Conflict
return Conflict(new { message = ex.Message });
}
}
[HttpGet("orders/{id}")]
public async Task<ActionResult<OrderDto>> GetOrder(int id)
{
var order = await _orderService.GetByIdAsync(id);
if (order == null)
return NotFound(); // 404
// Check if modified
var ifModifiedSince = Request.Headers["If-Modified-Since"].FirstOrDefault();
if (!string.IsNullOrEmpty(ifModifiedSince))
{
var lastModified = DateTime.Parse(ifModifiedSince);
if (order.ModifiedDate <= lastModified)
return StatusCode(304); // Not Modified
}
return Ok(order); // 200
}
[HttpPut("orders/{id}")]
public async Task<ActionResult<OrderDto>> UpdateOrder(int id, [FromBody] UpdateOrderDto dto)
{
try
{
var order = await _orderService.UpdateAsync(id, dto);
return Ok(order); // 200
}
catch (NotFoundException)
{
return NotFound(); // 404
}
}
[HttpDelete("orders/{id}")]
public async Task<ActionResult> DeleteOrder(int id)
{
await _orderService.DeleteAsync(id);
return NoContent(); // 204
}
}
16. What are the differences between 200, 201, and 204?
200 indicates a successful response with a representation, 201 indicates successful creation and should normally include a Location header for the new resource, and 204 indicates success with no response content. A 204 response must not include a message body.
17. Explain error handling in REST APIs
Error Handling Best Practices:
- Consistent Error Format
- Appropriate Status Codes
- Detailed Error Messages
- Error Logging
- Security Considerations
C# Example:
// Global Exception Handler
public class GlobalExceptionHandler : IExceptionHandler
{
private readonly ILogger<GlobalExceptionHandler> _logger;
public GlobalExceptionHandler(ILogger<GlobalExceptionHandler> logger)
{
_logger = logger;
}
public async ValueTask<bool> TryHandleAsync(
HttpContext httpContext,
Exception exception,
CancellationToken cancellationToken)
{
_logger.LogError(exception, "An unhandled exception occurred");
var errorResponse = new ErrorResponse
{
TraceId = httpContext.TraceIdentifier,
Message = "An error occurred while processing your request.",
Details = new List<string>()
};
switch (exception)
{
case ValidationException validationEx:
httpContext.Response.StatusCode = 400;
errorResponse.Message = "Validation failed";
errorResponse.Details = validationEx.Errors;
break;
case NotFoundException:
httpContext.Response.StatusCode = 404;
errorResponse.Message = "Resource not found";
break;
case UnauthorizedAccessException:
httpContext.Response.StatusCode = 401;
errorResponse.Message = "Unauthorized access";
break;
case ForbiddenException:
httpContext.Response.StatusCode = 403;
errorResponse.Message = "Access forbidden";
break;
default:
httpContext.Response.StatusCode = 500;
errorResponse.Message = "Internal server error";
break;
}
httpContext.Response.ContentType = "application/json";
await httpContext.Response.WriteAsJsonAsync(errorResponse, cancellationToken);
return true;
}
}
public class ErrorResponse
{
public string TraceId { get; set; }
public string Message { get; set; }
public List<string> Details { get; set; } = new();
}
// Controller with specific error handling
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<OrderDto>> CreateOrder([FromBody] CreateOrderDto dto)
{
try
{
if (!ModelState.IsValid)
{
var errors = ModelState
.Where(x => x.Value.Errors.Count > 0)
.SelectMany(x => x.Value.Errors.Select(e => $"{x.Key}: {e.ErrorMessage}"))
.ToList();
return BadRequest(new ErrorResponse
{
Message = "Validation failed",
Details = errors
});
}
var order = await _orderService.CreateAsync(dto);
return CreatedAtAction(nameof(GetOrder), new { id = order.Id }, order);
}
catch (BusinessRuleException ex)
{
return BadRequest(new ErrorResponse
{
Message = ex.Message,
Details = new List<string> { ex.Details }
});
}
}
}
18. What is the difference between client and server errors?
Client Errors (4xx):
- Caused by client (browser, API consumer)
- Client should fix the request
- Examples: 400, 401, 403, 404, 409
Server Errors (5xx):
- Caused by server or infrastructure
- Client cannot fix
- Examples: 500, 502, 503, 504
C# Example:
[ApiController]
[Route("api/[controller]")]
public class ErrorExamplesController : ControllerBase
{
[HttpGet("client-errors")]
public ActionResult GetClientErrors()
{
// 400 Bad Request - Client sent invalid data
if (!ModelState.IsValid)
return BadRequest("Invalid request data");
// 401 Unauthorized - Client not authenticated
if (!User.Identity.IsAuthenticated)
return Unauthorized("Authentication required");
// 403 Forbidden - Client authenticated but not authorized
if (!User.IsInRole("Admin"))
return Forbid("Insufficient permissions");
// 404 Not Found - Client requested non-existent resource
var resource = _service.GetResource();
if (resource == null)
return NotFound("Resource not found");
// 409 Conflict - Client request conflicts with current state
if (_service.HasConflict())
return Conflict("Resource conflict");
return Ok();
}
[HttpGet("server-errors")]
public async Task<ActionResult> GetServerErrors()
{
try
{
// Simulate server errors
await _service.ProcessAsync();
return Ok();
}
catch (DatabaseException)
{
// 500 Internal Server Error - Database issue
return StatusCode(500, "Database error occurred");
}
catch (ExternalServiceException)
{
// 502 Bad Gateway - External service unavailable
return StatusCode(502, "External service unavailable");
}
catch (ServiceUnavailableException)
{
// 503 Service Unavailable - Service temporarily unavailable
return StatusCode(503, "Service temporarily unavailable");
}
catch (TimeoutException)
{
// 504 Gateway Timeout - Request timeout
return StatusCode(504, "Request timeout");
}
}
}
19. Explain HTTP caching headers and strategies
Caching Headers:
Cache-Control: Caching behaviorETag: Entity tag for validationLast-Modified: Last modification dateExpires: Expiration date
C# Example:
[ApiController]
[Route("api/[controller]")]
public class CachingController : ControllerBase
{
[HttpGet("products/{id}")]
public async Task<ActionResult<ProductDto>> GetProduct(int id)
{
var product = await _productService.GetByIdAsync(id);
if (product == null)
return NotFound();
// Set caching headers
Response.Headers.Add("Cache-Control", "public, max-age=3600"); // Cache for 1 hour
Response.Headers.Add("ETag", $"\"{product.GetHashCode()}\"");
Response.Headers.Add("Last-Modified", product.ModifiedDate.ToString("R"));
return Ok(product);
}
[HttpGet("products")]
public async Task<ActionResult<IEnumerable<ProductDto>>> GetProducts()
{
// Check if client has cached version
var ifNoneMatch = Request.Headers["If-None-Match"].FirstOrDefault();
var ifModifiedSince = Request.Headers["If-Modified-Since"].FirstOrDefault();
var products = await _productService.GetAllAsync();
var etag = $"\"{products.GetHashCode()}\"";
// Return 304 if not modified
if (ifNoneMatch == etag)
return StatusCode(304);
Response.Headers.Add("Cache-Control", "public, max-age=1800"); // 30 minutes
Response.Headers.Add("ETag", etag);
Response.Headers.Add("Last-Modified", DateTime.UtcNow.ToString("R"));
return Ok(products);
}
[HttpPost("products")]
public async Task<ActionResult<ProductDto>> CreateProduct([FromBody] CreateProductDto dto)
{
var product = await _productService.CreateAsync(dto);
// Invalidate cache
Response.Headers.Add("Cache-Control", "no-cache, no-store, must-revalidate");
Response.Headers.Add("Pragma", "no-cache");
Response.Headers.Add("Expires", "0");
return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
}
}
// Response Caching Middleware
[ResponseCache(Duration = 300, Location = ResponseCacheLocation.Any)]
[HttpGet("cached-data")]
public async Task<ActionResult<CachedDataDto>> GetCachedData()
{
var data = await _dataService.GetDataAsync();
return Ok(data);
}
20. What are the differences between ETag and Last-Modified?
ETag supports conditional requests such as If-None-Match and can be strong or weak. Last-Modified/If-Modified-Since uses timestamp granularity and can be less precise. Neither prevents all cache mistakes; choose directives and variation keys that reflect identity, authorization, language, and representation.
21. Explain REST API design principles
Answer: REST (Representational State Transfer) is an architectural style for designing networked applications. Key principles include:
Core Principles: - Stateless: Each request contains all information needed - Client-Server: Separation of concerns - Cacheable: Responses must be cacheable - Uniform Interface: Consistent resource identification and manipulation - Layered System: Components can't see beyond immediate layer - Code on Demand: Optional execution of code on client
C# Example:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
// GET /api/products - Retrieve all products
[HttpGet]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
{
var products = await _productService.GetAllAsync();
return Ok(products);
}
// GET /api/products/{id} - Retrieve specific product
[HttpGet("{id}")]
public async Task<ActionResult<Product>> GetProduct(int id)
{
var product = await _productService.GetByIdAsync(id);
if (product == null)
return NotFound();
return Ok(product);
}
// POST /api/products - Create new product
[HttpPost]
public async Task<ActionResult<Product>> CreateProduct([FromBody] CreateProductDto dto)
{
var product = await _productService.CreateAsync(dto);
return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
}
// PUT /api/products/{id} - Update entire product
[HttpPut("{id}")]
public async Task<IActionResult> UpdateProduct(int id, [FromBody] UpdateProductDto dto)
{
var result = await _productService.UpdateAsync(id, dto);
if (!result)
return NotFound();
return NoContent();
}
// DELETE /api/products/{id} - Delete product
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteProduct(int id)
{
var result = await _productService.DeleteAsync(id);
if (!result)
return NotFound();
return NoContent();
}
}
22. What are the differences between REST and RPC-style APIs?
Answer:
| Aspect | REST | RPC |
|---|---|---|
| Resource Focus | Resources and their states | Actions/operations |
| URLs | Noun-based (/products/123) |
Verb-based (/getProduct) |
| HTTP Methods | Uses standard methods (GET, POST, PUT, DELETE) | Often only POST |
| State | Stateless | Can be stateful |
| Caching | Built-in caching support | Limited caching |
REST Example:
[HttpGet("products/{id}")]
public async Task<ActionResult<Product>> GetProduct(int id)
{
// RESTful approach
return Ok(await _productService.GetByIdAsync(id));
}
RPC Example:
[HttpPost("getProduct")]
public async Task<ActionResult<Product>> GetProduct([FromBody] GetProductRequest request)
{
// RPC-style approach
return Ok(await _productService.GetByIdAsync(request.ProductId));
}
23. Explain API versioning strategies
Answer: API versioning helps maintain backward compatibility while evolving APIs.
Strategies:
- URL Versioning:
/api/v1/products - Header Versioning:
Accept: application/vnd.company.v1+json - Query Parameter:
/api/products?version=1 - Media Type:
Content-Type: application/vnd.company.v1+json
C# Implementation:
// 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("api-version"),
new QueryStringApiVersionReader("api-version")
);
});
builder.Services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
// Controller with versioning
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.0")]
public async Task<ActionResult<IEnumerable<ProductV1>>> GetProductsV1()
{
// V1 implementation
return Ok(await _productService.GetAllV1Async());
}
[HttpGet]
[MapToApiVersion("2.0")]
public async Task<ActionResult<IEnumerable<ProductV2>>> GetProductsV2()
{
// V2 implementation with additional fields
return Ok(await _productService.GetAllV2Async());
}
}
24. What is the difference between URL versioning and header versioning?
Answer:
| Aspect | URL Versioning | Header Versioning |
|---|---|---|
| Visibility | Visible in URL | Hidden in headers |
| Caching | Version-specific caching | Same URL, different cache keys |
| Client Support | Easy for all clients | Requires header support |
| Bookmarking | Version-specific bookmarks | Generic bookmarks |
| Implementation | Simpler routing | More complex header handling |
URL Versioning:
[Route("api/v{version:apiVersion}/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
{
// Accessible via /api/v1/products or /api/v2/products
return Ok(await _productService.GetAllAsync());
}
}
Header Versioning:
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
{
var apiVersion = Request.Headers["api-version"].FirstOrDefault();
// Accessible via same URL with different headers
return Ok(await _productService.GetAllAsync(apiVersion));
}
}
25. Explain API documentation and OpenAPI/Swagger
Answer: OpenAPI (formerly Swagger) is a specification for documenting REST APIs.
Benefits: - Interactive documentation - Code generation - Testing capabilities - Standardized format
C# Implementation:
// Program.cs
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Product API",
Version = "v1",
Description = "A sample API for managing products",
Contact = new OpenApiContact
{
Name = "API Support",
Email = "support@company.com"
}
});
// Include XML comments
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
// Add security definitions
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"
}
},
new string[] {}
}
});
});
// Controller with documentation
[ApiController]
[Route("api/[controller]")]
[Produces("application/json")]
public class ProductsController : ControllerBase
{
/// <summary>
/// Retrieves all products
/// </summary>
/// <returns>A list of all products</returns>
/// <response code="200">Returns the list of products</response>
/// <response code="401">If the user is not authenticated</response>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<Product>), 200)]
[ProducesResponseType(401)]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
{
return Ok(await _productService.GetAllAsync());
}
/// <summary>
/// Creates a new product
/// </summary>
/// <param name="dto">The product data</param>
/// <returns>The created product</returns>
/// <response code="201">Returns the newly created product</response>
/// <response code="400">If the product data is invalid</response>
[HttpPost]
[ProducesResponseType(typeof(Product), 201)]
[ProducesResponseType(400)]
public async Task<ActionResult<Product>> CreateProduct([FromBody] CreateProductDto dto)
{
var product = await _productService.CreateAsync(dto);
return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
}
}
26. What are the differences between OpenAPI 2.0 and 3.0?
Answer:
| Feature | OpenAPI 2.0 | OpenAPI 3.0 |
|---|---|---|
| Security | Single security scheme | Multiple security schemes |
| Request Body | Limited body support | Enhanced body support |
| Components | Definitions | Components (reusable) |
| Servers | Single host/basePath | Multiple servers |
| Media Types | Limited | Enhanced support |
| Callbacks | Not supported | Webhook support |
| Links | Not supported | Response linking |
OpenAPI 3.0 Example:
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "Product API",
Version = "v1",
Description = "A sample API for managing products",
Servers = new List<OpenApiServer>
{
new OpenApiServer { Url = "https://api.company.com" },
new OpenApiServer { Url = "https://staging-api.company.com" }
}
});
// Multiple security schemes
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.Http,
Scheme = "bearer",
BearerFormat = "JWT"
});
c.AddSecurityDefinition("ApiKey", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.ApiKey,
In = ParameterLocation.Header,
Name = "X-API-Key"
});
});
27. Explain API pagination and its implementation
Answer: Pagination helps manage large datasets by returning data in chunks.
Implementation Strategies: 1. Offset-based: Skip N records, take M records 2. Cursor-based: Use cursor to navigate 3. Keyset-based: Use field values as pagination keys
C# Implementation:
// Pagination DTOs
public class PaginationParameters
{
private const int MaxPageSize = 50;
private int _pageSize = 10;
public int PageNumber { get; set; } = 1;
public int PageSize
{
get => _pageSize;
set => _pageSize = value > MaxPageSize ? MaxPageSize : value;
}
}
public class PagedResponse<T>
{
public IEnumerable<T> Data { get; set; }
public int PageNumber { get; set; }
public int PageSize { get; set; }
public int TotalPages { get; set; }
public int TotalRecords { get; set; }
public bool HasPreviousPage => PageNumber > 1;
public bool HasNextPage => PageNumber < TotalPages;
}
// Controller implementation
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public async Task<ActionResult<PagedResponse<Product>>> GetProducts(
[FromQuery] PaginationParameters parameters)
{
var products = await _productService.GetPagedAsync(
parameters.PageNumber,
parameters.PageSize);
var totalRecords = await _productService.GetTotalCountAsync();
var totalPages = (int)Math.Ceiling(totalRecords / (double)parameters.PageSize);
var response = new PagedResponse<Product>
{
Data = products,
PageNumber = parameters.PageNumber,
PageSize = parameters.PageSize,
TotalPages = totalPages,
TotalRecords = totalRecords
};
return Ok(response);
}
}
// Service implementation
public class ProductService : IProductService
{
private readonly ApplicationDbContext _context;
public ProductService(ApplicationDbContext context)
{
_context = context;
}
public async Task<IEnumerable<Product>> GetPagedAsync(int pageNumber, int pageSize)
{
return await _context.Products
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
public async Task<int> GetTotalCountAsync()
{
return await _context.Products.CountAsync();
}
}
28. What is the difference between offset and cursor-based pagination?
Answer:
| Aspect | Offset-based | Cursor-based |
|---|---|---|
| Performance | Slower with large offsets | Consistent performance |
| Consistency | Can miss/duplicate items | Consistent results |
| Implementation | Simple to implement | More complex |
| User Experience | Jump to any page | Sequential navigation |
| Database | Uses OFFSET/LIMIT | Uses WHERE clauses |
Offset-based:
public async Task<IEnumerable<Product>> GetPagedAsync(int pageNumber, int pageSize)
{
return await _context.Products
.OrderBy(p => p.Id)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
}
Cursor-based:
public class CursorPaginationParameters
{
public string Cursor { get; set; }
public int PageSize { get; set; } = 10;
}
public class CursorPagedResponse<T>
{
public IEnumerable<T> Data { get; set; }
public string NextCursor { get; set; }
public bool HasMore { get; set; }
}
public async Task<CursorPagedResponse<Product>> GetCursorPagedAsync(
CursorPaginationParameters parameters)
{
var query = _context.Products.AsQueryable();
if (!string.IsNullOrEmpty(parameters.Cursor))
{
var cursorValue = Convert.ToInt32(parameters.Cursor);
query = query.Where(p => p.Id > cursorValue);
}
var products = await query
.OrderBy(p => p.Id)
.Take(parameters.PageSize + 1) // Take one extra to check if more exists
.ToListAsync();
var hasMore = products.Count > parameters.PageSize;
if (hasMore)
{
products.RemoveAt(products.Count - 1);
}
return new CursorPagedResponse<Product>
{
Data = products,
NextCursor = hasMore ? products.Last().Id.ToString() : null,
HasMore = hasMore
};
}
29. Explain API filtering, sorting, and searching
Answer: These features enhance API usability by allowing clients to customize data retrieval.
C# Implementation:
// Filter and sort DTOs
public class ProductFilterParameters
{
public string SearchTerm { get; set; }
public decimal? MinPrice { get; set; }
public decimal? MaxPrice { get; set; }
public string Category { get; set; }
public bool? InStock { get; set; }
public string SortBy { get; set; } = "name";
public string SortOrder { get; set; } = "asc";
}
// Controller
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public async Task<ActionResult<PagedResponse<Product>>> GetProducts(
[FromQuery] PaginationParameters pagination,
[FromQuery] ProductFilterParameters filter)
{
var products = await _productService.GetFilteredPagedAsync(
pagination, filter);
return Ok(products);
}
}
// Service implementation
public class ProductService : IProductService
{
public async Task<PagedResponse<Product>> GetFilteredPagedAsync(
PaginationParameters pagination,
ProductFilterParameters filter)
{
var query = _context.Products.AsQueryable();
// Apply filters
if (!string.IsNullOrEmpty(filter.SearchTerm))
{
query = query.Where(p =>
p.Name.Contains(filter.SearchTerm) ||
p.Description.Contains(filter.SearchTerm));
}
if (filter.MinPrice.HasValue)
{
query = query.Where(p => p.Price >= filter.MinPrice.Value);
}
if (filter.MaxPrice.HasValue)
{
query = query.Where(p => p.Price <= filter.MaxPrice.Value);
}
if (!string.IsNullOrEmpty(filter.Category))
{
query = query.Where(p => p.Category == filter.Category);
}
if (filter.InStock.HasValue)
{
query = query.Where(p => p.StockQuantity > 0 == filter.InStock.Value);
}
// Apply sorting
query = filter.SortBy.ToLower() switch
{
"name" => filter.SortOrder.ToLower() == "desc"
? query.OrderByDescending(p => p.Name)
: query.OrderBy(p => p.Name),
"price" => filter.SortOrder.ToLower() == "desc"
? query.OrderByDescending(p => p.Price)
: query.OrderBy(p => p.Price),
"createddate" => filter.SortOrder.ToLower() == "desc"
? query.OrderByDescending(p => p.CreatedDate)
: query.OrderBy(p => p.CreatedDate),
_ => query.OrderBy(p => p.Name)
};
var totalRecords = await query.CountAsync();
var products = await query
.Skip((pagination.PageNumber - 1) * pagination.PageSize)
.Take(pagination.PageSize)
.ToListAsync();
return new PagedResponse<Product>
{
Data = products,
PageNumber = pagination.PageNumber,
PageSize = pagination.PageSize,
TotalPages = (int)Math.Ceiling(totalRecords / (double)pagination.PageSize),
TotalRecords = totalRecords
};
}
}
30. What are the differences between query parameters and path parameters?
Answer:
| Aspect | Query Parameters | Path Parameters |
|---|---|---|
| Usage | Optional filters, sorting, pagination | Required resource identifiers |
| Syntax | ?key=value&key2=value2 |
/resource/{id}/subresource/{subId} |
| Length | Limited by URL length | Part of path structure |
| Encoding | URL encoded | Part of path |
| Caching | Different cache keys | Same cache key |
C# Examples:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
// Path parameter - required resource identifier
[HttpGet("{id}")]
public async Task<ActionResult<Product>> GetProduct(int id)
{
var product = await _productService.GetByIdAsync(id);
if (product == null)
return NotFound();
return Ok(product);
}
// Query parameters - optional filters
[HttpGet]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts(
[FromQuery] string category, // Optional filter
[FromQuery] decimal? minPrice, // Optional filter
[FromQuery] string sortBy = "name", // Optional sorting
[FromQuery] int page = 1, // Optional pagination
[FromQuery] int pageSize = 10) // Optional pagination
{
var products = await _productService.GetFilteredAsync(
category, minPrice, sortBy, page, pageSize);
return Ok(products);
}
// Mixed approach
[HttpGet("{category}/items")]
public async Task<ActionResult<IEnumerable<Product>>> GetProductsByCategory(
string category, // Path parameter
[FromQuery] decimal? minPrice, // Query parameter
[FromQuery] string sortBy = "name") // Query parameter
{
var products = await _productService.GetByCategoryAsync(
category, minPrice, sortBy);
return Ok(products);
}
}
Authentication & Authorization
31. Explain API authentication methods
Use well-defined authentication schemes such as OAuth/OIDC-based bearer tokens, mutual TLS where appropriate, or signed API keys for service identification. API keys are usually identifiers rather than user authorization. Always enforce authorization separately and protect secrets in transit and at rest.
32. What are the differences between Basic Auth and Bearer Token?
Answer:
| Aspect | Basic Auth | Bearer Token |
|---|---|---|
| Format | Authorization: Basic base64(username:password) |
Authorization: Bearer <token> |
| Security | Credentials sent with each request | Token-based, credentials not sent |
| Expiration | No built-in expiration | Configurable expiration |
| Storage | Credentials stored on client | Token stored on client |
| Revocation | Change password | Revoke token |
| Performance | Server validates credentials each time | Server validates token |
Basic Auth Implementation:
public class BasicAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private readonly IUserService _userService;
public BasicAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
IUserService userService)
: base(options, logger, encoder, clock)
{
_userService = userService;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey("Authorization"))
{
return AuthenticateResult.Fail("Authorization header not found");
}
var authHeader = Request.Headers["Authorization"].ToString();
if (!authHeader.StartsWith("Basic ", StringComparison.OrdinalIgnoreCase))
{
return AuthenticateResult.Fail("Basic authentication required");
}
var encodedCredentials = authHeader.Substring("Basic ".Length);
var credentials = Encoding.UTF8.GetString(Convert.FromBase64String(encodedCredentials));
var parts = credentials.Split(':');
if (parts.Length != 2)
{
return AuthenticateResult.Fail("Invalid credentials format");
}
var username = parts[0];
var password = parts[1];
var user = await _userService.ValidateCredentialsAsync(username, password);
if (user == null)
{
return AuthenticateResult.Fail("Invalid credentials");
}
var claims = new[]
{
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Role, user.Role)
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
Bearer Token Implementation:
[ApiController]
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class SecureController : ControllerBase
{
[HttpGet]
public IActionResult GetSecureData()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var username = User.FindFirst(ClaimTypes.Name)?.Value;
var roles = User.FindAll(ClaimTypes.Role).Select(c => c.Value);
return Ok(new
{
UserId = userId,
Username = username,
Roles = roles,
Message = "Secure data accessed successfully"
});
}
}
33. Explain OAuth 2.0 and its flows
Answer: OAuth 2.0 is an authorization framework that enables third-party applications to access resources on behalf of users.
OAuth 2.0 Flows:
- Authorization Code Flow (Most secure)
- Implicit Flow (Legacy, less secure)
- Client Credentials Flow (Machine-to-machine)
- Resource Owner Password Flow (Direct credentials)
C# Implementation:
// OAuth 2.0 Configuration
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme)
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://login.microsoftonline.com/common";
options.ClientId = builder.Configuration["AzureAd:ClientId"];
options.ClientSecret = builder.Configuration["AzureAd:ClientSecret"];
options.ResponseType = "code";
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("api://your-api-id/access_as_user");
});
// OAuth 2.0 Client Implementation
public class OAuth2Service : IOAuth2Service
{
private readonly HttpClient _httpClient;
private readonly IConfiguration _configuration;
public OAuth2Service(HttpClient httpClient, IConfiguration configuration)
{
_httpClient = httpClient;
_configuration = configuration;
}
public async Task<string> GetAccessTokenAsync(string authorizationCode, string redirectUri)
{
var tokenRequest = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "authorization_code"),
new KeyValuePair<string, string>("client_id", _configuration["OAuth2:ClientId"]),
new KeyValuePair<string, string>("client_secret", _configuration["OAuth2:ClientSecret"]),
new KeyValuePair<string, string>("code", authorizationCode),
new KeyValuePair<string, string>("redirect_uri", redirectUri)
});
var response = await _httpClient.PostAsync(_configuration["OAuth2:TokenEndpoint"], tokenRequest);
var tokenResponse = await response.Content.ReadAsStringAsync();
// Parse token response and return access token
var tokenData = JsonSerializer.Deserialize<TokenResponse>(tokenResponse);
return tokenData.AccessToken;
}
public async Task<string> RefreshTokenAsync(string refreshToken)
{
var tokenRequest = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "refresh_token"),
new KeyValuePair<string, string>("client_id", _configuration["OAuth2:ClientId"]),
new KeyValuePair<string, string>("client_secret", _configuration["OAuth2:ClientSecret"]),
new KeyValuePair<string, string>("refresh_token", refreshToken)
});
var response = await _httpClient.PostAsync(_configuration["OAuth2:TokenEndpoint"], tokenRequest);
var tokenResponse = await response.Content.ReadAsStringAsync();
var tokenData = JsonSerializer.Deserialize<TokenResponse>(tokenResponse);
return tokenData.AccessToken;
}
}
public class TokenResponse
{
[JsonPropertyName("access_token")]
public string AccessToken { get; set; }
[JsonPropertyName("refresh_token")]
public string RefreshToken { get; set; }
[JsonPropertyName("expires_in")]
public int ExpiresIn { get; set; }
[JsonPropertyName("token_type")]
public string TokenType { get; set; }
}
34. What is the difference between OAuth 2.0 and OAuth 1.0?
Answer:
| Aspect | OAuth 1.0 | OAuth 2.0 |
|---|---|---|
| Complexity | More complex | Simpler |
| Security | Digital signatures | Bearer tokens |
| Flows | Single flow | Multiple flows |
| Mobile Support | Poor | Excellent |
| Performance | Slower (signature calculation) | Faster |
| Adoption | Legacy | Industry standard |
OAuth 1.0 Example (Legacy):
public class OAuth1Service
{
public string GenerateSignature(string method, string url, Dictionary<string, string> parameters, string consumerSecret, string tokenSecret = "")
{
// OAuth 1.0 signature generation (complex)
var sortedParams = parameters.OrderBy(p => p.Key).ToList();
var paramString = string.Join("&", sortedParams.Select(p => $"{p.Key}={Uri.EscapeDataString(p.Value)}"));
var signatureBase = $"{method.ToUpper()}&{Uri.EscapeDataString(url)}&{Uri.EscapeDataString(paramString)}";
var signingKey = $"{Uri.EscapeDataString(consumerSecret)}&{Uri.EscapeDataString(tokenSecret)}";
using var hmac = new HMACSHA1(Encoding.ASCII.GetBytes(signingKey));
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.ASCII.GetBytes(signatureBase)));
return signature;
}
}
OAuth 2.0 Example (Modern):
public class OAuth2Service
{
public async Task<string> GetAccessTokenAsync(string clientId, string clientSecret)
{
// OAuth 2.0 client credentials flow (simple)
var tokenRequest = new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("grant_type", "client_credentials"),
new KeyValuePair<string, string>("client_id", clientId),
new KeyValuePair<string, string>("client_secret", clientSecret)
});
var response = await _httpClient.PostAsync("https://auth.server.com/token", tokenRequest);
var tokenResponse = await response.Content.ReadAsStringAsync();
var tokenData = JsonSerializer.Deserialize<TokenResponse>(tokenResponse);
return tokenData.AccessToken;
}
}
35. Explain JWT tokens and their structure
A JWT is a compact signed or encrypted token format, not automatically a secure session design. Validate signature, allowed algorithms, issuer, audience, expiry, and intended token use. Do not place secrets or sensitive claims in an unsigned/readable token merely because it is encoded.
36. What are the differences between JWT and session tokens?
Answer:
| Aspect | JWT | Session Tokens |
|---|---|---|
| Storage | Client-side | Server-side |
| Size | Larger (contains data) | Smaller (just ID) |
| Stateless | Yes | No (requires server storage) |
| Revocation | Difficult (until expiration) | Easy (delete session) |
| Scalability | Excellent (no server storage) | Limited (session storage) |
| Security | Depends on implementation | Server-controlled |
Session-based Authentication:
// Program.cs
builder.Services.AddSession(options =>
{
options.IdleTimeout = TimeSpan.FromMinutes(30);
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
});
// Session Controller
[ApiController]
[Route("api/[controller]")]
public class SessionController : ControllerBase
{
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] LoginRequest request)
{
var user = await _userService.ValidateCredentialsAsync(request.Username, request.Password);
if (user == null)
{
return Unauthorized();
}
// Store user data in session
HttpContext.Session.SetString("UserId", user.Id.ToString());
HttpContext.Session.SetString("Username", user.Username);
HttpContext.Session.SetString("Roles", JsonSerializer.Serialize(user.Roles));
return Ok(new { Message = "Login successful" });
}
[HttpGet("profile")]
public IActionResult GetProfile()
{
var userId = HttpContext.Session.GetString("UserId");
var username = HttpContext.Session.GetString("Username");
var rolesJson = HttpContext.Session.GetString("Roles");
if (string.IsNullOrEmpty(userId))
{
return Unauthorized();
}
var roles = JsonSerializer.Deserialize<List<string>>(rolesJson);
return Ok(new
{
UserId = userId,
Username = username,
Roles = roles
});
}
[HttpPost("logout")]
public IActionResult Logout()
{
HttpContext.Session.Clear();
return Ok(new { Message = "Logout successful" });
}
}
JWT-based Authentication:
[ApiController]
[Route("api/[controller]")]
[Authorize]
public class JwtController : ControllerBase
{
[HttpGet("profile")]
public IActionResult GetProfile()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
var username = User.FindFirst(ClaimTypes.Name)?.Value;
var roles = User.FindAll(ClaimTypes.Role).Select(c => c.Value);
return Ok(new
{
UserId = userId,
Username = username,
Roles = roles
});
}
[HttpPost("logout")]
public IActionResult Logout()
{
// JWT logout is client-side (delete token)
// Server can maintain a blacklist for immediate invalidation
return Ok(new { Message = "Logout successful" });
}
}
37. Explain API key authentication
Answer: API key authentication uses a simple string-based key for authentication.
C# Implementation:
// API Key Authentication Handler
public class ApiKeyAuthenticationHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
private const string ApiKeyHeaderName = "X-API-Key";
private readonly IApiKeyService _apiKeyService;
public ApiKeyAuthenticationHandler(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger,
UrlEncoder encoder,
ISystemClock clock,
IApiKeyService apiKeyService)
: base(options, logger, encoder, clock)
{
_apiKeyService = apiKeyService;
}
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
if (!Request.Headers.ContainsKey(ApiKeyHeaderName))
{
return AuthenticateResult.Fail("API Key header not found");
}
var apiKey = Request.Headers[ApiKeyHeaderName].ToString();
var apiKeyInfo = await _apiKeyService.ValidateApiKeyAsync(apiKey);
if (apiKeyInfo == null)
{
return AuthenticateResult.Fail("Invalid API Key");
}
var claims = new[]
{
new Claim(ClaimTypes.Name, apiKeyInfo.ClientName),
new Claim(ClaimTypes.NameIdentifier, apiKeyInfo.ClientId.ToString()),
new Claim("ApiKeyId", apiKeyInfo.Id.ToString()),
new Claim("ApiKeyPermissions", string.Join(",", apiKeyInfo.Permissions))
};
var identity = new ClaimsIdentity(claims, Scheme.Name);
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, Scheme.Name);
return AuthenticateResult.Success(ticket);
}
}
// API Key Service
public class ApiKeyService : IApiKeyService
{
private readonly ApplicationDbContext _context;
public ApiKeyService(ApplicationDbContext context)
{
_context = context;
}
public async Task<ApiKeyInfo> ValidateApiKeyAsync(string apiKey)
{
var key = await _context.ApiKeys
.Include(ak => ak.Permissions)
.FirstOrDefaultAsync(ak => ak.Key == apiKey && ak.IsActive);
if (key == null)
return null;
// Update last used timestamp
key.LastUsed = DateTime.UtcNow;
await _context.SaveChangesAsync();
return new ApiKeyInfo
{
Id = key.Id,
ClientId = key.ClientId,
ClientName = key.ClientName,
Permissions = key.Permissions.Select(p => p.Name).ToList()
};
}
public async Task<string> GenerateApiKeyAsync(int clientId, string clientName, List<string> permissions)
{
var apiKey = Convert.ToBase64String(Guid.NewGuid().ToByteArray())
.Replace("/", "_")
.Replace("+", "-")
.Substring(0, 32);
var key = new ApiKey
{
Key = apiKey,
ClientId = clientId,
ClientName = clientName,
IsActive = true,
CreatedDate = DateTime.UtcNow,
Permissions = await _context.Permissions
.Where(p => permissions.Contains(p.Name))
.ToListAsync()
};
_context.ApiKeys.Add(key);
await _context.SaveChangesAsync();
return apiKey;
}
}
// Controller using API Key authentication
[ApiController]
[Route("api/[controller]")]
[Authorize(AuthenticationSchemes = "ApiKey")]
public class ApiKeyController : ControllerBase
{
[HttpGet]
public IActionResult GetData()
{
var clientName = User.FindFirst(ClaimTypes.Name)?.Value;
var permissions = User.FindFirst("ApiKeyPermissions")?.Value?.Split(',');
return Ok(new
{
Message = $"Data for client: {clientName}",
Permissions = permissions,
Timestamp = DateTime.UtcNow
});
}
}
38. What is the difference between API keys and OAuth tokens?
Answer:
| Aspect | API Keys | OAuth Tokens |
|---|---|---|
| Complexity | Simple | Complex |
| User Context | No user context | User-specific |
| Expiration | Usually long-lived | Short-lived with refresh |
| Scope | Fixed permissions | Dynamic scopes |
| Revocation | Manual key rotation | Immediate revocation |
| Use Case | Machine-to-machine | User authorization |
API Key Example:
[Authorize(AuthenticationSchemes = "ApiKey")]
public class MachineToMachineController : ControllerBase
{
[HttpGet("data")]
public IActionResult GetData()
{
// API key provides fixed permissions
return Ok(new { Data = "Machine-to-machine data" });
}
}
OAuth Token Example:
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
public class UserSpecificController : ControllerBase
{
[HttpGet("profile")]
public IActionResult GetUserProfile()
{
var userId = User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
// OAuth token provides user-specific access
return Ok(new { UserId = userId, Profile = "User-specific data" });
}
}
39. Role-Based Access Control (RBAC)
RBAC is a security model that restricts system access based on the roles of individual users within an organization. Users are assigned roles, and roles are granted permissions to perform specific operations.
Key Concepts:
- Users: Individuals who access the system
- Roles: Collections of permissions (e.g., Admin, User, Manager)
- Permissions: Specific actions users can perform (e.g., Read, Write, Delete)
- Resources: Objects being protected (e.g., files, databases, APIs)
SQL Implementation Example:
-- Users table
CREATE TABLE Users (
UserId INT PRIMARY KEY IDENTITY(1,1),
Username NVARCHAR(50) UNIQUE NOT NULL,
Email NVARCHAR(100) UNIQUE NOT NULL,
IsActive BIT DEFAULT 1
);
-- Roles table
CREATE TABLE Roles (
RoleId INT PRIMARY KEY IDENTITY(1,1),
RoleName NVARCHAR(50) UNIQUE NOT NULL,
Description NVARCHAR(200)
);
-- Permissions table
CREATE TABLE Permissions (
PermissionId INT PRIMARY KEY IDENTITY(1,1),
PermissionName NVARCHAR(50) UNIQUE NOT NULL,
Resource NVARCHAR(100) NOT NULL,
Action NVARCHAR(50) NOT NULL
);
-- User-Role mapping
CREATE TABLE UserRoles (
UserId INT,
RoleId INT,
AssignedDate DATETIME DEFAULT GETDATE(),
PRIMARY KEY (UserId, RoleId),
FOREIGN KEY (UserId) REFERENCES Users(UserId),
FOREIGN KEY (RoleId) REFERENCES Roles(RoleId)
);
-- Role-Permission mapping
CREATE TABLE RolePermissions (
RoleId INT,
PermissionId INT,
PRIMARY KEY (RoleId, PermissionId),
FOREIGN KEY (RoleId) REFERENCES Roles(RoleId),
FOREIGN KEY (PermissionId) REFERENCES Permissions(PermissionId)
);
-- Sample data
INSERT INTO Roles (RoleName, Description) VALUES
('Admin', 'Full system access'),
('Manager', 'Department-level access'),
('User', 'Basic user access');
INSERT INTO Permissions (PermissionName, Resource, Action) VALUES
('ReadUsers', 'Users', 'Read'),
('WriteUsers', 'Users', 'Write'),
('DeleteUsers', 'Users', 'Delete'),
('ReadReports', 'Reports', 'Read'),
('WriteReports', 'Reports', 'Write');
C# Implementation:
public class RBACService
{
private readonly IDbConnection _connection;
public RBACService(IDbConnection connection)
{
_connection = connection;
}
public async Task<bool> HasPermissionAsync(int userId, string resource, string action)
{
var sql = @"
SELECT COUNT(1)
FROM Users u
JOIN UserRoles ur ON u.UserId = ur.UserId
JOIN RolePermissions rp ON ur.RoleId = rp.RoleId
JOIN Permissions p ON rp.PermissionId = p.PermissionId
WHERE u.UserId = @UserId
AND p.Resource = @Resource
AND p.Action = @Action
AND u.IsActive = 1";
var count = await _connection.ExecuteScalarAsync<int>(sql, new { userId, resource, action });
return count > 0;
}
public async Task<IEnumerable<string>> GetUserRolesAsync(int userId)
{
var sql = @"
SELECT r.RoleName
FROM Users u
JOIN UserRoles ur ON u.UserId = ur.UserId
JOIN Roles r ON ur.RoleId = r.RoleId
WHERE u.UserId = @UserId AND u.IsActive = 1";
return await _connection.QueryAsync<string>(sql, new { userId });
}
}
// Usage in controller
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly RBACService _rbacService;
public UsersController(RBACService rbacService)
{
_rbacService = rbacService;
}
[HttpGet]
public async Task<IActionResult> GetUsers()
{
var userId = GetCurrentUserId();
if (!await _rbacService.HasPermissionAsync(userId, "Users", "Read"))
{
return Forbid();
}
// Proceed with getting users
return Ok(await _userService.GetUsersAsync());
}
}
40. Authentication vs Authorization
Authentication (Who are you?)
- Purpose: Verifies the identity of a user
- Process: Validates credentials (username/password, tokens, biometrics)
- Example: Login process, JWT token validation
Authorization (What can you do?)
- Purpose: Determines what resources/actions a user can access
- Process: Checks permissions after authentication
- Example: Role-based access, API permissions
C# Implementation Example:
// Authentication Service
public class AuthenticationService
{
private readonly IUserRepository _userRepository;
private readonly IPasswordHasher _passwordHasher;
private readonly IJwtTokenGenerator _jwtGenerator;
public async Task<AuthResult> AuthenticateAsync(string username, string password)
{
var user = await _userRepository.GetByUsernameAsync(username);
if (user == null || !_passwordHasher.Verify(password, user.PasswordHash))
{
return AuthResult.Failure("Invalid credentials");
}
var token = _jwtGenerator.GenerateToken(user);
return AuthResult.Success(token, user);
}
}
// Authorization Service
public class AuthorizationService
{
private readonly RBACService _rbacService;
public async Task<bool> AuthorizeAsync(int userId, string resource, string action)
{
return await _rbacService.HasPermissionAsync(userId, resource, action);
}
}
// Combined usage in middleware
public class AuthMiddleware
{
private readonly RequestDelegate _next;
private readonly AuthenticationService _authService;
private readonly AuthorizationService _authzService;
public async Task InvokeAsync(HttpContext context)
{
// Authentication
var token = context.Request.Headers["Authorization"].FirstOrDefault()?.Split(" ").Last();
if (string.IsNullOrEmpty(token))
{
context.Response.StatusCode = 401;
return;
}
var user = await _authService.ValidateTokenAsync(token);
if (user == null)
{
context.Response.StatusCode = 401;
return;
}
// Authorization
var resource = context.Request.Path.Value?.Split('/')[2]; // Extract resource from path
var action = context.Request.Method;
if (!await _authzService.AuthorizeAsync(user.Id, resource, action))
{
context.Response.StatusCode = 403;
return;
}
context.Items["User"] = user;
await _next(context);
}
}
41. API Security Best Practices
Security controls must be enforced server-side: authenticate, authorize object-level access, validate input, use parameterized database commands, constrain resource consumption, log safely, and rotate credentials. CORS and client-side route guards are not access control. Reference: OWASP API Security.
42. HTTPS vs HTTP
HTTP (Hypertext Transfer Protocol)
- Security: No encryption, data sent in plain text
- Port: 80
- Use Case: Non-sensitive data, internal networks
HTTPS (HTTP Secure)
- Security: Encrypted using SSL/TLS
- Port: 443
- Use Case: Sensitive data, public APIs
C# HTTPS Configuration:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Force HTTPS
builder.Services.AddHttpsRedirection(options =>
{
options.RedirectStatusCode = StatusCodes.Status307TemporaryRedirect;
options.HttpsPort = 443;
});
// HSTS (HTTP Strict Transport Security)
builder.Services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(365);
});
var app = builder.Build();
// Redirect HTTP to HTTPS
app.UseHttpsRedirection();
app.UseHsts();
43. Input Validation and Sanitization
Validation vs Sanitization:
- Validation: Checks if input meets requirements
- Sanitization: Removes/escapes potentially dangerous content
C# Implementation:
public class InputValidationService
{
public ValidationResult ValidateUserInput(string input)
{
var result = new ValidationResult();
// Check for null/empty
if (string.IsNullOrWhiteSpace(input))
{
result.AddError("Input cannot be empty");
return result;
}
// Check length
if (input.Length > 1000)
{
result.AddError("Input too long");
return result;
}
// Check for SQL injection patterns
var sqlPatterns = new[] { "SELECT", "INSERT", "UPDATE", "DELETE", "DROP", "EXEC" };
if (sqlPatterns.Any(pattern => input.ToUpper().Contains(pattern)))
{
result.AddError("Invalid input pattern detected");
return result;
}
// Check for XSS patterns
var xssPatterns = new[] { "<script", "javascript:", "onload=", "onerror=" };
if (xssPatterns.Any(pattern => input.ToLower().Contains(pattern)))
{
result.AddError("Potentially dangerous content detected");
return result;
}
return result;
}
public string SanitizeHtml(string input)
{
// Remove HTML tags
var sanitized = Regex.Replace(input, "<[^>]*>", "");
// Encode special characters
sanitized = HttpUtility.HtmlEncode(sanitized);
return sanitized;
}
public string SanitizeSql(string input)
{
// Use parameterized queries instead of string concatenation
// This is just for demonstration - always use parameters in real code
return input.Replace("'", "''").Replace(";", "");
}
}
44. Client-side vs Server-side Validation
Client-side Validation:
- Purpose: Immediate feedback, better UX
- Security: Can be bypassed
- Implementation: JavaScript, HTML5 attributes
Server-side Validation:
- Purpose: Security, data integrity
- Security: Cannot be bypassed
- Implementation: Backend code
C# Example:
// Server-side validation (ALWAYS required)
[HttpPost]
public async Task<IActionResult> CreateUser([FromBody] CreateUserRequest request)
{
// Server-side validation
if (!ModelState.IsValid)
{
return BadRequest(ModelState);
}
// Additional business logic validation
if (await _userService.EmailExistsAsync(request.Email))
{
return BadRequest("Email already registered");
}
// Proceed with creation
var user = await _userService.CreateUserAsync(request);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
// Client-side validation (for UX only)
public class CreateUserRequest
{
[Required(ErrorMessage = "Username is required")]
[StringLength(50, MinimumLength = 3, ErrorMessage = "Username must be between 3 and 50 characters")]
public string Username { get; set; }
[Required(ErrorMessage = "Email is required")]
[EmailAddress(ErrorMessage = "Invalid email format")]
public string Email { get; set; }
[Required(ErrorMessage = "Password is required")]
[StringLength(100, MinimumLength = 8, ErrorMessage = "Password must be at least 8 characters")]
[RegularExpression(@"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$",
ErrorMessage = "Password must contain uppercase, lowercase, number, and special character")]
public string Password { get; set; }
}
45. CORS and Configuration
CORS is a browser-enforced cross-origin sharing mechanism. It does not authenticate callers or protect a server from non-browser clients. Allow only known origins, methods, and headers; do not combine Access-Control-Allow-Origin: * with credentialed requests.
46. CORS vs Same-Origin Policy
Same-Origin Policy:
- Purpose: Security mechanism preventing cross-origin requests
- Scope: Browser-enforced security
- Default: Blocks cross-origin requests
CORS:
- Purpose: Mechanism to allow controlled cross-origin requests
- Scope: Server-controlled policy
- Default: Must be explicitly configured
Example:
// Without CORS - Same-Origin Policy blocks requests
[ApiController]
[Route("api/[controller]")]
public class BlockedController : ControllerBase
{
[HttpGet]
public IActionResult GetData()
{
return Ok(new { data = "This will be blocked by SOP" });
}
}
// With CORS - Allows cross-origin requests
[ApiController]
[Route("api/[controller]")]
[EnableCors("AllowSpecificOrigin")]
public class AllowedController : ControllerBase
{
[HttpGet]
public IActionResult GetData()
{
return Ok(new { data = "This will be allowed" });
}
}
47. Rate Limiting Implementation
Rate limiting needs an identity/key definition, an algorithm, limits, storage/coordination, and a documented client response. Apply it at the appropriate layer and return useful Retry-After information when applicable. Rate limiting limits admission; it is not a substitute for authorization or input validation.
48. Rate Limiting vs Throttling
Rate Limiting:
- Purpose: Prevent abuse, protect resources
- Action: Blocks requests when limit exceeded
- Scope: Usually per client/IP
Throttling:
- Purpose: Manage resource usage, ensure fair distribution
- Action: Delays/slows down requests
- Scope: Usually per endpoint/service
C# Throttling Example:
public class ThrottlingMiddleware
{
private readonly RequestDelegate _next;
private readonly SemaphoreSlim _semaphore;
public ThrottlingMiddleware(RequestDelegate next)
{
_next = next;
_semaphore = new SemaphoreSlim(10, 10); // Allow 10 concurrent requests
}
public async Task InvokeAsync(HttpContext context)
{
await _semaphore.WaitAsync();
try
{
// Add artificial delay for heavy operations
if (context.Request.Path.Value?.Contains("/api/heavy-operation") == true)
{
await Task.Delay(100); // 100ms delay
}
await _next(context);
}
finally
{
_semaphore.Release();
}
}
}
49. API Security Headers
C# Security Headers Implementation:
public class SecurityHeadersMiddleware
{
private readonly RequestDelegate _next;
public SecurityHeadersMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
// Security Headers
context.Response.Headers.Add("X-Content-Type-Options", "nosniff");
context.Response.Headers.Add("X-Frame-Options", "DENY");
context.Response.Headers.Add("X-XSS-Protection", "1; mode=block");
context.Response.Headers.Add("Referrer-Policy", "strict-origin-when-cross-origin");
context.Response.Headers.Add("Permissions-Policy", "geolocation=(), microphone=(), camera=()");
// Content Security Policy
context.Response.Headers.Add("Content-Security-Policy",
"default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';");
// HSTS (HTTP Strict Transport Security)
context.Response.Headers.Add("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload");
await _next(context);
}
}
50. Content-Security-Policy vs X-Frame-Options
X-Frame-Options:
- Purpose: Prevent clickjacking attacks
- Values: DENY, SAMEORIGIN, ALLOW-FROM
- Scope: Frame embedding only
Content-Security-Policy:
- Purpose: Comprehensive security policy
- Values: Multiple directives for different resource types
- Scope: All resources (scripts, styles, frames, etc.)
Example:
// X-Frame-Options only
context.Response.Headers.Add("X-Frame-Options", "DENY");
// Content-Security-Policy (more comprehensive)
context.Response.Headers.Add("Content-Security-Policy",
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline'; " +
"style-src 'self' 'unsafe-inline'; " +
"frame-ancestors 'none'; " + // Prevents framing (like X-Frame-Options)
"img-src 'self' data: https:; " +
"font-src 'self'; " +
"connect-src 'self'; " +
"media-src 'self'; " +
"object-src 'none'; " +
"base-uri 'self'; " +
"form-action 'self';");
51. JSON Structure
JSON (JavaScript Object Notation):
- Format: Lightweight data interchange format
- Structure: Key-value pairs, arrays, nested objects
- Types: String, Number, Boolean, null, Object, Array
C# JSON Handling:
// JSON Model
public class User
{
[JsonPropertyName("id")]
public int Id { get; set; }
[JsonPropertyName("username")]
public string Username { get; set; }
[JsonPropertyName("email")]
public string Email { get; set; }
[JsonPropertyName("is_active")]
public bool IsActive { get; set; }
[JsonPropertyName("created_at")]
public DateTime CreatedAt { get; set; }
[JsonPropertyName("roles")]
public List<string> Roles { get; set; } = new();
}
// JSON Serialization/Deserialization
public class JsonService
{
public string SerializeUser(User user)
{
var options = new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = true
};
return JsonSerializer.Serialize(user, options);
}
public User DeserializeUser(string json)
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
return JsonSerializer.Deserialize<User>(json, options);
}
}
// Example JSON output:
/*
{
"id": 1,
"username": "john_doe",
"email": "john@example.com",
"isActive": true,
"createdAt": "2024-01-15T10:30:00Z",
"roles": ["User", "Manager"]
}
*/
52. JSON vs XML
JSON:
- Format: Lightweight, human-readable
- Structure: Key-value pairs, arrays
- Size: Smaller file size
- Parsing: Native JavaScript support
XML:
- Format: Markup language, verbose
- Structure: Hierarchical with tags
- Size: Larger file size
- Parsing: Requires XML parser
C# Comparison:
// JSON Example
public class JsonExample
{
public string SerializeToJson(User user)
{
return JsonSerializer.Serialize(user, new JsonSerializerOptions
{
WriteIndented = true
});
}
}
// XML Example
public class XmlExample
{
public string SerializeToXml(User user)
{
var serializer = new XmlSerializer(typeof(User));
using var writer = new StringWriter();
serializer.Serialize(writer, user);
return writer.ToString();
}
}
// Output Comparison:
/*
JSON:
{
"id": 1,
"username": "john_doe",
"email": "john@example.com"
}
XML:
<?xml version="1.0" encoding="utf-16"?>
<User>
<Id>1</Id>
<Username>john_doe</Username>
<Email>john@example.com</Email>
</User>
*/
53. API Request/Response Formats
Standard API Response Format:
public class ApiResponse<T>
{
public bool Success { get; set; }
public string Message { get; set; }
public T Data { get; set; }
public List<string> Errors { get; set; } = new();
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
public string RequestId { get; set; }
}
// Controller implementation
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
[HttpGet]
public async Task<ActionResult<ApiResponse<List<User>>>> GetUsers()
{
try
{
var users = await _userService.GetUsersAsync();
return Ok(new ApiResponse<List<User>>
{
Success = true,
Message = "Users retrieved successfully",
Data = users,
RequestId = HttpContext.TraceIdentifier
});
}
catch (Exception ex)
{
return StatusCode(500, new ApiResponse<List<User>>
{
Success = false,
Message = "An error occurred while retrieving users",
Errors = new List<string> { ex.Message },
RequestId = HttpContext.TraceIdentifier
});
}
}
[HttpPost]
public async Task<ActionResult<ApiResponse<User>>> CreateUser([FromBody] CreateUserRequest request)
{
if (!ModelState.IsValid)
{
return BadRequest(new ApiResponse<User>
{
Success = false,
Message = "Validation failed",
Errors = ModelState.Values
.SelectMany(v => v.Errors)
.Select(e => e.ErrorMessage)
.ToList(),
RequestId = HttpContext.TraceIdentifier
});
}
var user = await _userService.CreateUserAsync(request);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, new ApiResponse<User>
{
Success = true,
Message = "User created successfully",
Data = user,
RequestId = HttpContext.TraceIdentifier
});
}
}
54. application/json vs application/xml
Content Types:
[ApiController]
[Route("api/[controller]")]
public class ContentTypeController : ControllerBase
{
[HttpGet]
[Produces("application/json", "application/xml")]
public async Task<IActionResult> GetData()
{
var data = new { id = 1, name = "Test" };
// The framework will automatically serialize based on Accept header
return Ok(data);
}
[HttpPost]
[Consumes("application/json", "application/xml")]
public async Task<IActionResult> PostData([FromBody] object data)
{
// The framework will automatically deserialize based on Content-Type header
return Ok(data);
}
}
// Custom content negotiation
public class CustomContentNegotiator : IContentNegotiator
{
public ContentNegotiationResult Negotiate(Type type, HttpRequestMessage request, IEnumerable<MediaTypeFormatter> formatters)
{
var acceptHeader = request.Headers.Accept.FirstOrDefault();
if (acceptHeader?.MediaType == "application/xml")
{
var xmlFormatter = formatters.OfType<XmlMediaTypeFormatter>().FirstOrDefault();
return new ContentNegotiationResult(xmlFormatter, acceptHeader);
}
// Default to JSON
var jsonFormatter = formatters.OfType<JsonMediaTypeFormatter>().FirstOrDefault();
return new ContentNegotiationResult(jsonFormatter, new MediaTypeHeaderValue("application/json"));
}
}
55. Data Serialization and Deserialization
C# Serialization Examples:
public class SerializationService
{
// JSON Serialization
public string SerializeToJson<T>(T obj)
{
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
};
return JsonSerializer.Serialize(obj, options);
}
public T DeserializeFromJson<T>(string json)
{
var options = new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
};
return JsonSerializer.Deserialize<T>(json, options);
}
// XML Serialization
public string SerializeToXml<T>(T obj)
{
var serializer = new XmlSerializer(typeof(T));
using var writer = new StringWriter();
serializer.Serialize(writer, obj);
return writer.ToString();
}
public T DeserializeFromXml<T>(string xml)
{
var serializer = new XmlSerializer(typeof(T));
using var reader = new StringReader(xml);
return (T)serializer.Deserialize(reader);
}
// Binary Serialization (for .NET objects)
public byte[] SerializeToBinary<T>(T obj) where T : class
{
using var stream = new MemoryStream();
var formatter = new BinaryFormatter();
formatter.Serialize(stream, obj);
return stream.ToArray();
}
public T DeserializeFromBinary<T>(byte[] data) where T : class
{
using var stream = new MemoryStream(data);
var formatter = new BinaryFormatter();
return (T)formatter.Deserialize(stream);
}
}
// Custom JSON Converter
public class DateTimeJsonConverter : JsonConverter<DateTime>
{
public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
{
return DateTime.Parse(reader.GetString());
}
public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
{
writer.WriteStringValue(value.ToString("yyyy-MM-ddTHH:mm:ssZ"));
}
}
// Usage with custom converter
public class UserWithCustomDate
{
public int Id { get; set; }
public string Name { get; set; }
[JsonConverter(typeof(DateTimeJsonConverter))]
public DateTime CreatedAt { get; set; }
}
56. Differences between Serialization and Marshaling
Serialization converts objects to a format that can be stored or transmitted, while marshaling is the process of transforming data between different formats or systems.
Key Differences:
Serialization: - Converts objects to byte streams or text formats - Focuses on object state preservation - Usually within the same platform/language - Examples: JSON, XML, Protocol Buffers
Marshaling: - Transforms data between different formats/systems - Handles platform-specific conversions - Often involves memory layout changes - Examples: COM interop, P/Invoke
C# Example:
// Serialization Example
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
// JSON Serialization
var person = new Person { Name = "John", Age = 30 };
string json = JsonSerializer.Serialize(person);
// XML Serialization
var serializer = new XmlSerializer(typeof(Person));
using var writer = new StringWriter();
serializer.Serialize(writer, person);
string xml = writer.ToString();
// Marshaling Example (P/Invoke)
[DllImport("kernel32.dll")]
public static extern IntPtr GetCurrentProcess();
// Marshaling between managed and unmanaged code
IntPtr processHandle = GetCurrentProcess();
57. API Content Negotiation
Content negotiation allows clients and servers to agree on the best representation of a resource.
Key Concepts:
- Accept Header: Client specifies preferred content types
- Content-Type Header: Server specifies actual content type
- Quality Values (q): Priority weighting for multiple types
C# ASP.NET Core Example:
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
[HttpGet]
public IActionResult GetProducts()
{
var products = GetProductsFromDatabase();
// Check Accept header for content negotiation
if (Request.Headers["Accept"].ToString().Contains("application/xml"))
{
return Ok(new XmlResult(products));
}
// Default to JSON
return Ok(products);
}
}
// Custom XML formatter
public class XmlResult : IActionResult
{
private readonly object _data;
public XmlResult(object data) => _data = data;
public async Task ExecuteResultAsync(ActionContext context)
{
var response = context.HttpContext.Response;
response.ContentType = "application/xml";
var serializer = new XmlSerializer(_data.GetType());
using var writer = new StringWriter();
serializer.Serialize(writer, _data);
await response.WriteAsync(writer.ToString());
}
}
SQL Example (Content Negotiation in Stored Procedures):
-- Stored procedure that returns different formats based on parameter
CREATE PROCEDURE GetProducts
@Format NVARCHAR(10) = 'JSON'
AS
BEGIN
IF @Format = 'XML'
BEGIN
SELECT
(SELECT
ProductID as '@ID',
ProductName as 'Name',
UnitPrice as 'Price'
FROM Products
FOR XML PATH('Product'), ROOT('Products'))
END
ELSE
BEGIN
SELECT ProductID, ProductName, UnitPrice
FROM Products
FOR JSON PATH
END
END
58. Accept vs Content-Type Headers
Accept Header:
- Client → Server: What the client can accept
- Specifies preferred response formats
- Can include multiple types with quality values
Content-Type Header:
- Server → Client: What the server is actually sending
- Specifies the actual format of the data
- Single value only
C# Example:
[HttpPost]
public async Task<IActionResult> CreateProduct([FromBody] Product product)
{
// Server sets Content-Type in response
Response.Headers.Add("Content-Type", "application/json");
// Process the product
var createdProduct = await _productService.CreateAsync(product);
return CreatedAtAction(nameof(GetProduct),
new { id = createdProduct.Id }, createdProduct);
}
// Client-side example
public async Task<Product> CreateProductAsync(Product product)
{
using var client = new HttpClient();
// Client sets Accept header
client.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
var json = JsonSerializer.Serialize(product);
var content = new StringContent(json, Encoding.UTF8, "application/json");
var response = await client.PostAsync("/api/products", content);
return await JsonSerializer.DeserializeAsync<Product>(
await response.Content.ReadAsStreamAsync());
}
59. Binary Data Handling in REST APIs
Approaches:
- Base64 Encoding: Convert binary to text
- Multipart Form Data: For file uploads
- Direct Binary: Using application/octet-stream
C# Examples:
[ApiController]
[Route("api/[controller]")]
public class FilesController : ControllerBase
{
// Method 1: Base64
[HttpPost("upload-base64")]
public async Task<IActionResult> UploadBase64([FromBody] FileUploadRequest request)
{
var bytes = Convert.FromBase64String(request.Base64Data);
await System.IO.File.WriteAllBytesAsync($"uploads/{request.FileName}", bytes);
return Ok(new { message = "File uploaded successfully" });
}
// Method 2: Multipart Form Data
[HttpPost("upload-multipart")]
public async Task<IActionResult> UploadMultipart(IFormFile file)
{
if (file.Length > 0)
{
var filePath = Path.Combine("uploads", file.FileName);
using var stream = new FileStream(filePath, FileMode.Create);
await file.CopyToAsync(stream);
}
return Ok(new { message = "File uploaded successfully" });
}
// Method 3: Direct Binary
[HttpPost("upload-binary")]
public async Task<IActionResult> UploadBinary()
{
using var stream = Request.Body;
using var fileStream = new FileStream("uploads/binary-file", FileMode.Create);
await stream.CopyToAsync(fileStream);
return Ok();
}
// Download binary data
[HttpGet("download/{fileName}")]
public IActionResult Download(string fileName)
{
var filePath = Path.Combine("uploads", fileName);
if (!System.IO.File.Exists(filePath))
return NotFound();
var bytes = System.IO.File.ReadAllBytes(filePath);
return File(bytes, "application/octet-stream", fileName);
}
}
public class FileUploadRequest
{
public string FileName { get; set; }
public string Base64Data { get; set; }
}
60. Base64 vs Binary Data
Base64:
- Text-based encoding of binary data
- 33% size increase (3 bytes → 4 characters)
- Human-readable and safe for text protocols
- Slower due to encoding/decoding overhead
Binary Data:
- Raw byte representation
- Original size maintained
- Not human-readable
- Faster processing
C# Performance Comparison:
public class BinaryDataComparison
{
public async Task PerformanceTest()
{
var originalData = new byte[1024 * 1024]; // 1MB
new Random().NextBytes(originalData);
// Base64 encoding
var stopwatch = Stopwatch.StartNew();
var base64String = Convert.ToBase64String(originalData);
stopwatch.Stop();
Console.WriteLine($"Base64 encoding: {stopwatch.ElapsedMilliseconds}ms");
// Base64 decoding
stopwatch.Restart();
var decodedBytes = Convert.FromBase64String(base64String);
stopwatch.Stop();
Console.WriteLine($"Base64 decoding: {stopwatch.ElapsedMilliseconds}ms");
// Size comparison
Console.WriteLine($"Original size: {originalData.Length} bytes");
Console.WriteLine($"Base64 size: {base64String.Length} characters");
Console.WriteLine($"Size increase: {((double)base64String.Length / originalData.Length - 1) * 100:F1}%");
}
}
61. API Performance Optimization Techniques
Key Techniques:
- Caching (Memory, Redis, CDN)
- Database Optimization (Indexing, Query optimization)
- Response Compression
- Async/Await patterns
- Connection pooling
- Load balancing
C# Implementation:
[ApiController]
[Route("api/[controller]")]
public class OptimizedProductsController : ControllerBase
{
private readonly IMemoryCache _cache;
private readonly IDbConnection _db;
public OptimizedProductsController(IMemoryCache cache, IDbConnection db)
{
_cache = cache;
_db = db;
}
[HttpGet]
[ResponseCache(Duration = 300)] // 5 minutes
public async Task<IActionResult> GetProducts()
{
const string cacheKey = "products_all";
// Check cache first
if (_cache.TryGetValue(cacheKey, out List<Product> cachedProducts))
{
return Ok(cachedProducts);
}
// Database query with optimization
var products = await _db.QueryAsync<Product>(
"SELECT ProductID, ProductName, UnitPrice FROM Products WITH (NOLOCK)");
// Cache the result
_cache.Set(cacheKey, products, TimeSpan.FromMinutes(5));
return Ok(products);
}
[HttpGet("{id}")]
public async Task<IActionResult> GetProduct(int id)
{
// Use async/await for I/O operations
var product = await _db.QueryFirstOrDefaultAsync<Product>(
"SELECT * FROM Products WHERE ProductID = @Id",
new { Id = id });
if (product == null)
return NotFound();
return Ok(product);
}
}
SQL Optimization Example:
-- Create optimized indexes
CREATE NONCLUSTERED INDEX IX_Products_CategoryID
ON Products(CategoryID) INCLUDE (ProductName, UnitPrice);
-- Optimized query with proper indexing
SELECT ProductID, ProductName, UnitPrice
FROM Products WITH (NOLOCK)
WHERE CategoryID = @CategoryID
ORDER BY ProductName;
-- Use stored procedures for complex queries
CREATE PROCEDURE GetProductsByCategory
@CategoryID INT,
@PageSize INT = 20,
@PageNumber INT = 1
AS
BEGIN
SET NOCOUNT ON;
SELECT ProductID, ProductName, UnitPrice
FROM Products WITH (NOLOCK)
WHERE CategoryID = @CategoryID
ORDER BY ProductName
OFFSET (@PageNumber - 1) * @PageSize ROWS
FETCH NEXT @PageSize ROWS ONLY;
END
62. Caching vs Database Optimization
Caching:
- Memory-based performance improvement
- Reduces database load
- Faster response times
- Temporary data storage
Database Optimization:
- Structural improvements to data access
- Permanent performance gains
- Reduces I/O operations
- Better query execution plans
C# Implementation:
public class CachingService
{
private readonly IMemoryCache _cache;
private readonly IDbConnection _db;
public CachingService(IMemoryCache cache, IDbConnection db)
{
_cache = cache;
_db = db;
}
public async Task<List<Product>> GetProductsWithCaching()
{
const string cacheKey = "products_cache";
// Try cache first
if (_cache.TryGetValue(cacheKey, out List<Product> products))
{
return products;
}
// Database query (optimized)
products = await _db.QueryAsync<Product>(
"SELECT ProductID, ProductName, UnitPrice FROM Products WITH (NOLOCK)");
// Cache with expiration
var cacheOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(10))
.SetAbsoluteExpiration(TimeSpan.FromHours(1));
_cache.Set(cacheKey, products, cacheOptions);
return products;
}
}
// Database optimization through proper indexing
public class DatabaseOptimization
{
public async Task<List<Product>> GetProductsOptimized(int categoryId)
{
// Use indexed query
var sql = @"
SELECT ProductID, ProductName, UnitPrice
FROM Products WITH (NOLOCK)
WHERE CategoryID = @CategoryID
ORDER BY ProductName";
return (await _db.QueryAsync<Product>(sql, new { CategoryID = categoryId }))
.ToList();
}
}
63. API Response Compression
Benefits:
- Reduced bandwidth usage
- Faster transmission
- Better user experience
C# Implementation:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddResponseCompression(options =>
{
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.EnableForHttps = true;
});
services.Configure<BrotliCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Optimal;
});
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseResponseCompression();
// ... other middleware
}
}
[ApiController]
[Route("api/[controller]")]
public class CompressedController : ControllerBase
{
[HttpGet("large-data")]
public async Task<IActionResult> GetLargeData()
{
// Large dataset that benefits from compression
var largeData = await GenerateLargeDataset();
// Response will be automatically compressed
return Ok(largeData);
}
}
64. Gzip vs Brotli Compression
Compression trades CPU for bandwidth. Brotli can often provide better compression for HTTPS responses, while gzip is broadly supported. Do not compress already-compressed formats or sensitive responses where compression side channels matter. Respect Accept-Encoding and send Vary: Accept-Encoding.
65. API Load Balancing Strategies
Common Strategies:
- Round Robin: Distribute requests evenly
- Least Connections: Send to server with fewest active connections
- Weighted Round Robin: Assign weights to servers
- IP Hash: Route based on client IP
- Health Check: Route to healthy servers only
C# Implementation:
public class LoadBalancer
{
private readonly List<Server> _servers;
private int _currentIndex = 0;
private readonly object _lock = new object();
public LoadBalancer()
{
_servers = new List<Server>
{
new Server { Url = "http://server1:5000", Weight = 1, IsHealthy = true },
new Server { Url = "http://server2:5000", Weight = 2, IsHealthy = true },
new Server { Url = "http://server3:5000", Weight = 1, IsHealthy = true }
};
}
// Round Robin
public Server GetNextRoundRobin()
{
lock (_lock)
{
var server = _servers[_currentIndex];
_currentIndex = (_currentIndex + 1) % _servers.Count;
return server;
}
}
// Least Connections
public Server GetLeastConnections()
{
return _servers
.Where(s => s.IsHealthy)
.OrderBy(s => s.ActiveConnections)
.FirstOrDefault();
}
// Weighted Round Robin
public Server GetWeightedRoundRobin()
{
var totalWeight = _servers.Sum(s => s.Weight);
var random = new Random().Next(totalWeight);
var currentWeight = 0;
foreach (var server in _servers)
{
currentWeight += server.Weight;
if (random < currentWeight)
return server;
}
return _servers.First();
}
}
public class Server
{
public string Url { get; set; }
public int Weight { get; set; }
public bool IsHealthy { get; set; }
public int ActiveConnections { get; set; }
}
66. Round Robin vs Least Connections
Round Robin:
- Simple implementation
- Even distribution
- Doesn't consider server load
- Predictable pattern
Least Connections:
- Considers server capacity
- Better load distribution
- More complex implementation
- Dynamic routing
C# Implementation:
public class LoadBalancingComparison
{
private readonly List<Server> _servers;
private int _roundRobinIndex = 0;
public LoadBalancingComparison()
{
_servers = new List<Server>
{
new Server { Id = 1, ActiveConnections = 5 },
new Server { Id = 2, ActiveConnections = 3 },
new Server { Id = 3, ActiveConnections = 7 }
};
}
public Server GetRoundRobin()
{
var server = _servers[_roundRobinIndex];
_roundRobinIndex = (_roundRobinIndex + 1) % _servers.Count;
return server;
}
public Server GetLeastConnections()
{
return _servers.OrderBy(s => s.ActiveConnections).First();
}
public void SimulateLoadBalancing()
{
Console.WriteLine("Round Robin Distribution:");
for (int i = 0; i < 10; i++)
{
var server = GetRoundRobin();
Console.WriteLine($"Request {i + 1}: Server {server.Id}");
}
Console.WriteLine("\nLeast Connections Distribution:");
for (int i = 0; i < 10; i++)
{
var server = GetLeastConnections();
server.ActiveConnections++; // Simulate connection
Console.WriteLine($"Request {i + 1}: Server {server.Id} (Connections: {server.ActiveConnections})");
}
}
}
67. API Monitoring and Metrics
Key Metrics:
- Response Time
- Throughput (RPS)
- Error Rate
- Availability
- Resource Usage
C# Implementation:
public class ApiMetrics
{
private readonly IMetrics _metrics;
public ApiMetrics(IMetrics metrics)
{
_metrics = metrics;
}
public void RecordRequest(string endpoint, TimeSpan duration, bool isSuccess)
{
_metrics.Measure.Counter.Increment("api.requests.total");
_metrics.Measure.Timer.Time("api.response.time", duration);
if (isSuccess)
{
_metrics.Measure.Counter.Increment("api.requests.success");
}
else
{
_metrics.Measure.Counter.Increment("api.requests.error");
}
}
}
[ApiController]
[Route("api/[controller]")]
public class MonitoredController : ControllerBase
{
private readonly ApiMetrics _metrics;
public MonitoredController(ApiMetrics metrics)
{
_metrics = metrics;
}
[HttpGet]
public async Task<IActionResult> GetData()
{
var stopwatch = Stopwatch.StartNew();
bool isSuccess = false;
try
{
var result = await ProcessRequest();
isSuccess = true;
return Ok(result);
}
catch (Exception ex)
{
_metrics.RecordError(ex);
throw;
}
finally
{
stopwatch.Stop();
_metrics.RecordRequest("GetData", stopwatch.Elapsed, isSuccess);
}
}
}
68. Monitoring vs Logging
Monitoring:
- Real-time metrics collection
- Performance indicators
- Alerting capabilities
- Trend analysis
Logging:
- Event recording
- Debug information
- Audit trails
- Historical data
C# Implementation:
public class MonitoringService
{
private readonly IMetrics _metrics;
private readonly ILogger _logger;
public MonitoringService(IMetrics metrics, ILogger logger)
{
_metrics = metrics;
_logger = logger;
}
// Monitoring - Real-time metrics
public void RecordApiCall(string endpoint, TimeSpan duration)
{
_metrics.Measure.Timer.Time($"api.{endpoint}.duration", duration);
_metrics.Measure.Counter.Increment($"api.{endpoint}.calls");
}
// Logging - Event recording
public void LogApiCall(string endpoint, TimeSpan duration, string userId)
{
_logger.LogInformation(
"API call completed - Endpoint: {Endpoint}, Duration: {Duration}ms, User: {UserId}",
endpoint, duration.TotalMilliseconds, userId);
}
public void LogError(Exception ex, string context)
{
_logger.LogError(ex, "Error in {Context}: {Message}", context, ex.Message);
}
}
69. API Caching Strategies
Strategies:
- Client-Side Caching
- Server-Side Caching
- CDN Caching
- Database Query Caching
C# Implementation:
public class CachingStrategies
{
private readonly IMemoryCache _memoryCache;
private readonly IDistributedCache _distributedCache;
public CachingStrategies(IMemoryCache memoryCache, IDistributedCache distributedCache)
{
_memoryCache = memoryCache;
_distributedCache = distributedCache;
}
// Memory Cache (Server-Side)
public async Task<List<Product>> GetProductsWithMemoryCache()
{
const string key = "products_memory";
if (_memoryCache.TryGetValue(key, out List<Product> products))
{
return products;
}
products = await FetchFromDatabase();
_memoryCache.Set(key, products, TimeSpan.FromMinutes(10));
return products;
}
// Distributed Cache (Multi-Server)
public async Task<List<Product>> GetProductsWithDistributedCache()
{
const string key = "products_distributed";
var cached = await _distributedCache.GetStringAsync(key);
if (!string.IsNullOrEmpty(cached))
{
return JsonSerializer.Deserialize<List<Product>>(cached);
}
var products = await FetchFromDatabase();
var options = new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(15)
};
await _distributedCache.SetStringAsync(key, JsonSerializer.Serialize(products), options);
return products;
}
// Cache-Aside Pattern
public async Task<Product> GetProductWithCacheAside(int id)
{
var key = $"product_{id}";
// Try cache first
if (_memoryCache.TryGetValue(key, out Product product))
{
return product;
}
// Cache miss - fetch from database
product = await FetchProductFromDatabase(id);
if (product != null)
{
_memoryCache.Set(key, product, TimeSpan.FromMinutes(5));
}
return product;
}
}
70. Client-Side vs Server-Side Caching
Client-Side Caching:
- Browser cache
- Reduces server load
- Faster for repeat requests
- Limited control
Server-Side Caching:
- Application cache
- Full control
- Shared across users
- More complex management
C# Implementation:
[ApiController]
[Route("api/[controller]")]
public class CachedController : ControllerBase
{
private readonly IMemoryCache _cache;
public CachedController(IMemoryCache cache)
{
_cache = cache;
}
// Server-Side Caching
[HttpGet("server-cached")]
[ResponseCache(Duration = 300)] // 5 minutes
public async Task<IActionResult> GetServerCachedData()
{
const string cacheKey = "server_data";
if (_cache.TryGetValue(cacheKey, out var data))
{
return Ok(data);
}
data = await GenerateData();
_cache.Set(cacheKey, data, TimeSpan.FromMinutes(5));
return Ok(data);
}
// Client-Side Caching Headers
[HttpGet("client-cached")]
public IActionResult GetClientCachedData()
{
var data = GenerateStaticData();
// Set cache headers for client-side caching
Response.Headers.Add("Cache-Control", "public, max-age=3600"); // 1 hour
Response.Headers.Add("ETag", GenerateETag(data));
return Ok(data);
}
// Conditional requests (ETag support)
[HttpGet("conditional")]
public IActionResult GetConditionalData()
{
var data = GenerateData();
var etag = GenerateETag(data);
// Check if client has latest version
if (Request.Headers["If-None-Match"] == etag)
{
return StatusCode(304); // Not Modified
}
Response.Headers.Add("ETag", etag);
return Ok(data);
}
private string GenerateETag(object data)
{
var json = JsonSerializer.Serialize(data);
using var sha256 = SHA256.Create();
var hash = sha256.ComputeHash(Encoding.UTF8.GetBytes(json));
return Convert.ToBase64String(hash);
}
}
SQL Caching Example:
-- Query result caching in SQL Server
SELECT ProductID, ProductName, UnitPrice
FROM Products WITH (NOLOCK)
OPTION (RECOMPILE); -- Forces new execution plan
-- Using query hints for caching
SELECT ProductID, ProductName, UnitPrice
FROM Products WITH (NOLOCK, INDEX = IX_Products_CategoryID)
WHERE CategoryID = @CategoryID
OPTION (OPTIMIZE FOR (@CategoryID = 1)); -- Optimize for specific value
-- Stored procedure with caching
CREATE PROCEDURE GetCachedProducts
@CategoryID INT
AS
BEGIN
SET NOCOUNT ON;
-- Use query plan caching
SELECT ProductID, ProductName, UnitPrice
FROM Products WITH (NOLOCK)
WHERE CategoryID = @CategoryID
ORDER BY ProductName;
END
API Testing Strategies and Tools
71. Explain API testing strategies and tools
API Testing Strategies:
- Functional Testing: Validates API endpoints work as expected
- Performance Testing: Tests response times and throughput
- Security Testing: Identifies vulnerabilities
- Contract Testing: Ensures API contracts are maintained
- Integration Testing: Tests API interactions with other systems
Popular Tools: - Postman: Manual and automated API testing - RestAssured: Java-based API testing - Newman: Command-line Postman collections - JMeter: Performance testing - SoapUI: SOAP and REST API testing
C# Example with HttpClient:
[Test]
public async Task TestUserAPI_ShouldReturnUser()
{
// Arrange
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/users/1");
// Act
var response = await client.SendAsync(request);
var content = await response.Content.ReadAsStringAsync();
var user = JsonSerializer.Deserialize<User>(content);
// Assert
Assert.AreEqual(HttpStatusCode.OK, response.StatusCode);
Assert.IsNotNull(user);
Assert.AreEqual(1, user.Id);
}
72. What are the differences between unit testing and integration testing?
| Aspect | Unit Testing | Integration Testing |
|---|---|---|
| Scope | Individual components/methods | Multiple components working together |
| Dependencies | Mocked/stubbed | Real dependencies |
| Speed | Fast execution | Slower execution |
| Isolation | Highly isolated | Tests interactions |
| Purpose | Verify component logic | Verify component integration |
C# Example - Unit Test:
[Test]
public void CalculateTotal_WithValidItems_ReturnsCorrectTotal()
{
// Arrange
var calculator = new OrderCalculator();
var items = new List<OrderItem>
{
new OrderItem { Price = 10.0m, Quantity = 2 },
new OrderItem { Price = 5.0m, Quantity = 1 }
};
// Act
var total = calculator.CalculateTotal(items);
// Assert
Assert.AreEqual(25.0m, total);
}
C# Example - Integration Test:
[Test]
public async Task CreateOrder_WithValidData_ShouldSaveToDatabase()
{
// Arrange
var context = new TestDbContext();
var orderService = new OrderService(context);
var order = new Order { CustomerId = 1, Total = 100.0m };
// Act
var result = await orderService.CreateOrderAsync(order);
// Assert
Assert.IsTrue(result.Id > 0);
var savedOrder = await context.Orders.FindAsync(result.Id);
Assert.IsNotNull(savedOrder);
}
73. Explain API mocking and stubbing
API Mocking/Stubbing creates fake implementations of external dependencies for testing.
C# Example with Moq:
[Test]
public async Task GetUserOrders_WithValidUserId_ReturnsOrders()
{
// Arrange
var mockUserService = new Mock<IUserService>();
var mockOrderService = new Mock<IOrderService>();
mockUserService.Setup(x => x.GetUserAsync(1))
.ReturnsAsync(new User { Id = 1, Name = "John" });
mockOrderService.Setup(x => x.GetOrdersByUserIdAsync(1))
.ReturnsAsync(new List<Order>
{
new Order { Id = 1, UserId = 1, Total = 100.0m }
});
var orderController = new OrderController(mockUserService.Object, mockOrderService.Object);
// Act
var result = await orderController.GetUserOrders(1);
// Assert
Assert.IsNotNull(result);
Assert.AreEqual(1, result.Count());
}
74. What is the difference between mocking and stubbing?
| Aspect | Mocking | Stubbing |
|---|---|---|
| Purpose | Verify behavior/interactions | Provide test data |
| Verification | Verifies method calls | No verification of calls |
| Focus | Behavior testing | State testing |
| Complexity | More complex setup | Simpler setup |
C# Example - Mocking (verifies behavior):
[Test]
public void ProcessOrder_ShouldCallPaymentService()
{
// Arrange
var mockPaymentService = new Mock<IPaymentService>();
var orderProcessor = new OrderProcessor(mockPaymentService.Object);
var order = new Order { Id = 1, Total = 100.0m };
// Act
orderProcessor.ProcessOrder(order);
// Assert - Verifies the method was called
mockPaymentService.Verify(x => x.ProcessPayment(It.IsAny<PaymentRequest>()), Times.Once);
}
C# Example - Stubbing (provides data):
[Test]
public void CalculateDiscount_WithValidUser_ReturnsDiscount()
{
// Arrange
var mockUserService = new Mock<IUserService>();
mockUserService.Setup(x => x.GetUserType(1))
.Returns(UserType.Premium); // Stub - just provides data
var discountCalculator = new DiscountCalculator(mockUserService.Object);
// Act
var discount = discountCalculator.CalculateDiscount(1, 100.0m);
// Assert
Assert.AreEqual(10.0m, discount);
}
75. Explain API contract testing
API Contract Testing ensures that API consumers and providers maintain compatible contracts.
C# Example with Pact.NET:
[Test]
public void Consumer_GetUser_ShouldMatchContract()
{
// Arrange
var pact = PactBuilder.Create()
.ServiceConsumer("UserConsumer")
.HasPactWith("UserProvider");
pact.Given("User exists")
.UponReceiving("A request for user")
.With(new ProviderServiceRequest
{
Method = HttpVerb.Get,
Path = "/api/users/1",
Headers = new Dictionary<string, object>
{
["Accept"] = "application/json"
}
})
.WillRespondWith(new ProviderServiceResponse
{
Status = 200,
Headers = new Dictionary<string, object>
{
["Content-Type"] = "application/json"
},
Body = new
{
id = 1,
name = Match.Type("John Doe"),
email = Match.Regex("john@example.com", @"^[^@]+@[^@]+\.[^@]+$")
}
});
// Act & Assert
pact.Verify(interaction =>
{
var client = new HttpClient();
var response = client.GetAsync("http://localhost:5000/api/users/1").Result;
return response.StatusCode == HttpStatusCode.OK;
});
}
76. What are the differences between contract testing and integration testing?
| Aspect | Contract Testing | Integration Testing |
|---|---|---|
| Focus | API contracts/interfaces | End-to-end functionality |
| Scope | Consumer-provider compatibility | Full system integration |
| Speed | Fast | Slower |
| Dependencies | Minimal | Full system dependencies |
| Purpose | Prevent breaking changes | Verify system works together |
77. Explain API performance testing
API Performance Testing evaluates API response times, throughput, and scalability.
C# Example with BenchmarkDotNet:
[MemoryDiagnoser]
public class ApiPerformanceBenchmark
{
private HttpClient _client;
[GlobalSetup]
public void Setup()
{
_client = new HttpClient();
_client.BaseAddress = new Uri("https://api.example.com");
}
[Benchmark]
public async Task<string> GetUsersBenchmark()
{
var response = await _client.GetAsync("/api/users");
return await response.Content.ReadAsStringAsync();
}
[Benchmark]
public async Task<string> CreateUserBenchmark()
{
var user = new { name = "Test User", email = "test@example.com" };
var content = new StringContent(JsonSerializer.Serialize(user), Encoding.UTF8, "application/json");
var response = await _client.PostAsync("/api/users", content);
return await response.Content.ReadAsStringAsync();
}
}
Load Testing with NBomber:
public class ApiLoadTest
{
[Test]
public void ApiLoadTest_ShouldHandleConcurrentRequests()
{
var scenario = ScenarioBuilder.CreateScenario("api_load_test", async context =>
{
using var client = new HttpClient();
var response = await client.GetAsync("https://api.example.com/users");
return response.IsSuccessStatusCode ? Response.Ok() : Response.Fail();
})
.WithLoadSimulations(
Simulation.Inject(rate: 100, interval: TimeSpan.FromSeconds(1), during: TimeSpan.FromMinutes(5))
);
NBomberRunner
.RegisterScenarios(scenario)
.Run();
}
}
78. What is the difference between load testing and stress testing?
| Aspect | Load Testing | Stress Testing |
|---|---|---|
| Purpose | Verify performance under expected load | Find breaking point |
| Load Level | Normal to peak expected load | Beyond normal capacity |
| Goal | Ensure system meets performance requirements | Identify system limits |
| Focus | Response times, throughput | System stability under extreme conditions |
| Duration | Extended periods | Shorter, intense periods |
79. Explain API security testing
API Security Testing identifies vulnerabilities and security weaknesses in APIs.
C# Example - Security Test:
[Test]
public async Task ApiEndpoint_WithoutAuthentication_ShouldReturn401()
{
// Arrange
var client = new HttpClient();
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/admin/users");
// Act
var response = await client.SendAsync(request);
// Assert
Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Test]
public async Task ApiEndpoint_WithInvalidToken_ShouldReturn401()
{
// Arrange
var client = new HttpClient();
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", "invalid-token");
var request = new HttpRequestMessage(HttpMethod.Get, "https://api.example.com/admin/users");
// Act
var response = await client.SendAsync(request);
// Assert
Assert.AreEqual(HttpStatusCode.Unauthorized, response.StatusCode);
}
[Test]
public async Task ApiEndpoint_WithSqlInjection_ShouldNotBeVulnerable()
{
// Arrange
var client = new HttpClient();
var sqlInjection = "'; DROP TABLE Users; --";
var request = new HttpRequestMessage(HttpMethod.Get, $"https://api.example.com/users?search={sqlInjection}");
// Act
var response = await client.SendAsync(request);
// Assert
Assert.AreEqual(HttpStatusCode.BadRequest, response.StatusCode);
}
80. What are the differences between penetration testing and security scanning?
| Aspect | Penetration Testing | Security Scanning |
|---|---|---|
| Approach | Manual, creative testing | Automated scanning |
| Depth | Deep, thorough analysis | Surface-level checks |
| Human Element | Requires security experts | Automated tools |
| Cost | Expensive | Relatively inexpensive |
| Frequency | Periodic | Continuous |
| Focus | Business logic vulnerabilities | Known vulnerabilities |
API Management & Governance
81. Explain API management platforms and their features
API Management Platforms provide tools for designing, deploying, and managing APIs.
Key Features: - API Gateway: Route and filter requests - Developer Portal: Self-service API discovery - Analytics: Usage monitoring and insights - Security: Authentication, authorization, rate limiting - Documentation: Auto-generated API docs
C# Example - API Gateway Configuration:
public class ApiGatewayMiddleware
{
private readonly RequestDelegate _next;
private readonly IConfiguration _configuration;
public ApiGatewayMiddleware(RequestDelegate next, IConfiguration configuration)
{
_next = next;
_configuration = configuration;
}
public async Task InvokeAsync(HttpContext context)
{
// Rate limiting
if (!await CheckRateLimit(context))
{
context.Response.StatusCode = 429; // Too Many Requests
return;
}
// Authentication
if (!await AuthenticateRequest(context))
{
context.Response.StatusCode = 401; // Unauthorized
return;
}
// Route to appropriate service
await RouteRequest(context);
await _next(context);
}
private async Task<bool> CheckRateLimit(HttpContext context)
{
var clientId = context.User.Identity.Name;
var cache = context.RequestServices.GetService<IDistributedCache>();
var key = $"rate_limit:{clientId}";
var currentCount = await cache.GetStringAsync(key);
if (int.TryParse(currentCount, out var count) && count >= 100)
{
return false;
}
await cache.SetStringAsync(key, (count + 1).ToString(),
new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1) });
return true;
}
}
82. What are the differences between API Gateway and Load Balancer?
An API gateway is an application-layer policy/routing component that can authenticate, rate limit, transform, and route API traffic. A load balancer distributes traffic across targets; it may operate at L4 or L7. A gateway can use a load balancer, and one product can combine both roles.
83. Explain API lifecycle management
API Lifecycle Management covers the entire journey from design to retirement.
C# Example - API Versioning:
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
public class UsersController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.0")]
public async Task<ActionResult<IEnumerable<UserV1>>> GetUsersV1()
{
// V1 implementation
return Ok(await _userService.GetUsersAsync());
}
[HttpGet]
[MapToApiVersion("2.0")]
public async Task<ActionResult<IEnumerable<UserV2>>> GetUsersV2()
{
// V2 implementation with additional fields
return Ok(await _userService.GetUsersV2Async());
}
[HttpDelete("{id}")]
[MapToApiVersion("2.0")]
public async Task<ActionResult> DeleteUserV2(int id)
{
// V2 implementation with soft delete
await _userService.SoftDeleteUserAsync(id);
return NoContent();
}
}
84. What is the difference between API versioning and API evolution?
| Aspect | API Versioning | API Evolution |
|---|---|---|
| Approach | Explicit version numbers | Backward-compatible changes |
| Breaking Changes | New versions for breaking changes | Avoid breaking changes |
| Maintenance | Multiple versions to maintain | Single version |
| Strategy | Version-based routing | Feature flags, optional fields |
| Complexity | Higher complexity | Lower complexity |
85. Explain API analytics and monitoring
API Analytics and Monitoring track API usage, performance, and health.
C# Example - Custom API Metrics:
public class ApiMetricsMiddleware
{
private readonly RequestDelegate _next;
private readonly IMetrics _metrics;
public ApiMetricsMiddleware(RequestDelegate next, IMetrics metrics)
{
_next = next;
_metrics = metrics;
}
public async Task InvokeAsync(HttpContext context)
{
var stopwatch = Stopwatch.StartNew();
try
{
await _next(context);
// Record metrics
_metrics.Measure.Counter.Increment("api.requests.total");
_metrics.Measure.Counter.Increment($"api.requests.{context.Response.StatusCode}");
_metrics.Measure.Timer.Time("api.response.time", stopwatch.ElapsedMilliseconds);
}
catch (Exception ex)
{
_metrics.Measure.Counter.Increment("api.errors.total");
throw;
}
}
}
public class ApiAnalyticsService
{
private readonly ILogger<ApiAnalyticsService> _logger;
public async Task<ApiAnalytics> GetAnalyticsAsync(DateTime from, DateTime to)
{
return new ApiAnalytics
{
TotalRequests = await GetTotalRequestsAsync(from, to),
AverageResponseTime = await GetAverageResponseTimeAsync(from, to),
ErrorRate = await GetErrorRateAsync(from, to),
TopEndpoints = await GetTopEndpointsAsync(from, to),
UserActivity = await GetUserActivityAsync(from, to)
};
}
}
86. What are the differences between analytics and monitoring?
| Aspect | Analytics | Monitoring |
|---|---|---|
| Purpose | Business insights and trends | System health and alerts |
| Time Focus | Historical analysis | Real-time observation |
| Data Type | Aggregated metrics | Raw metrics and logs |
| Use Case | Decision making | Operational awareness |
| Frequency | Periodic analysis | Continuous monitoring |
87. Explain API developer portal and documentation
API Developer Portal provides self-service access to API documentation and tools.
C# Example - Swagger Documentation:
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "User Management API",
Version = "v1",
Description = "API for managing users and their data",
Contact = new OpenApiContact
{
Name = "API Support",
Email = "support@example.com"
}
});
// Add XML comments
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
// Add authentication
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey
});
});
}
}
/// <summary>
/// Controller for managing user operations
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
/// <summary>
/// Retrieves a user by their unique identifier
/// </summary>
/// <param name="id">The unique identifier of the user</param>
/// <returns>The user information</returns>
/// <response code="200">Returns the requested user</response>
/// <response code="404">If the user is not found</response>
[HttpGet("{id}")]
[ProducesResponseType(typeof(User), 200)]
[ProducesResponseType(404)]
public async Task<ActionResult<User>> GetUser(int id)
{
var user = await _userService.GetUserByIdAsync(id);
if (user == null)
return NotFound();
return Ok(user);
}
}
88. What is the difference between developer portal and API documentation?
| Aspect | Developer Portal | API Documentation |
|---|---|---|
| Scope | Complete developer experience | Technical API reference |
| Features | Registration, keys, testing tools | Endpoint descriptions |
| Interactivity | Interactive testing, SDKs | Static documentation |
| Management | User management, analytics | Content management |
| Purpose | Developer onboarding | Technical reference |
89. Explain API governance and policies
API Governance establishes standards, policies, and processes for API development.
C# Example - API Policy Enforcement:
public class ApiGovernanceMiddleware
{
private readonly RequestDelegate _next;
private readonly IApiPolicyService _policyService;
public async Task InvokeAsync(HttpContext context)
{
// Check API naming conventions
if (!ValidateApiNaming(context.Request.Path))
{
context.Response.StatusCode = 400;
await context.Response.WriteAsync("Invalid API naming convention");
return;
}
// Check rate limiting policies
if (!await CheckRateLimitPolicy(context))
{
context.Response.StatusCode = 429;
return;
}
// Check security policies
if (!await CheckSecurityPolicy(context))
{
context.Response.StatusCode = 403;
return;
}
await _next(context);
}
private bool ValidateApiNaming(PathString path)
{
// Enforce RESTful naming conventions
var segments = path.Value.Split('/');
return segments.All(s => string.IsNullOrEmpty(s) ||
Regex.IsMatch(s, @"^[a-z][a-z0-9-]*$"));
}
}
public class ApiPolicyService
{
public async Task<bool> ValidateApiDesignAsync(ApiSpecification spec)
{
var policies = new List<IApiPolicy>
{
new NamingConventionPolicy(),
new ResponseFormatPolicy(),
new ErrorHandlingPolicy(),
new SecurityPolicy()
};
foreach (var policy in policies)
{
if (!await policy.ValidateAsync(spec))
return false;
}
return true;
}
}
90. What are the differences between governance and management?
| Aspect | Governance | Management |
|---|---|---|
| Focus | Strategic direction and policies | Operational execution |
| Scope | Long-term vision and standards | Day-to-day operations |
| Decision Making | Policy and framework setting | Implementation decisions |
| Responsibility | Board/leadership level | Operational teams |
| Time Horizon | Long-term | Short to medium-term |
91. Explain GraphQL and its comparison with REST
GraphQL is a query language and runtime for APIs that allows clients to request exactly the data they need, nothing more and nothing less.
Key Differences:
REST: - Multiple endpoints for different resources - Over-fetching or under-fetching data - Fixed data structure per endpoint
GraphQL: - Single endpoint - Flexible queries - Strong typing system
SQL Example - REST vs GraphQL:
REST Approach:
-- Multiple queries needed for different views
SELECT id, name, email FROM users WHERE id = 1;
SELECT id, title, content FROM posts WHERE user_id = 1;
SELECT id, name FROM comments WHERE post_id IN (SELECT id FROM posts WHERE user_id = 1);
GraphQL Equivalent:
query GetUserWithPosts($userId: ID!) {
user(id: $userId) {
id
name
email
posts {
id
title
content
comments {
id
name
}
}
}
}
C# Implementation Example:
// REST Controller
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
[HttpGet("{id}")]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var user = await _userService.GetUserAsync(id);
return Ok(user);
}
[HttpGet("{id}/posts")]
public async Task<ActionResult<List<PostDto>>> GetUserPosts(int id)
{
var posts = await _postService.GetPostsByUserIdAsync(id);
return Ok(posts);
}
}
// GraphQL Schema
public class UserType : ObjectGraphType<User>
{
public UserType()
{
Field(x => x.Id);
Field(x => x.Name);
Field(x => x.Email);
Field<ListGraphType<PostType>>("posts",
resolve: context => _postService.GetPostsByUserId(context.Source.Id));
}
}
92. What are the differences between GraphQL queries and REST endpoints?
Key Differences:
-
Data Fetching: - REST: Fixed data structure per endpoint - GraphQL: Client specifies exact fields needed
-
Number of Requests: - REST: Multiple requests for related data - GraphQL: Single request for complex data
-
Versioning: - REST: URL versioning or headers - GraphQL: Schema evolution
SQL Example:
REST - Multiple Queries:
-- First request
SELECT id, name, email FROM users WHERE id = 1;
-- Second request
SELECT id, title FROM posts WHERE user_id = 1;
-- Third request
SELECT id, name FROM comments WHERE post_id IN (1, 2, 3);
GraphQL - Single Query:
query {
user(id: 1) {
id
name
email
posts {
id
title
comments {
id
name
}
}
}
}
C# Example:
// REST - Multiple endpoints
[HttpGet("users/{id}")]
public async Task<ActionResult<UserDto>> GetUser(int id)
{
var user = await _context.Users
.Where(u => u.Id == id)
.Select(u => new UserDto { Id = u.Id, Name = u.Name, Email = u.Email })
.FirstOrDefaultAsync();
return Ok(user);
}
[HttpGet("users/{id}/posts")]
public async Task<ActionResult<List<PostDto>>> GetUserPosts(int id)
{
var posts = await _context.Posts
.Where(p => p.UserId == id)
.Select(p => new PostDto { Id = p.Id, Title = p.Title })
.ToListAsync();
return Ok(posts);
}
// GraphQL - Single resolver
public class UserResolver
{
public async Task<User> GetUser(int id, [Service] IDataContext context)
{
return await context.Users
.Include(u => u.Posts)
.ThenInclude(p => p.Comments)
.FirstOrDefaultAsync(u => u.Id == id);
}
}
93. Explain gRPC and its benefits
gRPC is a high-performance, open-source RPC framework that uses HTTP/2 for transport and Protocol Buffers for serialization.
Benefits:
- Performance: Binary protocol, HTTP/2 multiplexing
- Strong Typing: Protocol Buffer schemas
- Code Generation: Auto-generated client/server code
- Streaming: Bidirectional streaming support
- Interoperability: Language-agnostic
SQL Example - gRPC vs REST Performance:
-- Traditional REST approach - multiple round trips
SELECT id, name FROM users WHERE department_id = 1;
SELECT id, name FROM departments WHERE id = 1;
SELECT id, name FROM roles WHERE user_id IN (SELECT id FROM users WHERE department_id = 1);
-- gRPC equivalent - single optimized query
WITH user_data AS (
SELECT u.id, u.name, d.name as dept_name, r.name as role_name
FROM users u
JOIN departments d ON u.department_id = d.id
LEFT JOIN user_roles ur ON u.id = ur.user_id
LEFT JOIN roles r ON ur.role_id = r.id
WHERE u.department_id = 1
)
SELECT * FROM user_data;
C# Implementation:
// Protocol Buffer Definition (.proto)
syntax = "proto3";
package user;
service UserService {
rpc GetUser(GetUserRequest) returns (UserResponse);
rpc GetUsersStream(GetUsersRequest) returns (stream UserResponse);
}
message GetUserRequest {
int32 user_id = 1;
}
message UserResponse {
int32 id = 1;
string name = 2;
string email = 3;
repeated string roles = 4;
}
// C# Server Implementation
public class UserService : User.UserBase
{
public override async Task<UserResponse> GetUser(GetUserRequest request, ServerCallContext context)
{
var user = await _context.Users
.Include(u => u.Roles)
.FirstOrDefaultAsync(u => u.Id == request.UserId);
return new UserResponse
{
Id = user.Id,
Name = user.Name,
Email = user.Email,
Roles = { user.Roles.Select(r => r.Name) }
};
}
public override async Task GetUsersStream(GetUsersRequest request,
IServerStreamWriter<UserResponse> responseStream, ServerCallContext context)
{
var users = await _context.Users
.Include(u => u.Roles)
.Where(u => u.DepartmentId == request.DepartmentId)
.ToListAsync();
foreach (var user in users)
{
await responseStream.WriteAsync(new UserResponse
{
Id = user.Id,
Name = user.Name,
Email = user.Email,
Roles = { user.Roles.Select(r => r.Name) }
});
}
}
}
94. What is the difference between gRPC and REST?
gRPC uses HTTP/2 transport and Protocol Buffers by default, supports streaming, and is often efficient for controlled service-to-service contracts. REST commonly uses HTTP resource semantics and JSON but is not limited to JSON. Browser support, observability, caching, public-client needs, and ecosystem fit affect the choice.
95. Explain WebSockets and real-time APIs
WebSockets provide full-duplex communication channels over a single TCP connection, enabling real-time data exchange.
Key Features:
- Bidirectional: Client and server can send messages
- Persistent: Connection stays open
- Real-time: Low latency communication
- Event-driven: Push-based architecture
SQL Example - Real-time Data:
-- Traditional polling approach
SELECT id, message, created_at
FROM notifications
WHERE user_id = 1 AND created_at > @last_poll_time
ORDER BY created_at DESC;
-- WebSocket approach - server pushes changes
-- Server monitors for changes and pushes to connected clients
CREATE TRIGGER notification_insert_trigger
ON notifications
AFTER INSERT
AS
BEGIN
DECLARE @user_id INT = (SELECT user_id FROM inserted);
DECLARE @message NVARCHAR(MAX) = (SELECT message FROM inserted);
-- Push to WebSocket clients
EXEC sp_send_websocket_message @user_id, @message;
END;
C# Implementation:
// WebSocket Hub
public class ChatHub : Hub
{
private readonly IDataContext _context;
public ChatHub(IDataContext context)
{
_context = context;
}
public async Task SendMessage(string message, int roomId)
{
// Save to database
var chatMessage = new ChatMessage
{
Content = message,
RoomId = roomId,
UserId = GetCurrentUserId(),
Timestamp = DateTime.UtcNow
};
await _context.ChatMessages.AddAsync(chatMessage);
await _context.SaveChangesAsync();
// Broadcast to all clients in room
await Clients.Group($"room_{roomId}").SendAsync("ReceiveMessage",
new {
content = message,
userId = GetCurrentUserId(),
timestamp = chatMessage.Timestamp
});
}
public async Task JoinRoom(int roomId)
{
await Groups.AddToGroupAsync(Context.ConnectionId, $"room_{roomId}");
// Send recent messages
var recentMessages = await _context.ChatMessages
.Where(m => m.RoomId == roomId)
.OrderByDescending(m => m.Timestamp)
.Take(50)
.Select(m => new { m.Content, m.UserId, m.Timestamp })
.ToListAsync();
await Clients.Caller.SendAsync("LoadMessages", recentMessages);
}
}
// SignalR Configuration
public void ConfigureServices(IServiceCollection services)
{
services.AddSignalR();
services.AddDbContext<DataContext>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<ChatHub>("/chathub");
});
}
96. What are the differences between WebSockets and HTTP polling?
Key Differences:
| Aspect | WebSockets | HTTP Polling |
|---|---|---|
| Connection | Persistent | Per request |
| Latency | Low | High |
| Server Push | Yes | No |
| Resource Usage | Efficient | Inefficient |
| Scalability | Better | Limited |
SQL Performance Comparison:
-- HTTP Polling - Client repeatedly queries
-- Client polls every 5 seconds
SELECT id, message, created_at
FROM notifications
WHERE user_id = 1
AND created_at > @last_poll_time
ORDER BY created_at DESC;
-- WebSocket - Server pushes when data changes
-- Server monitors and pushes only when needed
CREATE PROCEDURE GetUserNotifications
@user_id INT,
@last_check DATETIME
AS
BEGIN
SELECT id, message, created_at
FROM notifications
WHERE user_id = @user_id
AND created_at > @last_check
ORDER BY created_at DESC;
END;
-- Trigger to notify WebSocket clients
CREATE TRIGGER notification_websocket_trigger
ON notifications
AFTER INSERT
AS
BEGIN
DECLARE @user_id INT = (SELECT user_id FROM inserted);
-- Push to WebSocket clients immediately
EXEC sp_notify_websocket_clients @user_id, 'new_notification';
END;
C# Implementation:
// HTTP Polling Controller
[ApiController]
[Route("api/[controller]")]
public class NotificationsController : ControllerBase
{
[HttpGet("poll")]
public async Task<ActionResult<List<NotificationDto>>> PollNotifications(
[FromQuery] DateTime lastCheck)
{
var notifications = await _context.Notifications
.Where(n => n.UserId == GetCurrentUserId() && n.CreatedAt > lastCheck)
.OrderByDescending(n => n.CreatedAt)
.Select(n => new NotificationDto
{
Id = n.Id,
Message = n.Message,
CreatedAt = n.CreatedAt
})
.ToListAsync();
return Ok(notifications);
}
}
// WebSocket Hub
public class NotificationHub : Hub
{
public async Task SendNotification(int userId, string message)
{
// Save notification
var notification = new Notification
{
UserId = userId,
Message = message,
CreatedAt = DateTime.UtcNow
};
await _context.Notifications.AddAsync(notification);
await _context.SaveChangesAsync();
// Push to specific user
await Clients.User(userId.ToString()).SendAsync("ReceiveNotification",
new { id = notification.Id, message, createdAt = notification.CreatedAt });
}
}
// Client-side polling (JavaScript)
setInterval(async () => {
const response = await fetch(`/api/notifications/poll?lastCheck=${lastCheckTime}`);
const notifications = await response.json();
// Process notifications
}, 5000);
// Client-side WebSocket (JavaScript)
const connection = new signalR.HubConnectionBuilder()
.withUrl("/notificationhub")
.build();
connection.on("ReceiveNotification", (notification) => {
// Process notification immediately
});
connection.start();
97. Explain API microservices architecture
Microservices is an architectural style where an application is built as a collection of small, independent services.
Key Characteristics:
- Service Independence: Each service can be developed, deployed, and scaled independently
- Technology Diversity: Different services can use different technologies
- Data Isolation: Each service has its own database
- API Gateway: Single entry point for client requests
SQL Example - Microservices Data Patterns:
-- Monolithic approach - single database
SELECT u.id, u.name, u.email, o.id as order_id, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE u.id = 1;
-- Microservices approach - separate databases
-- User Service Database
SELECT id, name, email FROM users WHERE id = 1;
-- Order Service Database
SELECT id, user_id, total FROM orders WHERE user_id = 1;
-- API Gateway aggregates data from multiple services
C# Implementation:
// API Gateway
[ApiController]
[Route("api/[controller]")]
public class UserProfileController : ControllerBase
{
private readonly IUserService _userService;
private readonly IOrderService _orderService;
private readonly IProductService _productService;
public async Task<ActionResult<UserProfileDto>> GetUserProfile(int userId)
{
// Parallel calls to microservices
var userTask = _userService.GetUserAsync(userId);
var ordersTask = _orderService.GetUserOrdersAsync(userId);
await Task.WhenAll(userTask, ordersTask);
var user = await userTask;
var orders = await ordersTask;
// Enrich order data with product information
var productIds = orders.SelectMany(o => o.Items.Select(i => i.ProductId)).Distinct();
var products = await _productService.GetProductsAsync(productIds);
return Ok(new UserProfileDto
{
User = user,
Orders = orders.Select(o => new OrderDto
{
Id = o.Id,
Total = o.Total,
Items = o.Items.Select(i => new OrderItemDto
{
ProductId = i.ProductId,
ProductName = products.First(p => p.Id == i.ProductId).Name,
Quantity = i.Quantity,
Price = i.Price
}).ToList()
}).ToList()
});
}
}
// User Service
public class UserService : IUserService
{
private readonly UserDbContext _context;
public async Task<UserDto> GetUserAsync(int userId)
{
return await _context.Users
.Where(u => u.Id == userId)
.Select(u => new UserDto
{
Id = u.Id,
Name = u.Name,
Email = u.Email
})
.FirstOrDefaultAsync();
}
}
// Order Service
public class OrderService : IOrderService
{
private readonly OrderDbContext _context;
public async Task<List<OrderDto>> GetUserOrdersAsync(int userId)
{
return await _context.Orders
.Where(o => o.UserId == userId)
.Include(o => o.Items)
.Select(o => new OrderDto
{
Id = o.Id,
Total = o.Total,
Items = o.Items.Select(i => new OrderItemDto
{
ProductId = i.ProductId,
Quantity = i.Quantity,
Price = i.Price
}).ToList()
})
.ToListAsync();
}
}
98. What is the difference between monolithic and microservices APIs?
Key Differences:
| Aspect | Monolithic | Microservices |
|---|---|---|
| Architecture | Single application | Multiple services |
| Database | Shared database | Database per service |
| Deployment | All-or-nothing | Independent |
| Scaling | Scale entire app | Scale individual services |
| Technology | Single tech stack | Multiple technologies |
SQL Example - Data Access Patterns:
-- Monolithic - Single database with joins
SELECT
u.id, u.name, u.email,
o.id as order_id, o.total, o.created_at,
p.id as product_id, p.name as product_name,
oi.quantity, oi.price
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
WHERE u.id = 1
ORDER BY o.created_at DESC;
-- Microservices - Separate databases
-- User Service
SELECT id, name, email FROM users WHERE id = 1;
-- Order Service
SELECT id, user_id, total, created_at FROM orders WHERE user_id = 1;
-- Product Service
SELECT id, name FROM products WHERE id IN (1, 2, 3);
C# Implementation:
// Monolithic Approach
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
private readonly ApplicationDbContext _context;
[HttpGet("{id}/profile")]
public async Task<ActionResult<UserProfileDto>> GetUserProfile(int id)
{
var userProfile = await _context.Users
.Where(u => u.Id == id)
.Select(u => new UserProfileDto
{
Id = u.Id,
Name = u.Name,
Email = u.Email,
Orders = u.Orders.Select(o => new OrderDto
{
Id = o.Id,
Total = o.Total,
CreatedAt = o.CreatedAt,
Items = o.Items.Select(i => new OrderItemDto
{
ProductId = i.ProductId,
ProductName = i.Product.Name,
Quantity = i.Quantity,
Price = i.Price
}).ToList()
}).ToList()
})
.FirstOrDefaultAsync();
return Ok(userProfile);
}
}
// Microservices Approach
[ApiController]
[Route("api/[controller]")]
public class UserController : ControllerBase
{
private readonly IUserService _userService;
private readonly IOrderService _orderService;
private readonly IProductService _productService;
[HttpGet("{id}/profile")]
public async Task<ActionResult<UserProfileDto>> GetUserProfile(int id)
{
// Get user data
var user = await _userService.GetUserAsync(id);
if (user == null) return NotFound();
// Get user orders
var orders = await _orderService.GetUserOrdersAsync(id);
// Get product details for all order items
var productIds = orders.SelectMany(o => o.Items.Select(i => i.ProductId)).Distinct();
var products = await _productService.GetProductsAsync(productIds);
// Build response
var userProfile = new UserProfileDto
{
Id = user.Id,
Name = user.Name,
Email = user.Email,
Orders = orders.Select(o => new OrderDto
{
Id = o.Id,
Total = o.Total,
CreatedAt = o.CreatedAt,
Items = o.Items.Select(i => new OrderItemDto
{
ProductId = i.ProductId,
ProductName = products.First(p => p.Id == i.ProductId).Name,
Quantity = i.Quantity,
Price = i.Price
}).ToList()
}).ToList()
};
return Ok(userProfile);
}
}
99. Explain API event-driven architecture
Event-driven architecture is a pattern where services communicate through events, enabling loose coupling and asynchronous processing.
Key Components:
- Event Producers: Services that generate events
- Event Consumers: Services that react to events
- Event Bus/Message Broker: Infrastructure for event distribution
- Event Store: Persistent storage for events
SQL Example - Event Sourcing:
-- Event Store Table
CREATE TABLE events (
id BIGINT IDENTITY(1,1) PRIMARY KEY,
aggregate_id NVARCHAR(50) NOT NULL,
event_type NVARCHAR(100) NOT NULL,
event_data NVARCHAR(MAX) NOT NULL,
version INT NOT NULL,
created_at DATETIME2 DEFAULT GETUTCDATE(),
INDEX idx_aggregate_version (aggregate_id, version)
);
-- Example events for user registration
INSERT INTO events (aggregate_id, event_type, event_data, version) VALUES
('user-123', 'UserRegistered', '{"id":"user-123","name":"John Doe","email":"john@example.com"}', 1),
('user-123', 'UserEmailUpdated', '{"id":"user-123","email":"john.doe@example.com"}', 2),
('user-123', 'UserProfileCompleted', '{"id":"user-123","phone":"123-456-7890"}', 3);
-- Reconstruct user state from events
WITH user_events AS (
SELECT event_type, event_data, version
FROM events
WHERE aggregate_id = 'user-123'
ORDER BY version
)
SELECT * FROM user_events;
C# Implementation:
// Event Base
public abstract class Event
{
public Guid Id { get; set; } = Guid.NewGuid();
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
public string AggregateId { get; set; }
public int Version { get; set; }
}
// Domain Events
public class UserRegisteredEvent : Event
{
public string Name { get; set; }
public string Email { get; set; }
}
public class UserEmailUpdatedEvent : Event
{
public string Email { get; set; }
}
// Event Store
public interface IEventStore
{
Task SaveEventsAsync(string aggregateId, IEnumerable<Event> events, int expectedVersion);
Task<List<Event>> GetEventsAsync(string aggregateId);
}
public class SqlEventStore : IEventStore
{
private readonly IDbConnection _connection;
public async Task SaveEventsAsync(string aggregateId, IEnumerable<Event> events, int expectedVersion)
{
using var transaction = _connection.BeginTransaction();
try
{
foreach (var @event in events)
{
var sql = @"
INSERT INTO events (aggregate_id, event_type, event_data, version, created_at)
VALUES (@AggregateId, @EventType, @EventData, @Version, @Timestamp)";
await _connection.ExecuteAsync(sql, new
{
@event.AggregateId,
EventType = @event.GetType().Name,
EventData = JsonSerializer.Serialize(@event),
@event.Version,
@event.Timestamp
}, transaction);
}
transaction.Commit();
}
catch
{
transaction.Rollback();
throw;
}
}
public async Task<List<Event>> GetEventsAsync(string aggregateId)
{
var sql = @"
SELECT event_type, event_data, version, created_at
FROM events
WHERE aggregate_id = @AggregateId
ORDER BY version";
var events = await _connection.QueryAsync<EventRecord>(sql, new { AggregateId = aggregateId });
return events.Select(e => JsonSerializer.Deserialize<Event>(e.EventData, new JsonSerializerOptions
{
PropertyNameCaseInsensitive = true
})).ToList();
}
}
// Event Handler
public class UserEventHandler : IEventHandler<UserRegisteredEvent>
{
private readonly IEmailService _emailService;
private readonly INotificationService _notificationService;
public async Task HandleAsync(UserRegisteredEvent @event)
{
// Send welcome email
await _emailService.SendWelcomeEmailAsync(@event.Email, @event.Name);
// Send notification to admin
await _notificationService.NotifyAdminAsync($"New user registered: {@event.Name}");
}
}
// Event Bus
public class EventBus : IEventBus
{
private readonly IServiceProvider _serviceProvider;
private readonly IEventStore _eventStore;
public async Task PublishAsync<T>(T @event) where T : Event
{
// Store event
await _eventStore.SaveEventsAsync(@event.AggregateId, new[] { @event }, @event.Version);
// Publish to handlers
var handlers = _serviceProvider.GetServices<IEventHandler<T>>();
var tasks = handlers.Select(h => h.HandleAsync(@event));
await Task.WhenAll(tasks);
}
}
100. What are the differences between synchronous and asynchronous APIs?
Key Differences:
| Aspect | Synchronous | Asynchronous |
|---|---|---|
| Response Time | Immediate | Delayed |
| Client Blocking | Yes | No |
| Resource Usage | High | Low |
| Complexity | Simple | Complex |
| Error Handling | Immediate | Deferred |
SQL Example - Processing Patterns:
-- Synchronous processing
CREATE PROCEDURE ProcessOrderSynchronous
@order_id INT
AS
BEGIN
BEGIN TRANSACTION;
-- Validate inventory
IF NOT EXISTS (SELECT 1 FROM inventory WHERE product_id = @product_id AND quantity >= @requested_quantity)
THROW 50001, 'Insufficient inventory', 1;
-- Update inventory
UPDATE inventory
SET quantity = quantity - @requested_quantity
WHERE product_id = @product_id;
-- Create order
INSERT INTO orders (id, user_id, total, status)
VALUES (@order_id, @user_id, @total, 'confirmed');
-- Send confirmation email (blocking)
EXEC sp_send_email @user_email, 'Order Confirmed', 'Your order has been confirmed';
COMMIT;
END;
-- Asynchronous processing
CREATE PROCEDURE ProcessOrderAsynchronous
@order_id INT
AS
BEGIN
BEGIN TRANSACTION;
-- Create order with pending status
INSERT INTO orders (id, user_id, total, status)
VALUES (@order_id, @user_id, @total, 'pending');
-- Queue processing task
INSERT INTO processing_queue (order_id, task_type, created_at)
VALUES (@order_id, 'order_processing', GETUTCDATE());
COMMIT;
END;
-- Background processor
CREATE PROCEDURE ProcessQueuedOrders
AS
BEGIN
WHILE EXISTS (SELECT 1 FROM processing_queue WHERE processed = 0)
BEGIN
DECLARE @order_id INT = (
SELECT TOP 1 order_id
FROM processing_queue
WHERE processed = 0
ORDER BY created_at
);
-- Process order asynchronously
EXEC sp_process_order_background @order_id;
UPDATE processing_queue
SET processed = 1, processed_at = GETUTCDATE()
WHERE order_id = @order_id;
END;
END;
C# Implementation:
// Synchronous API
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<OrderDto>> CreateOrder(CreateOrderRequest request)
{
// Validate inventory (blocking)
var inventory = await _inventoryService.CheckAvailabilityAsync(request.ProductId, request.Quantity);
if (!inventory.IsAvailable)
return BadRequest("Insufficient inventory");
// Process payment (blocking)
var paymentResult = await _paymentService.ProcessPaymentAsync(request.PaymentInfo);
if (!paymentResult.Success)
return BadRequest("Payment failed");
// Create order (blocking)
var order = await _orderService.CreateOrderAsync(request);
// Send confirmation email (blocking)
await _emailService.SendOrderConfirmationAsync(order);
// Send notification (blocking)
await _notificationService.SendOrderNotificationAsync(order);
return Ok(order);
}
}
// Asynchronous API
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpPost]
public async Task<ActionResult<OrderResponseDto>> CreateOrder(CreateOrderRequest request)
{
// Create order with pending status
var order = await _orderService.CreatePendingOrderAsync(request);
// Queue background processing
await _messageBus.PublishAsync(new OrderCreatedEvent
{
OrderId = order.Id,
UserId = order.UserId,
ProductId = request.ProductId,
Quantity = request.Quantity,
PaymentInfo = request.PaymentInfo
});
return Accepted(new OrderResponseDto
{
OrderId = order.Id,
Status = "processing",
Message = "Order is being processed. You will receive confirmation shortly."
});
}
[HttpGet("{id}/status")]
public async Task<ActionResult<OrderStatusDto>> GetOrderStatus(int id)
{
var status = await _orderService.GetOrderStatusAsync(id);
return Ok(status);
}
}
// Background Service
public class OrderProcessingService : BackgroundService
{
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
var events = await _messageBus.ReceiveAsync<OrderCreatedEvent>();
foreach (var @event in events)
{
try
{
// Process order asynchronously
await ProcessOrderAsync(@event);
}
catch (Exception ex)
{
await _messageBus.PublishAsync(new OrderProcessingFailedEvent
{
OrderId = @event.OrderId,
Error = ex.Message
});
}
}
await Task.Delay(1000, stoppingToken);
}
}
private async Task ProcessOrderAsync(OrderCreatedEvent @event)
{
// Validate inventory
var inventory = await _inventoryService.CheckAvailabilityAsync(@event.ProductId, @event.Quantity);
if (!inventory.IsAvailable)
{
await _orderService.UpdateOrderStatusAsync(@event.OrderId, "cancelled", "Insufficient inventory");
return;
}
// Process payment
var paymentResult = await _paymentService.ProcessPaymentAsync(@event.PaymentInfo);
if (!paymentResult.Success)
{
await _orderService.UpdateOrderStatusAsync(@event.OrderId, "cancelled", "Payment failed");
return;
}
// Update inventory
await _inventoryService.ReserveInventoryAsync(@event.ProductId, @event.Quantity);
// Confirm order
await _orderService.UpdateOrderStatusAsync(@event.OrderId, "confirmed");
// Send notifications asynchronously
_ = Task.Run(async () =>
{
await _emailService.SendOrderConfirmationAsync(@event.OrderId);
await _notificationService.SendOrderNotificationAsync(@event.OrderId);
});
}
}