.Net Core Entity Framework 电子邮件确认 'Click Here' link 不更新 'EmailConfirmed' DB 属性

.Net Core Entity Framework Email Confirmation 'Click Here' link does not update 'EmailConfirmed' DB property

我已按照 Microsoft 此处的说明在我的 .Net 5.0 应用程序中为我的用户注册电子邮件确认设置​​了 SendGrid:http://go.microsoft.com/fwlink/?LinkID=532713

一切正常,直到用户在其注册确认电子邮件中单击确认 link。

这个问题是由我确认的杂散放大器引起的 link。我正在尝试了解它的来源以及如何将其删除。

当新用户在 Register.cshtml 页面上单击 'Submit' 时,他们会成功定向到 RegisterConfirmation.cshtml 页面,并且他们的收件箱中会收到电子邮件。

实际行为:

用户点击电子邮件中的 link 并点击 ConfirmEmail 页面。

用户被重定向到 /Index 页面。

数据库中的 EmailConfirmed 布尔值未更新。

如果我在我的控制器中注释掉到 /Index 的重定向,则会收到如下所示的空值错误。

//if (userId == null || code == null)
//{
//    return RedirectToPage("/Index", new { culture });
//}

但是查询标签显示userId和code是正确的。

预期行为:

用户单击电子邮件中的 link。

数据库中的 EmailConfirmed bool IS 已更新。

用户看到带有成功消息的 ConfirmEmail 页面。

ConfirmEmail.cshtml.cs

    using System;
snip...
    
    namespace MyApp.Areas.Identity.Pages.Account
    {
        [AllowAnonymous]
        public class ConfirmEmailModel : PageModel
        {
            private readonly UserManager<ApplicationUser> _userManager;
            private readonly ISharedCultureLocalizer _loc;
            private readonly string culture;
            readonly ConfirmEmailPageLocSourceNames _locSourceConfirmEmailPageNameReferenceLibrary = new ConfirmEmailPageLocSourceNames();
            readonly SharedCrossPageLocSourceNames _locSourceSharedCrossPageNameReferenceLibrary = new SharedCrossPageLocSourceNames();
    
            public ConfirmEmailModel(UserManager<ApplicationUser> userManager, ISharedCultureLocalizer loc)
            {
                _userManager = userManager;
                _loc = loc;
                culture = System.Globalization.CultureInfo.CurrentCulture.Name;
            }
    
            [TempData]
            public string StatusMessage { get; set; }
snip...
    
            public async Task<IActionResult> OnGetAsync(string userId, string code)
            {
snip...
    
                if (userId == null || code == null)
                {
                    return RedirectToPage("/Index", new { culture });
                }
    
                var user = await _userManager.FindByIdAsync(userId);
                if (user == null)
                {
                    var msg = _loc.GetLocalizedString("Unable to load user with ID '{0}'.", userId);
                    return NotFound(msg);
                }
    
                code = Encoding.UTF8.GetString(WebEncoders.Base64UrlDecode(code));
                var result = await _userManager.ConfirmEmailAsync(user, code);
                if (result.Succeeded)
                {
                    var msg = _loc.GetLocalizedString("Thank you for confirming your email.");
                    TempData.Success(msg);
                }
                else
                {
                    var msg = _loc.GetLocalizedString("Error confirming your email.");
                    TempData.Danger(msg);
                }
                return Page();
            }
        }
    }

RegisterConfirmation.cshtml.cs

using Microsoft.AspNetCore.Authorization;
snip...

namespace MyApp.Areas.Identity.Pages.Account
{
    [AllowAnonymous]
    public class RegisterConfirmationModel : PageModel
    {
        private readonly UserManager<ApplicationUser> _userManager;
        private readonly IEmailSender _sender;
        readonly RegisterConfPageLocSourceNames _locSourceRegisterConfPageNameReferenceLibrary = new RegisterConfPageLocSourceNames();
        readonly SharedCrossPageLocSourceNames _locSourceSharedCrossPageNameReferenceLibrary = new SharedCrossPageLocSourceNames();

        public RegisterConfirmationModel(UserManager<ApplicationUser> userManager, IEmailSender sender)
        {
            _userManager = userManager;
            _sender = sender;
        }

        public string Email { get; set; }
 snip...

        public async Task<IActionResult> OnGetAsync(string email)
        {
            PageTabTitle = _locSourceRegisterConfPageNameReferenceLibrary.GetLocSourcePageTabTitleNameReferenceForRegisterConfPage();
 snip...

            if (email == null)
            {
                return RedirectToPage("/Index");
            }

            var user = await _userManager.FindByEmailAsync(email);
            if (user == null)
            {
                return NotFound($"Unable to load user with email '{email}'.");
            }

            Email = email;

            return Page();
        }
    }
}

Register.cshtml.cs

            var code = await _userManager.GenerateEmailConfirmationTokenAsync(user);
            Debug.WriteLine("**************** " + code);
            code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code));
            Debug.WriteLine("**************** 1 *** " + code);
            var callbackUrl = Url.Page("/Account/ConfirmEmail", pageHandler: null, values: new { area = "Identity", userId = user.Id, code = code, culture }, protocol: Request.Scheme);

            var mailHeader = _loc.GetLocalizedString("Confirm your GatheringForGood email");
            var mailBody = _loc.GetLocalizedString(CultureInfo.CurrentCulture.Name, "Please confirm your GatheringForGood account by <a href='{0}'>clicking here</a>.", HtmlEncoder.Default.Encode(callbackUrl));
                await _emailSender.SendEmailAsync(Input.Email, mailHeader, mailBody);

EmailSender.cs

中的函数
public Task SendEmailAsync(string email, string subject, string message)
{
    return Execute(Options.SendGridKey, subject, message, email);
}

public Task Execute(string apiKey, string subject, string message, string email)
{
    var client = new SendGridClient(apiKey);
    var msg = new SendGridMessage()
    {
        From = new EmailAddress("info@myemail.com", Options.SendGridUser),
        Subject = subject,
        PlainTextContent = message,
        HtmlContent = message
    };
    msg.AddTo(new EmailAddress(email));

    // Disable click tracking.
    // See https://sendgrid.com/docs/User_Guide/Settings/tracking.html
    msg.SetClickTracking(false, false);

    return client.SendEmailAsync(msg);
}

更新:

在我的日志中的回调字符串中说

userId=362c17ae-7854-42fb-91c3-efb19cc875f2&code=

但是在 gmail 中收到的 link 说

userId=362c17ae-7854-42fb-91c3-efb19cc875f2&amp;code=

在 Postman 中测试过,这绝对是问题所在。我需要电子邮件正文中的 link 看起来像这样。注意没有 amp;

https://localhost:44305/en/Identity/Account/ConfirmEmail?userId=d62d4727-f6ce-493c-bcf3-eb85a50a914f&code=Q2ZESjhKbkE2NU5BVk85S2drRnMvV3VtZXBySVFlTHZrQlNvUU9xbUxrYWQ5NjFDV0NvZGY1eHVCK01SSHVIL3EwMjEwYk8rU1lLaHJ4UHF1VS84RjJQTThBWlY4VHZTcGcrQVpiZU9wWHFyWnlsVkFpSFVUV3lIMGJjaG14aFJKQkgxNjZoQkVNM3ZETnR2WHhoZmx0ZnhQR095azdDREJVZVdJN01CTTRCcFptejJvSURjNHloZHdxRDl0UCs0eEdic1NMK25wbnFqb0xhdHFoR3M3T3BkTElhbG5TVU9obTJaTFpvc0xUb0RINzM2UmFBTVlrakZWL2VsV0YvUEJSaE1HQT09

看起来有值的变量是 amp;code不是代码。你有没有机会在某个地方有 2 个符号?是的,你知道 -

b3a&amp;code=Q2ZE

在确认电子邮件

中找出您的代码生成此类错误的原因link

似乎生成的 link 也在确认中对查询字符串参数之间的 & 进行编码 URL:

// This is the problem
...&amp;code=Q2ZESjhKb...

//it should be lilke below
...&code=Q2ZESjhKb...

已修复! 最后,我只需要从我的 mailBody 字符串中删除 Html 编码。

我替换了:

var mailBody = _loc.GetLocalizedString(CultureInfo.CurrentCulture.Name, "Please confirm your GatheringForGood account by <a href='{0}'>clicking here</a>.", HtmlEncoder.Default.Encode(callbackUrl));

与:

var mailBody = _loc.GetLocalizedString(CultureInfo.CurrentCulture.Name, "Please confirm your GatheringForGood account by <a href='" + callbackUrl + "'>clicking here</a>.");

感谢您的帮助。