চমৎকার! গত লেকচারে আমরা Entity Framework Core ব্যবহার করে স্বয়ংক্রিয়ভাবে একটি CRUD Controller তৈরি করেছিলাম। আপনি এখন আছেন কোর্সের Lecture 359: Web API Controllers with EF Core - Part 1-তে। আজ আমরা সেই জেনারেট হওয়া Controller-এর ভেতরের কোডগুলো খণ্ড খণ্ড করে বুঝবো। বিশেষ করে [ApiController] কীভাবে জাদুর মতো কাজ করে এবং GET রিকোয়েস্টগুলো ঠিক কীভাবে ডাটাবেজ থেকে ডেটা নিয়ে আসে, তা আজ পরিষ্কার হয়ে যাবে।

চলুন শুরু করা যাক!


📝 Lecture Summary

ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য পুরো লেকচারের মূল বিষয়গুলো নিচে তালিকাভুক্ত করা হলো:

  • Controller Rule: Controller ক্লাসের নামের শেষে অবশ্যই Controller থাকতে হবে এবং এটি ControllerBase থেকে ইনহেরিট করতে হবে।

  • [ApiController] Benefits: * এটি Attribute Routing ব্যবহার করা বাধ্যতামূলক করে।

  • POST/PUT রিকোয়েস্টে JSON ডেটা রিসিভ করার জন্য অটোমেটিক [FromBody] অ্যাপ্লাই করে।

  • Model Validation ফেইল করলে স্বয়ংক্রিয়ভাবে 400 Bad Request রিটার্ন করে।

  • Constructor Injection: ডাটাবেজের সাথে যোগাযোগের জন্য ApplicationDbContext-কে Constructor-এর মাধ্যমে ইনজেক্ট করা হয়।

  • GET Methods: * মেথডের নাম যাই হোক না কেন, [HttpGet] অ্যাট্রিবিউট নির্ধারণ করে এটি কোন রিকোয়েস্ট হ্যান্ডেল করবে।

  • নির্দিষ্ট রেকর্ড না পেলে NotFound() মেথড কল করা হয়, যা 404 Not Found স্ট্যাটাস কোড জেনারেট করে।

  • Automatic JSON Serialization: Action Method থেকে কোনো Object বা Collection রিটার্ন করলে Web API নিজে থেকেই সেটিকে JSON ফরম্যাটে কনভার্ট করে দেয়।


🧠 Comprehensive Breakdown

নিচে লেকচারের প্রতিটি কনসেপ্ট বিস্তারিত এবং সহজভাবে ব্যাখ্যা করা হলো।

১. The Magic of [ApiController] Attribute (Importance: 10/10)

Web API Controller-এর মাথার ওপর [ApiController] অ্যাট্রিবিউটটি থাকা কতটা জরুরি এবং এটি কী কী কাজ করে, তা বোঝা খুব গুরুত্বপূর্ণ।

Why is it so powerful?

  • Enforces Attribute Routing: এটি যদি থাকে, তবে আপনাকে অবশ্যই [Route] অ্যাট্রিবিউট ব্যবহার করতে হবে। না করলে অ্যাপ্লিকেশন রান করার সময় Exception থ্রো করবে। এটি ডেভেলপারদের একটি নির্দিষ্ট প্যাটার্ন ফলো করতে বাধ্য করে।
  • Automatic [FromBody]: Web API মূলত JSON ডেটা নিয়ে কাজ করে। [ApiController] না থাকলে POST রিকোয়েস্টের সময় JSON ডেটা রিড করার জন্য মেথডের প্যারামিটারে explicitly [FromBody] লিখতে হতো। না লিখলে ডেটা null বা 0 হিসেবে আসতো। কিন্তু এই অ্যাট্রিবিউটটি থাকলে ফ্রেমওয়ার্ক নিজ থেকেই বুঝে নেয় যে Request Body থেকে JSON ডেটা পার্স করতে হবে।
  • Automatic 400 Bad Request: Model Validation (যেমন: [Required]) ফেইল করলে এটি নিজে থেকেই 400 Bad Request রেসপন্স পাঠিয়ে দেয়, আলাদা করে if (!ModelState.IsValid) চেক করার দরকার হয় না।

২. Constructor Injection for DbContext (Importance: 8/10)

Controller-এর ভেতরে একটি Private Read-only ফিল্ড তৈরি করে Constructor-এর মাধ্যমে ApplicationDbContext ইনজেক্ট করা হয়েছে।

Why? যেহেতু আমরা এখানে কোনো আলাদা Service বা Repository লেয়ার ব্যবহার করিনি (সিম্পলিসিটির জন্য), তাই সরাসরি Controller থেকেই ডাটাবেজে CRUD অপারেশন চালানোর জন্য এই DbContext ইনজেক্ট করা হয়েছে।

৩. Get All Cities: [HttpGet] (Importance: 9/10)

সবগুলো শহরের লিস্ট পাওয়ার জন্য GetCities() মেথডটি ব্যবহার করা হয়।

  • Action Method Naming: Web API-তে মেথডের নাম GetCities নাকি অন্য কিছু, তা খুব একটা ম্যাটার করে না। আসল বিষয় হলো এর ওপরে থাকা [HttpGet] অ্যাট্রিবিউট।
  • LINQ and Async: এখানে Entity Framework-এর ToListAsync() ব্যবহার করা হয়েছে যা একটি Asynchronous অপারেশন। আপনি চাইলে এখানে LINQ-এর OrderBy(), Where() ইত্যাদি যুক্ত করে ডেটা ম্যানিপুলেট করতে পারেন।

৪. Get Single City: Route Parameters and 404 (Importance: 10/10)

একটি নির্দিষ্ট শহরের ডেটা পাওয়ার জন্য GetCity(Guid cityId) মেথডটি কাজ করে।

  • Route Combination: [HttpGet("{cityId}")] অ্যাট্রিবিউটটি মূলত দুটি কাজ করে। এটি বলে দেয় যে এটি একটি GET রিকোয়েস্ট এবং URL-এর শেষে থাকা ভ্যালুটি একটি Route Parameter হিসেবে রিসিভ করতে হবে (যেমন: /api/cities/123)। প্যারামিটারের নাম (cityId) অবশ্যই মেথডের আর্গুমেন্টের নামের সাথে মিলতে হবে।
  • Finding the Data: FirstOrDefaultAsync() ব্যবহার করে ডাটাবেজ থেকে নির্দিষ্ট ID-এর রেকর্ডটি খোঁজা হয়।
  • NotFound() Method: যদি ডাটাবেজে ওই ID-এর কোনো রেকর্ড না থাকে, তবে NotFound() কল করা হয়। এটি ControllerBase ক্লাসের একটি বিল্ট-ইন মেথড যা স্বয়ংক্রিয়ভাবে 404 Not Found স্ট্যাটাস কোড (যা IActionResult ইমপ্লিমেন্ট করে) তৈরি করে পাঠিয়ে দেয়।
  • JSON Conversion: যদি রেকর্ড পাওয়া যায়, তবে মেথডটি সরাসরি সেই Object রিটার্ন করে। Web API নিজে থেকেই সেই C# Object-কে JSON-এ রূপান্তর করে ক্লায়েন্টের কাছে পাঠায়।

💻 Code Implementation (Traditional vs Modern .NET 10)

Traditional Approach (From Transcript):

[Route("api/[controller]")]
[ApiController]
public class CitiesController : ControllerBase
{
    private readonly ApplicationDbContext _context;
 
    public CitiesController(ApplicationDbContext context)
    {
        _context = context;
    }
 
    [HttpGet]
    public async Task<ActionResult<IEnumerable<City>>> GetCities()
    {
        return await _context.Cities.OrderBy(c => c.CityName).ToListAsync();
    }
 
    [HttpGet("{cityId}")]
    public async Task<ActionResult<City>> GetCity(Guid cityId)
    {
        var city = await _context.Cities.FirstOrDefaultAsync(c => c.CityID == cityId);
 
        if (city == null)
        {
            return NotFound();
        }
 
        return city;
    }
}
 

Smarter Approach (.NET 10 with Primary Constructors): আধুনিক .NET (C# 12+) ভার্সনগুলোতে Primary Constructors ব্যবহার করে Controller-এর কোড আরও অনেক ক্লিন এবং ছোট করা যায়। এতে আলাদা করে Private ফিল্ড ডিক্লেয়ার এবং Constructor লেখার Boilerplate কোড কমে যায়।

[Route("api/[controller]")]
[ApiController]
// Primary Constructor directly in class definition
public class CitiesController(ApplicationDbContext context) : ControllerBase 
{
    [HttpGet]
    public async Task<ActionResult<IEnumerable<City>>> GetCities()
    {
        // context is directly accessible
        return await context.Cities.OrderBy(c => c.CityName).ToListAsync(); 
    }
 
    [HttpGet("{cityId}")]
    public async Task<ActionResult<City>> GetCity(Guid cityId)
    {
        var city = await context.Cities.FirstOrDefaultAsync(c => c.CityID == cityId);
 
        // Pattern matching feature makes it cleaner
        return city is not null ? city : NotFound();
    }
}
 

🏆 Best Practices for Web API Controllers

  • Use ActionResult<T>: শুধু IActionResult রিটার্ন করার চেয়ে ActionResult<T> ব্যবহার করা ভালো (যেমন: ActionResult<City>)। এটি Swagger ডকুমেন্টেশনকে বুঝতে সাহায্য করে যে আপনি ঠিক কোন টাইপের ডেটা রিটার্ন করছেন।
  • Avoid Sync Methods: ডাটাবেজ কল করার সময় সবসময় Async/Await (যেমন: FirstOrDefaultAsync, ToListAsync) ব্যবহার করবেন। এটি অ্যাপ্লিকেশনের স্কেলেবিলিটি অনেক বাড়িয়ে দেয়।
  • Keep Route Parameters Consistent: Route Attribute-এর প্যারামিটারের নাম এবং মেথডের আর্গুমেন্টের নাম সবসময় হুবহু এক রাখবেন (যেমন: [HttpGet("{id}")] এর সাথে public async Task<IActionResult> Get(Guid id))। এটি না মিললে Model Binding ঠিকমতো কাজ করবে না।