Blazor: Server и WebAssembly одновременно в одном приложении

jl6t2bw0ol9-mhqdvhcu1xmyo0a.png

ASP.NET Core Blazor — это разработанная Microsoft веб-платформа, предназначенная для запуска на стороне клиента в браузере на основе WebAssembly (Blazor WebAssembly) или на стороне сервера в ASP.NET Core (Blazor Server), но две эти модели нельзя использовать одновременно. Подробнее о моделях размещения написано в документации.

В статье я расскажу о том, как


  • запустить Server и WebAssembly одновременно в одном приложении,
  • переключаться с Server на WebAssembly без перезагрузки приложения,
  • реализовать универсальный механизм аутентификации,
  • синхронизировать состояние Server и WebAssembly с помощью gRPC.

TL; DR:


Gif с демонстрацией полученного результата

bebxzf5o-oqhryu926nicakfyg0.gif

Пример доступен на github.


Введение: зачем это нужно

Обе модели размещения имеют свои преимущества и свои недостатки:

Преимущества Blazor Server:


  • Небольшой объём загружаемых данных (blazor.server.js без сжатия ~ 250 КБ).
  • Быстрая загрузка.
  • Отзывчивый UI.

Недостатки Blazor Server:


  • Так как изменения DOM рассчитываются на сервере, для отзывчивости UI нужно надёжное и быстрое соединение с сервером.
  • В случае разрыва соединения, приложение в браузере перестанет работать.
  • В случае перезапуска сервера, приложение в браузере перестанет работать, а состояние интерфейса будет потеряно.
  • Сложно масштабировать, так как клиент должен работать только с тем сервером, который хранит его состояние.

Преимущества Blazor WebAssembly


  • Нет всех недостатков Blazor Server, так как приложение работает в браузере автономно. Например, можно работать offline, или делать PWA.

Недостатки Blazor WebAssembly


  • Неприлично большой размер: 10 — 15 Мб.
  • Из-за такого размера от перехода по ссылке до появления интерфейса может пройти 15 — 20 секунд (для первого запуска), что в современном мире уже за гранью допустимого.

Нужно отметить, что пререндеринг доступен для обеих моделей размещения, и здорово улучшает отзывчивость, мы будем его использовать. Но даже со включенным пререндерингом для WebAssembly интерфейс будет оставаться неотзывчивым слишком долго, те же 15 — 20 секунд для первого запуска и 5 — 10 секунд для повторных.

Чтобы объединить преимущества Server и WebAssembly, у меня появилась идея реализовать гибридный режим работы: приложение должно запускаться в режиме Server, а позже переходить в режим WebAssembly незаметно для пользователя, например, во время навигации между страницами.

Далее я расскажу как у меня получилось это реализовать.


Часть 1: запуск Server и WebAssembly одновременно

Начать нужно с размещения WebAssembly приложения в приложении ASP.NET Core и включения Prerendering.

В такой конфигурации запуск Blazor начинается в файле _Host.cshtml с добавления на страницу компонента, который создаст для нас DOM нашего приложения, и загрузки скрипта, делающего наше приложение интерактивным.

Для Server это выглядит так:


А для WebAssembly так:


Поэтому нам ничего не мешает загрузить их одновременно:


    


    

Работать это, естественно, не будет. Дело в том, что тег превращается в такой html:

В процессе инициализации приложения, blazor ищет этот фрагмент, а затем заменяет его на DOM приложения. Если скрипты blazor.server.js и blazor.webassembly.js запустить одновременно, они оба будут конкурировать за первый компонент, игнорируя второй.

Этого легко избежать, если начинать запуск blazor.webassembly.js только после того, как blazor.server.js закончил работу, примерно так:

var loadWasmFunction = function () {

    // Дождёмся момента, когда blazor.server.js закончит работу
    if (srvrApp.innerHTML.indexOf('