Core API Dependency Injection
Dependency Injection (DI) is one of the most important concepts in ASP.NET Core. It is built into the framework from the start. DI makes code loosely coupled, testable, and easy to maintain by letting the framework create and supply the objects (dependencies) that a class needs — instead of the class creating them itself.
The Problem Without Dependency Injection
Imagine BooksController needs to use a service to get books from the database. Without DI, the controller creates the service itself:
// BAD: Controller creates its own dependency
public class BooksController : ControllerBase
{
private readonly BookService _service;
public BooksController()
{
_service = new BookService(); // ← tightly coupled
}
}
Problems with this approach:
- If
BookServicechanges its constructor,BooksControllermust also change. - Unit testing is difficult because a real
BookService(with database) is always created. - The same instance is never reused — each controller creates a new one.
The Solution: Dependency Injection
With DI, the framework creates the service and passes it in. The controller asks for it through the constructor — it does not create it.
// GOOD: Controller receives its dependency through the constructor
public class BooksController : ControllerBase
{
private readonly IBookService _service;
public BooksController(IBookService service) // ← injected by framework
{
_service = service;
}
}
The controller does not know or care how IBookService is implemented. The framework handles that. This is loose coupling.
The Three Parts of Dependency Injection
1. INTERFACE → defines the contract (what can be done)
|
v
2. IMPLEMENTATION → provides the actual code (how it is done)
|
v
3. REGISTRATION → tells the framework "use this implementation for this interface"
Step 1 – Create the Interface
// Services/IBookService.cs
namespace BookStoreAPI.Services
{
public interface IBookService
{
List<Book> GetAll();
Book? GetById(int id);
Book Create(Book book);
bool Update(int id, Book updatedBook);
bool Delete(int id);
}
}
Step 2 – Create the Implementation
// Services/BookService.cs
using BookStoreAPI.Models;
namespace BookStoreAPI.Services
{
public class BookService : IBookService
{
private static List<Book> _books = new List<Book>
{
new Book { Id = 1, Title = "Clean Code", Author = "Robert C. Martin",
Price = 29.99m, Category = "Technology",
IsAvailable = true, CreatedDate = DateTime.Now },
new Book { Id = 2, Title = "The Pragmatic Programmer", Author = "David Thomas",
Price = 34.99m, Category = "Technology",
IsAvailable = true, CreatedDate = DateTime.Now }
};
public List<Book> GetAll() => _books;
public Book? GetById(int id) => _books.FirstOrDefault(b => b.Id == id);
public Book Create(Book book)
{
book.Id = _books.Any() ? _books.Max(b => b.Id) + 1 : 1;
book.CreatedDate = DateTime.Now;
_books.Add(book);
return book;
}
public bool Update(int id, Book updatedBook)
{
var book = _books.FirstOrDefault(b => b.Id == id);
if (book == null) return false;
book.Title = updatedBook.Title;
book.Author = updatedBook.Author;
book.Price = updatedBook.Price;
book.Category = updatedBook.Category;
book.IsAvailable = updatedBook.IsAvailable;
return true;
}
public bool Delete(int id)
{
var book = _books.FirstOrDefault(b => b.Id == id);
if (book == null) return false;
_books.Remove(book);
return true;
}
}
}
Step 3 – Register the Service in Program.cs
// Program.cs
builder.Services.AddScoped<IBookService, BookService>();
This line tells ASP.NET Core: "Whenever someone asks for an IBookService, create and provide a BookService."
Step 4 – Inject into the Controller
// Controllers/BooksController.cs
using Microsoft.AspNetCore.Mvc;
using BookStoreAPI.Models;
using BookStoreAPI.Services;
namespace BookStoreAPI.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class BooksController : ControllerBase
{
private readonly IBookService _bookService;
public BooksController(IBookService bookService)
{
_bookService = bookService;
}
[HttpGet]
public IActionResult GetAll()
{
var books = _bookService.GetAll();
return Ok(books);
}
[HttpGet("{id:int}")]
public IActionResult GetById(int id)
{
var book = _bookService.GetById(id);
if (book == null) return NotFound();
return Ok(book);
}
[HttpPost]
public IActionResult Create([FromBody] Book book)
{
var created = _bookService.Create(book);
return CreatedAtAction(nameof(GetById), new { id = created.Id }, created);
}
[HttpPut("{id:int}")]
public IActionResult Update(int id, [FromBody] Book book)
{
var success = _bookService.Update(id, book);
if (!success) return NotFound();
return NoContent();
}
[HttpDelete("{id:int}")]
public IActionResult Delete(int id)
{
var success = _bookService.Delete(id);
if (!success) return NotFound();
return NoContent();
}
}
}
Service Lifetimes
When registering a service, a lifetime is specified. The lifetime controls how long the service instance lives and whether it is shared between requests.
| Lifetime | Method | Instance Created | Use For |
|---|---|---|---|
| Singleton | AddSingleton<I, T>() | Once for the entire app lifetime | Config, caching, logging services |
| Scoped | AddScoped<I, T>() | Once per HTTP request | Database contexts, repository services |
| Transient | AddTransient<I, T>() | Every time it is requested | Lightweight, stateless services |
// Program.cs – registration examples
builder.Services.AddSingleton<ICacheService, CacheService>(); // app-wide, shared
builder.Services.AddScoped<IBookService, BookService>(); // per request
builder.Services.AddTransient<IEmailService, EmailService>(); // new every time
Lifetime Diagram
Singleton: [ App Start ] ──────────────────────────── [ App Stop ]
ONE instance
Scoped: Request 1: [ Instance A ] ─────────────[ Request 1 ends ]
Request 2: [ Instance B ]─ [ Request 2 ends ]
Transient: Request 1: Get service → New | Get service again → New (different)
Registering the BookStore API Services
// Program.cs – BookStore API service registrations
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// BookStore services
builder.Services.AddScoped<IBookService, BookService>();
var app = builder.Build();
Key Points
- Dependency Injection removes the need for a class to create its own dependencies.
- DI makes code loosely coupled and easier to test.
- The interface defines the contract, the implementation provides the code, and
Program.csconnects them. - ASP.NET Core's DI container provides three lifetimes: Singleton (once ever), Scoped (once per request), and Transient (every time).
- For the BookStore API,
IBookServiceis registered as Scoped because it will eventually use a database context (which is also Scoped).
