ASP.NET Core Blazor — это разработанная Microsoft веб-платформа, предназначенная для запуска на стороне клиента в браузере на основе WebAssembly (Blazor WebAssembly) или на стороне сервера в ASP.NET Core (Blazor Server), но две эти модели нельзя использовать одновременно. Подробнее о моделях размещения написано в документации. В статье я расскажу о том, как TL;DR:  Пример доступен на github. Обе модели размещения имеют свои преимущества и свои недостатки: Преимущества Blazor Server: Недостатки Blazor Server: Преимущества Blazor WebAssembly Недостатки Blazor WebAssembly Нужно отметить, что пререндеринг доступен для обеих моделей размещения, и здорово улучшает отзывчивость, мы будем его использовать. Но даже со включенным пререндерингом для WebAssembly интерфейс будет оставаться неотзывчивым слишком долго, те же 15 — 20 секунд для первого запуска и 5 — 10 секунд для повторных. Чтобы объединить преимущества Server и WebAssembly, у меня появилась идея реализовать гибридный режим работы: приложение должно запускаться в режиме Server, а позже переходить в режим WebAssembly незаметно для пользователя, например, во время навигации между страницами. Далее я расскажу как у меня получилось это реализовать. Начать нужно с размещения WebAssembly приложения в приложении ASP.NET Core и включения Prerendering. В такой конфигурации запуск Blazor начинается в файле _Host.cshtml с добавления на страницу компонента, который создаст для нас DOM нашего приложения, и загрузки скрипта, делающего наше приложение интерактивным. Для Server это выглядит так: А для WebAssembly так: Поэтому нам ничего не мешает загрузить их одновременно: Работать это, естественно, не будет. Дело в том, что тег <component> превращается в такой html:  В процессе инициализации приложения, blazor ищет этот фрагмент, а затем заменяет его на DOM приложения. Если скрипты blazor.server.js и blazor.webassembly.js запустить одновременно, они оба будут конкурировать за первый компонент, игнорируя второй. Этого легко избежать, если начинать запуск blazor.webassembly.js только после того, как blazor.server.js закончил работу, примерно так: Теперь оба приложения запускаются, но работают некорректно. Проблема в том, что оба приложения подписываются на события (click, submit, onpush и т.д.) у document и window. Из-за этого Server и WebAssembly пытаются обработать события друг друга. Нам нужно, чтобы они прослушивали события только внутри своих узлов <srvr-app> и <wasm-app>. Для этого придётся нарушить js best practices и переопределить addEventListener для window и document: Теперь оба приложения работают. Остаётся только дождаться загрузки WebAssembly и включить его, скрыв <srvr-app> и отобразив <wasm-app>: Поместим эту логику в файл blazor.hybrid.js и подключим его к _Host.cshtml вместо первых двух скриптов. В этот же файл поместим функцию, которая будет переключать модель размещения по сигналу из приложения. Вызывать её мы будем из c# кода нашего приложения. Со стороны c#-приложения создадим компонент RuntimeHeader.razor с примерно таким содержимым: Всё, гибридное приложение работает. Остаётся добавить немного удобств, например возможность задать тип запущенного приложения в appsettings.json где HybridType это Чтобы использовать Server и WebAssembly одновременно, нужно создать механизм аутентификации, работающий с обеими моделями. Раз серверная сторона и клиент расположены в одном приложении, нам хорошо подходит Cookie Authentication.  Настроим Startup.cs для использования Cookie Authentication как обычно. Остаётся решить проблему: когда Blazor Server будет выполнять код клиентской части приложения, которая делает вызовы к API по HTTP, он станет использовать HttpClient внутри своего процесса (обращаться сам к себе). Значит, чтобы механизм авторизации работал, нужно добавлять cookies клиента в этот экземпляр HttpClient. Настроим Dependency Injection так, чтобы он создавал правильный HttpClient для Blazor Server: Теперь запросы к API, которые Blazor Server делает сам к себе внутри процесса, будут авторизованы. Но в Blazor Server мы не сможем использовать HTTP-заголовок Set-Cookie, ведь он установит Cookie для нашего внутреннего HttpClient'а. Поэтому, для Blazor Server и для Blazor WebAssembly создадим разные реализации интерфейса IAuthService, чтобы заставить Blazor Server установить Cookie для браузера клиента. Для WebAssembly WasmAuthService.cs и ServerAuthService.cs для Server. Теперь мы имеем механизм аутентификации, работающий одновременно и с Blazor Server и с Blazor WebAssembly. Это сложная задача. Если ограничиться переключением Server на WebAssembly в момент навигации, можно её не решать. Но мы не будем искать простых путей, и сделаем синхронизацию состояния компонента Counter.razor с помощью gRPC streaming. Для этого создадим gRPC сервис и реализуем его в CounterService.cs. Смотрите как божественно статически типизовано наше приложение, на стороне клиента в Counter.razor мы создаём экземпляр ICounterService: А на стороне сервера мы видим место использования метода SubscribeAsync:  Возможно это благодаря библиотеке protobuf-net.Grpc, позволяющей использовать code-first подход в создании gRPC-сервисов, вместо ручного создания*.proto-файлов. Сконфигурируем Dependency Injection для создания экземпляров gRPC — сервисов: Теперь наш DI может создавать сервисы gRPC. Проверка авторизации в сервисах gRPC осуществляется с помощью аттрибута , как и в обычном ASP.NET Core контроллере. В этом приложении, сервис WeatherForecastService как раз помечен этим атрибутом. Как оказалось, сделать гибридное ASP.NET Core Blazor приложение не сложно. Приложение можно разместить в Kestrel, в IIS (IIS поддерживает только HTTPS) и в Docker (там используется Kestrel). Получившийся проект доступен на github.. Также, я опубликовал образ для docker: Можно запустить приложение в контейнере в любом из режимов работы: В приложении для входа можно использовать логин и пароль demo. Новый фреймворк Blazor даёт просто безграничные просторы для полёта фантазии c#-разработчикам. Пробуйте, экспериментируйте!