হ্যালো হাসিব! তুমি এখন Section 12: Dependency Injection-এর অষ্টম লেকচার “Transient, Scoped, Singleton - Part 2” (Lecture 135)-এ আছো।

আগের লেকচারে আমরা Transient, Scoped, এবং Singleton-এর থিওরি শিখেছিলাম। আজকের লেকচারে আমরা কোড লিখে প্রমাণ করে দেখবো যে IoC Container আসলেই এই নিয়মগুলো মেনে অবজেক্ট তৈরি করছে কি না। এটি বোঝার জন্য লেকচারার GUID (Globally Unique Identifier) ব্যবহার করে একটি চমৎকার এক্সপেরিমেন্ট করেছেন।

চলো, পুরো এক্সপেরিমেন্ট এবং ইমপ্লিমেন্টেশনটা ধাপে ধাপে ডিকোড করি।


সারসংক্ষেপ (Quick Revision List)

  • The Goal: Transient, Scoped, এবং Singleton-এর লাইফটাইম থিওরিকে কোডে প্রমাণ করা।
  • The Setup: CitiesService-এ একটি Guid প্রপার্টি তৈরি করে Constructor-এ সেটিতে নতুন একটি Guid (ইউনিক আইডি) সেট করা।
  • The Experiment: HomeController-এ একই ICitiesService তিনবার ইনজেক্ট করা এবং তাদের Guid ভ্যালুগুলো ভিউতে প্রিন্ট করা।
  • Transient Result: ৩টি ইনজেকশনের জন্য ৩টি আলাদা (Different) GUID পাওয়া যাবে। (Per Injection = New Object)।
  • Scoped Result: ১টি রিকোয়েস্টের ভেতরে ৩টি ইনজেকশনের জন্য একই (Same) GUID পাওয়া যাবে। কিন্তু ব্রাউজার রিফ্রেশ দিলে (নতুন রিকোয়েস্ট) নতুন GUID আসবে। (Per Request = Same Object)।
  • Singleton Result: ব্রাউজার যতবারই রিফ্রেশ দেওয়া হোক না কেন, আজীবন একই (Same) GUID পাওয়া যাবে। অ্যাপ রিস্টার্ট দিলেই কেবল নতুন GUID আসবে। (Per Application = Single Object)।

Comprehensive Breakdown

১. GUID সেটআপ করা (Service Instance ID) [Priority: 10/10]

আমাদের প্রমাণ করতে হবে যে, Container কখন নতুন অবজেক্ট বানাচ্ছে আর কখন পুরানো অবজেক্টটাই দিচ্ছে। এর জন্য ICitiesService ইন্টারফেসে একটি প্রপার্টি যোগ করা হলো, যার নাম ServiceInstanceId (টাইপ: Guid)।

Code Implementation (ServiceContracts/ICitiesService.cs):

namespace ServiceContracts
{
    public interface ICitiesService
    {
        Guid ServiceInstanceId { get; } // শুধু Get Accessor
        List<string> GetCities();
    }
}
 

এরপর CitiesService ক্লাসে এই ইন্টারফেসটি ইমপ্লিমেন্ট করা হলো এবং Constructor-এ নতুন একটি Guid জেনারেট করে দেওয়া হলো। Why Constructor? কারণ, જ્યારેই new CitiesService() কল হবে, তখনই Constructor এক্সিকিউট হবে এবং একটি সম্পূর্ণ নতুন ইউনিক আইডি তৈরি হবে। যদি আইডি সেম থাকে, তার মানে নতুন অবজেক্ট তৈরি হয়নি।

Code Implementation (Services/CitiesService.cs):

namespace Services
{
    public class CitiesService : ICitiesService
    {
        private Guid _serviceInstanceId;
        private List<string> _cities;
 
        public Guid ServiceInstanceId 
        { 
            get { return _serviceInstanceId; } 
        }
 
        public CitiesService()
        {
            // যখনই নতুন অবজেক্ট তৈরি হবে, নতুন একটি GUID জেনারেট হবে
            _serviceInstanceId = Guid.NewGuid(); 
            _cities = new List<string>() { "London", "Paris", "New York" };
        }
 
        public List<string> GetCities()
        {
            return _cities;
        }
    }
}
 

২. Controller-এ ৩ বার Service Inject করা (The Experiment) [Priority: 10/10]

এখন আমরা HomeController-এর Constructor-এ ঐ একই ICitiesService তিনবার ইনজেক্ট করবো।

Code Implementation (Controllers/HomeController.cs):

namespace DIExample.Controllers
{
    public class HomeController : Controller
    {
        private readonly ICitiesService _citiesService1;
        private readonly ICitiesService _citiesService2;
        private readonly ICitiesService _citiesService3;
 
        // একই ইন্টারফেস ৩ বার ইনজেক্ট করা হচ্ছে
        public HomeController(ICitiesService citiesService1, ICitiesService citiesService2, ICitiesService citiesService3)
        {
            _citiesService1 = citiesService1;
            _citiesService2 = citiesService2;
            _citiesService3 = citiesService3;
        }
 
        [Route("/")]
        public IActionResult Index()
        {
            // ৩টি সার্ভিসের Instance Id View-তে পাঠানোর জন্য ViewBag-এ সেট করা হচ্ছে
            ViewBag.InstanceId_1 = _citiesService1.ServiceInstanceId;
            ViewBag.InstanceId_2 = _citiesService2.ServiceInstanceId;
            ViewBag.InstanceId_3 = _citiesService3.ServiceInstanceId;
 
            // যেকোনো একটি সার্ভিস থেকে লিস্ট নিয়ে ভিউতে পাঠানো
            List<string> cities = _citiesService1.GetCities();
            return View(cities);
        }
    }
}
 

৩. View-তে GUID প্রিন্ট করা [Priority: 8/10]

Index.cshtml-এ ঐ তিনটি ViewBag-এর ভ্যালু প্রিন্ট করা হলো।

Code Implementation (Views/Home/Index.cshtml):

<h3>Instance ID 1: @ViewBag.InstanceId_1</h3>
<h3>Instance ID 2: @ViewBag.InstanceId_2</h3>
<h3>Instance ID 3: @ViewBag.InstanceId_3</h3>
 
<!-- (নিচে শহরের লিস্ট প্রিন্ট করার লুপ আগের মতোই থাকবে) -->
 

৪. The Results (প্রমাণ) [Priority: 10/10]

এবার Program.cs এ গিয়ে একেকবার একেকটি Lifetime সেট করে অ্যাপ রান করা হলো।

১. Transient (ServiceLifetime.Transient):

  • আউটপুট: ৩টি আলাদা અલગ (Unique) GUID।
  • ব্রাউজার রিফ্রেশ: আবার নতুন ৩টি আলাদা GUID।
  • ব্যাখ্যা: যেহেতু Transient বলা হয়েছে, Container প্রত্যেক ইনজেকশনের জন্য (প্যারামিটার ১, ২, ৩) একটি করে সম্পূর্ণ নতুন অবজেক্ট বানিয়েছে।

২. Scoped (ServiceLifetime.Scoped):

  • আউটপুট: ৩টিতেই একই (Same) GUID।
  • ব্রাউজার রিফ্রেশ (নতুন রিকোয়েস্ট): ৩টিতেই একই GUID, তবে আগেরবারের চেয়ে ভিন্ন।
  • ব্যাখ্যা: একটি রিকোয়েস্টের আন্ডারে Container একবারই অবজেক্ট বানিয়েছে এবং ঐ একই অবজেক্ট ৩ জায়গায় দিয়ে দিয়েছে। কিন্তু রিকোয়েস্ট চেঞ্জ হলে নতুন অবজেক্ট বানিয়েছে।

৩. Singleton (ServiceLifetime.Singleton):

  • আউটপুট: ৩টিতেই একই (Same) GUID।
  • ব্রাউজার রিফ্রেশ: হাজারবার রিফ্রেশ দিলেও ঐ একই (Same) GUID থাকছে!
  • ব্যাখ্যা: অ্যাপ স্টার্ট হওয়ার পর প্রথম ইনজেকশনের সময় Container যে অবজেক্টটি বানিয়েছিল, আজীবন সে সেটিই সবাইকে দিচ্ছে।

৫. Real-world Use Cases (কখন কোনটি ব্যবহার করবো?) [Priority: 10/10]

লেকচারার শেষে খুব চমৎকার রিয়েল-ওয়ার্ল্ড উদাহরণ দিয়েছেন:

  • Singleton: ডাটাবেস না বানিয়ে যদি ইন-মেমোরি (In-Memory) লিস্ট দিয়ে ডাটা ম্যানেজ করতে চাও, অথবা অ্যাপ্লিকেশন সেটিং বা ক্যাশিং (Cache) সার্ভিস বানাতে চাও— যা সবার জন্য সেম থাকবে, তখন Singleton ব্যবহার করবে।
  • Scoped: ডাটাবেস কানেকশন (যেমন: Entity Framework-এর DbContext) এর জন্য Scoped বেস্ট। কারণ প্রতি রিকোয়েস্টে নতুন কানেকশন ওপেন হবে এবং রিকোয়েস্ট শেষে ক্লোজ হয়ে যাবে।
  • Transient: এমন সার্ভিস যা হালকা-পাতলা, যার ভেতরে ডাটা সেভ রাখার দরকার নেই (Stateless), অথবা এমন সার্ভিস যা ক্রিপ্টোগ্রাফি/এনক্রিপশন করে বা ইমেইল পাঠায়— এগুলোর জন্য Transient বেস্ট।

Best Practices & .NET 10 Context

Best Practices for Lifetimes:

  1. State Management: যদি কোনো সার্ভিসের ভেতরে কোনো লিস্ট বা ডাটা জমা রাখতে হয়, তাহলে তাকে ভুলেও Transient বা Scoped বানাবে না, কারণ রিকোয়েস্ট শেষ হলেই ডাটা গায়েব হয়ে যাবে! সেক্ষেত্রে ডাটাবেস ব্যবহার করবে অথবা Singleton ব্যবহার করবে (তবে Singleton-এ Memory Leak-এর ঝুঁকি থাকে)।
  2. Lifetime Scoping Rule: একটি Scoped বা Transient সার্ভিসকে কখনোই একটি Singleton সার্ভিসের ভেতরে ইনজেক্ট করবে না। কারণ Singleton সারা জীবন বাঁচে, তার পেটের ভেতর Scoped ঢুকে গেলে সেও সারা জীবন বেঁচে থাকবে (Captive Dependency Error)।

.NET 10 Context: ASP.NET Core 10-এ এই ৩টি লাইফটাইমের কোর মেকানিজম .NET Core এর শুরু থেকেই ১০০% এক এবং অপরিবর্তিত আছে। ইন্টারভিউতে Lifetime থেকে যে প্রশ্নই আসুক, এই GUID-এর এক্সপেরিমেন্টটা মাথায় রাখলে তুমি চোখ বন্ধ করে সঠিক উত্তর দিতে পারবে।

হাসিব, ৩টি লাইফটাইমের এই প্র্যাকটিক্যাল এক্সপেরিমেন্টটা কি তুমি পুরোপুরি বুঝতে পেরেছো? রেডি থাকলে আমরা নেক্সট লেকচার “Service Scope”-এ মুভ করতে পারি!