.NET 8迁移后Blazor WASM本地正常生产环境404问题求助
我有一个从.NET 6迁移到.NET 8的Blazor WASM客户端应用,本地运行完全正常,但部署到生产环境后会直接跳转到自定义的NotFound页面。已经查了很多资料、试了各种办法,还是没找到问题根源。
项目文件(.csproj)
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly"> <PropertyGroup> <TargetFramework>net8.0</TargetFramework> <Nullable>enable</Nullable> <ImplicitUsings>enable</ImplicitUsings> <NeutralLanguage>en</NeutralLanguage> <Version>1.11.3</Version> <PublishTrimmed>true</PublishTrimmed> <TrimMode>link</TrimMode> <BlazorWebAssemblyLoadAllGlobalizationData>true</BlazorWebAssemblyLoadAllGlobalizationData> </PropertyGroup> <ItemGroup> <PackageReference Include="Blazored.LocalStorage" Version="4.5.0" /> <PackageReference Include="Google.Apis.Webfonts.v1" Version="1.68.0.3415" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="8.0.8"> <TreatAsUsed>true</TreatAsUsed> </PackageReference> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="8.0.8" PrivateAssets="all" /> <PackageReference Include="Microsoft.Authentication.WebAssembly.Msal" Version="8.0.8" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="MudBlazor" Version="7.6.0" /> <PackageReference Include="System.Text.Json" Version="8.0.4" /> </ItemGroup> <ItemGroup> <ProjectReference Include="..\SupplierProductRequest.Application\SupplierProductRequest.Application.csproj" /> <ProjectReference Include="..\SupplierProductRequest.Domain\SupplierProductRequest.Domain.csproj" /> <ProjectReference Include="..\SupplierProductRequest.Shared\SupplierProductRequest.Shared.csproj" /> <ProjectReference Include="..\SupplierProductRequest.UI.Shared\SupplierProductRequest.UI.Shared.csproj" /> </ItemGroup> <ItemGroup> <Content Update="wwwroot\css\app.css"> <CopyToOutputDirectory>Always</CopyToOutputDirectory> </Content> </ItemGroup> </Project>
Program.cs代码
WebAssemblyHostBuilder builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add<App>("#app"); builder.RootComponents.Add<HeadOutlet>("head::after"); builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); builder.Services.InjectSharedUI(); builder.Services.AddBlazoredLocalStorageAsSingleton(); builder.Services.AddMudBlazorSnackbar(); builder.Services.AddMudBlazorResizeListener(); builder.Services.AddMudBlazorResizeObserver(); builder.Services.AddMudBlazorResizeObserverFactory(); builder.Services.AddMudPopoverService(); builder.Services.AddMudServices(); builder.Services.AddCascadingAuthenticationState(); builder.Services.AddHttpClient<IApiClientService, ApiClientService>(factory: (client) => { UriBuilder uriBuilder = new(builder.Configuration.GetValue<string>("ApiEndpoint")); client.BaseAddress = uriBuilder.Uri; string appName = builder.Configuration.GetValue<string?>("ApplicationName") ?? "Online Product Applications"; string appVersion = Extensions.Version(Assembly.GetExecutingAssembly()); return new ApiClientService(client, builder.Configuration, appVersion, appName); }); builder.Services.AddMsalAuthentication<RemoteAuthenticationState, RemoteUserAccount>(options => { builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication); options.ProviderOptions.DefaultAccessTokenScopes.Add("openid"); options.ProviderOptions.DefaultAccessTokenScopes.Add("https://graph.microsoft.com/openid"); options.ProviderOptions.DefaultAccessTokenScopes.Add("offline_access"); options.ProviderOptions.LoginMode = "redirect"; options.UserOptions.RoleClaim = "roles"; options.ProviderOptions.Cache.StoreAuthStateInCookie = true; }); builder.Services.AddSingleton<AppConfiguration>(); CultureInfo.DefaultThreadCurrentCulture = Constants.DefaultCultureInfo; CultureInfo.DefaultThreadCurrentUICulture = Constants.DefaultCultureInfo; await builder.Build().RunAsync();
App.razor代码
<CascadingAuthenticationState> <Error> <Router AppAssembly="@typeof(App).Assembly"> <Found Context="routeData"> <AuthorizeRouteView RouteData="@routeData" DefaultLayout="typeof(InternalAppLayout)"> <NotAuthorized> @if (context.User.Identity is null || context.User.Identity.IsAuthenticated == false) { <RedirectToLogin /> } </NotAuthorized> </AuthorizeRouteView> <FocusOnNavigate RouteData="@routeData" Selector="h1" /> </Found> <NotFound> <SupplierProductRequest.Admin.Pages.NotFound /> </NotFound> </Router> </Error> </CascadingAuthenticationState>
Index.html代码
<title>Online Product Applications</title> <base href="/" /> <link rel="icon" type="image/x-ico href="_content/SupplierProductRequest.UI.Shared/favicon.ico"> <link href="_content/SupplierProductRequest.UI.Shared/css/custom-loader.css" rel="stylesheet" /> <link href="_content/MudBlazor/MudBlazor.min.css" rel="stylesheet" /> <link href="_content/SupplierProductRequest.UI.Shared/css/font.css" rel="stylesheet" /> <base /> <script> </script> <script src="_framework/blazor.webassembly.js?v=5" async></script> <script src="_content/Microsoft.Authentication.WebAssembly.Msal/AuthenticationService.js?v=2" async></script> <script src="_content/SupplierProductRequest.UI.Shared/js/shared_ui_methods.js?v=5" defer></script> <script src="_content/MudBlazor/MudBlazor.min.js?v=4" defer></script> </head> <body style="min-height:600px;"> <Routes @rendermode="InteractiveServer" /> <div id="app"></div> <!-- <App @rendermode="@RenderMode.WebAssembly" />--> </body> </html>
排查与解决方向
删除Index.html中的重复base标签
你的Index.html里有两个<base>标签:一个是<base href="/" />,另一个是空的<base />。重复的base标签会干扰Blazor路由解析,直接删除空的那个标签即可。配置服务器路由Fallback
Blazor WASM需要服务器将所有非文件/目录请求转发到index.html,否则刷新或直接访问深层路由会返回404,触发NotFound页面:- IIS环境:确保
web.config包含重写规则<system.webServer> <rewrite> <rules> <rule name="Serve Blazor WASM" stopProcessing="true"> <match url=".*" /> <conditions logicalGrouping="MatchAll"> <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="true" /> <add input="{REQUEST_FILENAME}" matchType="IsDirectory" negate="true" /> </conditions> <action type="Rewrite" url="index.html" /> </rule> </rules> </rewrite> </system.webServer> - Azure静态Web Apps:在
staticwebapp.config.json中配置fallback{ "routes": [ { "route": "/*", "serve": "/index.html", "statusCode": 200 } ] }
- IIS环境:确保
修复裁剪模式导致的组件丢失
项目启用了PublishTrimmed和TrimMode=link,.NET 8的链接裁剪可能会误删未被直接引用的路由组件:- 临时关闭
PublishTrimmed重新部署验证问题 - 若需保留裁剪,在Program.cs中显式引用所有路由页面:
// 显式引用防止被裁剪 _ = typeof(SupplierProductRequest.Admin.Pages.Home); _ = typeof(SupplierProductRequest.Admin.Pages.YourOtherPage);
- 临时关闭
移除冲突的RenderMode配置
Index.html中的<Routes @rendermode="InteractiveServer" />和注释的WebAssembly根组件冲突,直接删除这个<Routes>标签,确保根组件由Program.cs中的builder.RootComponents.Add<App>("#app");加载。检查生产环境认证配置
确认生产环境appsettings.json中的Azure AD回调URL与生产域名匹配,打开浏览器控制台查看是否有认证相关的错误日志,认证异常可能间接导致路由跳转异常。
内容的提问来源于stack exchange,提问作者Thompho Setjie

