ASP.NET Core BlazorSignalR 지침

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

이 문서에서는 Blazor 앱에서 SignalR 연결을 구성하고 관리하는 방법을 설명합니다.

ASP.NET Core SignalR 구성에 대한 일반적인 지침은 설명서의 ASP.NET CoreSignalR영역 개요, 특히 ASP.NET Core SignalR 구성의 항목을 참조하세요.

서버 쪽 앱은 ASP.NET Core SignalR 를 사용하여 브라우저와 통신합니다. SignalR'의 호스팅 및 크기 조정 조건은 서버 쪽 앱에 적용됩니다.

Blazor는 짧은 대기 시간, 안정성 및 보안 덕분에 WebSocket을 SignalR 전송으로 사용하는 경우에 가장 효과적입니다. WebSocket을 사용할 수 없거나 앱이 긴 폴링을 사용하도록 명시적으로 구성된 경우 SignalR에서 긴 폴링을 사용합니다.

대화형 서버 구성 요소에 대한 WebSocket 압축

기본적으로 대화형 서버 구성 요소는 다음과 같습니다.

CSP를 frame-ancestors 중앙 집중식으로 구성할 수 있으므로 값을 ConfigureWebSocketOptionsnull로 설정하여 수동으로 CSP를 제거할 수 있습니다. frame-ancestors CSP가 중앙 집중식으로 관리되는 경우 첫 번째 문서가 렌더링될 때마다 정책을 적용하기 위해 주의해야 합니다. 앱이 공격에 취약할 수 있으므로 정책을 완전히 제거하지 않는 것이 좋습니다.

사용 예시:

으로 설정하여 압축을 null사용하지 않도록 설정 ConfigureWebSocketOptions 하면 공격할 앱의 취약성이 줄어들지만 성능이 저하될 수 있습니다.

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ConfigureWebSocketOptions = null)

압축을 사용하도록 설정하면 값 'none' (작은따옴표 필요)을 사용하여 더 frame-ancestors 엄격한 CSP를 구성합니다. 그러면 WebSocket 압축이 가능하지만 브라우저에서 앱을 포함할 수 없습니다<iframe>.

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = "'none'")

압축을 사용하도록 설정하면 으로 설정 ContentSecurityFrameAncestorsPolicy 하여 CSP를 제거 frame-ancestors 합니다null. 이 시나리오는 CSP를 중앙 집중식으로 설정하는 앱에만 권장됩니다.

builder.MapRazorComponents<App>()
    .AddInteractiveServerRenderMode(o => o.ContentSecurityFrameAncestorsPolicy = null)

Important

브라우저는 가장 엄격한 정책 지시문 값을 사용하여 여러 CSP 헤더의 CSP 지시문을 적용합니다. 따라서 개발자는 의도적으로 또는 실수로 약한 'self' 정책을 추가할 frame-ancestors 수 없습니다.

다음으로 전달된 문자열 값에는 작은따옴표가 ContentSecurityFrameAncestorsPolicy필요합니다.

지원되지 않는 값:none, self

'self'

추가 옵션에는 하나 이상의 호스트 원본 및 스키마 원본 지정이 포함됩니다.

보안에 미치는 영향은 ASP.NET Core Blazor 대화형 서버 쪽 렌더링에 대한 위협 완화 지침을 참조하세요. 지시문에 대한 frame-ancestors 자세한 내용은 CSP: frame-ancestors (MDN 설명서)를 참조하세요.

핫 다시 로드에 응답 압축 사용 안 함

핫 다시 로드를 사용하는 경우 Development 환경에서 응답 압축 미들웨어를 사용하지 않도록 설정합니다. 프로젝트 템플릿의 기본 코드 사용 여부에 관계없이, 요청 처리 파이프라인에서는 항상 UseResponseCompression을 먼저 호출합니다.

Program 파일에서:

if (!app.Environment.IsDevelopment())
{
    app.UseResponseCompression();
}

인증을 위한 클라이언트 쪽 SignalR 원본 간 협상

이 섹션에서는 s 또는 HTTP 인증 헤더와 같은 cookie자격 증명을 보내도록 기본 클라이언트를 구성하는 SignalR방법을 설명합니다.

SetBrowserRequestCredentials를 사용하여 원본 간 fetch 요청에 Include를 설정합니다.

IncludeRequestCredentialsMessageHandler.cs:

using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Components.WebAssembly.Http;

public class IncludeRequestCredentialsMessageHandler : DelegatingHandler
{
    protected override Task<HttpResponseMessage> SendAsync(
        HttpRequestMessage request, CancellationToken cancellationToken)
    {
        request.SetBrowserRequestCredentials(BrowserRequestCredentials.Include);
        return base.SendAsync(request, cancellationToken);
    }
}

허브 연결이 구축되면 HttpMessageHandlerFactory 옵션에 HttpMessageHandler를 할당합니다.

private HubConnectionBuilder? hubConnection;

...

hubConnection = new HubConnectionBuilder()
    .WithUrl(new Uri(Navigation.ToAbsoluteUri("/chathub")), options =>
    {
        options.HttpMessageHandlerFactory = innerHandler => 
            new IncludeRequestCredentialsMessageHandler { InnerHandler = innerHandler };
    }).Build();

앞의 예제에서는 에 있는 절대 URI 주소 /chathub에 대한 허브 연결 URL을 구성합니다. 문자열(예: https://signalr.example.com) 또는 구성을 통해 URI를 설정할 수도 있습니다. Navigation는 삽입된 NavigationManager입니다.

자세한 내용은 ASP.NET Core SignalR 구성을 참조하세요.

클라이언트 쪽 렌더링

미리 렌더링이 구성된 경우 서버에 대한 클라이언트 연결이 설정되기 전에 미리 렌더링이 발생합니다. 자세한 내용은 Prerender ASP.NET Core Razor 구성 요소를 참조하세요.

미리 렌더링이 구성된 경우 서버에 대한 클라이언트 연결이 설정되기 전에 미리 렌더링이 발생합니다. 자세한 내용은 다음 문서를 참조하세요.

미리 렌더링된 상태 크기 및 SignalR 메시지 크기 제한

미리 렌더링된 큰 상태 크기가 SignalR 회로 메시지 크기 제한을 초과할 수 있으므로 다음이 발생합니다.

  • Circuit host not initialized. 클라이언트에서 오류가 발생하여 SignalR 회로를 초기화하지 못합니다.
  • 회로가 실패하면 클라이언트의 다시 연결 대화 상자가 나타납니다. 복구는 불가능합니다.

문제를 해결하려면 다음 방법 중 하나를 사용합니다.

  • 미리 렌더링된 상태로 전환할 데이터의 양을 줄입니다.
  • SignalR 메시지 크기 제한을 늘입니다. 경고: 제한을 늘리면 DoS(서비스 거부) 공격의 위험이 증가할 수 있습니다.

추가 클라이언트 쪽 리소스

서버 쪽 웹 팜 호스팅에 고정 세션 사용

Blazor 앱은 첫 번째 클라이언트 요청에 대응하여 미리 렌더링되며 이에 따라 서버의 UI 상태가 만들어집니다. 클라이언트가 SignalR 연결을 만들려는 경우 클라이언트는 동일한 서버에 다시 연결해야 합니다. 둘 이상의 백 엔드 서버를 사용하는 경우 앱은 연결에 대한 SignalR 고정 세션을 구현해야 합니다.

참고 항목

다음 오류는 webfarm에서 고정 세션을 사용하도록 설정하지 않은 앱에서 throw됩니다.

blazor.server.js:1 Catch되지 않은(프라미스 내) 오류: 기본 연결이 닫혀 있기 때문에 호출이 취소되었습니다.

서버 쪽 Azure SignalR 서비스

Microsoft Azure에서 호스트되는 서버 쪽 개발에 Azure SignalR 서비스를 사용하는 것이 좋습니다. 이 서비스는 서버 쪽 앱을 많은 수의 동시 SignalR 연결로 확장하기 위해 앱의 Blazor 허브와 함께 작동합니다. 또한 SignalR 서비스의 전역 도달 및 고성능 데이터 센터는 지리적 위치로 인한 대기 시간을 줄이는 데 큰 도움이 됩니다.

서비스의 ServerStickyMode 옵션 또는 구성 값을 Required로 설정하여 Azure SignalR Service에 고정 세션을 사용합니다. 자세한 내용은 ASP.NET Core 서버 쪽 Blazor 앱 호스트 및 배포를 참조하세요.

서버 쪽 회로 처리기 옵션

를 사용하여 회로를 구성합니다 CircuitOptions. 참조 원본에서 기본값을 봅니다.

참고 항목

.NET 참조 원본의 설명서 링크는 일반적으로 다음 릴리스의 .NET을 위한 현재 개발을 나타내는 리포지토리의 기본 분기를 로드합니다. 특정 릴리스를 위한 태그를 선택하려면 Switch branches or tags(분기 또는 태그 전환) 드롭다운 목록을 사용합니다. 자세한 내용은 ASP.NET Core 소스 코드(dotnet/AspNetCore.Docs #26205)의 버전 태그를 선택하는 방법을 참조하세요.

옵션 대리AddInteractiveServerComponents자를 사용하여 파일의 Program 옵션을 읽거나 설정합니다. {OPTION} 자리 표시자는 옵션을 나타내고 {VALUE} 자리 표시자는 값입니다.

Program 파일에서:

builder.Services.AddRazorComponents().AddInteractiveServerComponents(options =>
{
    options.{OPTION} = {VALUE};
});

옵션 대리AddServerSideBlazor자를 사용하여 파일의 Program 옵션을 읽거나 설정합니다. {OPTION} 자리 표시자는 옵션을 나타내고 {VALUE} 자리 표시자는 값입니다.

Program 파일에서:

builder.Services.AddServerSideBlazor(options =>
{
    options.{OPTION} = {VALUE};
});

옵션 대리AddServerSideBlazor자를 사용하여 옵션을 Startup.ConfigureServices 읽거나 설정합니다. {OPTION} 자리 표시자는 옵션을 나타내고 {VALUE} 자리 표시자는 값입니다.

Startup.csStartup.ConfigureServices에서 다음을 수행합니다.

services.AddServerSideBlazor(options =>
{
    options.{OPTION} = {VALUE};
});

HubConnectionContext를 구성하려면 AddHubOptions와 함께 HubConnectionContextOptions를 사용합니다. 참조 원본에서 허브 연결 컨텍스트 옵션의 기본값을 봅니다. 설명서의 옵션 설명은 SignalR ASP.NET Core SignalR 구성을 참조하세요. {OPTION} 자리 표시자는 옵션을 나타내고 {VALUE} 자리 표시자는 값입니다.

참고 항목

.NET 참조 원본의 설명서 링크는 일반적으로 다음 릴리스의 .NET을 위한 현재 개발을 나타내는 리포지토리의 기본 분기를 로드합니다. 특정 릴리스를 위한 태그를 선택하려면 Switch branches or tags(분기 또는 태그 전환) 드롭다운 목록을 사용합니다. 자세한 내용은 ASP.NET Core 소스 코드(dotnet/AspNetCore.Docs #26205)의 버전 태그를 선택하는 방법을 참조하세요.

Program 파일에서:

builder.Services.AddRazorComponents().AddInteractiveServerComponents().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

Program 파일에서:

builder.Services.AddServerSideBlazor().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

Startup.csStartup.ConfigureServices에서 다음을 수행합니다.

services.AddServerSideBlazor().AddHubOptions(options =>
{
    options.{OPTION} = {VALUE};
});

Warning

MaximumReceiveMessageSize의 기본값은 32KB입니다. 값을 늘리면 DoS(서비스 거부) 공격의 위험이 증가할 수 있습니다.

메모리 관리에 대한 자세한 내용은 ASP.NET Core 서버 쪽 Blazor 앱 호스트 및 배포를 참조하세요.

Blazor 허브 옵션

허브를 제어 HttpConnectionDispatcherOptions 하는 옵션을 구성 MapBlazorHub 합니다Blazor. 참조 원본에서 허브 연결 디스패처 옵션의 기본값을 확인합니다. {OPTION} 자리 표시자는 옵션을 나타내고 {VALUE} 자리 표시자는 값입니다.

참고 항목

.NET 참조 원본의 설명서 링크는 일반적으로 다음 릴리스의 .NET을 위한 현재 개발을 나타내는 리포지토리의 기본 분기를 로드합니다. 특정 릴리스를 위한 태그를 선택하려면 Switch branches or tags(분기 또는 태그 전환) 드롭다운 목록을 사용합니다. 자세한 내용은 ASP.NET Core 소스 코드(dotnet/AspNetCore.Docs #26205)의 버전 태그를 선택하는 방법을 참조하세요.

Program 파일에서 호출한 후 호출 app.MapBlazorHubapp.MapRazorComponents 합니다.

app.MapBlazorHub(options =>
{
    options.{OPTION} = {VALUE};
});

Program 의 파일에 옵션을 app.MapBlazorHub 제공합니다.

app.MapBlazorHub(options =>
{
    options.{OPTION} = {VALUE};
});

엔드포인트 라우팅 구성에 대한 옵션을 app.MapBlazorHub 제공합니다.

app.UseEndpoints(endpoints =>
{
    endpoints.MapBlazorHub(options =>
    {
        options.{OPTION} = {VALUE};
    });
    ...
});

최대 수신 메시지 크기

이 섹션은 구현 SignalR하는 프로젝트에만 적용됩니다.

허브 메서드에 허용되는 최대 수신 SignalR 메시지 크기는 (기본값: 32KB)로 제한 HubOptions.MaximumReceiveMessageSize 됩니다. SignalR 메시지가 오류를 throw하는 것보다 MaximumReceiveMessageSize 큰 경우 프레임워크는 허브에서 클라이언트로 전송되는 SignalR 메시지의 크기에 제한을 적용하지 않습니다.

SignalR 로깅이 디버그 또는 추적으로 설정되지 않은 경우 메시지 크기 오류는 브라우저의 개발자 도구 콘솔에만 표시됩니다.

오류: 커넥트오류: 서버가 닫을 때 오류를 반환했습니다. 커넥트오류로 닫혔습니다.'라는 오류로 연결이 끊어졌습니다.

SignalR 서버 쪽 로깅디버그 또는 추적으로 설정되면 서버 쪽 로깅은 메시지 크기 오류에 해당하는 InvalidDataException을 표시합니다.

appsettings.Development.json:

{
  "DetailedErrors": true,
  "Logging": {
    "LogLevel": {
      ...
      "Microsoft.AspNetCore.SignalR": "Debug"
    }
  }
}

오류:

System.IO.InvalidDataException: 최대 메시지 크기가 32768B를 초과했습니다. 메시지 크기는 AddHubOptions에서 구성할 수 있습니다.

한 가지 방법은 파일에서 설정 MaximumReceiveMessageSize 하여 제한을 늘리는 것입니다 Program . 다음 예제에서는 최대 수신 메시지 크기를 64KB로 설정합니다.

builder.Services.AddRazorComponents().AddInteractiveServerComponents()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

SignalR 들어오는 메시지 크기 제한을 늘리면 더 많은 서버 리소스가 필요하고 DoS(서비스 거부) 공격의 위험이 증가합니다. 또한, 메모리에 대용량의 콘텐츠를 문자열 또는 바이트 배열로 읽어오면 할당이 가비지 수집기에서 제대로 작동하지 않아서 추가적인 성능 손실로 이어질 수도 있습니다.

큰 페이로드를 읽기 위한 더 나은 옵션은 콘텐츠를 더 작은 청크로 보내고 페이로드를 로 처리하는 것입니다 Stream. 이는 큰 JavaScript(JS) interop JSON 페이로드를 읽거나 interop 데이터를 원시 바이트로 사용할 수 있는 경우에 JS 사용할 수 있습니다. 구성 요소와 유사한 기술을 사용하는 서버 쪽 앱에서 큰 이진 페이로드를 보내는 방법을 InputFile 보여 주는 예제는 이진 제출 샘플 앱BlazorInputLargeTextArea 구성 요소 샘플을 참조하세요.

참고 항목

.NET 참조 원본의 설명서 링크는 일반적으로 다음 릴리스의 .NET을 위한 현재 개발을 나타내는 리포지토리의 기본 분기를 로드합니다. 특정 릴리스를 위한 태그를 선택하려면 Switch branches or tags(분기 또는 태그 전환) 드롭다운 목록을 사용합니다. 자세한 내용은 ASP.NET Core 소스 코드(dotnet/AspNetCore.Docs #26205)의 버전 태그를 선택하는 방법을 참조하세요.

큰 페이로드를 처리하는 양식은 SignalR 스트리밍 interop을 JS 직접 사용할 수도 있습니다. 자세한 내용은 ASP.NET Core의 JavaScript 함수에서 .NET 메서드 호출을 참조하세요 Blazor. 데이터를 서버로 스트리밍하는 양식 예제는 <textarea> ASP.NET Core Blazor 양식 문제 해결을 참조하세요.

한 가지 방법은 파일에서 설정 MaximumReceiveMessageSize 하여 제한을 늘리는 것입니다 Program . 다음 예제에서는 최대 수신 메시지 크기를 64KB로 설정합니다.

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

SignalR 들어오는 메시지 크기 제한을 늘리면 더 많은 서버 리소스가 필요하고 DoS(서비스 거부) 공격의 위험이 증가합니다. 또한, 메모리에 대용량의 콘텐츠를 문자열 또는 바이트 배열로 읽어오면 할당이 가비지 수집기에서 제대로 작동하지 않아서 추가적인 성능 손실로 이어질 수도 있습니다.

큰 페이로드를 읽기 위한 더 나은 옵션은 콘텐츠를 더 작은 청크로 보내고 페이로드를 로 처리하는 것입니다 Stream. 이는 큰 JavaScript(JS) interop JSON 페이로드를 읽거나 interop 데이터를 원시 바이트로 사용할 수 있는 경우에 JS 사용할 수 있습니다. 구성 요소와 유사한 기술을 사용하는 대규모 이진 페이로드 Blazor Server 를 보내는 방법을InputFile보여 주는 예제는 이진 제출 샘플 앱 및BlazorInputLargeTextArea 구성 요소 샘플을 참조하세요.

참고 항목

.NET 참조 원본의 설명서 링크는 일반적으로 다음 릴리스의 .NET을 위한 현재 개발을 나타내는 리포지토리의 기본 분기를 로드합니다. 특정 릴리스를 위한 태그를 선택하려면 Switch branches or tags(분기 또는 태그 전환) 드롭다운 목록을 사용합니다. 자세한 내용은 ASP.NET Core 소스 코드(dotnet/AspNetCore.Docs #26205)의 버전 태그를 선택하는 방법을 참조하세요.

큰 페이로드를 처리하는 양식은 SignalR 스트리밍 interop을 JS 직접 사용할 수도 있습니다. 자세한 내용은 ASP.NET Core의 JavaScript 함수에서 .NET 메서드 호출을 참조하세요 Blazor. 앱에서 데이터를 스트리밍 <textarea> 하는 양식 예제는 Blazor Server ASP.NET Core Blazor 양식 문제 해결을 참조하세요.

Startup.ConfigureServices에서 MaximumReceiveMessageSize를 설정하여 제한을 늘립니다.

services.AddServerSideBlazor()
    .AddHubOptions(options => options.MaximumReceiveMessageSize = 64 * 1024);

SignalR 들어오는 메시지 크기 제한을 늘리면 더 많은 서버 리소스가 필요하고 DoS(서비스 거부) 공격의 위험이 증가합니다. 또한, 메모리에 대용량의 콘텐츠를 문자열 또는 바이트 배열로 읽어오면 할당이 가비지 수집기에서 제대로 작동하지 않아서 추가적인 성능 손실로 이어질 수도 있습니다.

많은 양의 데이터를 전송하는 코드를 개발할 때 다음 지침을 고려합니다.

  • 네이티브 스트리밍 JS interop 지원을 활용하여 들어오는 메시지 크기 제한보다 SignalR 큰 데이터를 전송합니다.
  • 일반적인 팁:
    • JS 및 C# 코드에서 큰 개체를 할당하면 안 됩니다.
    • 프로세스가 완료되거나 취소되면 사용된 메모리를 해제합니다.
    • 보안을 위해 다음과 같은 추가 요구 사항을 적용합니다.
      • 전달할 수 있는 최대 파일 또는 데이터 크기를 선언합니다.
      • 클라이언트에서 서버로의 최소 업로드 속도를 선언합니다.
    • 서버에서 데이터를 받은 후에는 다음과 같은 데이터가 될 수 있습니다.
      • 모든 세그먼트가 수집될 때까지 메모리 버퍼에 임시로 저장합니다.
      • 즉시 사용합니다. 예를 들어 각 세그먼트가 수신됨에 따라 데이터를 즉시 데이터베이스에 저장하거나 디스크에 쓸 수 있습니다.
  • 데이터를 작은 조각으로 분할하고, 서버가 모든 데이터를 받을 때까지 데이터 세그먼트를 순차적으로 보냅니다.
  • JS 및 C# 코드에서 큰 개체를 할당하면 안 됩니다.
  • 데이터를 보내거나 받을 때 주 UI 스레드를 장기간 차단하면 안 됩니다.
  • 프로세스가 완료되거나 취소되면 사용된 메모리를 해제합니다.
  • 보안을 위해 다음과 같은 추가 요구 사항을 적용합니다.
    • 전달할 수 있는 최대 파일 또는 데이터 크기를 선언합니다.
    • 클라이언트에서 서버로의 최소 업로드 속도를 선언합니다.
  • 서버에서 데이터를 받은 후에는 다음과 같은 데이터가 될 수 있습니다.
    • 모든 세그먼트가 수집될 때까지 메모리 버퍼에 임시로 저장합니다.
    • 즉시 사용합니다. 예를 들어 각 세그먼트가 수신됨에 따라 데이터를 즉시 데이터베이스에 저장하거나 디스크에 쓸 수 있습니다.

Blazor 서버 쪽 허브 엔드포인트 경로 구성

파일에서 Program 호출 MapBlazorHub 하여 앱의 기본 경로에 매핑 BlazorHub 합니다. Blazor 스크립트(blazor.*.js)는 MapBlazorHub에서 만든 엔드포인트를 자동으로 가리킵니다.

UI의 서버 쪽 연결 상태 반영

클라이언트에서 연결이 끊어진 것을 감지하면, 클라이언트가 다시 연결하는 동안 기본 UI가 사용자에게 표시됩니다. 다시 연결하지 못한 경우 사용자에게 다시 시도 옵션이 제공됩니다.

UI를 사용자 지정하려면 components-reconnect-modalid를 사용하여 단일 요소를 정의합니다. 다음 예제에서는 요소를 구성 요소에 배치합니다 App .

App.razor:

UI를 사용자 지정하려면 components-reconnect-modalid를 사용하여 단일 요소를 정의합니다. 다음 예제에서는 호스트 페이지에 요소를 배치합니다.

Pages/_Host.cshtml:

UI를 사용자 지정하려면 components-reconnect-modalid를 사용하여 단일 요소를 정의합니다. 다음 예제에서는 레이아웃 페이지에 요소를 배치합니다.

Pages/_Layout.cshtml:

UI를 사용자 지정하려면 components-reconnect-modalid를 사용하여 단일 요소를 정의합니다. 다음 예제에서는 호스트 페이지에 요소를 배치합니다.

Pages/_Host.cshtml:

<div id="components-reconnect-modal">
    There was a problem with the connection!
</div>

참고 항목

idcomponents-reconnect-modal인 둘 이상의 요소가 앱으로 렌더링되는 경우 렌더링된 첫 번째 요소만 CSS 클래스 변경 내용을 수신하여 요소를 표시하거나 숨깁니다.

사이트의 스타일시트에 다음 CSS 스타일을 추가합니다.

wwwroot/app.css:

wwwroot/css/site.css:

#components-reconnect-modal {
    display: none;
}

#components-reconnect-modal.components-reconnect-show, 
#components-reconnect-modal.components-reconnect-failed, 
#components-reconnect-modal.components-reconnect-rejected {
    display: block;
}

다음 표에서는 Blazor 프레임워크에 의해 components-reconnect-modal 요소에 적용된 CSS 클래스에 대해 설명합니다.

CSS 클래스 표시 내용
components-reconnect-show 연결이 끊어졌습니다. 클라이언트가 다시 연결하는 중입니다. 모달을 표시합니다.
components-reconnect-hide 서버에 대해 활성 연결이 다시 설정되었습니다. 모달을 숨깁니다.
components-reconnect-failed 네트워크 오류로 인해 다시 연결하지 못했습니다. 다시 연결을 시도하려면 JavaScript에서 window.Blazor.reconnect()를 호출합니다.
components-reconnect-rejected 다시 연결이 거부되었습니다. 서버에 접근했지만 연결이 거부되었으며, 서버의 사용자 상태가 손실되었습니다. 앱을 다시 로드하려면 JavaScript에서 location.reload()를 호출합니다. 이 연결 상태는 다음과 같은 경우에 발생할 수 있습니다.
  • 서버 쪽 회로에서 크래시가 발생한 경우
  • 서버에서 사용자 상태를 삭제하기에 충분한 기간에 클라이언트 연결이 끊긴 경우. 사용자 구성 요소의 인스턴스가 삭제됩니다.
  • 서버가 다시 시작되었거나, 앱의 작업자 프로세스가 재활용된 경우

사이트의 CSS에서 모달 요소에 대해 transition-delay 속성을 설정하여 다시 연결 표시가 나타날 때까지 지연 시간을 사용자 지정합니다. 다음 예제에서는 전환 지연 시간을 500ms(기본값)에서 1,000ms(1초)로 변경합니다.

wwwroot/app.css:

wwwroot/css/site.css:

#components-reconnect-modal {
    transition: visibility 0s linear 1000ms;
}

현재 다시 연결 시도를 표시하려면 components-reconnect-current-attemptid를 사용하여 요소를 정의합니다. 최대 재연결 재시도 횟수를 표시하려면 components-reconnect-max-retriesid를 사용하여 요소를 정의합니다. 다음 예제에서는 이전 예제에 따라 이러한 요소를 다시 연결 시도 모달 요소 내에 배치합니다.

<div id="components-reconnect-modal">
    There was a problem with the connection!
    (Current reconnect attempt: 
    <span id="components-reconnect-current-attempt"></span> /
    <span id="components-reconnect-max-retries"></span>)
</div>

사용자 지정 다시 연결 모달이 나타나면 이전 코드에 따라 다음과 유사한 콘텐츠를 렌더링합니다.

There was a problem with the connection! (Current reconnect attempt: 3 / 8)

서버 쪽 렌더링

기본적으로 구성 요소는 서버에 대한 클라이언트 연결이 설정되기 전에 서버에서 미리 렌더링됩니다. 자세한 내용은 Prerender ASP.NET Core Razor 구성 요소를 참조하세요.

기본적으로 구성 요소는 서버에 대한 클라이언트 연결이 설정되기 전에 서버에서 미리 렌더링됩니다. 자세한 내용은 ASP.NET Core 구성 요소 태그 도우미를 참조하세요.

서버 쪽 회로 작업 모니터링

에서 메서드를 사용하여 CreateInboundActivityHandler 인바운드 회로 작업을 모니터링합니다 CircuitHandler. 인바운드 회로 활동은 UI 이벤트 또는 JavaScript-to-.NET interop 호출과 같이 브라우저에서 서버로 전송되는 모든 활동입니다.

예를 들어 회로 작업 처리기를 사용하여 클라이언트가 유휴 상태인지 감지하고 회로 ID()Circuit.Id를 기록할 수 있습니다.

using Microsoft.AspNetCore.Components.Server.Circuits;
using Microsoft.Extensions.Options;
using Timer = System.Timers.Timer;

public sealed class IdleCircuitHandler : CircuitHandler, IDisposable
{
    private Circuit? currentCircuit;
    private readonly ILogger logger;
    private readonly Timer timer;

    public IdleCircuitHandler(ILogger<IdleCircuitHandler> logger, 
        IOptions<IdleCircuitOptions> options)
    {
        timer = new Timer
        {
            Interval = options.Value.IdleTimeout.TotalMilliseconds,
            AutoReset = false
        };

        timer.Elapsed += CircuitIdle;
        this.logger = logger;
    }

    private void CircuitIdle(object? sender, System.Timers.ElapsedEventArgs e)
    {
        logger.LogInformation("{CircuitId} is idle", currentCircuit?.Id);
    }

    public override Task OnCircuitOpenedAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        currentCircuit = circuit;

        return Task.CompletedTask;
    }

    public override Func<CircuitInboundActivityContext, Task> CreateInboundActivityHandler(
        Func<CircuitInboundActivityContext, Task> next)
    {
        return context =>
        {
            timer.Stop();
            timer.Start();

            return next(context);
        };
    }

    public void Dispose() => timer.Dispose();
}

public class IdleCircuitOptions
{
    public TimeSpan IdleTimeout { get; set; } = TimeSpan.FromMinutes(5);
}

public static class IdleCircuitHandlerServiceCollectionExtensions
{
    public static IServiceCollection AddIdleCircuitHandler(
        this IServiceCollection services, 
        Action<IdleCircuitOptions> configureOptions)
    {
        services.Configure(configureOptions);
        services.AddIdleCircuitHandler();

        return services;
    }

    public static IServiceCollection AddIdleCircuitHandler(
        this IServiceCollection services)
    {
        services.AddScoped<CircuitHandler, IdleCircuitHandler>();

        return services;
    }
}

파일에 서비스를 등록합니다 Program . 다음 예제에서는 이전 IdleCircuitHandler 구현을 테스트하기 위해 기본 유휴 시간 제한을 5분에서 5초로 구성합니다.

builder.Services.AddIdleCircuitHandler(options => 
    options.IdleTimeout = TimeSpan.FromSeconds(5));

회로 작업 처리기는 다른 DI(비Blazor 종속성 주입) 범위에서 범위가 지정된 Blazor 서비스에 액세스하는 방법도 제공합니다. 자세한 내용 및 예제는 다음을 참조하세요.

Blazor 시작

웹앱 파일 Blazor 에서 App.razor 앱 회로의 SignalR 수동 시작을 Blazor 구성합니다.

파일에서 앱 회로의 BlazorSignalR 수동 시작 구성(Blazor Server):Pages/_Host.cshtml

파일에서 앱 회로의 BlazorSignalR 수동 시작 구성(Blazor Server):Pages/_Layout.cshtml

파일에서 앱 회로의 BlazorSignalR 수동 시작 구성(Blazor Server):Pages/_Host.cshtml

  • blazor.*.js 스크립트의 <script> 태그에 autostart="false" 특성을 추가합니다.
  • Blazor.start()를 호출하는 스크립트를 Blazor 스크립트가 로드된 후 폐쇄 </body> 태그 안에 배치합니다.

autostart를 사용하지 않도록 설정하면 회로에 종속되지 않는 앱의 일부분도 정상적으로 작동합니다. 예를 들어 클라이언트 쪽 라우팅이 작동합니다. 그러나 회로에 종속되는 모든 측면은 Blazor.start()가 호출된 다음에야 작동합니다. 설정된 회로가 없으면 앱 동작을 예측할 수 없습니다. 예를 들어 회로의 연결이 끊어지면 구성 요소 메서드가 실행되지 않습니다.

문서가 준비되면 초기화하는 Blazor 방법 및 연결 방법에 JS Promise대한 자세한 내용은 ASP.NET Core Blazor 시작을 참조하세요.

클라이언트에서 SignalR 시간 제한 및 연결 유지 구성

클라이언트에 대해 다음 값을 구성합니다.

  • withServerTimeout: 서버 시간 제한을 밀리초 단위로 구성합니다. 서버에서 메시지를 받지 않고 이 시간 제한이 경과하면 연결이 오류와 함께 종료됩니다. 기본 제한 시간 값은 30초입니다. 서버 시간 제한은 연결 유지 간격(withKeepAliveInterval)에 할당된 값의 두 배 이상이어야 합니다.
  • withKeepAliveInterval: Keep-Alive 간격(서버를 ping할 기본 간격)을 밀리초 단위로 구성합니다. 이 설정을 사용하면 클라이언트가 네트워크에서 컴퓨터를 분리하는 경우 등 서버에서 하드 연결 끊기를 감지할 수 있습니다. ping은 서버 ping만큼 자주 발생합니다. 서버가 5초마다 ping하는 경우, 5초 미만의 5000 값을 할당하면 5초마다 ping이 수행됩니다. 기본값은 15초입니다. 연결 유지 간격은 서버 시간 제한(withServerTimeout)에 할당된 값의 절반보다 작거나 같아야 합니다.

파일(Blazor웹앱)에 대한 App.razor 다음 예제에서는 기본값 할당을 보여 줍니다.

Blazor 웹앱:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      configureSignalR: function (builder) {
        builder.withServerTimeout(30000).withKeepAliveInterval(15000);
      }
    }
  });
</script>

파일(Blazor Server.NET 6의 Pages/_Host.cshtml ASP.NET Core를 제외한 모든 버전) 또는 Pages/_Layout.cshtml 파일(Blazor Server.NET 6의 ASP.NET Core)에 대한 다음 예제입니다.

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
        builder.withServerTimeout(30000).withKeepAliveInterval(15000);
  });
</script>

앞의 예제 {BLAZOR SCRIPT} 에서 자리 표시자는 스크립트 경로 및 파일 이름입니다 Blazor . 스크립트의 위치와 사용할 경로는 ASP.NET Core Blazor 프로젝트 구조를 참조하세요.

구성 요소에서 허브 연결을 만들 때 (기본값: 30초) 및 KeepAliveInterval (기본값: 15초)를 HubConnectionBuilder설정합니다 ServerTimeout . 빌드HubConnection된 에서 HandshakeTimeout (기본값: 15초) 설정합니다. 다음 예제에서는 기본값의 할당을 보여줍니다.

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .WithServerTimeout(TimeSpan.FromSeconds(30))
        .WithKeepAliveInterval(TimeSpan.FromSeconds(15))
        .Build();

    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

클라이언트에 대해 다음 값을 구성합니다.

  • serverTimeoutInMilliseconds: 서버 시간 제한(밀리초). 서버에서 메시지를 받지 않고 이 시간 제한이 경과하면 연결이 오류와 함께 종료됩니다. 기본 제한 시간 값은 30초입니다. 서버 시간 제한은 연결 유지 간격(keepAliveIntervalInMilliseconds)에 할당된 값의 두 배 이상이어야 합니다.
  • keepAliveIntervalInMilliseconds: 서버를 ping할 기본 간격 이 설정을 사용하면 클라이언트가 네트워크에서 컴퓨터를 분리하는 경우 등 서버에서 하드 연결 끊기를 감지할 수 있습니다. ping은 서버 ping만큼 자주 발생합니다. 서버가 5초마다 ping하는 경우, 5초 미만의 5000 값을 할당하면 5초마다 ping이 수행됩니다. 기본값은 15초입니다. 연결 유지 간격은 서버 시간 제한(serverTimeoutInMilliseconds)에 할당된 값의 절반보다 작거나 같아야 합니다.

파일(Blazor Server.NET 6의 Pages/_Host.cshtml ASP.NET Core를 제외한 모든 버전) 또는 Pages/_Layout.cshtml 파일(Blazor Server.NET 6의 ASP.NET Core)에 대한 다음 예제는 다음과 같습니다.

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 30000;
      c.keepAliveIntervalInMilliseconds = 15000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

앞의 예제 {BLAZOR SCRIPT} 에서 자리 표시자는 스크립트 경로 및 파일 이름입니다 Blazor . 스크립트의 위치와 사용할 경로는 ASP.NET Core Blazor 프로젝트 구조를 참조하세요.

구성 요소에서 허브 연결을 만들 때 빌드된 HubConnection에서 ServerTimeout(기본값: 30초), HandshakeTimeout(기본값: 15초), KeepAliveInterval(기본값: 15초)를 설정합니다. 다음 예제에서는 기본값의 할당을 보여줍니다.

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(30);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(15);
    hubConnection.KeepAliveInterval = TimeSpan.FromSeconds(15);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

서버 시간 제한() 또는 Keep-Alive 간격(ServerTimeoutKeepAliveInterval)의 값을 변경하는 경우:

  • 서버 시간 제한은 연결 유지 간격에 할당된 값의 두 배 이상이어야 합니다.
  • 연결 유지 간격은 서버 시간 제한에 할당된 값의 절반보다 작거나 같아야 합니다.

자세한 내용은 다음 문서의 전역 배포 및 연결 실패 섹션을 참조하세요.

서버 쪽 다시 연결 처리기 수정

다음과 같은 사용자 지정 동작에 대한 다시 연결 처리기의 회로 연결 이벤트를 수정할 수 있습니다.

  • 연결이 삭제되는 경우 사용자에게 알립니다.
  • 회로가 연결된 경우 클라이언트에서 로깅을 수행합니다.

연결 이벤트를 수정하려면 다음과 같은 연결 변경 내용에 대한 콜백을 등록합니다.

  • 끊어진 연결은 onConnectionDown을 사용합니다.
  • 설정된 연결/다시 설정된 연결은 onConnectionUp을 사용합니다.

onConnectionDownonConnectionUp을 모두 지정해야 합니다.

Blazor 웹앱:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      reconnectionHandler: {
        onConnectionDown: (options, error) => console.error(error),
        onConnectionUp: () => console.log("Up, up, and away!")
      }
    }
  });
</script>

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    reconnectionHandler: {
      onConnectionDown: (options, error) => console.error(error),
      onConnectionUp: () => console.log("Up, up, and away!")
    }
  });
</script>

앞의 예제 {BLAZOR SCRIPT} 에서 자리 표시자는 스크립트 경로 및 파일 이름입니다 Blazor . 스크립트의 위치와 사용할 경로는 ASP.NET Core Blazor 프로젝트 구조를 참조하세요.

서버 쪽 다시 연결이 실패할 때 자동으로 페이지 새로 고침

기본 다시 연결 동작을 사용하려면 사용자가 다시 연결에 실패한 후 페이지를 새로 고치는 수동 작업을 수행해야 합니다. 그러나 사용자 지정 다시 연결 처리기를 사용하여 페이지를 자동으로 새로 고칠 수 있습니다.

App.razor:

Pages/_Host.cshtml:

<div id="reconnect-modal" style="display: none;"></div>
<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script src="boot.js"></script>

앞의 예제 {BLAZOR SCRIPT} 에서 자리 표시자는 스크립트 경로 및 파일 이름입니다 Blazor . 스크립트의 위치와 사용할 경로는 ASP.NET Core Blazor 프로젝트 구조를 참조하세요.

다음 파일을 만듭니다 wwwroot/boot.js .

Blazor 웹앱:

(() => {
  const maximumRetryCount = 3;
  const retryIntervalMilliseconds = 5000;
  const reconnectModal = document.getElementById('reconnect-modal');

  const startReconnectionProcess = () => {
    reconnectModal.style.display = 'block';

    let isCanceled = false;

    (async () => {
      for (let i = 0; i < maximumRetryCount; i++) {
        reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;

        await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));

        if (isCanceled) {
          return;
        }

        try {
          const result = await Blazor.reconnect();
          if (!result) {
            // The server was reached, but the connection was rejected; reload the page.
            location.reload();
            return;
          }

          // Successfully reconnected to the server.
          return;
        } catch {
          // Didn't reach the server; try again.
        }
      }

      // Retried too many times; reload the page.
      location.reload();
    })();

    return {
      cancel: () => {
        isCanceled = true;
        reconnectModal.style.display = 'none';
      },
    };
  };

  let currentReconnectionProcess = null;

  Blazor.start({
    circuit: {
      reconnectionHandler: {
        onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
        onConnectionUp: () => {
          currentReconnectionProcess?.cancel();
          currentReconnectionProcess = null;
        }
      }
    }
  });
})();

Blazor Server:

(() => {
  const maximumRetryCount = 3;
  const retryIntervalMilliseconds = 5000;
  const reconnectModal = document.getElementById('reconnect-modal');

  const startReconnectionProcess = () => {
    reconnectModal.style.display = 'block';

    let isCanceled = false;

    (async () => {
      for (let i = 0; i < maximumRetryCount; i++) {
        reconnectModal.innerText = `Attempting to reconnect: ${i + 1} of ${maximumRetryCount}`;

        await new Promise(resolve => setTimeout(resolve, retryIntervalMilliseconds));

        if (isCanceled) {
          return;
        }

        try {
          const result = await Blazor.reconnect();
          if (!result) {
            // The server was reached, but the connection was rejected; reload the page.
            location.reload();
            return;
          }

          // Successfully reconnected to the server.
          return;
        } catch {
          // Didn't reach the server; try again.
        }
      }

      // Retried too many times; reload the page.
      location.reload();
    })();

    return {
      cancel: () => {
        isCanceled = true;
        reconnectModal.style.display = 'none';
      },
    };
  };

  let currentReconnectionProcess = null;

  Blazor.start({
    reconnectionHandler: {
      onConnectionDown: () => currentReconnectionProcess ??= startReconnectionProcess(),
      onConnectionUp: () => {
        currentReconnectionProcess?.cancel();
        currentReconnectionProcess = null;
      }
    }
  });
})();

Blazor 시작에 대한 자세한 내용은 ASP.NET Core Blazor 시작을 참조하세요.

서버 쪽 다시 연결 재시도 횟수 및 간격 조정

다시 연결 다시 시도 횟수 및 간격을 조정하려면 다시 시도 횟수(maxRetries) 및 각 다시 시도에 허용되는 기간(밀리초)(retryIntervalMilliseconds)을 설정합니다.

Blazor 웹앱:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      reconnectionOptions: {
        maxRetries: 3,
        retryIntervalMilliseconds: 2000
      }
    }
  });
</script>

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    reconnectionOptions: {
      maxRetries: 3,
      retryIntervalMilliseconds: 2000
    }
  });
</script>

앞의 예제 {BLAZOR SCRIPT} 에서 자리 표시자는 스크립트 경로 및 파일 이름입니다 Blazor . 스크립트의 위치와 사용할 경로는 ASP.NET Core Blazor 프로젝트 구조를 참조하세요.

Blazor 시작에 대한 자세한 내용은 ASP.NET Core Blazor 시작을 참조하세요.

다시 연결 UI가 표시되는 시점 제어

다시 연결 UI가 나타나는 시기를 제어하는 것은 다음과 같은 경우에 유용할 수 있습니다.

  • 배포된 앱은 내부 네트워크 또는 인터넷 대기 시간으로 인한 ping 시간 제한으로 인해 다시 연결 UI를 자주 표시하며 지연 시간을 늘리려고 합니다.
  • 앱은 사용자에게 연결이 더 빨리 삭제되었음을 보고해야 하며 지연 시간을 단축하려고 합니다.

다시 연결 UI의 모양 타이밍은 클라이언트에서 Keep-Alive 간격 및 시간 제한을 조정하여 영향을 받습니다. 그러나 설정을 변경하려면 다른 Keep-Alive, 시간 제한 및 핸드셰이크 설정을 변경해야 할 수 있습니다.

다음 지침에 대한 일반적인 권장 사항으로:

  • Keep-Alive 간격은 클라이언트와 서버 구성 간에 일치해야 합니다.
  • 시간 제한은 Keep-Alive 간격에 할당된 값의 두 배 이상이어야 합니다.

서버 구성

다음과 같이 설정합니다.

  • ClientTimeoutInterval (기본값: 30초): 서버가 연결을 닫기 전에 시간 창 클라이언트가 메시지를 보내야 합니다.
  • HandshakeTimeout (기본값: 15초): 서버가 들어오는 핸드셰이크 요청을 클라이언트에서 시간 초과하기 위해 사용하는 간격입니다.
  • KeepAliveInterval (기본값: 15초): 연결된 클라이언트에 활성 유지 ping을 보내기 위해 서버에서 사용하는 간격입니다. 클라이언트에는 서버 값과 일치해야 하는 Keep-Alive 간격 설정도 있습니다.

HandshakeTimeoutClientTimeoutInterval 증가 될 수 있으며KeepAliveInterval, 다시 기본 수 있습니다. 중요한 고려 사항은 값을 변경하는 경우 시간 제한이 Keep-Alive 간격의 값의 두 배 이상이고 Keep-Alive 간격이 서버와 클라이언트 간에 일치해야 한다는 것입니다. 자세한 내용은 클라이언트 섹션에서 시간 제한 및 Keep-Alive 구성 SignalR 섹션을 참조하세요.

다음 예제에서

  • 이 값은 ClientTimeoutInterval 60초(기본값: 30초)로 증가합니다.
  • 이 값은 HandshakeTimeout 30초(기본값: 15초)로 증가합니다.
  • 개발자 KeepAliveInterval 코드에서는 설정되지 않으며 기본값인 15초가 사용됩니다. Keep-Alive 간격의 값을 줄이면 통신 ping 빈도가 증가하여 앱, 서버 및 네트워크의 부하가 증가합니다. Keep-Alive 간격을 낮출 때 성능 저하를 방지하려면 주의해야 합니다.

Blazor서버 프로젝트 파일의 Program 웹앱(.NET 8 이상):

builder.Services.AddRazorComponents().AddInteractiveServerComponents()
    .AddHubOptions(options =>
{
    options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
    options.HandshakeTimeout = TimeSpan.FromSeconds(30);
});

Blazor Server 파일에서 다음을 수행 Program 합니다.

builder.Services.AddServerSideBlazor()
    .AddHubOptions(options =>
    {
        options.ClientTimeoutInterval = TimeSpan.FromSeconds(60);
        options.HandshakeTimeout = TimeSpan.FromSeconds(30);
    });

자세한 내용은 서버 쪽 회로 처리기 옵션 섹션을 참조하세요.

클라이언트 구성

다음과 같이 설정합니다.

  • withServerTimeout (기본값: 30초): 회로의 허브 연결에 대해 밀리초 단위로 지정된 서버 시간 제한을 구성합니다.
  • withKeepAliveInterval (기본값: 15초): 연결에서 Keep-Alive 메시지를 보내는 간격(밀리초)입니다.

서버 시간 제한을 늘릴 수 있으며 Keep-Alive 간격은 동일하게 다시 기본 수 있습니다. 중요한 고려 사항은 값을 변경하는 경우 서버 시간 제한이 Keep-Alive 간격 값의 두 배 이상이고 Keep-Alive 간격 값이 서버와 클라이언트 간에 일치해야 한다는 것입니다. 자세한 내용은 클라이언트 섹션에서 시간 제한 및 Keep-Alive 구성 SignalR 섹션을 참조하세요.

다음 시작 구성 예제(스크립트의 Blazor 위치)에서는 서버 시간 제한에 60초의 사용자 지정 값이 사용됩니다. Keep-Alive 간격(withKeepAliveInterval)은 설정되지 않으며 기본값인 15초를 사용합니다.

Blazor 웹앱:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    circuit: {
      configureSignalR: function (builder) {
        builder.withServerTimeout(60000);
      }
    }
  });
</script>

Blazor Server:

<script src="{BLAZOR SCRIPT}" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      builder.withServerTimeout(60000);
    }
  });
</script>

구성 요소에서 허브 연결을 만들 때 서버 시간 제한(WithServerTimeout기본값: 30초) HubConnectionBuilder을 설정합니다. 빌드HubConnection된 에서 HandshakeTimeout (기본값: 15초) 설정합니다. 시간 제한이 Keep-Alive 간격(WithKeepAliveInterval/KeepAliveInterval)의 두 배 이상이며 Keep-Alive 값이 서버와 클라이언트 간에 일치하는지 확인합니다.

다음 예제는 SignalR의 Index 구성 요소를 기반으로 하는 Blazor 자습서입니다. 서버 시간 제한이 60초로 증가하고 핸드셰이크 시간 제한이 30초로 증가합니다. Keep-Alive 간격은 설정되지 않으며 기본값인 15초를 사용합니다.

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .WithServerTimeout(TimeSpan.FromSeconds(60))
        .Build();

    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

다음과 같이 설정합니다.

  • serverTimeoutInMilliseconds (기본값: 30초): 회로의 허브 연결에 대해 밀리초 단위로 지정된 서버 시간 제한을 구성합니다.
  • keepAliveIntervalInMilliseconds (기본값: 15초): 연결에서 Keep-Alive 메시지를 보내는 간격(밀리초)입니다.

서버 시간 제한을 늘릴 수 있으며 Keep-Alive 간격은 동일하게 다시 기본 수 있습니다. 중요한 고려 사항은 값을 변경하는 경우 서버 시간 제한이 Keep-Alive 간격 값의 두 배 이상이고 Keep-Alive 간격 값이 서버와 클라이언트 간에 일치해야 한다는 것입니다. 자세한 내용은 클라이언트 섹션에서 시간 제한 및 Keep-Alive 구성 SignalR 섹션을 참조하세요.

다음 시작 구성 예제(스크립트의 Blazor 위치)에서는 서버 시간 제한에 60초의 사용자 지정 값이 사용됩니다. Keep-Alive 간격(keepAliveIntervalInMilliseconds)은 설정되지 않으며 기본값인 15초를 사용합니다.

Pages/_Host.cshtml의 경우

<script src="_framework/blazor.server.js" autostart="false"></script>
<script>
  Blazor.start({
    configureSignalR: function (builder) {
      let c = builder.build();
      c.serverTimeoutInMilliseconds = 60000;
      builder.build = () => {
        return c;
      };
    }
  });
</script>

구성 요소에서 허브 연결을 만들 때, 빌드된 HubConnection에서 ServerTimeout(기본값: 30초) 및 HandshakeTimeout(기본값: 15초)를 설정합니다. 시간 제한이 Keep-Alive 간격의 두 배 이상임을 확인합니다. Keep-Alive 간격이 서버와 클라이언트 간에 일치하는지 확인합니다.

다음 예제는 SignalR의 Index 구성 요소를 기반으로 하는 Blazor 자습서입니다. ServerTimeout 60초 HandshakeTimeout 로 증가하고 30초로 증가합니다. Keep-Alive 간격(KeepAliveInterval)은 설정되지 않으며 기본값인 15초를 사용합니다.

protected override async Task OnInitializedAsync()
{
    hubConnection = new HubConnectionBuilder()
        .WithUrl(Navigation.ToAbsoluteUri("/chathub"))
        .Build();

    hubConnection.ServerTimeout = TimeSpan.FromSeconds(60);
    hubConnection.HandshakeTimeout = TimeSpan.FromSeconds(30);

    hubConnection.On<string, string>("ReceiveMessage", (user, message) => ...

    await hubConnection.StartAsync();
}

클라이언트에서 Blazor 회로 연결 끊기

기본적으로 unload 페이지 이벤트가 트리거될 때 Blazor 회로의 연결이 끊깁니다. 클라이언트에서 다른 시나리오에 대한 회로의 연결을 끊으려면 적절한 이벤트 처리기에서 Blazor.disconnect를 호출합니다. 다음 예제에서는 페이지가 숨겨질 때(pagehide 이벤트) 회로의 연결이 끊깁니다.

window.addEventListener('pagehide', () => {
  Blazor.disconnect();
});

Blazor 시작에 대한 자세한 내용은 ASP.NET Core Blazor 시작을 참조하세요.

서버 쪽 회로 처리기

사용자의 회로 상태에 대한 변경 내용에 대한 코드를 실행할 수 있도록 하는 회로 처리기를 정의할 수 있습니다. 회로 처리기는 CircuitHandler에서 파생되고 앱의 서비스 컨테이너에 클래스를 등록하여 구현됩니다. 다음 회로 처리기 예제에서는 열린 SignalR 연결을 추적합니다.

TrackingCircuitHandler.cs:

using Microsoft.AspNetCore.Components.Server.Circuits;

public class TrackingCircuitHandler : CircuitHandler
{
    private HashSet<Circuit> circuits = new();

    public override Task OnConnectionUpAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Add(circuit);

        return Task.CompletedTask;
    }

    public override Task OnConnectionDownAsync(Circuit circuit, 
        CancellationToken cancellationToken)
    {
        circuits.Remove(circuit);

        return Task.CompletedTask;
    }

    public int ConnectedCircuits => circuits.Count;
}

회로 처리기는 DI를 사용하여 등록됩니다. 회로 인스턴스별로 범위가 지정된 인스턴스가 생성됩니다. 위의 예제에서 TrackingCircuitHandler를 사용하면 모든 회로의 상태를 추적해야 하기 때문에 singleton 서비스가 생성됩니다.

Program 파일에서:

builder.Services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

Startup.csStartup.ConfigureServices에서 다음을 수행합니다.

services.AddSingleton<CircuitHandler, TrackingCircuitHandler>();

사용자 지정 회로 처리기의 메서드에서 처리되지 않은 예외가 throw될 경우 이 예외는 회로에 치명적입니다. 처리기의 코드 또는 호출된 메서드에서 예외를 허용하려면 오류 처리 및 로깅 기능을 사용하여 하나 이상의 try-catch 문에 코드를 래핑합니다.

사용자가 연결을 끊었으며 프레임워크에서 회로 상태를 정리하고 있어서 회로가 종료될 경우 프레임워크는 회로의 DI 범위를 삭제합니다. 범위를 삭제하면 System.IDisposable을 구현하는, 회로 범위가 지정된 DI 서비스가 모두 삭제됩니다. 삭제하는 동안 처리되지 않은 예외를 throw하는 DI 서비스가 있을 경우 프레임워크에서 예외를 기록합니다. 자세한 내용은 ASP.NET Core Blazor 종속성 주입을 참조하세요.

사용자 지정 서비스에 대한 사용자를 캡처하는 서버 쪽 회로 처리기

A를 CircuitHandler 사용하여 서비스에서 사용자를 AuthenticationStateProvider 캡처하고 해당 사용자를 설정합니다. 자세한 내용 및 예제 코드는 서버 쪽 ASP.NET Core Blazor 추가 보안 시나리오를 참조하세요.

대화형 서버 구성 요소를 다시 기본 없는 경우 회로가 닫힌 경우

대화형 서버 구성 요소는 회로라는 브라우저와 실시간 연결을 사용하여 웹 UI 이벤트를 처리합니다. 루트 대화형 서버 구성 요소가 렌더링될 때 회로 및 연결된 상태가 만들어집니다. 페이지에 대화형 서버 구성 요소를 다시 기본 없으면 회로가 닫혀 서버 리소스를 확보합니다.

IHttpContextAccessor/HttpContext구성 요소에서 Razor

IHttpContextAccessor 는 유효한 HttpContext 사용 가능한 렌더링이 없으므로 대화형 렌더링을 피해야 합니다.

IHttpContextAccessor 는 서버에서 정적으로 렌더링되는 구성 요소에 사용할 수 있습니다. 그러나 가능하면 피하는 것이 좋습니다.

HttpContext는 헤더 또는 구성 요소(Components/App.razor)의 다른 속성 검사 및 수정과 같은 일반적인 작업에 대해 정적으로 렌더링된 루트 구성 요소에서 App 만 연계 매개 변수로 사용할 수 있습니다. 이 값은 항상 null 대화형 렌더링을 위한 것입니다.

[CascadingParameter]
public HttpContext? HttpContext { get; set; }

대화형 구성 요소에 필요한 시나리오 HttpContext 의 경우 서버에서 영구 구성 요소 상태를 통해 데이터를 흐르는 것이 좋습니다. 자세한 내용은 서버 쪽 ASP.NET Core Blazor 추가 보안 시나리오를 참조하세요.

서버 쪽 Blazor 앱의 구성 요소에서 Razor 직접 또는 간접적으로 사용하지 IHttpContextAccessor/HttpContext 마세요. Blazor 앱은 ASP.NET Core 파이프라인 컨텍스트 외부에서 실행됩니다. IHttpContextAccessor 내에서의 HttpContext은 보장되지 않으며, Blazor 앱을 시작하는 컨텍스트 유지를 위해 HttpContext도 보장되지 않습니다.

앱에 요청 상태를 Blazor 전달하는 권장 방법은 앱의 초기 렌더링 중에 루트 구성 요소 매개 변수를 사용하는 것입니다. 또는 앱에서 사용할 루트 구성 요소의 초기화 수명 주기 이벤트에서 범위가 지정된 서비스로 데이터를 복사할 수 있습니다. 자세한 내용은 서버 쪽 ASP.NET Core Blazor 추가 보안 시나리오를 참조하세요.

서버 쪽 Blazor 보안의 중요한 측면은 회로가 설정된 후 Blazor 특정 시점에 지정된 회로에 연결된 사용자가 업데이트될 수 있지만 IHttpContextAccessor업데이트되지 않는다는 것입니다. 사용자 지정 서비스를 사용하여 이 상황을 해결하는 방법에 대한 자세한 내용은 서버 쪽 ASP.NET Core Blazor 추가 보안 시나리오를 참조하세요.

추가 서버 쪽 리소스