# Dependency Injection📖 שיעור

## מה זה?

השיעור הזה בנוי כמו סיפור עם התקדמות: מתחילים מפתרון פשוט וטבעי, רואים **בדיוק** למה הוא נשבר במציאות, ורק אז מכניסים את הרעיון החדש כתשובה ישירה לבעיה הזאת — לא כמושג מופשט שצריך "לדעת שקיים".

הדוגמה שנלווה לאורך כל השיעור: `OrdersController` צריך לשלוח התראה בכל פעם שנוצרת הזמנה חדשה — נתחיל מהדרך הכי פשוטה לעשות את זה, ונבנה משם צעד-צעד עד ל-Dependency Injection (בקיצור: DI) מלא.

## מילות מפתח שחשוב לזכור

• DI Container — המנגנון המובנה של ASP.NET Core שמנהל אילו מימושים תואמים לאילו Interfaces

• `services.AddScoped<...>` — רישום: "כשמישהו מבקש `INotifier`, תן לו `EmailNotifier`"

• Constructor Injection — הדרך המקובלת לקבל תלות: פרמטר בקונסטרוקטור, ה-DI Container ממלא אותו אוטומטית

• Service Lifetime — כמה זמן "חי" כל מופע: `Scoped` (לכל בקשת HTTP), `Singleton` (אחד לכל התוכנית), `Transient` (מופע חדש בכל בקשה לו)

## בונים DI שלב אחר שלב

### שלב 0: הבעיה עצמה — מי שולח את ההתראה?

`OrdersController` (Controller רגיל, בדיוק כמו `StudentsController` שכבר בנינו) צריך שכל הזמנה חדשה תפיק התראה. **מישהו** צריך לדעת "איך שולחים התראה" — ואת ה"מישהו" הזה אנחנו כותבים בעצמנו, כמחלקה רגילה בפרויקט שלנו:

```csharp
// Notifications/EmailNotifier.cs — קובץ שאנחנו כותבים, לא משהו שמגיע מוכן מ-ASP.NET
public class EmailNotifier
{
    public void Notify(string message)
    {
        // בפרויקט אמיתי כאן היה קוד ששולח מייל בפועל (SMTP וכו').
        // כדי שהדוגמה תישאר ממוקדת ב-DI ולא בפרטי שליחת מייל, נסתפק בהדפסה.
        Console.WriteLine($"Sending email: {message}");
    }
}
```

אין כאן שום דבר חדש — זו מחלקה בדיוק כמו `Student` או `Product` שכבר כתבנו: `public class EmailNotifier` מגדיר אותה, ובתוכה `public void Notify(string message)` היא מתודה רגילה שמקבלת טקסט ולא מחזירה כלום (`void`). היא יושבת בקובץ משלה בפרויקט (למשל `Notifications/EmailNotifier.cs`), בדיוק כמו שכל מחלקה אחרת שכתבנו יושבת בקובץ משלה.

עכשיו משתמשים בה מתוך ה-Controller, בדרך הכי טבעית שיש:

```csharp
[ApiController]
[Route("api/[controller]")]
public class OrdersController : ControllerBase
{
    [HttpPost]
    public IActionResult PlaceOrder()
    {
        var notifier = new EmailNotifier();   // יוצרים אובייקט חדש מהמחלקה שכתבנו למעלה
        notifier.Notify("Order placed");
        return Ok();
    }
}
```

`new EmailNotifier()` הוא בדיוק אותו `new` שכבר השתמשנו בו המון פעמים (`new Student(...)`, `new List<int>()`) — כאן פשוט יוצרים מופע מהמחלקה שלנו במקום ממחלקת נתונים. `notifier` הוא משתנה מקומי שמחזיק את האובייקט הזה, ואז קוראים למתודה `Notify` שלו.

**וזה עובד.** מריצים, שולחים הזמנה, מקבלים "Sending email: Order placed" בקונסולה. אין שום באג כאן ואין שום דבר "לא נכון" בקוד הזה כרגע.

### שלב 1: מתי הפתרון הזה נשבר

הבעיה לא מופיעה היום — היא מופיעה כשמשהו **משתנה**. נניח שמחליטים לעבור מהתראות במייל להתראות ב-SMS. עכשיו צריך:

1. לכתוב מחלקה חדשה, `SmsNotifier`, עם מתודת `Notify` שיודעת לשלוח SMS.
2. לחפש **כל מקום** בפרויקט שכתוב בו `new EmailNotifier()` — לא רק ב-`OrdersController`, גם ב-`InvoicesController`, ב-`ShippingController`, ובכל Controller אחר שצריך לשלוח התראה — ולהחליף שם ל-`new SmsNotifier()`.

בפרויקט עם 5 Controllers כאלה, זה 5 מקומות לתקן ידנית. שוכחים מקום אחד? הוא ימשיך לשלוח מייל בזמן שכל השאר עברו ל-SMS — באג שקט, לא שגיאת קומפילציה, רק התנהגות לא-עקבית שמישהו יגלה מאוחר.

השורש של הבעיה: כל Controller **יוצר** בעצמו את `EmailNotifier` הספציפי, ולכן כל Controller "יודע" איזה סוג התראה קיים במערכת. זה בדיוק המושג "תלות ישירה במימוש קונקרטי" — אם למדתם את יחידת ה-SOLID, זו בדיוק הבעיה ש-DIP מדבר עליה.

### שלב 2: הפתרון — לתלות בהבטחה, לא בפרט

הרעיון: `OrdersController` לא צריך לדעת אם ההתראה נשלחת במייל, ב-SMS, או בכל דרך אחרת. הוא רק צריך לדעת ש**יש למישהו מתודת `Notify`**. את ההבטחה הזו מבטא Interface:

```csharp
// Notifications/INotifier.cs
public interface INotifier
{
    void Notify(string message);
}
```

Interface, כמו שכבר ראינו ביחידת ה-OOP, הוא רשימת מתודות בלי מימוש — הבטחה ש"כל מי שמממש אותי, יש לו את המתודות האלה". עכשיו `EmailNotifier` **מממש** את ההבטחה הזו:

```csharp
public class EmailNotifier : INotifier   // ": INotifier" = "אני מבטיח שיש לי את כל מה שב-INotifier"
{
    public void Notify(string message)
    {
        Console.WriteLine($"Sending email: {message}");
    }
}
```

שום דבר בתוך `EmailNotifier` לא השתנה חוץ מה-`: INotifier` — היא עדיין אותה מחלקה, רק שעכשיו רשום עליה במפורש שהיא עומדת בהבטחה של `INotifier`.

### שלב 3: ה-Controller תלוי בהבטחה, לא ב-EmailNotifier

עכשיו משנים את ה-Controller כך שיקבל `INotifier` — ההבטחה — ולא יידע בכלל ש-`EmailNotifier` קיים:

```csharp
public class OrdersController : ControllerBase
{
    private readonly INotifier _notifier;

    public OrdersController(INotifier notifier)
    {
        _notifier = notifier;
    }

    [HttpPost]
    public IActionResult PlaceOrder()
    {
        _notifier.Notify("Order placed");
        return Ok();
    }
}
```

בואו נעצור על השורה `private readonly INotifier _notifier;` — כל מילה בה עושה משהו:

- `private` — השדה נגיש רק מתוך המחלקה `OrdersController` עצמה, בדיוק כמו `private` שכבר ראינו על שדות ביחידת ה-OOP.
- `readonly` — אפשר לתת לשדה ערך רק פעם אחת, בקונסטרוקטור. אחרי זה אי אפשר לשנות אותו. זה מתאים כאן כי ברגע שה-Controller נוצר, ה-Notifier שלו לא אמור להתחלף באמצע החיים שלו.
- `INotifier` — הטיפוס של השדה. שימו לב: זה ה-**Interface**, לא `EmailNotifier`. זו בדיוק הנקודה — השדה יודע רק "יש כאן משהו שמממש `Notify`", לא איזה מימוש ספציפי.
- `_notifier` — שם השדה. קו-תחתי בהתחלה (`_`) הוא מוסכמת שמות נפוצה ב-C# לשדות `private` — מבדיל אותם ויזואלית מפרמטרים ומשתנים מקומיים.

ועכשיו הקונסטרוקטור: `public OrdersController(INotifier notifier)` — הפרמטר `notifier` מגיע **מבחוץ**, מישהו אחר צריך להעביר אותו כשיוצרים `OrdersController`. בגוף הקונסטרוקטור, `_notifier = notifier;` פשוט שומר את מה שהתקבל בתוך השדה, כדי שאר המתודות של המחלקה (כמו `PlaceOrder`) יוכלו להשתמש בו.

**אבל זה יוצר שאלה חדשה:** מי בכלל קורא ל-`new OrdersController(...)`, ומה הוא מעביר בתור `notifier`? עד עכשיו אף אחד לא ענה על זה.

### שלב 4: מי יוצר Controller בכלל?

התשובה, שכבר ראינו בשיעור ה-Controllers: אתם אף פעם לא כותבים `new OrdersController(...)` בעצמכם. ASP.NET Core עצמו יוצר את ה-Controller בכל פעם שמגיעה בקשת HTTP מתאימה (זוכרים את `AddControllers()`/`MapControllers()` מ-`Program.cs`?). זה אומר שעכשיו **ASP.NET** הוא זה שצריך לדעת מה להעביר בתור `notifier` — ולזה בדיוק משמש ה-DI Container.

### שלב 5: אומרים ל-ASP.NET מה להעביר — רישום ב-Program.cs

ASP.NET Core מחזיק "ספר טלפונים" פנימי — עבור כל Interface, מי המימוש שצריך להעביר כשמישהו מבקש אותו. השורה שרושמת ערך בספר הזה:

```csharp
// Program.cs
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddScoped<INotifier, EmailNotifier>();   // "כשמישהו מבקש INotifier, תן לו EmailNotifier"

var app = builder.Build();
```

`builder.Services` הוא בדיוק הספר-טלפונים הזה. `AddScoped<INotifier, EmailNotifier>()` היא הרשומה: הטיפוס הראשון (`INotifier`) הוא **מה מבקשים**, הטיפוס השני (`EmailNotifier`) הוא **מה לתת**. המילה `Scoped` קובעת לכמה זמן "חי" המימוש שנוצר — נרחיב על זה בהמשך השיעור; בשלב הזה מספיק לדעת שזו הדרך הנפוצה ביותר לרשום שירות.

### שלב 6: רואים את זה מתחבר לבד

עכשיו, כשמגיעה בקשת `POST /api/orders`, קורה בדיוק זה:

```
Client
  │  POST /api/orders
  ▼
ASP.NET Core: "אני צריך ליצור OrdersController.
               הקונסטרוקטור שלו מבקש INotifier.
               בספר הטלפונים כתוב: INotifier → EmailNotifier."
  ▼
יוצר EmailNotifier, ומעביר אותו כפרמטר notifier
  ▼
OrdersController.PlaceOrder() רץ, _notifier הוא כבר EmailNotifier מוכן
```

שימו לב: ה-Controller משלב 3 **לא השתנה בכלל**. כל מה שהיה צריך זה שורת הרישום ב-`Program.cs`.

### שלב 7: היתרון האמיתי — מחליפים מימוש בלי לגעת ב-Controllers

עכשיו, כשרוצים לעבור ל-SMS, כותבים מחלקה חדשה שמממשת את אותה הבטחה:

```csharp
public class SmsNotifier : INotifier
{
    public void Notify(string message)
    {
        Console.WriteLine($"Sending SMS: {message}");
    }
}
```

ומשנים **שורה אחת** ב-`Program.cs`:

```csharp
// Program.cs — שורה אחת, שום דבר אחר לא משתנה
builder.Services.AddScoped<INotifier, SmsNotifier>();   // עכשיו כולם מקבלים SmsNotifier
```

`OrdersController` לא נגעו בו. אם יש עוד 5 Controllers שמבקשים `INotifier` בקונסטרוקטור שלהם — גם בהם לא נגעו. כולם מקבלים אוטומטית את `SmsNotifier` החדש, כי כולם תלויים בהבטחה (`INotifier`) ולא במימוש הספציפי. זו בדיוק הבעיה משלב 1, פתורה לחלוטין.

### טעות נפוצה: שכחתם לרשום ב-Program.cs

נניח שכתבתם את הכול משלבים 2-3, אבל שכחתם את שורת ה-`AddScoped` משלב 5. מריצים את הפרויקט, שולחים `POST /api/orders`, ומקבלים שגיאה בסגנון:

```
InvalidOperationException: Unable to resolve service for type 'INotifier'
while attempting to activate 'OrdersController'.
```

בואו נבין למה השגיאה הזו הגיונית, לא רק "נזכור לתקן": ASP.NET ניגש לספר-הטלפונים שלו כדי לדעת מה להעביר בתור `notifier`, ולא מוצא שם רשומה בשביל `INotifier`. הוא לא יכול "לנחש" איזה מימוש התכוונתם אליו — `EmailNotifier`? `SmsNotifier`? אולי מימוש שלישי שעוד לא כתבתם? במקום לנחש, הוא זורק שגיאה ברורה. התיקון: לוודא שיש שורת `AddScoped<INotifier, ...>` ב-`Program.cs` לפני `builder.Build()`.

## הסבר עיקרי

מה שבנינו כאן הוא בדיוק DIP מיחידת SOLID, רק שהפעם ה-**Framework** עושה את ה"הזרקה" בעצמו. ב-DIP הזרקנו תלות ידנית (כתבנו בעצמנו איזה מימוש להעביר). עם DI Container, אנחנו רק **אומרים** ל-ASP.NET Core "כשמבקשים `INotifier`, תן `EmailNotifier`" — פעם אחת, ב-`Program.cs` — וההזרקה בפועל לכל Controller קורית אוטומטית בכל בקשה. זה אותו עיקרון בדיוק, רק עם Framework שמבצע את החיבור בשבילנו במקום שנעשה את זה ידנית בכל מקום.

**Service Lifetime** קובע כמה זמן "חי" כל מופע שנוצר:

- `Scoped` (הכי נפוץ) — מופע חדש לכל בקשת HTTP; כל השירותים שמעורבים באותה בקשה (Controller, Service, Repository) משתפים את אותו מופע, אבל בקשה אחרת מקבלת מופע חדש לגמרי.
- `Singleton` — מופע אחד ויחיד לכל חיי התוכנית, מרגע שהשרת עלה ועד שהוא נסגר. בדיוק כמו תבנית ה-Singleton מיחידת Design Patterns — רק שכאן ה-DI Container מנהל את זה בשבילכם, לא אתם כותבים לוגיקת Singleton ידנית.
- `Transient` — מופע חדש בכל פעם שמבקשים אותו, אפילו בתוך אותה בקשת HTTP אם הוא מבוקש פעמיים.

למה זה חשוב מעבר לנוחות: בדיוק כמו ש-DIP איפשר להזריק `MockPaymentGateway` לבדיקות בלי לגעת ב-`CheckoutService`, DI מאפשר לרשום מימוש-בדיקה שונה בסביבת טסטים (למשל `FakeNotifier` שרק זוכר מה נשלח, בלי לשלוח שום דבר באמת), בזמן שקוד ה-Controller עצמו לא משתנה כלל.

## יתרונות

Constructor Injection הופך תלויות לגלויות וברורות (רואים מיד מה Controller צריך כדי לפעול); החלפת מימוש היא שינוי במקום אחד (`Program.cs`), לא בכל מקום שמשתמש בתלות; Service Lifetimes נותנים שליטה מדויקת על ניהול משאבים.

## חסרונות

בחירת Lifetime שגויה (למשל `Singleton` על שירות שאמור להיות `Scoped`) עלולה ליצור באגים עדינים של שיתוף מצב לא-רצוי בין בקשות; יותר מדי הפשטות/Interfaces רק לצורך DI, בלי צורך אמיתי, מוסיף מורכבות.

## נקודות חשובות

• DI Container מנהל אילו מימושים תואמים לאילו Interfaces, נרשם ב-`Program.cs`

• Constructor Injection = קבלת תלות כפרמטר קונסטרוקטור, לא `new` ידני בפנים

• `Scoped` = לכל בקשת HTTP; `Singleton` = אחד לכל התוכנית; `Transient` = חדש בכל בקשה לתלות

• DI Container הוא המימוש המעשי של DIP מיחידת SOLID, לא רעיון נפרד

## טעויות נפוצות

• לשכוח לרשום Interface ב-`Program.cs` ולקבל שגיאת ריצה "Unable to resolve service" כשה-Controller עולה (ראו את ההסבר המלא בשלבים למעלה)

• לבחור `Singleton` לשירות שמחזיק מצב ספציפי-לבקשה (כמו משתמש מחובר) — דליפת מידע בין בקשות שונות

• ליצור `new EmailNotifier()` ידנית בתוך Controller במקום להזריק — חוזר אחורה מ-DIP

## סיכום

בנינו DI שלב אחר שלב: התחלנו מ-`new EmailNotifier()` מקושח ישירות בקוד (עובד, אבל שינוי דורש לחפש ולתקן בכל מקום), עברנו ל-Interface (`INotifier`) שמבטא "הבטחה" במקום מימוש ספציפי, המשכנו ל-Constructor Injection ידני (טוב יותר, אבל מי יוצר את ה-Controller?), ועד רישום ב-`Program.cs` עם DI Container שמבצע את ההזרקה אוטומטית לכל Controller. Service Lifetime (`Scoped`/`Singleton`/`Transient`) קובע כמה זמן חי כל מופע. היתרון המרכזי: החלפת מימוש היא שינוי במקום אחד, לא בכל מקום שמשתמש בתלות.

## דוקומנטציה רשמית

[Microsoft Learn — Dependency injection in ASP.NET Core](https://learn.microsoft.com/aspnet/core/fundamentals/dependency-injection)

---

## תרגילים

### תרגיל 1 — רישום והזרקה ראשונים

**המשימה:** צרו `interface ILogger { void Log(string message); }` עם מימוש `ConsoleLogger`, רשמו אותו ב-`Program.cs` עם `AddScoped`, והזריקו אותו ל-Controller קיים דרך הקונסטרוקטור.

**בדיקה:** ה-Controller משתמש ב-`ILogger` שהוזרק, בלי ליצור `new ConsoleLogger()` בעצמו בשום מקום.

### תרגיל 2 — החלפת מימוש

**המשימה:** צרו מימוש שני, `FileLogger`, והחליפו את הרישום ב-`Program.cs` מ-`ConsoleLogger` ל-`FileLogger`, בלי לגעת ב-Controller בכלל.

**בדיקה:** אחרי השינוי ב-`Program.cs` בלבד, ה-Controller "מקבל" את `FileLogger` החדש אוטומטית.

---

## פרויקט מסכם

**המשימה:** הוסיפו שכבת שירות מוזרקת ל-`TasksController` (מהפרויקטים המסכמים הקודמים).

**דרישות:**
1. `interface ITaskNotifier` עם מתודה `NotifyTaskCreated(string title)`, ולפחות שני מימושים (`ConsoleTaskNotifier`, `EmailTaskNotifier` — יכול להדפיס בלבד לצורך התרגול)
2. רישום המימוש הנבחר ב-`Program.cs` עם `AddScoped`
3. `TasksController` מקבל `ITaskNotifier` דרך הקונסטרוקטור, וקורא לו בכל יצירת משימה חדשה (`Create` Action)

**בדיקה:** יצירת משימה מדפיסה הודעת התראה מהמימוש הרשום; החלפת הרישום ב-`Program.cs` בלבד (בלי לגעת ב-`TasksController`) משנה איזה מימוש רץ בפועל.

## מה בפרק הבא

עכשיו שאנחנו יודעים איך ASP.NET מזריק תלויות אוטומטית, אפשר להשתמש בדיוק באותו מנגנון כדי להזריק ל-Controller לא רק "שולח התראות", אלא גם "מקום שמור בו הנתונים" — **תבנית DAL (IRepository)**: איך מפרידים לחלוטין בין הלוגיקה העסקית לבין איך הנתונים נשמרים בפועל, כהכנה למעבר מ-InMemory למסד נתונים אמיתי ביחידה הבאה.
