
ASP.NET Core Blazor é uma estrutura da web desenvolvida pela Microsoft projetada para executar o lado do cliente em um navegador baseado em WebAssembly (Blazor WebAssembly) ou do lado do servidor em ASP.NET Core (Blazor Server), mas os dois não podem ser usados no mesmo tempo . Mais informações sobre modelos de posicionamento estão escritas na documentação .
Neste artigo, falarei sobre como
- execute Server e WebAssembly ao mesmo tempo no mesmo aplicativo,
- 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#-.
, !