Hello, I'm moving from ASP.net Blazor standard Identity UI to a Radzen-based set of pages. (Using ASP.net 8).
I have a problem with the Login page.
My code is as follows:
App.razor
<!DOCTYPE html>
<html lang="en" data-bs-theme="light">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="BlazId.styles.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<link rel="stylesheet" href="_content/Radzen.Blazor/css/material-base.css">
<HeadOutlet @rendermode="@InteractiveServer" />
</head>
<body>
<Routes @rendermode="@InteractiveServer" />
<script src="_framework/blazor.web.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="_content/Radzen.Blazor/Radzen.Blazor.js"></script>
</body>
</html>
In the file SideMenu.razor
the following line calls the login page:
<RadzenPanelMenu>
...
<AuthorizeView>
<Authorized>
...
</Authorized>
<NotAuthorized>
<RadzenPanelMenuItem Text="Login" Icon="login" Path="Account/Login2"></RadzenPanelMenuItem>
</NotAuthorized>
</AuthorizeView>
</RadzenPanelMenu>
The Login2.razor
page is the following, but it's totally irrelevant for the issue I have. For sake of simplicity, I'll bypass it in my example here
@page "/Account/Login2"
@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Authentication
@using Microsoft.AspNetCore.Identity
@using BlazId.Data
@using Account
@inject SignInManager<MyUser> SignInManager
@inject ILogger<Login2> Logger
@inject NavigationManager NavigationManager
@inject IdentityRedirectManager RedirectManager
<RadzenCard class="rz-my-12 rz-mx-auto rz-p-4 rz-p-md-12" style="max-width: 600px;">
<RadzenTemplateForm Data=@Input Method="post">
@* <AntiforgeryToken /> *@
<RadzenLogin AllowRegister="true" AllowResetPassword="true" Username=@userName Password=@password
AllowRememberMe="true" RememberMe="@rememberMe"
Login=@(args => OnLogin(args, "Login with default values"))
ResetPassword=@(args => OnResetPassword(args, "Login with default values"))
Register=@(args => OnRegister("Login with default values")) />
</RadzenTemplateForm>
</RadzenCard>
@code {
string userName = "user@domain.com";
string password = "Welcome1";
bool rememberMe = true;
async void Submit(InputModel args)
{
await LoginUser(args);
}
protected override async Task OnInitializedAsync()
{
if (HttpMethods.IsGet(HttpContext.Request.Method))
{
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
}
}
public async Task OnLogin(LoginArgs args, string name)
{
// console.Log($"{name} -> Username: {args.Username}, password: {args.Password}, remember me: {args.RememberMe}");
var result = await SignInManager.PasswordSignInAsync(args.Username, args.Password, args.RememberMe, lockoutOnFailure: false);
if (result.Succeeded)
{
Logger.LogInformation("User logged in.");
// RedirectManager.RedirectTo(ReturnUrl);
}
}
void OnRegister(string name)
{
// console.Log($"{name} -> Register");
}
void OnResetPassword(string value, string name)
{
// console.Log($"{name} -> ResetPassword for user: {value}");
}
[SupplyParameterFromForm]
private InputModel Input { get; set; } = new();
[SupplyParameterFromQuery]
private string? ReturnUrl { get; set; }
[SupplyParameterFromQuery]
private string? RedirectUrl { get; set; }
[CascadingParameter]
private HttpContext HttpContext { get; set; } = default!;
public async Task LoginUser(InputModel model)
{
// 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.");
// RedirectManager.RedirectTo(ReturnUrl);
}
else if (result.RequiresTwoFactor)
{
// RedirectManager.RedirectTo(
// "Account/LoginWith2fa",
// new() { ["returnUrl"] = ReturnUrl, ["rememberMe"] = Input.RememberMe });
}
else if (result.IsLockedOut)
{
Logger.LogWarning("User account locked out.");
// RedirectManager.RedirectTo("Account/Lockout");
}
else
{
// errorMessage = "Error: Invalid login attempt.";
}
}
public sealed class InputModel
{
[Required]
[EmailAddress]
public string Email { get; set; } = "";
[Required]
[DataType(DataType.Password)]
public string Password { get; set; } = "";
[Display(Name = "Remember me?")]
public bool RememberMe { get; set; }
}
}
The Login2.razor page refers to the follwing _imports.razor
file
@using BlazId.Components.Account.Shared
@layout AccountLayout
The big issue comes in the AccountLayout.razor
file:
@inherits LayoutComponentBase
@layout BlazId.Components.Layout.MainLayout
@inject NavigationManager NavigationManager
@if (HttpContext is null)
{
<p>Loading...</p>
}
else
{
<p>LOADED @HttpContext.ToString();</p>
@* @Body *@
}
@code {
[CascadingParameter]
public HttpContext? HttpContext { get; set; } = default!;
protected override void OnParametersSet()
{
if (HttpContext is null)
{
// If this code runs, we're currently rendering in interactive mode, so there is no HttpContext.
// The identity pages need to set cookies, so they require an HttpContext. To achieve this we
// must transition back from interactive mode to a server-rendered page.
NavigationManager.Refresh(forceReload: true);
}
}
}
Here I intentionally commented out the @Body
just to avoid any overhead from the Layout2.razor
content, because the bad behavior is the same:
It continues to reload the page, once with a HttpContext
set to null
and once with HttpContext
set to the proper value. It continues to flash between the two situations indefinitely. If I stop the page using the browser's 'X' (stop) button, sometimes it stops on "Loading...", sometimes it stops on the correct page. When it stops in the correct side, I can properly login and the cookies are properly set.
If in the App.razor page, I change
<HeadOutlet @rendermode="@InteractiveServer" />
<Routes @rendermode="@InteractiveServer" />
with
<HeadOutlet />
<Routes />
this part work properly, but (as you could imagine) the modal DialogService
like DialogService.Alert(...)
don't work any more.
How can I have both modal Dialogs AND proper Login page loaded correctly ?