0

I'm trying to create a register account page in Blazor Web App. I need the form submission callback to run on the client as it needs to set the authentication cookie, but I can't manage to achieve this.

This is my current component code:

@using ClassLibrary.Models.Auth
@using ClassLibrary.Models.Database
@using ClassLibrary.Localization.Shared.Forms
@using IDFW.Models.HttpClients

@rendermode InteractiveWebAssembly
@page "/auth/register"

@inject IStringLocalizer<Register> localizer
@inject ApiHttpClient api

<HeadContent>
    <link rel="stylesheet" href="/css/auth/login.css" />
    <link rel="stylesheet" href="/css/components/loader.css" />
</HeadContent>
<div class="content-container">
    <EditForm FormName="LoginForm" EditContext="editContext" class="data-form" OnValidSubmit="HandleOnValidSubmit">
        <DataAnnotationsValidator />
        <h1>@localizer["PageTitle"]</h1>
        <fieldset disabled="@isProcessing">
            <label>
                <p>@FormLocalization.FirstName<span class="danger">*</span></p>
                <InputText DisplayName="@FormLocalization.FirstName" @bind-Value="RegisterModel.FirstName" />
                <ValidationMessage For="() => RegisterModel.FirstName" class="danger" />
            </label>
            <label>
                <p>@FormLocalization.LastName<span class="danger">*</span></p>
                <InputText DisplayName="@FormLocalization.LastName" @bind-Value="RegisterModel.LastName" />
                <ValidationMessage For="() => RegisterModel.LastName" class="danger" />
            </label>
            <label>
                <p>Email<span class="danger">*</span></p>
                <InputText DisplayName="Email" @bind-Value="RegisterModel.Email" type="email" />
                <ValidationMessage For="() => RegisterModel.Email" class="danger" />
            </label>
            <label>
                <p>Password<span class="danger">*</span></p>
                <div class="form-password">
                    <InputText DisplayName="Password" @bind-Value="RegisterModel.Password" type="password" autocomplete="new-password" />
                    <span class="fluent-icon icon-ic_fluent_eye_16_filled password-toggle"></span>
                </div>
                <ValidationMessage For="() => RegisterModel.Password" class="danger" />
            </label>
            <label>
                <p>@AuthFormLocalization.ConfirmPassword<span class="danger">*</span></p>
                <div class="form-password">
                    <InputText DisplayName="@AuthFormLocalization.ConfirmPassword" @bind-Value="RegisterModel.ConfirmPassword" type="password" autocomplete="new-password" />
                    <span class="fluent-icon icon-ic_fluent_eye_16_filled password-toggle"></span>
                </div>
                <ValidationMessage For="() => RegisterModel.ConfirmPassword" class="danger" />
            </label>
        </fieldset>
        <fieldset class="form-checkbox-area" disabled="@isProcessing">
            <label>
                <InputCheckbox @bind-Value="RegisterModel.KeepLogin" />
                @AuthFormLocalization.KeepLogin
            </label>
            <label>
                <InputCheckbox @bind-Value="RegisterModel.AgreeLegal" />
                @((MarkupString)FormLocalization.LegalNote)
                <ValidationMessage For="() => RegisterModel.AgreeLegal" class="danger" />
            </label>
        </fieldset>
        <button type="submit" class="cta-btn primary-cta-btn" disabled="@isProcessing">
            @if (!isProcessing)
            {
                @localizer["Register"]
            }
            else
            {
                <span class="loader"></span>
            }
        </button>
        <p class="form-hint">@((MarkupString)localizer["FormHint"].Value)</p>
    </EditForm>
</div>
<script src="/js/form.js"></script>

@code {
    private RegisterModel RegisterModel { get; set; } = null!;
    private EditContext editContext = null!;
    private ValidationMessageStore validationMessageStore = null!;
    private bool isProcessing = false;

    [Inject]
    private NavigationManager navigation { get; set; } = null!;

    protected override void OnInitialized()
    {
        RegisterModel ??= new();
        editContext = new(RegisterModel);
        validationMessageStore = new(editContext);
        editContext.OnFieldChanged += (_, args) =>
        {
            validationMessageStore.Clear(args.FieldIdentifier);
            editContext.NotifyValidationStateChanged();
        };
    }

    private async Task HandleOnValidSubmit()
    {
        isProcessing = true;
        validationMessageStore.Clear();

        // Try register account
        HttpResponseMessage res = await api.HttpClient.PostAsJsonAsync("auth/register", RegisterModel);

        if (res.IsSuccessStatusCode)
        {
            navigation.NavigateTo("/");
        }
        else if (res.StatusCode == System.Net.HttpStatusCode.BadRequest)
        {
            HttpValidationProblemDetails problemDetails = (await res.Content.ReadFromJsonAsync<HttpValidationProblemDetails>())!;

            if (problemDetails.Errors != null)
            {
                foreach (KeyValuePair<string, string[]> message in problemDetails.Errors)
                {
                    FieldIdentifier fieldIdentifier = new(RegisterModel, message.Key);
                    validationMessageStore.Add(fieldIdentifier, message.Value);
                }

                editContext.NotifyValidationStateChanged();
            }
        }

        isProcessing = false;
    }
}

I want everything in the page to be rendered on the server as well as realtime data validation, except HandleOnValidSubmit() to run on the client. I tried using the InteractiveWebAssembly rendermode, but apparently it's not the right approach or I'm doing something wrong.

7
  • you'd set the cookie from server-side. (It'll send a set-cookie header first, before the response...) Commented Nov 7 at 18:00
  • Yes, that's what I'm doing in an API controller. The problem is the returned cookie is set in the server's HTTP context, basically on the server which is pointless. Furthermore, no request to auth/register appears in the network tab which confirms the code is running on the server. Commented Nov 7 at 18:02
  • sry, not familiar with Blazor, but this looks sort of odd: "@inject ApiHttpClient". This looks to be front-end code here... your "api" would usually be a sort of "web api" which is run at the server. This page is currently at route "auth/register" (@page "/auth/register") and you make a POST to same. Seems like that POST should be to something like "api/auth/register"? Commented Nov 7 at 18:29
  • It is a post to /api/auth/register, the api part is set as HttpClient.BaseAddress. Commented Nov 7 at 20:34
  • I don't think you have a need for HttpClient at all here... just do something like var response = await Http.PostAsJsonAsync("api/auth/register", RegisterModel); I'm pretty sure HttpClient is for server-side (which you don't want... you want to POST from client-side...) Commented Nov 7 at 21:04

0

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.