# Controllers — Route, Action, IActionResult📖 שיעור

## מה זה?

בשיעור ה-CRUD היו לנו 6 endpoints על משאב אחד (`Student`). זה כבר מרגיש עמוס — עכשיו דמיינו שמוסיפים גם `Product`, גם `Order`, וכל אחד עם 5-6 endpoints משלו:

```csharp
// Program.cs — every resource's endpoints, all crammed into one file
app.MapGet("/api/students", () => students);
app.MapGet("/api/students/{id}", (int id) => /* ... */ );
app.MapPost("/api/students", (Student s) => /* ... */ );
// ... 3 more student endpoints ...

app.MapGet("/api/products", () => products);
app.MapGet("/api/products/{id}", (int id) => /* ... */ );
app.MapPost("/api/products", (Product p) => /* ... */ );
// ... 3 more product endpoints ...

app.MapGet("/api/orders", () => orders);
// ... and it keeps growing ...
```

`Program.cs` הופך לקובץ ענק ובלתי-קריא, עם endpoints של משאבים שונים לגמרי מעורבבים זה בזה. **Controllers** פותרים את זה: מארגנים את כל ה-endpoints של משאב אחד (כל מה שקשור ל"תלמידים", למשל) יחד, במחלקה נפרדת משלו — SRP במימוש: מחלקה אחת, אחריות אחת.

## מאיפה זה רץ — החיבור בין הקבצים

עד עכשיו כל שיעור ביחידה חי בקובץ אחד: `Program.cs`. ברגע שעוברים ל-Controllers הפרויקט מתפצל לכמה קבצים, וזו בדיוק הנקודה שצריך להבין **לפני** שרואים את קוד ה-Controller עצמו — אחרת לא ברור מאיפה הוא רץ ומי מפעיל אותו.

```
MyFirstApi/
├─ Program.cs                    # entry point — starts the server, wires everything together
├─ Models/
│  └─ Student.cs                 # the "record Student" — moved here out of Program.cs
└─ Controllers/
   └─ StudentsController.cs      # every "students" endpoint — the new file this lesson builds
```

### מה משתנה ב-`Program.cs`

בשיעור ה-CRUD `Program.cs` הכיל את כל שורות ה-`app.MapGet(...)`. עכשיו הוא מתקצר לשלד שרק **מפעיל** את מנגנון ה-Controllers — שתי שורות זה כל ההבדל:

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

builder.Services.AddControllers();   // 1. turn on the Controllers system

var app = builder.Build();

app.MapControllers();   // 2. connect the discovered Controllers to the request pipeline

app.Run();
```

• `AddControllers()` — אומר ל-ASP.NET Core "המערכת הזו משתמשת ב-Controllers". בהפעלת השרת זה גורם לו לסרוק את הפרויקט, למצוא כל מחלקת Controller, ולקרוא את ה-attributes שמעליה (`[Route]`, `[HttpGet]`...).

• `MapControllers()` — לוקח את טבלת ה-routing שנבנתה מהסריקה הזו ומחבר אותה ל-Middleware Pipeline מהשיעור הקודם. **בלי השורה הזו אף בקשה לא תגיע לאף Action** — גם אם ה-Controller מתקמפל בלי שגיאה, כל כתובת תחזיר `404`.

שימו לב: `Program.cs` לא מזכיר את `StudentsController` בשמו אף פעם. הוא לא צריך — הגילוי אוטומטי.

### מי "קורא" ל-Controller?

אף אחד לא קורא לו ישירות — לא אתם, ולא `Program.cs`. הזרימה:

1. **בהפעלת השרת** (`dotnet run`): `AddControllers()` גורם ל-ASP.NET Core לסרוק את הקוד, למצוא את `StudentsController`, ולקרוא `[Route("api/[controller]")]` + `[HttpGet]` מעל כל Action. נבנית טבלה: `GET api/students` → `StudentsController.GetAll()`, `GET api/students/{id}` → `GetById()`, וכן הלאה.

2. **כשמגיעה בקשה** `GET /api/students`: היא עוברת דרך ה-Middleware Pipeline, ואז ה-routing מסתכל בטבלה, מוצא התאמה, **יוצר instance של `StudentsController`**, וקורא ל-`GetAll()`. מה ש-`Ok(...)` מחזיר הופך ל-Response.

זו **בדיוק** אותה זרימה של `app.MapGet("/api/students", () => ...)` מהשיעור הקודם — רק שבמקום שכתבתם את המיפוי בעצמכם בשורה אחת, ה-Framework מגלה אותו מה-attributes. Controller הוא לא "תוכנית שרצה", הוא ארגז של פונקציות שה-Framework מפעיל בתגובה לבקשות.

### איך `StudentsController` מכיר את `Student`?

שני הקבצים באותו פרויקט = אותו assembly. אם `Student` יושב תחת `namespace MyFirstApi.Models`, מוסיפים בראש `StudentsController.cs` שורת `using MyFirstApi.Models;` והטיפוס זמין. בפרויקט קטן שהכל בו תחת namespace אחד — אין צורך אפילו ב-`using`.

### מריצים ובודקים

```bash
dotnet run
```

השרת מדפיס כתובת (למשל `http://localhost:5123`). בודקים endpoint:

```bash
curl http://localhost:5123/api/students
```

או פותחים `http://localhost:5123/swagger` בדפדפן — טמפלט `webapi` (זה שהוזכר בשיעור ה-HTTP, `dotnet new webapi`) מגיע עם Swagger UI שמציג את כל ה-Actions ומאפשר לשלוח אליהם בקשות ישירות מהדפדפן, בלי curl.

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

• Controller — מחלקה שמאגדת endpoints קשורים, בדרך כלל יורשת מ-`ControllerBase`, ויושבת בקובץ נפרד משלה תחת `Controllers/`

• `AddControllers()` — קריאה ב-`Program.cs` שמפעילה את סריקת ה-Controllers בהפעלת השרת

• `MapControllers()` — קריאה ב-`Program.cs` שמחברת את ה-Controllers שהתגלו ל-Middleware Pipeline; בלעדיה כל בקשה מחזירה `404`

• `[Route]` — attribute שקובע את ה-URL הבסיסי של כל ה-Controller (למשל `api/[controller]`)

• Action — מתודה בתוך Controller שמטפלת ב-endpoint בודד (המקבילה ל-`app.MapGet(...)` הבודד מהשיעור הקודם)

• `[HttpGet]`/`[HttpPost]`/`[HttpPut]`/`[HttpDelete]` — attributes שמתייגים כל Action עם ה-HTTP Method שלה

• `IActionResult` — טיפוס החזרה שמאפשר להחזיר Status Code + Body יחד (`Ok(...)`, `NotFound()`, `BadRequest(...)`)

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

### שלב 1: מחלקה ריקה — רק השלד

כל הקוד מכאן ואילך נכתב בקובץ **חדש**, `Controllers/StudentsController.cs` — לא ב-`Program.cs` (ש-`AddControllers()`/`MapControllers()` שבו כבר במקום, ראו למעלה).

```csharp
[ApiController]
[Route("api/[controller]")]   // "[controller]" is replaced automatically with "students" (from the class name)
public class StudentsController : ControllerBase
{
    // no Actions yet — just the shell
}
```

`ControllerBase` נותן למחלקה את כל היכולות של Controller (כמו `Ok()`/`NotFound()` שנראה בקרוב). `[Route("api/[controller]")]` בונה את כתובת הבסיס אוטומטית — `[controller]` הוא placeholder שמוחלף בשם המחלקה בלי הסיומת "Controller" (`StudentsController` → `students`, אז כל ה-Actions בפנים יהיו תחת `api/students`).

### שלב 2: Action ראשון — GET לכל הרשימה

```csharp
private static List<Student> _students = new() { new(1, "Dana", 25) };

[HttpGet]   // GET api/students
public IActionResult GetAll()
{
    return Ok(_students);   // 200 + the list as JSON
}
```

`[HttpGet]` בלי פרמטר מתייג את המתודה כ-`GET api/students` — בדיוק כתובת הבסיס מה-`[Route]`. `IActionResult` נותן שליטה על Status Code + Body יחד: `Ok(data)` = `200` עם `data` כ-JSON.

### שלב 3: Action שני — GET למשאב בודד

```csharp
[HttpGet("{id}")]   // GET api/students/5
public IActionResult GetById(int id)
{
    var student = _students.FirstOrDefault(s => s.Id == id);
    if (student == null) return NotFound();   // 404
    return Ok(student);   // 200
}
```

`[HttpGet("{id}")]` מוסיף `{id}` **אחרי** כתובת הבסיס — בדיוק כמו ה-Route Parameter מהשיעור הקודם, רק שעכשיו הוא חלק מ-attribute במקום פרמטר ל-`MapGet`. `NotFound()` מחזיר `404` — אותה מוסכמה בדיוק, קוד מאורגן אחרת.

### שלב 4: Action שלישי — יצירה עם Body

```csharp
[HttpPost]   // POST api/students
public IActionResult Create([FromBody] Student newStudent)
{
    _students.Add(newStudent);
    return CreatedAtAction(nameof(GetById), new { id = newStudent.Id }, newStudent);   // 201
}
```

`[FromBody]` אומר ל-ASP.NET Core "קח את ה-JSON מגוף הבקשה, בצע Deserialize אוטומטי ל-`Student`". `CreatedAtAction` הוא הגרסה-ל-Controllers של `Results.Created` — בונה אוטומטית את כתובת ה-`Location` (מצביעה ל-Action בשם `GetById`, עם ה-`id` החדש), בלי לכתוב את הכתובת ידנית.

### שלב 5: Action רביעי — מחיקה

```csharp
[HttpDelete("{id}")]   // DELETE api/students/5
public IActionResult Delete(int id)
{
    var student = _students.FirstOrDefault(s => s.Id == id);
    if (student == null) return NotFound();
    _students.Remove(student);
    return NoContent();   // 204 — succeeded, no content to return
}
```

אותה מוסכמה שוב: מוצאים לפי `id`, `404` אם לא נמצא, אחרת מבצעים ומחזירים `204`.

**התוצאה:** כל 4 ה-Actions יחד יוצרים `StudentsController` שלם, מאורגן במחלקה אחת נפרדת — לא עוד שורות ב-`Program.cs`.

## הסבר עיקרי

`[Route("api/[controller]")]` עקבי אוטומטית בין Controllers — אם ה-Controller נקרא `ProductsController`, ה-URL הבסיס יהיה `api/products` בלי לכתוב אותו ידנית שוב.

`IActionResult` נותן שליטה על Status Code + Body יחד, בדיוק המוסכמה מ-REST שלמדנו בשיעור ה-HTTP — כל Action בוחר את התשובה המתאימה במפורש, לא רק מחזיר ערך גולמי.

`[FromBody]` מחבר ל-JSON מהיחידה הקודמת — כשלקוח שולח `POST` עם JSON ב-Body, ASP.NET Core מבצע Deserialize אוטומטי, בדיוק `JsonSerializer.Deserialize<Student>` שכבר תרגלנו, רק שה-Framework עושה את זה מאחורי הקלעים.

## יתרונות

Controllers מארגנים endpoints קשורים תחת מחלקה אחת ברורה (SRP); attributes (`[HttpGet]` וכו') הופכים את המיפוי Method↔URL↔קוד לגלוי וקריא ישירות מעל כל מתודה; `IActionResult` מאפשר תשובה מפורשת ומתאימה לכל תרחיש.

## חסרונות

Controller "שמן" מדי (יותר מדי Actions, יותר מדי לוגיקה עסקית ישירות בתוכו) חוזר לבעיית SRP — לוגיקה עסקית כבדה צריכה לעבור לשכבת Service נפרדת (יילמד בשיעור ה-DAL); attributes רבים על כל מתודה עלולים להעמיס ויזואלית על קוד פשוט.

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

• Controller יושב בקובץ נפרד תחת `Controllers/`; `Program.cs` מפעיל אותו עם `AddControllers()` + `MapControllers()` ולעולם לא מזכיר אותו בשמו

• אף אחד לא "קורא" ל-Controller — ASP.NET Core סורק אותו בהפעלה, בונה טבלת routing מה-attributes, ומפעיל את ה-Action המתאים לכל בקשה נכנסת (אותה זרימה כמו `app.MapGet`, רק מגולה אוטומטית)

• `[Route("api/[controller]")]` בונה URL בסיס אוטומטית משם המחלקה

• `[HttpGet]`/`[HttpPost]` וכו' מתייגים כל Action עם ה-Method שלה

• `IActionResult` (`Ok`/`NotFound`/`CreatedAtAction`) שולט על Status Code + Body יחד

• `[FromBody]` מבצע Deserialize אוטומטי מ-JSON לאובייקט C#

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

• לשכוח `[HttpGet("{id}")]` ולכתוב שני `[HttpGet]` ללא הבחנה — קונפליקט routing

• להחזיר ערך גולמי (`return student;`) במקום `IActionResult` עם Status Code מפורש כשצריך שליטה על תגובת השגיאה

• לשים לוגיקה עסקית כבדה ישירות ב-Controller במקום להעביר לשכבת Service — הפרת SRP

• לשכוח `AddControllers()` או `MapControllers()` ב-`Program.cs` — ה-Controller מתקמפל בלי שגיאה, אבל כל בקשה מחזירה `404`; קל לבזבז זמן על חיפוש הבאג ב-Controller עצמו כשהוא בכלל ב-`Program.cs`

## סיכום

Controllers מארגנים endpoints קשורים תחת מחלקה אחת, עם `[Route]` שבונה URL בסיס אוטומטית ו-`[HttpGet]`/`[HttpPost]`/וכו' שמתייגים כל Action עם ה-Method שלה. בנינו `StudentsController` שלם שלב אחר שלב — משלד ריק, דרך GET-הכול, GET-בודד, POST ו-DELETE — כשכל Action הוא בדיוק אותה לוגיקה שכתבנו בשיעור הקודם כ-`app.MapGet` בודד, רק מאורגנת במקום ברור ומסודר.

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

[Microsoft Learn — Controller-based APIs](https://learn.microsoft.com/aspnet/core/web-api/)

---

## תרגילים

### תרגיל 1 — Controller בסיסי

**המשימה:** צרו `ProductsController` עם `[HttpGet]` שמחזיר `List<Product>` קבועה מראש (בזיכרון), ו-`[HttpGet("{id}")]` שמחזיר מוצר בודד או `404`.

**בדיקה:** `GET api/products` מחזיר את כל המוצרים; `GET api/products/99` (לא קיים) מחזיר `404`.

### תרגיל 2 — Create עם IActionResult

**המשימה:** הוסיפו `[HttpPost]` ל-`ProductsController` שמקבל `[FromBody] Product`, מוסיף אותו לרשימה, ומחזיר `CreatedAtAction`.

**בדיקה:** בקשת `POST` עם JSON תקין מחזירה `201` עם המוצר החדש ב-Body.

---

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

**המשימה:** בנו `TasksController` מלא לניהול משימות (Task Manager API) — הפרויקט שילווה את שאר יחידות ה-Backend בהמשך הקורס.

**דרישות:**
1. CRUD מלא: `GET` (רשימה + בודד), `POST`, `PUT`, `DELETE` — כל אחד עם `[Http...]` מתאים
2. כל Action מחזיר `IActionResult` עם Status Code הגיוני (`200`/`201`/`204`/`404`)
3. `PUT`/`DELETE` על משימה לא-קיימת מחזירים `404`, לא קורסים

**בדיקה:** רצף מלא של יצירה→קריאה→עדכון→מחיקה על אותה משימה עובד מקצה לקצה, עם Status Codes נכונים בכל שלב; ניסיון על ID לא-קיים בכל אחת מהפעולות מחזיר `404` מסודר.

## מה בפרק הבא

בפרק הבא — **Validation**: איך מוודאים שהנתונים שמגיעים בבקשה תקינים **לפני** שהם מגיעים ללוגיקה העסקית, עם Model Binding ו-DataAnnotations.
