TL; DR: Это руководство знакомит с фреймворком Blazor, помогая вам создать простое веб-приложение на C #. Он также покажет вам, как интегрировать ваше приложение Blazor с Auth0, чтобы защитить его. Следуя шагам, описанным в этом руководстве, вы создадите две версии простого веб-приложения. Вы можете найти полный код в этом репозитории GitHub.

Что такое Blazor?

Blazor в последнее время набирает популярность, особенно после выпуска .NET Core 3.0, который обогатил его множеством интересных функций. К нему есть большой интерес, и Microsoft делает большие ставки на его будущее. Но что такое Blazor?

Blazor - это среда программирования для создания клиентских веб-приложений с .NET. Это позволяет разработчикам .NET использовать свои знания C # и Razor для создания интерактивных пользовательских интерфейсов, работающих в браузере. Разработка клиентских приложений с помощью Blazor дает разработчикам .NET несколько преимуществ:

  • Они используют C # и Razor вместо JavaScript и HTML.
  • Они могут использовать все функции .NET.
  • Они могут делиться кодом между сервером и клиентом
  • Они могут использовать инструменты разработки .NET, к которым они привыкли.

Вкратце, Blazor обещает .NET-разработчикам позволить им создавать клиентские веб-приложения на удобной для них платформе разработки.

Модели хостинга

Blazor предоставляет два способа запуска клиентского веб-приложения: Blazor Server и Blazor WebAssembly. Это так называемые модели хостинга.

Модель размещения Blazor Server запускает ваше приложение на сервере в приложении ASP.NET Core. Пользовательский интерфейс отправляется в браузер, но обновления пользовательского интерфейса и обработка событий выполняются на стороне сервера. Это похоже на традиционные веб-приложения, но связь между стороной клиента и стороной сервера происходит через соединение SignalR. Следующее изображение дает вам представление об общей архитектуре модели хостинга Blazor Server:

Модель хостинга Blazor Server предоставляет несколько преимуществ, таких как меньший размер загружаемого клиентского приложения и совместимость с недавно выпущенными браузерами. Однако у него есть некоторые недостатки, такие как более высокая задержка из-за двустороннего обмена между клиентом и сервером для большинства взаимодействий с пользователем и сложная масштабируемость в сценариях с высоким трафиком.

Модель размещения Blazor WebAssembly позволяет вашему приложению работать полностью в браузере пользователя. Полный код приложения, включая его зависимости и среду выполнения .NET, компилируется в WebAssembly, загружается браузером пользователя и выполняется локально. На следующем рисунке показана модель хостинга Blazor WebAssembly:

Преимущества, предоставляемые моделью хостинга Blazor WebAssembly, аналогичны преимуществам, предоставляемым одностраничными приложениями. После загрузки приложение не зависит от сервера, за исключением необходимых взаимодействий. Кроме того, вам не нужен веб-сервер ASP.NET Core для размещения вашего приложения. Вы можете использовать любой веб-сервер, поскольку результатом компиляции WebAssembly является просто набор статических файлов.

С другой стороны, вы должны знать о недостатках этой модели хостинга. Модель размещения Blazor WebAssembly требует, чтобы браузер поддерживал WebAssembly. Кроме того, первоначальная загрузка приложения может занять некоторое время.

Дорожная карта Blazor

Blazor обещает отличные возможности для разработчиков .NET. Однако имейте в виду, что весь проект все еще находится в стадии разработки. Фактически, на момент написания в .NET Core 3.0 официально поддерживается только модель хостинга Blazor Server. Модель хостинга Blazor WebAssembly доступна в предварительной версии .NET Core 3.1 только для целей тестирования, но ее общедоступность запланирована на май 2020 года.

Цели Microsoft в отношении проекта Blazor очень амбициозны, особенно в отношении Blazor WebAssembly. По их мнению, Blazor WebAssembly не только станет основной моделью хостинга, но и совершит большую революцию в развитии клиентов.

Модель хостинга Blazor WebAssembly будет включать одностраничные приложения, скомпилированные в WebAssembly, прогрессивные веб-приложения, гибридные мобильные приложения, настольные приложения на основе Electron и собственные приложения.

Предпосылки

Перед тем, как начать сборку приложения Blazor, вам необходимо убедиться, что на вашем компьютере установлены правильные инструменты. В частности, вам необходимо проверить, установили ли вы .NET Core 3.0 SDK, введя следующую команду в окне терминала:

dotnet --version

В результате должно получиться значение 3.0.100 или выше. В противном случае вам следует загрузить .NET Core 3.0 SDK и установить его на свой компьютер.

Если вы собираетесь использовать Visual Studio, имейте в виду, что вам необходимо использовать Visual Studio 2019 16.3 или Visual Studio для Mac 8.3 или выше.

Примечание. Если вы обновите Visual Studio до последней версии, вы получите пакет SDK для .NET Core 3.0.

Создание серверного приложения Blazor

Чтобы начать работу с Blazor, вы собираетесь создать простое приложение для викторин, которое отображает список вопросов с несколькими ответами и присваивает вам балл на основе предоставленных вами правильных ответов. Вы создадите это приложение, используя модель хостинга Blazor Server. Позже вы увидите, как создать такое же приложение, используя модель Blazor WebAssembly.

Итак, создайте базовый проект Blazor Server, введя следующую команду в окне терминала:

dotnet new blazorserver -o QuizManager

Эта команда использует шаблон blazorserver для создания проекта для вашего приложения в папке QuizManager. Эта недавно созданная папка имеет много содержимого, но, помимо корневой папки, вы собираетесь коснуться следующих папок:

  • Папка Data: она содержит модели и сервисы, реализующие бизнес-логику.
  • Папка Pages: она содержит компоненты Razor, которые создают представления HTML. В частности, эта папка содержит страницу _Host.cshtml Razor, которая действует как отправная точка веб-интерфейса.
  • Папка Shared: она содержит компоненты Razor и другие элементы, общие для страниц.

Создание модели и сервиса

Итак, в качестве первого шага удалите файлы в папке Data. Затем добавьте в эту папку файл QuizItem.cs и вставьте следующий код:

// Data/QuizItem.cs using System; using System.Collections.Generic; 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 using System; using System.Collections.Generic; using System.Threading.Tasks; 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.

Теперь перейдите в корень вашего проекта и отредактируйте файл Startup.cs, заменив определение метода ConfigureServices() следующим кодом:

// Startup.cs using System; // Other using clauses namespace QuizManager { public class Startup { //leave the rest untouched public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); services.AddSingleton<QuizService>(); } //leave the rest untouched } }

С этим изменением вы зарегистрировали службу QuizService, которую вы определили выше, вместо службы примера приложения, поставляемого с шаблоном проекта Blazor по умолчанию.

Создание компонентов Razor

Теперь, когда вы создали модель и службу приложения, пора реализовать пользовательский интерфейс. Blazor использует Razor в качестве процессора шаблонов для создания динамического HTML. В частности, Blazor использует компоненты Razor для создания пользовательского интерфейса приложения. Компоненты Razor - это автономные единицы разметки и кода, которые можно вкладывать и повторно использовать даже в других проектах. Они реализованы в файле с расширением .razor.

Чтобы показать тест пользователю и позволить ему взаимодействовать с ним, вам необходимо реализовать определенное представление как компонент Razor. Итак, перейдите в папку Pages и удалите файлы Counter.razor и FetchData.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 для определения этого компонента как страницы, которая является элементом пользовательского интерфейса, который напрямую доступен через адрес (в данном случае /quizViewer) в системе маршрутизации Blazor. Затем у вас есть директива @using, которая обеспечивает доступ к пространству имен QuizManager.Data, в котором вы определили модель QuizItem и службу QuizService. Директива @inject просит систему внедрения зависимостей получить экземпляр класса QuizService, сопоставленный с переменной QuizRepository.

После этих инициализаций вы найдете разметку, определяющую пользовательский интерфейс. Как видите, эта часть представляет собой смесь кода HTML и C #, цель которой - создать список вопросов с соответствующими возможными ответами, представленными в виде переключателей.

Последний блок компонента заключен в директиву @code. Здесь вы помещаете логику компонента. В случае компонента QuizViewer у вас есть методы OnInitializedAsync() и UpdateScore(). Первый метод вызывается при инициализации компонента и в основном получает данные теста путем вызова метода GetQuizAsync() экземпляра службы QuizRepository. Метод UpdateScore() вызывается, когда пользователь щелкает один из предложенных ответов, и он обновляет список присвоенных оценок в соответствии с ответом, выбранным пользователем. В том же методе значение текущего балла вычисляется и присваивается переменной currentScore. Значение этой переменной показано над списком вопросов, как вы можете видеть в разметке.

Теперь приступим к последнему штриху, переместившись в папку Shared и заменив содержимое файла NavMenu.razor следующим кодом:

// Shared/NavMenu.razor <div class="top-row pl-4 navbar navbar-dark"> <a class="navbar-brand" href="">QuizManager</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="" 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="quizViewer"> <span class="oi oi-list-rich" aria-hidden="true"></span> Quiz </NavLink> </li> </ul> </div> @code { bool collapseNavMenu = true; string NavMenuCssClass => collapseNavMenu ? "collapse" : null; void ToggleNavMenu() { collapseNavMenu = !collapseNavMenu; } }

Файл NavMenu.razor содержит определение компонента панели навигации приложения. Код, который вы помещаете в этот файл, определяет меню навигации из двух элементов: один указывает на домашнюю страницу, а другой - на компонент QuizViewer.

Вы пока не собираетесь менять компонент App, реализованный файлом App.razor в корневой папке проекта, но все равно стоит взглянуть на него.

// App.razor <Router AppAssembly="@typeof(Program).Assembly"> <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>

Этот компонент подключает систему маршрутизации Blazor к вашему приложению с помощью встроенного компонента Router. Он позволяет перемещаться между страницами вашего приложения, различая, когда страница найдена, от того, когда она не существует. Для получения дополнительной информации о системе маршрутизации Blazor обратитесь к официальной документации.

Запуск вашего серверного приложения Blazor

Теперь у вас есть приложение для викторины, поэтому запустите его, набрав следующую команду в окне терминала:

Через несколько секунд ваше приложение должно быть доступно по адресу https://localhost:5001. Итак, если вы откроете свой браузер по этому адресу, вы получите доступ к домашней странице, как показано на следующем рисунке:

Выбрав элемент Тест в меню навигации, вы должны получить интерактивный тест, который вы создали до сих пор. Это должно выглядеть примерно так:

Если вы откроете инструменты разработчика в своем браузере, щелкните вкладку Сеть и обновите страницу, вы обнаружите, что для связи между клиентской и серверной частью вашего приложения не используется протокол HTTP, но он это двунаправленная двоичная связь, управляемая SignalR. На следующем рисунке показан канал WebSocket в инструментах разработчика Chrome:

Создание приложения Blazor WebAssembly

Теперь вы собираетесь реализовать то же приложение, используя модель хостинга WebAssembly. Как вы знаете, эта модель хостинга заставляет ваше приложение компилироваться в WebAssembly и запускаться в вашем браузере. Однако, в зависимости от структуры вашего проекта, у вас есть два варианта создания приложения:

  • У вас может быть только клиентское приложение, которое будет вызывать существующий веб-API.
  • У вас может быть как клиентское приложение, так и приложение веб-API. В этом случае приложение веб-API также обслуживает приложение Blazor WebAssembly для браузеров. Этот вариант называется размещено в ASP.NET Core.

Для этого проекта вы выберете второй вариант. Фактически, у вас будет клиентское приложение, которое будет отвечать за отображение пользовательского интерфейса и управление взаимодействием с пользователем, а также приложение веб-API, которое предоставит клиенту тест.

На момент написания модель хостинга Blazor WebAssembly недоступна для производства, но поддерживается в предварительной версии .NET Core SDK 3.1. Итак, чтобы использовать эту модель хостинга, вам необходимо установить эту предварительную версию SDK на свой компьютер.

После завершения установки SDK по указанной выше ссылке вам необходимо вручную установить шаблон проекта Blazor WebAssembly, введя следующую команду в окне терминала:

dotnet new --install Microsoft.AspNetCore.Blazor.Templates::3.1.0-preview1.19508.20

Наконец, чтобы создать этот новый проект, введите следующую команду:

dotnet new blazorwasm -o QuizManagerClientHosted --hosted

Примечание. Если вы хотите создать только клиентское приложение, вы должны опустить флаг --hosted в предыдущей команде.

Если вы посмотрите на папку QuizManagerClientHosted, вы найдете три папки: папку Client, папку Server и папку Shared. Каждая из этих папок содержит проект .NET. Хотя вы можете понять, что содержится в Client и Server папках, вы можете задаться вопросом, что содержится в папке Shared. Он содержит проект библиотеки классов с кодом, совместно используемым клиентскими и серверными приложениями. В случае приложения, которое вы собираетесь повторно реализовать, оно будет содержать модель данных.

Итак, перейдите в папку Shared, удалите файл WeatherForecasts.cs и поместите в него тот же файл QuizItem.cs, который вы создали для серверного приложения Blazor. Затем откройте файл QuizItem.cs и измените пространство имен на QuizManagerClientHosted.Shared. Содержимое файла будет выглядеть следующим образом:

// Shared/QuizItem.cs using System; using System.Collections.Generic; namespace QuizManagerClientHosted.Shared { 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>(); } } }

Создание сервера

Теперь перейдите в папку Server/Controllers и удалите файл WeatherForecastController.cs. Затем добавьте в ту же папку новый файл с именем QuizController.cs и поместите в него следующий код:

// Server/Controllers/QuizController.cs using QuizManagerClientHosted.Shared; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace QuizManagerClientHosted.Server.Controllers { [ApiController] [Route("[controller]")] public class QuizController : ControllerBase { private static readonly List<QuizItem> 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 } }; [HttpGet] public List<QuizItem> Get() { return Quiz; } } }

Как видите, это версия веб-API класса QuizService, созданного вами в серверном приложении Blazor. Вы заметили инициализацию Quiz статической переменной несколькими QuizItem экземплярами и определение действия Get(), возвращающего эту переменную.

Для получения дополнительной информации о том, как создать веб-API в .NET Core 3.0, см. Это руководство.

Создание клиента

Чтобы создать клиентское приложение Blazor, перейдите в папку Client/Pages и удалите файлы Counter.razor и FetchData.razor. Затем добавьте в эту папку файл с именем QuizViewer.razor со следующим содержимым:

// Client/Pages/QuizViewer.cs @page "/quizViewer" @using QuizManagerClientHosted.Shared @inject HttpClient Http <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 Http.GetJsonAsync<List<QuizItem>>("Quiz"); } 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(); } }

Если вы сравните этот код с созданным ранее компонентом QuizViewer Razor, вы можете заметить, что единственные различия заключаются во внедрении экземпляра HttpClient и тела метода OnInitializedAsync(). Что касается последнего, вы выполняете HTTP-вызов GET к конечной точке Quiz веб-API, реализованного ранее.

Чтобы завершить заявку, замените содержимое файла NavMenu.razor в папке Client/Shared следующим кодом:

// Shared/NavMenu.razor <div class="top-row pl-4 navbar navbar-dark"> <a class="navbar-brand" href="">QuizManager</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="" 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="quizViewer"> <span class="oi oi-list-rich" aria-hidden="true"></span> Quiz </NavLink> </li> </ul> </div> @code { bool collapseNavMenu = true; string NavMenuCssClass => collapseNavMenu ? "collapse" : null; void ToggleNavMenu() { collapseNavMenu = !collapseNavMenu; } }

Как вы могли заметить, это тот же NavMenu.razor файл, который вы создали в приложении Blazor Server. Как вы помните, он переопределяет меню навигации, включая элемент для доступа к компоненту QuizViewer.

Запуск вашего приложения Blazor WebAssembly

Ваше приложение Blazor WebAssembly готово. Просто перейдите в папку Server и введите следующую команду:

Указав в браузере адрес https://localhost:5001, вы должны увидеть тот же веб-интерфейс, что и в случае реализации сервера Blazor.

Даже если внешний вид в основном тот же, архитектура приложения совершенно другая. В этом случае у вас есть клиентская сторона, скомпилированная в WebAssembly и работающая в вашем браузере, в то время как серверная сторона работает на встроенном веб-сервере. Кроме того, в этой архитектуре клиент и сервер взаимодействуют с классическими HTTP-запросами. Вы можете проверить это, проанализировав сетевой трафик с помощью инструментов разработчика вашего браузера.

Защита приложения с помощью Auth0

Теперь у вас есть две версии одного и того же веб-приложения для викторин: одна, реализующая модель хостинга Blazor Server, а другая - модель Blazor WebAssembly. Чтобы обезопасить это приложение, вы узнаете, как интегрировать его с Сервисами Auth0. Модель хостинга Blazor WebAssembly еще не выпущена официально, поэтому в этом руководстве будет рассказано только о том, как интегрировать Auth0 с моделью хостинга Blazor Server.

Создание приложения Auth0

Первым шагом к защите вашего приложения Blazor Server является доступ к панели инструментов Auth0, чтобы создать приложение Auth0. Если вы еще не создавали учетную запись Auth0, вы можете зарегистрироваться бесплатно прямо сейчас.

Попав в личный кабинет, перейдите в раздел Приложения и выполните следующие действия:

  1. Нажмите Создать приложение.
  2. Укажите понятное имя для своего приложения (например, Quiz Blazor Server App) и выберите Обычные веб-приложения в качестве типа приложения.
  3. Наконец, нажмите кнопку Создать.

Эти шаги позволят Auth0 узнать о вашем приложении Blazor и позволят вам контролировать доступ.

После создания приложения перейдите на вкладку Настройки и обратите внимание на свой домен Auth0, идентификатор клиента и секрет клиента. Затем в той же форме присвойте значение https://localhost:5001/callback полю Разрешенные URL-адреса обратного вызова и значение https://localhost:5001/ полю Разрешенные URL-адреса для выхода.

Первое значение сообщает Auth0, по какому URL перезвонить после аутентификации пользователя. Второе значение сообщает Auth0, на какой URL-адрес следует перенаправить пользователя после выхода из системы.

Нажмите кнопку Сохранить изменения, чтобы применить их.

Настройка приложения Blazor

Откройте файл appsettings.json в корневой папке проекта Blazor Server и замените его содержимое следующим:

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

Замените заполнители YOUR_AUTH0_DOMAIN, YOUR_CLIENT_ID и YOUR_CLIENT_SECRET соответствующими значениями, взятыми из панели инструментов Auth0.

Интеграция с Auth0

Теперь установите библиотеку Microsoft.AspNetCore.Authentication.OpenIdConnect, введя следующую команду в окне терминала:

dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect

После завершения установки откройте файл Startup.cs и замените текущий раздел using следующим кодом:

// Startup.cs using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using QuizManager.Data; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authentication.Cookies; //leave the rest untouched

Теперь замените определение метода ConfigureServices() следующим кодом:

// Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddRazorPages(); services.AddServerSideBlazor(); services.Configure<CookiePolicyOptions>(options => { // This lambda determines whether user consent for non-essential cookies is needed for a given request. options.CheckConsentNeeded = context => true; options.MinimumSameSitePolicy = SameSiteMode.None; }); // Add authentication services services.AddAuthentication(options => { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie() .AddOpenIdConnect("Auth0", options => { // Set the authority to your Auth0 domain options.Authority = $"https://{Configuration["Auth0:Domain"]}"; // Configure the Auth0 Client ID and Client Secret options.ClientId = Configuration["Auth0:ClientId"]; options.ClientSecret = Configuration["Auth0:ClientSecret"]; // Set response type to code options.ResponseType = "code"; // Configure the scope options.Scope.Clear(); options.Scope.Add("openid"); // Set the callback path, so Auth0 will call back to http://localhost:3000/callback // Also ensure that you have added the URL as an Allowed Callback URL in your Auth0 dashboard options.CallbackPath = new PathString("/callback"); // Configure the Claims Issuer to be Auth0 options.ClaimsIssuer = "Auth0"; options.Events = new OpenIdConnectEvents { // handle the logout redirection OnRedirectToIdentityProviderForSignOut = (context) => { var logoutUri = $"https://{Configuration["Auth0:Domain"]}/v2/logout?client_id={Configuration["Auth0:ClientId"]}"; var postLogoutUri = context.Properties.RedirectUri; if (!string.IsNullOrEmpty(postLogoutUri)) { if (postLogoutUri.StartsWith("/")) { // transform to absolute var request = context.Request; postLogoutUri = request.Scheme + "://" + request.Host + request.PathBase + postLogoutUri; } logoutUri += $"&returnTo={ Uri.EscapeDataString(postLogoutUri)}"; } context.Response.Redirect(logoutUri); context.HandleResponse(); return Task.CompletedTask; } }; }); services.AddHttpContextAccessor(); services.AddSingleton<QuizService>(); }

Здесь вы настраиваете приложение Blazor для поддержки аутентификации через OpenID Connect. Параметры конфигурации Auth0 берутся из файла конфигурации appsetting.json.

Кроме того, замените метод Configure() в файле Startup.cs следующим определением:

// Startup.cs public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { if (env.IsDevelopment()) { app.UseDeveloperExceptionPage(); } else { app.UseExceptionHandler("/Error"); app.UseHsts(); } app.UseHttpsRedirection(); app.UseStaticFiles(); app.UseRouting(); app.UseCookiePolicy(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapBlazorHub(); endpoints.MapFallbackToPage("/_Host"); }); }

Вы просто добавили промежуточное ПО для управления политикой файлов cookie, аутентификацией и авторизацией. Теперь у вашего приложения есть инфраструктура для поддержки аутентификации.

Обеспечение безопасности на стороне сервера

Чтобы предотвратить доступ неавторизованных пользователей к серверным функциям вашего приложения, вам необходимо защитить их. Итак, откройте файл Index.razor в папке Pages и добавьте атрибут Authorize, как показано ниже:

// Pages/Index.razor @page "/" @attribute [Authorize] <h1>Hello, world!</h1> Welcome to your new app.

Также добавьте тот же атрибут к компоненту QuizViewer.razor:

// Pages/QuizViewer.razor @page "/quizViewer" @attribute [Authorize] @using QuizManager.Data @inject QuizService QuizRepository //leave the rest untouched

Это гарантирует, что рендеринг ваших страниц на стороне сервера запускается только авторизованными пользователями.

Создание конечных точек входа и выхода

Как было сказано ранее, в модели хостинга Blazor Server связь между стороной клиента и стороной сервера происходит не через HTTP, а через SignalR. Поскольку Auth0 использует стандартные протоколы, такие как OpenID и OAuth, которые полагаются на HTTP, вам необходимо предоставить способ перенести эти протоколы на Blazor.

Чтобы решить эту проблему, вы собираетесь создать две конечные точки, /login и /logout, которые перенаправляют запросы на вход и выход в Auth0. За этими конечными точками отвечают две стандартные страницы Razor.

Итак, добавьте в проект страницу Login razor, введя следующую команду в окне терминала:

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

Эта команда создает два файла в папке Pages: Login.cshtml и Login.cshtml.cs.

Откройте файл Login.cshtml.cs в папке Pages и замените его содержимое следующим:

// Pages/Login.cshtml.cs using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc.RazorPages; namespace QuizManager.Pages { public class LoginModel : PageModel { public async Task OnGet(string redirectUri) { await HttpContext.ChallengeAsync("Auth0", new AuthenticationProperties { RedirectUri = redirectUri }); } } }

Этот код запускает вызов для схемы аутентификации Auth0, которую вы определили в классе Startup.

Затем добавьте страницу Выход, введя следующую команду:

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

Как и в предыдущем случае, вы получите два новых файла в папке Pages: Logout.cshtml и Logout.cshtml.cs.

Замените содержимое файла Logout.cshtml.cs следующим кодом:

// Pages/Logout.cshtml.cs using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; namespace QuizManager.Pages { public class LogoutModel : PageModel { public async Task<IActionResult> OnGet() { await HttpContext.SignOutAsync(); return Redirect("/"); } } }

Этот код закрывает сеанс пользователя на Auth0.

Обеспечение безопасности на стороне клиента

Теперь вам нужно защитить клиентскую часть своего приложения Blazor, чтобы пользователи видели разный контент, когда они вошли в систему или нет.

Откройте файл App.razor в корневой папке проекта и замените его содержимое следующей разметкой:

// App.razor <Router AppAssembly="@typeof(Program).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> </Found> <NotFound> <p>Sorry, there's nothing at this address.</p> </NotFound> </Router>

Здесь вы используете компонент AuthorizeRouteView, который отображает связанный компонент, только если пользователь авторизован. На практике содержимое компонента MainLayout будет показано только авторизованным пользователям. Если пользователь не авторизован, он увидит контент, заключенный в компонент NotAuthorized. Если авторизация выполняется, пользователь увидит содержимое внутри компонента Authorizing.

На этом этапе убедитесь, что вы находитесь в папке QuizManager, и запустите dotnet run в своем терминале. Когда пользователь пытается получить доступ к вашему приложению, он увидит только неавторизованное сообщение, как показано на следующем рисунке:

Итак, вам нужен способ аутентификации пользователей. Для этого создайте компонент бритвы, добавив файл 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 <div class="sidebar"> <NavMenu /> </div> <div class="main"> <div class="top-row px-4"> <AccessControl /> <a href="https://docs.microsoft.com/en-us/aspnet/" target="_blank">About</a> </div> <div class="content px-4"> @Body </div> </div>

Как видите, единственная разница заключается в добавлении компонента AccessControl непосредственно перед ссылкой О программе.

Теперь ваше приложение Blazor доступно только авторизованным пользователям. Когда пользователи нажимают ссылку Войти, они будут перенаправлены на страницу универсального входа Auth0 для аутентификации. После завершения аутентификации они вернутся на домашнюю страницу вашего приложения и смогут пройти тест.

Резюме

В этой статье были представлены основы Blazor, среды программирования, которая позволяет создавать веб-клиентские приложения с использованием платформы C # и .NET Core. Вы узнали о двух моделях хостинга, Blazor Server и Blazor WebAssembly, и шаг за шагом создали приложение для управления тестами в обеих моделях хостинга.

Наконец, вы защитили версию приложения Blazor Server, интегрировав ее с Auth0.

Полный исходный код обоих приложений можно скачать из этого репозитория GitHub.

Первоначально опубликовано на https://auth0.com.