হ্যালো হাসিব! এই লেকচারে আমরা আগের লেকচারে তৈরি করা Search UI-কে ফাংশনাল করার কাজ করেছি। ইউজার যখন সার্চ ফর্মে কিছু লিখে সাবমিট করবে, তখন কীভাবে ডাটা ফিল্টার হয়ে আসবে এবং পেজ রিলোড হওয়ার পরও সার্চের ইনপুটগুলো কীভাবে ফর্মে ধরে রাখা (State Persistence) যায়, সেটাই মূলত এখানে দেখানো হয়েছে।

চলো প্রথমে লেকচারটির একটি কুইক সামারি দেখে নিই।

📌 Quick Summary for Revision

  • Model Binding & Filtering: Controller-এর Index মেথডে searchBy এবং searchString প্যারামিটার রিসিভ করা এবং সার্ভিস থেকে GetFilteredPersons() কল করে ডাটা ফিল্টার করা।
  • State Persistence with ViewBag: ফর্ম সাবমিট হওয়ার পর পেজ রিলোড হলে টেক্সটবক্স ও ড্রপডাউন রিসেট হয়ে যায়। এই ডাটা ধরে রাখতে ViewBag.CurrentSearchBy এবং ViewBag.CurrentSearchString ব্যবহার করা হয়েছে।
  • Retaining Textbox Value: ভিউয়ের <input> ট্যাগে value="@ViewBag.CurrentSearchString" সেট করে আগের সার্চ ভ্যালু ধরে রাখা।
  • Retaining Dropdown Selection: foreach লুপের ভেতর কন্ডিশন চেক করে, ইউজার যেই অপশনটি সিলেক্ট করেছিলো, সেই <option> ট্যাগে selected="selected" অ্যাট্রিবিউট যুক্ত করা।
  • DTO Mapping Issue Fixed: Service লেয়ারে Person ক্লাসের বদলে PersonResponse ক্লাসের প্রপার্টি নাম (যেমন: nameof(PersonResponse.PersonName)) ব্যবহার করা যাতে Controller-এর সাথে কনফ্লিক্ট না হয়।

🚀 Comprehensive Breakdown & The “Why”

নিচে লেকচারের প্রতিটি কনসেপ্ট বিস্তারিত এবং কারণসহ এক্সপ্লেইন করা হলো:

১. Controller-এ Data Filtering [Priority: 10/10]

The “Why”: ইউজারের সার্চ ইনপুটগুলো GET রিকোয়েস্টের Query String হিসেবে Controller-এ আসে। আমাদের কাজ হলো ওই প্যারামিটারগুলো রিসিভ করা এবং ডাটাবেস (বা ইন-মেমোরি কালেকশন) থেকে সব ডাটা না এনে, শুধু শর্ত পূরণ করা ডাটাগুলো আনা। এর জন্য GetAllPersons() এর বদলে GetFilteredPersons() মেথড ব্যবহার করা হয়েছে।

Implementation:

[Route("persons/index")]
[Route("/")]
public IActionResult Index(string searchBy, string searchString)
{
    // Search Fields Dropdown-এর জন্য
    ViewBag.SearchFields = new Dictionary<string, string>()
    {
        { nameof(PersonResponse.PersonName), "Person Name" },
        { nameof(PersonResponse.Email), "Email" },
        // ... অন্যান্য ফিল্ড
    };
 
    // সব ডাটার বদলে শুধুমাত্র ফিল্টার করা ডাটা আনা হচ্ছে
    List<PersonResponse> persons = _personsService.GetFilteredPersons(searchBy, searchString);
    
    return View(persons); 
}
 

২. State Persistence (ফর্মের ইনপুট ধরে রাখা) [Priority: 10/10]

The “Why”: যখন ফর্মটি সাবমিট হয়, Controller ডাটা প্রসেস করে আবার Index.cshtml ভিউটিকে নতুন করে রেন্ডার করে। ডিফল্টভাবে HTML ফর্ম তার আগের স্টেট মনে রাখতে পারে না, ফলে সার্চের পর ড্রপডাউন এবং টেক্সটবক্স ফাঁকা হয়ে যায়। ইউজার এক্সপেরিয়েন্স (UX) ঠিক রাখতে ইউজারকে দেখানো উচিত সে কী লিখে সার্চ করেছিলো। এই ডাটাগুলো View-তে ফেরত পাঠানোর জন্য ViewBag ব্যবহার করা হয়েছে।

Implementation (Controller):

// ইউজারের দেওয়া ইনপুটগুলো ViewBag-এ সেভ করে View-তে ফেরত পাঠানো হচ্ছে
ViewBag.CurrentSearchBy = searchBy;
ViewBag.CurrentSearchString = searchString;
 

৩. View আপডেট করা (Textbox এবং Dropdown) [Priority: 9/10]

The “Why”: Controller থেকে ViewBag-এ ডাটা পাঠানোর পর, সেই ডাটাগুলো HTML এলিমেন্টগুলোতে সেট করতে হবে। টেক্সটবক্সের ক্ষেত্রে value অ্যাট্রিবিউট এবং ড্রপডাউনের ক্ষেত্রে <option> ট্যাগের selected অ্যাট্রিবিউট ব্যবহার করতে হয়।

Implementation (View):

@* Dropdown Retain Logic *@
<select class="form-input" name="searchBy">
    @foreach (var field in ViewBag.SearchFields)
    {
        if (field.Key == ViewBag.CurrentSearchBy)
        {
            <option value="@field.Key" selected="selected">@field.Value</option>
        }
        else
        {
            <option value="@field.Key">@field.Value</option>
        }
    }
</select>
 
@* Textbox Retain Logic *@
<input type="search" class="form-input" name="searchString" value="@ViewBag.CurrentSearchString" />
 

৪. Business Logic-এ “Contains” vs “Exact Match” [Priority: 6/10]

The “Why”: লেকচারার শেষে একটি লজিক্যাল সমস্যার কথা বলেছেন। যদি Gender কলামে “Male” লিখে সার্চ করা হয়, তাহলে “Female”-ও রেজাল্টে চলে আসে, কারণ “Female” শব্দের ভেতরে “Male” শব্দটি আছে (Partial Match / Contains)। এর সমাধান হলো Service লেয়ারে Gender বা এই ধরনের নির্দিষ্ট কলামের ক্ষেত্রে .Contains() এর বদলে == (Exact Match) ব্যবহার করা।


🆕 .NET 10 & Modern Approaches (The ViewModel Pattern)

লেকচারে State Persistence-এর জন্য ViewBag ব্যবহার করা হয়েছে। ছোট প্রজেক্ট বা শেখার জন্য এটি ঠিক থাকলেও, রিয়েল-ওয়ার্ল্ড প্রজেক্টে ViewBag ব্যবহার করা Best Practice নয়, কারণ এতে বানানে ভুল হলে কম্পাইলার ধরতে পারে না (Type-safety নেই) এবং কোড অগোছালো হয়।

Smart MVC Approach: একটি ViewModel তৈরি করা এবং Tag Helpers ব্যবহার করা। Tag Helper অটোমেটিক্যালি ইনপুট ফিল্ডের State ধরে রাখে (অর্থাৎ value এবং selected ম্যানুয়ালি লিখতে হয় না)।

Step 1: Create a ViewModel

public class PersonIndexViewModel
{
    public string? SearchBy { get; set; }
    public string? SearchString { get; set; }
    public List<PersonResponse> Persons { get; set; } = new List<PersonResponse>();
}
 

Step 2: Controller Implementation

public IActionResult Index(PersonIndexViewModel model)
{
    // Tag Helpers-এর কারণে ড্রপডাউনের ডাটা ViewBag-এ রাখা যায়, তবে স্টেট ViewModel-এ থাকবে
    model.Persons = _personsService.GetFilteredPersons(model.SearchBy, model.SearchString);
    return View(model); 
}
 

Step 3: View Implementation (No manual ‘selected’ or ‘value’ logic needed!)

@model PersonIndexViewModel
 
<form asp-controller="Persons" asp-action="Index" method="get">
    <select class="form-input" asp-for="SearchBy" asp-items="ViewBag.SearchFields"></select>
    
    <input type="search" class="form-input" asp-for="SearchString" />
    
    <button type="submit">Search</button>
</form>
 

⌨️ IDE Shortcuts (Navigation)

এই লেকচারে লেকচারার Service থেকে Controller-এ বারবার মুভ করছিলেন। এই কাজগুলো দ্রুত করার জন্য:

  • Go to Definition (ইন্টারফেস বা মেথডের সিগনেচার দেখা): F12
  • Go to Implementation (ইন্টারফেসের বদলে সরাসরি ক্লাসের আসল কোডে যাওয়া): Ctrl + F12
  • Navigate Back (আগের ফাইলে বা আগের লাইনে ফেরত আসা): Ctrl + - (Minus)

💡 Best Practices (Search & Filtering in MVC)

১. Strongly Typed ViewModel: উপরে দেখানো নিয়মে সব সময় ViewBag-এর বদলে ViewModel ব্যবহার করবে। ২. Safe Data Handling: যখন Query String থেকে searchString আসবে, তখন সার্ভিস লেয়ারে ফিল্টার করার আগে সেটার লিডিং বা ট্রেইলিং স্পেস রিমুভ (Trim) করে নেওয়া ভালো (যেমন: searchString?.Trim())। ৩. Case-Insensitive Search: সার্চ সব সময় Case-insensitive হওয়া উচিত। অর্থাৎ ইউজার “John” বা “john” যাই লিখুক না কেন, ডাটাবেস যেন ঠিকঠাক রেজাল্ট দেয়। (এটি সাধারণত Entity Framework বা LINQ-এ .Contains() ব্যবহার করলে অটোমেটিক হ্যান্ডেল হয়, তবে খেয়াল রাখা ভালো)।

এই ছিলো সার্চ ইমপ্লিমেন্টেশন। এরপরের লেকচারটি কি Sorting নিয়ে হবে?