Introduction
In modern ASP.NET Core applications, Dependency Injection (DI) and the Repository Pattern are often used together to create clean, maintainable, and testable code.
Using both together ensures your Web API is scalable, loosely coupled, and easier to maintain.
This article explains step by step how to implement DI with the Repository Pattern in an ASP.NET Core Web API project.
Step 1: Create a New ASP.NET Core Web API Project
Open a terminal and run:
dotnet new webapi -n DIRepoApi
cd DIRepoApi
dotnet run
This creates a new Web API project that runs at https://localhost:5001.
Step 2: Create the Data Model
Create a Models/User.cs file:
namespace DIRepoApi.Models
{
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
}
This model represents a User entity in the application.
Step 3: Create the Repository Interface
Create a Repositories/IUserRepository.cs file:
using DIRepoApi.Models;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace DIRepoApi.Repositories
{
public interface IUserRepository
{
Task<IEnumerable<User>> GetAllUsersAsync();
Task<User> GetUserByIdAsync(int id);
Task AddUserAsync(User user);
Task UpdateUserAsync(User user);
Task DeleteUserAsync(int id);
}
}
This defines all methods to interact with users.
Notice that there is no database code here, just method signatures.
Step 4: Implement the Repository
Create Repositories/UserRepository.cs:
using DIRepoApi.Models;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace DIRepoApi.Repositories
{
public class UserRepository : IUserRepository
{
private readonly List<User> _users = new List<User>();
public Task<IEnumerable<User>> GetAllUsersAsync() => Task.FromResult(_users.AsEnumerable());
public Task<User> GetUserByIdAsync(int id) => Task.FromResult(_users.FirstOrDefault(u => u.Id == id));
public Task AddUserAsync(User user)
{
user.Id = _users.Count + 1;
_users.Add(user);
return Task.CompletedTask;
}
public Task UpdateUserAsync(User user)
{
var existing = _users.FirstOrDefault(u => u.Id == user.Id);
if (existing != null)
{
existing.Name = user.Name;
existing.Email = user.Email;
}
return Task.CompletedTask;
}
public Task DeleteUserAsync(int id)
{
var user = _users.FirstOrDefault(u => u.Id == id);
if (user != null) _users.Remove(user);
return Task.CompletedTask;
}
}
}
For simplicity, this example uses an in-memory list instead of a database.
You can replace it with EF Core and SQL Server later.
Step 5: Register Repository in Dependency Injection
In Program.cs, add the repository to the DI container:
using DIRepoApi.Repositories;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// Register repository using Scoped lifetime
builder.Services.AddScoped<IUserRepository, UserRepository>();
var app = builder.Build();
app.MapControllers();
app.Run();
Step 6: Create the Controller
Create Controllers/UsersController.cs:
using DIRepoApi.Models;
using DIRepoApi.Repositories;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace DIRepoApi.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
private readonly IUserRepository _userRepository;
// Dependency injected via constructor
public UsersController(IUserRepository userRepository)
{
_userRepository = userRepository;
}
[HttpGet]
public async Task<IEnumerable<User>> GetUsers()
{
return await _userRepository.GetAllUsersAsync();
}
[HttpGet("{id}")]
public async Task<ActionResult<User>> GetUser(int id)
{
var user = await _userRepository.GetUserByIdAsync(id);
if (user == null) return NotFound();
return user;
}
[HttpPost]
public async Task<ActionResult> AddUser(User user)
{
await _userRepository.AddUserAsync(user);
return CreatedAtAction(nameof(GetUser), new { id = user.Id }, user);
}
[HttpPut("{id}")]
public async Task<ActionResult> UpdateUser(int id, User user)
{
if (id != user.Id) return BadRequest();
await _userRepository.UpdateUserAsync(user);
return NoContent();
}
[HttpDelete("{id}")]
public async Task<ActionResult> DeleteUser(int id)
{
await _userRepository.DeleteUserAsync(id);
return NoContent();
}
}
}
Key Points
The controller never creates the repository manually.
DI provides the IUserRepository instance automatically.
Clean separation between controller logic and data access logic.
Step 7: Testing the API
Run the project and use Postman or Swagger:
GET /api/users – Retrieve all users.
POST /api/users – Add a new user. Example JSON:
{
"name": "Rahul",
"email": "[email protected]"
}
PUT /api/users/1 – Update user.
DELETE /api/users/1 – Delete user.
Everything works without the controller knowing the implementation details of the repository.
Advantages of Using DI with Repository Pattern
Loose Coupling: Controllers don’t depend on concrete repository classes.
Easier Unit Testing: Mock IUserRepository for tests.
Scalability: Easily replace UserRepository with EF Core or any other database provider.
Maintainability: Data access logic is separated and easier to update.
Example: Unit Test Using DI
using DIRepoApi.Controllers;
using DIRepoApi.Models;
using DIRepoApi.Repositories;
using Moq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Xunit;
public class UsersControllerTests
{
[Fact]
public async Task GetUsers_ReturnsAllUsers()
{
var mockRepo = new Mock<IUserRepository>();
mockRepo.Setup(repo => repo.GetAllUsersAsync())
.ReturnsAsync(new List<User> { new User { Id = 1, Name = "Test" } });
var controller = new UsersController(mockRepo.Object);
var result = await controller.GetUsers();
Assert.Single(result);
}
}
This test works without any real database, thanks to dependency injection.
Conclusion
Using Dependency Injection with Repository Pattern in ASP.NET Core Web API provides a clean, modular, and testable architecture.
DI allows the controller to receive dependencies automatically.
Repository Pattern encapsulates data access logic.
Together, they make applications scalable, maintainable, and unit-testable.
This is a professional approach for building production-ready ASP.NET Core Web APIs.
Once you master this, you can combine it with Entity Framework Core, Angular, or React to create a full-stack application.