diff --git a/src/MudBlazor.Templates.csproj b/src/MudBlazor.Templates.csproj index d355a396..677e0a91 100644 --- a/src/MudBlazor.Templates.csproj +++ b/src/MudBlazor.Templates.csproj @@ -18,7 +18,7 @@ Template - net8.0 + net9.0 true false content diff --git a/src/mudblazor/.template.config/dotnetcli.host.json b/src/mudblazor/.template.config/dotnetcli.host.json index 897cc417..9c853513 100644 --- a/src/mudblazor/.template.config/dotnetcli.host.json +++ b/src/mudblazor/.template.config/dotnetcli.host.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/dotnetcli.host", + "$schema": "https://json.schemastore.org/dotnetcli.host", "symbolInfo": { "skipRestore": { "longName": "no-restore", diff --git a/src/mudblazor/.template.config/ide.host.json b/src/mudblazor/.template.config/ide.host.json index 5c5a1d00..71b187ae 100644 --- a/src/mudblazor/.template.config/ide.host.json +++ b/src/mudblazor/.template.config/ide.host.json @@ -1,5 +1,5 @@ { - "$schema": "http://json.schemastore.org/ide.host", + "$schema": "https://json.schemastore.org/ide.host", "order": 610, "icon": "icon.png", "disableHttpsSymbol": "NoHttps", diff --git a/src/mudblazor/.template.config/template.json b/src/mudblazor/.template.config/template.json index fccfc427..585307d5 100644 --- a/src/mudblazor/.template.config/template.json +++ b/src/mudblazor/.template.config/template.json @@ -18,7 +18,7 @@ "650B3CE7-2E93-4CC4-9F46-466686815EAA", "53bc9b9d-9d6a-45d4-8429-2a2761773502" ], - "identity": "MudBlazorWebApp.CSharp.8.0", + "identity": "MudBlazorWebApp.CSharp.10.0", "thirdPartyNotices": "", "preferNameDirectory": true, "primaryOutputs": [ @@ -52,13 +52,7 @@ "**/wwwroot/bootstrap/**" ], "modifiers": [ - { - "condition": "(!IndividualLocalAuth)", - "exclude": [ - "**/wwwroot/bootstrap/**" - ] - }, - { + { "condition": "(!UseWebAssembly)", "exclude": [ "MudBlazor.Template.Client/**", @@ -73,6 +67,7 @@ "rename": { "MudBlazor.Template/Components/Layout/": "./MudBlazor.Template.Client/Layout/", "MudBlazor.Template/Components/Pages/Home.razor": "./MudBlazor.Template.Client/Pages/Home.razor", + "MudBlazor.Template/Components/Pages/Weather.razor": "./MudBlazor.Template.Client/Pages/Weather.razor", "MudBlazor.Template/Components/Routes.razor": "./MudBlazor.Template.Client/Routes.razor" } }, @@ -94,10 +89,9 @@ } }, { - "condition": "(UseWebAssembly || (!UseWebAssembly && !UseServer))", + "condition": "(!(UseServer && !UseWebAssembly))", "exclude": [ - "MudBlazor.Template/Components/Pages/Counter.razor", - "MudBlazor.Template/Components/Pages/Weather.razor" + "MudBlazor.Template/Components/Pages/Counter.razor" ] }, { @@ -130,7 +124,6 @@ "exclude": [ "MudBlazor.Template/Components/Account/**", "MudBlazor.Template/Data/**", - "MudBlazor.Template.Client/PersistentAuthenticationStateProvider.cs", "MudBlazor.Template.Client/UserInfo.cs", "MudBlazor.Template.Client/Pages/Auth.razor" ] @@ -148,24 +141,12 @@ ] }, { - "condition": "(!(IndividualLocalAuth && UseServer && UseWebAssembly))", - "exclude": [ - "MudBlazor.Template/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs" - ] - }, - { - "condition": "(!(IndividualLocalAuth && UseServer && !UseWebAssembly))", + "condition": "(!(IndividualLocalAuth && UseServer))", "exclude": [ "MudBlazor.Template/Components/Account/IdentityRevalidatingAuthenticationStateProvider.cs" ] }, - { - "condition": "(!(IndividualLocalAuth && !UseServer && UseWebAssembly))", - "exclude": [ - "MudBlazor.Template/Components/Account/PersistingServerAuthenticationStateProvider.cs" - ] - }, - { + { "condition": "(IndividualLocalAuth && UseLocalDB && UseWebAssembly)", "rename": { "MudBlazor.Template/Data/SqlServer/": "MudBlazor.Template/Data/Migrations/" @@ -217,12 +198,12 @@ "datatype": "choice", "choices": [ { - "choice": "net8.0", - "description": "Target net8.0" + "choice": "net10.0", + "description": "Target net10.0" } ], - "replaces": "net8.0", - "defaultValue": "net8.0" + "replaces": "net10.0", + "defaultValue": "net10.0" }, "HostIdentifier": { "type": "bind", @@ -364,7 +345,7 @@ "type": "parameter", "datatype": "choice", "defaultValue": "InteractiveGlobal", - "displayName": "_Interactivity location", + "displayName": "Interactivity _location", "description": "Chooses which components will have interactive rendering enabled", "isEnabled": "(InteractivityPlatform != \"None\")", "choices": [ @@ -392,7 +373,7 @@ "type": "parameter", "datatype": "bool", "defaultValue": "true", - "displayName": "_Include sample pages", + "displayName": "Include _sample pages", "description": "Configures whether to add sample pages and styling to demonstrate basic usage patterns." }, "Empty": { diff --git a/src/mudblazor/MudBlazor.Template.Client/MudBlazor.Template.Client.csproj b/src/mudblazor/MudBlazor.Template.Client/MudBlazor.Template.Client.csproj deleted file mode 100644 index 670e7b5b..00000000 --- a/src/mudblazor/MudBlazor.Template.Client/MudBlazor.Template.Client.csproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - net8.0 - enable - enable - true - Default - MudBlazor.Template.Client - `$(AssemblyName.Replace(' ', '_')) - - - - - - - - - diff --git a/src/mudblazor/MudBlazor.Template.Client/Pages/Weather.razor b/src/mudblazor/MudBlazor.Template.Client/Pages/Weather.razor deleted file mode 100644 index 465fd796..00000000 --- a/src/mudblazor/MudBlazor.Template.Client/Pages/Weather.razor +++ /dev/null @@ -1,77 +0,0 @@ -@page "/weather" -@*#if (UseServer && !InteractiveAtRoot) -@rendermode InteractiveAuto -##elseif (!InteractiveAtRoot) -@rendermode InteractiveWebAssembly -##endif*@ - -@*#if (!InteractiveAtRoot) - - - -##endif*@ - -@*#if (!InteractiveAtRoot) --> -@attribute [StreamRendering] -##endif*@ - -Weather - -Weather forecast -This component demonstrates fetching data from the server. - -@if (forecasts == null) -{ - -} -else -{ - - - Date - Temp. (C) - Temp. (F) - Summary - - - @context.Date - @context.TemperatureC - @context.TemperatureF - @context.Summary - - - - - -} - -@code { - private WeatherForecast[]? forecasts; - - protected override async Task OnInitializedAsync() - { -@*#if (InteractiveAtRoot) --> - // Simulate asynchronous loading to demonstrate a loading indicator -##else - // Simulate asynchronous loading to demonstrate streaming rendering -##endif*@ - await Task.Delay(500); - - var startDate = DateOnly.FromDateTime(DateTime.Now); - var summaries = new[] { "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; - forecasts = Enumerable.Range(1, 5).Select(index => new WeatherForecast - { - Date = startDate.AddDays(index), - TemperatureC = Random.Shared.Next(-20, 55), - Summary = summaries[Random.Shared.Next(summaries.Length)] - }).ToArray(); - } - - private class WeatherForecast - { - public DateOnly Date { get; set; } - public int TemperatureC { get; set; } - public string? Summary { get; set; } - public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); - } -} diff --git a/src/mudblazor/MudBlazor.Template.Client/PersistentAuthenticationStateProvider.cs b/src/mudblazor/MudBlazor.Template.Client/PersistentAuthenticationStateProvider.cs deleted file mode 100644 index 9c8e7318..00000000 --- a/src/mudblazor/MudBlazor.Template.Client/PersistentAuthenticationStateProvider.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Security.Claims; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Authorization; - -namespace MudBlazor.Template.Client; - -// This is a client-side AuthenticationStateProvider that determines the user's authentication state by -// looking for data persisted in the page when it was rendered on the server. This authentication state will -// be fixed for the lifetime of the WebAssembly application. So, if the user needs to log in or out, a full -// page reload is required. -// -// This only provides a user name and email for display purposes. It does not actually include any tokens -// that authenticate to the server when making subsequent requests. That works separately using a -// cookie that will be included on HttpClient requests to the server. -internal class PersistentAuthenticationStateProvider : AuthenticationStateProvider -{ - private static readonly Task defaultUnauthenticatedTask = - Task.FromResult(new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()))); - - private readonly Task authenticationStateTask = defaultUnauthenticatedTask; - - public PersistentAuthenticationStateProvider(PersistentComponentState state) - { - if (!state.TryTakeFromJson(nameof(UserInfo), out var userInfo) || userInfo is null) - { - return; - } - - Claim[] claims = [ - new Claim(ClaimTypes.NameIdentifier, userInfo.UserId), - new Claim(ClaimTypes.Name, userInfo.Email), - new Claim(ClaimTypes.Email, userInfo.Email) ]; - - authenticationStateTask = Task.FromResult( - new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(claims, - authenticationType: nameof(PersistentAuthenticationStateProvider))))); - } - - public override Task GetAuthenticationStateAsync() => authenticationStateTask; -} diff --git a/src/mudblazor/MudBlazor.Template.Client/Program.Main.cs b/src/mudblazor/MudBlazor.Template.Client/Program.Main.cs index 2ddab1a5..7dd2b730 100644 --- a/src/mudblazor/MudBlazor.Template.Client/Program.Main.cs +++ b/src/mudblazor/MudBlazor.Template.Client/Program.Main.cs @@ -1,7 +1,3 @@ -#if (IndividualLocalAuth) -using MudBlazor.Template.Client; -using Microsoft.AspNetCore.Components.Authorization; -#endif using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MudBlazor.Services; @@ -12,16 +8,14 @@ class Program static async Task Main(string[] args) { var builder = WebAssemblyHostBuilder.CreateDefault(args); - - builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddMudServices(); #if (IndividualLocalAuth) builder.Services.AddAuthorizationCore(); builder.Services.AddCascadingAuthenticationState(); - builder.Services.AddSingleton(); -#endif + builder.Services.AddAuthenticationStateDeserialization(); + #endif await builder.Build().RunAsync(); } } diff --git a/src/mudblazor/MudBlazor.Template.Client/Program.cs b/src/mudblazor/MudBlazor.Template.Client/Program.cs index 1824e22b..2a917fae 100644 --- a/src/mudblazor/MudBlazor.Template.Client/Program.cs +++ b/src/mudblazor/MudBlazor.Template.Client/Program.cs @@ -1,19 +1,14 @@ -#if (IndividualLocalAuth) -using MudBlazor.Template.Client; -using Microsoft.AspNetCore.Components.Authorization; -#endif using Microsoft.AspNetCore.Components.WebAssembly.Hosting; using MudBlazor.Services; var builder = WebAssemblyHostBuilder.CreateDefault(args); -builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.AddMudServices(); #if (IndividualLocalAuth) builder.Services.AddAuthorizationCore(); builder.Services.AddCascadingAuthenticationState(); -builder.Services.AddSingleton(); +builder.Services.AddAuthenticationStateDeserialization(); #endif await builder.Build().RunAsync(); diff --git a/src/mudblazor/MudBlazor.Template.Client/UserInfo.cs b/src/mudblazor/MudBlazor.Template.Client/UserInfo.cs deleted file mode 100644 index fe85b65c..00000000 --- a/src/mudblazor/MudBlazor.Template.Client/UserInfo.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace MudBlazor.Template.Client; - -// Add properties to this class and update the server and client AuthenticationStateProviders -// to expose more information about the authenticated user to the client. -public class UserInfo -{ - public required string UserId { get; set; } - public required string Email { get; set; } -} diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs b/src/mudblazor/MudBlazor.Template/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs index a5f32bbf..ceb03f24 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs +++ b/src/mudblazor/MudBlazor.Template/Components/Account/IdentityComponentsEndpointRouteBuilderExtensions.cs @@ -42,7 +42,7 @@ public static IEndpointConventionBuilder MapAdditionalIdentityEndpoints(this IEn accountGroup.MapPost("/Logout", async ( ClaimsPrincipal user, - SignInManager signInManager, + [FromServices] SignInManager signInManager, [FromForm] string returnUrl) => { await signInManager.SignOutAsync(); diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ExternalLogin.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ExternalLogin.razor index 997b7da8..3c253a4c 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ExternalLogin.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ExternalLogin.razor @@ -35,8 +35,8 @@
- - + +
@@ -48,7 +48,7 @@ public const string LoginCallbackAction = "LoginCallback"; private string? message; - private ExternalLoginInfo externalLoginInfo = default!; + private ExternalLoginInfo? externalLoginInfo; [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; @@ -65,7 +65,7 @@ [SupplyParameterFromQuery] private string? Action { get; set; } - private string? ProviderDisplayName => externalLoginInfo.ProviderDisplayName; + private string? ProviderDisplayName => externalLoginInfo?.ProviderDisplayName; protected override async Task OnInitializedAsync() { @@ -98,6 +98,11 @@ private async Task OnLoginCallbackAsync() { + if (externalLoginInfo is null) + { + RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information.", HttpContext); + } + // Sign in the user with this external login provider if the user already has a login. var result = await SignInManager.ExternalLoginSignInAsync( externalLoginInfo.LoginProvider, @@ -127,6 +132,11 @@ private async Task OnValidSubmitAsync() { + if (externalLoginInfo is null) + { + RedirectManager.RedirectToWithStatus("Account/Login", "Error loading external login information during confirmation.", HttpContext); + } + var emailStore = GetEmailStore(); var user = CreateUser(); diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ForgotPassword.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ForgotPassword.razor index 27ef1192..061c98fc 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ForgotPassword.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ForgotPassword.razor @@ -15,15 +15,15 @@ Forgot your password? Forgot your password? - Enter your email. + - diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/InvalidPasswordReset.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/InvalidPasswordReset.razor index 509578bb..561b651a 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/InvalidPasswordReset.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/InvalidPasswordReset.razor @@ -3,6 +3,6 @@ Invalid password reset

Invalid password reset

-

+

The password reset link is invalid.

diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Lockout.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Lockout.razor index a8d1e0af..017e31d3 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Lockout.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Lockout.razor @@ -4,5 +4,5 @@

Locked out

-

This account has been locked out, please try again later.

+
diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Login.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Login.razor index ec185008..187f3a89 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Login.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Login.razor @@ -26,7 +26,7 @@ + UserAttributes="@(new() { { "autocomplete", "username" }, { "aria-required", "true" } } )" />
- - + +
diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Manage/Email.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Manage/Email.razor index 51165739..2159d22f 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Manage/Email.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Manage/Email.razor @@ -43,7 +43,7 @@ } - + diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Manage/SetPassword.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Manage/SetPassword.razor index ba50d093..0fe59726 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Manage/SetPassword.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Manage/SetPassword.razor @@ -18,18 +18,18 @@ account so you can log in without an external login.

-
+
- - + +
- - + +
diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Register.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Register.razor index 1f7d3c73..bd35081b 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Register.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/Register.razor @@ -31,7 +31,7 @@ + UserAttributes="@(new() { { "autocomplete", "username" }, { "aria-required", "true" } } )" /> Please check your email to confirm your account.

+

Please check your email to confirm your account.

} @code { diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ResendEmailConfirmation.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ResendEmailConfirmation.razor index 86e4ee8b..7aff4072 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ResendEmailConfirmation.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ResendEmailConfirmation.razor @@ -27,7 +27,7 @@ + UserAttributes="@(new() { { "autocomplete", "username" }, { "aria-required", "true" } } )" /> Resend diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ResetPassword.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ResetPassword.razor index 922a2a69..7cc68a58 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ResetPassword.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ResetPassword.razor @@ -23,18 +23,18 @@
- - + +
- - + +
- - + +
diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ResetPasswordConfirmation.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ResetPasswordConfirmation.razor index ac54ab60..247e96ec 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ResetPasswordConfirmation.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/ResetPasswordConfirmation.razor @@ -2,6 +2,6 @@ Reset password confirmation

Reset password confirmation

-

- Your password has been reset. Please click here to log in. +

+ Your password has been reset. Please click here to log in.

diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/_Imports.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/_Imports.razor index ef2581cd..78eca821 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Pages/_Imports.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Pages/_Imports.razor @@ -1,3 +1,3 @@ @using MudBlazor @using MudBlazor.Template.Components.Account.Shared -@layout AccountLayout +@attribute [ExcludeFromInteractiveRouting] diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs b/src/mudblazor/MudBlazor.Template/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs deleted file mode 100644 index 5be232d3..00000000 --- a/src/mudblazor/MudBlazor.Template/Components/Account/PersistingRevalidatingAuthenticationStateProvider.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System.Diagnostics; -using System.Security.Claims; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Components.Server; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using MudBlazor.Template.Client; -using MudBlazor.Template.Data; - -namespace MudBlazor.Template.Components.Account; - -// This is a server-side AuthenticationStateProvider that revalidates the security stamp for the connected user -// every 30 minutes an interactive circuit is connected. It also uses PersistentComponentState to flow the -// authentication state to the client which is then fixed for the lifetime of the WebAssembly application. -internal sealed class PersistingRevalidatingAuthenticationStateProvider : RevalidatingServerAuthenticationStateProvider -{ - private readonly IServiceScopeFactory scopeFactory; - private readonly PersistentComponentState state; - private readonly IdentityOptions options; - - private readonly PersistingComponentStateSubscription subscription; - - private Task? authenticationStateTask; - - public PersistingRevalidatingAuthenticationStateProvider( - ILoggerFactory loggerFactory, - IServiceScopeFactory serviceScopeFactory, - PersistentComponentState persistentComponentState, - IOptions optionsAccessor) - : base(loggerFactory) - { - scopeFactory = serviceScopeFactory; - state = persistentComponentState; - options = optionsAccessor.Value; - - AuthenticationStateChanged += OnAuthenticationStateChanged; - subscription = state.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly); - } - - protected override TimeSpan RevalidationInterval => TimeSpan.FromMinutes(30); - - protected override async Task ValidateAuthenticationStateAsync( - AuthenticationState authenticationState, CancellationToken cancellationToken) - { - // Get the user manager from a new scope to ensure it fetches fresh data - await using var scope = scopeFactory.CreateAsyncScope(); - var userManager = scope.ServiceProvider.GetRequiredService>(); - return await ValidateSecurityStampAsync(userManager, authenticationState.User); - } - - private async Task ValidateSecurityStampAsync(UserManager userManager, ClaimsPrincipal principal) - { - var user = await userManager.GetUserAsync(principal); - if (user is null) - { - return false; - } - else if (!userManager.SupportsUserSecurityStamp) - { - return true; - } - else - { - var principalStamp = principal.FindFirstValue(options.ClaimsIdentity.SecurityStampClaimType); - var userStamp = await userManager.GetSecurityStampAsync(user); - return principalStamp == userStamp; - } - } - - private void OnAuthenticationStateChanged(Task task) - { - authenticationStateTask = task; - } - - private async Task OnPersistingAsync() - { - if (authenticationStateTask is null) - { - throw new UnreachableException($"Authentication state not set in {nameof(OnPersistingAsync)}()."); - } - - var authenticationState = await authenticationStateTask; - var principal = authenticationState.User; - - if (principal.Identity?.IsAuthenticated == true) - { - var userId = principal.FindFirst(options.ClaimsIdentity.UserIdClaimType)?.Value; - var email = principal.FindFirst(options.ClaimsIdentity.EmailClaimType)?.Value; - - if (userId != null && email != null) - { - state.PersistAsJson(nameof(UserInfo), new UserInfo - { - UserId = userId, - Email = email, - }); - } - } - } - - protected override void Dispose(bool disposing) - { - subscription.Dispose(); - AuthenticationStateChanged -= OnAuthenticationStateChanged; - base.Dispose(disposing); - } -} diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/PersistingServerAuthenticationStateProvider.cs b/src/mudblazor/MudBlazor.Template/Components/Account/PersistingServerAuthenticationStateProvider.cs deleted file mode 100644 index 3c2912cb..00000000 --- a/src/mudblazor/MudBlazor.Template/Components/Account/PersistingServerAuthenticationStateProvider.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Diagnostics; -using Microsoft.AspNetCore.Components; -using Microsoft.AspNetCore.Components.Authorization; -using Microsoft.AspNetCore.Components.Server; -using Microsoft.AspNetCore.Components.Web; -using Microsoft.AspNetCore.Identity; -using Microsoft.Extensions.Options; -using MudBlazor.Template.Client; - -namespace MudBlazor.Template.Components.Account; - -// This is a server-side AuthenticationStateProvider that uses PersistentComponentState to flow the -// authentication state to the client which is then fixed for the lifetime of the WebAssembly application. -internal sealed class PersistingServerAuthenticationStateProvider : ServerAuthenticationStateProvider, IDisposable -{ - private readonly PersistentComponentState state; - private readonly IdentityOptions options; - - private readonly PersistingComponentStateSubscription subscription; - - private Task? authenticationStateTask; - - public PersistingServerAuthenticationStateProvider( - PersistentComponentState persistentComponentState, - IOptions optionsAccessor) - { - state = persistentComponentState; - options = optionsAccessor.Value; - - AuthenticationStateChanged += OnAuthenticationStateChanged; - subscription = state.RegisterOnPersisting(OnPersistingAsync, RenderMode.InteractiveWebAssembly); - } - - private void OnAuthenticationStateChanged(Task task) - { - authenticationStateTask = task; - } - - private async Task OnPersistingAsync() - { - if (authenticationStateTask is null) - { - throw new UnreachableException($"Authentication state not set in {nameof(OnPersistingAsync)}()."); - } - - var authenticationState = await authenticationStateTask; - var principal = authenticationState.User; - - if (principal.Identity?.IsAuthenticated == true) - { - var userId = principal.FindFirst(options.ClaimsIdentity.UserIdClaimType)?.Value; - var email = principal.FindFirst(options.ClaimsIdentity.EmailClaimType)?.Value; - - if (userId != null && email != null) - { - state.PersistAsJson(nameof(UserInfo), new UserInfo - { - UserId = userId, - Email = email, - }); - } - } - } - - public void Dispose() - { - subscription.Dispose(); - AuthenticationStateChanged -= OnAuthenticationStateChanged; - } -} diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Shared/AccountLayout.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Shared/AccountLayout.razor deleted file mode 100644 index f574ce88..00000000 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Shared/AccountLayout.razor +++ /dev/null @@ -1,32 +0,0 @@ -@inherits LayoutComponentBase -@*#if (UseWebAssembly && InteractiveAtRoot) -@layout MudBlazor.Template.Client.Layout.MainLayout -##else -@layout MudBlazor.Template.Components.Layout.MainLayout -##endif*@ -@inject NavigationManager NavigationManager - -@if (HttpContext is null) -{ -

Loading...

-} -else -{ - @Body -} - -@code { - [CascadingParameter] - private HttpContext? HttpContext { get; set; } - - protected override void OnParametersSet() - { - if (HttpContext is null) - { - // If this code runs, we're currently rendering in interactive mode, so there is no HttpContext. - // The identity pages need to set cookies, so they require an HttpContext. To achieve this we - // must transition back from interactive mode to a server-rendered page. - NavigationManager.Refresh(forceReload: true); - } - } -} diff --git a/src/mudblazor/MudBlazor.Template/Components/Account/Shared/ManageLayout.razor b/src/mudblazor/MudBlazor.Template/Components/Account/Shared/ManageLayout.razor index 6ab82b4b..38c4cee1 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Account/Shared/ManageLayout.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Account/Shared/ManageLayout.razor @@ -1,5 +1,9 @@ @inherits LayoutComponentBase -@layout AccountLayout +@*#if (UseWebAssembly && InteractiveAtRoot) +@layout MudBlazor.Template.Client.Layout.MainLayout +##else +@layout MudBlazor.Template.Components.Layout.MainLayout +##endif*@ Manage your account diff --git a/src/mudblazor/MudBlazor.Template/Components/App.razor b/src/mudblazor/MudBlazor.Template/Components/App.razor index 8602ef92..bae5c738 100644 --- a/src/mudblazor/MudBlazor.Template/Components/App.razor +++ b/src/mudblazor/MudBlazor.Template/Components/App.razor @@ -6,14 +6,15 @@ - + + @*#if (SampleContent) ##endif*@ @*#if (!InteractiveAtRoot) ##elseif (IndividualLocalAuth) - + ##elseif (UseServer && UseWebAssembly) ##elseif (UseServer) @@ -27,7 +28,7 @@ @*#if (!InteractiveAtRoot) ##elseif (IndividualLocalAuth) - + ##elseif (UseServer && UseWebAssembly) ##elseif (UseServer) @@ -50,9 +51,8 @@ [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account") - ? null - : InteractiveAuto; + private IComponentRenderMode? PageRenderMode => + HttpContext.AcceptsInteractiveRouting() ? InteractiveAuto : null; } #elseif (UseServer) @@ -60,9 +60,8 @@ [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account") - ? null - : InteractiveServer; + private IComponentRenderMode? PageRenderMode => + HttpContext.AcceptsInteractiveRouting() ? InteractiveServer : null; } #else @@ -70,8 +69,7 @@ [CascadingParameter] private HttpContext HttpContext { get; set; } = default!; - private IComponentRenderMode? RenderModeForPage => HttpContext.Request.Path.StartsWithSegments("/Account") - ? null - : InteractiveWebAssembly; + private IComponentRenderMode? PageRenderMode => + HttpContext.AcceptsInteractiveRouting() ? InteractiveWebAssembly : null; } #endif*@ diff --git a/src/mudblazor/MudBlazor.Template/Components/Layout/MainLayout.razor b/src/mudblazor/MudBlazor.Template/Components/Layout/MainLayout.razor index 1bf401c2..f2bf60b1 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Layout/MainLayout.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Layout/MainLayout.razor @@ -35,10 +35,10 @@ @*#if (UseServer || UseWebAssembly) --> -
+
An unhandled error has occurred. - Reload - 🗙 + Reload + 🗙
##endif*@ diff --git a/src/mudblazor/MudBlazor.Template/Components/Layout/NavMenu.razor b/src/mudblazor/MudBlazor.Template/Components/Layout/NavMenu.razor index 4a29e319..d67071bb 100644 --- a/src/mudblazor/MudBlazor.Template/Components/Layout/NavMenu.razor +++ b/src/mudblazor/MudBlazor.Template/Components/Layout/NavMenu.razor @@ -8,8 +8,10 @@ Home @*#if (UseServer || UseWebAssembly) Counter - Weather ##endif*@ + + Weather + @*#if (IndividualLocalAuth) Auth Required @@ -53,6 +55,5 @@ NavigationManager.LocationChanged -= OnLocationChanged; } } - ##endif*@ diff --git a/src/mudblazor/MudBlazor.Template/MudBlazor.Template.csproj b/src/mudblazor/MudBlazor.Template/MudBlazor.Template.csproj deleted file mode 100644 index 6bdcc57a..00000000 --- a/src/mudblazor/MudBlazor.Template/MudBlazor.Template.csproj +++ /dev/null @@ -1,39 +0,0 @@ - - - - net8.0 - enable - enable - aspnet-MudBlazor.Template-53bc9b9d-9d6a-45d4-8429-2a2761773502 - True - MudBlazor.Template - `$(AssemblyName.Replace(' ', '_')) - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/mudblazor/MudBlazor.Template/Program.Main.cs b/src/mudblazor/MudBlazor.Template/Program.Main.cs index 6886cdd2..d495cd81 100644 --- a/src/mudblazor/MudBlazor.Template/Program.Main.cs +++ b/src/mudblazor/MudBlazor.Template/Program.Main.cs @@ -1,7 +1,6 @@ #if (IndividualLocalAuth) +#if (UseServer) using Microsoft.AspNetCore.Components.Authorization; -#if (!UseServer && !UseWebAssembly) -using Microsoft.AspNetCore.Components.Server; #endif using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; @@ -28,16 +27,23 @@ public static void Main(string[] args) builder.Services.AddMudServices(); // Add services to the container. -#if (!UseServer && !UseWebAssembly) + #if (!UseServer && !UseWebAssembly) builder.Services.AddRazorComponents(); #else builder.Services.AddRazorComponents() - #if (UseServer && UseWebAssembly) + #if (UseServer && UseWebAssembly && IndividualLocalAuth) + .AddInteractiveServerComponents() + .AddInteractiveWebAssemblyComponents() + .AddAuthenticationStateSerialization(); + #elif (UseServer && UseWebAssembly) .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents(); - #elif(UseServer) + #elif (UseServer) .AddInteractiveServerComponents(); - #elif(UseWebAssembly) + #elif (UseWebAssembly && IndividualLocalAuth) + .AddInteractiveWebAssemblyComponents() + .AddAuthenticationStateSerialization(); + #elif (UseWebAssembly) .AddInteractiveWebAssemblyComponents(); #endif #endif @@ -46,25 +52,19 @@ public static void Main(string[] args) builder.Services.AddCascadingAuthenticationState(); builder.Services.AddScoped(); builder.Services.AddScoped(); - #if (UseServer && UseWebAssembly) - builder.Services.AddScoped(); - #elif (UseServer) + #if (UseServer) builder.Services.AddScoped(); - #elif (UseWebAssembly) - builder.Services.AddScoped(); - #else - builder.Services.AddScoped(); #endif - #if (!UseServer) - builder.Services.AddAuthorization(); - #endif builder.Services.AddAuthentication(options => { options.DefaultScheme = IdentityConstants.ApplicationScheme; options.DefaultSignInScheme = IdentityConstants.ExternalScheme; }) .AddIdentityCookies(); + #if (!UseServer) + builder.Services.AddAuthorization(); + #endif var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); builder.Services.AddDbContext(options => @@ -112,9 +112,9 @@ public static void Main(string[] args) app.UseHttpsRedirection(); #endif - app.UseStaticFiles(); app.UseAntiforgery(); + app.MapStaticAssets(); #if (UseServer && UseWebAssembly) app.MapRazorComponents() .AddInteractiveServerRenderMode() diff --git a/src/mudblazor/MudBlazor.Template/Program.cs b/src/mudblazor/MudBlazor.Template/Program.cs index 298da549..bcd86487 100644 --- a/src/mudblazor/MudBlazor.Template/Program.cs +++ b/src/mudblazor/MudBlazor.Template/Program.cs @@ -1,7 +1,6 @@ #if (IndividualLocalAuth) +#if (UseServer) using Microsoft.AspNetCore.Components.Authorization; -#if (!UseServer && !UseWebAssembly) -using Microsoft.AspNetCore.Components.Server; #endif using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; @@ -26,11 +25,18 @@ builder.Services.AddRazorComponents(); #else builder.Services.AddRazorComponents() - #if (UseServer && UseWebAssembly) + #if (UseServer && UseWebAssembly && IndividualLocalAuth) + .AddInteractiveServerComponents() + .AddInteractiveWebAssemblyComponents() + .AddAuthenticationStateSerialization(); + #elif (UseServer && UseWebAssembly) .AddInteractiveServerComponents() .AddInteractiveWebAssemblyComponents(); #elif (UseServer) .AddInteractiveServerComponents(); + #elif (UseWebAssembly && IndividualLocalAuth) + .AddInteractiveWebAssemblyComponents() + .AddAuthenticationStateSerialization(); #elif (UseWebAssembly) .AddInteractiveWebAssemblyComponents(); #endif @@ -40,25 +46,19 @@ builder.Services.AddCascadingAuthenticationState(); builder.Services.AddScoped(); builder.Services.AddScoped(); -#if (UseServer && UseWebAssembly) -builder.Services.AddScoped(); -#elif (UseServer) +#if (UseServer) builder.Services.AddScoped(); -#elif (UseWebAssembly) -builder.Services.AddScoped(); -#else -builder.Services.AddScoped(); #endif -#if (!UseServer) -builder.Services.AddAuthorization(); -#endif builder.Services.AddAuthentication(options => { options.DefaultScheme = IdentityConstants.ApplicationScheme; options.DefaultSignInScheme = IdentityConstants.ExternalScheme; }) .AddIdentityCookies(); +#if (!UseServer) +builder.Services.AddAuthorization(); +#endif var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found."); builder.Services.AddDbContext(options => @@ -106,9 +106,10 @@ app.UseHttpsRedirection(); #endif -app.UseStaticFiles(); + app.UseAntiforgery(); +app.MapStaticAssets(); #if (UseServer && UseWebAssembly) app.MapRazorComponents() .AddInteractiveServerRenderMode() diff --git a/src/mudblazor/MudBlazor.Template/Properties/launchSettings.json b/src/mudblazor/MudBlazor.Template/Properties/launchSettings.json index 8963735e..0d554caa 100644 --- a/src/mudblazor/MudBlazor.Template/Properties/launchSettings.json +++ b/src/mudblazor/MudBlazor.Template/Properties/launchSettings.json @@ -1,17 +1,5 @@ { - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:8080", - //#if (HasHttpsProfile) - "sslPort": 44300 - //#else - "sslPort": 0 - //#endif - } - }, + "$schema": "https://json.schemastore.org/launchsettings.json", "profiles": { //#if (HasHttpProfile) "http": { @@ -25,7 +13,11 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } + //#if (HasHttpsProfile) }, + //#else + } + //#endif //#endif //#if (HasHttpsProfile) "https": { @@ -39,17 +31,7 @@ "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } - }, - //#endif - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - //#if (UseWebAssembly) - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - //#endif - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } } + //#endif } }