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:
:
:
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#-.
, !