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:
:
:
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();
}
, 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).
, 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#-.
, !