در این مقاله به بررسی روش پیاده سازی ثبت نام کاربر جدید با استفاده از ASP .NET Core Identity در Blazor WebAssembly می پردازیم. در قسمت قبل به بررسی AuthenticationStateProvider پرداختیم. از دو کاربری که به صورت استاتیک و Fake ساختیم، برای تست احراز هویت در بلیزر استفاده کردیم. اما در این قسمت یک گام رو به جلو بر میداریم و امکان ثبت نام کاربر در Blazor را فراهم می کنیم.

توجه داشته باشید، در این مقاله از ASP .NET Core Identity استفاده می کنیم و بهتر است از قبل با این کتابخانه آشنا باشید. تمرکز اصلی ما بر روی پیاده سازی عملیات ثبت نام کاربر در بلیزر است.

سورس کد آموزش احراز هویت در Blazor در گیت هاب سافت آموز : https://github.com/softamoz/LearnAuthBlazor

راه اندازی ASP .NET Core Identity در پروژه Web API

همانطور که بخاطر دارید، در قسمت قبل، بعد از ایجاد پروژه، در Solution ایجاد شده سه پروژه وجود دارد. می خواهیم در پروژه Server که از نوع Web API هست Identity را نصب کنیم.

کافی است پکیج های زیر را از طریق Nuget نصب کنیم:

Microsoft.AspNetCore.Identity.EntityFrameworkCore
Microsoft.EntityFrameworkCore.SqlServer
Microsoft.EntityFrameworkCore.Tools

سپس در پروژه (سرور) یک پوشه با نام Data ایجاد می کنیم. در آن پوشه کلاسی با نام Context ایجاد می کنیم که از IdentityDbContext ارث بری می کند. به کد زیر توجه کنید:

public class Context : IdentityDbContext
    {
        public Context(DbContextOptions options) : base(options)
        {
        }
    }

سپس در فایل appsettings.json رشته اتصال جدیدی ایجاد می کنیم:

{
  "ConnectionStrings": { "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=BlazorAuthDb;Trusted_Connection=True;MultipleActiveResultSets=true" },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*"
}

سپس در Startup.cs تنظیمات مربوط به Identity و Entity Framework Core را در services رجیستر می کنیم:

services.AddDbContext<Context>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>().AddEntityFrameworkStores<Context>();

.................................

app.UseAuthentication();
app.UseAuthorization();

سپس با استفاده از Package Manager Console دستورات زیر را برای ساخت جداول Identity اجرا می کنیم:

Add-Migration IdentityInit
Update-Database

پیاده سازی Registration در پروژه Web API

در پروژه Shared پوشه ای با نام DTOs ایجاد می کنیم. سپس دو کلاس زیر را در آن ایجاد می کنیم:

    public class UserForRegistrationDto
    {
        [Required(ErrorMessage = "Email is required.")]
        public string Email { get; set; }

        [Required(ErrorMessage = "Password is required")]
        public string Password { get; set; }

        [Compare("Password", ErrorMessage = "The password and confirmation password do not match.")]
        public string ConfirmPassword { get; set; }
    }

و :

public class RegistrationResponseDto
    {
        public bool IsSuccessfulRegistration { get; set; }
        public IEnumerable<string> Errors { get; set; }
    }

در صورت استفاده از Annotation ها، پکیج زیر را از Nuget نصب کنید:

System.ComponentModel.Annotations

حال در پروژه سرور Controller با نام Accounts ایجاد می کنیم. در این Controller امکان Register شدن کاربر را فراهم می کنیم:

[Route("api/[controller]")]
    [ApiController]
    public class AccountsController : ControllerBase
    {
        private readonly UserManager<IdentityUser> _userManager;

        public AccountsController(UserManager<IdentityUser> userManager)
        {
            _userManager = userManager;
        }

        [HttpPost("Registration")]
        public async Task<IActionResult> RegisterUser([FromBody] UserForRegistrationDto userForRegistration)
        {
            if (userForRegistration == null || !ModelState.IsValid)
                return BadRequest();

            var user = new IdentityUser { UserName = userForRegistration.Email, Email = userForRegistration.Email };

            var result = await _userManager.CreateAsync(user, userForRegistration.Password);
            if (!result.Succeeded)
            {
                var errors = result.Errors.Select(e => e.Description);

                return BadRequest(new RegistrationResponseDto { Errors = errors });
            }

            return StatusCode(201);
        }
    }

در این کنترلر با استفاده از RegisterUser امکان ثبت نام کاربر را پیاده سازی کردیم. تا اینجا موارد مورد نیاز در سمت Server انجام شده اند. در ادامه به سمت کلاینت یعنی Blazor WebAssembly می پردازیم.

پیاده سازی سرویس Registration در Blazor WebAssembly

ابتدا پوشه ای با نام Services در پروژه کلاینت ایجاد می کنیم. سپس یک interface با نام IAuthenticationService در آن ایجاد می کنیم. در این اینترفیس سرویس مورد نیاز برای اتصال به سرور و Register در کنترلر Account قرار دارد.

public interface IAuthenticationService
    {
        Task<RegistrationResponseDto> RegisterUser(UserForRegistrationDto userForRegistration);
    }

حال کلاس AuthenticationService را برای پیاده سازی اینترفیس فوق ایجاد می کنیم:

public class AuthenticationService : IAuthenticationService
    {
        private readonly HttpClient _client;

        public AuthenticationService(HttpClient client)
        {
            _client = client;
        }


        public async Task<RegistrationResponseDto> RegisterUser(UserForRegistrationDto userForRegistration)
        {
            var content = JsonSerializer.Serialize(userForRegistration);
            var bodyContent = new StringContent(content, Encoding.UTF8, "application/json");

            var registrationResult = await _client.PostAsync("https://localhost:44355/api/accounts/registration", bodyContent);
            var registrationContent = await registrationResult.Content.ReadAsStringAsync();

            if (!registrationResult.IsSuccessStatusCode)
            {
                var result = JsonSerializer.Deserialize<RegistrationResponseDto>(registrationContent, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
                return result;
            }

            return new RegistrationResponseDto { IsSuccessfulRegistration = true };
        }
    }

به صورت خلاصه از یک Typed Instance در HTTP Client استفاده کردیم تا Request لازم برای ثبت نام را به سرور ارسال کنیم. اگر موفقیت آمیز بود یک نمونه از RegistrationResponseDto با پراپرتی IsSuccessfulRegistration با مقدار true بر میگردانیم. در غیر این صورت، response را deserialize کرده و بر می گردانیم.

حال برای استفاده از این سرویس باید آن را در Program.cs رجیستر کنیم:

builder.Services.AddScoped<IAuthenticationService, AuthenticationService>();

ساخت فرم ثبت نام در Blazor WebAssembly

برای ساخت فرم ثبت نام، در پوشه Pages یک Razor Component جدید با نام Register ایجاد می کنیم:

@page "/register"
@using LearnAuthBlazor.Client.Services
@using LearnAuthBlazor.Shared.DTOs
@inject IAuthenticationService AuthenticationService
@inject NavigationManager NavigationManager


    <h3>Registration</h3>

    @if (ShowRegistrationErros)
    {
        <div class="alert alert-danger" role="alert">
            @foreach (var error in Errors)
            {
                <p>@error</p>
            }
        </div>
    }

    <EditForm Model="_userForRegistration" OnValidSubmit="Submit" class="card card-body bg-light mt-5">
        <DataAnnotationsValidator />
        <div class="form-group row">
            <label for="email" class="col-md-2 col-form-label">Email:</label>
            <div class="col-md-10">
                <InputText id="email" class="form-control" @bind-Value="_userForRegistration.Email" />
                <ValidationMessage For="@(() => _userForRegistration.Email)" />
            </div>
        </div>

        <div class="form-group row">
            <label for="password" class="col-md-2 col-form-label">Password:</label>
            <div class="col-md-10">
                <InputText type="password" id="password" class="form-control" @bind-Value="_userForRegistration.Password" />
                <ValidationMessage For="@(() => _userForRegistration.Password)" />
            </div>
        </div>

        <div class="form-group row">
            <label for="confirm" class="col-md-2 col-form-label">Confirm Password:</label>
            <div class="col-md-10">
                <InputText type="password" id="confirm" class="form-control" @bind-Value="_userForRegistration.ConfirmPassword" />
                <ValidationMessage For="@(() => _userForRegistration.ConfirmPassword)" />
            </div>
        </div>

        <div class="row">
            <div class="col-md-12 text-right">
                <button type="submit" class="btn btn-success">Register</button>
            </div>
        </div>
    </EditForm>

@code
{
    private UserForRegistrationDto _userForRegistration = new UserForRegistrationDto();


    public bool ShowRegistrationErros { get; set; }
    public IEnumerable<string> Errors { get; set; }

    public async Task Submit()
    {
        ShowRegistrationErros = false;

        var result = await AuthenticationService.RegisterUser(_userForRegistration);
        if (!result.IsSuccessfulRegistration)
        {
            Errors = result.Errors;
            ShowRegistrationErros = true;
        }
        else
        {
            NavigationManager.NavigateTo("/");
        }
    }
}

در صورت موفقیت آمیز بودن ثبت نام، کاربر به صفحه Index منتقل خواهد شد. در قسمت های بعدی، پس از ساخت فرم لاگین، این قسمت را ویرایش می کنیم تا کاربر به فرم لاگین منتقل شود.

ساخت یک Component برای نمایش لینک های Login و Register

در پوشه Shared در پروژه کلاینت، کامپوننتی با عنوان AuthLinks لینک ایجاد می کنیم:

<AuthorizeView>
    <Authorized>
        Hello, @context.User.Identity.Name!
        <a href="Logout">Log out</a>
    </Authorized>
    <NotAuthorized>
        <a href="Registration">Register</a>
        <a href="Login">Log in</a>
    </NotAuthorized>
</AuthorizeView>

تمامی موارد موجود در این کامپوننت، در قسمت قبل آموزش با عنوان “بررسی AuthenticationStateProvider در Blazor WebAssembly” بررسی شدند.

توجه داشته باشید در کلاس FakeAuthStateProvider باید کاربر anonymous برگردانده شود. در قسمت های بعدی این کلاس را با یک AuthStateProvider واقعی جایگزین می کنیم.
در پایان، در کامپوننت MainLayout.razor از کامپوننت AuthLinks استفاده می کنیم:

@inherits LayoutComponentBase

<div class="sidebar">
    <NavMenu />
</div>

<div class="main">
    <div class="top-row px-4">
        <a href="http://blazor.net" target="_blank" class="ml-md-auto">About</a>
        <AuthLinks></AuthLinks>
    </div>

    <div class="content px-4">
        @Body
    </div>
</div>

بررسی صحت موارد انجام شده

با اجرای پروژه، خواهیم دید لینک ثبت نام در منو بالای سایت قرار گرفته و کار می کند:

Register Form In Blazor

همچنین validation های قرار داده شده هم فعال هستند:

Form Validations In Client

در صورتی که ثبت نام موفق نباشد، پیام متناسبی دریافت خواهید کرد:

Form Validations

در صورت ثبت نام موفق به صفحه Index منتقل می شوید. با بررسی پایگاه داده برنامه خواهید دید کاربر ایجاد شده است.

 

جمع بندی

در این قسمت از آموزش احراز هویت در بلیزر آموختیم:

  • چگونه از ASP .Net Identity در ASP .Net Core API استفاده کنیم.
  • نحوه ثبت نام کاربر در دو سمت کلاینت و سرور را بررسی کردیم.
  • فرم لازم برای ثبت نام کاربر به چه شکل قابل پیاده سازی است.

در قسمت های آینده از یک AuthStateProvider واقعی استفاده می کنیم و همچنین، امکان ورود و خروج کاربر را نیز فراهم خواهیم کرد.

 

منابع:

سورس کد در گیت هاب سافت آموز : https://github.com/softamoz/LearnAuthBlazor

منبع : https://code-maze.com/blazor-webassembly-registration-aspnetcore-identity