ASP.NET Core Web
ASP.NET Core Web API Development
Table of Contents
- RESTful API Design Principles
- Content Negotiation
- API Versioning Strategies
- OpenAPI/Swagger Integration
- Interview Questions
RESTful API Design Principles
What is REST?
REST (Representational State Transfer) is an architectural style for designing networked applications. It relies on stateless, client-server communication, typically over HTTP.
Core REST Principles
1. Stateless Communication
- Each request from client to server must contain all the information needed to understand and process the request
- Server doesn't store client state between requests
- Improves scalability and reliability
// Stateless example - each request includes authentication
[HttpGet("users/{id}")]
[Authorize]
public async Task<IActionResult> GetUser(int id)
{
var user = await _userService.GetUserByIdAsync(id);
return Ok(user);
}
2. Client-Server Architecture
- Clear separation between client and server concerns
- Client handles user interface, server handles data storage and business logic
- Allows independent evolution of client and server
3. Uniform Interface
- Identification of resources (URIs)
- Manipulation of resources through representations
- Self-descriptive messages
- Hypermedia as the engine of application state (HATEOAS)
4. Resource-Based Design
- Resources are identified by URIs
- Resources have representations (JSON, XML, etc.)
- Resources can have multiple representations
// Resource-based endpoints
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
[HttpGet] // GET /api/products
public async Task<IActionResult> GetProducts() { ... }
[HttpGet("{id}")] // GET /api/products/123
public async Task<IActionResult> GetProduct(int id) { ... }
[HttpPost] // POST /api/products
public async Task<IActionResult> CreateProduct([FromBody] ProductDto product) { ... }
[HttpPut("{id}")] // PUT /api/products/123
public async Task<IActionResult> UpdateProduct(int id, [FromBody] ProductDto product) { ... }
[HttpDelete("{id}")] // DELETE /api/products/123
public async Task<IActionResult> DeleteProduct(int id) { ... }
}
5. HTTP Methods and Status Codes
- GET: Retrieve resource(s)
- POST: Create new resource
- PUT: Update entire resource
- PATCH: Partial update
- DELETE: Remove resource
Common HTTP Status Codes:
- 200 OK - Success
- 201 Created - Resource created
- 204 No Content - Success, no response body
- 400 Bad Request - Invalid request
- 401 Unauthorized - Authentication required
- 403 Forbidden - Authorization failed
- 404 Not Found - Resource doesn't exist
- 409 Conflict - Resource conflict
- 422 Unprocessable Entity - Validation errors
- 500 Internal Server Error - Server error
6. Idempotency
- GET, PUT, DELETE should be idempotent (multiple identical requests = same result)
- POST is not idempotent (creates new resources)
7. HATEOAS (Hypermedia as the Engine of Application State)
- API responses include links to related resources
- Clients discover available actions through links
public class ProductResponse
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public List<Link> Links { get; set; } = new();
public ProductResponse(Product product)
{
Id = product.Id;
Name = product.Name;
Price = product.Price;
Links.Add(new Link { Rel = "self", Href = $"/api/products/{Id}", Method = "GET" });
Links.Add(new Link { Rel = "update", Href = $"/api/products/{Id}", Method = "PUT" });
Links.Add(new Link { Rel = "delete", Href = $"/api/products/{Id}", Method = "DELETE" });
}
}
public class Link
{
public string Rel { get; set; }
public string Href { get; set; }
public string Method { get; set; }
}
Best Practices for RESTful APIs
URL Design
- Use nouns, not verbs:
/productsnot/getProducts - Use plural nouns:
/productsnot/product - Use lowercase with hyphens:
/product-categories - Avoid deep nesting: prefer
/products/123/reviewsover/products/categories/electronics/items/123/reviews
Filtering, Sorting, Pagination
// GET /api/products?category=electronics&sort=price&order=asc&page=1&limit=10
[HttpGet]
public async Task<IActionResult> GetProducts(
[FromQuery] string category,
[FromQuery] string sort = "name",
[FromQuery] string order = "asc",
[FromQuery] int page = 1,
[FromQuery] int limit = 10)
{
var query = _context.Products.AsQueryable();
if (!string.IsNullOrEmpty(category))
query = query.Where(p => p.Category == category);
query = order.ToLower() == "desc"
? query.OrderByDescending(GetSortExpression(sort))
: query.OrderBy(GetSortExpression(sort));
var totalCount = await query.CountAsync();
var products = await query
.Skip((page - 1) * limit)
.Take(limit)
.ToListAsync();
var result = new
{
Data = products,
Pagination = new
{
Page = page,
Limit = limit,
TotalCount = totalCount,
TotalPages = (int)Math.Ceiling(totalCount / (double)limit)
}
};
return Ok(result);
}
private Expression<Func<Product, object>> GetSortExpression(string sortBy)
{
return sortBy.ToLower() switch
{
"price" => p => p.Price,
"name" => p => p.Name,
_ => p => p.Id
};
}
Error Handling
public class ErrorResponse
{
public string Type { get; set; }
public string Title { get; set; }
public int Status { get; set; }
public string Detail { get; set; }
public Dictionary<string, string[]> Errors { get; set; }
}
[ApiController]
public class BaseController : ControllerBase
{
protected IActionResult BadRequestWithErrors(Dictionary<string, string[]> errors)
{
var errorResponse = new ErrorResponse
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.5.1",
Title = "Bad Request",
Status = 400,
Detail = "One or more validation errors occurred.",
Errors = errors
};
return BadRequest(errorResponse);
}
}
Content Negotiation
What is Content Negotiation?
Content negotiation is the process of selecting the best representation for a given response when there are multiple representations available. It allows clients to specify their preferred format for request/response bodies.
Content Types in ASP.NET Core
1. Input Formatters
Handle incoming request bodies (deserialization)
2. Output Formatters
Handle outgoing response bodies (serialization)
Configuring Content Negotiation
// Program.cs or Startup.cs
builder.Services.AddControllers(options =>
{
// Clear default formatters
options.InputFormatters.Clear();
options.OutputFormatters.Clear();
// Add JSON formatter
options.InputFormatters.Add(new SystemTextJsonInputFormatter(
new JsonOptions
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
}));
options.OutputFormatters.Add(new SystemTextJsonOutputFormatter(
new JsonOptions
{
PropertyNameCaseInsensitive = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
WriteIndented = false
}));
// Add XML formatter
options.InputFormatters.Add(new XmlDataContractSerializerInputFormatter(
new XmlOptions { UseXmlSerializer = false }));
options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
});
Accept Header Negotiation
Clients can specify preferred content types using the Accept header:
Accept: application/json, application/xml;q=0.9, */*;q=0.8
Custom Content Negotiation
public class CustomContentNegotiator : IContentNegotiator
{
public ContentNegotiationResult Negotiate(
ContentNegotiationContext context)
{
var acceptHeader = context.Request.Headers.Accept.ToString();
if (acceptHeader.Contains("application/vnd.api+json"))
{
return new ContentNegotiationResult(
new MediaTypeHeaderValue("application/vnd.api+json"),
context.ConnegContext.FirstFormatter);
}
// Default to JSON
return new ContentNegotiationResult(
new MediaTypeHeaderValue("application/json"),
context.ConnegContext.FirstFormatter);
}
}
// Register custom negotiator
builder.Services.AddControllers(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
}).AddXmlDataContractSerializerFormatters();
Format-Specific Actions
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
[HttpGet("export")]
[Produces("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet")]
public async Task<IActionResult> ExportToExcel()
{
var products = await _productService.GetAllProductsAsync();
var excelData = GenerateExcel(products);
return File(excelData, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "products.xlsx");
}
[HttpGet("v2")]
[Produces("application/vnd.api.v2+json")]
public async Task<IActionResult> GetProductsV2()
{
var products = await _productService.GetAllProductsAsync();
return Ok(new { data = products, version = "2.0" });
}
}
Custom Media Types
// Custom media type for API versioning
public static class MediaTypes
{
public const string Json = "application/json";
public const string Xml = "application/xml";
public const string VndApiJson = "application/vnd.api+json";
public const string VndApiV1Json = "application/vnd.api.v1+json";
public const string VndApiV2Json = "application/vnd.api.v2+json";
}
[HttpGet]
[Produces(MediaTypes.VndApiV1Json, MediaTypes.Json)]
public async Task<IActionResult> GetProducts()
{
var products = await _productService.GetAllProductsAsync();
return Ok(products);
}
Content Negotiation Best Practices
- Default to JSON - Most modern APIs use JSON as the default format
- Support XML - For legacy system compatibility
- Use Vendor Media Types - For API versioning (
application/vnd.api.v1+json) - Respect Accept Headers - Honor client preferences
- Provide Format Overrides - Allow query parameters like
?format=xml - Document Supported Formats - Clearly document available content types
API Versioning Strategies
Why API Versioning?
APIs evolve over time. Versioning ensures backward compatibility while allowing new features.
Common Versioning Strategies
1. URI Versioning
Include version in the URL path.
Pros:
- Explicit and clear
- Easy to understand
- Caching friendly
Cons:
- Violates REST principles (version is not a resource)
- Requires routing changes
// URI Versioning
[Route("api/v1/[controller]")]
[ApiController]
public class ProductsV1Controller : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetProducts() { ... }
}
[Route("api/v2/[controller]")]
[ApiController]
public class ProductsV2Controller : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetProducts() { ... }
}
// Routing configuration
app.MapControllers();
2. Query Parameter Versioning
Pass version as query parameter.
Pros:
- Single endpoint
- Easy to implement
Cons:
- Less visible in URLs
- Can be forgotten
- Not cache-friendly
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetProducts([FromQuery] string version = "1")
{
if (version == "2")
{
// V2 logic
return await GetProductsV2();
}
// V1 logic
return await GetProductsV1();
}
}
3. Header Versioning
Use custom headers for versioning.
Pros:
- Keeps URLs clean
- Follows REST principles
- Good for content negotiation
Cons:
- Less discoverable
- Requires documentation
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
[HttpGet]
public async Task<IActionResult> GetProducts()
{
var version = Request.Headers["X-API-Version"].ToString();
if (version == "2")
{
// V2 logic
return await GetProductsV2();
}
// V1 logic
return await GetProductsV1();
}
}
4. Media Type Versioning
Use Accept header with vendor media types.
Pros:
- REST-compliant
- Leverages content negotiation
- Supports multiple representations
Cons:
- Complex to implement
- Less intuitive
[Route("api/[controller]")]
[ApiController]
public class ProductsController : ControllerBase
{
[HttpGet]
[Produces("application/vnd.api.v1+json", "application/vnd.api.v2+json")]
public async Task<IActionResult> GetProducts()
{
var acceptHeader = Request.Headers.Accept.ToString();
if (acceptHeader.Contains("vnd.api.v2"))
{
// V2 response
Response.ContentType = "application/vnd.api.v2+json";
return await GetProductsV2();
}
// V1 response
Response.ContentType = "application/vnd.api.v1+json";
return await GetProductsV1();
}
}
ASP.NET Core API Versioning Package
Microsoft provides a comprehensive API versioning package.
// Program.cs
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
options.ApiVersionReader = ApiVersionReader.Combine(
new UrlSegmentApiVersionReader(),
new HeaderApiVersionReader("X-API-Version"),
new MediaTypeApiVersionReader("version"));
});
// Controller
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.0")]
public async Task<IActionResult> GetProductsV1() { ... }
[HttpGet]
[MapToApiVersion("2.0")]
public async Task<IActionResult> GetProductsV2() { ... }
}
Versioning Best Practices
- Plan for Versioning from Day One
- Use Semantic Versioning (Major.Minor.Patch)
- Document Version Changes clearly
- Support Multiple Versions simultaneously
- Deprecation Policy - warn users before removing versions
- Version Headers in responses
- Test All Versions in CI/CD
Handling Breaking Changes
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.0")]
public async Task<IActionResult> GetProductsV1()
{
var products = await _productService.GetAllProductsAsync();
return Ok(products.Select(p => new ProductV1Dto
{
Id = p.Id,
Name = p.Name,
Price = p.Price
}));
}
[HttpGet]
[MapToApiVersion("2.0")]
public async Task<IActionResult> GetProductsV2()
{
var products = await _productService.GetAllProductsAsync();
return Ok(products.Select(p => new ProductV2Dto
{
Id = p.Id,
Name = p.Name,
Price = p.Price,
Category = p.Category,
InStock = p.Quantity > 0
}));
}
}
OpenAPI/Swagger Integration
What is OpenAPI?
OpenAPI Specification (formerly Swagger) is a standard for describing REST APIs. It allows tools to generate documentation, client libraries, and server stubs.
Setting up Swashbuckle.AspNetCore
// Program.cs
builder.Services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Title = "My API",
Version = "v1",
Description = "A sample API",
Contact = new OpenApiContact
{
Name = "API Support",
Email = "support@example.com"
}
});
c.SwaggerDoc("v2", new OpenApiInfo
{
Title = "My API",
Version = "v2",
Description = "A sample API v2"
});
// Include XML comments
var xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
c.IncludeXmlComments(xmlPath);
// Enable JWT Bearer token support
c.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{
Description = "JWT Authorization header using the Bearer scheme",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.ApiKey,
Scheme = "Bearer"
});
c.AddSecurityRequirement(new OpenApiSecurityRequirement
{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "Bearer"
}
},
Array.Empty<string>()
}
});
});
app.UseSwagger();
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API v1");
c.SwaggerEndpoint("/swagger/v2/swagger.json", "My API v2");
});
Documenting Controllers and Actions
/// <summary>
/// Manages product operations
/// </summary>
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase
{
/// <summary>
/// Gets all products
/// </summary>
/// <param name="category">Optional category filter</param>
/// <param name="page">Page number (default: 1)</param>
/// <param name="limit">Items per page (default: 10)</param>
/// <returns>List of products</returns>
/// <response code="200">Returns the list of products</response>
/// <response code="400">If parameters are invalid</response>
[HttpGet]
[ProducesResponseType(typeof(IEnumerable<ProductDto>), 200)]
[ProducesResponseType(400)]
public async Task<IActionResult> GetProducts(
[FromQuery] string category,
[FromQuery, Range(1, int.MaxValue)] int page = 1,
[FromQuery, Range(1, 100)] int limit = 10)
{
// Implementation
}
/// <summary>
/// Creates a new product
/// </summary>
/// <param name="product">Product data</param>
/// <returns>Created product</returns>
/// <response code="201">Returns the created product</response>
/// <response code="400">If product data is invalid</response>
[HttpPost]
[ProducesResponseType(typeof(ProductDto), 201)]
[ProducesResponseType(400)]
public async Task<IActionResult> CreateProduct([FromBody] CreateProductDto product)
{
// Implementation
}
}
Custom Schema Examples
builder.Services.AddSwaggerGen(c =>
{
c.MapType<ProductDto>(() => new OpenApiSchema
{
Example = new OpenApiObject
{
["id"] = new OpenApiInteger(1),
["name"] = new OpenApiString("Sample Product"),
["price"] = new OpenApiDouble(29.99),
["category"] = new OpenApiString("Electronics")
}
});
});
API Versioning with Swagger
builder.Services.AddSwaggerGen(c =>
{
c.DocInclusionPredicate((version, desc) =>
{
if (!desc.TryGetMethodInfo(out var methodInfo)) return false;
var versions = methodInfo.DeclaringType?
.GetCustomAttributes(true)
.OfType<ApiVersionAttribute>()
.SelectMany(attr => attr.Versions);
var maps = methodInfo.GetCustomAttributes(true)
.OfType<MapToApiVersionAttribute>()
.SelectMany(attr => attr.Versions)
.ToArray();
return versions?.Any(v => $"v{v}" == version) == true &&
(!maps.Any() || maps.Any(v => $"v{v}" == version));
});
});
Advanced Swagger Configuration
Custom Operation Filters
public class AddRequiredHeaderParameter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (operation.Parameters == null)
operation.Parameters = new List<OpenApiParameter>();
operation.Parameters.Add(new OpenApiParameter
{
Name = "X-API-Key",
In = ParameterLocation.Header,
Required = true,
Schema = new OpenApiSchema { Type = "string" }
});
}
}
// Register filter
builder.Services.AddSwaggerGen(c =>
{
c.OperationFilter<AddRequiredHeaderParameter>();
});
Custom Document Filters
public class AddVersionParameter : IDocumentFilter
{
public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
{
swaggerDoc.Info.Version = "1.0.0";
swaggerDoc.Info.Title = "My API";
}
}
Testing APIs with Swagger UI
Swagger UI provides an interactive interface for testing API endpoints directly from the documentation.
Best Practices for OpenAPI Documentation
- Comprehensive Descriptions - Document all endpoints, parameters, and responses
- Examples - Provide request/response examples
- Error Responses - Document all possible error codes
- Authentication - Show how to authenticate
- Versioning - Document version differences
- Deprecation Notices - Mark deprecated endpoints
- Rate Limiting - Document rate limits if applicable
Interview Questions
Basic Level (1-2 years experience)
Q1. What is REST and what are its key principles?
Answer: REST (Representational State Transfer) is an architectural style for designing networked applications. Key principles include:
- Stateless: Each request contains all necessary information
- Client-Server: Clear separation of concerns
- Uniform Interface: Consistent way to interact with resources
- Resource-Based: Everything is a resource with unique URIs
- HTTP Methods: GET, POST, PUT, DELETE for CRUD operations
- Stateless Communication: No client state stored on server
Q2. What are the main HTTP methods used in REST APIs?
Answer:
- GET: Retrieve resource(s) - safe and idempotent
- POST: Create new resource - not idempotent
- PUT: Update entire resource - idempotent
- PATCH: Partial update - not necessarily idempotent
- DELETE: Remove resource - idempotent
Q3. What is content negotiation in Web APIs?
Answer: Content negotiation is the process of selecting the best representation for a response when multiple formats are available. It allows clients to specify preferred content types via Accept headers, and servers respond with appropriate format (JSON, XML, etc.).
Q4. What are the common API versioning strategies?
Answer:
- URI Versioning:
/api/v1/products - Query Parameters:
/api/products?version=1 - Headers:
X-API-Version: 1 - Media Types:
Accept: application/vnd.api.v1+json
Q5. What is Swagger/OpenAPI?
Answer: OpenAPI (formerly Swagger) is a specification for describing REST APIs. It provides a standard way to document API endpoints, request/response formats, authentication, etc. Tools can generate documentation, client libraries, and server stubs from OpenAPI definitions.
Intermediate Level (2-3 years experience)
Q6. How do you implement content negotiation in ASP.NET Core?
Answer:
// In Program.cs
builder.Services.AddControllers(options =>
{
options.RespectBrowserAcceptHeader = true;
options.ReturnHttpNotAcceptable = true;
}).AddXmlDataContractSerializerFormatters();
// In controller
[HttpGet]
[Produces("application/json", "application/xml")]
public IActionResult GetData()
{
var data = GetData();
return Ok(data);
}
Q7. What are the pros and cons of different API versioning approaches?
Answer: URI Versioning:
- Pros: Explicit, cache-friendly, easy to understand
- Cons: Violates REST, requires routing changes
Header Versioning:
- Pros: REST-compliant, clean URLs
- Cons: Less discoverable, requires documentation
Query Parameter:
- Pros: Simple to implement
- Cons: Clutters URLs, not cache-friendly
Media Type:
- Pros: REST-compliant, supports content negotiation
- Cons: Complex, less intuitive
Q8. How do you handle API versioning with the Microsoft.AspNetCore.Mvc.Versioning package?
Answer:
builder.Services.AddApiVersioning(options =>
{
options.DefaultApiVersion = new ApiVersion(1, 0);
options.AssumeDefaultVersionWhenUnspecified = true;
options.ReportApiVersions = true;
});
[ApiController]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiVersion("1.0")]
[ApiVersion("2.0")]
public class ProductsController : ControllerBase
{
[HttpGet]
[MapToApiVersion("1.0")]
public IActionResult GetV1() { ... }
[HttpGet]
[MapToApiVersion("2.0")]
public IActionResult GetV2() { ... }
}
Q9. What is HATEOAS and why is it important?
Answer: HATEOAS (Hypermedia as the Engine of Application State) means that API responses include links to related resources and available actions. This allows clients to discover API capabilities dynamically rather than hardcoding URLs.
Example response:
{
"id": 1,
"name": "Product A",
"_links": {
"self": { "href": "/api/products/1" },
"update": { "href": "/api/products/1", "method": "PUT" },
"delete": { "href": "/api/products/1", "method": "DELETE" }
}
}
Q10. How do you implement global error handling in ASP.NET Core Web API?
Answer:
// Custom exception filter
public class ApiExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
{
var error = new ErrorResponse
{
Type = "https://tools.ietf.org/html/rfc7231#section-6.6.1",
Title = "Internal Server Error",
Status = 500,
Detail = "An unexpected error occurred."
};
context.Result = new ObjectResult(error)
{
StatusCode = 500
};
context.ExceptionHandled = true;
}
}
// Register filter
builder.Services.AddControllers(options =>
{
options.Filters.Add<ApiExceptionFilter>();
});
Advanced Level (3+ years experience)
Q11. How would you design a RESTful API for a complex domain with nested resources?
Answer: For complex domains, consider:
- Resource Modeling: Identify main entities and relationships
- URL Design: Use shallow nesting, avoid deep hierarchies
- Sub-resources:
/users/123/postsvs/posts?userId=123 - Actions: Use proper HTTP methods, avoid verbs in URLs
- Filtering/Sorting: Query parameters for complex queries
- Pagination: Consistent pagination across all list endpoints
Example:
// Good design
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
[HttpGet("{orderId}/items")]
public IActionResult GetOrderItems(int orderId) { ... }
[HttpPost("{orderId}/items")]
public IActionResult AddOrderItem(int orderId, [FromBody] OrderItem item) { ... }
}
// Avoid deep nesting
// Bad: /api/users/123/orders/456/items/789
// Good: /api/order-items/789?orderId=456&userId=123
Q12. How do you implement API rate limiting and throttling?
Answer:
// Using AspNetCoreRateLimit package
builder.Services.AddMemoryCache();
builder.Services.Configure<IpRateLimitOptions>(options =>
{
options.GeneralRules = new List<RateLimitRule>
{
new RateLimitRule
{
Endpoint = "*",
Limit = 100,
Period = "1m"
}
};
});
builder.Services.AddSingleton<IRateLimitCounterStore, MemoryCacheRateLimitCounterStore>();
builder.Services.AddSingleton<IIpPolicyStore, MemoryCacheIpPolicyStore>();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();
app.UseIpRateLimiting();
Q13. What strategies do you use for API security beyond authentication?
Answer:
- Input Validation: Model validation, custom validators
- Output Encoding: Prevent XSS in responses
- Rate Limiting: Prevent abuse
- CORS: Control cross-origin requests
- HTTPS Only: Enforce secure connections
- API Keys: For client identification
- Request Signing: Verify request integrity
- Audit Logging: Track all API access
Q14. How do you handle long-running operations in Web APIs?
Answer:
- Asynchronous Processing: Return 202 Accepted with status URL
- Webhooks: Notify clients when operation completes
- Polling: Provide status endpoint for clients to check
- WebSockets/SignalR: Real-time updates
Example:
[HttpPost("reports/generate")]
public async Task<IActionResult> GenerateReport([FromBody] ReportRequest request)
{
var operationId = await _reportService.StartReportGenerationAsync(request);
var result = new
{
OperationId = operationId,
Status = "Processing",
StatusUrl = Url.Action("GetReportStatus", new { operationId })
};
return Accepted(result);
}
[HttpGet("reports/status/{operationId}")]
public async Task<IActionResult> GetReportStatus(string operationId)
{
var status = await _reportService.GetReportStatusAsync(operationId);
if (status.IsCompleted)
{
return Ok(new
{
Status = "Completed",
DownloadUrl = Url.Action("DownloadReport", new { operationId })
});
}
return Ok(new { Status = "Processing" });
}
Q15. How do you implement API analytics and monitoring?
Answer:
// Middleware for API analytics
public class ApiAnalyticsMiddleware
{
private readonly RequestDelegate _next;
public ApiAnalyticsMiddleware(RequestDelegate next)
{
_next = next;
}
public async Task InvokeAsync(HttpContext context)
{
var startTime = DateTime.UtcNow;
var originalBodyStream = context.Response.Body;
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
await _next(context);
var endTime = DateTime.UtcNow;
var duration = endTime - startTime;
// Log analytics
await _analyticsService.LogRequestAsync(new ApiRequestLog
{
Method = context.Request.Method,
Path = context.Request.Path,
StatusCode = context.Response.StatusCode,
Duration = duration,
UserAgent = context.Request.Headers["User-Agent"],
IpAddress = context.Connection.RemoteIpAddress?.ToString()
});
await responseBody.CopyToAsync(originalBodyStream);
}
}
}
Q16. What are the challenges of API versioning and how do you mitigate them?
Answer: Challenges:
- Backward Compatibility: Breaking changes affect existing clients
- Maintenance Burden: Supporting multiple versions
- Documentation Complexity: Keeping docs for all versions
- Testing: Testing all supported versions
Mitigation Strategies:
- Semantic Versioning: Clear rules for breaking changes
- Deprecation Policy: Warn clients before removing versions
- Feature Flags: Gradual rollout of new features
- Contract Testing: Ensure versions meet their contracts
- Automated Testing: Test all versions in CI/CD
- Monitoring: Track usage of different versions
Q17. How do you design APIs for mobile applications?
Answer:
- Efficient Data Transfer: Minimize payload size
- Pagination: Essential for large datasets
- Conditional Requests: ETags, Last-Modified headers
- Offline Support: Sync endpoints for offline-first apps
- Push Notifications: Webhooks or Firebase for real-time updates
- Version Compatibility: Support older app versions longer
- Error Handling: Clear error messages for mobile UX
Q18. What is GraphQL and how does it compare to REST?
Answer: GraphQL is a query language for APIs that allows clients to request exactly the data they need.
Comparison:
- REST: Fixed endpoints return fixed data structures
- GraphQL: Single endpoint, clients specify exact data needs
- Over-fetching: REST can return unnecessary data, GraphQL avoids this
- Under-fetching: REST may require multiple calls, GraphQL can do in one
- Versioning: GraphQL schemas evolve without versioning
- Caching: REST easier to cache, GraphQL more complex
Q19. How do you implement API caching strategies?
Answer:
- HTTP Caching: Cache-Control, ETags, Last-Modified
- Application-Level Caching: In-memory, distributed cache
- Database Query Caching: Cache expensive queries
- Response Caching: Cache entire responses
[HttpGet]
[ResponseCache(Duration = 300, Location = ResponseCacheLocation.Any)]
public async Task<IActionResult> GetProducts()
{
var products = await _cache.GetOrCreateAsync("products", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
return await _productService.GetAllProductsAsync();
});
return Ok(products);
}
Q20. How do you handle database concurrency in Web APIs?
Answer:
[HttpPut("{id}")]
public async Task<IActionResult> UpdateProduct(int id, [FromBody] UpdateProductDto dto)
{
try
{
var product = await _context.Products.FindAsync(id);
if (product == null) return NotFound();
// Check ETag for optimistic concurrency
var etag = Request.Headers["If-Match"].ToString();
if (!string.IsNullOrEmpty(etag) && etag != $"\"{product.RowVersion}\"")
{
return StatusCode(412, "Resource has been modified");
}
// Update entity
_mapper.Map(dto, product);
product.RowVersion = Guid.NewGuid().ToString();
await _context.SaveChangesAsync();
// Return updated entity with new ETag
Response.Headers["ETag"] = $"\"{product.RowVersion}\"";
return Ok(product);
}
catch (DbUpdateConcurrencyException)
{
return Conflict("Resource was modified by another request");
}
}
Additional Interview Tips
API Design Best Practices
- Use Consistent Naming: camelCase for JSON, PascalCase for .NET
- Handle Errors Gracefully: Meaningful error messages and appropriate status codes
- Document Everything: Use OpenAPI/Swagger for comprehensive docs
- Version from Day One: Plan for evolution
- Security First: Authentication, authorization, input validation
- Performance Matters: Caching, pagination, async operations
Common Anti-Patterns to Avoid
- Over-Engineering: Keep it simple, don't add unnecessary complexity
- Tight Coupling: Design for change, use dependency injection
- Ignoring HTTP Semantics: Use correct status codes and methods
- Poor Error Handling: Don't expose internal errors to clients
- No Pagination: Large datasets without pagination cause performance issues
- Inconsistent Responses: Standardize response formats across the API
Testing Strategies
- Unit Tests: Test business logic in isolation
- Integration Tests: Test API endpoints with real dependencies
- Contract Tests: Ensure API contracts are maintained
- Performance Tests: Load testing for scalability
- Security Tests: Penetration testing and vulnerability scanning
Deployment Considerations
- Environment Parity: Similar environments for dev/staging/prod
- Blue-Green Deployments: Zero-downtime deployments
- Feature Toggles: Enable/disable features without redeployment
- Monitoring: Track API usage, errors, and performance
- Rollback Strategy: Quick rollback plans for failed deployments
Comments
Post a Comment