从Web/Razor模板构建Blazor服务器应用程序

本文的目的是探讨BlazorRazor应用程序之间的区别。我们将从开箱即用的Razor ASPNetCore Web应用程序模板开始,逐步构建运行Blazor Server SPA所需的基础结构。

在本文中,我们将从标准AspNetCore Razor Web应用程序模板逐块构建Blazor Server应用程序。

本文的目的是探讨BlazorRazor应用程序之间的区别。我们将从开箱即用的Razor ASPNetCore Web应用程序模板开始,逐步构建运行Blazor Server SPA所需的基础架构。它应该可以帮助您更快地了解Blazor,而不是简单地部署模板并使用它。

虽然我是Visual Studio的坚定拥护者,但我们在本练习中使用Visual Studio代码来更接近煤层。

先决条件

  • Visual Studio Code
  • NET 6.0 SDK

为简单起见,所有代码和组件都在一个命名空间Blazr中。

代码库

您可以在BlazrServer Github存储库中找到所有代码。

构建项目

  1. Documents中创建一个Repos文件夹(如果您还没有)。
  2. 创建一个Repos/BlazorServer文件夹。
  3. 在文件夹上打开Visual Studio Code
  4. 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

<h1 class="display-4">Welcome To my Nascient Blazor App</h1>

并保存它。我们得到:

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();

这:

  1. 创建WebApplicationBuilder类的一个实例。
  2. 将一组服务添加到builderServiceCollection。这些定义了WebApplication实例的依赖注入容器可以使用的服务。
  3. 构建WebApplication实例。
  4. 添加一组中间件来处理WebApplication实例服务的Web请求管道。
  5. 运行配置的WebApplication实例。

Blazor组件添加到Razor页面

添加一个Components文件夹和一个/Component/HelloBlazor.razor组件文件。

它显示一条消息和时间:时间很有用,因为我们可以很容易地看到渲染事件何时发生。

@inherits ComponentBase
@namespace Blazr

<div>
    <h1>Hello Blazor at @(time.ToLongTimeString())</h1>
    <div class="m-3">
        Todays Message is : @Message
    </div>
    <div class="m-3">
        <button class="btn btn-primary" @onclick="GetTime">Set Time</button>
    </div>
</div>

@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";
}

<div class="text-center">
    <h1 class="display-4">Welcome To my Component Page</h1>
    @(await Html.RenderComponentAsync<Components.HelloBlazor>
     (RenderMode.ServerPrerendered, new { Message = "Hello there!" }))

    <script src="_framework/blazor.server.js"></script>
</div>

_layout.cshtml中,添加一个新的顶部菜单项,以便我们可以导航到新页面。

<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-page="/Component">Component</a>
</li>

您现在应该能够导航到Component并查看页面渲染。单击按钮,然后……没有任何反应。

组件已在服务器上呈现,但未配置Blazor服务。打开开发者工具<F12>,你会看到一个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

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

/Routes/Counter.razor

@namespace Blazr

<PageTitle>Counter</PageTitle>

<h1>Counter</h1>

<p role="status">Current count: @currentCount</p>

<button class="btn btn-primary" @onclick="IncrementCount">Click me</button>

@code {
    private int currentCount = 0;

    private void IncrementCount()
     =>  currentCount++;
}

/Routes/Hello.razor

@namespace Blazr

<HelloBlazor></HelloBlazor>

添加一个Apps文件夹并添加:

/Apps/BaseApp.razor

@using Microsoft.AspNetCore.Components;
@using Microsoft.AspNetCore.Components.Rendering;
@using Microsoft.AspNetCore.Components.Web;

@namespace Blazr

<nav class="navbar navbar-expand-lg navbar-light bg-light">
    <div class="container-fluid">
        <h2 class="navbar-brand">Blazor Simple App</h2>
        <div class="collapse navbar-collapse" id="navbarSupportedContent">
            <ul class="navbar-nav me-auto mb-2 mb-lg-0">
                <li class="nav-item">
                    <a class=" btn nav-link" 
                     @onclick='() => this.ChangeRootComponent("Index")'>Index</a>
                </li>
                <li class="nav-item">
                    <a class="btn nav-link" 
                     @onclick='() => this.ChangeRootComponent("Counter")'>Counter</a>
                </li>
                <li class="nav-item">
                    <a class="btn nav-link" 
                     @onclick='() => this.ChangeRootComponent("Hello")'>Hello</a>
                </li>
                <li class="nav-item">
                    <a class="btn nav-link " 
                     @onclick="this.GoServerIndex">Server Home</a>
                </li>
            </ul>
        </div>
    </div>
</nav>
<div class="ms-5">
    @body
</div>
@code {
    [Inject] private NavigationManager? NavManager { get; set; }

    private Dictionary<string, Type> Routes => new Dictionary<string, Type> {
        {"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.razorNavBar调用ChangeRootComponent更改rootComponent并通过调用StateHasChanged请求组件重新渲染。

body是一个RenderFragment简单地添加rootComponent到渲染树并渲染它。在实践中,我们会检查实现IComponentrootComponent:所有组件都必须实现IComponent。我还没有实现代码来保持简单易读。

GoHome使用NavigationManager触发完整的浏览器重新加载,从而加载默认服务器页面。

添加指向_Layout.cshtml的链接:

<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" 
    asp-page="/SimpleBlazor">Blazor Simple App</a>
</li>

添加/Pages/SimpleBlazor.cshtml

@page
@{
    Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>@ViewData["Title"] - BlazorServer</title>
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" />
    <link rel="stylesheet" href="~/BlazorServer.styles.css" asp-append-version="true" />
</head>
<body>
        @(await Html.RenderComponentAsync<Blazr.BaseApp>(RenderMode.ServerPrerendered))

    <script src="_framework/blazor.server.js"></script>
</body>
</html>

您现在应该能够导航到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

这些是带有命名空间集和调整了NavLinkBlazor模板文件。

将以下文件添加到wwwroot/css

  • blazor-site.css

这是重命名的Blazor模板CSS文件:我们已经有一个site.css

应用组件

添加/Apps/App.razor并添加以下代码:这是标准代码。

@namespace Blazr
<Router AppAssembly="@typeof(App).Assembly">
    <Found Context="routeData">
        <RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)" />
        <FocusOnNavigate RouteData="@routeData" Selector="h1" />
    </Found>
    <NotFound>
        <PageTitle>Not found</PageTitle>
        <LayoutView Layout="@typeof(MainLayout)">
            <p role="alert">Sorry, there's nothing at this address.</p>
        </LayoutView>
    </NotFound>
</Router>

路由

为了使路由工作,我们需要将router `page`属性添加到我们希望将Router其视为路由的组件。

更新以下组件,添加页面路由。一个组件可以有多个路由。

Routes/Index.razor

@page "/"
@page "/App"
......

Routes /Counter.razor

@page "/Counter"
......

Routes /Hello.razor

@page "/Hello"
......

Razor服务器端页面

添加/Pages/Shared/_AppLayout.cshtml

这是Blazor Server启动页面,带有经过调整的样式表设置。

@using Microsoft.AspNetCore.Components.Web
@namespace Layouts
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

<!DOCTYPE html<span class="pl-kos">>
<html lang="en">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <base href="~/" />
    <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" />
    <link href="css/blazor-site.css" rel="stylesheet" />
    <link href="BlazorServer.styles.css" rel="stylesheet" />
    <component type="typeof(HeadOutlet)" render-mode="ServerPrerendered" />
</head>
<body>
    @RenderBody()

    <div id="blazor-error-ui">
        <environment include="Staging,Production">
            An error has occurred. This application may no longer respond until reloaded.
        </environment>
        <environment include="Development">
            An unhandled exception has occurred. See browser dev tools for details.
        </environment>
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>

    <script src="_framework/blazor.server.js"></script>
</body>
</html>

添加/Pages/App.cshtml

这是Blazor应用程序启动页面。Blazor.App被指定为启动类,即App.razor

@page 
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@{
    Layout = "_AppLayout";
}

<component type="typeof(Blazr.App)" render-mode="ServerPrerendered" />

导航更改

更新/Routes/Shared/NavMenu.razor

添加一个新的NavLink

<div class="nav-item px-3">
    <NavLink class="btn nav-link" @onclick="GoServerIndex">
        <span class="oi oi-list-rich" aria-hidden="true"> Server Home
    </NavLink>
</div>

并添加GoServerIndex导航到服务器端主页的方法。

public void GoServerIndex()
    => this.NavManager?.NavigateTo("/Index", true);

网页更改

/Pages/Shared/_Layout.cshtml中添加指向主页导航的新链接。

<li class="nav-item">
    <a class="nav-link text-dark" asp-area="" asp-page="/App">Blazor App</a>
</li>

将后备端点添加到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组件会引起混淆的地方。将它们称为除页面之外的任何内容RoutesRouteComponentsRouteViewsWeb服务器对这些路由一无所知。请求通过配置的中间件管道运行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导航、重新加载页面并点击程序中间件管道。

概括

我做了一些调整,使我的实现与开箱即用的模板不同。这些都是:

  1. 我已经删除FetchData了,它只会使事情复杂化。
  2. App NavMenuIndex指向/App而不是/
  3. Index.razor添加了一个@page "/App"
  4. 所有Blazor页面组件现在都在Routes中。

23修复了默认页面问题,即默认页面是服务器Razor文件,而不是Blazor应用程序。

https://www.codeproject.com/Articles/5321697/Building-a-Blazor-Server-Application-from-the-Web

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值