0

I am new to Blazor and creating a login form for user authorization. I am using Custom authentication using my user database. I have learnt that I cannot use InteractiveServer rendermode on the login page as I need to use the HttpContext.SignInAsync method to register the claim. Having done that, I'm unable to figure out how do I disable to login button or show a loader once the button is clicked until I get the authentication done?

Below is my login razor page code

@page "/login"
@layout Layout.MainLayout
@attribute [AllowAnonymous]

@inject ILoggingService LoggingService
@inject IUserService UserService
@inject NavigationManager NavigationManager

<div class="card">
    <div class="card-body p-4">
        <div class="text-center w-75 mx-auto auth-logo mb-4">
            <NavLink class="logo-dark" href="">
                <span><img src="assets/images/logo-dark.png" alt="" height="22"></span>
            </NavLink>
        </div>

        @if (!string.IsNullOrWhiteSpace(ErrorMessage))
        {
            <div class="alert alert-danger fade show" role="alert">
                <i class="mdi mdi-block-helper me-2"></i>
                @ErrorMessage
            </div>
        }

        <EditForm FormName="LoginForm" Model="@Model" OnValidSubmit="Submit" Enhance novalidate>
            <DataAnnotationsValidator />

            <div class="form-group mb-3">
                <label for="email" class="form-label">Your Email</label>
                <InputText @bind-Value="Model.EmailAddress" class="form-control" id="email" type="email" maxlength="50" required />
                <ValidationMessage For="@(() => Model.EmailAddress)" class="invalid-feedback" />
            </div>

            <div class="form-group mb-3">
                <label for="password" class="form-label">Your Password</label>
                <InputText @bind-Value="Model.Password" class="form-control" id="password" type="password" maxlength="30" required />
                <ValidationMessage For="@(() => Model.Password)" class="invalid-feedback" />
            </div>

            <button class="btn btn-primary w-100" type="submit">Login</button>
        </EditForm>
    </div> <!-- end card-body -->
</div>
<!-- end card -->

<div class="row mt-3">
    <div class="col-12 text-center">
        <p class="text-white-50">
            <NavLink href="/forgot-password" class="text-white-50 ms-1">Forgot your password?</NavLink>
        </p>
        <p class="text-white-50">
            Don't have an account? <NavLink href="/register" class="text-white font-weight-medium ms-1">Register</NavLink>
        </p>
    </div> <!-- end col -->
</div>
<!-- end row -->

@code {
    [CascadingParameter]
    private HttpContext? HttpContext { get; set; }

    [SupplyParameterFromForm]
    private LoginRequest Model { get; set; } = new();

    private string? ErrorMessage { get; set; }

    protected async Task Submit()
    {
        LoggingService.LogDebug("Login form submitted");

        try
        {
            var response = await UserService.LoginAsync(Model!);
            if (!response.Success)
            {
                LoggingService.LogDebug("Login failed using Model: {@Model}", Model);

                // Show error
                ErrorMessage = response.ErrorMessage;
            }
            else
            {
                var claims = new List<Claim>
                {
                    new (ClaimTypes.Name, response.UserName!),
                    new (ClaimTypes.Role, response.Role)
                };

                var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
                var principal = new ClaimsPrincipal(identity);

                await HttpContext!.SignInAsync(principal);

                NavigationManager.NavigateTo("", true);
            }
        }
        finally
        {
            Model.Password = null!;
        }
    }
}

I tried to use IJSRuntime.InvokeVoidAsync inside the Submit method to call a Javascript function to show the loader but that throws an error: JavaScript interop calls cannot be issued at this time. This is because the component is being statically rendered. When prerendering is enabled, JavaScript interop calls can only be performed during the OnAfterRenderAsync lifecycle method.

1
  • Have you tried setting the Javascript listener in the OnAfterRenderAsync method as it is hinted in your error ? Commented Aug 20, 2024 at 12:27

1 Answer 1

0

Maybe you can add a parameter isLoading to show a loader as below:

@rendermode InteractiveServer

@if (isLoading)
{
    <p>Loading...</p>
}
else
{
    ...
}

And disable the login button also use the isLoading:

<button type="submit" disabled="@isLoading">Login</button>

But in blazor server, HttpContext may not always be available, refer to the tutorial: IHttpContextAccessor/HttpContext in Razor components. So, you should ensure that HttpContext is not null before calling SignInAsync.

if (HttpContext != null)
{
    await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal);
}
else
{
    ErrorMessage ...
}

Update:

how I can show loader or disable the submit button once it is clicked when not using InteractiveServer

You can remove this line @rendermode InteractiveServer and add another render: @attribute [StreamRendering], then it can work.

Sign up to request clarification or add additional context in comments.

4 Comments

I have already tried this, but it only works with InteractiveServer rendermode. The problem with InteractiveServer is that I cannot use HttpContext.SignInAsync with it.
Could you try to use the AuthenticationStateProvider? It is recommended in blazor server. Refer to this tutorial and this reply.
I will give that a try. But that doesn't solve the problem I'm referring to. I want to know how I can show loader or disable the submit button once it is clicked when not using InteractiveServer
@Murtaza, you can use StreamRendering by adding @attribute [StreamRendering], I have updated my reply.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.