আজকের লেকচারে স্বাগতম!
কোথায় আছি আমরা? আপনি বর্তমানে Section 25: Identity-এর অন্যতম গুরুত্বপূর্ণ লেকচারে আছেন। গত লেকচারে আমরা Register UI তৈরি করেছিলাম। আজ আমরা সেই UI থেকে আসা ডেটাগুলো রিসিভ করে UserManager-এর মাধ্যমে ডাটাবেসে সেভ করা শিখবো। পাশাপাশি দেখবো কীভাবে ডাটাবেসে Identity-র টেবিলগুলো জেনারেট হয়। চলুন শুরু করি!
📝 Quick Summary for Revision
ভবিষ্যতে দ্রুত রিভিশন দেওয়ার জন্য পুরো লেকচারের মূল বিষয়গুলো নিচে লিস্ট করা হলো:
- UserManager: এটি ইউজারদের ম্যানেজ (Create, Read, Update, Delete) করার মূল Business Logic লেয়ার। এটি ইন্টারনালি
UserStoreব্যবহার করে। - Create User: Controller-এ
RegisterDTOথেকে ডেটা নিয়েApplicationUserঅবজেক্ট তৈরি করতে হয়। এরপর_userManager.CreateAsync(user, password)কল করে ইউজার তৈরি করতে হয়। - IdentityResult:
CreateAsyncমেথডটি একটিIdentityResultরিটার্ন করে, যা দিয়ে আমরা বুঝতে পারি ইউজার তৈরি সফল (succeeded) হয়েছে কিনা। - Error Handling: ফেইল করলে
result.Errorsথেকে মেসেজগুলো পড়েModelState-এ যুক্ত করতে হয়। - Migrations: Identity-র টেবিলগুলো (যেমন:
AspNetUsers) ডাটাবেসে তৈরি করার জন্য Entity Framework Core-এর Migration কমান্ড (update-database) চালাতে হয়। - Password Hashing: পাসওয়ার্ড কখনো Plain Text হিসেবে সেভ হয় না, বরং SHA অ্যালগরিদমের মাধ্যমে Hash হয়ে ডাটাবেসে
PasswordHashকলামে সেভ হয়।
🧠 Comprehensive Breakdown
এখানে লেকচারের প্রতিটি বিষয় বিস্তারিতভাবে এবং ধাপে ধাপে ব্যাখ্যা করা হলো:
১. UserManager কী এবং কীভাবে কাজ করে? (Priority: 10/10)
Identity-তে ইউজার ডেটা ম্যানেজ করার জন্য আমরা সরাসরি IdentityDbContext বা UserStore ব্যবহার করি না। এর বদলে আমরা UserManager ক্লাস ব্যবহার করি।
- Why? কারণ UserManager-এর ভেতরে ইউজার ভ্যালিডেশন, পাসওয়ার্ড হ্যাশিং ইত্যাদি সব Business Logic আগে থেকেই লেখা আছে। এটি ইন্টারনালি Repository Layer (
UserStore)-কে কল করে ডেটা সেভ করে।
💻 Code Implementation (Dependency Injection):
AccountController-এ আমাদের UserManager ইনজেক্ট করতে হবে।
using Microsoft.AspNetCore.Identity;
using ContactManager.Core.IdentityEntities; // আপনার ApplicationUser যেখানে আছে
public class AccountController : Controller
{
private readonly UserManager<ApplicationUser> _userManager;
// Constructor Injection
public AccountController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
// ...
}
২. Register HTTP POST Action তৈরি করা (Priority: 10/10)
ইউজার যখন ফর্ম ফিলাপ করে “Register” বাটনে ক্লিক করে, তখন ডেটাগুলো এই মেথডে আসবে।
Step-by-step Logic:
- Validation Check: প্রথমে
ModelStateচেক করতে হবে। - Mapping:
RegisterDTO-এর ডেটা দিয়ে একটি নতুনApplicationUserঅবজেক্ট তৈরি করতে হবে। - Create User:
_userManager.CreateAsyncমেথড কল করে ডেটাবেসে পাঠাতে হবে। এখানে পাসওয়ার্ড আলাদাভাবে পাস করতে হয় যাতে এটি Hash হয়ে যায়। - Check Result: অপারেশন সাকসেসফুল হলে রিডাইরেক্ট করবে, আর ফেইল করলে এরর মেসেজ দেখাবে।
💻 Code Implementation:
[HttpPost]
public async Task<IActionResult> Register(RegisterDTO registerDTO)
{
// 1. Validation Check
if (!ModelState.IsValid)
{
// লেকচারারের স্টাইল: ViewBag এ এরর রাখা (তবে ModelState সরাসরি ব্যবহার করা বেটার)
ViewBag.Errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage);
return View(registerDTO);
}
// 2. Mapping DTO to ApplicationUser Entity
ApplicationUser user = new ApplicationUser
{
Email = registerDTO.Email,
PhoneNumber = registerDTO.PhoneNumber,
UserName = registerDTO.Email, // Login এর জন্য Email কেই UserName হিসেবে ব্যবহার করা হচ্ছে
PersonName = registerDTO.PersonName
};
// 3. Create User in Database
// Note: Password আলাদাভাবে পাস করতে হবে, যাতে UserManager এটিকে Hash করতে পারে
IdentityResult result = await _userManager.CreateAsync(user, registerDTO.Password);
// 4. Check IdentityResult
if (result.Succeeded)
{
// রেজিস্ট্রেশন সফল হলে Persons/Index পেজে নিয়ে যাবে
return RedirectToAction("Index", "Persons");
}
else
{
// রেজিস্ট্রেশন ফেইল করলে (যেমন: Email already exists, Password too weak)
foreach (IdentityError error in result.Errors)
{
// ModelState এ এররগুলো যুক্ত করা হচ্ছে যাতে UI-তে Validation Summary-তে দেখায়
ModelState.AddModelError("Register", error.Description);
}
return View(registerDTO);
}
}
৩. Database Migrations & Identity Tables (Priority: 10/10)
কোড লেখার পর রান করলে প্রথমবার SQL Exception আসবে (Invalid object name AspNetUsers)। কারণ, ডাটাবেসে এখনও টেবিলগুলো তৈরি হয়নি।
টেবিল জেনারেট করার জন্য Package Manager Console (PMC)-এ কমান্ড চালাতে হবে। খেয়াল রাখবেন, কনসোলে প্রজেক্ট হিসেবে Infrastructure সিলেক্ট করা থাকতে হবে।
- Command 1:
add-migration InitialIdentity(মাইগ্রেশন ফাইল জেনারেট করার জন্য) - Command 2:
update-database(ডাটাবেসে টেবিলগুলো তৈরি করার জন্য)
⌨️ VS Code Shortcut: যদি আপনি Visual Studio Code ব্যবহার করেন, তবে Terminal-এ নিচের কমান্ডগুলো চালাবেন:
dotnet ef migrations add InitialIdentity --project <Infrastructure_Project_Path>dotnet ef database update --project <Infrastructure_Project_Path>
Generated Tables: কমান্ড চালানোর পর ডাটাবেসে বেশ কিছু টেবিল তৈরি হবে:
AspNetUsers: ইউজারদের সব তথ্য (নাম, ইমেইল, পাসওয়ার্ড হ্যাশ) এখানে থাকে।AspNetRoles: অ্যাপ্লিকেশন রোল (Admin, User) এখানে থাকে।AspNetUserRoles: কোন ইউজার কোন রোলে আছে তার ম্যাপিং (UserId, RoleId)।AspNetUserClaims/AspNetRoleClaims: ইউজার বা রোলের অতিরিক্ত ডেটা (Claims)।AspNetUserTokens: OTP বা পাসওয়ার্ড রিসেট টোকেন রাখার জন্য।
৪. Password Hashing (Priority: 9/10)
আপনি যখন CreateAsync মেথডে পাসওয়ার্ড পাস করেছেন (registerDTO.Password), Identity তখন আপনার দেওয়া অরিজিনাল পাসওয়ার্ড (যেমন: “John123#”) সরাসরি ডাটাবেসে সেভ করেনি।
- Why? সিকিউরিটির জন্য। ডাটাবেস হ্যাক হলেও যেন কেউ প্লেইন পাসওয়ার্ড না দেখতে পায়।
- How it works? Identity এটি গ্রহণ করে SHA (Secure Hash Algorithm) দিয়ে একটি জটিল স্ট্রিং বা Hash-এ কনভার্ট করে।
AspNetUsersটেবিলেরPasswordHashকলামে এই এনক্রিপ্টেড ডেটা সেভ হয়। লগইনের সময় ইউজার যে পাসওয়ার্ড দেয়, তাকে আবার Hash করে ডাটাবেসের Hash-এর সাথে মিলিয়ে দেখা হয়।
🌟 Best Practices & Smarter Updates (.NET 10 Context)
- Avoid ViewBag for Validation: লেকচারার
ModelStateএররগুলোViewBag.Errors-এ নিয়ে গেছেন। আধুনিক MVC প্র্যাকটিসে এটি অপ্রয়োজনীয়।ModelState.IsValidফলস হলে সরাসরিreturn View(registerDTO);করে দিলে View-এর<div asp-validation-summary="All"></div>অটোমেটিকভাবেModelStateথেকে এররগুলো রেন্ডার করে নেবে। এতে কোড অনেক ক্লিন থাকে। - Use AutoMapper (Advanced):
RegisterDTOথেকেApplicationUser-এ ম্যানুয়ালি ডেটা ম্যাপ (assign) করার বদলেAutoMapperলাইব্রেরি ব্যবহার করা একটি বেস্ট প্র্যাকটিস, বিশেষ করে যখন অনেকগুলো প্রপার্টি থাকে। - Smarter Object Initialization: .NET 10 (বা C# 9+)-এ আপনি Target-typed
newexpression ব্যবহার করে কোড আরও শর্ট করতে পারেন:
// C# 9+ Update
ApplicationUser user = new()
{
Email = registerDTO.Email,
// ...
};
পরবর্তী লেকচারে আমরা শিখবো কীভাবে রেজিস্ট্রেশন সফল হওয়ার পর ইউজারকে অটোমেটিকভাবে লগইন (Sign-In) করানো যায় SignInManager ব্যবহার করে!