相关文章推荐

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">
    <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>
    <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");
            // 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>
    <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" />
<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 ?

Another things you can try:

  • Replace RadzenTemplateForm with EditForm and see if this makes a difference.
  • Avoid setting @rendermode at Routes. Set it to <RadzenComponent /> which is in your layout instead as per our getting started instructions. I am not 100% sure that accessing HttpContext works in interactive rendering modes.
  • In any case I would start from a working configuration and make changes step by step to see what breaks the implementation.

    Hi @mpolazzo

    It may be right about HttpContext not working in InteractiveServer mode.

    My project created from scratch in Visual Studio 2022 has the following code in App.razor -

    @code {
        [CascadingParameter]
        private HttpContext HttpContext { get; set; } = default!;
        private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
            ? null
            : InteractiveServer;
    

    All my "Login" routes begin "/Account".

    As already mentioned above, I would strongly recommend creating a fresh project and using the generated code as a learning curve / starting point.

    Many regards

    Thanks for your answer.
    I tried this, the effect is that now it's only flashing (very quickly) "Loading...". The other Login page is not shown alternatively. (:frowning:).
    I'll try further... thanks.

    @korchev Thanks for your answer.
    Mine was a clean new application and I isolated the cause of breakage in

    <HeadOutlet @rendermode="@InteractiveServer" />
    <Routes @rendermode="@InteractiveServer" />
    

    instead of the standard

    <HeadOutlet  />
    <Routes  />
    

    I changed it back (without InteractiveServer) and added it to <RadzenComponent /> in MainLayout.razor as follows:

    @inject ContextMenuService ContextMenuService @inject NotificationService NotificationService <RadzenComponents @rendermode="@InteractiveServer" /> <RadzenLayout style="grid-template-areas: 'rz-sidebar rz-header' 'rz-sidebar rz-body';"> <RadzenHeader>

    Now, Login works properly (no flashing with "Loading..."), but the Dialog boxes don't work any more (original reason why I had to set InteractiveServer in App.razor).

    Hi @Paul_Ruston finally it worked!!!
    I used your @code snippet (that I also had in my App.razor), but instead of using InteractiveServer I had to use RenderModeForPage in the declarations.

    Finally, App.razor is as follows:

    <html lang="en" data-bs-theme="light">
        <HeadOutlet @rendermode="@RenderModeForPage" />
    </head>
        <Routes @rendermode="@RenderModeForPage" />
    </body>
    </html>
    @code {
        [CascadingParameter]
        private HttpContext HttpContext { get; set; } = default!;
        private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account")
            ? null
            : InteractiveServer;
    

    Thank you all!!

    mpolazzo:

    but the Dialog boxes don't work any more (original reason why I had to set InteractiveServer in App.razor).

    This would work if used from pages that have @rendermode InteractiveServer. The workaround that you have applied in the end does something similar by changing the "global" render mode depending on the route.

    This is case number X where rendering modes make things harder than easier. And there is no end in sight.

    I had exactly the same problems but I already solved them.

  • In App.razor use this in the head:
    <HeadOutlet @rendermode="RenderModeForPage" />
    then use this in the body:
    <Routes @rendermode="RenderModeForPage" />
  • then in the code:
    @code {
    ** [CascadingParameter]**
    ** private HttpContext HttpContext { get; set; } = default!;**

    ** private IComponentRenderMode RenderModeForPage => (HttpContext.Request.Path.StartsWithSegments("/Account") &&**
    ** !HttpContext.Request.Path.StartsWithSegments("/Account/Manage"))**
    ** ? null**
    ** : InteractiveServer;**

    in the ManageLayout.razor use @layout MainLayout instead of @layout AccountLayout
    then @inject IHttpContextAccessor ctxAccessor
    and use:
    ** @Body**

    stop using these 3 classes in all InteractiveServer Mode components:
    IdentityUserAccessor
    IdentityRedirectManager
    IdentityRevalidatingAuthenticationStateProvider

    Good luck

     
    推荐文章