আজকের লেকচারে স্বাগতম!

কোথায় আছি আমরা? আপনি বর্তমানে 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:

  1. Validation Check: প্রথমে ModelState চেক করতে হবে।
  2. Mapping: RegisterDTO-এর ডেটা দিয়ে একটি নতুন ApplicationUser অবজেক্ট তৈরি করতে হবে।
  3. Create User: _userManager.CreateAsync মেথড কল করে ডেটাবেসে পাঠাতে হবে। এখানে পাসওয়ার্ড আলাদাভাবে পাস করতে হয় যাতে এটি Hash হয়ে যায়।
  4. 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 new expression ব্যবহার করে কোড আরও শর্ট করতে পারেন:
// C# 9+ Update
ApplicationUser user = new() 
{
    Email = registerDTO.Email,
    // ...
};
 

পরবর্তী লেকচারে আমরা শিখবো কীভাবে রেজিস্ট্রেশন সফল হওয়ার পর ইউজারকে অটোমেটিকভাবে লগইন (Sign-In) করানো যায় SignInManager ব্যবহার করে!