在 ASP.NET Core razor 登录页面中,如何在 POST 处理程序中重新显示登录页面?

In ASP.NET Core razor Login page, how to redisplay the Login page in the POST handler?

我修改了标准 ASP.NET 核心登录页面 POST 例程 (Areas/Identity/Pages/Account/Login.cshtml.cs) 中的逻辑。 用户成功登录后,我有额外的逻辑可能会拒绝登录尝试。如果该附加逻辑拒绝登录尝试,我想重新显示登录页面,并在页面上显示适当的消息。

我的问题是,与 MVC 控制器不同,在 MVC 控制器中,在 POST 操作中调用 return View() 会重新显示视图,而在 Razor 页面中调用 return Page() 显然会重定向到“/” (网站默认页面)。

我有两个问题:

  1. 在 Razor 页面 POST 例程中,如何重新显示 Razor 页面?
  2. return Page() 在 POST 例程中实际做了什么?

这是重现我看到的行为的代码:

  1. 在 VS 2022 中,创建一个新的 ASP.NET 核心 Web 应用程序(模型-视图-控制器) 项目。
    • 框架:.NET 6.0
    • 认证类型:个人账户
  2. 在包管理器 window 中,键入:update-database
  3. 运行 申请。创建一个新帐户并验证您是否可以登录。
  4. 在解决方案资源管理器中右键单击项目,然后 select 添加>新建基架项
    • Select Identity 然后点击 Add
    • Select Account\Login 并单击下拉箭头 select ApplicationDbContext 数据上下文 class.
  5. 在Controllers/HomeController.cs中,给Index方法添加一个[Authorize]属性:
using Microsoft.AspNetCore.Authorization; // Added this line
using Microsoft.AspNetCore.Mvc;
using System.Diagnostics;
using WebApplication3.Models;

namespace WebApplication3.Controllers
{
    public class HomeController : Controller
    {
        private readonly ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

        [Authorize] // Added this line
        public IActionResult Index()
        {
            return View();
        }

        public IActionResult Privacy()
        {
            return View();
        }

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
    }
}
  1. 运行 项目。 [Authorize] 属性强制您在重定向到索引页面之前登录。

  2. 现在,打开 Areas/Identity/Pages/Account/Login.cshtml.cs 并向 OnPostAsync() 例程添加一些代码,模拟用户成功验证后检测到错误的情况。

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
    returnUrl ??= Url.Content("~/");

    ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();

    if (ModelState.IsValid) {
        // This doesn't count login failures towards account lockout
        // To enable password failures to trigger account lockout, set lockoutOnFailure: true
        var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
        if (result.Succeeded) {
            // User is logged in, but we want to deny the login for some reason
            ModelState.AddModelError("", "Some login error");   // <--------------
            return Page();                                      // <--------------

        // <snip>
    }

    // If we got this far, something failed, redisplay form
    return Page();
}
  1. 运行 应用程序。新的 return Page() 语句出现在 用户通过身份验证 之后,导致应用程序愉快地显示 Index 页面。它没有像我预期的那样重新显示显示模型错误的登录页面。

成功调用 SignInAsync

后,如果您想 return 到登录页面,只需添加一个 SignOutAsync
var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
if (result.Succeeded) {
     
    // User is logged in, but we want to deny the login for some reason  
    await _signInManager.SignOutAsync();
    ModelState.AddModelError("", "Some login error");           
    return Page();                                      
}

TL,DR 我想我们应该看看应用程序启动时的预期流程。
启动逻辑尝试到达索引页面,但此页面具有 [授权] 属性,因此如果没有 登录用户 无论我们有什么角色,它都无法显示。

从逻辑上讲,代码流需要绕道登录页面。在这里,当它收到 POST 消息时,它会查找给定的凭据并启动导致登录或未登录用户的身份方法。

以登录用户退出登录页面意味着我们已经解决了 [Authorize] 属性的要求,否则...

现在上面的代码成功登录了用户,然后启动了一些内部逻辑,这些逻辑应该会导致 return 到登录页面。但是代码退出时用户仍然登录,因此身份引擎认为一切正常并返回导致识别过程开始的索引页面。

因此我们需要在调用 return Page();

之前添加缺少的 SignOutAsync

你说,

My problem is that, unlike an MVC controller, where calling return View() in a POST action redisplays the view, calling return Page() in a Razor page apparently redirects to "/" (the default page in the website).

我建议您在代码执行之前添加并检查额外的逻辑 if (ModelState.IsValid)

示例:

Login.cshtml.cs

public async Task<IActionResult> OnPostAsync(string returnUrl = null)
        {
            returnUrl ??= Url.Content("~/");

            ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList();

            string str = "invalid";
            ViewData["Message"] = "";
            if (str=="invalid")
            {
                ViewData["Message"] = "Something is invalid";
            }
            else
            { 
                if (ModelState.IsValid)
                {
                    // This doesn't count login failures towards account lockout
                    // To enable password failures to trigger account lockout, set lockoutOnFailure: true
                    var result = await _signInManager.PasswordSignInAsync(Input.Email, Input.Password, Input.RememberMe, lockoutOnFailure: false);
                    if (result.Succeeded)
                    {
                        _logger.LogInformation("User logged in.");
                        return LocalRedirect(returnUrl);
                    }
                    if (result.RequiresTwoFactor)
                    {
                        return RedirectToPage("./LoginWith2fa", new { ReturnUrl = returnUrl, RememberMe = Input.RememberMe });
                    }
                    if (result.IsLockedOut)
                    {
                        _logger.LogWarning("User account locked out.");
                        return RedirectToPage("./Lockout");
                    }
                    else
                    {
                        ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                        return Page();
                    }
                }
            }
            // If we got this far, something failed, redisplay form
            return Page();
        }

然后你可以在同一个登录页面上显示相应的消息(我们在上面示例的 'if' 条件中设置),如下所示。

Login.cshml

<p style="color:red; font-size:larger">
                       
      @ViewData["Message"]
                        
</p>

输出:

如果一切正常,它将登录到站点。

另外,您可以根据自己的需要修改逻辑。