Add Timer To Login Page In Blazor

Mahdi Elahi 31 Reputation points
2025-04-13T15:26:28.08+00:00

Hi i create new blazor 8 web app auto render I want to define a C# timer on the default login page razor


private TimeSpan timeLeft = TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(59));
private System.Timers.Timer? countdownTimer;

private void StartTimer()
{
    countdownTimer = new System.Timers.Timer(1000); // 1 second
    countdownTimer.Elapsed += (sender, e) =>
    {
        if (timeLeft.TotalSeconds > 0)
        {
            timeLeft = timeLeft.Subtract(TimeSpan.FromSeconds(1));
            InvokeAsync(StateHasChanged);
        }
        else
        {
            countdownTimer?.Stop();
        }
    };
    countdownTimer.Start();
}

But because the page uses SSR (server-side rendering), it doesn't work — when the time decreases, the page doesn't update at all; it basically doesn't work.

When I change the RenderMode to something like interactive server, it works fine.

But then, when I try to sign in, it throws a "headers are read-only, response has already started" error, meaning the page must use SSR.

Login.razor


@page "/admin-pro/account/sign-in"

@layout AuthLayout
@inject SignInManager<ApplicationUser> SignInManager
@inject UserManager<ApplicationUser> userManager
@inject IUserAdminService authAdminService
@inject ILogger<Login> Logger
@inject NavigationManager NavigationManager
@inject IdentityRedirectManager RedirectManager

<PageTitle>Sign In</PageTitle>
<div class="card p-2 bg-opacity-8" id="form-s">
    <!-- Logo -->
    <div class="app-brand justify-content-center mt-2">
        <span class="app-brand-logo demo">
            <img src="/images/main-brand.png" width="350" />
        </span>
        @* <span class="app-brand-text demo text-heading fw-bold">Aircraft Finder</span> *@
    </div>
    <!-- /Logo -->
    <div class="card-body mt-2">

        <EditForm Model="Input" class="login-form mb-3" method="post" OnValidSubmit="LoginUser" FormName="login">
            <DataAnnotationsValidator />
            <div class="alert alert-solid-danger d-flex align-items-center @visibleError" role="alert">
                <i class="mdi mdi-alert-outline me-2"></i>
                @errorMessage
            </div>
            <div class="form-floating form-floating-outline mb-3">
                <InputText @bind-Value="Input.Username" type="text" class="form-control" placeholder="Enter your username" autofocus />
                <label for="email">Username</label>
                <ValidationMessage For="@(() => Input.Username)" />
            </div>
            <div class="mb-3">
                <div class="form-password-toggle">
                    <div class="input-group input-group-merge">
                        <div class="form-floating form-floating-outline">
                            <InputText @bind-Value="Input.Password" type="password" class="form-control" placeholder="Enter your password" />
                            <label for="password">Password</label>
                            <ValidationMessage For="@(() => Input.Password)" />
                        </div>
                    </div>
                </div>
            </div>
            <div class="mb-3 d-flex justify-content-between">
                <div class="form-check">
                    <input class="form-check-input" type="checkbox" id="remember-me">
                    <label class="form-check-label" for="remember-me">
                        Remember me
                    </label>
                </div>
            </div>
            <div class="mb-3">
                <button class="btn btn-primary d-grid w-100" type="submit">Sign In</button>
            </div>

        </EditForm>
        <div class="col-md-8 row m-auto mt-2">
            <p class="notice is-md text-right text-2 text-grays-500 mb-4  text-center " data-v-4c79b6d3="">
                <span class="fw-bold">@timeLeft.ToString(@"mm\:ss")</span>
                until you can request the code again
            </p>
        </div>
    </div>
</div>

@code {
    private string? errorMessage;
    private string visibleError = "d-none";
    [CascadingParameter]
    private HttpContext HttpContext { get; set; } = default!;

    [SupplyParameterFromForm]
    private LoginAdminDTO Input { get; set; } = new();

    [SupplyParameterFromQuery]
    private string? ReturnUrl { get; set; }

    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 LoginUser()
    {
        var result = await authAdminService.LoginAsync(Input);
        if (result.Status is ResponseStatus.Success)
        {
            NavigationManager.NavigateTo("/admin-pro");
        }
        else
        {
            errorMessage = result.Message;
            visibleError = "";
        }
    }

    private TimeSpan timeLeft = TimeSpan.FromMinutes(1).Add(TimeSpan.FromSeconds(59));
    private System.Timers.Timer? countdownTimer;

    private void StartTimer()
    {
        countdownTimer = new System.Timers.Timer(1000); // 1 second
        countdownTimer.Elapsed += (sender, e) =>
        {
            if (timeLeft.TotalSeconds > 0)
            {
                timeLeft = timeLeft.Subtract(TimeSpan.FromSeconds(1));
                InvokeAsync(StateHasChanged);
            }
            else
            {
                countdownTimer?.Stop();
            }
        };
        countdownTimer.Start();
    }

    public void Dispose()
    {
        countdownTimer?.Dispose();
    }
}

and

Is it even possible to implement an interactive server or assembly so that I can build anything I want and not run into issues like 'Headers are read only...

Community Center Not monitored
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Bruce (SqlWork.com) 77,686 Reputation points Volunteer Moderator
    2025-04-13T16:45:12.4566667+00:00

    The timer should only be started after the page is interactive. So place in OnInitializedAsync(). Your current OnInitializedAsync() code accesses HttpContext, which only valid in the SSR phase and you certainly can not call SignOutAsync() as the page html has already been rendered. The HttpContext is is either the SSR context or in interactive mode the context from the blazor bootstrap js request that opened the signal/r connection and started the app.

    Blazor server apps in interactive mode can not set a cookie, except by unloading and redirecting to a url that sets the cookie. This is typically done via jsinterop and setting the location. If it’s a Blazor WASM you can set a cookie via Ajax call to a webapi that sets the cookie.


Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.