I am new to both Blazor and Mudblazor. Now I have a simple web app with a login page.The login.razor code is shown below.
@page "/Login"@using System.ComponentModel.DataAnnotations@using FileGrid.Entities.Dto@using Microsoft.AspNetCore.Authentication@using Microsoft.AspNetCore.Authentication.Cookies@using System.Security.Claims@using Microsoft.AspNetCore.Identity@inject NavigationManager Navigation<MudPaper Class="mx-auto mt-12 pa-4" Elevation="4" Style="max-width:400px;"><MudText Typo="Typo.h5" Align="Align.Center" Class="mb-4">LogIn</MudText><EditForm Model=@Input OnValidSubmit="HandleValidSubmit" FormName="LoginForm"><DataAnnotationsValidator /><MudTextField @bind-value="@Input.UserName" Immediate="true" T="string" Label="User Name" For="@(() => Input.UserName)" FullWidth="true" Class="mb-4" Required="true" /><MudTextField @bind-value="@Input.Password" Immediate="true" T="string" Label="Password" For="@(() => Input.Password)" Variant="Variant.Text" InputType="InputType.Password" FullWidth="true" Class="mb-4" Required="true" /><MudCheckBox T="bool" Label="Remember Me" @bind-checked="@Input.RememberMe" Class="mb-4" /><MudButton ButtonType="ButtonType.Submit" Color="Color.Primary" Variant="Variant.Filled" Class="mt-2" FullWidth="true"> LogIn</MudButton></EditForm> @if (!string.IsNullOrEmpty(errorMessage)) {<MudAlert Class="mt-3" Color="Color.Error">@errorMessage</MudAlert> }</MudPaper>@code { [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; [SupplyParameterFromForm] private InputModel Input { get; set; } = new InputModel(); private string errorMessage = string.Empty; private async Task HandleValidSubmit() { try { var claims = new List<Claim>{new Claim(ClaimTypes.Name, Input.UserName),new Claim(ClaimTypes.Role, "Admin")}; var identity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme); var principal = new ClaimsPrincipal(identity); var authProperties = new AuthenticationProperties { IsPersistent = Input.RememberMe, ExpiresUtc = DateTimeOffset.UtcNow.Add( Input.RememberMe ? TimeSpan.FromDays(30) : TimeSpan.FromMinutes(30)), AllowRefresh = true }; await HttpContext.SignInAsync( CookieAuthenticationDefaults.AuthenticationScheme, principal, authProperties); Navigation.NavigateTo("/", true); } catch (Exception ex) { errorMessage = "Login failed: " + ex.Message; } } private class InputModel { [Required(ErrorMessage = "UserName cannot be empty")] [StringLength(20, ErrorMessage = "1-20 length")] public string UserName { get; set; } = string.Empty; [Required(ErrorMessage = "Password cannot be empty")] [DataType(DataType.Password)] [StringLength(20, MinimumLength = 1, ErrorMessage = "1-20 length")] public string Password { get; set; } = string.Empty; public bool RememberMe { get; set; } = false; }}As you can see, I am using MudTextField to bind a login user model. However, it took me so long to understand that MudTexField's bind-value only functioning under InteractiveServer mode. However, you can see I need HttpContext to set cookies once login, and HttpContext is unavailable under static mode. This is such a dilemma and have no idea to work out. Is there a solution to simultaneously keep Mudblazor style and using HttpContext to set cookies?
BTW, below is my Program.cs, currently it is set to Server mode and per page/compnent:
using MudBlazor.Services;using Microsoft.EntityFrameworkCore;using FileGrid.Components;using Minio;using Microsoft.AspNetCore.Components.Authorization;using Microsoft.AspNetCore.Components.Server;using Microsoft.AspNetCore.Authentication.Cookies;var builder = WebApplication.CreateBuilder(args);// Add db contextbuilder.Services.AddDbContext<FileGrid.Entities.FileGridContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("FileGrid")));// Add Controllersbuilder.Services.AddControllers();// Add MudBlazor servicesbuilder.Services.AddMudServices();// Add services to the container.builder.Services.AddRazorComponents() .AddInteractiveServerComponents();// Add HttpClient servicebuilder.Services.AddHttpClient("FileGridHTTPClient", client => client.BaseAddress = new Uri(builder.Configuration["HTTPClientBaseUri"] ?? "http://localhost:5039/")) .ConfigurePrimaryHttpMessageHandler(() => { var handler = new HttpClientHandler(); handler.UseCookies = true; handler.CookieContainer = new System.Net.CookieContainer(); return handler; });// Add services to Authbuilder.Services.AddCascadingAuthenticationState();builder.Services.AddAuthorization();builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => { options.Cookie.HttpOnly = true; options.Cookie.SecurePolicy = CookieSecurePolicy.Always; options.LoginPath = "/login"; options.ExpireTimeSpan = TimeSpan.FromDays(7); options.SlidingExpiration = true; });// builder.Services.Configure<CookiePolicyOptions>(options =>// {// options.ConsentCookie.IsEssential = true;// // This lambda determines whether user consent for non-essential cookies is needed for a given request.// options.CheckConsentNeeded = context => false;// });// Add Minio client as singleton servicebuilder.Services.AddSingleton(new MinioClient() .WithEndpoint("localhost:9000") .WithCredentials("minioadmin", "minioadmin") .Build());// 添加在其他服务注册之后builder.Services.AddScoped<AuthenticationStateProvider, ServerAuthenticationStateProvider>();var app = builder.Build();// Configure the HTTP request pipeline.if (!app.Environment.IsDevelopment()){ app.UseExceptionHandler("/Error", createScopeForErrors: true); // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. app.UseHsts();}app.UseHttpsRedirection();app.UseRouting();app.UseAuthentication();app.UseAuthorization();app.UseAntiforgery();app.MapStaticAssets();app.MapControllers();app.MapRazorComponents<App>() .AddInteractiveServerRenderMode();app.Run();And App.razor:
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><base href="/" /><link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" rel="stylesheet" /><link href=@Assets["_content/MudBlazor/MudBlazor.min.css"] rel="stylesheet" /><ImportMap /><link rel="icon" type="image/ico" href="favicon.ico" /><HeadOutlet /></head><body><Routes /><script src="_framework/blazor.web.js"></script><script src=@Assets["_content/MudBlazor/MudBlazor.min.js"]></script></body></html>For the code above,
- I can't trigger remember me check box, seems it can't bind to login model properly.
- When login button is clicked, errors are like:
An unhandled exception occurred while processing the request.InvalidOperationException: EditForm requires either a Model parameter, or an EditContext parameter, please provide one of these.Microsoft.AspNetCore.Components.Forms.EditForm.OnParametersSet()Microsoft.AspNetCore.Components.Forms.EditForm.OnParametersSet()Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()Microsoft.AspNetCore.Components.RenderTree.Renderer.HandleExceptionViaErrorBoundary(Exception error, ComponentState errorSourceOrNull)Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasksWithErrorHandling(Task task, ComponentState owningComponentState)Microsoft.AspNetCore.Components.Rendering.ComponentState.SupplyCombinedParameters(ParameterView directAndCascadingParameters)Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewComponentFrame(ref DiffContext diffContext, int frameIndex)Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InitializeNewSubtree(ref DiffContext diffContext, int frameIndex)Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.InsertNewFrame(ref DiffContext diffContext, int newFrameIndex)Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.AppendDiffEntriesForRange(ref DiffContext diffContext, int oldStartIndex, int oldEndIndexExcl, int newStartIndex, int newEndIndexExcl)Microsoft.AspNetCore.Components.RenderTree.RenderTreeDiffBuilder.ComputeDiff(Renderer renderer, RenderBatchBuilder batchBuilder, int componentId, ArrayRange<RenderTreeFrame> oldTree, ArrayRange<RenderTreeFrame> newTree)Microsoft.AspNetCore.Components.Rendering.ComponentState.RenderIntoBatch(RenderBatchBuilder batchBuilder, RenderFragment renderFragment, out Exception renderFragmentException)Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderInExistingBatch(RenderQueueEntry renderQueueEntry)Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessRenderQueue()Microsoft.AspNetCore.Components.RenderTree.Renderer.ProcessPendingRender()Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToRenderQueue(int componentId, RenderFragment renderFragment)Microsoft.AspNetCore.Components.RenderHandle.Render(RenderFragment renderFragment)Microsoft.AspNetCore.Components.ComponentBase.StateHasChanged()Microsoft.AspNetCore.Components.ComponentBase.CallOnParametersSetAsync()Microsoft.AspNetCore.Components.ComponentBase.RunInitAndSetParametersAsync()Microsoft.AspNetCore.Components.RenderTree.Renderer.HandleExceptionViaErrorBoundary(Exception error, ComponentState errorSourceOrNull)Microsoft.AspNetCore.Components.RenderTree.Renderer.AddToPendingTasksWithErrorHandling(Task task, ComponentState owningComponentState)Microsoft.AspNetCore.Components.Rendering.ComponentState.SupplyCombinedParameters(ParameterView directAndCascadingParameters)Microsoft.AspNetCore.Components.Rendering.ComponentState.SetDirectParameters(ParameterView parameters)Microsoft.AspNetCore.Components.RenderTree.Renderer.RenderRootComponentAsync(int componentId, ParameterView initialParameters)Microsoft.AspNetCore.Components.HtmlRendering.Infrastructure.StaticHtmlRenderer.BeginRenderingComponent(IComponent component, ParameterView initialParameters)Microsoft.AspNetCore.Components.Endpoints.EndpointHtmlRenderer.RenderEndpointComponent(HttpContext httpContext, Type rootComponentType, ParameterView parameters, bool waitForQuiescence)System.Runtime.CompilerServices.ValueTaskAwaiter<TResult>.GetResult()Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore(HttpContext context)Microsoft.AspNetCore.Components.Endpoints.RazorComponentEndpointInvoker.RenderComponentCore(HttpContext context)Microsoft.AspNetCore.Components.Rendering.RendererSynchronizationContext+<>c+<<InvokeAsync>b__10_0>d.MoveNext()Microsoft.AspNetCore.Builder.ServerRazorComponentsEndpointConventionBuilderExtensions+<>c__DisplayClass1_1+<<AddInteractiveServerRenderMode>b__1>d.MoveNext()Microsoft.AspNetCore.Antiforgery.Internal.AntiforgeryMiddleware.InvokeAwaited(HttpContext context)Microsoft.AspNetCore.Authorization.AuthorizationMiddleware.Invoke(HttpContext context)Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)This error is due to MudTextField's binding malfunction.So I add @rendermode @(RenderMode.InteractiveServer) to Login.razor, and now it binding well, however, leads to Null reference to HttpContext.So this is it, anyone can help with my hair?
I finally figured out how to bind login user mode properly, simple add a name attribute to MudTextField. Like this:
<MudTextField @bind-Value="@Input.UserName" name="Input.UserName" />But another question remains: the checkbox refused to toggle, ie, I can't toggle the value of RememberMe.