হাসিব, তোমার দেওয়া লেকচার ট্রান্সক্রিপ্টটির বিস্তারিত বিশ্লেষণ নিচে দেওয়া হলো। এই লেকচারে Model Validation-এর সবচেয়ে গুরুত্বপূর্ণ টপিকগুলোর একটি— Custom Validation Attribute নিয়ে আলোচনা করা হয়েছে। যখন ফ্রেমওয়ার্কের বিল্ট-ইন Attribute (যেমন: [Required], [Range]) দিয়ে কমপ্লেক্স বা স্পেসিফিক রিকোয়ারমেন্ট পূরণ করা যায় না, তখন আমাদের নিজেদের ভ্যালিডেশন রুলস লিখতে হয়।
📝 Quick Summary for Revision
ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য মূল পয়েন্টগুলো নিচে লিস্ট করা হলো:
- Custom Validation: নিজের মতো ভ্যালিডেশন রুলস বানানোর জন্য একটি ক্লাস তৈরি করে তাকে
ValidationAttributeক্লাস থেকে Inherit করতে হয়। - The Execution Block: ভ্যালিডেশনের মেইন লজিকটি
IsValidনামের একটি মেথডকে Override করে লিখতে হয়। - Success or Failure: ডেটা ভ্যালিড হলে
ValidationResult.Successরিটার্ন করতে হয়, আর ইনভ্যালিড হলেnew ValidationResult("Error Message")রিটার্ন করতে হয়। - Dynamic Data: Constructor-এর মাধ্যমে Model থেকে ডাইনামিক ডেটা (যেমন: Minimum Year) রিসিভ করা যায়।
- Optional Error Message: ডিফল্ট Error Message সেট করা যায়, যা ইউজার কোনো Custom Error Message না দিলে কাজ করবে।
🧠 Comprehensive Breakdown
এখানে লেকচারের প্রতিটি কনসেপ্ট, লজিক এবং কোড ইমপ্লিমেন্টেশন ধাপে ধাপে ব্যাখ্যা করা হলো।
1. The Concept of Custom Validation Attribute [Priority: 10/10]
Why do we need this?
ধরে নাও, আমাদের ফর্মে একটি DateOfBirth ফিল্ড আছে। আমাদের রিকোয়ারমেন্ট হলো, ইউজারের জন্মসাল ২০০০ বা তার আগে হতে হবে। ২০০০ এর পরে (যেমন ২০০২ বা ২০০৫) হলে সেটি ইনভ্যালিড হবে।
ASP.NET Core-এ এমন কোনো বিল্ট-ইন Attribute নেই যা সরাসরি “Minimum Year” চেক করতে পারে। তাই আমাদের নিজেদের একটি Custom Attribute বানাতে হবে।
কিভাবে বানাবো?
যেকোনো ক্লাসকে Custom Attribute বানানোর শর্ত হলো, তাকে অবশ্যই System.ComponentModel.DataAnnotations Namespace-এর অধীনে থাকা ValidationAttribute নামের বেস (Base) ক্লাস থেকে Inherit করতে হবে।
2. Creating the Validator Class [Priority: 10/10]
প্রজেক্টে কোড অর্গানাইজ রাখার জন্য CustomValidators নামে একটি ফোল্ডার তৈরি করে সেখানে MinimumYearValidatorAttribute.cs নামের একটি ক্লাস তৈরি করা হয়েছে।
(Best Practice: যেকোনো Attribute ক্লাসের নামের শেষে Attribute শব্দটি রাখা উচিত, যদিও এটি ব্যবহার করার সময় লিখতে হয় না।)
using System;
using System.ComponentModel.DataAnnotations;
namespace YourProjectName.CustomValidators
{
// Inheriting from ValidationAttribute is MUST
public class MinimumYearValidatorAttribute : ValidationAttribute
{
// We will write the logic here
}
}
3. Overriding the IsValid Method [Priority: 10/10]
ভ্যালিডেশনের মূল লজিকটি কোথায় রান করবে? ValidationAttribute ক্লাসের ভেতরে IsValid নামের একটি ভার্চুয়াল মেথড আছে। আমাদের কাজ হলো এই মেথডটিকে Override করা।
(VS Code Shortcut: ক্লাসের ভেতরে override IsValid টাইপ করে এন্টার চাপলে মেথডের সিগনেচার অটোমেটিকভাবে জেনারেট হয়ে যায়।)
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
// The "value" parameter contains the actual input sent by the user (e.g., "2002-05-10")
if (value != null)
{
// 1. Cast the object to DateTime
DateTime date = (DateTime)value;
// 2. Check the condition (e.g., Year must be less than 2000)
// Wait, if it's less than 2000, it's valid. If it's greater than or equal to 2000, it's an ERROR!
if (date.Year >= 2000)
{
// Invalid Data: Return a ValidationResult with an error message
return new ValidationResult("Minimum year allowed is 1999.");
}
else
{
// Valid Data: Return Success
return ValidationResult.Success;
}
}
// If value is null, we return null (Validation is skipped if the data is empty, use [Required] for null checks)
return null;
}
4. Using the Custom Attribute in the Model [Priority: 8/10]
ক্লাসটি তৈরি করার পর, মডেল ক্লাসে গিয়ে DateOfBirth প্রোপার্টির ওপরে এটি ব্যবহার করতে হবে। এর জন্য প্রথমে CustomValidators Namespace টি ইমপোর্ট করে নিতে হবে।
using YourProjectName.CustomValidators;
public class Person
{
// Applying our newly created attribute!
[MinimumYearValidator]
public DateTime? DateOfBirth { get; set; }
}
Execution Flow: ইউজার যখন রিকোয়েস্ট পাঠাবে, Model Binding-এর পর ফ্রেমওয়ার্ক অটোমেটিকভাবে এই MinimumYearValidator ক্লাসের IsValid মেথডটিকে কল করবে এবং ইউজারের পাঠানো ডেটাটি value প্যারামিটার দিয়ে পাস করে দেবে।
5. Making the Validator Dynamic with Constructors [Priority: 9/10]
উপরে আমরা ২০০০ সালটিকে হার্ডকোড (Hardcode) করে দিয়েছি। কিন্তু রিয়েল-ওয়ার্ল্ড প্রোজেক্টে এটি ডায়নামিক হওয়া উচিত। আমরা চাই [MinimumYearValidator(2005)] লিখলে সেটি ২০০৫ চেক করবে।
এর জন্য আমাদের Custom Attribute ক্লাসে একটি Constructor এবং একটি Property তৈরি করতে হবে।
public class MinimumYearValidatorAttribute : ValidationAttribute
{
// Property to hold the dynamic year
public int MinimumYear { get; set; } = 2000; // Default is 2000
// Parameterless Constructor (Optional but good practice)
public MinimumYearValidatorAttribute() { }
// Parameterized Constructor to accept the year from the Model
public MinimumYearValidatorAttribute(int minimumYear)
{
MinimumYear = minimumYear;
}
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
if (value != null)
{
DateTime date = (DateTime)value;
// Checking against the dynamic MinimumYear property
if (date.Year >= MinimumYear)
{
return new ValidationResult($"Minimum year allowed is {MinimumYear - 1}.");
}
return ValidationResult.Success;
}
return null;
}
}
এখন মডেলে গিয়ে আমরা ডায়নামিকভাবে সাল পাস করতে পারবো:
// Now it checks for year 2005 dynamically!
[MinimumYearValidator(2005)]
public DateTime? DateOfBirth { get; set; }
6. Handling Custom Error Messages and Null Coalescing [Priority: 7/10]
লেকচারের শেষের দিকে বলা হয়েছে যে, ফ্রেমওয়ার্কের বিল্ট-ইন Attribute-গুলোর মতো আমরাও চাইলে ErrorMessage রিসিভ করতে পারি। ErrorMessage প্রোপার্টিটি ValidationAttribute বেস ক্লাসেই তৈরি করা আছে, তাই আমাদের নতুন করে ডিক্লেয়ার করতে হবে না।
আর যদি ইউজার কোনো মেসেজ না দেয়, তবে যেন ডিফল্ট মেসেজ দেখায়, সেটার জন্য ?? (Null Coalescing Operator) ব্যবহার করা হয়েছে।
public class MinimumYearValidatorAttribute : ValidationAttribute
{
public int MinimumYear { get; set; } = 2000;
// Default error message
public string DefaultErrorMessage { get; set; } = "Year should not be greater than {0}";
public MinimumYearValidatorAttribute(int minimumYear)
{
MinimumYear = minimumYear;
}
protected override ValidationResult? IsValid(object? value, ValidationContext validationContext)
{
if (value != null)
{
DateTime date = (DateTime)value;
if (date.Year >= MinimumYear)
{
// Logic: If ErrorMessage (from model) is null, use DefaultErrorMessage.
// string.Format replaces {0} with the MinimumYear
string errorMsg = ErrorMessage ?? DefaultErrorMessage;
return new ValidationResult(string.Format(errorMsg, MinimumYear - 1));
}
return ValidationResult.Success;
}
return null;
}
}
মডেলে ব্যবহার:
// Scenario 1: Will use Default Error Message
[MinimumYearValidator(2005)]
// Scenario 2: Will use Custom Error Message provided by the user
[MinimumYearValidator(2005, ErrorMessage = "You must be born before {0} to register.")]
🚀 Best Practices & .NET 10 / C# 13 Updates
1. Using ValidationContext.MemberName (Best Practice)
Error Message-এ প্রোপার্টির নাম (যেমন: DateOfBirth) ডাইনামিকভাবে দেখানোর জন্য validationContext.MemberName ব্যবহার করাটা Best Practice।
// Inside IsValid method
return new ValidationResult($"The field {validationContext.MemberName} must be before {MinimumYear}.");
2. Modern C# Updates: FluentValidation (Industry Standard) Data Annotation (এই Custom Attribute পদ্ধতি) খুবই চমৎকার, কিন্তু বর্তমান ইন্ডাস্ট্রিতে (বিশেষ করে Clean Architecture বা CQRS প্যাটার্নে) Model Class-এর ভেতরে Attribute বসিয়ে ভ্যালিডেশন করাকে নিরুৎসাহিত করা হয়। কারণ এটি Model-কে নোংরা করে এবং Single Responsibility Principle ব্রেক করে।
এর বদলে .NET 8/9/10 এ FluentValidation লাইব্রেরিটি ইন্ডাস্ট্রি স্ট্যান্ডার্ড হিসেবে ব্যবহৃত হয়। এখানে Model সম্পূর্ণ ক্লিন থাকে এবং ভ্যালিডেশন রুলসগুলো সম্পূর্ণ আলাদা একটি ক্লাসে লেখা হয়।
FluentValidation Example:
// 1. Keep the Model 100% Clean! No attributes!
public class Person
{
public DateTime? DateOfBirth { get; set; }
}
// 2. Create a separate Validator Class
using FluentValidation;
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
// Custom logic written in a much cleaner, chainable way
RuleFor(x => x.DateOfBirth)
.Must(BeAValidYear)
.WithMessage("Minimum year allowed is 1999.");
}
// Custom method for logic
private bool BeAValidYear(DateTime? date)
{
if (!date.HasValue) return true; // Let [Required] handle nulls
return date.Value.Year < 2000;
}
}
পরবর্তী লেকচারগুলোতে তুমি Multiple Properties-এর ওপর কীভাবে Validation করতে হয় তা শিখবে। এই কনসেপ্ট নিয়ে কোনো কনফিউশন থাকলে নির্দ্বিধায় প্রশ্ন করতে পারো!