dotnet / Scaffolding

Code generators to speed up development.
MIT License
638 stars 229 forks source link

Identity (not `msidentity`) scaffolding doens't work with `InteractiveServer` render mode on Blazor server #2738

Open tommyvct opened 5 months ago

tommyvct commented 5 months ago

Ask a question

I am using the Identity scaffolding to build a Blazor server-based website.

One of my customers is so bugged with the default "complex" password requirements, stating it makes the whole thing "unusable". Instead of lowering the password standard, I proposed a solution that use a password generator built-in on the registration page.

So I added a button to generate the password, but the button never ever worked. I set a breakpoint on the randomPassword() method but it was never hit.

This StackOverflow question pointed out that The newly introduced RenderMode settings is to blame, so I added @rendermode InteractiveServer on this page. Unfortunately, this didn't solve the problem, but also introduced a new one. I now have a NullReferenceException originating from \Components\Account\Shared\StatusMessage.razor. The HTTPContext is null for some reason.

image

``` > EquipmentTrack.dll!EquipmentTrack.Components.Account.Shared.StatusMessage.OnInitialized() Line 22 C# Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync() Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.ComponentState.SupplyCombinedParameters(Microsoft.AspNetCore.Components.ParameterView directAndCascadingParameters) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(Microsoft.AspNetCore.Components.ParameterView parameters) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(ref Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.DiffContext diffContext, int frameIndex) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(ref Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.DiffContext diffContext, int frameIndex) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(ref Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.DiffContext diffContext, int newFrameIndex) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(ref Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Microsoft.AspNetCore.Components.RenderTree.Renderer renderer, Microsoft.AspNetCore.Components.Rendering.RenderBatchBuilder batchBuilder, int componentId, Microsoft.AspNetCore.Components.RenderTree.ArrayRange oldTree, Microsoft.AspNetCore.Components.RenderTree.ArrayRange newTree) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(Microsoft.AspNetCore.Components.Rendering.RenderBatchBuilder batchBuilder, Microsoft.AspNetCore.Components.RenderFragment renderFragment, out System.Exception renderFragmentException) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(Microsoft.AspNetCore.Components.Rendering.RenderQueueEntry renderQueueEntry) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue() Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender() Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(int componentId, Microsoft.AspNetCore.Components.RenderFragment renderFragment) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderHandle.Render(Microsoft.AspNetCore.Components.RenderFragment renderFragment) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged() Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync() Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync() Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.ComponentBase.SetParametersAsync(Microsoft.AspNetCore.Components.ParameterView parameters) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.ComponentState.SupplyCombinedParameters(Microsoft.AspNetCore.Components.ParameterView directAndCascadingParameters) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(Microsoft.AspNetCore.Components.ParameterView parameters) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(int componentId, Microsoft.AspNetCore.Components.ParameterView initialParameters) Unknown Microsoft.AspNetCore.Components.Server.dll!Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer.WebRootComponentManager.AddRootComponentAsync(int ssrComponentId, System.Type componentType, Microsoft.AspNetCore.Components.ComponentMarkerKey? key, Microsoft.AspNetCore.Components.WebRootComponentParameters parameters) Unknown [Resuming Async Method] System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncMethodBuilderCore.Startd__3>(ref Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer.WebRootComponentManager.d__3 stateMachine) Unknown System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder.Startd__3>(ref Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer.WebRootComponentManager.d__3 stateMachine) Unknown Microsoft.AspNetCore.Components.Server.dll!Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost.PerformRootComponentOperations.AnonymousMethod__0() Unknown Microsoft.AspNetCore.Components.Server.dll!Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost.PerformRootComponentOperations(Microsoft.AspNetCore.Components.RootComponentOperation[] operations, bool shouldWaitForQuiescence) Unknown Microsoft.AspNetCore.Components.Server.dll!Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost.UpdateRootComponents.AnonymousMethod__0() Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.InvokeAsync.AnonymousMethod__10_0((System.Runtime.CompilerServices.AsyncTaskMethodBuilder completion, System.Func asyncAction) state) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.InvokeWithThisAsCurrentSyncCtxThenSetResult<(System.Runtime.CompilerServices.AsyncTaskMethodBuilder, System.Func)>(System.Runtime.CompilerServices.AsyncTaskMethodBuilder completion, System.Action<(System.Runtime.CompilerServices.AsyncTaskMethodBuilder, System.Func)> callback, (System.Runtime.CompilerServices.AsyncTaskMethodBuilder, System.Func) state) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.SendIfQuiescedOrElsePost<(System.Runtime.CompilerServices.AsyncTaskMethodBuilder, System.Func)>(System.Action<(System.Runtime.CompilerServices.AsyncTaskMethodBuilder, System.Func)> callback, (System.Runtime.CompilerServices.AsyncTaskMethodBuilder, System.Func) state) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext.InvokeAsync(System.Func asyncAction) Unknown Microsoft.AspNetCore.Components.dll!Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContextDispatcher.InvokeAsync(System.Func workItem) Unknown Microsoft.AspNetCore.Components.Server.dll!Microsoft.AspNetCore.Components.Server.ComponentHub.UpdateRootComponents(string serializedComponentOperations, string applicationState) Unknown [Lightweight Function] Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher.ExecuteMethod(Microsoft.Extensions.Internal.ObjectMethodExecutor methodExecutor, Microsoft.AspNetCore.SignalR.Hub hub, object[] arguments) Unknown Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher.Invoke.__ExecuteInvocation|18_0(Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher dispatcher, Microsoft.Extensions.Internal.ObjectMethodExecutor methodExecutor, Microsoft.AspNetCore.Components.Server.ComponentHub hub, object[] arguments, Microsoft.Extensions.DependencyInjection.AsyncServiceScope scope, Microsoft.AspNetCore.SignalR.IHubActivator hubActivator, Microsoft.AspNetCore.SignalR.HubConnectionContext connection, Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage hubMethodInvocationMessage, bool isStreamCall) Unknown Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher.Invoke(Microsoft.AspNetCore.SignalR.Internal.HubMethodDescriptor descriptor, Microsoft.AspNetCore.SignalR.HubConnectionContext connection, Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage hubMethodInvocationMessage, bool isStreamResponse, bool isStreamCall) Unknown Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher.ProcessInvocation.AnonymousMethod__17_0((Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher, Microsoft.AspNetCore.SignalR.Internal.HubMethodDescriptor descriptor, Microsoft.AspNetCore.SignalR.HubConnectionContext connection, Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage hubMethodInvocationMessage) state) Unknown Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.ChannelBasedSemaphore.RunTask<(Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher, Microsoft.AspNetCore.SignalR.Internal.HubMethodDescriptor, Microsoft.AspNetCore.SignalR.HubConnectionContext, Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage)>(System.Func<(Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher, Microsoft.AspNetCore.SignalR.Internal.HubMethodDescriptor, Microsoft.AspNetCore.SignalR.HubConnectionContext, Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage), System.Threading.Tasks.Task> callback, (Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher, Microsoft.AspNetCore.SignalR.Internal.HubMethodDescriptor, Microsoft.AspNetCore.SignalR.HubConnectionContext, Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage) state) Unknown Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.ChannelBasedSemaphore.RunAsync<(Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher, Microsoft.AspNetCore.SignalR.Internal.HubMethodDescriptor, Microsoft.AspNetCore.SignalR.HubConnectionContext, Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage)>(System.Func<(Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher, Microsoft.AspNetCore.SignalR.Internal.HubMethodDescriptor, Microsoft.AspNetCore.SignalR.HubConnectionContext, Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage), System.Threading.Tasks.Task> callback, (Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher, Microsoft.AspNetCore.SignalR.Internal.HubMethodDescriptor, Microsoft.AspNetCore.SignalR.HubConnectionContext, Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage) state) Unknown Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher.ProcessInvocation(Microsoft.AspNetCore.SignalR.HubConnectionContext connection, Microsoft.AspNetCore.SignalR.Protocol.HubMethodInvocationMessage hubMethodInvocationMessage, bool isStreamResponse) Unknown Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.Internal.DefaultHubDispatcher.DispatchMessageAsync(Microsoft.AspNetCore.SignalR.HubConnectionContext connection, Microsoft.AspNetCore.SignalR.Protocol.HubMessage hubMessage) Unknown Microsoft.AspNetCore.SignalR.Core.dll!Microsoft.AspNetCore.SignalR.HubConnectionHandler.DispatchMessagesAsync(Microsoft.AspNetCore.SignalR.HubConnectionContext connection) Unknown [Resuming Async Method] System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AsyncStateMachineBox.d__21>.ExecutionContextCallback(object s) Unknown System.Private.CoreLib.dll!System.Threading.ExecutionContext.RunFromThreadPoolDispatchLoop(System.Threading.Thread threadPoolThread, System.Threading.ExecutionContext executionContext, System.Threading.ContextCallback callback, object state) Unknown System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AsyncStateMachineBox.d__21>.MoveNext(System.Threading.Thread threadPoolThread) Unknown System.Private.CoreLib.dll!System.Runtime.CompilerServices.AsyncTaskMethodBuilder.AsyncStateMachineBox.d__21>.ExecuteFromThreadPool(System.Threading.Thread threadPoolThread) Unknown System.Private.CoreLib.dll!System.Threading.ThreadPoolWorkQueue.Dispatch() Unknown System.Private.CoreLib.dll!System.Threading.PortableThreadPool.WorkerThread.WorkerThreadStart() Unknown ```

This is the code in question:

```razor @page "/Account/Register" @using System.ComponentModel.DataAnnotations @using System.Text @using System.Text.Encodings.Web @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Identity @using Microsoft.AspNetCore.WebUtilities @using EquipmentTrack.Data @inject UserManager UserManager @inject IUserStore UserStore @inject SignInManager SignInManager @inject IEmailSender EmailSender @inject ILogger Logger @inject NavigationManager NavigationManager @inject IdentityRedirectManager RedirectManager @inject RoleManager RoleManager @if (initialize) { 初始化

初始化


随机密码 @* This doesn't work at all *@ 注册
} else { 新建用户

新建用户


注册
} @code { private IEnumerable? identityErrors; [SupplyParameterFromForm] private InputModel Input { get; set; } = new(); [SupplyParameterFromQuery] private string? ReturnUrl { get; set; } private string? Message => identityErrors is null ? null : $"Error: {string.Join(", ", identityErrors.Select(error => error.Description))}"; private bool initialize => GetAdminCount().Result == 0; private async Task RegisterUser(EditContext editContext) { var user = CreateUser(); await UserStore.SetUserNameAsync(user, Input.UserName, CancellationToken.None); var emailStore = GetEmailStore(); await emailStore.SetEmailAsync(user, Input.UserName, CancellationToken.None); user.DepartmentName = Input.Department; var result = await UserManager.CreateAsync(user, Input.Password); if (!result.Succeeded) { identityErrors = result.Errors; return; } if (initialize) { var addRoleResult = await RoleManager.CreateAsync(new IdentityRole(Constants.AdminRole)); if (!addRoleResult.Succeeded) { identityErrors = addRoleResult.Errors; return; } addRoleResult = await UserManager.AddToRoleAsync(user, Constants.AdminRole); if (!addRoleResult.Succeeded) { identityErrors = addRoleResult.Errors; return; } await SignInManager.SignInAsync(user, isPersistent: true); RedirectManager.RedirectTo("/"); } else { RedirectManager.RedirectTo("/UserManagement"); } Logger.LogInformation("User created a new account with password."); // var userId = await UserManager.GetUserIdAsync(user); // var code = await UserManager.GenerateEmailConfirmationTokenAsync(user); // code = WebEncoders.Base64UrlEncode(Encoding.UTF8.GetBytes(code)); // var callbackUrl = NavigationManager.GetUriWithQueryParameters( // NavigationManager.ToAbsoluteUri("Account/ConfirmEmail").AbsoluteUri, // new Dictionary { ["userId"] = userId, ["code"] = code, ["returnUrl"] = ReturnUrl }); // await EmailSender.SendConfirmationLinkAsync(user, Input.UserName, HtmlEncoder.Default.Encode(callbackUrl)); // if (UserManager.Options.SignIn.RequireConfirmedAccount) // { // RedirectManager.RedirectTo( // "Account/RegisterConfirmation", // new() { ["email"] = Input.UserName, ["returnUrl"] = ReturnUrl }); // } } private ApplicationUser CreateUser() { try { return Activator.CreateInstance(); } catch { throw new InvalidOperationException($"Can't create an instance of '{nameof(ApplicationUser)}'. " + $"Ensure that '{nameof(ApplicationUser)}' is not an abstract class and has a parameterless constructor."); } } private IUserEmailStore GetEmailStore() { if (!UserManager.SupportsUserEmail) { throw new NotSupportedException("The default UI requires a user store with email support."); } return (IUserEmailStore)UserStore; } private sealed class InputModel { [Required] [Display(Name = "登录用户名")] public string UserName { get; set; } = ""; [Required] [Display(Name = "科室")] public string Department { get; set; } = ""; [Required] [StringLength(100, ErrorMessage = "{0}长度最少为{2}位,最大为{1}位。", MinimumLength = 6)] [DataType(DataType.Password)] [Display(Name = "密码")] public string Password { get; set; } = ""; [DataType(DataType.Password)] [Display(Name = "再输一遍密码")] [Compare("Password", ErrorMessage = "两个密码不一致。")] public string ConfirmPassword { get; set; } = ""; } private async Task GetAdminCount() { var usersInAdminRole = await UserManager.GetUsersInRoleAsync(Constants.AdminRole); return usersInAdminRole.Count; } private void randomPassword() { // This never get's executed var password = AuthUtils.GenerateRandomPassword(); Input.Password = password; Input.ConfirmPassword = password; Logger.LogInformation("randompassword: "+ password); } } ```

Then I tried to change the render mode in App.razor to InteractiveServer. Notice that the render mode was set to null when the route starts with /Account with the scaffolding. Unfortunately, things are even worse with the page being completely unusable, refershing itself once per second and with a memory leak about 1MB per second.

```razor @code { [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account") ? null : InteractiveServer; } ```

For your reference, this should be the template: https://github.com/dotnet/Scaffolding/blob/main/src/Scaffolding/VS.Web.CG.Mvc/Templates/BlazorIdentity/Pages/Register.tt

To solve this problem, I could:

  1. Tell my customer to deal with it
  2. Use JavaScript to generate the password

But the ultimate question is, why Identity doesn't with InteractiveServer render mode?

Include provider and version information

Microsoft.VisualStudio.Web.CodeGeneration.Design version: 8.0.2 Target framework: .NET 8 Operating system: Windows 11 Pro IDE: (e.g. Visual Studio 2022 17.10.0 Preview 7.0)