🔌Web APIs
Bygg moderna, säkra och skalbara Web APIs med ASP.NET Core. Från RESTful design till avancerad optimering.
Kodexempel
🔌
RESTful API Design
Design och implementation av RESTful APIs med ASP.NET Core.
// Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// Add services
builder.Services.AddScoped<IProductService, ProductService>();
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
// Configure CORS
builder.Services.AddCors(options =>
{
options.AddPolicy("AllowSpecificOrigin",
builder => builder
.WithOrigins("https://example.com")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
var app = builder.Build();
// Configure pipeline
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors("AllowSpecificOrigin");
app.UseAuthorization();
app.MapControllers();
app.Run();
// ProductsController.cs
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly IProductService _productService;
public ProductsController(IProductService productService)
{
_productService = productService;
}
[HttpGet]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts(
[FromQuery] int page = 1,
[FromQuery] int pageSize = 10)
{
var products = await _productService.GetProductsAsync(page, pageSize);
return Ok(products);
}
[HttpGet("{id}")]
public async Task<ActionResult<Product>> GetProduct(int id)
{
var product = await _productService.GetProductAsync(id);
if (product == null)
return NotFound();
return Ok(product);
}
[HttpPost]
public async Task<ActionResult<Product>> CreateProduct(
[FromBody] CreateProductDto dto)
{
if (!ModelState.IsValid)
return BadRequest(ModelState);
var product = await _productService.CreateProductAsync(dto);
return CreatedAtAction(
nameof(GetProduct),
new { id = product.Id },
product);
}
[HttpPut("{id}")]
public async Task<IActionResult> UpdateProduct(
int id,
[FromBody] UpdateProductDto dto)
{
if (id != dto.Id)
return BadRequest();
var result = await _productService.UpdateProductAsync(dto);
if (!result)
return NotFound();
return NoContent();
}
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteProduct(int id)
{
var result = await _productService.DeleteProductAsync(id);
if (!result)
return NotFound();
return NoContent();
}
}
Komplett RESTful API med CRUD-operationer, Swagger och CORS
🔢
API Versioning
Implementera API-versionering för bakåtkompatibilitet.
// Add API versioning in 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("ver")
);
});
builder.Services.AddVersionedApiExplorer(options =>
{
options.GroupNameFormat = "'v'VVV";
options.SubstituteApiVersionInUrl = true;
});
// Versioned 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<ActionResult<IEnumerable<ProductV1>>> GetProductsV1()
{
// Version 1 implementation
return Ok(await _service.GetProductsV1Async());
}
[HttpGet]
[MapToApiVersion("2.0")]
public async Task<ActionResult<IEnumerable<ProductV2>>> GetProductsV2()
{
// Version 2 with additional fields
return Ok(await _service.GetProductsV2Async());
}
}
// Configure Swagger for multiple versions
builder.Services.AddSwaggerGen(options =>
{
options.SwaggerDoc("v1", new OpenApiInfo
{
Title = "My API",
Version = "v1"
});
options.SwaggerDoc("v2", new OpenApiInfo
{
Title = "My API",
Version = "v2"
});
});
API-versionering med URL, header och media type support
⚡
Response Caching & Compression
Optimera API-prestanda med caching och komprimering.
// Configure response caching and compression
builder.Services.AddResponseCaching();
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<BrotliCompressionProvider>();
options.Providers.Add<GzipCompressionProvider>();
options.MimeTypes = ResponseCompressionDefaults.MimeTypes
.Concat(new[] { "application/json" });
});
// Configure caching policies
builder.Services.AddControllers(options =>
{
options.CacheProfiles.Add("Default30",
new CacheProfile()
{
Duration = 30,
Location = ResponseCacheLocation.Any,
NoStore = false
});
options.CacheProfiles.Add("NoCache",
new CacheProfile()
{
NoStore = true,
Location = ResponseCacheLocation.None
});
});
// Use in middleware pipeline
app.UseResponseCompression();
app.UseResponseCaching();
// Controller with caching
[ApiController]
[Route("api/[controller]")]
public class CatalogController : ControllerBase
{
private readonly IMemoryCache _cache;
private readonly ICatalogService _service;
public CatalogController(IMemoryCache cache, ICatalogService service)
{
_cache = cache;
_service = service;
}
[HttpGet("products")]
[ResponseCache(CacheProfileName = "Default30")]
public async Task<IActionResult> GetProducts()
{
// Response caching header
Response.Headers.Add("X-Cache", "HIT");
var products = await _service.GetProductsAsync();
return Ok(products);
}
[HttpGet("categories")]
public async Task<IActionResult> GetCategories()
{
const string cacheKey = "categories_all";
// Try get from memory cache
if (_cache.TryGetValue(cacheKey, out List<Category> categories))
{
Response.Headers.Add("X-Cache", "HIT");
return Ok(categories);
}
// Get from database and cache
categories = await _service.GetCategoriesAsync();
var cacheOptions = new MemoryCacheEntryOptions()
.SetSlidingExpiration(TimeSpan.FromMinutes(5))
.SetAbsoluteExpiration(TimeSpan.FromHours(1));
_cache.Set(cacheKey, categories, cacheOptions);
Response.Headers.Add("X-Cache", "MISS");
return Ok(categories);
}
}
Response caching och komprimering för bättre prestanda
🛡️
API Error Handling
Robust felhantering med konsekvent error response format.
// Global error handling middleware
public class GlobalExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<GlobalExceptionMiddleware> _logger;
public GlobalExceptionMiddleware(
RequestDelegate next,
ILogger<GlobalExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError(ex, "An unhandled exception occurred");
await HandleExceptionAsync(context, ex);
}
}
private static async Task HandleExceptionAsync(
HttpContext context,
Exception exception)
{
context.Response.ContentType = "application/json";
var response = new ApiErrorResponse();
switch (exception)
{
case NotFoundException:
response.StatusCode = StatusCodes.Status404NotFound;
response.Message = exception.Message;
break;
case ValidationException validationEx:
response.StatusCode = StatusCodes.Status400BadRequest;
response.Message = "Validation failed";
response.Errors = validationEx.Errors;
break;
case UnauthorizedException:
response.StatusCode = StatusCodes.Status401Unauthorized;
response.Message = "Unauthorized access";
break;
default:
response.StatusCode = StatusCodes.Status500InternalServerError;
response.Message = "An error occurred while processing your request";
break;
}
context.Response.StatusCode = response.StatusCode;
var jsonResponse = JsonSerializer.Serialize(response, new JsonSerializerOptions
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
});
await context.Response.WriteAsync(jsonResponse);
}
}
// Error response model
public class ApiErrorResponse
{
public int StatusCode { get; set; }
public string Message { get; set; }
public string Details { get; set; }
public Dictionary<string, string[]> Errors { get; set; }
public string TraceId { get; set; }
public DateTime Timestamp { get; set; } = DateTime.UtcNow;
}
// Custom exceptions
public class NotFoundException : Exception
{
public NotFoundException(string message) : base(message) { }
}
public class ValidationException : Exception
{
public Dictionary<string, string[]> Errors { get; }
public ValidationException(Dictionary<string, string[]> errors)
{
Errors = errors;
}
}
// Register middleware
app.UseMiddleware<GlobalExceptionMiddleware>();
// Problem Details for standard error responses
builder.Services.AddProblemDetails(options =>
{
options.CustomizeProblemDetails = context =>
{
context.ProblemDetails.Extensions["traceId"] =
Activity.Current?.Id ?? context.HttpContext.TraceIdentifier;
context.ProblemDetails.Extensions["timestamp"] =
DateTime.UtcNow;
};
});
Centraliserad felhantering med konsekvent error response format
Best Practices
📐
API Design
- •Använd RESTful principer konsekvent
- •Versionera API:er från början
- •Dokumentera med OpenAPI/Swagger
- •Använd meningsfulla HTTP-statuskoder
🔒
Säkerhet
- •Implementera autentisering och auktorisering
- •Använd HTTPS för all kommunikation
- •Validera och sanera all input
- •Implementera rate limiting
🚀
Prestanda
- •Implementera caching strategiskt
- •Använd paginering för stora dataset
- •Komprimera responses
- •Optimera databas-queries
🔧
Underhåll
- •Logga alla requests och errors
- •Använd health checks
- •Implementera monitoring
- •Håll dependencies uppdaterade
📊 HTTP Status Codes
2xx Success
- 200 OK - Lyckad request
- 201 Created - Resurs skapad
- 204 No Content - Lyckad, inget innehåll
4xx Client Error
- 400 Bad Request - Ogiltig request
- 401 Unauthorized - Autentisering krävs
- 403 Forbidden - Åtkomst nekad
- 404 Not Found - Resurs ej hittad
5xx Server Error
- 500 Internal Server Error - Serverfel
- 502 Bad Gateway - Gateway-fel
- 503 Service Unavailable - Tjänst ej tillgänglig
🧪 API Testing & Documentation
Testing Tools
- • Postman - API testing och dokumentation
- • Insomnia - REST och GraphQL klient
- • cURL - Command line HTTP klient
- • REST Client - VS Code extension
Documentation
- • Swagger/OpenAPI - API specifikation
- • ReDoc - API dokumentation
- • API Blueprint - Design-first approach
- • Postman Collections - Delbar dokumentation