হাসিব, তোমার দেওয়া লেকচার ট্রান্সক্রিপ্টটির বিস্তারিত বিশ্লেষণ নিচে দেওয়া হলো। এই লেকচারে Model Validation-এর আরও কিছু গুরুত্বপূর্ণ Attributes (যেমন: [Display], [StringLength], [Range]) এবং Error Message-কে কীভাবে ডাইনামিক করা যায়, তা নিয়ে আলোচনা করা হয়েছে।


📝 Quick Summary for Revision

ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য মূল পয়েন্টগুলো নিচে লিস্ট করা হলো:

  • Dynamic Error Messages: ErrorMessage-এর ভেতরে {0} ব্যবহার করলে ফ্রেমওয়ার্ক অটোমেটিকভাবে সেখানে Property-র নাম বসিয়ে দেয় (string.Format এর মতো)।
  • [Display] Attribute: C# Property-র নামে স্পেস দেওয়া যায় না। তাই Error Message-এ সুন্দরভাবে (স্পেসসহ) নাম দেখানোর জন্য [Display(Name = "Your Name")] ব্যবহার করা হয়।
  • [StringLength] Attribute: String-এর Character limit (সর্বোচ্চ এবং সর্বনিম্ন) সেট করতে ব্যবহার করা হয়। এখানে {1} হলো Max length এবং {2} হলো Min length।
  • [Range] Attribute: Numeric ফিল্ডের (যেমন: int, double) একটি নির্দিষ্ট সীমা (Min এবং Max value) সেট করতে ব্যবহার করা হয়। এখানে {1} হলো Min value এবং {2} হলো Max value।
  • Execution Flow: একটি Request আসলে ব্যাকএন্ডে সিরিয়ালটি হলো: Routing ➡️ Model Binding ➡️ Model Validation ➡️ Action Method

🧠 Comprehensive Breakdown

এখানে লেকচারের প্রতিটি কনসেপ্ট বিস্তারিতভাবে এবং সহজবোধ্যভাবে ব্যাখ্যা করা হলো।

1. Dynamic Error Messages with string.Format [Priority: 8/10]

Why do we need this? আগের লেকচারে আমরা হার্ডকোডেড Error Message দিয়েছিলাম (যেমন: “Person Name is required”)। কিন্তু ডাইনামিকভাবে মেসেজ জেনারেট করাটা বেস্ট প্র্যাকটিস। ASP.NET Core ইন্টারনালি C#-এর string.Format ব্যবহার করে।

তুমি যদি ErrorMessage-এ {0} লেখো, তবে রানটাইমে ফ্রেমওয়ার্ক অটোমেটিকভাবে ওই {0} এর জায়গায় Property-র নাম (যেমন: PersonName) বসিয়ে দেবে।

public class Person
{
    // Runtime message will be: "PersonName cannot be empty or null"
    [Required(ErrorMessage = "{0} cannot be empty or null")]
    public string? PersonName { get; set; }
}
 

2. The [Display] Attribute [Priority: 7/10]

Why do we need this? C#-এর রুলস অনুযায়ী কোনো ভেরিয়েবল বা Property-র নামের মাঝখানে স্পেস (Space) দেওয়া যায় না (যেমন: Person Name লেখা যাবে না, লিখতে হবে PersonName)। কিন্তু আমরা যখন ইউজারের কাছে Error Message পাঠাব, তখন “PersonName cannot be empty” দেখতে ভালো লাগে না। আমরা চাই “Person Name cannot be empty” দেখাতে। এই সমস্যার সমাধান করে [Display] Attribute।

[Display(Name = "Custom Name")] ব্যবহার করলে, {0} তখন অরিজিনাল Property নামের বদলে এই কাস্টম নামটিকেই রিড করে।

using System.ComponentModel.DataAnnotations;
 
public class Person
{
    [Display(Name = "Person Name")]
    [Required(ErrorMessage = "{0} cannot be empty or null")]
    public string? PersonName { get; set; }
}
// Output Error: "Person Name cannot be empty or null"
 

3. The [StringLength] Attribute [Priority: 9/10]

Why do we need this? নাম, পাসওয়ার্ড বা ফোন নাম্বারের মতো String প্রোপার্টির সাইজ কন্ট্রোল করার জন্য এটি ব্যবহার করা হয়। এটি দিয়ে তুমি বলে দিতে পারো একটি স্ট্রিং সর্বোচ্চ কত ক্যারেক্টার হতে পারবে এবং সর্বনিম্ন কত ক্যারেক্টার হতে পারবে।

Index Positioning:

  • {0} = Property Name বা Display Name
  • {1} = Maximum Length (এটি সবসময় index 1 এ থাকে)
  • {2} = Minimum Length (এটি সবসময় index 2 তে থাকে)
public class Person
{
    [Display(Name = "Person Name")]
    [StringLength(40, MinimumLength = 3, ErrorMessage = "{0} should be between {2} and {1} characters long.")]
    public string? PersonName { get; set; }
}
// If user inputs "ab" (which is 2 characters)
// Output Error: "Person Name should be between 3 and 40 characters long."
 

4. The [Range] Attribute [Priority: 9/10]

Why do we need this? [StringLength] শুধু String-এর ক্যারেক্টার গোনে। কিন্তু Numeric ভ্যালুর (যেমন: int, double, short) মান চেক করার জন্য [Range] ব্যবহার করতে হয়। ধরো, তুমি চাও কোনো প্রডাক্টের দাম (Price) যেন 0 থেকে 999 এর মধ্যেই থাকে।

Index Positioning:

  • {0} = Property Name বা Display Name
  • {1} = Minimum Value (এটি সবসময় index 1 এ থাকে)
  • {2} = Maximum Value (এটি সবসময় index 2 তে থাকে)
public class Person
{
    [Display(Name = "Product Price")]
    [Range(0, 999, ErrorMessage = "{0} should be between ${1} and ${2}.")]
    public double? Price { get; set; }
}
// If user inputs "-50"
// Output Error: "Product Price should be between $0 and $999."
 

5. Pipeline Execution Flow [Priority: 10/10]

লেকচারের শেষে পুরো লাইফসাইকেলটা রিক্যাপ করা হয়েছে। ব্যাকএন্ডে কাজগুলো ঠিক এই সিরিয়ালে এক্সিকিউট হয়:

  1. Routing: রিকোয়েস্টটি কোন Controller-এর কোন Action Method-এ যাবে তা নির্ধারণ করে।
  2. Model Binding: Form Data বা URL থেকে ডেটা নিয়ে অবজেক্ট (যেমন Person) তৈরি করে।
  3. Model Validation: ওই অবজেক্টের ওপর বসানো Attributes ([Required], [Range]) চেক করে।
  4. Action Method Execution: সবশেষে তোমার লেখা Controller-এর ভেতরের কোড রান করে।

🚀 Best Practices & .NET 10 / C# 13 Updates

যেহেতু তুমি .NET ইকোসিস্টেম নিয়ে কাজ করছো (এবং লেকচারটি .NET 6 এর কনটেক্সটে), তাই আধুনিক .NET 8/9/10-এর কিছু দারুণ আপডেট তোমার জেনে রাখা উচিত:

1. Use [Length] instead of [StringLength] (Modern C#)

.NET 8 থেকে String বা Collection-এর লেন্থ ভ্যালিডেট করার জন্য নতুন [Length] Attribute আনা হয়েছে। এটি [StringLength]-এর চেয়ে দেখতে অনেক বেশি ক্লিন এবং লজিক্যাল।

// Old Way (Transcript approach)
[StringLength(40, MinimumLength = 3)]
public string? PersonName { get; set; }
 
// Modern .NET 8/10 Way
[Length(3, 40, ErrorMessage = "{0} must be exactly between {1} and {2} characters.")]
public string? PersonName { get; set; }
 

2. Exclusive Range Bounds in .NET 8+

আগে [Range(0, 100)] মানে ছিল 0 থেকে 100 পর্যন্ত (inclusive)। কিন্তু অনেক সময় আমাদের দরকার হয় “0 এর চেয়ে বড় কিন্তু 100 এর চেয়ে ছোট” (Exclusive)। নতুন .NET-এ এটি করা যায়:

// Modern .NET approach with Exclusive bounds
[Range(0, 999, MinimumIsExclusive = true, MaximumIsExclusive = false, ErrorMessage = "Price must be strictly greater than 0 and up to 999.")]
public double? Price { get; set; }
 

3. Global Error Handling

প্রতিটি Attribute-এ হার্ডকোড করে ErrorMessage লেখার চেয়ে FluentValidation লাইব্রেরি ব্যবহার করাটা ইন্ডাস্ট্রি স্ট্যান্ডার্ড Best Practice। এতে Model Class ক্লিন থাকে এবং Validation Logic আলাদা ফাইলে ম্যানেজ করা যায়। তবে বেসিক জানার জন্য Data Annotations (যেগুলো এই লেকচারে দেখানো হলো) শেখাটা অত্যন্ত জরুরি।