Blazor: serveur et WebAssembly dans une seule application en même temps







ASP.NET Core Blazor est un framework Web développé par Microsoft conçu pour s'exécuter côté client dans un navigateur WebAssembly (Blazor WebAssembly) ou côté serveur dans ASP.NET Core (Blazor Server), mais les deux ne peuvent pas être utilisés sur le même temps . Plus d'informations sur les modèles de placement sont écrites dans la documentation .







Dans cet article, je vais parler de la façon dont







  • exécuter Server et WebAssembly en même temps dans la même 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