# Middleware Pipeline📖 שיעור

## מה זה?

בשיעור הקודם כתבנו כמה endpoints. עכשיו דמיינו שרוצים שכל בקשה תירשם ביומן (log), וגם שתיבדק הרשאה לפני שהיא מטופלת. הפתרון ה"נאיבי" — להוסיף את שתי הבדיקות בתוך **כל** endpoint בנפרד:

```csharp
app.MapGet("/api/students", (HttpContext ctx) =>
{
    Console.WriteLine($"Incoming: {ctx.Request.Method} {ctx.Request.Path}");   // ①
    if (!ctx.Request.Headers.ContainsKey("Authorization"))                     // ②
        return Results.Unauthorized();
    return Results.Ok(students);
});

app.MapPost("/api/students", (HttpContext ctx, Student s) =>
{
    Console.WriteLine($"Incoming: {ctx.Request.Method} {ctx.Request.Path}");   // ① — same two lines
    if (!ctx.Request.Headers.ContainsKey("Authorization"))                     // ② — copy-pasted again
        return Results.Unauthorized();
    students.Add(s);
    return Results.Created($"/api/students/{s.Id}", s);
});
```

שתי השורות המסומנות (①②) חוזרות על עצמן בכל endpoint, ואם יש 20 endpoints — הן מועתקות 20 פעמים. זו בדיוק אותה בעיה שכבר פגשנו ביחידת SOLID (הפרת SRP): אם צריך לשנות את פורמט הלוג, צריך לזכור לתקן בכל מקום בנפרד — ובקלות שוכחים אחד.

**הרעיון של Middleware:** לכתוב את הלוגיקה החוזרת הזו **פעם אחת בלבד**, ולתת ל-ASP.NET Core להריץ אותה **אוטומטית** על כל בקשה נכנסת — לפני שהיא בכלל מגיעה לקוד ה-endpoint שלכם. Middleware Pipeline הוא שרשרת שכבות כאלה, שכל בקשה עוברת בסדר קבוע.

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

• Middleware — רכיב שרץ על כל בקשה נכנסת (או חלק ממנה), יכול לבדוק/לשנות אותה ואז להעביר הלאה

• Pipeline — השרשרת המלאה של כל ה-Middleware, בסדר שבו הם נרשמו

• `next()` — קריאה שמעבירה את הבקשה ל-Middleware הבא בשרשרת; בלעדיה, הבקשה "נעצרת" כאן

• `app.Use(...)` — הדרך לרשום Middleware בפרויקט ASP.NET Core, בקובץ `Program.cs`

## איך בונים את זה — שלב אחר שלב

### שלב 1: הכי בסיסי שיש — Middleware שרק רושם ביומן

מוסיפים ל-`Program.cs`, **מיד אחרי** `var app = builder.Build();` ו**לפני** כל `app.MapX(...)`:

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

app.Use(async (context, next) =>
{
    Console.WriteLine($"Incoming request: {context.Request.Method} {context.Request.Path}");
    await next();   // ממשיכים הלאה לשכבה הבאה (בסוף — ל-endpoint עצמו)
});

app.MapGet("/api/students", () => students);   // מגיע לכאן רק אחרי ה-Middleware
```

`app.Use` מקבל פונקציה עם שני פרמטרים: `context` (כל המידע על הבקשה הנוכחית) ו-`next` (קריאה שממשיכה הלאה). בלי `await next()`, הבקשה פשוט "נתקעת" ב-Middleware הזה ואף פעם לא מגיעה ל-endpoint. זה בדיוק ה"מימוש בקוד" של הרעיון: הלוגיקה נכתבה **פעם אחת**, לא בכל endpoint בנפרד.

### שלב 2: Middleware שיכול לעצור את השרשרת

עכשיו מוסיפים בדיקת הרשאה — הפעם, אם היא נכשלת, **לא** קוראים ל-`next()` בכלל:

```csharp
app.Use(async (context, next) =>
{
    if (!context.Request.Headers.ContainsKey("Authorization"))
    {
        context.Response.StatusCode = 401;
        return;   // אין next() — הבקשה נעצרת כאן, לא ממשיכה הלאה
    }
    await next();
});
```

דילוג על `next()` הוא הדרך לעצור בקשה באמצע השרשרת — בדיוק כמו מאבטח שלא נותן למישהו בלי תג להיכנס בכלל, לא משנה מה הוא רצה לעשות בפנים.

### שלב 3: הסדר קובע הכול

שני ה-Middleware מהשלבים הקודמים, ביחד, בסדר הנכון:

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

app.Use(async (context, next) =>                 // ① רץ ראשון על כל בקשה
{
    Console.WriteLine($"Incoming: {context.Request.Method} {context.Request.Path}");
    await next();
    Console.WriteLine($"Response sent: {context.Response.StatusCode}");   // רץ אחרי שהתשובה חזרה
});

app.Use(async (context, next) =>                 // ② רץ שני — רק אם ① קרא ל-next()
{
    if (!context.Request.Headers.ContainsKey("Authorization"))
    {
        context.Response.StatusCode = 401;
        return;
    }
    await next();
});

app.MapGet("/api/students", () => "Student list");   // ③ מגיע לכאן רק אם גם ① וגם ② קראו ל-next()
```

Middleware שנרשם ראשון (`app.Use` הראשון) רץ ראשון על כל בקשה נכנסת. אם היינו רושמים את בדיקת ההרשאה **לפני** הלוג, בקשה בלי הרשאה הייתה נעצרת ב-401 לפני שהלוג בכלל ירוץ — לפעמים רצוי, לפעמים לא. הסדר הוא החלטת עיצוב מכוונת, לא מקרית.

```mermaid
flowchart RL
    REQ["Incoming request"] --> LOG["Middleware: Log"]
    LOG --> AUTH{"Middleware: Authorization?"}
    AUTH -->|"missing"| E401["401 — stops here"]
    AUTH -->|"present"| MAP["Endpoint"]
    MAP --> RES["Outgoing response"]
    RES -.->|"await next() returns"| LOG
```

## הסבר עיקרי

`next()` הוא "המשך לשכבה הבאה" — כל Middleware מקבל את הבקשה, יכול לעשות משהו לפניה (כמו הלוג), לקרוא ל-`next()` כדי להעביר אותה הלאה, ואז (אופציונלי) לעשות עוד משהו **אחרי** שהבקשה חזרה מהשכבות הפנימיות (כמו הדפסת ה-Status Code בדוגמה בשלב 3). דילוג על `next()` עוצר את השרשרת שם — בדיוק כמו ש-Middleware בדיקת ה-JWT שיופיע ביחידת NoSQL/Auth יעצור בקשות לא-מאומתות.

זה בדיוק SRP+OCP במימוש חדש — כל Middleware אחראי על **דבר אחד** (SRP) — הוספת Middleware חדש (למשל Rate Limiting) היא הוספת שכבה נוספת, בלי לגעת בשכבות הקיימות (OCP). זו לא תיאוריה מופשטת — זה בדיוק אותו עיקרון שכבר תרגלתם, במימוש קונקרטי בתשתית אמיתית של ASP.NET Core.

## יתרונות

Middleware מרכז לוגיקה חוצת-בקשות (לוג, הרשאות, טיפול-שגיאות) במקום אחד, לא חוזרת בכל endpoint; סדר מפורש וברור קובע בדיוק מה קורה מתי; קל להוסיף שכבה חדשה בלי לגעת בקיימות.

## חסרונות

יותר מדי שכבות Middleware מאטות כל בקשה (כל שכבה מוסיפה עיבוד); סדר שגוי (למשל הרשאות אחרי לוגיקה עסקית) עלול ליצור פרצות אבטחה או באגים קשים לאבחון.

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

• Middleware רץ על כל בקשה, בסדר הרישום המדויק שלו ב-`Program.cs` — מיד אחרי `builder.Build()`, לפני ה-endpoints

• `next()` מעביר לשכבה הבאה; דילוג עליו עוצר את השרשרת שם

• סדר Middleware הוא החלטה מכוונת — לוג/הרשאות/טיפול-שגיאות בדרך כלל מוקדם בשרשרת

• Middleware הוא יישום מעשי של SRP+OCP על תשתית שרת אמיתית

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

• לשכוח `await next()` ב-Middleware שאמור להמשיך לשכבה הבאה — הבקשה "נתקעת" שם בטעות

• לרשום Middleware בסדר שגוי (למשל הרשאות אחרי שהלוגיקה העסקית כבר רצה) — פוגע במטרה כולה

• לחשוב ש-Middleware הוא רק "לוג" — הוא כלי כללי לכל דבר שצריך לרוץ על **כל** בקשה

## סיכום

Middleware Pipeline הוא שרשרת שכבות שכל בקשה נכנסת עוברת, בסדר קבוע לפי סדר הרישום ב-`Program.cs`. כל Middleware יכול לבדוק/לשנות את הבקשה, לקרוא ל-`next()` כדי להמשיך הלאה, או לעצור את השרשרת (כמו החזרת `401` בלי `next()`). זהו מימוש מעשי של SRP (כל שכבה אחריות אחת) ו-OCP (הוספת שכבה בלי לגעת בקיימות) על תשתית שרת אמיתית — ובנינו אותו מאפס, שלב אחר שלב: קודם שכבה בודדת שרק ממשיכה הלאה, אחר כך שכבה שיודעת לעצור, ולבסוף השילוב של שתיהן יחד לפי סדר מכוון.

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

[Microsoft Learn — ASP.NET Core Middleware](https://learn.microsoft.com/aspnet/core/fundamentals/middleware/)

---

## תרגילים

### תרגיל 1 — Middleware לוג ראשון

**המשימה:** כתבו Middleware שמדפיס את ה-Method וה-Path של כל בקשה נכנסת, ורשמו אותו ראשון בשרשרת. ודאו שהוא רץ על כל endpoint שתגדירו.

**בדיקה:** כל בקשה (לכל endpoint) מדפיסה שורת לוג לפני שהתשובה חוזרת.

### תרגיל 2 — Middleware עוצר

**המשימה:** כתבו Middleware שבודק אם קיים Header בשם `X-Api-Key`, ואם לא — מחזיר `401` בלי לקרוא ל-`next()`.

**בדיקה:** בקשה בלי ה-Header מקבלת `401` ולא מגיעה ל-endpoint; בקשה עם ה-Header מגיעה כרגיל.

---

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

**המשימה:** בנו שרשרת Middleware בת 3 שכבות לפרויקט ASP.NET Core פשוט.

**דרישות:**
1. Middleware לוג — מדפיס Method+Path+זמן לפני, ו-Status Code אחרי (עם `await next()`)
2. Middleware הרשאה — בודק Header ומחזיר `401` אם חסר, בלי `next()`
3. Middleware טיפול-שגיאות — עוטף את `next()` ב-`try`/`catch`, ומחזיר `500` עם הודעה גנרית אם נזרקה חריגה בלתי-מטופלת מהשכבות הפנימיות

**בדיקה:** בקשה תקינה עם Header עוברת את כל השכבות ומגיעה ל-endpoint; בקשה בלי Header נעצרת ב-401; endpoint שזורק חריגה מכוונת נתפס על ידי Middleware השגיאות ומחזיר 500 מסודר, לא Stack Trace גולמי.

## מה בפרק הבא

בפרק הבא — **Controllers**: איך מארגנים endpoints בצורה מובנית עם `[Route]`/`[HttpGet]` וכו', במקום `app.MapGet` בודדים ב-`Program.cs`.
