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 BookService changes its constructor, BooksController must 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.

LifetimeMethodInstance CreatedUse For
SingletonAddSingleton<I, T>()Once for the entire app lifetimeConfig, caching, logging services
ScopedAddScoped<I, T>()Once per HTTP requestDatabase contexts, repository services
TransientAddTransient<I, T>()Every time it is requestedLightweight, 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.cs connects 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, IBookService is registered as Scoped because it will eventually use a database context (which is also Scoped).

Leave a Comment