Blazor: Server dan WebAssembly dalam satu aplikasi pada waktu yang sama







ASP.NET Core Blazor adalah kerangka kerja web yang dikembangkan oleh Microsoft yang dirancang untuk menjalankan sisi klien di browser berbasis WebAssembly (Blazor WebAssembly) atau sisi-server di ASP.NET Core (Blazor Server), tetapi keduanya tidak dapat digunakan di waktu yang sama . Informasi lebih lanjut tentang model penempatan tertulis dalam dokumentasi .







Pada artikel ini saya akan berbicara tentang caranya







  • jalankan Server dan WebAssembly secara bersamaan di aplikasi yang sama,
  • 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