# CRUD בסיסי — Minimal APIs📖 שיעור

## מה זה?

בשיעור הקודם למדנו איך Request ו-Response בנויים, מה כל Method אומר, ומה המשמעות של כל Status Code — אבל עדיין לא כתבנו שורת קוד אחת שבאמת מגיבה לבקשה. השיעור הזה סוגר את הפער: לוקחים בדיוק את מה שנלמד (GET/POST/PUT/DELETE, 200/201/404) והופכים אותו לקוד עובד — **CRUD** (Create, Read, Update, Delete) בסיסי על משאב אחד, ישירות ב-`Program.cs`.

לאורך השיעור נבנה את זה **שלב אחר שלב**, כשכל שלב מוסיף בדיוק דבר אחד חדש. בסוף תדעו לא רק "להעתיק דוגמה", אלא להחליט בעצמכם — לכל שדה מידע חדש שאתם צריכים לקבל מהלקוח — **איפה** הוא צריך להופיע בבקשה: בתוך הכתובת עצמה (Route), אחרי סימן שאלה (Query String), או בתוך הגוף (Body). זו בדיוק השאלה שממשיכה ללוות כל מפתח Backend, בכל endpoint שהוא כותב.

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

• Endpoint — צירוף של Method+URL שממופה לקוד שמטפל בו (למשל `app.MapGet("/api/students", ...)`)

• Route Parameter — חלק **מהכתובת עצמה** שמזהה משאב ספציפי — `{id}` בתוך `/api/students/{id}` אומר "בדיוק התלמיד הזה, לא אחר"

• Query String Parameter — מידע אופציונלי אחרי סימן שאלה בכתובת, לסינון/מיון/הגבלה של רשימה — למשל `/api/students?minAge=20` אומר "כל התלמידים, אבל רק מעל גיל 20"

• Body — תוכן הבקשה עצמו, בפורמט JSON — משמש כשצריך לשלוח אובייקט שלם (ליצירה או עדכון), לא רק ערך בודד

• In-Memory Store — רשימה פשוטה בזיכרון (לא DB אמיתי עדיין) לשמירת נתונים בין בקשות, לצורך הדוגמה בשיעור הזה בלבד

## הסבר עיקרי — בונים CRUD שלב אחר שלב

### שלב 1: GET שמחזיר את כל הרשימה

הפעולה הכי פשוטה שיש: לבקש את כל התלמידים, בלי לציין שום פרט נוסף.

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

var students = new List<Student> { new(1, "Dana", 25), new(2, "Avi", 19) };

app.MapGet("/api/students", () => students);

app.Run();

record Student(int Id, string Name, int Age);
```

`app.MapGet` רושם endpoint: "כשמגיעה בקשת `GET` לכתובת `/api/students`, תריץ את הפונקציה הזו ותחזיר את מה שהיא מחזירה". ASP.NET Core הופך את `students` אוטומטית ל-JSON.

### שלב 2: GET משאב בודד — מציגים Route Parameter

מה אם רוצים **רק** את התלמיד עם ID מספר 5, לא את כל הרשימה? צריך דרך "לומר" לשרת איזה תלמיד בדיוק מבוקש — וזה בדיוק תפקידו של Route Parameter: חלק משתנה בתוך הכתובת עצמה.

```csharp
app.MapGet("/api/students/{id}", (int id) =>
    students.FirstOrDefault(s => s.Id == id) is { } student
        ? Results.Ok(student)
        : Results.NotFound());
```

`{id}` בכתובת נקשר אוטומטית לפרמטר `int id` בפונקציה — ASP.NET Core "קורא" את החלק הזה מה-URL וממיר אותו לטיפוס C# בעצמו. זה נקרא **Model Binding**, ונחזור אליו עוד בשיעור ה-Validation. שימו לב: אם לא נמצא תלמיד כזה, מחזירים `404` — בדיוק כמו שנלמד בשיעור הקודם.

### שלב 3: סינון עם Query String — מציגים Query String Parameter

עכשיו תרחיש אחר: רוצים את **כל** התלמידים מעל גיל מסוים — לא תלמיד אחד ספציפי, אלא סינון על הרשימה כולה. זה **לא** מתאים ל-Route Parameter (זה לא מזהה משאב יחיד) — בשביל זה יש Query String: מידע אופציונלי שמגיע **אחרי סימן שאלה** בכתובת.

```csharp
app.MapGet("/api/students/search", (int? minAge) =>
{
    var result = minAge.HasValue
        ? students.Where(s => s.Age >= minAge.Value)
        : students;
    return Results.Ok(result);
});
```

קריאה ל-`GET /api/students/search?minAge=20` תחזיר רק תלמידים מגיל 20 ומעלה. שימו לב ל-`int?` (Nullable) — Query String תמיד אופציונלי, אז אם לא סופק `minAge` בכלל, `minAge` יהיה `null` ומחזירים את כל הרשימה. אפשר לשלב כמה פרמטרים עם `&`, למשל `?minAge=20&maxAge=25`.

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

### שלב 4: יצירת משאב חדש — מציגים Body

ליצור תלמיד חדש צריך לשלוח הרבה מידע ביחד (שם, גיל...) — יותר מדי בשביל למקם בכתובת עצמה. בשביל זה יש Body: תוכן הבקשה עצמו, בפורמט JSON.

```csharp
app.MapPost("/api/students", (Student student) =>
{
    students.Add(student);
    return Results.Created($"/api/students/{student.Id}", student);
});
```

ASP.NET Core רואה שהפרמטר `student` הוא טיפוס מורכב (לא `int`/`string` בודד) ומבין אוטומטית: "זה מגיע מה-Body כ-JSON, לא מה-URL". `Results.Created` מחזיר `201`, בדיוק כמו שנלמד — עם כתובת המשאב החדש.

### שלב 5: עדכון — Route Parameter ו-Body יחד

עדכון משאב קיים צריך **את שניהם**: איזה משאב לעדכן (Route Parameter) **וגם** מה הנתונים החדשים (Body).

```csharp
app.MapPut("/api/students/{id}", (int id, Student updated) =>
{
    var index = students.FindIndex(s => s.Id == id);
    if (index == -1) return Results.NotFound();
    students[index] = updated;
    return Results.NoContent();
});
```

`id` מגיע מה-Route (מ-`{id}` בכתובת), `updated` מגיע מה-Body — ASP.NET Core מבין את שניהם בו-זמנית, כל אחד מהמקום הנכון לו, בלי שצריך לציין את זה בעצמכם.

### שלב 6: מחיקה — Route Parameter בלבד

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

```csharp
app.MapDelete("/api/students/{id}", (int id) =>
{
    students.RemoveAll(s => s.Id == id);
    return Results.NoContent();
});
```

### מה עדיין חסר

כל ה-endpoints כאן חיים באותו `Program.cs`, ו-`students` הוא רשימה בזיכרון שנעלמת בכל הפעלה מחדש של השרת. שני החוסרים האלה בדיוק — ארגון endpoints וקביעות הנתונים — הם מה ששני השיעורים הקרובים (Controllers, ואחר כך DAL Pattern) פותרים, אחד בכל פעם.

## יתרונות

קוד מינימלי — כל endpoint שורה עד כמה שורות, בלי טקס מיותר; קשר ישיר וברור בין המוסכמות של HTTP (Methods/Status Codes) לביטוי שלהן בקוד; אפשר לבדוק מיד עם curl/Postman/Swagger, בלי תשתית נוספת.

## חסרונות

כל ה-endpoints ב-`Program.cs` אחד — עובד מצוין לדוגמה קטנה, אבל API עם עשרות endpoints הופך את הקובץ לענק ובלתי-קריא (בדיוק הבעיה ש-Controllers פותרים, בשיעור הקרוב); ה-In-Memory Store נמחק בכל הפעלה מחדש של השרת — לא מתאים לנתונים אמיתיים עד שנגיע לתבנית DAL בהמשך היחידה.

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

• Route Parameter (`{id}`) = מזהה משאב ספציפי, חלק מהכתובת עצמה

• Query String (`?minAge=20`) = סינון/מיון/הגבלה אופציונליים על רשימה, אחרי סימן שאלה

• Body = תוכן מלא בפורמט JSON, לשליחת אובייקט שלם (יצירה/עדכון)

• `Results.Ok`/`NotFound`/`Created`/`NoContent` ממפים ישירות ל-Status Codes המוכרים — 200/404/201/204

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

• להשתמש ב-Route Parameter כשמתכוונים לסינון — למשל `/api/students/{minAge}` במקום `/api/students/search?minAge=20`; זה מבלבל כי `{minAge}` נראה כאילו הוא מזהה משאב ספציפי, אבל הוא לא

• לשכוח ש-Query String תמיד אופציונלי, ולא להשתמש בטיפוס Nullable (`int?`) — התוכנית תיכשל אם הלקוח לא שלח את הפרמטר

• להחזיר תמיד `Results.Ok()` גם כשהמשאב לא נמצא, במקום `Results.NotFound()`

• לשכוח את סוג הפרמטר (לכתוב `id` בלי `int`) ואז ה-Route Parameter לא נקשר כמו שצריך

## סיכום

בשיעור הזה הפכנו את התיאוריה של HTTP לקוד עובד, שלב אחר שלב: GET לכל הרשימה, GET למשאב בודד עם Route Parameter, סינון עם Query String, ויצירה/עדכון/מחיקה עם Body ו-Route Parameter יחד. שלוש הדרכים שמידע מגיע לשרת — Route, Query, Body — הן לא שרירותיות: לכל אחת יש תפקיד ברור ושונה, וההחלטה איפה למקם כל פיסת מידע היא חלק בסיסי מתכנון כל endpoint.

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

[Microsoft Learn — Minimal APIs overview](https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis/overview)

[Microsoft Learn — Parameter binding in Minimal APIs](https://learn.microsoft.com/aspnet/core/fundamentals/minimal-apis/parameter-binding)

---

## תרגילים

### תרגיל 1 — סינון עם Query String

**המשימה:** הוסיפו למשאב `Student` שדה `Grade` (ציון). הוסיפו endpoint `GET /api/students/search` שמקבל Query String אופציונלי `minGrade`, ומחזיר רק תלמידים עם ציון גבוה או שווה לו (אם לא סופק — מחזיר את כולם).

**בדיקה:** `GET /api/students/search?minGrade=60` מחזיר רק תלמידים עוברים; `GET /api/students/search` (בלי פרמטר) מחזיר את כל התלמידים.

### תרגיל 2 — קודי סטטוס נכונים

**המשימה:** בדקו עם curl/Postman את כל 4 ה-endpoints הבסיסיים, כולל תרחישי כישלון (ID שלא קיים ל-GET/PUT/DELETE).

**בדיקה:** `GET`/`PUT`/`DELETE` על ID לא קיים מחזירים `404`; `POST` תקין מחזיר `201` עם `Location` header תקין.

---

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

**המשימה:** ממשו בקוד את ה-API שתכננתם על נייר בשיעור הקודם — `TasksController` בתור endpoints בסיסיים (עדיין לא Controller class — זה בשיעור הבא), עם In-Memory Store.

**דרישות:**
1. משאב `TaskItem` עם `Id`, `Title`, `IsDone`
2. חמשת ה-endpoints הבסיסיים: `GET /api/tasks`, `GET /api/tasks/{id}`, `POST /api/tasks`, `PUT /api/tasks/{id}`, `DELETE /api/tasks/{id}`
3. endpoint נוסף `GET /api/tasks/search` שמקבל Query String אופציונלי `isDone` (true/false) ומחזיר רק משימות שתואמות
4. כל endpoint מחזיר את ה-Status Code הנכון לפי התכנון מהשיעור הקודם

**בדיקה:** כל ה-endpoints עובדים דרך curl/Postman; יצירה ואז קריאה של אותה משימה מחזירה את הנתונים הנכונים; `GET /api/tasks/search?isDone=false` מחזיר רק משימות פתוחות; מחיקה ואז קריאה מחזירה `404`.

## מה בפרק הבא

בפרק הבא — **Middleware Pipeline**: עכשיו שיש קוד אמיתי שמטפל בבקשות, נראה איך ASP.NET Core מעבד כל בקשה נכנסת דרך שרשרת שכבות מסודרת (לוג, הרשאות, טיפול בשגיאות) **לפני** שהיא מגיעה בכלל לקוד שכתבנו כאן.
