Blazor—使用验证技术构建网络应用程序的教程

322 阅读19分钟

TL;DR本教程介绍了Blazor框架,指导你用C#构建一个简单的Web应用程序。它还将向你展示如何将你的Blazor应用程序与Auth0集成,以确保其安全性。你可以在这个GitHub资源库中找到完整的应用程序代码。

什么是Blazor?

Blazor越来越受欢迎,特别是在.NET Core 3.0发布后,它以许多有趣的功能丰富了它。随后的.NET版本巩固了它的基础,人们对它的兴趣越来越大,以至于微软在它的未来上下了很大的赌注。但Blazor究竟是什么?

Blazor是一个用.NET构建客户端网络应用的编程框架。它允许.NET开发人员使用他们的C#和Razor知识来构建在浏览器中运行的交互式用户界面。用Blazor开发客户端应用程序给.NET开发人员带来了一些好处。

  • 他们使用C#和Razor,而不是JavaScript和HTML。
  • 他们可以利用整个.NET的功能。
  • 他们可以在服务器和客户端共享代码。
  • 他们可以使用他们所习惯的.NET开发工具。

简而言之,Blazor向.NET开发人员承诺,让他们用自己熟悉的开发平台建立客户端Web应用程序。

托管模式

Blazor为您提供了两种方式来运行您的Web客户端应用程序。Blazor服务器Blazor WebAssembly。这些被称为托管模式

Blazor服务器托管模式在服务器上的ASP.NET Core应用程序中运行你的应用程序。UI被发送到浏览器,但UI更新和事件处理是在服务器端进行的。这类似于传统的Web应用程序,但客户端和服务器端之间的通信是通过SignalR连接进行的。下图让你了解了Blazor服务器托管模式的整体架构。

Blazor Server architecture diagram

Blazor服务器托管模式有一些好处,比如客户端应用程序的下载量较小,而且与最近的浏览器兼容。然而,与传统的单页应用程序(SPA)相比,它也有一些缺点,比如由于大多数用户互动需要在客户端和服务器之间进行往返,因此延迟较高,而且在高流量情况下的可扩展性也存在挑战。

Blazor WebAssembly托管模式,也被称为Blazor WASM,让你的应用程序完全在用户的浏览器上运行。应用程序的全部代码,包括其依赖性和.NET运行时,被编译成WebAssembly,由用户的浏览器下载,并在本地执行。下图描述了Blazor WebAssembly的托管模式。

Blazor WebAssembly architecture diagram

Blazor WebAssembly托管模式所提供的好处与单页应用所提供的好处相似。在下载之后,除了需要的交互之外,应用程序是独立于服务器的。另外,你不需要ASP.NET Core Web服务器来托管你的应用程序。你可以使用任何Web服务器,因为WebAssembly编译的结果只是一组静态文件。

在另一方面,你应该注意到这种托管模式的缺点。Blazor的WebAssembly托管模式要求浏览器支持WebAssembly。此外,应用程序的初始下载可能需要一些时间。

[ "你不需要ASP.NET Core Web服务器来托管Blazor WebAssembly应用程序。"(twitter.com/intent/twee…

Blazor路线图

Blazor承诺为.NET开发者提供一个巨大的机会。微软在Blazor项目上的目标非常宏大,尤其是对Blazor WebAssembly。在他们的设想中,Blazor WebAssembly不仅将成为主要的托管模式,而且还将推动客户端开发的巨大革命。

Blazor WebAssembly托管模式将包括编入WebAssembly的单页应用程序、渐进式Web应用程序、混合移动应用程序、基于电子的桌面应用程序和本地应用程序。

前提条件

在开始构建你的Blazor应用程序之前,你需要确保在你的机器上安装了正确的工具。特别是,您需要.NET 6.0 SDK或以上版本。您可以在终端窗口中输入以下命令,检查您是否安装了正确的版本。

dotnet --version

你应该得到的值是 6.0.100或以上作为结果。如果你没有,你应该下载.NET SDK并将其安装在你的机器上。

如果你要使用Visual Studio,请注意,你至少需要使用Visual Studio 2019 16.8或Visual Studio for Mac 8.9。

注意:如果你将Visual Studio更新到最新版本,你会得到所需的.NET SDK捆绑。

构建一个Blazor服务器应用程序

要开始使用Blazor,您将建立一个简单的测验应用程序,显示一个有多个答案的问题列表,并根据您提供的正确答案给您打分。你将使用Blazor服务器托管模式创建这个应用程序。如果你对使用Blazor WebAssembly来构建和保护同样的应用程序感兴趣,请查看这篇文章

因此,在终端窗口中输入以下命令,创建一个基本的Blazor服务器项目。

dotnet new blazorserver -o QuizManager

该命令使用blazorserver 模板,在QuizManager 文件夹中为你的应用程序生成项目。这个新创建的文件夹有很多内容,但除了根文件夹外,你要接触的相关文件夹是。

  • Data 文件夹:它包含模型和实现业务逻辑的服务。
  • Pages 文件夹:它包含生成HTML视图的Razor组件。特别是,这个文件夹包含了 _Host.cshtmlRazor页面,它作为Web UI的起点。
  • Shared 文件夹:它包含Razor组件和其他页面间共享的元素。

创建模型和服务

作为第一步,删除Data 文件夹内的文件。接下来,在这个文件夹中添加一个 QuizItem.cs文件,并粘贴以下代码。

// Data/QuizItem.cs

namespace QuizManager.Data
{
    public class QuizItem
    {
        public string Question { get; set; }
        public List<string> Choices { get; set; }
        public int AnswerIndex { get; set; }
        public int Score { get; set; }

        public QuizItem()
        {
            Choices = new List<string>();
        }
    }
}

这个类为测验的每个项目实现了模型。它提供了一个问题,一个可能的答案列表,正确答案的零基指数,以及用户给出正确答案时分配的分数。

在同一个Data 文件夹中,添加第二个文件,名为 QuizService.cs的文件,内容如下。

// Data/QuizService.cs

namespace QuizManager.Data
{
    public class QuizService
    {
        private static readonly List<QuizItem> Quiz;

        static QuizService()
        {
            Quiz = new List<QuizItem> {
                new QuizItem
                {
                    Question = "Which of the following is the name of a Leonardo da Vinci's masterpiece?",
                    Choices = new List<string> {"Sunflowers", "Mona Lisa", "The Kiss"},
                    AnswerIndex = 1,
                    Score = 3
                },
                new QuizItem
                {
                    Question = "Which of the following novels was written by Miguel de Cervantes?",
                    Choices = new List<string> {"The Ingenious Gentleman Don Quixote of La Mancia", "The Life of Gargantua and of Pantagruel", "One Hundred Years of Solitude"},
                    AnswerIndex = 0,
                    Score = 5
                }
            };
        }

        public Task<List<QuizItem>> GetQuizAsync()
        {
            return Task.FromResult(Quiz);
        }
    }
}

该类将测验定义为由构造函数初始化的QuizItem 实例的列表。 QuizService()构造函数初始化的列表。为了简单起见,该列表用静态变量实现,但在真实世界的情况下,它应该被持久化到数据库中。该 GetQuizAsync()方法只是返回Quiz 变量的值。

现在,移动到你项目的根目录,编辑 Program.cs文件,应用下面代码中的修改。

// Program.cs

using Microsoft.AspNetCore.Components;
// ...other using clauses...

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddRazorPages();
builder.Services.AddServerSideBlazor();
// 👇 existing code
//builder.Services.AddSingleton<WeatherForecastService>();
builder.Services.AddSingleton<QuizService>();
// ☝️ new code

var app = builder.Build();

// ...existing code...

通过这一改变,你注册了你上面定义的QuizService 服务,而不是默认Blazor项目模板中的示例应用程序的服务。

创建Razor组件

现在你已经创建了应用程序的模型和服务,现在是实现用户界面的时候了。Blazor利用Razor作为模板处理器来生成动态HTML。特别是,Blazor使用Razor组件来建立应用程序的用户界面。Razor组件是独立的标记和代码单元,即使在其他项目中也可以嵌套和重复使用。它们在文件中的实现是以 .razor后缀的文件中实现。

"Razor组件是Blazor应用程序中的基本UI元素。" Tweet

Tweet This(twitter.com/intent/twee…)

为了向用户展示测验并让他们与之互动,你需要将一个特定的视图作为Razor组件来实现。因此,移动到Pages 文件夹中,将 Counter.razorFetchData.razor文件。这些文件属于默认的样本项目。然后,在同一文件夹中添加 QuizViewer.razor文件,其内容如下。

// Pages/QuizViewer.razor

 @page "/quizViewer"

 @using QuizManager.Data
 @inject QuizService QuizRepository

 <h1>Take your quiz!</h1>
 <p>Your current score is @currentScore</p>

@if (quiz == null)
{
    <p><em>Loading...</em></p>
}
else
{
    int quizIndex = 0;
    @foreach (var quizItem in quiz)
    {
        <section>
            <h3>@quizItem.Question</h3>
            <div class="form-check">
            @{
                int choiceIndex = 0;
                quizScores.Add(0);
            }
            @foreach (var choice in quizItem.Choices)
            {
                int currentQuizIndex = quizIndex;
                <input class="form-check-input" type="radio" name="@quizIndex" value="@choiceIndex" @onchange="@((eventArgs) => UpdateScore(Convert.ToInt32(eventArgs.Value), currentQuizIndex))"/>@choice<br>

                choiceIndex++;
            }
            </div>
        </section>

        quizIndex++;
    }
}

@code {
    List<QuizItem> quiz;
    List<int> quizScores = new List<int>();
    int currentScore = 0;

    protected override async Task OnInitializedAsync()
    {
        quiz = await QuizRepository.GetQuizAsync();
    }

    void UpdateScore(int chosenAnswerIndex, int quizIndex)
    {
        var quizItem = quiz[quizIndex];

        if (chosenAnswerIndex == quizItem.AnswerIndex)
        {
            quizScores[quizIndex] = quizItem.Score;
        } else
        {
            quizScores[quizIndex] = 0;
        }
        currentScore = quizScores.Sum();
    }
}

看一下这个组件的代码。它的第一行使用@page 指令将这个组件定义为一个页面,这是一个UI元素,可以通过Blazor的路由系统中的一个地址(在本例中)直接到达。/quizViewer在本例中)直接到达的UI元素。然后,你有一个@using 指令,它提供了对你定义 的命名空间的访问。 QuizManager.Data命名空间,在那里你定义了QuizItem 模型和QuizService 服务。@inject 指令要求依赖性注入系统获得一个映射到QuizRepository 变量的QuizService 类的实例。

在这些初始化之后,你会发现定义了用户界面的标记。正如你所看到的,这部分是HTML和C#代码的混合体,其目的是建立问题列表,并以单选按钮表示各自可能的答案。

该组件的最后一个块被包含在@code 指令中。这是你放置组件的逻辑的地方。在QuizViewer 组件的例子中,你有 OnInitializedAsync()UpdateScore()方法。第一个方法在组件被初始化时被调用,它基本上是通过调用 GetQuizAsync()``QuizRepository 的方法来获取测验数据。第二个方法 UpdateScore()当用户点击其中一个建议的答案时,该方法被调用,并根据用户选择的答案更新分配的分数列表。在同一方法中,当前分数的值被计算出来并分配给currentScore 。这个变量的值显示在问题列表的上方,你可以在标记中看到。

现在,去应用最后的修饰,移动到Shared 文件夹中,用以下代码替换掉 NavMenu.razor文件的内容,替换为以下代码。

// Shared/NavMenu.razor

<div class="top-row ps-3 navbar navbar-dark">
    <div class="container-fluid">
        <a class="navbar-brand" href="">QuizManager</a>
        <button title="Navigation menu" class="navbar-toggler" @onclick="ToggleNavMenu">
            <span class="navbar-toggler-icon"></span>
        </button>
    </div>
</div>

<div class="@NavMenuCssClass" @onclick="ToggleNavMenu">
    <nav class="flex-column">
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="" Match="NavLinkMatch.All">
                <span class="oi oi-home" aria-hidden="true"></span> Home
            </NavLink>
        </div>
        <div class="nav-item px-3">
            <NavLink class="nav-link" href="quizViewer">
                <span class="oi oi-list-rich" aria-hidden="true"></span> Quiz
            </NavLink>
        </div>
    </nav>
</div>

@code {
    private bool collapseNavMenu = true;

    private string? NavMenuCssClass => collapseNavMenu ? "collapse" : null;

    private void ToggleNavMenu()
    {
        collapseNavMenu = !collapseNavMenu;
    }
}

NavMenu.razor文件包含了应用程序的导航栏组件的定义。你放其这个文件的代码定义了一个由两个项目组成的导航菜单:一个指向主页,另一个指向QuizViewer 组件。

你不打算改变项目根目录下的App组件所实现的 App.razor文件实现的,但还是值得一看。

// App.razor

<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组件将Blazor路由系统连接到你的应用程序。它可以在你的应用程序的页面中进行导航,区分找到的页面和不存在的页面。关于Blazor路由系统的更多信息,请查看官方文档

⚠️如果你使用的是Mac,请阅读!⚠️

在撰写本文时,Mac用户在通过.NET CLI运行ASP.NET Core应用程序时受到了一个问题的影响。在运行.NET CLI时,你可能会得到以下对话窗口。

Keychain message issue in Mac

这是由于MacOS上的.NET CLI的一个已知问题。目前的解决方法是,你打开 QuizManager.csproj文件并添加 <UseAppHost>false</UseAppHost>元素,如下图所示。

<!-- QuizManager.csproj -->

<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
  <TargetFramework>net6.0</TargetFramework>
  <Nullable>enable</Nullable>
  <ImplicitUsings>enable</ImplicitUsings>
  <!--👇 new element --->
  <UseAppHost>false</UseAppHost>
</PropertyGroup>

</Project>

如果你使用Visual Studio,你不会受到这个问题的影响。

运行你的Blazor服务器应用程序

现在你有了你的测验应用程序,所以在终端窗口中输入以下命令来启动它。

dotnet run

如果这是你第一次运行ASP.NET Core应用程序,你应该信任.NET Core SDK中包含的HTTPS开发证书。这项任务取决于你的操作系统。请看一下官方文件,以应用正确的程序。

你也可能被要求允许应用程序访问开发者证书密钥。

几秒钟后,你应该让你的应用程序启动和运行。看一下你的终端窗口,得到你的应用程序正在监听的地址。在我的例子中,我得到的地址是 https://localhost:7290,我将在整个文章中提到它。

从.NET 6.0开始,任何通过模板创建的ASP.NET项目都会为HTTP分配一个5000至5300之间的随机端口,为HTTPS分配7000至7300之间的随机端口。更多信息请参见本文档

因此,如果你用这个地址打开浏览器,你应该可以进入主页,如下图所示。

Blazor application default home page

选择导航菜单中的 "测验"项目,你应该得到你到目前为止建立的互动测验。它看起来应该如下图所示。

Quiz view in the Blazor application

如果你打开浏览器的开发工具,点击网络标签,然后刷新,你会发现应用程序的客户端和服务器端之间的通信并没有使用HTTP,而是由SignalR管理的双向二进制通信。下图显示了Chrome开发工具中的WebSocket通道。

Blazor server communication based on SignalR

用Auth0保证应用程序的安全

现在你已经有了一个作为Blazor服务器应用程序实现的工作测验Web应用程序。为了确保该应用的安全,您将学习如何将其与Auth0服务集成。

创建Auth0应用程序

确保Blazor服务器应用程序安全的第一步是访问Auth0仪表板,注册你的Auth0应用程序。如果你没有Auth0账户,你可以现在就注册一个免费账户。

免费试用最强大的认证平台。开始吧→

进入仪表板后,移动到应用程序部分,并遵循以下步骤。

  1. 单击 "创建应用程序"。
  2. 为您的应用程序提供一个友好的名称(例如,Quiz Blazor服务器应用程序),并选择常规Web应用程序作为应用程序类型。
  3. 最后,单击 "创建"按钮。

这些步骤使Auth0知道你的Blazor应用程序,并将允许你控制访问。

在应用程序被注册后,移动到 "设置"选项卡,注意你的Auth0域和客户ID。然后,在同一表格中,将该值分配给 https://localhost:<YOUR_PORT_NUMBER>/callback允许的回调URLs字段,以及 https://localhost:<YOUR_PORT_NUMBER>/允许注销的URLs字段。将 <YOUR_PORT_NUMBER>占位符替换为分配给你的应用程序的实际端口号。在我的例子中,这些值是 https://localhost:7290/callbackhttps://localhost:7290/.

第一个值告诉Auth0在用户认证后要回调哪个URL。第二个值告诉Auth0,用户在注销后应被重定向到哪个URL。

点击 "保存更改"按钮来应用它们。

配置你的Blazor应用程序

打开Blazor服务器项目根目录下的 appsettings.json文件,并将其内容替换为以下内容。

// appsettings.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "Auth0": {
    "Domain": "YOUR_AUTH0_DOMAIN",
    "ClientId": "YOUR_CLIENT_ID"
  }
}

替换占位符 YOUR_AUTH0_DOMAINYOUR_CLIENT_ID替换为从Auth0仪表板上获取的相应数值。

与Auth0集成

现在,在终端窗口中运行以下命令,安装Auth0 ASP.NET Core Authentication SDK

dotnet add package Auth0.AspNetCore.Authentication

Auth0 ASP.NET Core SDK可以让您轻松地将基于OpenID Connect的认证整合到您的应用程序中,而无需处理所有低级别的细节。

如果你想了解更多,这篇博文为你提供了关于Auth0 ASP.NET Core SDK的概述

安装完成后,打开 Program.cs文件,并修改其内容如下。

// Program.cs

using QuizManager.Data;
using Auth0.AspNetCore.Authentication; // 👈 new code

var builder = WebApplication.CreateBuilder(args);

// 👇 new code
builder.Services
    .AddAuth0WebAppAuthentication(options => {
      options.Domain = builder.Configuration["Auth0:Domain"];
      options.ClientId = builder.Configuration["Auth0:ClientId"];
    });
// 👆 new code

// Add services to the container.
builder.Services.AddRazorPages();

// ...existing code...

app.UseRouting();

app.UseAuthentication(); // 👈 new code
app.UseAuthorization();  // 👈 new code

app.MapBlazorHub();
app.MapFallbackToPage("/_Host");

app.Run();

在突出显示的代码之后,你在文件的开头添加了一个对 Auth0.AspNetCore.Authentication命名空间的引用,在文件的开头。然后你调用了 AddAuth0WebAppAuthentication()方法,并将Auth0域和客户端ID作为参数。这些Auth0配置参数取自你之前准备的 appsetting.json配置文件。最后,你调用了 UseAuthentication()UseAuthorization()方法来启用认证和授权中间件。

现在你的应用程序已经有了支持通过Auth0认证的基础设施。

保证服务器端的安全

为了防止未经授权的用户访问你的应用程序的服务器端功能,你需要保护它们。因此,打开 Index.razor``Pages 文件,添加Authorize 属性,如下图所示。

// Pages/Index.razor

@page "/"
@attribute [Authorize]

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

将同样的属性添加到 QuizViewer.razor组件也添加同样的属性。

// Pages/QuizViewer.razor

@page "/quizViewer"
@attribute [Authorize]

 @using QuizManager.Data
 @inject QuizService QuizRepository

// ...existing code...

这可以确保你的页面的服务器端渲染只由授权用户触发。

创建登录和注销端点

如前所述,在Blazor服务器托管模式中,客户端和服务器端之间的通信不是通过HTTP进行的,而是通过SignalR。由于Auth0使用了像OpenIDOAuth这样依赖HTTP的标准协议,所以你需要提供一种方法,将这些协议带到Blazor上。

为了解决这个问题,你要创建两个端点。 /login/logout,将登录和注销的请求重定向到Auth0。两个标准的Razor页面在这些端点后面做出响应。

因此,通过在终端窗口中输入以下命令,将Loginrazor页面添加到项目中。

dotnet new page --name Login --namespace QuizManager.Pages --output Pages

这个命令在Pages 文件夹中创建了两个文件。 Login.cshtmlLogin.cshtml.cs.

打开 Login.cshtml.cs文件夹中的Pages ,并将其内容替换为以下内容。

// Pages/Login.cshtml.cs

using Microsoft.AspNetCore.Authentication;
using Auth0.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc.RazorPages;

namespace QuizManager.Pages
{
  public class LoginModel : PageModel
  {
    public async Task OnGet(string redirectUri)
    {
      var authenticationProperties = new LoginAuthenticationPropertiesBuilder()
          .WithRedirectUri(redirectUri)
          .Build();

      await HttpContext.ChallengeAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
    }
  }
}

这段代码创建了一套登录所需的认证属性,并通过Auth0触发了认证过程。

然后,通过输入以下命令来添加注销页面。

dotnet new page --name Logout --namespace QuizManager.Pages --output Pages

与前面的情况类似,你将在Pages 文件夹中得到两个新文件。 Logout.cshtmlLogout.cshtml.cs.

用下面的代码替换 Logout.cshtml.cs文件的内容替换为以下代码。

// Pages/Logout.cshtml.cs

using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Authentication;
using Auth0.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;

namespace QuizManager.Pages
{
  public class LogoutModel : PageModel
  {
    [Authorize]
    public async Task OnGet()
    {
      var authenticationProperties = new LogoutAuthenticationPropertiesBuilder()
           .WithRedirectUri("/")
           .Build();

      await HttpContext.SignOutAsync(Auth0Constants.AuthenticationScheme, authenticationProperties);
      await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
    }
  }
}

这段代码将关闭用户在Blazor应用程序和Auth0上的会话。

保护客户端的安全

现在,你必须保护Blazor应用程序的客户端,以便用户在登录或未登录时看到不同的内容。

打开项目根目录下的 App.razor文件,并用下面的标记替换其内容。

// App.razor

<CascadingAuthenticationState>
    <Router AppAssembly="@typeof(App).Assembly">
        <Found Context="routeData">
            <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)">
                <Authorizing>
                    <p>Determining session state, please wait...</p>
                </Authorizing>
                <NotAuthorized>
                    <h1>Sorry</h1>
                    <p>You're not authorized to reach this page. You need to log in.</p>
                </NotAuthorized>
            </AuthorizeRouteView>
            <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>
</CascadingAuthenticationState>

在这里,你使用的是AuthorizeRouteView ,只有当用户被授权时,才会显示相关的组件。在实践中,MainLayout 组件的内容将只显示给授权用户。如果用户没有被授权,他们将看到由NotAuthorized 组件包裹的内容。如果授权正在进行中,用户将看到Authorizing 组件内的内容。CascadingAuthenticationState 组件将把当前的认证状态传播给内部组件,以便它们能够一致地工作。

[ "AuthorizeRouteView组件允许你控制对Blazor应用程序的UI部分的访问。"](twitter.com/intent/twee… Tweet

在这一点上,确保你在QuizManager 文件夹中,并在终端运行dotnet run 。当用户试图访问你的应用程序时,他们将只看到未授权的信息,如下图所示。

Unauthorized view in Blazor application

所以,你需要一种方法让用户进行认证。为此创建一个Razor组件,在 文件夹中添加一个 AccessControl.razor文件,内容如下:Shared

// Shared/AccessControl.razor

<AuthorizeView>
    <Authorized>        
        <a href="logout">Log out</a>
    </Authorized>
    <NotAuthorized>
        <a href="login?redirectUri=/">Log in</a>
    </NotAuthorized>
</AuthorizeView>

这个组件使用Authorized 组件让授权用户看到注销链接,使用NotAuthorized 组件让未授权用户访问登录链接。这两个链接都指向你之前创建的端点。特别是,登录链接将主页指定为用户认证后重定向的URI。

最后一步是把这个组件放在你的Blazor应用程序的顶部栏中。因此,将该文件的内容替换为 MainLayout.razor文件的内容,改为以下内容。

// Shared/MainLayout.razor

@inherits LayoutComponentBase

<PageTitle>QuizManager</PageTitle>

<div class="page">
    <div class="sidebar">
        <NavMenu />
    </div>

    <main>
        <div class="top-row px-4">
            <AccessControl />    //👈 new markup
            <a href="https://docs.microsoft.com/aspnet/" target="_blank">About</a>
        </div>

        <article class="content px-4">
            @Body
        </article>
    </main>
</div>

正如你所看到的,唯一的区别是在 "*关于 "*链接之前增加了AccessControl 组件。

现在,你的Blazor应用程序只能被授权用户访问。当用户点击登录链接时,他们将被重定向到Auth0通用登录页面进行认证。认证完成后,他们将回到你的应用程序的主页,并能够参加测验。

访问用户资料

一旦你在你的Blazor服务器应用程序中添加了认证,你可能需要访问一些关于认证用户的信息,例如他们的名字和照片。默认情况下,Auth0 ASP.NET Core认证SDK会在认证过程中为您获取这些信息。

为了在页面上显示用户的名字和图片,请将该组件的内容改为 Index.razor组件的内容如下。

// Pages/Index.razor

@page "/"
@inject AuthenticationStateProvider AuthState
@attribute [Authorize]

<PageTitle>Quiz Manager</PageTitle>

<h1>Welcome, @Username!</h1>
You can only see this content if you're authenticated.
<br />
<img src="@Picture">

@code { 
  private string Username = "Anonymous User";
  private string Picture = "";

  protected override async Task OnInitializedAsync()
  {
    var state = await AuthState.GetAuthenticationStateAsync();

    Username = state.User.Identity.Name?? string.Empty;

    Picture = state.User.Claims
                .Where(c => c.Type.Equals("picture"))
                .Select(c => c.Value)
                .FirstOrDefault() ?? string.Empty;

    await base.OnInitializedAsync();
  }
}

在这个新版本的组件中,你注入了AuthenticationStateProvider ,它为你提供了关于当前认证状态的信息。你在代码块中通过使用其 GetAuthenticationStateAsync()方法获得实际的认证状态。由于认证状态,你可以提取当前用户的名字和照片,并将它们分配给UsernamePicture 变量。这些是你在组件的标记中用来定制这个视图的变量。

在这一点上,你可以运行你的应用程序,登录,并得到一个类似于下面的主页。

Blazor user profile

总结

本文介绍了Blazor的基本知识,这个编程框架允许您使用C#和.NET平台来构建Web客户端应用程序。你了解了两种托管模式,即Blazor服务器和Blazor WebAssembly,并使用Blazor服务器托管模式建立了一个测验管理器应用程序。

最后,你通过将Blazor Server应用程序与Auth0集成,确保了它的安全性。

文章中所构建的应用程序的完整源代码可以从这个GitHub仓库下载。

你可以通过阅读这篇文章继续学习如何构建和保护Blazor WebAssembly应用程序