# Validation — Model Binding ו-DataAnnotations📖 שיעור

## מה זה?

ה-`Create` Action מהשיעור הקודם מקבל `[FromBody] Student newStudent` ופשוט מוסיף אותו — בלי לבדוק שום דבר:

```csharp
[HttpPost]
public IActionResult Create([FromBody] Student newStudent)
{
    _students.Add(newStudent);   // no check at all — anything gets in
    return CreatedAtAction(nameof(GetById), new { id = newStudent.Id }, newStudent);
}
```

מה קורה אם שולחים `POST` עם `Name` ריק, או `Age = -5`? התשובה: זה פשוט נכנס למערכת בלי שום התנגדות — `_students` עכשיו מכיל תלמיד בלי שם ובגיל שלילי. אין כלום שעוצר את זה. השיעור הזה בונה, שלב אחר שלב, את הדרך הנכונה למנוע את זה.

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

• Model Binding — התהליך שממיר JSON נכנס לאובייקט C# אוטומטית (המשך ישיר ל-`[FromBody]` מהשיעור הקודם)

• DataAnnotations — attributes על Properties שמגדירים כללי תקינות: `[Required]`, `[Range]`, `[StringLength]`, `[EmailAddress]`

• `ModelState.IsValid` — בודק אם כל כללי ה-DataAnnotations עמדו בבקשה הנוכחית

• `[ApiController]` — attribute על ה-Controller שמפעיל ולידציה **אוטומטית** — מחזיר `400` בעצמו אם המודל לא תקין, בלי בדיקה ידנית בכל Action

## בונים ולידציה שלב אחר שלב

### שלב 1: הדרך הידנית והמייגעת — בדיקת if לכל שדה

הפתרון הראשון שעולה לראש: לבדוק כל שדה בעצמנו, בתוך ה-Action.

```csharp
[HttpPost]
public IActionResult Create([FromBody] Student newStudent)
{
    if (string.IsNullOrEmpty(newStudent.Name))
        return BadRequest("Name is required");
    if (newStudent.Name.Length < 2 || newStudent.Name.Length > 50)
        return BadRequest("Name must be between 2 and 50 characters");
    if (newStudent.Age < 0 || newStudent.Age > 120)
        return BadRequest("Age must be between 0 and 120");

    _students.Add(newStudent);
    return CreatedAtAction(nameof(GetById), new { id = newStudent.Id }, newStudent);
}
```

זה **עובד** — אבל תארו לעצמכם `Product` עם 6 שדות, או `Order` עם 10. כל שדה חדש מוסיף עוד `if`, וכל Action (`Create`, `Update`) שמקבל אותו טיפוס צריך לחזור על אותן בדיקות בעצמו. כפילות מסיבית, בדיוק כמו הבעיה שכבר פגשנו לפני ה-Middleware.

### שלב 2: הצהרה במקום בדיקה — DataAnnotations

במקום לבדוק כל שדה בקוד, **מצהירים** את הכלל ישירות על השדה במודל עצמו:

```csharp
class Student
{
    [Required(ErrorMessage = "Name is required")]
    [StringLength(50, MinimumLength = 2, ErrorMessage = "Name must be between 2 and 50 characters")]
    public string Name { get; set; }

    [Range(0, 120, ErrorMessage = "Age must be between 0 and 120")]
    public int Age { get; set; }

    [EmailAddress(ErrorMessage = "Invalid email address")]
    public string Email { get; set; }
}
```

`[Required]`, `[StringLength]`, `[Range]`, `[EmailAddress]` הם בדיוק אותם כללים משלב 1 — רק שהם כתובים **פעם אחת**, על השדה עצמו, ולא בכל Action שמשתמש בו. זה בדיוק הגישה של `enum` מיחידת היסודות: הצהרה ברורה במקום בדיקה ידנית שקל לשכוח.

### שלב 3: עדיין צריך לבדוק — ModelState.IsValid

הצהרת הכללים לבד לא מספיקה — עדיין צריך **מישהו** שיבדוק אם הם התקיימו:

```csharp
[HttpPost]
public IActionResult Create([FromBody] Student newStudent)
{
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);   // 400 with all the validation errors
    }
    _students.Add(newStudent);
    return CreatedAtAction(nameof(GetById), new { id = newStudent.Id }, newStudent);
}
```

`ModelState` הוא תוצאה שה-Framework ממלא אוטומטית אחרי ה-Model Binding — הוא כבר בדק את כל ה-DataAnnotations על `newStudent`, ו-`IsValid` אומר אם כולם עברו. עדיין `if` אחד — אבל רק אחד, לא אחד לכל שדה.

### שלב 4: גם ה-if האחד הזה מיותר — [ApiController]

```csharp
[ApiController]   // Enables automatic validation for the whole Controller
[Route("api/[controller]")]
public class StudentsController : ControllerBase
{
    [HttpPost]
    public IActionResult Create([FromBody] Student newStudent)
    {
        // With [ApiController], if newStudent doesn't meet the DataAnnotations —
        // ASP.NET Core already returned 400 automatically, this code doesn't even run!
        _students.Add(newStudent);
        return CreatedAtAction(nameof(GetById), new { id = newStudent.Id }, newStudent);
    }
}
```

`[ApiController]` על המחלקה כולה הופך את בדיקת `ModelState.IsValid` לאוטומטית — היא רצה **לפני** שקוד ה-Action בכלל מתחיל, בדיוק כמו Middleware ברמה אחרת. אם המודל לא תקין, `[ApiController]` כבר החזיר `400` בעצמו, ואף שורת קוד שלכם לא רצה. זו הסיבה שהקוד הסופי "לא בודק כלום" — הוא לא צריך, ה-Framework כבר בדק.

## הסבר עיקרי

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

חיבור ישיר ל-Zod ביחידת ההוספות (בהמשך הקורס, אם רלוונטי) — DataAnnotations הם ל-C# בדיוק מה ש-Zod (בעולם ה-JavaScript/TypeScript) עושה שם — שכבת ולידציה מוצהרת שרצה **לפני** הלוגיקה העסקית. הרעיון זהה, גם אם התחביר שונה בין השפות.

## יתרונות

DataAnnotations הצהרתיות וקצרות — כלל תקינות אחד = attribute אחד, קריא ישירות מעל השדה; `[ApiController]` הופך את בדיקת התקינות לאוטומטית, בלי `if` ידני בכל Action; הודעות שגיאה מותאמות אישית (`ErrorMessage`) נותנות פידבק ברור ללקוח.

## חסרונות

DataAnnotations מוגבלות לכללים פשוטים יחסית — ולידציה מורכבת (למשל "תאריך סיום חייב להיות אחרי תאריך התחלה", תלוי בשני שדות יחד) דורשת קוד נוסף (`IValidatableObject` או ולידציה ידנית); יותר מדי attributes על מודל אחד עלולים להעמיס ויזואלית.

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

• DataAnnotations = attributes על Properties שמגדירים כללי תקינות (`[Required]`, `[Range]`, `[StringLength]`)

• `[ApiController]` מפעיל ולידציה אוטומטית — מחזיר `400` בעצמו אם `ModelState` לא תקין

• Model Binding ממיר JSON נכנס לאובייקט C# אוטומטית, לפני שהוולידציה בכלל רצה

• ולידציה מורכבת (תלוית-כמה-שדות) דורשת מעבר ל-`IValidatableObject` או קוד ידני

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

• לשכוח `[ApiController]` ואז לתהות למה מודל לא-תקין "עובר" בלי שגיאה

• לכתוב בדיקת `if` ידנית לכל שדה במקום DataAnnotations — כפילות מיותרת ופחות קריא

• לצפות ש-DataAnnotations יטפלו בכללים עסקיים מורכבים (תלות בין שדות) — הן מוגבלות לכללים פשוטים per-field

## סיכום

עברנו מסע של ארבעה שלבים: בדיקות `if` ידניות ומייגעות לכל שדה (עובד, אבל כפילות מסיבית) → הצהרת אותם כללים כ-DataAnnotations על המודל (פעם אחת, לא בכל Action) → `ModelState.IsValid` כבדיקה מרוכזת (עדיין `if` אחד) → `[ApiController]` שמבצע גם את הבדיקה האחת הזו אוטומטית. התוצאה הסופית: שכבת הגנה מוקדמת שמבטיחה שרק נתונים תקינים מגיעים ללוגיקה העסקית, כמעט בלי קוד נראה-לעין.

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

[Microsoft Learn — Model validation in ASP.NET Core](https://learn.microsoft.com/aspnet/core/mvc/models/validation)

---

## תרגילים

### תרגיל 1 — DataAnnotations בסיסיות

**המשימה:** הוסיפו ל-`Product` (מהשיעור הקודם) `[Required]` על `Name` ו-`[Range(0.01, 100000)]` על `Price`. נסו לשלוח `POST` עם `Price = -5`.

**בדיקה:** הבקשה עם `Price` שלילי מקבלת `400` אוטומטית, בלי קוד ידני ב-Action.

### תרגיל 2 — הודעות שגיאה מותאמות

**המשימה:** הוסיפו `ErrorMessage` מותאם אישית לכל אחד מה-attributes בתרגיל 1, ובדקו שהודעת השגיאה שמוחזרת ב-`400` מכילה את הטקסט שהגדרתם.

**בדיקה:** תשובת ה-`400` כוללת את הודעות השגיאה המותאמות-אישית שלכם, לא רק טקסט גנרי.

---

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

**המשימה:** הוסיפו ולידציה מלאה למודל `Task` מ-`TasksController` (מהפרויקט המסכם הקודם).

**דרישות:**
1. `[Required]` על `Title`, עם `[StringLength]` (מקסימום 100 תווים)
2. שדה `Priority` (int) עם `[Range(1, 5)]`
3. `[ApiController]` מופעל על ה-Controller — ולידציה אוטומטית, בלי `if` ידני ב-`Create`

**בדיקה:** בקשת `POST` עם `Title` ריק או `Priority` מחוץ לטווח מקבלת `400` אוטומטית עם הודעה ברורה; בקשה תקינה לגמרי נוצרת בהצלחה כרגיל.

## מה בפרק הבא

בפרק הבא — **Dependency Injection**: איך ASP.NET Core "מזריק" תלויות (כמו שירותים ו-Repositories) אוטומטית, במקום ליצור אותן ידנית בכל מקום — DIP מהיחידה הקודמת, בתשתית אמיתית.
