目录
介绍
使用新框架构建绿色现场非常棒,但我们并非所有人都得到如此奢侈。如果那些拥有经典Razor / Server Page ASPNetCore网站,想要迁移到大爆炸不可行的地方的人呢?您希望以经典模式运行您的大多数站点,并以一个大小的块迁移各个部分。也许首先要在小部分上进行试点。
本文向您展示了如何做到这一点。我将描述Blazor服务器、WASM和Razor站点/应用程序如何交互,以及如何在单个AspNetCore网站上一起运行它们。
本文的前半部分探讨了技术挑战,深入研究了Blazor的一些关键部分是如何工作的。在下半部分,我们将使用现成的项目模板来研究一个简单的实际部署。
代码库
Hydra是此处讨论的概念的实现,并且该代码将在讨论中广泛使用。
该代码位于两个存储库中:
- CEC.Blazor.Examples是主要存储库。它包含包括该文章在内的几篇文章的代码,并且是Azure上的演示站点的源代码repo。它使用本文讨论的方法在一个网站上托管多个WASM和Server SPA。Mongrel是本文的特定站点。
- Hydra包含本文后半部分的代码。演示站点上运行一个Hydra版本。
误解
Blazor Server和Blazor WASM是APPLICATIONS:它们不是网站。在本文中,我将它们称为SPA [Single Page Applications]。Razor网站是唯一的常规网站。
Blazor SPA中唯一真正的网页是启动页面。一旦启动,它就是一个应用程序。它不发布帖子,也不获取页面。
Blazor页面不是网页,而是组成部分。
Web服务器
查看代码存储库和测试站点,您将看到一个项目调用Hydra.Web。这是宿主网站。这是一个现成的AspNetCore Pages模板网站。
让我们看一下Startup.cs。
AspNetCore具有由IServiceCollection定义的“控制/依赖项注入服务反转”容器。如果不确定IOC/DI容器是什么,请进行一些背景阅读。我们在ConfigureServices中配置它。
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddControllersWithViews();
services.AddServerSideBlazor();
services.AddServerSideHttpClient();
services.AddSingleton<IWeatherForecastService, WeatherForecastService>();
services.AddCECRouting();
}
您将看到直接配置的一组服务——例如WeatherForecastService和调用如AddServerSideBlazor和AddCECRouting的ServiceCollectionExtensions集合。
为使这些信息神秘化,下面显示了AddCECRouting的代码:
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddCECRouting(this IServiceCollection services)
{
services.AddScoped<RouterSessionService>();
return services;
}
}
ServiceCollectionExtensions提供一种在一个屋檐下针对特定功能配置所有服务的方法。它们是IServiceCollection的扩展方法。AddServerSideBlazor只需添加Blazor Server的所有必要服务,例如NavigationManager和IJSRuntime。
如果要查看ServiceCollectionExtension实现,请在CEC.Blazor.Hydra.Web/Extensions中定义AddServerSideHttpClient。
Configure设置由Web服务器运行的中间件。我们将其分为几部分。
第一部分是ASPNetCore Web服务器标准代码。
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
// 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.UseStaticFiles();
接下来,我们为每个WASM SPA定义一个IApplicationBuilder.MapWhen。我在这里显示为红色的。这些定义了针对特定站点段运行的中间件——由网址定义。这是针对红色WASM。注意:
- 我们使用特定的网址细分进行UseBlazorFrameworkFiles配置——我们将在下面的WASM部分中了解其重要性。框架文件路径wwwroot/red/_framework/将是唯一的,从而为该WASM SPA提供了唯一的路径。
- 该段的后备页面是/_Red.cshtml。
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/red"), app1 =>
{
app1.UseBlazorFrameworkFiles("/red");
app1.UseRouting();
app1.UseEndpoints(endpoints =>
{
endpoints.MapFallbackToPage("/red/{*path:nonfile}", "/_Red");
});
});
最后,我们定义默认的细分中间件。注意:
- 我们定义MapBlazorHub将所有SignalR流量映射到Blazor Hub。
- 我们为每个Blazor Server SPA定义了一组特定于段的回退页面,并为SPA提供了特定的startup页面。
app.UseRouting();
app.UseBlazorFrameworkFiles();
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapRazorPages();
endpoints.MapFallbackToPage("/examplesserver/{*path:nonfile}", "/_ExamplesServer");
endpoints.MapFallbackToPage("/grey/{*path:nonfile}", "/_Grey");
endpoints.MapFallbackToPage("/blue/{*path:nonfile}", "/_Blue");
endpoints.MapFallbackToPage("/routing/{*path:nonfile}", "/_Routing");
endpoints.MapFallbackToPage("/Index");
});
}
Blazor WASM
每个WASM SPA必须有一个单独的项目。<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">将项目声明为WASM SPA,并指示编译器将项目构建为WASM SPA。在Program中定义了一个RootComponent。
public class Program
{
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<WASMRedApp>("#app");
builder.Services.AddScoped(sp => new HttpClient
{ BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
await builder.Build().RunAsync();
}
}
您无需坚持app。此声明定义了与App的不同类。它说的是在id为app的HTML标签中渲染TComponent类型的组件。不要被RootComponents和Add误导。声明一个。您可以声明更多,但是它们必须全部存在于呈现的页面上,因此这毫无意义:布局以更大的灵活性实现了相同的结果。
多重SPA托管的关键是在项目定义文件中指定唯一的StaticWebAssetBasePath。
<PropertyGroup>
<StaticWebAssetBasePath>red</StaticWebAssetBasePath>
</PropertyGroup>
记住,我们在Startup.cs的中间件映射中使用过UseBlazorFrameworkFiles("/red")。这是事物联系在一起的地方。这是区分大小写的!
上面的项目视图显示了项目中的所有文件。
- 布局和导航对于所有Mongrel项目都是通用的,因此已移至共享库。
- App.razor已移至“组件”中,并已重命名以使其具有唯一性。我不喜欢到处都是Apps。
- wwwroot已经消失了,因为启动index.html和CSS都移到了Hydra。
编译后,bin如下所示:
请注意_framework目录,其中包含blazor.webaseembly.js和blazor.boot.json。当Hydra引用此项目时,该目录将以red/_framework形式提供。如果没有唯一的StaticWebAssetBasePath,则所有SPA都将映射到_framework。
SPA的“Startup”页面在Hydra._Red.cshtml上。
@page "/spared"
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = null;
}
<!DOCTYPE html<span class="pl-kos">></span>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=no" />
<title>Mongrel.WASM.RED</title>
<base href="/red/" />
<link href="/css/site.css" rel="stylesheet" />
<link href="/CEC.Blazor.Hydra.styles.css" rel="stylesheet" />
</head>
<body>
<div id="app">
<div class="mt-4" style="margin-right:auto; margin-left:auto; width:100%;">
<div class="loader"></div>
<div style="width:100%; text-align:center;"><h4>Web Application Loading</h4></div>
</div>
</div>
<script src="/red/_framework/blazor.webassembly.js"></script>
</body>
</html>
注意:
- 将base设置为"/red/"。重要提示——您需要在斜杠的前面和后面加上斜线。
- site.css是一个SASS构建的CSS文件。
- CEC.Blazor.Hydra.styles.css是SASS构建的CSS系统,其中包含所有参考项目组件样式。
- 我们定义特定的webassembly脚本src="/red/_framework/blazor.webassembly.js"。它在{base}/_ framework/blazor.boot.json中寻找其配置文件,其中{base}在<base href="">中定义。./blazor.boot.json是用于加载WASM可执行文件的配置文件。重要的是要认识到,base需要有一个尾随的“/”才能起作用。
拼图的最后一部分是Hydra的Startup.cs中的映射。中间件配置有正确的BlazorFrameworkFiles,并且对/red/的任何获取请求都定向到/_Red.cshtml。
SPA启动后,SPA将路由到应用程序中已知Urls的所有导航。Http获取的所有外部Urls。在Hydra和Urls中,将/red/*定向到/_Red.cshtml。因此,/red/counter将在计数器页面上启动SPA,而/red/nopage将加载SPA,但是您将看到“此地址无内容”消息。
您需要对路由“/ ”稍加注意。我更喜欢使用路由“/index”设置SPA主页,并始终以这种方式引用它。
Blazor服务器
Blazor Server SPA的配置略有不同。我们不需要带有单个入口点的Program.cs。我们将启动页面配置为加载实现IComponent的任何类,因此我们的入口点可以来自任何地方。对于代码和路由管理,请为每个SPA定义一个Razor Library项目:将所有内容分隔开。
我们已经看到了主站点Startup.cs的配置,我在上面的讨论中加入了Blazor位。它:
- 通过调用ConfigureServices中的services.AddServerSideBlazor()来添加特定的Blazor服务。
- 通过调用Configure中的endpoints.MapBlazorHub()来添加Blazor Hub中间件。
这里要了解的关键机器人是所有SPA都有一个Blazor集线器。为ConfigureServices中的每个SPA加载所有服务。这确实对可以一起运行和不能一起运行的内容设置了一些限制和控制,但是在大多数情况下,这不会成为问题。切记,仅在需要时才加载服务,并在处置它们时对其进行清理。确保正确获得服务范围。
每个Server SPA都有一个端点。您不需要MapWhen使用它们进行配置,因为它们都使用相同的中间件序列。它们都在默认的UseEndpoints中定义。您已经在Startup.cs中看到了它们。
Server SPA的客户端再次被定义为单个页面。与WASM页面不同,WASM页面是包装在Razor页面中的静态HTML,而Server SPA页面是Razor页面。它具有一个Component入口点,该入口点由TagHelper读取并由服务器处理。Razor对页面所做的操作由rendermode决定。
@page "/spagrey"
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = null;
}
<!DOCTYPE html<span class="pl-kos">></span>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0,
maximum-scale=1.0, user-scalable=no" />
<title>Mongrel.Server.GREY</title>
<base href="/grey" />
<link href="/css/site.css" rel="stylesheet" />
<link href="/CEC.Blazor.Hydra.styles.css" rel="stylesheet" />
</head>
<body>
<component type="typeof(Mongrel.Server.Grey.Components.ServerGreyApp)"
render-mode="ServerPrerendered" />
<script src="_framework/blazor.server.js"></script>
</body>
</html>
浏览器收到服务器产生的内容后,将呈现所提供的初始静态DOM,然后调用已加载的JavaScript。这是魔术开始的地方。blazor.server.js加载,读取页面中合并的配置数据,并通过SignalR与BlazorHub建立会话,并通过<component>请求在页面上定义的根IComponent——通常App由App.Razor定义。在服务器上配置的Blazor中间件通过endpoints.MapBlazorHub()接收请求并建立会话。Blazor Hub呈现了请求的IComponent组件树,并将更改通过SignalR传递回客户端:初始重新渲染时,这就是整个DOM。SPA现在已上线。事件发生在页面上,并传递回中心会话。它处理事件,并将所有DOM更改传递回客户端。到已知路由的导航事件由SPA路由器处理:SPA会话持续存在。已知路由之外的URL导致SPA路由器为URL提交完整的http get:SPA会话结束。我在浏览器中查看“页面”选项卡,以查看请求是否已路由或导致页面刷新。
那么SignalR会话中发生了什么。该网站的AspNetCore编译代码库包含所有Blazor组件代码——它们只是标准类。Service容器运行服务,再次运行已编译代码库中的标准类。signalR会话的核心是特定SPA会话的渲染器。它从ComponentTree构建和重建DOM 。将Blazor Server SPA视为两个实体:一个在服务器上的Blazor Hub中运行,完成大部分工作;另一个在服务器上运行。客户端上的其他内容,则拦截事件并将其传递回服务器,然后使用返回的DOM更改重新呈现页面。事件客户端到服务器,DOM更改服务器到客户端。
建立Hydra网站
我尝试将这些网站以开箱即用的模板为基础,以使其尽可能简单。我没有尝试将代码合并到共享库中。
如下构建启动解决方案(所有解决方案均基于标准VS 2019模板构建):
- Hydra.Web——Razor MVC项目。这是启动项目。
- Hydra.Grey——Razor库项目
- Hydra.Blue——另一个Razor库项目
- Hydra.Steel——Blazor WASM项目(独立——未托管NetCore)
- Hydra.Red——Blazor WASM项目(独立——未托管NetCore)
设置Hydra.Web为启动项目。如果您与我一起工作,请运行该项目以检查Razor项目的编译和运行情况。
清除两个库
从两个库中清除以下文件:
- wwwroot和内容
- Component1.razor
- ExampleJsInterop.cs
从WASM项目开始。
Hydra.Red
更新项目文件。注意StaticWebAssetBasePath设置为red。
<Project Sdk=<span class="pl-pds">"Microsoft.NET.Sdk.BlazorWebAssembly"</span>>
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<StaticWebAssetBasePath>red</StaticWebAssetBasePath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer"
Version="5.0.3" PrivateAssets="all" />
<PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
</ItemGroup>
</Project>
通过在链接顶部添加以下链接进行NavMenu.razor更新:
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="/Hydra" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Hydra
</NavLink>
</li>
......
通过更改.sidebar背景更新MainLayout.razor.css。
.sidebar {
background-image: linear-gradient(180deg, #400 0%, #800 70%);
}
Hydra.Steel
更新项目文件。注意添加StaticWebAssetBasePath设置为steel。
<Project Sdk="Microsoft.NET.Sdk.BlazorWebAssembly">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<StaticWebAssetBasePath>steel</StaticWebAssetBasePath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="5.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer"
Version="5.0.3" PrivateAssets="all"/>
<PackageReference Include="System.Net.Http.Json" Version="5.0.0" />
</ItemGroup>
</Project>
通过在链接顶部添加以下链接进行NavMenu.razor更新。
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="/Hydra" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Hydra
</NavLink>
</li>
......
更新sidebar中的MainLayout。
<div class="sidebar sidebar-steel">
<NavMenu />
</div>
// Hydra.Steel/Pages/FetchData.razor
....
protected override async Task OnInitializedAsync()
{
// Note /sample-data
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>
("/sample-data/weather.json");
}
....
我们将在此SPA的Hydra.Web中使用CSS。
删除:
- 该wwwroot的文件夹结构
- /Shared/MainLayout.razor.css
- /Shared/NavMenu.razor.css
Hydra.Web
更新项目文件。我们添加了所有项目以及对Blazor WASM Server库的软件包引用。
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server"
Version="5.0.3"/>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Hydra.Blue\Hydra.Blue.csproj" />
<ProjectReference Include="..\Hydra.Red\Hydra.Red.csproj" />
<ProjectReference Include="..\Hydra.Steel\Hydra.Steel.csproj" />
<ProjectReference Include="..\Razor.Grey\Hydra.Grey.csproj" />
</ItemGroup>
</Project>
从仓库中拉出以下带有文件的文件夹:
- wwwroot/css
- wwwroot/sample-data
wwwroot/css/app.css是自定义构建的BootStrap发行版。
将两个Razor Pages添加到Pages文件夹中:
- _Blue.cshtml
- _Red.cshtml
- _Steel.cshtml
- _Grey.cshtml
删除与这些文件关联的关联模型cs文件。
// Hydra.Web/Pages/_Red.cshtml
@page "/spared"
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = null;
}
<!DOCTYPE html<span class="pl-kos">></span>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Hydra.RED</title>
<base href="/red/" />
<link href="/red/css/bootstrap/bootstrap.min.css" rel="stylesheet" />
<link href="/red/css/app.css" rel="stylesheet" />
<link href="/red/Hydra.Red.styles.css" rel="stylesheet" />
</head>
<body>
<div id="app">
<div class="mt-4" style="margin-right:auto; margin-left:auto; width:100%;">
<div class="loader"></div>
<div style="width:100%; text-align:center;">
<h4>Web Application Loading</h4></div>
</div>
</div>
<script src="/red/_framework/blazor.webassembly.js"></script>
</body>
</html>
// Hydra.Web/Pages/_Steel.cshtml
@page "/spasteel"
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = null;
}
<!DOCTYPE html<span class="pl-kos">></span>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Hydra.Steel</title>
<base href="/steel/" />
<link href="/css/app.css" rel="stylesheet" />
<link href="Hydra.Web.styles.css" rel="stylesheet" />
</head>
<body>
<div id="app">
<div class="mt-4" style="margin-right:auto; margin-left:auto; width:100%;">
<div class="loader"></div>
<div style="width:100%; text-align:center;">
<h4>Web Application Loading</h4></div>
</div>
</div>
<script src="/steel/_framework/blazor.webassembly.js"></script>
</body>
</html>
// Hydra.Web/Pages/_Grey.cshtml & _Blue.cshtml_
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = null;
}
<!DOCTYPE>
<html>
<head>
</head>
<body>
Holding page
</body>
</html>
Startup.cs
我们:
- 在服务中添加对Razor页面的支持。
- 为Red和Steel SPA添加特定的Endpoint配置。
- 挂载与SPA <StaticWebAssetBasePath>关联的_framework文件
- 将所有/purple/* URL的Fallback设置为_Colour.cshtml
- 同时添加Blazor支持。
- 添加Blazor服务。
- 添加Blazor Hub。
- 添加Blazor服务器SPA的端点。
// Hydra.Web/StartUp.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddRazorPages();
services.AddServerSideBlazor();
// Server Side Blazor doesn't register HttpClient by default
// Thanks to Robin Sue - Suchiman https://github.com/Suchiman/BlazorDualMode
if (!services.Any(x => x.ServiceType == typeof(HttpClient)))
{
// Setup HttpClient for server side in a client side compatible fashion
services.AddScoped<HttpClient>(s =>
{
<span class="pl-c">// Creating the URI helper needs to wait
until the JS Runtime is initialized, so defer it.</span>
var uriHelper = s.GetRequiredService<NavigationManager>();
return new HttpClient
{
BaseAddress = new Uri(uriHelper.BaseUri)
};
});
}
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
....
app.UseStaticFiles();
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/red"), app1 =>
{
app1.UseBlazorFrameworkFiles("/red");
app1.UseRouting();
app1.UseEndpoints(endpoints =>
{
endpoints.MapFallbackToPage("/red/{*path:nonfile}", "/_Red");
});
});
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/steel"), app1 =>
{
app1.UseBlazorFrameworkFiles("/steel");
app1.UseRouting();
app1.UseEndpoints(endpoints =>
{
endpoints.MapFallbackToPage("/steel/{*path:nonfile}", "/_Steel");
});
});
app.UseRouting();
<span class="pl-c">// default EndPoint Configuration</span>
app.UseEndpoints(endpoints =>
{
endpoints.MapBlazorHub();
endpoints.MapRazorPages();
endpoints.MapFallbackToPage("/grey/{*path:nonfile}", "/_Grey");
endpoints.MapFallbackToPage("/blue/{*path:nonfile}", "/_Blue");
endpoints.MapFallbackToPage("/Index");
});
}
Index.cshtml
更新@page指令并添加一些导航按钮。
@page "/"
....
<div class="text-center">
<h1 class="display-4">Welcome</h1>
<p>Learn about <a href="https://docs.microsoft.com/aspnet/core">
building Web apps with ASP.NET Core</a>.</p>
<div class="container">
<a class="btn btn-danger" href="/red">Hydra Red</a>
<a class="btn btn-dark" href="/steel">Hydra Steel</a>
<a class="btn btn-primary" href="/blue">Hydra Blue</a>
<a class="btn btn-secondary" href="/grey">Hydra Grey</a>
</div>
</div>
现在所有这些都应该编译并运行。
Hydra.Blue
这是我们用来构建Blazor Server SPA之一的库。
项目文件
包括以下程序包和项目。
<Project Sdk="Microsoft.NET.Sdk.Razor"
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.3" />
</ItemGroup>
</Project>
文件移动和重命名
从Hydra.Red复制网页和共享文件夹。
- Pages文件夹
- Shared文件夹
- App.razor
- _Imports.razor
_Imports.razor
<Project Sdk="Microsoft.NET.Sdk.Razor"
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.3" />
</ItemGroup>
</Project>
// Hydra.Blue/Pages/Index.razor
@page "/"
@page "/blue"
@page "/blue/index"
....
// Hydra.Blue/Pages/Counter.razor
@page "/counter"
@page "/blue/counter"
....
// Hydra.Blue/Pages/FetchData.razor
@page "/fetchdata"
@page "/blue/fetchdata"
....
protected override async Task OnInitializedAsync()
{
// Note /sample-data
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>
("/sample-data/weather.json");
}
....
在MainLayout中更新侧边栏。
<div class="sidebar sidebar-blue">
<NavMenu />
</div>
更新NavMenu.razor:
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="/Hydra" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Hydra
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="blue/index" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="blue/counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="blue/fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
</ul>
</div>
更新 App.razor,改变AppAssembly,将其设置为Hydra.Blue.App,因此它将在此程序集中找到路由。
// Hydra.Blue/App.razor
<Router AppAssembly="@typeof(Hydra.Blue.App).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
Hydra.Grey
项目文件
包括以下程序包和项目。
<Project Sdk="Microsoft.NET.Sdk.Razor">
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Components.Web" Version="5.0.3" />
</ItemGroup>
</Project>
文件移动和重命名
从Hydra.Blue复制网页和共享文件夹。
- Pages文件夹
- Shared文件夹
- App.razor
- _Imports.razor
_Imports.razor
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using Hydra.Grey
@using Hydra.Grey.Shared
// Hydra.Grey/Pages/Index.razor
@page "/"
@page "/Grey"
@page "/Grey/index"
....
// Hydra.Grey/Pages/Counter.razor
@page "/Grey/counter"
....
// Hydra.Grey/Pages/FetchData.razor
@page "/fetchdata"
@page "/Grey/fetchdata"
....
protected override async Task OnInitializedAsync()
{
// Note /sample-data
forecasts = await Http.GetFromJsonAsync<WeatherForecast[]>
("/sample-data/weather.json");
}
....
Update the *sidebar* in `MainLayout`.
```html
<div class="sidebar sidebar-grey">
<NavMenu />
</div>
更新NavMenu.razor:
<div class="top-row pl-4 navbar navbar-dark">
<a class="navbar-brand" href="">Hydra.Grey</a>
<button class="navbar-toggler" @onclick="ToggleNavMenu">
<span class="navbar-toggler-icon"></span>
</button>
</div>
<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
<ul class="nav flex-column">
<li class="nav-item px-3">
<NavLink class="nav-link" href="/Hydra" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Hydra
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="grey/index" Match="NavLinkMatch.All">
<span class="oi oi-home" aria-hidden="true"></span> Home
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="grey/counter">
<span class="oi oi-plus" aria-hidden="true"></span> Counter
</NavLink>
</li>
<li class="nav-item px-3">
<NavLink class="nav-link" href="grey/fetchdata">
<span class="oi oi-list-rich" aria-hidden="true"></span> Fetch data
</NavLink>
</li>
</ul>
</div>
更新 App.razor,改变AppAssembly,将其设置为Hydra.Grey.App,因此它将在此程序集中找到路由。
// Hydra.Grey/App.razor
<Router AppAssembly="@typeof(Hydra.Grey.App).Assembly" PreferExactMatches="@true">
<Found Context="routeData">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
</Found>
<NotFound>
<LayoutView Layout="@typeof(MainLayout)">
<p>Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
Hydra.Web
// Hydra.Web/Pages/_Blue.html
@page "/spablue"
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = null;
}
<!DOCTYPE></span>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Hydra.Blue</title>
<base href="/blue" />
<link href="/css/app.css" rel="stylesheet" />
<link href="/CEC.Blazor.Hydra.styles.css" rel="stylesheet" />
</head>
<body>
<component type="typeof(Hydra.Blue.App)" render-mode="ServerPrerendered" />
<script src="_framework/blazor.server.js"></script>
</body>
</html>
// Hydra.Web/Pages/_Grey.html
@page "/spagrey"
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = null;
}
<!DOCTYPE></span>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,
initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
<title>Hydra.Grey</title>
<base href="/grey" />
<link href="/css/app.css" rel="stylesheet" />
<link href="/CEC.Blazor.Hydra.styles.css" rel="stylesheet" />
</head>
<body>
<component type="typeof(Hydra.Grey.App)" render-mode="ServerPrerendered" />
<script src="_framework/blazor.server.js"></script>
</body>
</html>
现在所有这些都应该编译并运行。
总结
本文有很多内容可供参考。在我证明了这些概念之后,花了我一段时间才将本文整理在一起。一些重要的概念要理解:
- 要编写Blazor SPA,您需要摆脱“Web”范式。考虑老式的桌面应用程序。有点复古!如果您不这样做,那么您的SPA可能有点像狗的早餐。x版与1版几乎没有相似之处。
- WASM SPA是已编译的可执行文件——就像桌面应用程序一样。启动页面是一个快捷方式。
- Server SPA是指向网站上运行的库中的类的指针。启动页面是服务器端启动它并运行的快捷方式。一旦启动,SPA便分为两个部分:一半是浏览器SPA,另一半是服务器上Blazor Hub上的会话,而SignalR会话无可避免地加入了该会话。
- 您在引用网址时需要非常小心:缺少“/”或大写字母可能会使您不知所措!遵守以下规则:所有定义Urls位的网址和内容都只使用小写字母。我就掉进去过这种坑里面!
https://www.codeproject.com/Articles/5287009/Blazor-Hydra-Hosting-Multiple-Blazor-SPAs-on-a-sin