স্বাগতম! আজকে আমরা আপনার আউটলাইনের #311: Custom Exceptions টপিকটি শিখতে যাচ্ছি।
গত লেকচারে আমরা Exception Handling Middleware শিখেছি। কিন্তু আপনি কি জানেন, বড় বড় প্রোজেক্টে ডিফল্ট Exception (যেমন ArgumentException) ব্যবহার করলে ডিবাগিং করা কতটা কঠিন হতে পারে? আজকে আমরা শিখবো কীভাবে নিজেদের প্রয়োজন অনুযায়ী Domain-Specific Custom Exception তৈরি করতে হয়, যা আপনার লগিং এবং ডিবাগিং এক্সপেরিয়েন্সকে অনেক সহজ করে দেবে।
📝 Quick Summary (For Future Revision)
- The Problem: সব জায়গায় ডিফল্ট
ArgumentExceptionবাSystem.Exceptionব্যবহার করলে লগে দেখে বোঝা কঠিন হয় যে ঠিক কোন মডিউল বা লজিকে এরর হয়েছে। - The Solution: নির্দিষ্ট সিনারিওর জন্য Custom Exception Class তৈরি করা (যেমন:
InvalidPersonIDException)। - Project Structure: Exception-গুলোর জন্য একটি আলাদা Class Library প্রোজেক্ট তৈরি করা বেস্ট প্র্যাকটিস, যাতে এটি যেকোনো লেয়ারে (UI, Service, etc.) রেফারেন্স করা যায়।
- The 3 Constructors Rule: Microsoft-এর রেকমেন্ডেশন অনুযায়ী একটি Custom Exception-এ কমপক্ষে ৩টি Constructor থাকতে হবে (Parameterless, Message, এবং Message + InnerException)।
- Integration: এই Custom Exception-গুলো থ্রো করা হলে আমাদের আগের তৈরি করা Global Exception Handling Middleware সহজেই তা ক্যাচ করে ইউজারকে সুন্দর রেসপন্স দিতে পারে।
🧠 Comprehensive Breakdown
১. The “Why”: Custom Exception কেন প্রয়োজন? (Priority: 9/10)
ধরে নিন, আপনার UpdatePerson মেথডে একটি PersonID আসলো যা ডাটাবেসে নেই। আপনি চাইলে একটি সাধারণ ArgumentException থ্রো করতে পারেন। কিন্তু সমস্যা হলো, একটি Large-scale codebase-এ শত শত মেথডে হাজার হাজার প্যারামিটার থাকে। যখন লগ ফাইলে একটি ArgumentException জমা হবে, তখন আপনার বুঝতে কষ্ট হবে যে এটি ঠিক কোন মেথডের, কোন প্যারামিটারের জন্য হয়েছে।
এর বদলে যদি লগে লেখা থাকে InvalidPersonIDException, তখন আপনি এক সেকেন্ডেই বুঝে যাবেন ঠিক কোথায় সমস্যা হয়েছে! এটি মূলত Domain-specific বা Scenario-specific error হ্যান্ডেল করার একটি চমৎকার উপায়।
২. Exceptions-এর জন্য আলাদা Class Library তৈরি (Priority: 5/10)
ভিডিওতে বলা হয়েছে Exception-গুলোর জন্য একটি আলাদা Class Library তৈরি করতে।
- কেন? কারণ Exception ক্লাসগুলো অ্যাপ্লিকেশন লেয়ার, সার্ভিস লেয়ার বা ইনফ্রাস্ট্রাকচার লেয়ার—যেকোনো জায়গায় লাগতে পারে। আলাদা প্রোজেক্ট থাকলে Circular Dependency-এর ঝামেলা ছাড়াই যেকোনো জায়গায় এটি রেফারেন্স করা যায়।
- (Shortcut Tip: Visual Studio-তে নতুন প্রোজেক্ট অ্যাড করতে
Ctrl + Shift + Nচাপুন। Visual Studio Code-এ Command Palette ওপেন করতেCtrl + Shift + Pচেপে.NET: New Projectলিখে Class Library সিলেক্ট করুন।)
৩. Custom Exception Class তৈরি করার নিয়ম (Priority: 10/10)
যেকোনো Custom Exception তৈরি করার সময় اسے অবশ্যই কোনো না কোনো Built-in Exception ক্লাস থেকে ইনহেরিট (Inherit) করতে হবে। যেমন: Exception, ArgumentException, বা InvalidOperationException।
Microsoft-এর গাইডলাইন অনুযায়ী, একটি Custom Exception-এ কমপক্ষে ৩টি কনস্ট্রাক্টর থাকতে হয়:
- Parameterless Constructor: যা কোনো প্যারামিটার নেবে না।
- Message Constructor: যা শুধু Error Message রিসিভ করে Base Class-এ পাঠাবে।
- Message & InnerException Constructor: যা Message-এর পাশাপাশি আগের কোনো Exception (InnerException) রিসিভ করবে।
Code Implementation (.NET 6 / Classic Approach):
using System;
namespace Exceptions
{
// Inheriting from ArgumentException
public class InvalidPersonIDException : ArgumentException
{
// 1. Parameterless Constructor
public InvalidPersonIDException() : base()
{
}
// 2. Constructor with Exception Message
public InvalidPersonIDException(string message) : base(message)
{
}
// 3. Constructor with Message and InnerException
public InvalidPersonIDException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
৪. Service-এ Custom Exception Throw করা (Priority: 8/10)
এখন আমরা আমাদের সার্ভিস লেয়ারে ডিফল্ট Exception-এর বদলে আমাদের তৈরি করা Custom Exception ব্যবহার করবো।
Code Implementation:
public async Task<PersonResponse> UpdatePerson(PersonUpdateRequest personUpdateRequest)
{
// ... validation logic ...
var matchingPerson = await _personsRepository.GetPersonByPersonID(personUpdateRequest.PersonID);
if (matchingPerson == null)
{
// Throwing our Domain-Specific Custom Exception
throw new InvalidPersonIDException("Given Person ID does not exist.");
}
// ... update logic ...
}
৫. Controller ও Middleware-এর ফ্লো (Priority: 7/10)
ভিডিওতে দেখানোর জন্য Controller-এ ইচ্ছে করে একটি ফেক (Fake) Guid জেনারেট করে সার্ভিসে পাঠানো হয়েছে।
যখন ফেক আইডি নিয়ে সার্ভিস কল হয়, সার্ভিস InvalidPersonIDException থ্রো করে। যেহেতু আমরা গত লেকচারে Request Pipeline-এর শুরুতে Exception Handling Middleware বসিয়েছিলাম, তাই এই Custom Exception-টিও ঠিকঠাক ওই Middleware-এর catch ব্লকে গিয়ে ধরা পড়ে এবং ইউজার একটি 500 Status Code সহ সুন্দর মেসেজ দেখতে পায়।
🚀 Modern .NET Update (Smarter & Updated)
ভিডিওতে দেখানো প্রসেসটি পারফেক্ট এবং এখনো ব্যাপকভাবে ব্যবহৃত হয়। তবে, C# 12 (যা .NET 8 এবং .NET 10-এ সাপোর্টেড)-এ Primary Constructors ফিচার এসেছে। এটি ব্যবহার করে আমরা Custom Exception Class-কে আরও অনেক ছোট এবং ক্লিন করে লিখতে পারি।
Modern C# 12+ Implementation:
Primary Constructor ব্যবহার করলে বারবার public মেথড লিখে base কল করার বয়লারপ্লেট কোড কমে যায়।
using System;
namespace Exceptions
{
// Look how clean this is using C# 12!
public class InvalidPersonIDException(string message = "Invalid Person ID", Exception? innerException = null)
: ArgumentException(message, innerException)
{
// No need to write 3 separate constructors manually.
// Default parameters handle the Parameterless, Message only, and Message+InnerException scenarios!
}
}
🏆 Best Practices (Custom Exceptions)
- Meaningful Naming: Custom Exception-এর নামের শেষে সবসময়
Exceptionশব্দটি যুক্ত করুন (যেমন:InvalidPersonIDException,DatabaseConnectionException)। - Inherit from the closest Base Class: সরাসরি
System.Exceptionথেকে ইনহেরিট করার চেয়ে, আপনার এররের সাথে সবচেয়ে বেশি মেলে এমন ক্লাস থেকে ইনহেরিট করুন। প্যারামিটারের সমস্যা হলেArgumentExceptionব্যবহার করুন। - Preserve InnerException: যখন আপনি কোনো ডাটাবেস এরর (যেমন SqlException) ক্যাচ করে নতুন Custom Exception থ্রো করবেন, তখন অবশ্যই ডাটাবেসের এররটিকে
innerExceptionহিসেবে পাস করবেন। নাহলে আসল সমস্যার ট্রেস হারিয়ে যাবে।
- Example:
throw new InvalidPersonIDException("Error updating person.", sqlException);