Building RESTful APIs with ASP.NET Core requires careful planning, adherence to best practices, and the right use of frameworks and tools. Here are some best practices to follow.
1. Project Structure & Organization
A well-structured project ensures maintainability, scalability, and separation of concerns. In ASP.NET Core, it's best to follow Clean Architecture or Onion Architecture to keep things modular and decoupled.
- Follow Clean Architecture or Onion Architecture to maintain the separation of concerns.
- Use Controllers for API endpoints and Services for business logic.
- Implement DTOs (Data Transfer Objects) to avoid exposing internal models.
📂 MyApiProject
│── 📂 API
│ ├── Controllers
│ ├── Filters
│ ├── Middleware
│── 📂 Application
│ ├── Interfaces
│ ├── Services
│ ├── Validators
│── 📂 Domain
│ ├── Entities
│ ├── Enums
│── 📂 Infrastructure
│ ├── Data (EF Core DbContext, Migrations)
│ ├── Repositories
│ ├── Identity (Auth, JWT)
│── 📂 Shared
│ ├── DTOs
│ ├── Helpers
│ ├── Extensions
│── 📂 Tests
│ ├── UnitTests
│ ├── IntegrationTests
│── appsettings.json
│── Program.cs
│── Startup.cs
│── README.md
- API Layer (Presentation)
- Controllers: Handles HTTP requests and responses.
- Filters: Implements action filters, exception filters, etc.
- Middleware: Custom middleware for logging, exception handling, etc.
- Application Layer (Business Logic)
- Interfaces: Defines contracts for services and repositories.
- Services: Contains business logic and service implementations.
- Validators: Implements FluentValidation for model validation.
- Domain Layer (Core Business Models)
- Entities: Represents domain models (e.g., Product, Order).
- Enums: Defines constant values like OrderStatus.
- Infrastructure Layer (Data Access & External Services)
- Repositories: Implements repository pattern for database operations.
- Identity: Manages authentication and authorization (JWT, Identity).
- Data: Contains EF Core DbContext and migration scripts.
- Shared Layer (Common Utilities)
- DTOs (Data Transfer Objects): Models for API requests/responses.
- Helpers: Common utility functions (e.g., JWT token generation).
- Extensions: Extension methods for better code reusability.
- Tests (Unit & Integration Tests)
- UnitTests: Uses xUnit/NUnit with Moq to test services.
- IntegrationTests: Uses TestServer to test API endpoints.
2. Use RESTful Principles
Use proper HTTP methods
- GET for retrieval
- POST for creation
- PUT for a full update
- PATCH for partial update
- DELETE for removal
Design meaningful URIs
GET /api/products // Get all products
GET /api/products/{id} // Get a specific product
POST /api/products // Create a new product
PUT /api/products/{id} // Update an existing product
DELETE /api/products/{id} // Delete a product
Use plural nouns in routes (/api/products instead of /api/product).
3. Implementing API Versioning
Use API versioning to avoid breaking changes.
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
});
Versioning strategies
- URL-based (/api/v1/products)
- Header-based (Accept: application/vnd.myapi.v1+json)
- Query parameter-based (/api/products?version=1.0)
4. Exception Handling & Logging
Implement global exception handling using Middleware.
public class ExceptionMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<ExceptionMiddleware> _logger;
public ExceptionMiddleware(RequestDelegate next, ILogger<ExceptionMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
try
{
await _next(context);
}
catch (Exception ex)
{
_logger.LogError($"Something went wrong: {ex}");
await HandleExceptionAsync(context, ex);
}
}
private static Task HandleExceptionAsync(HttpContext context, Exception exception)
{
context.Response.ContentType = "application/json";
context.Response.StatusCode = (int)HttpStatusCode.InternalServerError;
var response = new
{
StatusCode = context.Response.StatusCode,
Message = "Internal Server Error. Please try again later.",
Details = exception.Message // Remove in production to avoid exposing details
};
return context.Response.WriteAsync(JsonConvert.SerializeObject(response));
}
}
Register exception in middleware.
app.UseMiddleware<ExceptionMiddleware>();
5. Authentication & Authorization
Use JWT (JSON Web Token) authentication for securing APIs.
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = "https://your-auth-server.com";
options.Audience = "your-api";
});
Implement role-based authorization using [Authorize(Roles = "Admin")].
6. Data Validation
Use FluentValidation for model validation instead of [Required].
public class ProductValidator : AbstractValidator<ProductDto>
{
public ProductValidator()
{
RuleFor(p => p.Name).NotEmpty().WithMessage("Name is required.");
RuleFor(p => p.Price).GreaterThan(0).WithMessage("Price must be greater than zero.");
}
}
7. Caching & Performance Optimization
Use Response Caching for static data.
[ResponseCache(Duration = 60)]
public IActionResult GetProducts() { ... }
- Implement In-Memory Caching or Redis for frequently accessed data.
- Use EF Core Lazy Loading or Projection (DTOs) to optimize database queries.
8. Rate Limiting & Throttling
Protect APIs using rate limiting with AspNetCoreRateLimit.
services.Configure<IpRateLimitOptions>(options =>
{
options.GeneralRules = new List<RateLimitRule>
{
new RateLimitRule
{
Endpoint = "*",
Limit = 100,
Period = "1m"
}
};
});
9. API Documentation with Swagger
Use Swashbuckle to generate OpenAPI docs.
services.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo { Title = "My API", Version = "v1" });
});
Include authentication support in Swagger UI.
10. Unit Testing & Integration Testing
- Use xUnit + Moq for unit testing services.
- Use TestServer for integration testing APIs.
Conclusion
Following these best practices ensures that your RESTful APIs in ASP.NET Core are scalable, secure, and maintainable. Let me know if you need deeper insights into any of these topics!