Using Radzen components in Identity UI Net.8

Now I'm transferring projects from Net.7 to Net.8. I wanted to use Radzen components in IdentityUI, but I realized that Template Form and RadzenTextBox are not quite suitable for this. I solved the problem with RadzenTextBox by assigning a name, for example, Name="Input.Email". And there is a problem with sending the form. I'm still using the native EditForm, but I can't use data validators from Radzen. Please modify the Template Form so that it can be used in Identity UI, or tell me how to solve this problem.

Hi @pedrobulano,

You haven't mentioned what the actual problem is. Without some specific it is hard to suggest anything.

I am facing a similar challenge, if I understand his question correctly. The problem I encounter is that most components, such as the login component, require the component to be interactive (wasm/server) for validation and other features to work. However, the SignInManager class, a key element of the dotnet 8 Identity UI, requires access to the http context, which is only available in server rendered components.

I want to β€œtheme” all of the identity components to match the rest of the app using Radzen components, but this is proving to be difficult!

What are your suggestions?

Every application created with Radzen Blazor Studio provides this out of the box when configured for ASP.NET Identity security. The form can post to an action which performs the login.

        <RadzenTemplateForm Action="@($"account/login?redirectUrl={redirectUrl}")" Data="@("login")"
            Method="post">
            <RadzenAlert Shade="Radzen.Shade.Lighter" Variant="Radzen.Variant.Flat" Size="Radzen.AlertSize.Small" AlertStyle="Radzen.AlertStyle.Danger" Visible="@errorVisible">@error</RadzenAlert>
            <RadzenAlert Shade="Radzen.Shade.Lighter" Variant="Radzen.Variant.Flat" Size="Radzen.AlertSize.Small" AlertStyle="Radzen.AlertStyle.Info" Visible="@infoVisible">@info</RadzenAlert>
            <RadzenLogin AllowResetPassword="true" ResetPassword="@ResetPassword" AllowRegister="false" />
        </RadzenTemplateForm>

Other than that yes - most Blazor components require interactivity and will continue to do so. We have discussed that at length in the past.

Hi @korchev
When sending the TemplateForm, I get this error: "The POST request does not specify which form is being submitted. To fix this, ensure elements have a @formname attribute with any unique value, or pass a FormName parameter if using ". Applying the attribute @formname does not give anything.

To be honest I am not sure what you are trying to achieve since you haven't provided any code whatsoever. Please check the forum FAQ for tips how to improve your questions.

OK. I'll continue in more detail. I created a project Net8 from a Blazor web application with personal accounts and Auto rendering mode and including sample pages. Visual Studio has generated sample pages to implement Identity. I am conducting an experiment with the Login page. Initially, its code is like this:

@page "/Account/Login"

@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Authentication
@using Microsoft.AspNetCore.Identity
@using BlazorApp3.Data

@inject SignInManager<ApplicationUser> SignInManager
@inject ILogger<Login> Logger
@inject NavigationManager NavigationManager
@inject IdentityRedirectManager RedirectManager

<PageTitle>Log in</PageTitle>

<h1>Log in</h1>
<div class="row">
    <div class="col-md-4">
        <section>
            <StatusMessage Message="@errorMessage" />
            <EditForm Model="Input" method="post" OnValidSubmit="LoginUser" FormName="login">
                <DataAnnotationsValidator />
                <h2>Use a local account to log in.</h2>
                <hr />
                <ValidationSummary class="text-danger" role="alert" />
                <div class="form-floating mb-3">
                    <InputText @bind-Value="Input.Email" class="form-control" autocomplete="username" aria-required="true" placeholder="name@example.com" />
                    <label for="email" class="form-label">Email</label>
                    <ValidationMessage For="() => Input.Email" class="text-danger" />
                </div>
                <div class="form-floating mb-3">
                    <InputText type="password" @bind-Value="Input.Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="password" />
                    <label for="password" class="form-label">Password</label>
                    <ValidationMessage For="() => Input.Password" class="text-danger" />
                </div>
                <div class="checkbox mb-3">
                    <label class="form-label">
                        <InputCheckbox @bind-Value="Input.RememberMe" class="darker-border-checkbox form-check-input" />
                        Remember me
                    </label>
                </div>
                <div>
                    <button type="submit" class="w-100 btn btn-lg btn-primary">Log in</button>
                </div>
                <div>
                    <p>
                        <a href="Account/ForgotPassword">Forgot your password?</a>
                    </p>
                    <p>
                        <a href="@(NavigationManager.GetUriWithQueryParameters("Account/Register", new Dictionary<string, object?> { ["ReturnUrl"] = ReturnUrl }))">Register as a new user</a>
                    </p>
                    <p>
                        <a href="Account/ResendEmailConfirmation">Resend email confirmation</a>
                    </p>
                </div>
            </EditForm>
        </section>
    </div>
    <div class="col-md-6 col-md-offset-2">
        <section>
            <h3>Use another service to log in.</h3>
            <hr />
            <ExternalLoginPicker />
        </section>
    </div>
</div>

@code {
    private string? errorMessage;
    [CascadingParameter]
    private HttpContext HttpContext { get; set; } = default!;
    [SupplyParameterFromForm]
    private InputModel 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()
    {
        // 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.";
        }
    }
    private 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; }
    }
}

Next, I wanted to replace all the standard components with Radzen components. The Radzen Login component could not be used. The standard Input Text components when using the binding @bind-Value="Input.Email" in HTML markup have name="Input.Email" (as I understand it for binding the model). So I used RadzenTextBox with the assignment Name="Input.Email". Everything worked like this:

        <EditForm id="account" Model="@Input" method="post" FormName="login" OnValidSubmit="LoginUser">
          <RadzenStack Gap="10">
            <RadzenFormField Text="Login (e-mail)" Style="width: 100%;">
              <End>
                <RadzenIcon Icon="alternate_email" />
              </End>
              <ChildContent>
                <RadzenTextBox @bind-Value="@Input.Email" Style="width: 100%;" Name="Input.Email" />
              </ChildContent>
            </RadzenFormField>
            <RadzenFormField Text="Password" Style="width: 100%;">
              <End>
                <RadzenIcon Icon="password" />
              </End>
              <ChildContent>
                <RadzenPassword @bind-Value="@Input.Password" Style="width: 100%;" Name="Input.Password" />
              </ChildContent>
            </RadzenFormField>
            <div class="checkbox">
              <label class="form-label">
                <InputCheckbox @bind-Value="Input.RememberMe" class="form-check-input" />
                Remember My
              </label>
            </div>
            <RadzenStack Gap="10" JustifyContent="JustifyContent.Center" AlignItems="AlignItems.End">
              <RadzenButton ButtonStyle="ButtonStyle.Primary" ButtonType="ButtonType.Submit" Text="Login" Style="width:100px" />
              <RadzenLink Text="Register?" Icon="app_registration" IconColor="@Colors.Info" Path="/Account/Register" />
            </RadzenStack>
          </RadzenStack>
        </EditForm>

TemplateForm can't be used instead of EditForm. When sending TemplateForm, an error is issued, which I wrote about above. And Radzen Validators do not work with EditForm.
EditForm can be replaced with HTML form> by adding AntiForgeryToken, and everything will work too.

Yes, Radzen validators only work inside RadzenTemplateForm. You can use the built-in Blazor validators instead. We will probably add the equivalent of FormName to RadzenTemplateForm in the future. We would also accept a pull request doing that :wink:

Yes, I tried Blazor validators - they work, but yours are more convenient to use :+1:

The Radzen.Blazor validators currently require interactivity so they won't work in static rendering mode. Not sure if we can change that.

It may be useful to someone. Here is the code that works:

<EditForm id="account" Model="@Input" method="post" FormName="login" OnValidSubmit="LoginUser">
  <DataAnnotationsValidator />
     <RadzenFormField Text="Login (e-mail)" Style="width: 100%;">
      <End>
        <RadzenIcon Icon="alternate_email" />
      </End>
      <ChildContent>
        <RadzenTextBox @bind-Value="@Input.Email" Style="width: 100%;" Name="Input.Email"  />
      </ChildContent>
      <Helper>
        <ValidationMessage For="() => Input.Email" class="text-danger" />
      </Helper>
    </RadzenFormField>
     ...
</EditForm>
1 Like

Thank you for that. I think we are trying to achieve the same thing, a Radzen styled version of the identity pages. And thank you @korchev for your input so far. I realize that it is tricky dealing with the new server rendered pages when the components are designed to be interactive. In some respects it would be nice just to use out of the box components for for server rendered pages but be able to apply Radzen stules to them for these types of scenarios.