DEV Community

Dependency Injection στο .NET Core: Τι είναι και πώς εφαρμόζεται

Το Dependency Injection (DI) είναι ένα από τα πιο βασικά και σημαντικά patterns στο .NET Core και σε οποιοδήποτε μοντέρνο framework. Κατανοώντας το σωστά, μπορείς να γράφεις καθαρό, επεκτάσιμο και testable κώδικα.


1. Τι είναι Dependency Injection

Dependency Injection είναι ένα design pattern που σου επιτρέπει να περνάς εξαρτήσεις (dependencies) σε ένα αντικείμενο, αντί να τις δημιουργεί το ίδιο.

  • Dependency: Κάθε αντικείμενο που μια κλάση χρειάζεται για να λειτουργήσει.
  • Injection: Η διαδικασία με την οποία το dependency παρέχεται στην κλάση.

Παραδοσιακή προσέγγιση χωρίς DI

public class UserService
{
    private readonly UserRepository _repo;

    public UserService()
    {
        _repo = new UserRepository(); // tight coupling
    }

    public void CreateUser(string name)
    {
        _repo.Add(new User { Name = name });
    }
}

Enter fullscreen mode Exit fullscreen mode

Πρόβλημα: Η UserService είναι σφιχτά συνδεδεμένη με την UserRepository. Δεν μπορείς εύκολα να αλλάξεις repository ή να κάνεις unit testing χωρίς πραγματική βάση δεδομένων.


2. DI προσέγγιση

Με DI, περνάμε την εξάρτηση μέσω constructor:

public class UserService
{
    private readonly IUserRepository _repo;

    public UserService(IUserRepository repo)
    {
        _repo = repo; // dependency injected
    }

    public void CreateUser(string name)
    {
        _repo.Add(new User { Name = name });
    }
}

Enter fullscreen mode Exit fullscreen mode

Πλεονέκτημα:

  • Loose coupling
  • Testable κώδικας
  • Ευκολότερη συντήρηση και επεκτασιμότητα

3. Τύποι Dependency Injection

Στο .NET Core, υπάρχουν τρεις βασικοί τύποι DI:

  1. Constructor Injection (συνήθης και προτεινόμενη)

public UserService(IUserRepository repo) { ... }

  1. Property Injection (λιγότερο συχνή, χρησιμοποιείται σε rare cases)

public IUserRepository Repo { get; set; }

  1. Method Injection

public void Execute(IUserRepository repo) { ... }

Στο .NET Core, constructor injection είναι ο κανόνας.


4. DI στο .NET Core

Το .NET Core έχει ενσωματωμένο Dependency Injection container, το οποίο διαχειρίζεται την δημιουργία και διάρκεια ζωής των αντικειμένων.

Βήματα για χρήση:
4.1. Δημιουργία interface και implementation

public interface IUserRepository
{
    void Add(User user);
}

public class UserRepository : IUserRepository
{
    public void Add(User user)
    {
        // Αποθήκευση στη βάση
    }
}
Enter fullscreen mode Exit fullscreen mode

4.2. Καταχώρηση υπηρεσίας στο Program.cs

var builder = WebApplication.CreateBuilder(args);

// Dependency Injection registration
builder.Services.AddScoped<IUserRepository, UserRepository>();
builder.Services.AddScoped<UserService>();

var app = builder.Build();

Enter fullscreen mode Exit fullscreen mode

Scopes στο .NET Core DI container:

Scope Περιγραφή Παράδειγμα
Transient Νέο αντικείμενο κάθε φορά που ζητηθεί builder.Services.AddTransient<IService, Service>();
Scoped Ένα αντικείμενο ανά HTTP request builder.Services.AddScoped<IService, Service>();
Singleton Ένα αντικείμενο για όλη τη διάρκεια ζωής της εφαρμογής builder.Services.AddSingleton<IService, Service>();

4.3. Χρήση στο Controller

[ApiController]
[Route("api/[controller]")]
public class UsersController : ControllerBase
{
    private readonly UserService _userService;

    public UsersController(UserService userService)
    {
        _userService = userService;
    }

    [HttpPost]
    public IActionResult Create(string name)
    {
        _userService.CreateUser(name);
        return Ok("User created!");
    }
}

Enter fullscreen mode Exit fullscreen mode

Σημείωση: Δεν χρειάζεται ο controller να ξέρει πώς να φτιάξει το UserService ή το UserRepository. Όλα γίνονται αυτόματα από το DI container.


5. Πλεονεκτήματα του DI

1. Loose coupling: Οι κλάσεις δεν γνωρίζουν συγκεκριμένες υλοποιήσεις, μόνο interfaces.

2. Testability: Μπορείς να αντικαταστήσεις dependencies με mocks για unit testing.

3. Maintainability: Αλλαγές σε dependencies γίνονται σε ένα μόνο σημείο.

4. Configuration centralization: Όλες οι εξαρτήσεις καταχωρούνται σε ένα σημείο (Program.cs).

5. Επαναχρησιμοποίηση: Services μπορούν να μοιράζονται αντικείμενα με διαφορετικά scopes.


6. Καλές πρακτικές στο .NET Core

  • Προτιμάμε interfaces για όλα τα services
  • Χρησιμοποιούμε constructor injection πάντα
  • Αποφεύγουμε Service Locator pattern
  • Διαχωρίζουμε business logic από infrastructure
  • Επιλέγουμε σωστά το scope ανάλογα με τις ανάγκες (Transient, Scoped, Singleton)

7. Παραδείγματα σε Test

Μπορούμε εύκολα να κάνουμε unit test στο UserService χωρίς πραγματικό repository:

[Fact]
public void CreateUser_ShouldCallRepositoryAdd()
{
    var mockRepo = new Mock<IUserRepository>();
    var service = new UserService(mockRepo.Object);

    service.CreateUser("Alice");

    mockRepo.Verify(r => r.Add(It.Is<User>(u => u.Name == "Alice")), Times.Once);
}
Enter fullscreen mode Exit fullscreen mode

8. Συμπέρασμα

Το Dependency Injection στο .NET Core:

  • Είναι ενσωματωμένο, εύκολο στη χρήση και απαραίτητο για καθαρό, επεκτάσιμο κώδικα
  • Επιτρέπει loose coupling, testability, maintainability
  • Συνδυάζεται με scopes, middleware, controllers και services για πλήρη dependency management

30 Ερωτήσεις για .NET Senior Developer

nikosst

Top comments (0)