হ্যালো হাসিব! তুমি এখন Section 12: Dependency Injection-এর নবম লেকচার “Service Scope” (Lecture 136)-এ আছো।

এই লেকচারে আমরা শিখবো Scope আসলে কী, কখন একটি Scope তৈরি হয় এবং ডাটাবেস কানেকশন বা মেমোরি ম্যানেজমেন্টের জন্য কীভাবে ম্যানুয়ালি Child Scope তৈরি করে সার্ভিস ইনজেক্ট ও ডিসপোজ (Dispose) করতে হয়। এটি ইন্টারভিউ এবং অ্যাডভান্সড ডটনেট ডেভেলপমেন্টের জন্য খুবই গুরুত্বপূর্ণ।

চলো, পুরো লেকচারটি বিস্তারিতভাবে ডিকোড করি।


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

  • Scope কী: Scope হলো একটি বাউন্ডারি (Boundary) বা লাইফটাইম, যার ভেতরে Scoped সার্ভিসগুলো বেঁচে থাকে।
  • Default Scope: ASP.NET Core-এ প্রতিটি নতুন HTTP রিকোয়েস্টের জন্য ডিফল্টভাবে একটি রিকোয়েস্ট স্কোপ (Request Scope) তৈরি হয় এবং রিকোয়েস্ট শেষে সেটি ডেস্ট্রয় হয়ে যায়।
  • Child Scope কেন দরকার: Scoped সার্ভিস (যেমন Db Connection) ডিফল্টভাবে রিকোয়েস্ট শেষ হওয়ার আগ পর্যন্ত মেমোরিতে থাকে। কিন্তু তুমি যদি চাও কাজ শেষ হওয়ার সাথে সাথেই (রিকোয়েস্ট শেষ হওয়ার আগেই) ডাটাবেস কানেকশন ক্লোজ বা মেমোরি রিলিজ (Dispose) করতে, তখন তোমাকে ম্যানুয়ালি Child Scope বানাতে হবে।
  • কীভাবে বানাবো: Controller-এর কনস্ট্রাক্টরে IServiceScopeFactory ইনজেক্ট করতে হয়। তারপর মেথডের ভেতর using (var scope = _serviceScopeFactory.CreateScope()) দিয়ে স্কোপ তৈরি করতে হয়।
  • কীভাবে সার্ভিস পাবো: ঐ স্কোপের ভেতর scope.ServiceProvider.GetRequiredService<ICitiesService>() কল করে সার্ভিসটি রিসিভ করতে হয়।
  • The Magic: using ব্লকের ব্র্যাকেট শেষ হওয়া মাত্রই Child Scope-এর ভেতরে তৈরি হওয়া সব সার্ভিস অটোমেটিক ডিসপোজ (Dispose() মেথড কল) হয়ে যায়।

Comprehensive Breakdown

১. IDisposable ইমপ্লিমেন্ট করা (The Setup) [Priority: 9/10]

ধরো, আমাদের CitiesService ডাটাবেসের সাথে কানেক্টেড। আমরা চাই এর কাজ শেষ হওয়ার সাথে সাথে ডাটাবেস কানেকশন ক্লোজ করে দিতে। এজন্য ক্লাসটিকে IDisposable ইন্টারফেস ইমপ্লিমেন্ট করতে হবে, যাতে এর ভেতরে Dispose() মেথড থাকে।

Code Implementation (Services/CitiesService.cs):

using ServiceContracts;
 
namespace Services
{
    // IDisposable ইমপ্লিমেন্ট করা হলো
    public class CitiesService : ICitiesService, IDisposable
    {
        private Guid _serviceInstanceId;
        private List<string> _cities;
 
        public Guid ServiceInstanceId { get { return _serviceInstanceId; } }
 
        public CitiesService()
        {
            _serviceInstanceId = Guid.NewGuid();
            _cities = new List<string>() { "London", "Paris", "New York" };
            // (ধরে নাও এখানে ডাটাবেস কানেকশন ওপেন হলো)
        }
 
        public List<string> GetCities() { return _cities; }
 
        // Dispose মেথড (কাজ শেষ হলে এটি অটোমেটিক কল হবে)
        public void Dispose()
        {
            // (ধরে নাও এখানে ডাটাবেস কানেকশন ক্লোজ করার কোড আছে)
            Console.WriteLine("Connection Closed.");
        }
    }
}
 

২. Controller-এ IServiceScopeFactory ইনজেক্ট করা [Priority: 10/10]

ম্যানুয়ালি Child Scope বানাতে হলে আমাদের IServiceScopeFactory নামক একটি বিল্ট-ইন সার্ভিস লাগবে। একে Controller-এর কনস্ট্রাক্টরে ইনজেক্ট করতে হবে।

Code Implementation (Controllers/HomeController.cs):

using Microsoft.AspNetCore.Mvc;
using ServiceContracts;
 
namespace DIExample.Controllers
{
    public class HomeController : Controller
    {
        // অন্যান্য সার্ভিস (রিকোয়েস্ট স্কোপের জন্য)
        private readonly ICitiesService _citiesService1;
        // ১. Scope Factory ইনজেক্ট করার জন্য ফিল্ড
        private readonly IServiceScopeFactory _serviceScopeFactory;
 
        public HomeController(ICitiesService citiesService1, IServiceScopeFactory serviceScopeFactory)
        {
            _citiesService1 = citiesService1;
            _serviceScopeFactory = serviceScopeFactory;
        }
 
        [Route("/")]
        public IActionResult Index()
        {
            ViewBag.InstanceId_1 = _citiesService1.ServiceInstanceId;
 
            // ২. Child Scope তৈরি করা
            using (var scope = _serviceScopeFactory.CreateScope())
            {
                // ৩. Child Scope-এর ভেতর থেকে সার্ভিস রিসিভ করা (Dependency Resolution)
                ICitiesService childScopedService = scope.ServiceProvider.GetRequiredService<ICitiesService>();
 
                // ৪. Child Scope-এর সার্ভিসের GUID আলাদা হবে, তা দেখানোর জন্য ViewBag-এ সেট
                ViewBag.InstanceId_InChildScope = childScopedService.ServiceInstanceId;
                
                // (childScopedService দিয়ে ডাটাবেসের কাজ করা হলো)
 
            } // <--- ঠিক এই ব্র্যাকেট শেষ হওয়ার সাথে সাথে childScopedService.Dispose() অটোমেটিক কল হয়ে যাবে!
 
            return View(_citiesService1.GetCities());
        }
    }
}
 

৩. The Magic Explained (কী হলো এখানে?) [Priority: 10/10]

  • _citiesService1 তৈরি হয়েছে রিকোয়েস্ট স্কোপে (Request Scope)। এটি ডিলিট (Dispose) হবে যখন পুরো রিকোয়েস্ট শেষ হয়ে ইউজার রেসপন্স পেয়ে যাবে।
  • কিন্তু childScopedService তৈরি হয়েছে আমাদের বানানো চাইল্ড স্কোপে (Child Scope)using ব্লক শেষ হওয়ার সাথে সাথেই এটি ডিলিট হয়ে ডাটাবেস কানেকশন ক্লোজ করে দেবে। এর জন্য পুরো রিকোয়েস্ট শেষ হওয়ার অপেক্ষা করতে হবে না।
  • Proof: তুমি যদি ভিউতে গিয়ে এই দুটি GUID প্রিন্ট করো, দেখবে _citiesService1 এবং childScopedService এর GUID সম্পূর্ণ আলাদা! কারণ দুটি અલગ স্কোপে তৈরি হয়েছে।

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

লেকচারার খুব সুন্দর একটি রিয়েল লাইফ এক্সাম্পল দিয়েছেন:

  • ADO.NET বা Raw Database Connection: যদি তুমি Entity Framework ব্যবহার না করে নিজে কোড লিখে ডাটাবেস কানেকশন ওপেন করো (ADO.NET), তাহলে মেমোরি লিক ঠেকানোর জন্য কাজ শেষে দ্রুত কানেকশন ক্লোজ করতে এই Child Scope মেকানিজম ব্যবহার করা বেস্ট প্র্যাকটিস।
  • Entity Framework Core: তুমি যদি Entity Framework (EF Core) ব্যবহার করো, তবে তোমার এই Child Scope নিয়ে চিন্তা করার কোনো দরকার নেই! EF Core নিজের ডাটাবেস কানেকশন এবং স্কোপিং নিজে থেকেই সবচেয়ে অপটিমাইজড ওয়েতে ম্যানেজ করে।
  • Background Tasks: IHostedService বা Background Worker-এ সাধারণত কোনো রিকোয়েস্ট স্কোপ থাকে না (কারণ এগুলো ইউজারের রিকোয়েস্টের বাইরে চলে)। সেখানে ডাটাবেসের কাজ করতে চাইলে এভাবেই IServiceScopeFactory দিয়ে ম্যানুয়ালি স্কোপ বানিয়ে কাজ করতে হয়।

VS / VS Code Shortcuts (For faster development)

  • Implement Interface (IDisposable): IDisposable-এর উপর কার্সর রেখে Ctrl + . চাপলে “Implement Interface” অপশন আসবে, ক্লিক করলেই Dispose() মেথডটি অটোমেটিক্যালি তৈরি হয়ে যাবে।

Best Practices & .NET 10 Context

Best Practices for Service Scopes:

  1. Always use using block: Child Scope বানালে অবশ্যই using ব্লকের ভেতরে বানাবে। নাহলে মেমোরি লিক হবে এবং স্কোপ কখনোই ডিসপোজ হবে না।
  2. Avoid in standard HTTP requests: সাধারণ CRUD অপারেশনের জন্য Child Scope বানানোর কোনো প্রয়োজন নেই, ডিফল্ট রিকোয়েস্ট স্কোপই যথেষ্ট। শুধুমাত্র হেভি ডিউটি টাস্ক বা ব্যাকগ্রাউন্ড টাস্কেই এটি ব্যবহার করবে।

.NET 10 Context: ASP.NET Core 10-এ এই Child Scope-এর মেকানিজম সম্পূর্ণ এক। তবে আধুনিক C# 12/13-এ using ব্লককে আরও স্মার্টলি লেখা যায় (Using Declarations), যেখানে ব্লকের ব্র্যাকেট { } দেওয়া লাগে না।

// Modern .NET 10 style (Using Declarations)
[Route("/")]
public IActionResult Index()
{
    // ব্র্যাকেট ছাড়া using। এই মেথড (Index) শেষ হওয়া মাত্রই এটি ডিসপোজ হয়ে যাবে।
    using var scope = _serviceScopeFactory.CreateScope();
    
    var childScopedService = scope.ServiceProvider.GetRequiredService<ICitiesService>();
    ViewBag.InstanceId = childScopedService.ServiceInstanceId;
 
    return View();
}
 

হাসিব, Service Scope এবং Child Scope-এর এই ডিপ ডাইভ কনসেপ্ট কি ক্লিয়ার হয়েছে? রেডি থাকলে আমরা নেক্সট লেকচার “AddTransient(), AddScoped(), AddSingleton()“-এ মুভ করতে পারি, যেখানে আমরা শিখবো কীভাবে খুব সহজে সার্ভিসগুলো রেজিস্টার করা যায়!