চমৎকার! গত লেকচারে আমরা Web API-এর GET রিকোয়েস্ট নিয়ে কাজ করেছিলাম। আজ আমরা কোর্সের এই অংশের সবচেয়ে গুরুত্বপূর্ণ বিষয়গুলো শিখবো— কীভাবে ডাটাবেজে নতুন ডেটা সেভ (POST), বিদ্যমান ডেটা আপডেট (PUT) এবং ডেটা মুছে (DELETE) ফেলা যায়। আজ আপনার Web API-এর CRUD সাইকেল সম্পূর্ণ হতে যাচ্ছে!
চলুন শুরু করা যাক!
📝 Lecture Summary
ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য পুরো লেকচারের মূল বিষয়গুলো নিচে তালিকাভুক্ত করা হলো:
- PUT Request (Update): Route Parameter-এর ID এবং Request Body-এর JSON ID অবশ্যই এক হতে হবে। অমিল থাকলে
BadRequest(400) রিটার্ন করবে। - Safe Updating: সরাসরি পুরো Object-কে
Modifiedস্টেটাস না দিয়ে, আগে ডাটাবেজ থেকে ডেটাFindAsyncদিয়ে নিয়ে এসে শুধু প্রয়োজনীয় ফিল্ডগুলো আপডেট করা নিরাপদ। - POST Request (Create): সফলভাবে ডেটা ইনসার্ট হলে
CreatedAtActionমেথড কল করা হয়, যা স্বয়ংক্রিয়ভাবে201 Createdস্ট্যাটাস, নতুন Object এবং একটিLocationHeader রিটার্ন করে। - Location Header: এটি ক্লায়েন্টকে বলে দেয় নতুন তৈরি হওয়া ডেটাটি ভবিষ্যতে কোন URL (যেমন:
/api/cities/{id}) দিয়ে খুঁজে পাওয়া যাবে। - DELETE Request: Route থেকে ID নিয়ে ডেটাবেজে খুঁজবে। পেলে
RemoveকরেSaveChangesAsyncকল করবে এবংNoContent(204) রিটার্ন করবে। - [Bind] Attribute: Over-posting হ্যাকিং বা অনাকাঙ্ক্ষিত ডেটা ইনসার্ট ঠেকানোর জন্য
[Bind]ব্যবহার করে নির্দিষ্ট প্রপার্টিগুলোকে রিসিভ করার পারমিশন দেওয়া যায়।
🧠 Comprehensive Breakdown
নিচে লেকচারের প্রতিটি কনসেপ্ট বিস্তারিত এবং সহজভাবে ব্যাখ্যা করা হলো।
১. PUT Request: Updating Data Safely (Importance: 10/10)
PUT রিকোয়েস্ট মূলত ডাটাবেজে থাকা কোনো ডেটা আপডেট করার জন্য ব্যবহৃত হয়।
- ID Matching: URL-এ থাকা ID (Route Parameter) এবং JSON ডেটার ভেতরে থাকা ID সমান হতে হবে। যদি না মেলে, তবে ফ্রেমওয়ার্ক
BadRequest(400) পাঠাবে। - Why manual update is better? Entity Framework-এ আপনি চাইলে সরাসরি একটি Object-কে
EntityState.Modifiedবলে দিতে পারেন। কিন্তু এটি খুবই বিপজ্জনক! কারণ এটি ডাটাবেজের ঐ রেকর্ডের সব কলাম আপডেট করে দেয়। রিয়েল-ওয়ার্ল্ড প্রোজেক্টে (যেমন: Facebook-এ) ইউজার হয়তো শুধু তার ফোন নাম্বার আপডেট করতে চাচ্ছে। তাই বেস্ট প্র্যাকটিস হলো— প্রথমেFindAsyncদিয়ে পুরনো ডেটা তুলে আনা, তারপর শুধু কাঙ্ক্ষিত প্রপার্টি (যেমন:CityName) পরিবর্তন করেSaveChangesAsync()কল করা। - Concurrency Exception: আপনি যখন ডেটা আপডেট করছেন, ঠিক সেই মুহূর্তেই যদি অন্য কোনো ইউজার একই ডেটা আপডেট করার চেষ্টা করে, তখন
DbUpdateConcurrencyExceptionফায়ার হয়। এটি হ্যান্ডেল করেNotFoundবা এরর রেসপন্স পাঠানো উচিত।
২. POST Request & The Magic of CreatedAtAction (Importance: 10/10)
নতুন ডেটা ডাটাবেজে সেভ করার জন্য POST রিকোয়েস্ট ব্যবহৃত হয়।
- How it works:
[ApiController]থাকার কারণে Request Body থেকে JSON ডেটা সরাসরি আমাদের Model Object-এ বাইন্ড হয়ে যায়। ডাটাবেজে.Add()এবং.SaveChangesAsync()করার পর ডেটা সেভ হয়। - The
CreatedAtActionMethod: ডেটা সেভ হওয়ার পর শুধু200 OKপাঠানো RESTful স্ট্যান্ডার্ড নয়। এর বদলেCreatedAtActionকল করা হয়। এটি তিনটি কাজ করে:
- এটি HTTP Status Code
201 Createdরিটার্ন করে। - এটি Response Body-তে নতুন তৈরি হওয়া সম্পূর্ণ Object-টি পাঠিয়ে দেয় (কারণ ডাটাবেজ হয়তো নতুন কোনো অটো-জেনারেটেড ভ্যালু তৈরি করেছে)।
- Location Header: এটি HTTP Response Header-এ একটি
Locationযুক্ত করে। এটি ডাইনামিক্যালি একটি URL তৈরি করে দেয় (যেমন:api/cities/123), যা দিয়ে ক্লায়েন্ট বুঝতে পারে তার তৈরি করা ডেটাটি কোথায় আছে।
- Why? এর জন্য
CreatedAtAction-কে বলতে হয় কোন মেথড (GetCity), কোন প্যারামিটার (cityId) এবং কোন ডেটা (নতুন City Object) রিটার্ন করতে হবে।
৩. DELETE Request: Removing Data (Importance: 9/10)
এটি সবচেয়ে সহজ অপারেশন।
- Process: প্রথমে Route Parameter থেকে পাওয়া ID দিয়ে ডাটাবেজে
FindAsync()করা হয়। যদি অবজেক্টটিnullহয়, তবেNotFound(404) পাঠানো হয়। - Deletion: অবজেক্ট পেলে
_context.Cities.Remove(city)কল করা হয়। মনে রাখবেন,Removeকরলেই ডাটাবেজ থেকে ডেটা ডিলিট হয় না, এটি শুধু Entity-কে Mark করে। এরপরSaveChangesAsync()কল করলেই আসলে SQL-এ Delete কমান্ড এক্সিকিউট হয়। - Response: সফলভাবে ডিলিট হলে
NoContent(204) রিটার্ন করা হয়, কারণ ক্লায়েন্টকে দেখানোর মতো আর কোনো ডেটা নেই।
৪. Over-posting and [Bind] Attribute (Importance: 7/10)
Over-posting হলো একটি সিকিউরিটি রিস্ক যেখানে হ্যাকাররা Request Body-তে অতিরিক্ত বা অননুমোদিত প্রপার্টি (যেমন: IsAdmin = true) পাঠিয়ে ডাটাবেজ ম্যানিপুলেট করতে পারে।
- Prevention: মেথডের প্যারামিটারে
[Bind("CityID, CityName")]ব্যবহার করলে ফ্রেমওয়ার্ক শুধু এই দুটি প্রপার্টিই রিসিভ করবে, বাকি সব ইগনোর করবে।
💻 Code Implementation (Traditional vs Modern .NET 10)
ভিডিওতে দেখানো Traditional Controller কোডের বদলে, নিচে .NET 10-এর স্ট্যান্ডার্ড অনুযায়ী Primary Constructors এবং Pattern Matching ব্যবহার করে একটি ক্লিন ও আপডেটেড Web API Controller-এর উদাহরণ দেওয়া হলো:
[Route("api/[controller]")]
[ApiController]
public class CitiesController(ApplicationDbContext context) : ControllerBase
{
// POST Request
[HttpPost]
public async Task<ActionResult<City>> PostCity(City city)
{
context.Cities.Add(city);
await context.SaveChangesAsync();
// CreatedAtAction expects: ActionName, RouteValues, Value
return CreatedAtAction(nameof(GetCity), new { cityId = city.CityID }, city);
}
// GET City (Required for CreatedAtAction to generate Location Header)
[HttpGet("{cityId}")]
public async Task<ActionResult<City>> GetCity(Guid cityId)
{
var city = await context.Cities.FindAsync(cityId);
return city is not null ? city : NotFound();
}
// PUT Request
[HttpPut("{cityId}")]
public async Task<IActionResult> PutCity(Guid cityId, City city)
{
if (cityId != city.CityID) return BadRequest();
var existingCity = await context.Cities.FindAsync(cityId);
if (existingCity is null) return NotFound();
// Update specific properties safely
existingCity.CityName = city.CityName;
try
{
await context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
return StatusCode(500, "Concurrency error occurred.");
}
return NoContent(); // 204
}
// DELETE Request
[HttpDelete("{cityId}")]
public async Task<IActionResult> DeleteCity(Guid cityId)
{
var city = await context.Cities.FindAsync(cityId);
if (city is null) return NotFound();
context.Cities.Remove(city);
await context.SaveChangesAsync();
return NoContent(); // 204
}
}
🏆 Best Practices for Web API
- DTOs for Prevention of Over-posting:
[Bind]অ্যাট্রিবিউট ব্যবহার করার চেয়ে DTO (Data Transfer Object) ব্যবহার করা আধুনিক এবং বেস্ট প্র্যাকটিস। ক্লায়েন্ট থেকেCityCreateDTOরিসিভ করুন, যাতে শুধু কাঙ্ক্ষিত ফিল্ডগুলোই থাকে। এতে Over-posting-এর কোনো ভয় থাকে না। - Proper Async Naming (Optional but good): আপনার মেথডের কাজ যদি Asynchronous হয় (যেখানে
awaitআছে), তবে মেথডের নামের শেষেAsyncযুক্ত করা একটি ভালো কনভেনশন (যেমন:PostCityAsync)। - Avoid Exposing Full Exception Details: Production এনভায়রনমেন্টে
catchব্লকের ভেতর থেকে কখনোই হুবহু Exception-এর এরর মেসেজ ক্লায়েন্টকে পাঠাবেন না, এটি সিকিউরিটির জন্য হুমকিস্বরূপ। একটি Generic Error Message (500 Internal Server Error) পাঠান।
এখন যেহেতু আপনি Web API-এর কোর CRUD অপারেশনগুলো (GET, POST, PUT, DELETE) সম্পূর্ণভাবে আয়ত্ত করেছেন, আপনি কি জানতে চান কীভাবে DTO (Data Transfer Object) ব্যবহার করে এই API-কে আরও সিকিউর ও প্রফেশনাল করা যায়?