Advanced APIs with ASP.NET Core: Middleware, EF Core, and Versioning

Introduction

When it comes to building scalable web services, ASP.NET Core Web API is the framework I prefer.

I prefer ASP.NET Core Web API when building scalable web services. I'm excited to share it with you. In this guide, I’ll show you how I built it using Entity Framework Core, Dependency Injection, API versioning, and some extra code to keep track of requests and responses.

This won’t be just a beginner's basic task—we're exploring advanced ideas for creating APIs that grow as your app gets bigger. Let’s jump in.

Step 1. Setting Up the Project.

First, I fired up Visual Studio and created a new ASP.NET Core Web API project. Here’s how I did it.

  1. Open Visual Studio and click on Create a new project.
  2. I chose ASP.NET Core Web API and clicked Next.
  3. Named the project EmployeeManagementAPI—you can name it whatever you like—and clicked Create.
  4. I selected .NET 7.0 and hit Create.

Once Visual Studio had set up the basic project structure, I was ready to roll. Next, it was time to integrate Entity Framework Core so I could store and manage employee data in a database.

Step 2. Integrating Entity Framework Core.

I needed to hook up a database to store employee records. For this, I went with Entity Framework Core because it’s super flexible and easy to work with.

Installing EF Core

First things first, I installed the required packages via Package Manager Console.

Install-Package Microsoft.EntityFrameworkCore.SqlServer  
Install-Package Microsoft.EntityFrameworkCore.Tools

With that out of the way, I moved on to creating a DbContext to represent the database. I created a folder called Data and added a new class called EmployeeContext. Here’s the code I put in.

using Microsoft.EntityFrameworkCore;
using EmployeeManagementAPI.Models;

namespace EmployeeManagementAPI.Data
{
    public class EmployeeContext : DbContext
    {
        public EmployeeContext(DbContextOptions<EmployeeContext> options) 
            : base(options)
        {
        }

        public DbSet<Employee> Employees { get; set; }
    }
}

Next, I needed an Employee model. In the Models folder, I added the Employee.cs class.

namespace EmployeeManagementAPI.Models
{
    public class Employee
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string Department { get; set; }
        public decimal Salary { get; set; }
    }
}

Configuring the Database Connection

With the DbContext and model in place, I needed to configure the connection string. I added the connection string in appsettings.json like this.

"ConnectionStrings": {
  "EmployeeConnection": "Server=(localdb)\\mssqllocaldb;Database=EmployeeManagementDB;Trusted_Connection=True;"
}

Then, in Program.cs, I added the following line to register EmployeeContext with Dependency Injection.

builder.Services.AddDbContext<EmployeeContext>(options =>  
    options.UseSqlServer(builder.Configuration.GetConnectionString("EmployeeConnection")));

Running Migrations

Finally, I created the database using EF Core migrations. Here’s what I did.

  • Add-Migration InitialCreate
  • Update-Database

This created the Employees table in the database. With the database ready, it was time to move on to the service layer.

Step 3. Building the Service Layer.

Rather than dumping all the logic into the controller, I created a service layer to handle employee operations. This helps keep the code cleaner and easier to maintain.

Creating the Service Interface and Implementation

In the Services folder, I added an interface, IEmployeeService, and its implementation, EmployeeService. Here's what I came up with,

First, the interface.

public interface IEmployeeService
{
    Task<IEnumerable<Employee>> GetAllEmployeesAsync();
    Task<Employee> GetEmployeeByIdAsync(int id);
    Task AddEmployeeAsync(Employee employee);
    Task UpdateEmployeeAsync(Employee employee);
    Task DeleteEmployeeAsync(int id);
}

Then, I implemented the interface in EmployeeService.cs.

public class EmployeeService : IEmployeeService
{
    private readonly EmployeeContext _context;

    public EmployeeService(EmployeeContext context)
    {
        _context = context;
    }

    public async Task<IEnumerable<Employee>> GetAllEmployeesAsync()
    {
        return await _context.Employees.ToListAsync();
    }

    public async Task<Employee> GetEmployeeByIdAsync(int id)
    {
        return await _context.Employees.FindAsync(id);
    }

    public async Task AddEmployeeAsync(Employee employee)
    {
        _context.Employees.Add(employee);
        await _context.SaveChangesAsync();
    }

    public async Task UpdateEmployeeAsync(Employee employee)
    {
        _context.Entry(employee).State = EntityState.Modified;
        await _context.SaveChangesAsync();
    }

    public async Task DeleteEmployeeAsync(int id)
    {
        var employee = await _context.Employees.FindAsync(id);
        if (employee != null)
        {
            _context.Employees.Remove(employee);
            await _context.SaveChangesAsync();
        }
    }
}

Now, I needed to register this service in Program.cs so it could be injected into the controllers.

builder.Services.AddScoped<IEmployeeService, EmployeeService>();

Step 4. Building the Employee Controller.

With the service layer ready, I moved on to the controller. In the Controllers folder, I created EmployeesController.cs.

[Route("api/[controller]")]
[ApiController]
public class EmployeesController : ControllerBase
{
    private readonly IEmployeeService _employeeService;

    public EmployeesController(IEmployeeService employeeService)
    {
        _employeeService = employeeService;
    }

    [HttpGet]
    public async Task<IActionResult> GetAllEmployees()
    {
        var employees = await _employeeService.GetAllEmployeesAsync();
        return Ok(employees);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetEmployeeById(int id)
    {
        var employee = await _employeeService.GetEmployeeByIdAsync(id);
        if (employee == null)
        {
            return NotFound();
        }
        return Ok(employee);
    }

    [HttpPost]
    public async Task<IActionResult> AddEmployee([FromBody] Employee employee)
    {
        await _employeeService.AddEmployeeAsync(employee);
        return CreatedAtAction(nameof(GetEmployeeById), new { id = employee.Id }, employee);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateEmployee(int id, [FromBody] Employee employee)
    {
        if (id != employee.Id)
        {
            return BadRequest();
        }
        await _employeeService.UpdateEmployeeAsync(employee);
        return NoContent();
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteEmployee(int id)
    {
        await _employeeService.DeleteEmployeeAsync(id);
        return NoContent();
    }
}

This controller was straightforward and tied everything together. I now had a fully functional API for managing employees.

Step 5. Adding API Versioning.

As the API grew, I knew I’d need to implement API versioning to ensure backward compatibility. I installed the versioning package.

Install-Package Microsoft.AspNetCore.Mvc.Versioning

Next, I configured versioning in Program.cs.

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

Now, I could version my controllers like this.

[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class EmployeesV1Controller : ControllerBase
{
    // Version 1.0 controller code
}

Step 6. Custom Middleware for Logging.

One thing I always like to do is log requests and responses, especially when working with APIs. So, I wrote some custom middleware to log incoming requests and outgoing responses.

Here’s what my middleware looked like.

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

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

    public async Task InvokeAsync(HttpContext context)
    {
        _logger.LogInformation($"Incoming request: {context.Request.Method} {context.Request.Path}");
        await _next(context);
        _logger.LogInformation($"Outgoing response: {context.Response.StatusCode}");
    }
}

Then, I registered this middleware in Program.cs.

app.UseMiddleware<RequestLoggingMiddleware>();

Now, every request and response was being logged, which made debugging much easier.

Conclusion

And there you have it—an advanced Employee Management API built with ASP.NET Core Web API. We covered a lot of ground, from integrating Entity Framework Core to creating a solid service layer, and even added some extra touches like API versioning and custom middleware.

This is the kind of architecture that scales well and keeps things organized. If you’ve made it this far, your API is in great shape for future growth.

Next Steps

  • Consider adding authentication and authorization to secure the API (I recommend using JWT).
  • Look into caching to improve performance, especially for frequently accessed data.
  • Write unit tests for your services and controllers to ensure your API behaves as expected.

Happy coding!

Up Next
    Ebook Download
    View all
    Learn
    View all