本文的目的是探讨Blazor和Razor应用程序之间的区别。我们将从开箱即用的Razor ASPNetCore Web应用程序模板开始,逐步构建运行Blazor Server SPA所需的基础结构。
在本文中,我们将从标准AspNetCore Razor Web应用程序模板逐块构建Blazor Server应用程序。
本文的目的是探讨Blazor和Razor应用程序之间的区别。我们将从开箱即用的Razor ASPNetCore Web应用程序模板开始,逐步构建运行Blazor Server SPA所需的基础架构。它应该可以帮助您更快地了解Blazor,而不是简单地部署模板并使用它。
虽然我是Visual Studio的坚定拥护者,但我们在本练习中使用Visual Studio代码来更接近煤层。
先决条件- Visual Studio Code
- NET 6.0 SDK
为简单起见,所有代码和组件都在一个命名空间Blazr中。
代码库您可以在BlazrServer Github存储库中找到所有代码。
构建项目- 在Documents中创建一个Repos文件夹(如果您还没有)。
- 创建一个Repos/BlazorServer文件夹。
- 在文件夹上打开Visual Studio Code。
- Ctl + '打开终端。
我们现在准备将模板项目部署到当前文件夹中。但是哪一个?
S C:\Users\shaun\source\repos\BlazrServer > dotnet new --list
获取所有已安装模板的列表。
我们追求:
ASP.NET Core Web App webapp,razor [C#] Web/MVC/Razor Pages
要使用它:
PS > dotnet new razor
我们得到:
The template "ASP.NET Core Web App" was created successfully.
This template contains technologies from parties other than Microsoft,
see https://aka.ms/aspnetcore/6.0-third-party-notices for details.
Processing post-creation actions...
Running 'dotnet restore' on C:\Users\shaun\source\repos\BlazorServer\BlazorServer.csproj...
Determining projects to restore...
Restored C:\Users\shaun\source\repos\BlazorServer\BlazorServer.csproj (in 90 ms).
Restore succeeded.
以及部署到目录中的一组文件夹和文件。
至此,我们可以运行项目了:
PS > dotnet watch run debug
得到这个:
PS C:\Users\shaun\source\repos\BlazorServer> dotnet watch run debug
watch : Started
Building...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7280
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5280
info: Microsoft.Hostingetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Users\shaun\source\repos\BlazorServer\
和我们的网站。
要检查热重载,请更改Index.cshtml:
Welcome To my Nascient Blazor App
并保存它。我们得到:
watch : Exited
watch : File changed: C:\Users\shaun\source\repos\BlazorServer\Pages\Index.cshtml
watch : Started
Building...
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:7280
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://localhost:5280
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
Content root path: C:\Users\shaun\source\repos\BlazorServer\
并查看页面上的更改:
热重载正在工作。我们有一个正在运行的Razor Web应用程序。
为了结束本节,让我们快速浏览一下Program。
// Initialize the WebApplication Builder
var builder = WebApplication.CreateBuilder(args);
// Add services to the container
builder.Services.AddRazorPages();
// Build the App from the builder
var app = builder.Build();
// Configure the HTTP request pipeline
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
// Run the Application
app.Run();
这:
- 创建WebApplicationBuilder类的一个实例。
- 将一组服务添加到builder的ServiceCollection。这些定义了WebApplication实例的依赖注入容器可以使用的服务。
- 构建WebApplication实例。
- 添加一组中间件来处理WebApplication实例服务的Web请求管道。
- 运行配置的WebApplication实例。
添加一个Components文件夹和一个/Component/HelloBlazor.razor组件文件。
它显示一条消息和时间:时间很有用,因为我们可以很容易地看到渲染事件何时发生。
@inherits ComponentBase
@namespace Blazr
Hello Blazor at @(time.ToLongTimeString())
Todays Message is : @Message
Set Time
@code {
[Parameter] public string Message {get; set;} = string.Empty;
private DateTime time = DateTime.Now;
protected override void OnInitialized()
=> time = DateTime.Now;
private void GetTime()
=> time = DateTime.Now;
}
将Component.cshtml添加到Pages。它使用服务器端`Html.RenderComponentAsync`来呈现组件并加载Blazor服务器JavaScript代码:
@page
@{
ViewData["Title"] = "Component page";
}
Welcome To my Component Page
@(await Html.RenderComponentAsync
(RenderMode.ServerPrerendered, new { Message = "Hello there!" }))
在_layout.cshtml中,添加一个新的顶部菜单项,以便我们可以导航到新页面。
Component
您现在应该能够导航到Component并查看页面渲染。单击按钮,然后……没有任何反应。
组件已在服务器上呈现,但未配置Blazor服务。打开开发者工具,你会看到一个JS错误。
没有_framework/blazor.server.js可供下载。
配置服务器以运行Blazor服务首先,我们添加Blazor服务器端服务。更新Program。`AddServerSideBlazor` 添加所有Blazor特定服务。
// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
现在检查浏览器,您将看到两个错误。blazor.server.js现在可以下载,但它无法运行,因为服务器上没有运行Blazor Hub中间件来为SignalR请求提供服务。
这需要Blazor中间件。要在Program中配置:
app.MapRazorPages();
app.MapBlazorHub();
现在一切都运行没有错误。但是按钮点击不起作用:时间没有更新!
转到HelloBlazor.razor。请注意,VS Code无法识别@onclick。
我们需要Microsoft.AspNetCore.Components.Web。
@inherits ComponentBase
@namespace Components
@using Microsoft.AspNetCore.Components.Web // New
该按钮现在可以工作并更新时间。
我们有一个Blazor组件运行我们的Razor服务器端页面。老手的似曾相识!
构建Blazor SPA在Razor页面中运行的组件不是单页应用程序。或者是吗?
在我们构建完整版本之前——如在Blazor模板中——让我们构建一个非常简单的SPA。
将_Imports.razor文件添加到项目根目录并添加以下代码。这将为所有razor组件设置全局程序集。
@using System.Net.Http
@using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Components.Authorization
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
将Routes文件夹添加到项目并添加以下razor组件:
@namespace Blazr
Index
Hello, world!
Welcome to your new app.
/Routes/Counter.razor
@namespace Blazr
Counter
Counter
Current count: @currentCount
Click me
@code {
private int currentCount = 0;
private void IncrementCount()
=> currentCount++;
}
/Routes/Hello.razor
@namespace Blazr
添加一个Apps文件夹并添加:
/Apps/BaseApp.razor
@using Microsoft.AspNetCore.Components;
@using Microsoft.AspNetCore.Components.Rendering;
@using Microsoft.AspNetCore.Components.Web;
@namespace Blazr
Blazor Simple App
-
this.ChangeRootComponent("Index")'>Index
-
this.ChangeRootComponent("Counter")'>Counter
-
this.ChangeRootComponent("Hello")'>Hello
-
Server Home
@body
@code {
[Inject] private NavigationManager? NavManager { get; set; }
private Dictionary Routes => new Dictionary {
{"Index", typeof(Blazr.Index)},
{"Counter", typeof(Blazr.Counter)},
{"Hello", typeof(Blazr.Hello)}
};
private Type rootComponent = typeof(Blazr.Index);
private RenderFragment body => (RenderTreeBuilder builder) =>
{
builder.OpenComponent(0, rootComponent);
builder.CloseComponent();
};
public void ChangeRootComponent(string route)
{
if (Routes.ContainsKey(route))
{
rootComponent = Routes[route];
StateHasChanged();
}
}
public void GoServerIndex()
=> this.NavManager?.NavigateTo("/Index", true);
}
rootComponent是要渲染的组件Type:默认是Index.razor。NavBar调用ChangeRootComponent更改rootComponent并通过调用StateHasChanged请求组件重新渲染。
body是一个RenderFragment简单地添加rootComponent到渲染树并渲染它。在实践中,我们会检查实现IComponent的rootComponent:所有组件都必须实现IComponent。我还没有实现代码来保持简单易读。
GoHome使用NavigationManager触发完整的浏览器重新加载,从而加载默认服务器页面。
添加指向_Layout.cshtml的链接:
Blazor Simple App
添加/Pages/SimpleBlazor.cshtml:
@page
@{
Layout = null;
}
@ViewData["Title"] - BlazorServer
@(await Html.RenderComponentAsync(RenderMode.ServerPrerendered))
您现在应该能够导航到Simple App,并在顶部菜单栏链接之间导航。
我们创建了一个服务器端razor页面,该页面将Blazor组件加载为其主要内容。该组件由导航栏和子组件组成。单击导航栏中的链接只需更改子组件。在渲染器的StateHasChanged队列中排队重新渲染页面。Renderer运行渲染(实际上是代表页面的RenderFragment)并计算出旧DOM和新DOM之间的任何差异。它将差异传递给浏览器端Blazor JS代码,该代码更新浏览器显示的DOM。不涉及页面导航,只是DOM更改。
构建完整的Blazor服务器应用程序 从Repo添加文件我们需要从Blazor应用程序中添加一些文件。
添加/Routes/Shared文件夹并从Repo添加以下文件:
- MainLayout.razor
- MainLayout.razor.css
- NavMenu.razor
- NavMenu.razor.css
这些是带有命名空间集和调整了NavLink的Blazor模板文件。
将以下文件添加到wwwroot/css:
- blazor-site.css
这是重命名的Blazor模板CSS文件:我们已经有一个site.css。
应用组件添加/Apps/App.razor并添加以下代码:这是标准代码。
@namespace Blazr
Not found
Sorry, there's nothing at this address.
为了使路由工作,我们需要将router `page`属性添加到我们希望将Router其视为路由的组件。
更新以下组件,添加页面路由。一个组件可以有多个路由。
Routes/Index.razor
@page "/"
@page "/App"
......
Routes /Counter.razor
@page "/Counter"
......
Routes /Hello.razor
@page "/Hello"
......
添加/Pages/Shared/_AppLayout.cshtml。
这是Blazor Server启动页面,带有经过调整的样式表设置。
@using Microsoft.AspNetCore.Components.Web
@namespace Layouts
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@RenderBody()
An error has occurred. This application may no longer respond until reloaded.
An unhandled exception has occurred. See browser dev tools for details.
Reload
🗙
添加/Pages/App.cshtml。
这是Blazor应用程序启动页面。Blazor.App被指定为启动类,即App.razor。
@page
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
Layout = "_AppLayout";
}
更新/Routes/Shared/NavMenu.razor。
添加一个新的NavLink。
Server Home
并添加GoServerIndex“硬”导航到服务器端主页的方法。
public void GoServerIndex()
=> this.NavManager?.NavigateTo("/Index", true);
在/Pages/Shared/_Layout.cshtml中添加指向主页导航的新链接。
Blazor App
将后备端点添加到Program。所有回退都指向Blazor应用程序。
//.....
app.MapBlazorHub();
app.MapFallbackToPage("/App");
app.Run();
您现在应该能够导航到应用程序并按F5重新加载它。
部分摘要我们现在拥有一个使用路由运行的成熟Blazor Server应用程序。区分Blazor路由和浏览器导航非常重要。物理检测差异的一种方法是查看工具栏中的“刷新”按钮——前进按钮旁边的圆圈。当浏览器导航事件发生时,您可以看到它激活。
当您单击Blazor应用程序中的左侧导航菜单时,会发生路由。您可能正在单击anchor,但浏览器事件被Blazor Javascript代码拦截,并由Router组件接收。它有一个Routes/Component字典——通过在当前程序集中查找所有具有@page属性的组件来构建。它根据路由查找组件,并加载新组件。我们在Simple Blazor组件中创建了一个非常简单的版本。
将Blazor应用程序设置为默认值当前设置有一个没有@page设置的Index.cshtml页面。这被视为站点https://localhost:nnnnn/的默认页面。
如果设置了Blazor路由组件@page "/",为什么不使用它?这就是使用路由属性“Pages”调用Blazor组件会引起混淆的地方。将它们称为除页面之外的任何内容Routes:RouteComponents或RouteViews。Web服务器对这些路由一无所知。请求通过配置的中间件管道运行Program。在我们的设置中,将Pages app.MapRazorPages()中的Razor页面映射到web路由。如果它找到一个索引或默认Web文件,它就会使用它。
要了解发生了什么,请查看Program中的端点映射:
app.MapRazorPages();
app.MapBlazorHub();
app.MapFallbackToPage("/App");
app.Run();
当前的Index.cshtml被视为默认页面并MapRazorPages()返回它。
要更改我们的设置,请在Index.cshtml上设置页面属性。
@page "/index"
@model IndexModel
MapRazorPages现在将Index.cshtml映射到https://localhost:nnnnn/Index并且不再将其视为默认页面。
该请求app.MapFallbackToPage("/App")会返回App.cshtml我们的Blazor应用程序启动页面。
Blazor应用程序中的导航那么如果我们在Blazor应用程序中有一个导航到Web服务器索引的链接会发生什么?我们可以看到这一点NavMenu。
如果我们要这样编码GoServerIndex:
public void GoServerIndex()
=> this.NavManager?.NavigateTo("/Index");
Blazor将请求视为本地请求,而不是导航。路由器找不到路由匹配,因此显示“抱歉,此地址没有任何内容”。信息。尝试一下!
要“硬”导航,我们需要这样做:
public void GoServerIndex()
=> this.NavManager?.NavigateTo("/Index", true);
这会强制NavigationManager导航、重新加载页面并点击“程序”中间件管道。
概括我做了一些调整,使我的实现与开箱即用的模板不同。这些都是:
- 我已经删除FetchData了,它只会使事情复杂化。
- App NavMenu将Index指向/App而不是/。
- Index.razor添加了一个@page "/App"。
- 所有Blazor页面组件现在都在Routes中。
2和3修复了“默认页面问题”,即默认页面是服务器Razor文件,而不是Blazor应用程序。
https://www.codeproject.com/Articles/5321697/Building-a-Blazor-Server-Application-from-the-Web