NET8, Blazor and Custom User Management

blazor microsoft identity

I will show how to create custom user management with NET8 and Blazor based on Microsoft Identity. Here how to add custom fields for users

In every application I wrote, there is always the requirement to authenticate the user: in this new series of posts, I will show how to create custom user management with NET8 and Blazor based on Microsoft Identity.

The full source code of this post is available on GitHub. If you find this post useful, please consider making a donation on GitHub.

For any comment, suggestion or help, please see the Forum.

Other posts:

Allow Login with both Username and Email

In the first post, I showed how to add custom fields to the registration page. Ideally, I may want to allow my users to log in with both the username and the email ID. Under Components \ Pages \ Account open the Razor page Login.razor. Almost at the beginning of the code section you have the InputModel.

Now, replace the InputModel with this one:

public class InputModel
{
    [Required]
    [Display(Name = "Email / Username")]
    public string Email { get; set; }

    [Required]
    [DataType(DataType.Password)]
    public string Password { get; set; }

    [Display(Name = "Remember me?")]
    public bool RememberMe { get; set; }
}

At the model level, we made the Email Property accept both email IDs and plain text. Now, I add a new function to check if the entered data is a valid email ID or not.

First, at the top of the page add

@using System.Net.Mail
@inject UserManager<ApplicationUser> UserManager

Then, at the bottom of the code section add this function to check is the email is a valid one:

public bool IsValidEmail(string emailaddress)
{
    try
    {
        MailAddress m = new MailAddress(emailaddress);
        return true;
    }
    catch (FormatException)
    {
        return false;
    }
}

Now, I have to check if the email is valid in order to find the username of the user. For this reason, I have to change the LoginUser() function adding a few lines at the beginning (full code for clarity):

public async Task LoginUser()
{
    var userName = Input.Email;
    if (IsValidEmail(Input.Email))
    {
        var user = await UserManager.FindByEmailAsync(Input.Email);
        if (user != null)
        {
            userName = user.UserName;
        }
    }

    // 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);
    }
    if (result.RequiresTwoFactor)
    {
        RedirectManager.RedirectTo(
            "/Account/LoginWith2fa",
            new() { ["ReturnUrl"] = ReturnUrl, ["RememberMe"] = Input.RememberMe });
    }
    if (result.IsLockedOut)
    {
        Logger.LogWarning("User account locked out.");
        RedirectManager.RedirectTo("/Account/Lockout");
    }
    else
    {
        errorMessage = "Error: Invalid login attempt.";
    }
}

Build the application and run it. You would be able to log in with both the username and the email id now.

Adding the Custom User Fields To Profile Settings

If you click on the left side on your name, you are redirected to the Manage your account section.

Manage your account - NET8, Blazor and Custom User Management
Manage your account

There are quite a lot of basic options here, like changing your phone number, updating the email id, changing the password, and so on. Let’s try to extend these pages in the coming sections.

Change the InputModel

As the first step, let’s try to add the first name and last name fields to this form. Navigate to Components/Pages/Account/Manage/Index.razor.

Replace the InputModel. Here we added the new fields (including Profile Picture, although we will implement it in the next section)

private sealed class InputModel
{
    [Display(Name = "First Name")]
    public string? FirstName { get; set; }
    [Display(Name = "Last Name")]
    public string? LastName { get; set; }
    [Display(Name = "Username")]
    public string? Username { get; set; }
    [Phone]
    [Display(Name = "Phone number")]
    public string? PhoneNumber { get; set; }
    [Display(Name = "Profile Picture")]
    public byte[]? ProfilePicture { get; set; }
}

Next, while loading the form we need to load these data to the memory as well. So, change the OnInitializedAsync with the following code:

@code {
    private ApplicationUser _user = default!;
    private string? _firstname;
    private string? _lastname;
    private string? _username;
    private string? _phoneNumber;

    [SupplyParameterFromForm]
    private InputModel Input { get; set; } = default!;

    protected override async Task OnInitializedAsync()
    {
        Input ??= new();

        _user = await UserAccessor.GetRequiredUserAsync();
        _username = await UserManager.GetUserNameAsync(_user);
        _phoneNumber = await UserManager.GetPhoneNumberAsync(_user);
        _firstname = _user.FirstName;
        _lastname = _user.LastName;

        Input.FirstName ??= _firstname;
        Input.LastName ??= _lastname;
        Input.PhoneNumber ??= _phoneNumber;
        Input.ProfilePicture ??= _user.ProfilePicture;
    }

Change the UI

The next step is to add the fields in the form. For that, in the EditForm tag add the following HTML code:

<div class="form-floating mb-3">
    <InputText id="firstname" @bind-Value="Input.FirstName" class="form-control" placeholder="Please enter your first name." />
    <label for="firstname" class="form-label">First name</label>
    <ValidationMessage For="() => Input.FirstName" class="text-danger" />
</div>
<div class="form-floating mb-3">
    <InputText id="lastname" @bind-Value="Input.LastName" class="form-control" placeholder="Please enter your last name." />
    <label for="lastname" class="form-label">Last name</label>
    <ValidationMessage For="() => Input.LastName" class="text-danger" />
</div>

That’s it. Build and Run your application. Go to the “Manage your account” page. You will see the changes here.

Your new profile page - NET8, Blazor and Custom User Management
Your new profile page

Adding a Profile Picture

Remember the part where we added the Update code for FirstName and Lastname? Now, I want to do something similar for the picture profile of the user. There is a BUT. With NET8, Microsoft changes the lifecycle of a Blazor application. I recommend reading the article from Microsoft Learn to have a glance at the new changes. One of the exciting new entry is the QuickGrid component and you have more info here.

Before implementing the profile picture, there is one thing I want to highlight and it is the new RenderMode.InteractiveServer.

Render modes

So, every component in a Blazor Web App adopts a render mode to determine the hosting model that it uses, where it’s rendered, and whether or not it’s interactive.

The following table shows the available render modes for rendering Razor components in a Blazor Web App. To apply a render mode to a component use the @rendermode directive on the component instance or on the component definition. Later in this article, examples are shown for each render mode scenario.

NameDescriptionRender locationInteractive
StaticStatic server renderingServer❌No
Interactive ServerInteractive server rendering using Blazor ServerServer✔️Yes
Interactive WebAssemblyInteractive client rendering using Blazor WebAssemblyClient✔️Yes
Interactive AutoInteractive client rendering using Blazor Server initially and then WebAssembly on subsequent visits after the Blazor bundle is downloadedServer, then client✔️Yes

Prerendering is enabled by default for interactive components. Guidance on controlling prerendering is provided later in this article.

The following examples demonstrate setting the component’s render mode with a few basic Razor component features.

To test the render mode behaviours locally, you can place the following components in an app created from the Blazor Web App project template. When you create the app, select the checkboxes (Visual Studio) or apply the CLI options (.NET CLI) to enable both server-side and client-side interactivity. For guidance on how to create a Blazor Web App, see Tooling for ASP.NET Core Blazor.

Prepare the profile

Now, in the InputModel I already added a field for the ProfilePicture to be saved in the database. In the Index.razor file, I have to accommodate the picture to be uploaded. So, I wrap the EditForm in a Bootstrap col and define 2 columns like that

<div class="row">
    <div class="col-md-6">
        <EditForm id="profile-form" Model="Input" FormName="profile" 
            OnValidSubmit="OnValidSubmitAsync" method="post">
            <DataAnnotationsValidator />
            <ValidationSummary class="text-danger" />

            // ... code omitted ...

        </EditForm>
    </div>
    <div class="col-md-6">
        <div class="form-floating">
            <UploadProfilePicture />
        </div>
    </div>
</div>

As you can see, I added the UploadProfilePicture, a Razor component that is not created yet.

Implement the UploadProfilePicture component

For reasons I ignore myself, to upload a file in the new Blazor with NET8, we have to use a component that uses a specific render mode, the InteractiveServer.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.