Building Web APIs with ASP.NET Core 8: A Step-by-Step Guide

Tapesh Mehta Tapesh Mehta | Published on: May 23, 2024 | Est. reading time: 19 minutes
Building Web APIs with ASP.NET Core 8 A Step-by-Step Guide

ASP.NET Core 8 is a powerful and flexible framework designed for building modern web applications and services. It provides a unified platform for creating web APIs that are high-performing, scalable, and easy to maintain. This tutorial will guide you through the process of building a robust and scalable web APIs with ASP.NET Core 8. We will cover the following key areas:

  • Setup: Setting up the development environment and creating a new ASP.NET Core Web API project.
  • Development: Defining models, setting up Entity Framework Core for data access, and creating CRUD operations with controllers.
  • Authentication: Setting up JWT authentication and protecting API endpoints with authorization attributes.
  • Versioning: Implementing API versioning to maintain backward compatibility and handle versioned requests in controllers.
  • Performance and Scalability: Implementing caching strategies, pagination, optimizing database queries, and using asynchronous programming.
  • Error Handling and Logging: Implementing global error handling with middleware, structured logging, and returning meaningful error responses.
  • Security: Implementing role-based access control, data encryption, and rate limiting.
  • Testing and Documentation: Writing unit and integration tests, generating API documentation with Swagger/OpenAPI, and maintaining up-to-date documentation.

Table of Contents

Setting Up the Development Environment

Installing .NET SDK 8

To start building web APIs with ASP.NET Core 8, you’ll first need to install the .NET SDK 8. This SDK includes the tools and libraries needed for developing, running, and debugging your .NET applications.

  1. Download and Install: Visit the .NET download page and download the installer for your operating system (Windows, macOS, or Linux).
  2. Verify Installation: Open a terminal or command prompt and run the following command to verify the installation:
dotnet --version

Setting Up a New ASP.NET Core Web API Project

With the .NET SDK installed, you can create a new ASP.NET Core Web API project.

Create a New Project: Open a terminal or command prompt and run the following command to create a new project:

dotnet new webapi -n MyWebApi

This command creates a new directory called MyWebApi with the basic files needed for an ASP.NET Core Web API project.

Navigate to the Project Directory:

cd MyWebApi

Run the Project: To ensure everything is set up correctly, run the project using:

dotnet run

Open a browser and navigate to https://localhost:5001 to see the default API endpoint.

Introduction to the Project Structure and Files

Understanding the project structure is crucial for efficient custom software development. Here’s a brief overview of the key files and folders in a new ASP.NET Core Web API project:

  • Controllers Folder: Contains the controllers that handle HTTP requests. By default, you’ll find WeatherForecastController.cs here.
  • Program.cs: The main entry point for the application. Configures the web host and middleware pipeline.
  • appsettings.json: Configuration file for application settings, such as connection strings and environment-specific settings.
  • Startup.cs (if present): Configures services and the request pipeline. Note: Starting from .NET 6, some configurations have moved to Program.cs.
  • Properties/launchSettings.json: Contains settings for launching the application, including profiles for different environments.

With these steps, you’re now ready to start building your web API with ASP.NET Core 8.

Creating Your First Web API

Defining Models and Creating the Database Context

First, define your data models and create a database context for Entity Framework Core to interact with the database. (Refer our tutorial on Entity Framework core for more details.)

Model Definition:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Database Context:

public class MyDbContext : DbContext
{
    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }
    
    public DbSet<Product> Products { get; set; }
}

Setting Up Entity Framework Core for Data Access

Configure Entity Framework Core in your application.

Install EF Core Packages:

dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer

Configure Services in Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseAuthorization();

app.MapControllers();

app.Run();

Creating CRUD Operations with Controllers

Create a controller to handle CRUD operations for the Product entity.

ProductController:

[Route("api/[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
    private readonly MyDbContext _context;

    public ProductController(MyDbContext context)
    {
        _context = context;
    }

    // Create
    [HttpPost]
    public async Task<ActionResult<Product>> PostProduct(Product product)
    {
        _context.Products.Add(product);
        await _context.SaveChangesAsync();
        return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
    }

    // Read
    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var product = await _context.Products.FindAsync(id);

        if (product == null)
        {
            return NotFound();
        }

        return product;
    }

    // Update
    [HttpPut("{id}")]
    public async Task<IActionResult> PutProduct(int id, Product product)
    {
        if (id != product.Id)
        {
            return BadRequest();
        }

        _context.Entry(product).State = EntityState.Modified;
        await _context.SaveChangesAsync();

        return NoContent();
    }

    // Delete
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteProduct(int id)
    {
        var product = await _context.Products.FindAsync(id);
        if (product == null)
        {
            return NotFound();
        }

        _context.Products.Remove(product);
        await _context.SaveChangesAsync();

        return NoContent();
    }
}

Testing the API Endpoints Using Tools Like Postman or Swagger

Swagger provides an easy way to test your API endpoints directly from your browser.

Using Swagger:

  1. Run your application.
  2. Open a browser and navigate to https://localhost:5001/swagger.

You will see an interactive interface where you can test all your API endpoints.

Using Postman:

  1. Open Postman and create a new request.
  2. Set the request type (GET, POST, PUT, DELETE) and enter the appropriate URL, such as https://localhost:5001/api/product.
  3. Use the body tab to send data for POST and PUT requests.

With these steps, you have set up a simple, yet complete, Web API with CRUD operations using ASP.NET Core 8, tested through Swagger and Postman.

Adding Authentication

Overview of Authentication Mechanisms in ASP.NET Core 8

ASP.NET Core 8 supports various authentication mechanisms, including JWT (JSON Web Token), OAuth, and cookie-based authentication. JWT is commonly used for securing APIs due to its stateless nature and scalability.

Setting Up JWT (JSON Web Token) Authentication

Install the Required Packages:

dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer

Configure JWT Authentication in Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidateAudience = true,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            ValidIssuer = builder.Configuration["Jwt:Issuer"],
            ValidAudience = builder.Configuration["Jwt:Audience"],
            IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"]))
        };
    });

builder.Services.AddAuthorization();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthentication();  // Add this line
app.UseAuthorization();

app.MapControllers();

app.Run();

Add JWT Configuration in appsettings.json:

{
  "Jwt": {
    "Key": "YourSecretKeyHere",
    "Issuer": "YourIssuer",
    "Audience": "YourAudience"
  }
}

Configuring Authentication Middleware in the Startup Class

The authentication middleware is already configured in Program.cs with app.UseAuthentication(); and app.UseAuthorization();.

Protecting API Endpoints with Authorization Attributes

Add authorization attributes to protect your API endpoints.

Example: Protecting Endpoints in ProductController:

[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ProductController : ControllerBase
{
    private readonly MyDbContext _context;

    public ProductController(MyDbContext context)
    {
        _context = context;
    }

    // Create
    [HttpPost]
    public async Task<ActionResult<Product>> PostProduct(Product product)
    {
        _context.Products.Add(product);
        await _context.SaveChangesAsync();
        return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
    }

    // Read
    [AllowAnonymous] // This endpoint can be accessed without authentication
    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var product = await _context.Products.FindAsync(id);
        if (product == null)
        {
            return NotFound();
        }
        return product;
    }

    // Update
    [HttpPut("{id}")]
    public async Task<IActionResult> PutProduct(int id, Product product)
    {
        if (id != product.Id)
        {
            return BadRequest();
        }

        _context.Entry(product).State = EntityState.Modified;
        await _context.SaveChangesAsync();
        return NoContent();
    }

    // Delete
    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteProduct(int id)
    {
        var product = await _context.Products.FindAsync(id);
        if (product == null)
        {
            return NotFound();
        }

        _context.Products.Remove(product);
        await _context.SaveChangesAsync();
        return NoContent();
    }
}

With these configurations and code samples, you have successfully set up JWT authentication, configured middleware, and protected your API endpoints in ASP.NET Core 8.

Implementing API Versioning

Importance of API Versioning in Maintaining Backward Compatibility

API versioning is crucial for maintaining backward compatibility, allowing you to evolve your API without breaking existing clients. It enables you to introduce new features and improvements while ensuring older versions remain functional.

Setting Up API Versioning in ASP.NET Core 8

Install the Required Package:

dotnet add package Microsoft.AspNetCore.Mvc.Versioning

Configure API Versioning in Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddApiVersioning(options =>
{
    options.AssumeDefaultVersionWhenUnspecified = true;
    options.DefaultApiVersion = new ApiVersion(1, 0);
    options.ReportApiVersions = true;
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

Defining Multiple Versions of an API

Create Versioned Controllers:

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class ProductsV1Controller : ControllerBase
{
    private readonly MyDbContext _context;

    public ProductsV1Controller(MyDbContext context)
    {
        _context = context;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
    {
        return await _context.Products.ToListAsync();
    }
}

[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class ProductsV2Controller : ControllerBase
{
    private readonly MyDbContext _context;

    public ProductsV2Controller(MyDbContext context)
    {
        _context = context;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
    {
        var products = await _context.Products.ToListAsync();
        return products.Select(p => new ProductV2
        {
            Id = p.Id,
            Name = p.Name,
            Price = p.Price,
            Description = "Sample description" // New field in V2
        }).ToList();
    }
}

public class ProductV2
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Description { get; set; } // New field in V2
}

Handling Versioned Requests in Controllers

Test the Versioned API Endpoints:

  • For version 1.0: https://localhost:5001/api/v1/products
  • For version 2.0: https://localhost:5001/api/v2/products

By following these steps, you can effectively set up and manage API versioning in ASP.NET Core 8, ensuring your API remains backward compatible while allowing for new enhancements and features.

Enhancing API Performance and Scalability

Implementing Caching Strategies to Improve Performance

Caching helps reduce the load on the database by storing frequently accessed data in memory.

Example: Using In-Memory Caching

Add Memory Cache Service:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddMemoryCache();
builder.Services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
builder.Services.AddControllers();

Implement Caching in Controller:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly MyDbContext _context;
    private readonly IMemoryCache _cache;

    public ProductsController(MyDbContext context, IMemoryCache cache)
    {
        _context = context;
        _cache = cache;
    }

    [HttpGet]
    public async Task<ActionResult<IEnumerable<Product>>> GetProducts()
    {
        var cacheKey = "productList";
        if (!_cache.TryGetValue(cacheKey, out List<Product> products))
        {
            products = await _context.Products.ToListAsync();
            var cacheOptions = new MemoryCacheEntryOptions().SetSlidingExpiration(TimeSpan.FromMinutes(5));
            _cache.Set(cacheKey, products, cacheOptions);
        }

        return products;
    }
}

Using Pagination for Large Data Sets

Pagination reduces the amount of data returned in a single request, improving performance and user experience.

Example: Implementing Pagination in Controller:

[HttpGet]
public async Task<ActionResult<IEnumerable<Product>>> GetProducts(int pageNumber = 1, int pageSize = 10)
{
    var products = await _context.Products
        .Skip((pageNumber - 1) * pageSize)
        .Take(pageSize)
        .ToListAsync();

    return products;
}

Optimizing Database Queries and Data Access

Optimizing queries ensures efficient data retrieval and minimizes database load.

Example: Using Projection for Efficient Data Retrieval:

[HttpGet]
public async Task<ActionResult<IEnumerable<ProductDto>>> GetProductSummaries()
{
    var productSummaries = await _context.Products
        .Select(p => new ProductDto
        {
            Id = p.Id,
            Name = p.Name,
            Price = p.Price
        })
        .ToListAsync();

    return productSummaries;
}

public class ProductDto
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
}

Asynchronous Programming in Web APIs With ASP.NET Core

Asynchronous programming improves scalability by allowing the server to handle more requests simultaneously.

Example: Using Asynchronous Methods:

[HttpGet("{id}")]
public async Task<ActionResult<Product>> GetProduct(int id)
{
    var product = await _context.Products.FindAsync(id);
    if (product == null)
    {
        return NotFound();
    }

    return product;
}

[HttpPost]
public async Task<ActionResult<Product>> PostProduct(Product product)
{
    _context.Products.Add(product);
    await _context.SaveChangesAsync();

    return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
}

By implementing these strategies, you can significantly enhance the performance and scalability of your web APIs with ASP.NET Core, ensuring it can handle high loads efficiently and provide a responsive user experience.

Error Handling and Logging

Implementing Global Error Handling with Middleware

Global error handling ensures that all unhandled exceptions are caught and processed consistently.

Example: Error Handling Middleware:

Create Middleware:

public class ErrorHandlingMiddleware
{
    private readonly RequestDelegate _next;
    private readonly ILogger<ErrorHandlingMiddleware> _logger;

    public ErrorHandlingMiddleware(RequestDelegate next, ILogger<ErrorHandlingMiddleware> logger)
    {
        _next = next;
        _logger = logger;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        try
        {
            await _next(context);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "An error occurred.");
            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 { message = "Internal Server Error", details = exception.Message };
        return context.Response.WriteAsJsonAsync(response);
    }
}

Register Middleware in Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container
builder.Services.AddControllers();
builder.Services.AddSwaggerGen();
builder.Services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseMiddleware<ErrorHandlingMiddleware>();

app.UseAuthorization();
app.MapControllers();

app.Run();

Structured Logging Using Serilog or Another Logging Framework

Structured logging provides detailed and queryable logs, improving the ability to diagnose issues.

Example: Setting Up Serilog:

Install Serilog Packages:

dotnet add package Serilog.AspNetCore
dotnet add package Serilog.Sinks.Console
dotnet add package Serilog.Sinks.File

Configure Serilog in Program.cs:

using Serilog;

var builder = WebApplication.CreateBuilder(args);

// Configure Serilog
Log.Logger = new LoggerConfiguration()
    .WriteTo.Console()
    .WriteTo.File("logs/log.txt", rollingInterval: RollingInterval.Day)
    .CreateLogger();

builder.Host.UseSerilog();

// Add services to the container
builder.Services.AddControllers();
builder.Services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseSerilogRequestLogging(); // Enable Serilog request logging

app.UseAuthorization();
app.MapControllers();

app.Run();

Returning Meaningful Error Responses to Clients

Providing detailed and user-friendly error messages improves the client experience and aids in debugging.

Example: Custom Error Responses in Controller:

[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly MyDbContext _context;

    public ProductsController(MyDbContext context)
    {
        _context = context;
    }

    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        try
        {
            var product = await _context.Products.FindAsync(id);
            if (product == null)
            {
                return NotFound(new { message = "Product not found" });
            }

            return product;
        }
        catch (Exception ex)
        {
            return StatusCode(StatusCodes.Status500InternalServerError, new { message = "An error occurred", details = ex.Message });
        }
    }
}

By implementing global error handling, structured logging, and meaningful error responses, you can significantly enhance the robustness and maintainability of your ASP.NET Core 8 Web API.

Securing the Web API

Securing your Web API is essential to protect against unauthorized access, ensure data integrity, and maintain overall system security. Here are key strategies for enhancing the security of your ASP.NET Core Web API.

Implementing Role-Based Access Control (RBAC)

Role-Based Access Control (RBAC) restricts access to resources based on the roles assigned to users. This ensures that only authorized users can access specific endpoints or perform certain actions.

Example: Setting Up RBAC

1. Configure Roles in Startup:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<MyDbContext>()
    .AddDefaultTokenProviders();

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("RequireAdminRole", policy => policy.RequireRole("Admin"));
    options.AddPolicy("RequireUserRole", policy => policy.RequireRole("User"));
});

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();

app.MapControllers();

app.Run();

2. Protect Endpoints with Roles:

[Authorize(Roles = "Admin")]
[Route("api/[controller]")]
[ApiController]
public class AdminController : ControllerBase
{
    [HttpGet]
    public IActionResult GetAdminData()
    {
        return Ok("This is protected admin data.");
    }
}

[Authorize(Roles = "User")]
[Route("api/[controller]")]
[ApiController]
public class UserController : ControllerBase
{
    [HttpGet]
    public IActionResult GetUserData()
    {
        return Ok("This is protected user data.");
    }
}

Using Data Encryption for Sensitive Information

Encrypting sensitive data ensures that even if data is intercepted or accessed without authorization, it remains unreadable and secure.

Example: Encrypting Data at Rest

1. Add Encryption Attribute to Model Properties:

public class User
{
    public int Id { get; set; }
    public string Username { get; set; }

    [Encrypted]
    public string SocialSecurityNumber { get; set; }
}

2. Implement Encryption Logic:

public class MyDbContext : DbContext
{
    public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { }

    public DbSet<User> Users { get; set; }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
        {
            foreach (var property in entityType.GetProperties())
            {
                if (property.PropertyInfo.GetCustomAttribute<EncryptedAttribute>() != null)
                {
                    property.SetValueConverter(new ValueConverter<string, string>(
                        v => Encrypt(v),
                        v => Decrypt(v)));
                }
            }
        }
    }

    private static string Encrypt(string value)
    {
        // Simplified encryption logic (use a robust encryption method in production)
        return Convert.ToBase64String(Encoding.UTF8.GetBytes(value));
    }

    private static string Decrypt(string value)
    {
        return Encoding.UTF8.GetString(Convert.FromBase64String(value));
    }
}

Example: Encrypting Data in Transit

1. Enforce HTTPS in ASP.NET Core:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection(); // Enforce HTTPS

app.UseAuthorization();

app.MapControllers();

app.Run();

2. Use HTTPS for API Calls:

Ensure that all API calls use HTTPS by specifying the protocol in your API endpoints.

public async Task<HttpResponseMessage> GetUserDataAsync()
{
    using var client = new HttpClient();
    client.BaseAddress = new Uri("https://api.example.com/");
    var response = await client.GetAsync("users");
    return response;
}

Implementing Rate Limiting to Protect Against Abuse

Rate limiting helps to protect your API from abuse by limiting the number of requests a client can make in a specified period.

Example: Implementing Rate Limiting Middleware:

1. Install the Required Package:

dotnet add package AspNetCoreRateLimit

2. Configure Rate Limiting in Startup:

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<MyDbContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

builder.Services.AddMemoryCache();
builder.Services.Configure<IpRateLimitOptions>(options =>
{
    options.GeneralRules = new List<RateLimitRule>
    {
        new RateLimitRule
        {
            Endpoint = "*",
            Limit = 100,
            Period = "1h"
        }
    };
});
builder.Services.AddInMemoryRateLimiting();

builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI();
}

app.UseHttpsRedirection();

app.UseIpRateLimiting();

app.UseAuthorization();
app.MapControllers();

app.Run();

Testing and Documentation

Ensuring the reliability and maintainability of your Web API is crucial, and this can be achieved through comprehensive testing and thorough documentation. Here’s a detailed look at how to implement these practices in your ASP.NET Core Web API.

Writing Unit Tests for Controllers and Services

Unit tests validate individual components of your application in isolation, ensuring they behave as expected.

Example: Setting Up Unit Tests

1. Install Testing Packages:

dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package xunit
dotnet add package Moq
dotnet add package FluentAssertions

2. Create a Test Project:

dotnet new xunit -n MyWebApi.Tests

3. Write Unit Tests for a Controller:

public class ProductsControllerTests
{
    private readonly ProductsController _controller;
    private readonly MyDbContext _context;

    public ProductsControllerTests()
    {
        var options = new DbContextOptionsBuilder<MyDbContext>()
            .UseInMemoryDatabase(databaseName: "TestDatabase")
            .Options;
        _context = new MyDbContext(options);

        _controller = new ProductsController(_context);
    }

    [Fact]
    public async Task GetProduct_ReturnsProduct_WhenProductExists()
    {
        // Arrange
        var product = new Product { Id = 1, Name = "Test Product", Price = 10.0m };
        _context.Products.Add(product);
        _context.SaveChanges();

        // Act
        var result = await _controller.GetProduct(1);

        // Assert
        result.Value.Should().BeEquivalentTo(product);
    }

    [Fact]
    public async Task GetProduct_ReturnsNotFound_WhenProductDoesNotExist()
    {
        // Act
        var result = await _controller.GetProduct(99);

        // Assert
        result.Result.Should().BeOfType<NotFoundResult>();
    }
}

Generating API Documentation with Swagger/OpenAPI

Swagger/OpenAPI automatically generates interactive API documentation, making it easier to understand and use your API.

Example: Setting Up Swagger

1. Install Swagger Packages:

dotnet add package Swashbuckle.AspNetCore

2. Configure Swagger in Program.cs:

var builder = WebApplication.CreateBuilder(args);

// Add services to the container
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "My API V1");
    });
}

app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();

3. Annotate Controllers for Better Documentation:

/// <summary>
/// Handles operations related to products.
/// </summary>
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
    private readonly MyDbContext _context;

    public ProductsController(MyDbContext context)
    {
        _context = context;
    }

    /// <summary>
    /// Gets a product by ID.
    /// </summary>
    /// <param name="id">The ID of the product.</param>
    /// <returns>The product with the specified ID.</returns>
    [HttpGet("{id}")]
    public async Task<ActionResult<Product>> GetProduct(int id)
    {
        var product = await _context.Products.FindAsync(id);
        if (product == null)
        {
            return NotFound();
        }
        return product;
    }

    // Other actions...
}

4. View Documentation:

Run your application and navigate to https://localhost:5001/swagger to view and interact with the generated API documentation.

Conclusion

Today, building robust and scalable Web APIs with ASP.NET Core is a crucial first step. These APIs make up the basis of modern applications and allow interoperability among services and systems. Configuration, optimize, secure, and monitor your ASP.NET Core Web API with best practices can help you reach high performance in all the locations & scalability to meet changing requirements.

Building a great API requires attention to detail and understanding development and deployment best practices. As your application grows, be sure to continue exploring advanced features like API versioning, caching strategies and comprehensive testing. Such tweaks may significantly enhance the functionality, security, and maintainability of your API.

In case you want to extend your API or need extra skills, consider to hire backend developers with expertise in ASP.NET Core and web API development. Extensive backend developers can help you build robust functionality, speed up processes, and make your API secure and scalable. Investing in talented developers can make an impact in the quality and functionality of your application.

Share

clutch profile designrush wirefuture profile goodfirms wirefuture profile
Bring Your Ideas to Life with Expert Developers! 🚀

At WireFuture, we believe every idea has the potential to disrupt markets. Join us, and let's create software that speaks volumes, engages users, and drives growth.

Hire Now

Categories
.NET Development Angular Development JavaScript Development KnockoutJS Development NodeJS Development PHP Development Python Development React Development Software Development SQL Server Development VueJS Development All
About Author
wirefuture - founder

Tapesh Mehta

verified Verified
Expert in Software Development

Tapesh Mehta is a seasoned tech worker who has been making apps for the web, mobile devices, and desktop for over 13+ years. Tapesh knows a lot of different computer languages and frameworks. For robust web solutions, he is an expert in Asp.Net, PHP, and Python. He is also very good at making hybrid mobile apps, which use Ionic, Xamarin, and Flutter to make cross-platform user experiences that work well together. In addition, Tapesh has a lot of experience making complex desktop apps with WPF, which shows how flexible and creative he is when it comes to making software. His work is marked by a constant desire to learn and change.

Get in Touch
Your Ideas, Our Strategy – Let's Connect.

No commitment required. Whether you’re a charity, business, start-up or you just have an idea – we’re happy to talk through your project.

Embrace a worry-free experience as we proactively update, secure, and optimize your software, enabling you to focus on what matters most – driving innovation and achieving your business goals.

Hire Your A-Team Here to Unlock Potential & Drive Results
You can send an email to contact@wirefuture.com
clutch wirefuture profile designrush wirefuture profile goodfirms wirefuture profile good firms award-4 award-5 award-6