Backend-utveckling
Webb-API:er
Bygga RESTful API:er med ASP.NET Core: design, versionering, prestanda och felhantering.
01 · ASP.NET Core
Kodexempel
RESTful API-design
Design och implementation av RESTful API:er med ASP.NET Core.
C#
// 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-versionering
Implementera API-versionering för bakåtkompatibilitet.
C#
// 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 & komprimering
Optimera API-prestanda med caching och komprimering.
C#
// 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
Felhantering
Robust felhantering med konsekvent error response-format.
C#
// 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
02 · Riktlinjer
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
03 · Referens
HTTP-statuskoder
| Kod | Betydelse |
|---|---|
| 200 OK | Lyckad request |
| 201 Created | Resurs skapad |
| 204 No Content | Lyckad, inget innehåll |
| 400 Bad Request | Ogiltig request |
| 401 Unauthorized | Autentisering krävs |
| 403 Forbidden | Åtkomst nekad |
| 404 Not Found | Resurs ej hittad |
| 500 Internal Server Error | Serverfel |
| 502 Bad Gateway | Gateway-fel |
| 503 Service Unavailable | Tjänst ej tillgänglig |
04 · Verktyg
Testning & dokumentation
Testverktyg
- Postman
- API-testning och dokumentation
- Insomnia
- REST- och GraphQL-klient
- cURL
- HTTP-klient i kommandoraden
- REST Client
- VS Code-tillägg
Dokumentation
- Swagger/OpenAPI
- API-specifikation
- ReDoc
- API-dokumentation
- API Blueprint
- Design-first approach
- Postman Collections
- Delbar dokumentation