🔌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