Building RESTful APIs with ASP.NET Core: Best Practices

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
  1. API Layer (Presentation)
    • Controllers: Handles HTTP requests and responses.
    • Filters: Implements action filters, exception filters, etc.
    • Middleware: Custom middleware for logging, exception handling, etc.
  2. Application Layer (Business Logic)
    • Interfaces: Defines contracts for services and repositories.
    • Services: Contains business logic and service implementations.
    • Validators: Implements FluentValidation for model validation.
  3. Domain Layer (Core Business Models)
    • Entities: Represents domain models (e.g., Product, Order).
    • Enums: Defines constant values like OrderStatus.
  4. 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.
  5. 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.
  6. 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!

Up Next
    Ebook Download
    View all
    Learn
    View all