Το 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 });
}
}
Πρόβλημα: Η 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 });
}
}
Πλεονέκτημα:
- Loose coupling
- Testable κώδικας
- Ευκολότερη συντήρηση και επεκτασιμότητα
3. Τύποι Dependency Injection
Στο .NET Core, υπάρχουν τρεις βασικοί τύποι DI:
- Constructor Injection (συνήθης και προτεινόμενη)
public UserService(IUserRepository repo) { ... }
- Property Injection (λιγότερο συχνή, χρησιμοποιείται σε rare cases)
public IUserRepository Repo { get; set; }
- 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)
{
// Αποθήκευση στη βάση
}
}
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();
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!");
}
}
Σημείωση: Δεν χρειάζεται ο 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);
}
8. Συμπέρασμα
Το Dependency Injection στο .NET Core:
- Είναι ενσωματωμένο, εύκολο στη χρήση και απαραίτητο για καθαρό, επεκτάσιμο κώδικα
- Επιτρέπει loose coupling, testability, maintainability
- Συνδυάζεται με scopes, middleware, controllers και services για πλήρη dependency management
30 Ερωτήσεις για .NET Senior Developer
nikosst
Top comments (0)