Когда в любой момент времени стало понятно, что такое this... Когда неявное преобразование типов осталось только в былинах аксакалов эпохи зарождения веба... Когда умные книжки по Javascript нашли свой бесславный конец в мусорках...

Всё это случилось когда мир фронтенда спас Он.

Ну ладно, сбавим обороты у нашей машины пафоса.

Сегодня я предлагаю вам взглянуть на возможности Blazor в версии .Net 6. Внезапно, под катом не будет очередного ПриветМир, а окажется полноценное SaaS веб-приложение, написанное на Блейзоре, пощупав которое вы сможете гораздо лучше оценить: убийца или чижика съел?

Приложение

Мы написали приложение-парсер для поиска рекламной аудитории в социальной сети ВКонтакте, где в качестве фронтенда выступает Blazor в клиентском варианте (WebAssembly). Приложение достаточно простое (всего несколько страниц), тем не менее, в нём удалось реализовать следующий функционал:

  • Аутентификация пользователей

  • Совместная работа пользователей (тенанты, права доступа)

  • Валидация в формах ввода

  • Интеграция с внешними Javascript-компонентами

  • Использование браузерных API (локальное хранилище, Page Visibility API)

Работающее приложение можно посмотреть здесь: https://app.venando.ru

Для любителей обмазаться, исходники доступны здесь: https://github.com/a-postx/YA.BlazorVkParser

Приступим к препарированию.

Компоненты

Несмотря на то, что технология WebAssembly (WASM) поддерживается большинством браузеров, скорость её распространения пока достаточно скромная. В случае с Blazor, одним из главных тормозов остаётся скудная система компонентов. Но сегодня (конец 2021-го года) на рынке даже сугубо бесплатных компонентов уже появился какой-никакой выбор.

Мы в своём WASM-клиенте воспользовались одним из самых развитых наборов компонент, который не привязан к конкретному визуальному стеку - Blazorise. С помощью него можно очень просто создавать компоненты уже под свои конкретные нужды и эти компоненты будут изменяться, если вы решите сменить визуал, скажем, с Bootstrap на Material.

Например, мы хотим создать диалоговое окно подтверждения действия, которое будет открываться пользователю в самых разных местах приложения. Для этого с помощью компонентов Блейзорайса создаём сам компонент

@using System.ComponentModel
@using YA.WebClient.Application.Interfaces

@inject IJSRuntime JS
@inject IThemeOptionsState ThemeOptions

<Modal @ref="_modal" Closing="@OnModalClosing" ShowBackdrop="true">
    <ModalContent Centered="true" Size="ModalSize.Default" Class="@ThemeOptions.ModalContentClass">
        <ModalBody MaxHeight="65">
            <Field>
                @MessageText
            </Field>
        </ModalBody>
        <ModalFooter>
            <Button Color="Color.Primary" Block="false" Clicked="@(() => Confirm())">
                Да
            </Button>
            <Button Color="Color.Secondary" Block="false" Clicked="@(() => Hide())">
                Отмена
            </Button>
        </ModalFooter>
    </ModalContent>
</Modal>


@code
{
    [Parameter]
    public string MessageText { get; set; }
    [Parameter]
    public EventCallback<string> MessageTextChanged { get; set; }

    [Parameter]
    public Action OnYesAction { get; set; }
    [Parameter]
    public EventCallback<Action> OnYesActionChanged { get; set; }

    // если уже открыто, то выпадает исключение, поэтому контролируем это на нашей стороне
    public bool IsShowing { get; set; }

    private Modal _modal;

    public void Show()
    {
        if (!IsShowing)
        {
            _modal.Show();
            IsShowing = true;
        }
    }

    public void Hide()
    {
        if (IsShowing)
        {
            _modal.Hide();
        }
    }

    private void Confirm()
    {
        Hide();
        OnYesAction?.Invoke();
    }

    private void OnModalClosing(CancelEventArgs e)
    {
        IsShowing = false;
    }
}

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

<ActionConfirmationModal @ref="_confirmationModal"
            @bind-OnYesAction="_confirmationAction"
            @bind-MessageText="_confirmationText" />

@code
{
	  private ActionConfirmationModal _confirmationModal;
    private string _confirmationText = string.Empty;
    private Action _confirmationAction = () => Empty();
    
    private void ShowInvitationDeletionConfirmation(InvitationVm invitation)
    {
        _confirmationAction = async () => await DeleteInvitation(invitation.YaInvitationID);
        _confirmationText = $"Вы действительно хотите отменить приглашение в аккаунт для {invitation.Email}?";

        _confirmationModal.Show();
    }

    private void ShowMembershipDeletionConfirmation(MembershipVm membership)
    {
        _confirmationAction = async () => await DeleteMembership(membership.MembershipID);
        _confirmationText = $"Вы действительно хотите удалить доступ {membership.User?.Email} к этому аккаунту?";

        _confirmationModal.Show();
    }
}

Также просто мы ловим события из дочернего компонента в родительском.

Как вы видите по комментарию в коде, в Blazorise ещё довольно много болячек, но с ними уже можно как-то уживаться.

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

Обильный послужной список дотнета в бекенде даёт условному фронтендеру широчайший набор средств по решению тех или иных задач в коде, но когда дело доходит до браузеров, HTML и прочих этих ваших компонентов, то тут уже не всё так радужно.

Безусловно, экосистема Блейзора пока не идёт ни в какое сравнение с джаваскриптовой. Хоть сообщество и активно притаскивает из окружающего мира всё новые компоненты, и дотнетчику в дилемме "взять готовый компонент из сообщества" и "написать компонент самому, погрузившись в Javascript" всё меньше приходится прибегать к последнему варианту, но для сколь-либо масштабного приложения без JS не обойтись.

С другой стороны, в папке js нашего SPA-клиента всего пяток файлов, поэтому в итоге можно сказать, что для написания схожего по функционалу приложения Джаваскрипта уже почти не нужно. В кои-то веке кликбейтный заголовок оказался правдой.

Аутентификация

В нашем проекте хочется обслуживать только аутентифицированных пользователей. Для реализации достаточно в корневом компоненте (App.razor) обрамить приложение в компонент CascadingAuthenticationState, после чего на любой странице (или в компоненте) вы сможете получить текущее состояние аутентификации с помощью проброса параметра AuthenticationState

@code
{
    [CascadingParameter]
    public Task<AuthenticationState> AuthState { get; set; }
}

Вам остаётся разместить атрибут [Authorize] в файле _Imports.razor соответствующего уровня приложения, чтобы закрыть анонимный доступ ко всем нижележащим страницам. Можно также размещать атрибуты на каждой странице, если у вас будут и страницы для анонимусов.

Сама по себе настройка аутентификации в Blazor очень простая и много где описана. В нашем приложении в качестве поставщика мы выбрали Auth0, поскольку не было желания поддерживать свой сервер токенов и дополнительную логику. Единственный нюанс при использовании Auth0 - необходимость указывать аудиторию вашего бекенд-АПИ в дополнительном параметре AdditionalProviderParameters. Если этого не сделать, то вместо JWT-токена на клиент будет возвращаться токен внутреннего формата Auth0, с помощью которого на ваше серверное приложение аутентифицироваться уже не получится.

"OauthOptions": {
    "Authority": "https://yaapp-80.eu.auth0.com",
    "ClientId": "OYXLTT9VPs2Ll6w0e6tOI3g4RAT3fZNS",
    "MetadataUrl": "https://yaapp-80.eu.auth0.com/.well-known/openid-configuration",
    "RedirectUri": "https://app.venando.ru/authentication/login-callback",
    "AdditionalProviderParameters": {
      "audience": "https://yaapp-prod.ru"
    }
}

Состояние

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

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

public class RuntimeState : IRuntimeState
{
    public RuntimeState()
    {

    }

    public event EventHandler<TenantUpdatedEventArgs> TenantUpdated;

    private TenantVm _tenant;

    private TenantVm Tenant
    {
        get
        {
            return _tenant;
        }
        
        set
        {
            if (value != _tenant)
            {
                _tenant = value;
                NotifyTenantUpdated(value);
            }
        }
    }
    
    private void NotifyTenantUpdated(TenantVm tenant)
    {
        TenantUpdated?.Invoke(this, new TenantUpdatedEventArgs { Tenant = tenant });
    }

    public TenantVm GetTenant() => Tenant;
    public void PutTenant(TenantVm tenant)
    {
        Tenant = tenant;
    }
    public void RemoveTenant()
    {
        Tenant = null;
    }
}

Компоненты, которые используют модель арендатора, просто подписываются на событие TenantUpdated и выполняют необходимую для себя логику.

Планируете разрабатывать более развесистое приложение? Отлично, тогда вам в помощь будет редукс-подобный контроль состояния - например, проект Fluxor.

Мультитенант

Когда нам нужно отделить пользователя от сущностей, которыми он оперирует в приложении, то необходимо добавить слой тенанта. Работа пользователей через абстрактного "арендатора" также даст им возможность шарить сущности между собой, что весьма полезно при создании корпоративных приложений и является частой фишкой продвинутых тарифных планов SaaS-сервисов.

Поскольку аутентификация на бекенде нашего приложения работает через JWT-токены, в клиенте мы добавили шаг по созданию идентификатора арендатора при регистрации нового пользователя: клиент дёргает соответствующий метод сервера, после чего переполучает токен.

На клиенте интересная дилемма возникла при добавлении переключения между тенантами: в Блейзоре существует стандартный механизм переопределения удостоверений пользователя, которым можно воспользоваться, чтобы добавить идентификатор арендатора или любые другие атрибуты.

public class CustomUserFactory : AccountClaimsPrincipalFactory<CustomUserAccount>
{
    public CustomUserFactory(IAccessTokenProviderAccessor accessor) : base(accessor)
    {
        _tokenProviderAccessor = accessor;
    }

    private readonly IAccessTokenProviderAccessor _tokenProviderAccessor;
    private readonly JsonSerializerOptions _serializerOptions = new() { PropertyNameCaseInsensitive = true };

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(CustomUserAccount account, RemoteAuthenticationUserOptions options)
    {
        ClaimsPrincipal user = await base.CreateUserAsync(account, options);

        if (user.Identity.IsAuthenticated)
        {
            ClaimsIdentity identity = (ClaimsIdentity)user.Identity;

            AccessTokenResult tokenResult = await _tokenProviderAccessor.TokenProvider.RequestAccessToken();

            if (tokenResult.TryGetToken(out AccessToken token))
            {
                JwtSecurityTokenHandler handler = new();
                SecurityToken jsonToken = handler.ReadToken(token.Value);
                JwtSecurityToken tokenS = jsonToken as JwtSecurityToken;

                Claim metadataClaim = tokenS.Claims.FirstOrDefault(e => e.Type == "http://yaapp.app_metadata");

                if (metadataClaim != null)
                {
                    AppMetadata appMetadata = JsonSerializer
                        .Deserialize<AppMetadata>(metadataClaim.Value, _serializerOptions);

                    if (!string.IsNullOrEmpty(appMetadata.Tid))
                    {
                        identity.AddClaim(new Claim("tid", appMetadata.Tid));
                    }
                }
            }
        }

        return user;
    }
}

Однако этот механизм работает только при смене статуса пользователя (напр. с НеАутентифицирован на Аутентифицирован), поэтому для переключения между тенантами пользователю приходится делать дополнительный цикл выхода-входа, что ухудшает пользовательский опыт (в случае OAuth цикл не такой уж быстрый).

Ввиду этой печали для работы с арендаторами было решено отказаться от встроенного механизма удостоверений - идентификатор тенанта просто стал частью состояния приложения, что дало полный контроль над механизмом его переключения. Для этого в корне приложения (App.razor) мы добавили компонент UserAndTenantManager, который и отвечает за подготовку состояния перед тем, как пользователь сможет что-то делать в приложении.

<CascadingAuthenticationState>
    <Blazorise.ThemeProvider Theme="@_theme" WriteVariables="true">
        @* этап определения пользователя и арендатора, установки состояния.
        Из-за асинхронности страницы инициализируются ещё до окончания установки состояния после логина,
        поэтому объектам приложения необходимо подписываться на обновления соответствующих данных *@
        <UserAndTenantManager>
            <Router AppAssembly="@typeof(Program).Assembly" PreferExactMatches="@true">
                <Found Context="routeData">
                    <AuthorizeRouteView RouteData="@routeData" DefaultLayout="@typeof(LoadingLayout)">
                        <Authorizing>
                            <AuthorizingInProgress />
                        </Authorizing>
                        <NotAuthorized>
                            @if (context.User.Identity?.IsAuthenticated ?? false)
                            {
                                <UserNotAuthorized />
                            }
                            else
                            {
                                <RedirectToLogin />
                            }
                        </NotAuthorized>
                    </AuthorizeRouteView>
                </Found>
                <NotFound>
                    <PageNotFound />
                </NotFound>
            </Router>
        </UserAndTenantManager>
        <ToastContainer />
    </Blazorise.ThemeProvider>
</CascadingAuthenticationState>

Интеграции

В любом клиентском приложении скорее рано, чем поздно встаёт вопрос интеграции сторонних JS-компонентов и внешних сервисов. В нашем случае потребовалось прикрутить чат и плеер Ютуба.

С плеером всё получилось достаточно просто, благо в мире Джаваскрипт есть VideoJs. Добавляем в наше приложение скрипт плеера, плагина к нему и мааааленький скриптик

<script src="https://vjs.zencdn.net/7.14.3/video.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/videojs-youtube/2.6.1/Youtube.min.js"></script>
<script src="js/videoplayer.js" asp-append-version="true"></script>

Скриптик нужен потому что запуск плеера из Blazor не работает, а вот из JS - работает (не спрашивайте меня почему).

Затем на странице мы просто добавляем элемент плеера и передаём в него необходимые параметры.

<div class="row align-items-center">
    <div class="col-8">
        <video id="@_playerId" controls class="video-js"></video>
    </div>
</div>


@code
{
    private string _playerId;

    protected override void OnInitialized()
    {
        _playerId = "home-player";

        base.OnInitialized();
    }

    protected override async Task OnAfterRenderAsync(bool firstRender)
    {
        await JS.InvokeVoidAsync("loadPlayer", _playerId, new
        {
            controls = true,
            autoplay = false,
            preload = "auto",
            width = 640,
            height = 360,
            techOrder = new[] { "youtube" },
            sources = new[] {
                new { type = "video/youtube", src = "https://www.youtube.com/watch?v=YhrzIDkNYPk" }
            },
            youtube = new { ytControls = 2 }
        });

        await base.OnAfterRenderAsync(firstRender);
    }
}

Это конечно мало похоже на идеальный код, но что есть то есть.

С чатом повозиться пришлось побольше, но в итоге и он был побеждён. Среди большого количества разработок выбор пал на Чатру. Для добавления чата в наше Blazor-приложение мы традиционно добавляем их скрипт.

<script>
        window.ChatraSetup = {
            startHidden: true
        };
        (function (d, w, c) {
            w.ChatraID = '9Fff8PkPWdEwP6fAG';
            var s = d.createElement('script');
            w[c] = w[c] || function () {
                (w[c].q = w[c].q || []).push(arguments);
            };
            s.async = true;
            s.src = 'https://call.chatra.io/chatra.js';
            if (d.head) d.head.appendChild(s);
        })(document, window, 'Chatra');
    </script>

Обратите внимание на скрытый виджет при запуске. Мы не хотим, чтобы чат показывался не аутентифицированным пользователям, но при загрузке документа index.html все скрипты оттуда выполняются автоматически, запуская наш чат ещё до того, как запустится WASM. При запуске мы скрываем виджет, а уже после входа пользователя из C# мы дёргаем за ручки управления чатом.

@using YA.WebClient.Application.Interfaces
@using YA.WebClient.Application.Models.ViewModels
@using YA.WebClient.Application.Models.Dto
@using YA.WebClient.Extensions
@using YA.WebClient.Application.Events

@inject IJSRuntime JS
@inject IThemeOptionsState ThemeOptions
@inject IRuntimeState RuntimeState

@implements IDisposable

@code
{
    [CascadingParameter]
    public Task<AuthenticationState> AuthState { get; set; }

    [CascadingParameter]
    public UserAndTenantManager UserManager { get; set; }

    private TenantVm _userTenant;

    private EventHandler<TenantUpdatedEventArgs> _tenantUpdatedHandler;


    protected async override Task OnInitializedAsync()
    {
        TenantVm tenantVm = UserManager.GetTenant();

        if (tenantVm != null)
        {
            _userTenant = tenantVm;
        }

        _tenantUpdatedHandler = async (s, args) => await RefreshTenantAsync(args);
        RuntimeState.TenantUpdated += _tenantUpdatedHandler;

        await ShowSupportChat();

        await base.OnInitializedAsync();
    }

    private async Task RefreshTenantAsync(TenantUpdatedEventArgs args)
    {
        _userTenant = args.Tenant;
        StateHasChanged();

        await ShowSupportChat();
    }

    private async Task ShowSupportChat()
    {
        SupportChatUserInfo userInfo = new SupportChatUserInfo();

        if (_userTenant != null)
        {
            userInfo.TenantId = _userTenant.TenantId.ToString();
            userInfo.ТарифныйПлан = _userTenant.PricingTier?.Title;
        }

        AuthenticationState authState = await AuthState;

        userInfo.name = !string.IsNullOrEmpty(authState?.User?.Identity?.Name)
            ? authState?.User?.Identity?.Name
            : null;

        await JS.InvokeVoidAsync("Chatra", "setIntegrationData", userInfo);
        await JS.InvokeVoidAsync("Chatra", "show");
    }

    public void Dispose()
    {
        RuntimeState.TenantUpdated += _tenantUpdatedHandler;
    }
}

Chatra также позволяет добавлять свои атрибуты в виджет, поэтому мы передаём туда идентификатор арендатора (компонент обновляет его, если пользователь переключился в другой тенант) и его тарифный план, чтобы агент поддержки не запрашивал у пользователя дополнительную информацию.

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

А поясни за скорость загрузки

Демо-приложение https://app.venando.ru лежит в статическом хранилище Azure, поверх которого подключён CDN от Майкрософта, поэтому загрузка должна быть достаточно быстрой как во Владивостоке, так и в Калининграде. Желающие могут самостоятельно оценить насколько внезапность загрузки клиента на Blazor отличается от стремительности на вашем любимом фреймворке.

Итоги

Что можно сказать по итогам этого опыта: писать SPA на .Net очень приятно, его возможности дают разработчику отличные инструменты для создания надёжного кода.

Самая большая боль остаётся в долгой перекомпиляции и запуске приложения под дебагом. Хорошая новость в том, что в .Net 6 и VS2022 появилась горячая перезагрузка, которая заметно улучшает эффективность разработчика при дебаге.

Плохая новость в том, что по части производительности всё ещё есть куда стремиться: представленная AOT-компиляция помогает только ценой сильного раздувания размера приложения, что для публичных одностраничников неприемлемо.

Итак: убийца? Пока нет, но технология из разряда "вот поднатореет..."

Пишите в комментариях, насколько плох или хорош Блейзор, поплачем/посмеёмся вместе.

Напоминаю: рассмотреть детальки можно в https://github.com/a-postx/YA.BlazorVkParser

Комментарии (22)


  1. alemiks
    28.10.2021 18:47
    -1

    в WebForms хоть разметку мышкой можно было накидать, а тут теги руками писать? уу


    1. a-postx Автор
      29.10.2021 08:13

      Автокомплит работает и в компонентах и в разметке, но не без проблем конечно - форматирование кода иногда ведёт себя не совсем адекватно.


  1. i360u
    28.10.2021 18:52
    +1

    Вкладку "Memory" в девтулзах лучше не открывать, во избежание инфаркта. Вкладку "Network" - тоже. Приложение на моем любимом фреймворке весит 10kb. Продолжать?


    1. Drag13
      28.10.2021 19:16
      +2

      А какой у вас любимый фреймворк? Интересуюсь потому что тоже люблю маленькие приложения.



    1. Bigata
      28.10.2021 20:44

      Совсем худо? С телефона особо не поглядеть, но первоначальная загрузка оооочень долгая


      1. Alexufo
        29.10.2021 04:07

        В шарпе дженерики не мономорфизируются, поэтому весь блазор огроменно раздувается. На шарпе 5мб на хелоу ворлд, а на расте килобайты

        t.me/WebAssembly_ru/45428


    1. acefly
      29.10.2021 07:09

      Все равно сколько показывает Memory и Network, зато можно нормально написать на c#, не выбиваясь из экосистемы. Ну не всем заходит этот JS.


      1. i360u
        29.10.2021 09:27
        -2

        Все равно сколько показывает Memory и Network

        Простите, это сарказм? Или вы серьезно?


        1. acefly
          29.10.2021 11:34
          +1

          Да я серьезно, есть задачи, где эти показатели не критичны. Самый простой пример админка какого-то локального сервиса


    1. a-postx Автор
      29.10.2021 08:52

      Всё так, WebAssembly сама по себе немаленькая, а тут ещё и дотнет запихивать приходится. Ушёл пить горькую...


  1. Alexufo
    29.10.2021 04:19

    хотите без джава скрипта — флаттер веб. Вот там без js все на канве)


  1. beh
    29.10.2021 07:09

    Почему то секунд 10 наблюдаю прелоадер, хотя по идее все должно быть очень быстро (если использовался blazor server).


    1. a-postx Автор
      29.10.2021 07:32

      У нас используется client-side, поэтому при первой загрузке знатно подтормаживает. Сегодня делаются попытки подпереть скорость загрузки и SEO добавлением серверного пререндеринга, но он сильно усложняет дело и это пока больше в режиме экспериментов.


      1. Medd
        29.10.2021 17:29

        Привет! Вообщем это дело такое себе, мы больше года использовали клиентское приложение но сейчас переехали на сервер. Будьте осторожны при использовании клиентского приложения так как скорость очень медленная и если проверить его на разных устройствах то будут большие проблемы например некоторые грузят его по 10-20 секунд. Ну и ещё сео - это отдельная часть которая не очень то хорошо работает с клиентом и после переезда на сервер и фишек из .NET 6 позволили сделать это красиво и быстро. Если могу чем то помочь то пиши, рад буду ответить.


        1. a-postx Автор
          30.10.2021 08:13

          Спасибо! Да проблема медленной загрузки и СЕО очевидна, но если ставить стандартный лендинг на входе, где этих проблем не будет, и предполагать, что в само приложение пойдут уже только те, кому оно действительно нужно, то 10-секундное ожидание client-side версии уже не так сильно будет бить по доходимости.

          Серверная версия конечно решает проблему долгой загрузки, но приносит другие. У вас на беке монолит? Используете контейнеры? Делать зацепление совмещением бека и интерфейса в одном само по себе удовольствие ниже среднего, ведь будут ещё и другие реализации, но если у вас бессостоятельные сервисы и оркестрация, то отваливающиеся вебсокетные подключения придётся как-то обрабатывать и это отдельная боль. Если приложение развёрнуто в Азуре, то там хотя бы можно сделать прокси control-plate, который эту проблему должен решать, а в других местах такого может не быть и придётся велосипедить. Опять же, рендерить на сервере это огромное количество ресурсов, маркетинг с "бесплатным тарифным планом" будет влетать в копеечку. В общем, в серверном варианте свои заморочки - и все они влияют на то, что Blazor так и не может сегодня набрать обороты.

          Кинете ссылку на своё серверное приложение? Очень интересно.


  1. Alexandroppolus
    29.10.2021 10:12

    Так и не понял, в чем удобство по сравнению со связкой TS+React+MobX.


    1. AriesUa
      29.10.2021 12:39

      Да нет там особого профита. Просто у людей пригорает от вида JS/TS. Ну вот не заходит им, а взять в команду выделенного фронтенда, возможно, бюджет не позволяет. Вот и страдают.

      Но в таких кейсах, почему-то всегда забывают о том что за любой комфорт надо платить, и в конце концов это низкий перфоманс и большое время загрузки. Выше кстати уже написали, что лучше в DevTools не заглядывать. С чего бы это, да? :)

      Как по мне. Не вижу никакой проблемы делать фронт отдельно на JS/TS + ваш любимый фреймвок (React/Anguar/Vue etc), а бек на С#.


  1. Artouiros
    29.10.2021 16:53
    +1

    то, что оно грузится около 10-15 секунд это так и задумано? я где-то видел, что кто-то из МС отвечая на вопрос по блейзору и тому, что он пипец какой огромный рантайм тянет за собой - ответил, что дотнет6 там все шито крыто и рантайм там влазит в 2 мб. но что-то я такого не наблюдаю


    1. Alexufo
      29.10.2021 17:58

      скачка + компиляция


  1. ckaut
    29.10.2021 19:06
    +2

    Задумка с этим blazor-ом была интересная, но как дело подошло к релизу все больше похоже, что что-то с ним не так.

    1) Получился новый фреймворк сильно отличающийся и от WinForms и от Razor-a. Такой React на C#. Т.е. толку от предыдущего опыта разработки UI на C# мало, нужно изучать все заново.

    2) WebAssembly версия получилась медленной. Изначально там не было ahead of time компиляции, грешили на это. В .NET 6 ее вроде завезут, но это сильно увеличит время компиляции и размер бинарников. И возможно все равно будет медленно. С нативными приложениями (например, WinForms) по восприятию не сравнить.

    3) WebAssembly получилась огромной. Размер сжатых бинарников, загружаемых клиенту, под 4 Мб. А при включенной ahead of time компиляции - под 8 Мб. И это для hello world проекта. Это будет постоянной головной болью при разработке и сопровождении. Как уменьшить размер начальной загрузки, как уменьшить объем потребляемой памяти и т.п. Оно нам надо?

    4) Серверный режим работает на веб сокетах. Постоянно нужно сетевое соединение. Весь рендеринг идет на сервере. Очень-очень-очень на любителя.

    5) Взаимодействие с существующими js библиотеками, скажем так, будет непростым.

    6) Ну и традиционно для Microsoft все закончится тем, что новая версия фреймворка будет требовать новую версию Visual Studio, какую-нибудь новую версию браузера или работать только на Windows 12. Или вообще забьют на него в пользу другого решения. Не забываем Silverlight.


  1. Ascar
    30.10.2021 23:33

    Основной минус всей идеи в том, что лезть в js придется. И эта синхронизация далеко не всегда проходит как надо. Но есть крутые механизмы для transclusion куда можно типизированный контекст передавать. В ангуляре тоже есть такое, но только без типа. В целом тот же ангуляр гораздо профитнее.