Blazor: Server y WebAssembly en una aplicación al mismo tiempo







ASP.NET Core Blazor es un marco web desarrollado por Microsoft diseñado para ejecutarse en el lado del cliente en un navegador basado en WebAssembly (Blazor WebAssembly) o en el lado del servidor en ASP.NET Core (Blazor Server), pero los dos no se pueden usar en el mismo tiempo . Más información acerca de los modelos de colocación está escrito en la documentación .







En este artículo hablaré sobre cómo







  • ejecutar Server y WebAssembly al mismo tiempo en la misma aplicación,
  • Server WebAssembly ,
  • ,
  • Server WebAssembly gRPC.


TL;DR:







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 :







<component type="typeof(App)" render-mode="ServerPrerendered" />
<script src="_framework/blazor.server.js"></script>
      
      





WebAssembly :







<component type="typeof(App)" render-mode="WebAssemblyPrerendered" />
<script src="_framework/blazor.webassembly.js"></script>
      
      





:







<srvr-app>
    <component type="typeof(App)" render-mode="ServerPrerendered" />
</srvr-app>
<wasm-app style="display: none;">
    <component type="typeof(App)" render-mode="WebAssembly">
</wasm-app>
      
      





, , . , <component>



html:







<!--Blazor:{ ... }> ... <-->
      
      





, blazor , DOM . blazor.server.js



blazor.webassembly.js



, , .







, blazor.webassembly.js



, blazor.server.js



, :







var loadWasmFunction = function () {

    //  ,  blazor.server.js  
    if (srvrApp.innerHTML.indexOf('<!--Blazor:{') !== -1) {
        setTimeout(loadWasmFunction, 100);
        return;
    }

    //  ,  blazor.webassembly.js
    loadScript('webassembly');
};

setTimeout(loadWasmFunction, 100);
      
      





, . , (click, submit, onpush ..) document



window



. - Server WebAssembly .







, <srvr-app>



<wasm-app>



. js best practices addEventListener



window document:







var addServerEvent = function (type, listener, options) {
    srvrApp.addEventListener(type, listener, options);
}

var addWasmEvent = function (type, listener, options) {
    wasmApp.addEventListener(type, listener, options);
}

//   blazor.server.js

window.addEventListener = addServerEvent;
document.addEventListener = addServerEvent;

// ...

//   blazor.server.js, 
//    blazor.webassembly.js

window.addEventListener = addWasmEvent;
document.addEventListener = addWasmEvent;
      
      





. WebAssembly , <srvr-app>



<wasm-app>



:







//    Blazor Server  
window.BlazorServer._internal.forceCloseConnection();

//   
wasmApp.style.display = "block";
srvrApp.style.display = "none";
//     Server,     
      
      





blazor.hybrid.js



_Host.cshtml



. , . c# .







c#- RuntimeHeader.razor



:







private string Runtime => RuntimeInformation.RuntimeIdentifier;

protected override async Task OnAfterRenderAsync(bool firstRender)
{
    if (!firstRender) return;

    if (Runtime == "browser-wasm")
    {
        //     wasm-runtime,
        //  WebAssembly -   

        await JSRuntime.InvokeVoidAsync("wasmReady");
    }

    //   WebAssembly   

    EventHandler<LocationChangedEventArgs> switchFunc = null;
    switchFunc = async (_, e) =>
    {
        await JSRuntime.InvokeAsync<bool>("switchToWasm", e.Location);
        NavManager.LocationChanged -= switchFunc;
    };
    NavManager.LocationChanged += switchFunc;
}
      
      





, . , appsettings.json









"HybridType": "HybridOnNavigation"
      
      





HybridType









public enum HybridType
{
    //     Server
    ServerSide,

    //     WebAssembly
    WebAssembly,

    //   WebAssembly   switchToWasm
    HybridManual,

    //   WebAssembly  
    HybridOnNavigation,

    //   WebAssembly ,    
    HybridOnReady
}
      
      





2:



Server WebAssembly , , .







, Cookie Authentication.







Startup.cs



Cookie Authentication .







: Blazor Server , API HTTP, HttpClient ( ). , , cookies HttpClient. Dependency Injection , HttpClient Blazor Server:







//  ConfigureServices  Startup.cs:

services.AddTransient(sp =>
{
    var httpContextAccessor = sp.GetService<IHttpContextAccessor>();
    var httpContext = httpContextAccessor.HttpContext;

    //  Cookies 

    var cookies = httpContext.Request.Cookies;
    var cookieContainer = new System.Net.CookieContainer();

    //       HttpClientHandler

    foreach (var c in cookies)
    {
        cookieContainer.Add(
            new System.Net.Cookie(c.Key, c.Value) 
            { 
                Domain = httpContext.Request.Host.Host 
            });
    }

    return new HttpClientHandler { CookieContainer = cookieContainer };
});

services.AddTransient(sp =>
{
    var handler = sp.GetService<HttpClientHandler>();
    return new HttpClient(handler);
});

      
      





API, Blazor Server , .







Blazor Server HTTP- Set-Cookie, Cookie HttpClient'. , Blazor Server Blazor WebAssembly IAuthService



, Blazor Server Cookie .







public interface IAuthService
{
    Task<string> Login(LoginRequest loginRequest, string returnUrl);
    Task<string> Logout();

    Task<CurrentUser> CurrentUserInfo();
}
      
      





WebAssembly WasmAuthService.cs



ServerAuthService.cs



Server.







, Blazor Server Blazor WebAssembly.







3: Server WebAssembly



. Server WebAssembly , .







, Counter.razor



gRPC streaming.







gRPC







public interface ICounterService
{
    Task Increment();
    Task Decrement();

    IAsyncEnumerable<CounterState> SubscribeAsync();
}
      
      





CounterService.cs



.







, Counter.razor



ICounterService



:







[Inject] ICounterService CounterService { get; set; }

protected override void OnInitialized()
{
    var asyncState = CounterService.SubscribeAsync();
}
      
      





SubscribeAsync



:













protobuf-net.Grpc, code-first gRPC-, *.proto-.







Dependency Injection gRPC — :







services.AddTransient(sp =>
{
    // Interceptor     
    var interceptor = sp.GetService<GrpcClientInterceptor>();

    //    
    var httpHandler = sp.GetService<HttpClientHandler>();

    // ,   URI   
    var httpClient = sp.GetService<HttpClient>();

    var handler = new Grpc.Net.Client.Web.GrpcWebHandler(
        Grpc.Net.Client.Web.GrpcWebMode.GrpcWeb,
        httpHandler ?? new HttpClientHandler());

    var channel = Grpc.Net.Client.GrpcChannel.ForAddress(
        httpClient.BaseAddress,
        new Grpc.Net.Client.GrpcChannelOptions()
        {
            HttpHandler = handler
        });

    //      
    var invoker = channel.Intercept(interceptor);

    //     protobuf-net.Grpc
    return GrpcClientFactory.CreateGrpcService<T>(invoker);
});
      
      





DI gRPC. gRPC [Authorize]



, ASP.NET Core . , WeatherForecastService



.









, ASP.NET Core Blazor . Kestrel, IIS (IIS HTTPS) Docker ( Kestrel).







github..







, docker:







docker run --rm -p 80:80/tcp jdtcn/hybridblazorserver:latest
      
      





:







docker run --rm -p 80:80/tcp jdtcn/hybridblazorserver:latest -e HybridType=HybridManual
      
      





demo.







Blazor c#-.







, !








All Articles