Blazor: Server and WebAssembly in one app at the same time







ASP.NET Core Blazor is a web framework developed by Microsoft designed to run client-side in a WebAssembly-based browser (Blazor WebAssembly) or server-side in ASP.NET Core (Blazor Server), but the two cannot be used at the same time . More information about placement models is written in the documentation .







In this article I will talk about how







  • run Server and WebAssembly at the same time in the same application,
  • 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