在 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()
显然会重定向到“/” (网站默认页面)。
我有两个问题:
- 在 Razor 页面 POST 例程中,如何重新显示 Razor 页面?
return Page()
在 POST 例程中实际做了什么?
这是重现我看到的行为的代码:
- 在 VS 2022 中,创建一个新的 ASP.NET 核心 Web 应用程序(模型-视图-控制器) 项目。
- 框架:.NET 6.0
- 认证类型:个人账户
- 在包管理器 window 中,键入:update-database
- 运行 申请。创建一个新帐户并验证您是否可以登录。
- 在解决方案资源管理器中右键单击项目,然后 select 添加>新建基架项
- Select
Identity
然后点击 Add
- Select
Account\Login
并单击下拉箭头 select ApplicationDbContext
数据上下文 class.
- 在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 });
}
}
}
运行 项目。 [Authorize]
属性强制您在重定向到索引页面之前登录。
现在,打开 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();
}
- 运行 应用程序。新的
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>
输出:
如果一切正常,它将登录到站点。
另外,您可以根据自己的需要修改逻辑。
我修改了标准 ASP.NET 核心登录页面 POST 例程 (Areas/Identity/Pages/Account/Login.cshtml.cs
) 中的逻辑。 在 用户成功登录后,我有额外的逻辑可能会拒绝登录尝试。如果该附加逻辑拒绝登录尝试,我想重新显示登录页面,并在页面上显示适当的消息。
我的问题是,与 MVC 控制器不同,在 MVC 控制器中,在 POST 操作中调用 return View()
会重新显示视图,而在 Razor 页面中调用 return Page()
显然会重定向到“/” (网站默认页面)。
我有两个问题:
- 在 Razor 页面 POST 例程中,如何重新显示 Razor 页面?
return Page()
在 POST 例程中实际做了什么?
这是重现我看到的行为的代码:
- 在 VS 2022 中,创建一个新的 ASP.NET 核心 Web 应用程序(模型-视图-控制器) 项目。
- 框架:.NET 6.0
- 认证类型:个人账户
- 在包管理器 window 中,键入:update-database
- 运行 申请。创建一个新帐户并验证您是否可以登录。
- 在解决方案资源管理器中右键单击项目,然后 select 添加>新建基架项
- Select
Identity
然后点击Add
- Select
Account\Login
并单击下拉箭头 selectApplicationDbContext
数据上下文 class.
- Select
- 在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 });
}
}
}
运行 项目。
[Authorize]
属性强制您在重定向到索引页面之前登录。现在,打开
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();
}
- 运行 应用程序。新的
return Page()
语句出现在 用户通过身份验证 之后,导致应用程序愉快地显示Index
页面。它没有像我预期的那样重新显示显示模型错误的登录页面。
成功调用 SignInAsync
后,如果您想 return 到登录页面,只需添加一个 SignOutAsyncvar 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>
输出:
如果一切正常,它将登录到站点。
另外,您可以根据自己的需要修改逻辑。