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 context
builder.Services.AddDbContext<FileGrid.Entities.FileGridContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("FileGrid")));
// Add Controllers
builder.Services.AddControllers();
// Add MudBlazor services
builder.Services.AddMudServices();
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents();
// Add HttpClient service
builder.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 Auth
builder.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 service
builder.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,
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.
The account pages are static pages and as such Interactive components will not function as expected within them. You can use the community package MudBlazor.StaticInput which provides static versions of a few components that would look and function like standard MudBlazor components in static pages. If you create your project using the MudBlazor template, StaticInput would be included by default in the templated account pages.