Search code examples
asp.netasp.net-coreblazorasp.net-identity

Editing custom ASP.NET IdentityUser properties in Blazor


I am trying to make a Blazor form to update properties / settings for AppUser which inherits from the default ASP.NET IdentityUser in my Blazor webapp. For this, I am trying to modify the default identity page, specifically /Account/Manage. The default page can be used to change the user's stored phone number. I want to change a property of AppUser called PageSize instead.

@page "/Account/Manage"

@using System.ComponentModel.DataAnnotations
@using Microsoft.AspNetCore.Identity
@using MyApp.Data

@inject UserManager<AppUser> UserManager
@inject SignInManager<AppUser> SignInManager
@inject IdentityUserAccessor UserAccessor
@inject IdentityRedirectManager RedirectManager

<PageTitle>Profile</PageTitle>

<h3>Profile</h3>
<StatusMessage />

<div class="row">
    <div class="col-md-6">
        <EditForm Model="Input" FormName="profile" OnValidSubmit="OnValidSubmitAsync" method="post">
            <DataAnnotationsValidator />
            <ValidationSummary class="text-danger" role="alert" />
            <div class="form-floating mb-3">
                <input type="text" value="@username" class="form-control" placeholder="Please choose your username." disabled />
                <label for="username" class="form-label">Username</label>
            </div>
            <div class="form-floating mb-3">
                <InputNumber @bind-Value="Input.PageSize" class="form-control" placeholder="Number of rows in each page of data grid." />
                <label for="page-size" class="form-label">Page size</label>
                <ValidationMessage For="() => Input.PageSize" class="text-danger" />
            </div>
            <button type="submit" class="w-100 btn btn-lg btn-primary">Save</button>
        </EditForm>
    </div>
</div>

@code {
    private AppUser user = default!;
    private string? username;
    private int pageSize;

    [CascadingParameter]
    private HttpContext HttpContext { get; set; } = default!;

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

    protected override async Task OnInitializedAsync()
    {
        user = await UserAccessor.GetRequiredUserAsync(HttpContext);
        username = await UserManager.GetUserNameAsync(user);
        pageSize = user.PageSize;

        Input.PageSize = pageSize;
    }

    private async Task OnValidSubmitAsync()
    {
        if (Input.PageSize != pageSize)
        {
            user.PageSize = Input.PageSize;
            var setPageSizeResult = await UserManager.UpdateAsync(user);
            if (!setPageSizeResult.Succeeded)
            {
                RedirectManager.RedirectToCurrentPageWithStatus("Error: Failed to set page size.", HttpContext);
            }
        }

        await SignInManager.RefreshSignInAsync(user);
        RedirectManager.RedirectToCurrentPageWithStatus("Your profile has been updated", HttpContext);
    }

    private sealed class InputModel
    {
        [Display(Name = "Page size")]
        [Range(1, int.MaxValue)]
        public int PageSize { get; set; }
    }
}

As you can see I have only changed the lines of code from PhoneNumber which was a string? to PageSize which is an int.

The form loads fine but does not update the data in the database. In fact, the Input object in the OnValidSubmitAsync method does not contain the updated data from the form. I have checked from browser dev console that the correct value for Input.PageSize does get POSTed, but the code behind does not receive the correct value.

Through trial and error I have found that changing int to a required int? prop in InputModel and elsewhere makes my code work. But I do not understand why. And since I was also hoping to update a bool property using this form, and the Blazor InputCheckbox component cannot handle nullable booleans, I need the code above to work.

Any help would be much appreciated.


Solution

  • However, my AppUser also has some bool properties that I would like to set using this form, and InputCheckbox does not allow bool?

    Yes, the InputCheckbox doesn't allow the bool?, according to your codes , the reason why the iputsize doesn't bind the correct value is you use the blazor using the SSR mode by default, since you have set the inputsize inside the OnInitializedAsync method and this is the SSR, changing the value inside the input will not autobind to the new value.

    To solve this issue, I suggest you could modify the page mode to the InteractiveServer then this issue will be solved.

    More details, you could refer to this codes:

    @rendermode InteractiveServer
    

    Details you could refer to below codes:

    @rendermode InteractiveServer
    
    <div class="row">
        <EditForm Model="Input" FormName="profile" OnValidSubmit="OnValidSubmitAsync" method="post">
            <DataAnnotationsValidator />
         <div class="form-floating mb-3">
                <InputNumber @bind-Value="Input.PageSize" class="form-control" placeholder="Number of rows in each page of data grid." />
                <label for="page-size" class="form-label">Page size</label>
                <ValidationMessage For="() => Input.PageSize" class="text-danger" />
            </div>
    
    
            <InputCheckbox @bind-Value="@Input.MyBoolProp" />
    
            <ValidationMessage For="() => Input.MyBoolProp" class="text-danger" />
    
    
    
            <button type="submit" class="w-100 btn btn-lg btn-primary">Save</button>
    
        </EditForm>
    
    </div>
    @code {
        [SupplyParameterFromForm]
        private InputModel Input { get; set; } = new();
        private int pageSize;
    
    
        protected override async Task OnInitializedAsync()
        {
            pageSize = 1;
            Input.PageSize = pageSize;
    
        }
    
        private async Task OnValidSubmitAsync()
        {
    
            var result = Input.PageSize;
            var result1 = Input.MyBoolProp;
            int i = 0;
    
    
    
        }
        private sealed class InputModel
        {
            [Display(Name = "Page size")]
            [Range(1, int.MaxValue)]
            public int PageSize { get; set; }
    
            public bool MyBoolProp { get; set; } = false;
        }
    
    }
    

    Result:

    enter image description here