হ্যালো হাসিব! এটি আগের লেকচারের (Asynchronous Programming) দ্বিতীয় পার্ট। আগের লেকচারে আমরা শুধু Service Layer-এ async এবং Task রিটার্ন করেছিলাম। কিন্তু এই লেকচারে দেখানো হয়েছে যে, Service-কে async বানালে Controller এবং Unit Test-এর কোডগুলো ভেঙে যায় (Build errors)।

তাই এই লেকচারে আমরা শিখবো কীভাবে “Async All The Way Down” নীতি মেনে Controller-এর Action Method এবং xUnit Test মেথডগুলোকে Asynchronous-এ রূপান্তর করতে হয়।

📝 Lecture Summary at a Glance

  • Controller Updates: Controller-এর সব Action Method-কে async Task<IActionResult> এ রূপান্তর করা হয়েছে এবং সার্ভিস কলগুলোর আগে await বসানো হয়েছে।
  • xUnit Test Updates: Unit Test মেথডগুলো void এর বদলে async Task রিটার্ন করে।
  • Testing Exceptions: xUnit-এ Exception টেস্ট করার সময় Assert.Throws() এর বদলে Assert.ThrowsAsync() ব্যবহার করতে হয় এবং ভেতরের ল্যাম্বডা এক্সপ্রেশনটিকেও async বানাতে হয়।

🧠 Comprehensive Breakdown & Deep Dive

১. Controller Action Methods কে Async করা [Importance: 10/10]

  • The “Why”: ASP.NET Core-এ যখন কোনো HTTP রিকোয়েস্ট আসে, তখন একটি থ্রেড সেই রিকোয়েস্টটি রিসিভ করে Controller-এ পাঠায়। Controller যদি Service-এর async মেথডকে await না করে কল করে, তবে ডাটা আসার আগেই মেথড এক্সিকিউট হয়ে যাবে এবং তুমি কিছুই পাবে না (বা মেমোরি লিক হবে)। তাই Controller-এর Action Method গুলোকেও async করতে হয়।

💻 Code Implementation (PersonsController):

using Microsoft.AspNetCore.Mvc;
using System.Threading.Tasks;
 
public class PersonsController : Controller
{
    private readonly IPersonsService _personsService;
 
    // ❌ Old Synchronous Method:
    // public IActionResult Index(string searchBy, string searchString)
    
    // ✅ Modern Asynchronous Method:
    [Route("[action]")]
    [Route("/")]
    public async Task<IActionResult> Index(string searchBy, string searchString)
    {
        // সার্ভিসের অ্যাসিংক্রোনাস মেথড কল করার আগে await বসাতে হবে
        var persons = await _personsService.GetFilteredPersons(searchBy, searchString);
        
        return View(persons);
    }
    
    [HttpGet]
    [Route("[action]")]
    public async Task<IActionResult> Create()
    {
        // Dropdown এর জন্য দেশের লিস্ট আনতে await ব্যবহার করা হয়েছে
        ViewBag.Countries = await _countriesService.GetAllCountries();
        return View();
    }
}
 

২. Updating xUnit Test Methods [Importance: 9/10]

  • The “Why”: xUnit টেস্ট রানার সিঙ্ক্রোনাস এবং অ্যাসিংক্রোনাস দুই ধরনের টেস্টই রান করতে পারে। কিন্তু যখন তুমি টেস্টের ভেতর কোনো await ব্যবহার করবে, তখন টেস্ট মেথডটিকে অবশ্যই async হতে হবে এবং তার রিটার্ন টাইপ Task হতে হবে (টেস্ট মেথডে async void ব্যবহার করা একদমই নিষিদ্ধ)।

💻 Code Implementation (CountriesServiceTest):

using System.Threading.Tasks;
using Xunit;
 
public class CountriesServiceTest
{
    // ❌ Old Way (void):
    // [Fact]
    // public void GetAllCountries_EmptyList()
    
    // ✅ New Way (async Task):
    [Fact]
    public async Task GetAllCountries_EmptyList()
    {
        // Act
        var actual_country_response_list = await _countriesService.GetAllCountries();
 
        // Assert
        Assert.Empty(actual_country_response_list);
    }
}
 

৩. Handling Exceptions in xUnit Async Tests [Importance: 10/10]

  • The “Why”: এটি সবচেয়ে ট্রিকি পার্ট! সিঙ্ক্রোনাস টেস্টে তুমি Assert.Throws<ArgumentNullException>(() => ...) ব্যবহার করতে। কিন্তু মেথডটি যেহেতু এখন async, তাই xUnit কে বলতে হবে যে এটি একটি অ্যাসিংক্রোনাস প্রসেস এবং এর জন্য Assert.ThrowsAsync ব্যবহার করতে হবে।

💻 Code Implementation (Testing Exceptions):

[Fact]
public async Task AddCountry_NullCountry()
{
    // Arrange
    CountryAddRequest? request = null;
 
    // Assert & Act
    // ১. ThrowsAsync এর আগে await বসাতে হবে।
    // ২. ভেতরের ল্যাম্বডাকে (async () => ...) বানাতে হবে।
    // ৩. সার্ভিস কল করার সময় await ব্যবহার করতে হবে।
    await Assert.ThrowsAsync<ArgumentNullException>(async () =>
    {
        await _countriesService.AddCountry(request);
    });
}
 

🚀 Modern .NET Architecture Notes

১. Return Task instead of void: লেকচারার খুব সুন্দর একটি পয়েন্ট বলেছেন: “When the return type is Task, it is logically equivalent to returning void”. অর্থাৎ, মেথড যদি কোনো ভ্যালু রিটার্ন না করে (যেমন আগে void রিটার্ন করতো), অ্যাসিংক্রোনাস করার পর সেখানে শুধু Task লিখতে হবে (Task<void> লেখা যায় না)।

২. Action Methods and View Generation: Controller-এ যখন তুমি await _personsService.GetAllPersons() কল করো, তখন ডাটা না আসা পর্যন্ত ওই Action Method-টি পজ (Pause) হয়ে থাকে। কিন্তু থ্রেডটি ব্লক হয় না, থ্রেডটি অন্য ইউজারের রিকোয়েস্ট প্রসেস করতে চলে যায়। ডাটাবেস থেকে ডাটা আসার পর থ্রেড আবার ফিরে এসে return View(persons) এক্সিকিউট করে। এটাই Async/Await এর মূল জাদু!

লেকচারার অ্যাসাইনমেন্ট হিসেবে PersonsServiceTest ক্লাসটিকে অ্যাসিংক্রোনাসে রূপান্তর করতে বলেছেন। তুমি যদি সেই অ্যাসাইনমেন্টের কোড দেখতে চাও বা পরের লেকচারের ট্রান্সক্রিপ্ট দিতে চাও, আমি রেডি আছি!