فعال سازی از طریق ایمیل در ASP.NET Core قسمت اول

ایمیل

بخش اول از آموزش فعال سازی از طریق ایمیل در ۲.۲ ASP.NET Core ، میخواهیم یک برنامه ی وب ASP.NET Core 2.2 جهت بروز رسانی یک ایمیل تأیید شده ایجاد کنیم.

در این جا مراحل چارچوب بندی و تغییر Identity جهت نیاز به یک ایمیل تأیید شده پیش از ورود به سیستم آورده شده است.در واقع میخواهیم کاربر را از طریق ارسال ایمیل فعال سازی فعال کنیم.

بکارگیری کد

پیش نیازها:

 ویژوال استودیو نسخه ی ۲۰۱۷، ۱۵.۹ یا بالاتر
ویژوال استودیو برای Mac نسخه ی ۷.۷ یا بالاتر
افزونه ی سی شارپ ویژوال استودیو کد (VS Code) نسخه ی ۱.۱۷.۱ یا بالاتر

می توانید پروژه ی VS 2017 را دانلود کرده یا این مراحل را برای تغییر پروژه ی خود دنبال کنید.

مرحله ۱ : ایجاد Web Application

یک VS 2017 Project جدید ایجاد کنید.

ایمیل

یک ASP.NET Core Web Application جدید ایجاد کرده و Authentication را به Individual User Accounts تغییر دهید.

ایمیل

OK را کلیک کنید.

مرحله ۲ : مقداردهی اولیه ی پایگاه داده

پروژه از SQL Server Express استفاده می کند.

appsettings.json > ConnectionStrings > DefaultConnection را برای تنظیم پایگاه داده تصحیح کنید.

فرمان “Update-Database” را از Package Manager Console در VS 2017 اجرا کنید.

مرحله ۳ : چارچوب بندی Identity

بر روی نام پروژه کلیک راست کرده، Add > New Scaffold Item را انتخاب کنید.

ایمیل

در منوی سمت چپ Identity را انتخاب کنید.

ایمیل

Add را کلیک کنید.

Override all files (بازنویسی تمام فایل ها) را تیک زده و ApplicationDbContext را انتخاب کنید.

ایمیل

Add را کلیک کنید.

مرحله ۴ : جایگزینی EmailSender پیش فرض

appsettings.json را تصحیح کرده، EmailSettings را با تنظیمات سرور ایمیل خود اضافه کنید:

"EmailSettings": {
    "MailServer": "smtp.some_server.com",
    "MailPort": 587,
    "SenderName": "some name",
    "Sender": "some_email@some_server.com",
    "Password": "some_password"
}

پوشه ی جدیدی به نام Entities به پروژه اضافه کنید.

کلاس جدیدی به نام EmailSettings در Entities اضافه کنید:

public class EmailSettings
{
    public string MailServer { get; set; }
    public int MailPort { get; set; }
    public string SenderName { get; set; }
    public string Sender { get; set; }
    public string Password { get; set; }
}

پوشه ی جدیدی به نام Services به پروژه اضافه کنید.

کلاس جدیدی به نام EmailSender در Services اضافه کنید:

public interface IEmailSender
{
    Task SendEmailAsync(string email, string subject, string htmlMessage);
}

public class EmailSender : IEmailSender
{
    private readonly EmailSettings _emailSettings;

    public EmailSender(IOptions<emailsettings> emailSettings)
    {
        _emailSettings = emailSettings.Value;
    }

    public Task SendEmailAsync(string email, string subject, string message)
    {
        try
        {
            // Credentials
            var credentials = new NetworkCredential(_emailSettings.Sender, _emailSettings.Password);

            // Mail message
            var mail = new MailMessage()
            {
                From = new MailAddress(_emailSettings.Sender, _emailSettings.SenderName),
                Subject = subject,
                Body = message,
                IsBodyHtml = true
            };

            mail.To.Add(new MailAddress(email));

            // Smtp client
            var client = new SmtpClient()
            {
                Port = _emailSettings.MailPort,
                DeliveryMethod = SmtpDeliveryMethod.Network,
                UseDefaultCredentials = false,
                Host = _emailSettings.MailServer,
                EnableSsl = true,
                Credentials = credentials
            };

            // Send it...         
            client.Send(mail);
        }
        catch (Exception ex)
        {
            // TODO: handle exception
            throw new InvalidOperationException(ex.Message);
        }

        return Task.CompletedTask;
    }
}

فضای نام ها را به EmailSender.cs اضافه کنید:

using Microsoft.Extensions.Options;
using <YourProjectName>.Entities;
using System.Net;
using System.Net.Mail;

Startup.cs > ConfigureServices را تصحیح کرده، گزینه ی EmailSettings را اضافه کنید:

services.AddOptions();
services.Configure<EmailSettings>(Configuration.GetSection("EmailSettings"));

این بخش را به پایین Startup.cs > ConfigureServices اضافه کنید:

services.AddSingleton<IEmailSender, EmailSender>();

فضای نام ها را به Startup.cs اضافه کنید:

using <YourProjectName>.Entities;
using <YourProjectName>.Services;

Register.cshtml.cs، ForgotPassword.cshtml.cs و Manage\Index.cshtml.cs را جهت استفاده از فضای نام جدید EmailSender تصحیح کنید:

//using Microsoft.AspNetCore.Identity.UI.Services;
using <YourProjectName>.Services;

مرحله ۵ : نیاز به ایمیل تأیید شده و منحصر بفرد

Startup.cs > ConfigureServices را جهت استفاده از AddIdentity<IdentityUser, IdentityRole> به جای AddDefaultIdentity<IdentityUser> تصحیح کنید:

//services.AddDefaultIdentity<IdentityUser>()
services.AddIdentity<IdentityUser, IdentityRole>(config =>
    {
        config.SignIn.RequireConfirmedEmail = true;
        config.User.RequireUniqueEmail = true;
    })
    .AddDefaultUI(UIFramework.Bootstrap4)
    .AddEntityFrameworkStores<ApplicationDbContext>();
    .AddDefaultTokenProviders();

services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
    .AddRazorPagesOptions(options =>
    {
        options.AllowAreas = true;
        options.Conventions.AuthorizeAreaFolder("Identity", "/Account/Manage");
        options.Conventions.AuthorizeAreaPage("Identity", "/Account/Logout");
    });

services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = $"/Identity/Account/Login";
    options.LogoutPath = $"/Identity/Account/Logout";
    options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
});

یک صفحه ی razor به نام CheckEmail در Areas\Identity\Pages\Account اضافه کنید:

ایمیل

سپس:

ایمیل

CheckEmail.cshtml را تصحیح کنید:

@page
@model CheckEmailModel
@{
    ViewData["Title"] = "Check email";
}


<h2>@ViewData["Title"]</h2>



    Please check your inbox to confirm your account.


CheckEmail.cshtml.cs را تصحیح کرده، آذین AllowAnonymous را اضافه کنید:

[AllowAnonymous]
public class CheckEmailModel : PageModel
{
    public void OnGet()
    {
    }
}

فضای نام را به CheckEmail.cshtml.cs اضافه کنید:

using Microsoft.AspNetCore.Authorization;

Register.cshtml.cs > OnPostAsync را تصحیح کنید:

//await _signInManager.SignInAsync(user, isPersistent: false);
//return LocalRedirect(returnUrl);
return RedirectToPage("./CheckEmail");

مرحله ۶ : افزودن Login Name برای UserName

Areas\Identity\Pages\Account\Register.cshtml.cs را تصحیح کرده، ویژگی UserName را به InputModel اضافه کنید:

[Required]
[StringLength(100, ErrorMessage = "The {0} must be at least {2} and 
                                   at max {1} characters long.", MinimumLength = 6)]
[Display(Name = "Login Name")]
public string UserName { get; set; }

Register.cshtml را تصحیح کرده، ورودی UserName را اضافه کنید:


<div class="form-group">
    <label asp-for="Input.UserName"></label>
    <input asp-for="Input.UserName" class="form-control" />
    <span asp-validation-for="Input.UserName" class="text-danger"></span>
</div<

Register.cshtml.cs > OnPostAsync را تصحیح کرده، از Input.UserName در سازنده ی جدید IdentityUser استفاده کنید:

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

Login.cshtml.cs > InputModel را تصحیح کرده، Email را با UserName جایگزین کنید:

public class InputModel
{
    [Required]
    [Display(Name = "Login Name")]
    public string UserName { get; set; }

    [Required]
    [DataType(DataType.Password)]
    public string Password { get; set; }

    [Display(Name = "Remember me?")]
    public bool RememberMe { get; set; }
}

Login.cshtml.cs > OnPostAsync را تصحیح کرده، Input.Email را با Input.UserName جایگزین کنید:

var result = await _signInManager.PasswordSignInAsync
  (Input.UserName, Input.Password, Input.RememberMe, lockoutOnFailure: true);

Login.cshtml را تصحیح کرده، Email را با UserName در aspfor جایگزین کنید:


<div class="form-group">
    <label asp-for="Input.UserName"></label>
    <input asp-for="Input.UserName" class="form-control" />
    <span asp-validation-for="Input.UserName" class="text-danger"></span>
</div<

مرحله ۷ : افزودن صفحه ی ایمیل تأیید نشده

یک صفحه ی razor به نام UnconfirmedEmail در Areas\Identity\Pages\Account اضافه کنید:

UnconfirmedEmail.cshtml را تصحیح کنید:

@page "{userId}"
@model UnconfirmedEmailModel
@{
    ViewData["Title"] = "Confirm your email.";
}


<h2>@ViewData["Title"]</h2>


<h4>Enter your email.</h4>


<hr />



<div class="row">
    
<div class="col-md-4">
        
<form method="post">
            
<div asp-validation-summary="All" class="text-danger"></div>

            
<div class="form-group">
                <label asp-for="Input.Email"></label>
                <input asp-for="Input.Email" class="form-control" />
                <span asp-validation-for="Input.Email" class="text-danger"></span>
            </div>

            <button type="submit" class="btn btn-primary">Submit</button>
        </form>

    </div>

</div>

	

@section Scripts {
    <partial name="_ValidationScriptsPartial" />
}

UnconfirmedEmail.cshtml.cs را تصحیح کنید:

using <YourProjectName>.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Text.Encodings.Web;
using System.Threading.Tasks;

namespace <YourProjectName>.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class UnconfirmedEmailModel : PageModel
    {
        private readonly UserManager<IdentityUser> _userManager;
        private readonly IEmailSender _emailSender;

        public UnconfirmedEmailModel(UserManager<IdentityUser> userManager, IEmailSender emailSender)
        {
            _userManager = userManager;
            _emailSender = emailSender;
        }

        [TempData]
        public string UserId { get; set; }

        [BindProperty(SupportsGet = true)]
        public InputModel Input { get; set; }

        public class InputModel
        {
            [Required]
            [EmailAddress]
            public string Email { get; set; }
        }

        public async Task OnGetAsync(string userId)
        {
            UserId = userId;
            var user = await _userManager.FindByIdAsync(userId);
            Input.Email = user.Email;
            ModelState.Clear();
        }

        public async Task<IActionResult> OnPostAsync()
        {
            if (ModelState.IsValid)
            {
                var user = await _userManager.FindByIdAsync(UserId);

                if (user == null)
                {
                    // Don't reveal that the user does not exist
                    return RedirectToPage("./CheckEmail");
                }

                if (user.Email != Input.Email)
                {
                    var errors = new List<IdentityError>();
                    if (_userManager.Options.User.RequireUniqueEmail)
                    {
                        var owner = await _userManager.FindByEmailAsync(Input.Email);
                        if (owner != null && !string.Equals
                           (await _userManager.GetUserIdAsync(owner), 
                            await _userManager.GetUserIdAsync(user)))
                        {
                            ModelState.AddModelError(string.Empty, 
                            new IdentityErrorDescriber().DuplicateEmail(Input.Email).Description);
                            return Page();
                        }
                    }

                    await _userManager.SetEmailAsync(user, Input.Email);
                }
                
                var result = await _userManager.UpdateSecurityStampAsync(user);
                if (!result.Succeeded)
                {
                    foreach (var error in result.Errors)
                    {
                        ModelState.AddModelError(string.Empty, error.Description);
                        return Page();
                    }
                }
                
                var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
                
                var callbackUrl = Url.Page(
                    "/Account/ConfirmEmail",
                    pageHandler: null,
                    values: new { userId = user.Id, code = code },
                    protocol: Request.Scheme);

                await _emailSender.SendEmailAsync(Input.Email, "Confirm your email",
                    $"Please confirm your account by 
                    <a href='{HtmlEncoder.Default.Encode(callbackUrl)}'>clicking here</a>.");

                return RedirectToPage("./CheckEmail");
            }

            return Page();
        }
    }
}

مرحله ۸ : تغییر Login

UserManager را به Areas\Identity\Pages\Account\Login.cshtml.cs تزریق کنید:

private readonly UserManager<IdentityUser> _userManager;
private readonly SignInManager<IdentityUser> _signInManager;
private readonly ILogger<LoginModel> _logger;

public LoginModel(
    UserManager<IdentityUser> userManager,
    SignInManager<IdentityUser> signInManager,
    ILogger<LoginModel> logger)
{
    _userManager = userManager;
    _signInManager = signInManager;
    _logger = logger;
}

ویژگی های ShowResend و UserId را به Login.cshtml.cs اضافه کنید:

public bool ShowResend { get; set; }
public string UserId { get; set; }

این را پس از result.IsLockedOut به Login.cshtml.cs > OnPostAsync اضافه کنید:

if (result.IsNotAllowed)
{
    _logger.LogWarning("User email is not confirmed.");
    ModelState.AddModelError(string.Empty, "Email is not confirmed.");
    var user = await _userManager.FindByNameAsync(Input.UserName);
    UserId = user.Id;
    ShowResend = true;
    return Page();
}

Login.cshtml را پس از asp-validation-summary تصحیح کنید:

@{
    if (Model.ShowResend)
    {
        

            <a asp-page="./UnconfirmedEmail" asp-route-userId="@Model.UserId">Resend verification?</a>
        

    }
}

مرحله ۹ : تغییر تأیید ایمیل

ویژگی ShowInvalid را به Areas\Identity\Pages\Account\ConfirmEmail.cshtml.cs اضافه کنید:

public bool ShowInvalid { get; set; }

ConfirmEmail.cshtml.cs > OnGetAsync را تصحیح کنید:

if (!result.Succeeded)
{
    //throw new InvalidOperationException($"Error confirming email for user with ID '{userId}':");
    foreach (var error in result.Errors)
    {
        ModelState.AddModelError(string.Empty, error.Description);
    }
    ShowInvalid = true;
}

ConfirmEmail.cshtml را تصحیح کنید:


<div asp-validation-summary="All" class="text-danger"></div>

@{
    if (Model.ShowInvalid)
    {
        

            Error confirming your email.
        

        

            If you can login, try updating your email again.
            If you cannot login, try resend verification.
        

    }
    else
    {
        

            Thank you for confirming your email.
        

    }
}

پروژه را ساخته و آزمایش کنید.

نکات جالب

توجه به UpdateSecurityStampAsync پیش از GenerateEmailConfirmationTokenAsync.

این مورد هر کد قبلی ارسال شده به کاربر را نامعتبر می کند.

Forgot Password و Reset Password همچنان از Email برای یافتن کاربر استفاده می کند. اکنون، امکان یافتن کاربر از طریق UserName را دارید.


زهره سلطانیان

نوشته‌های مرتبط

دیدگاه‌ها

*
*

این سایت از اکیسمت برای کاهش هرزنامه استفاده می کند. بیاموزید که چگونه اطلاعات دیدگاه های شما پردازش می‌شوند.