ASP.NET Core용 SignalR에서 허브 사용

작성자: Rachel AppelKevin Griffin

SignalR Hubs API를 사용하면 연결된 클라이언트가 서버에서 메서드를 호출할 수 있습니다. 서버는 클라이언트에서 호출되는 메서드를 정의하고 클라이언트는 서버에서 호출되는 메서드를 정의합니다. SignalR는 실시간 클라이언트 간 통신 및 서버 간 통신을 가능하게 하는 데 필요한 모든 것을 처리합니다.

SignalR 허브 구성

SignalR 허브에 필요한 서비스를 등록하려면 Program.cs에서 AddSignalR를 호출합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

SignalR 엔드포인트를 구성하려면 Program.cs에서 MapHub을 호출합니다.

app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");

app.Run();

참고 항목

ASP.NET Core SignalR 서버 쪽 어셈블리는 이제 .NET Core SDK와 함께 설치됩니다. 자세한 내용은 공유 프레임워크의 어셈블리를 참조 SignalR 하세요.

허브 만들기 및 사용

Hub에서 상속되는 클래스를 선언하여 허브를 만듭니다. 클래스에 public 메서드를 추가하여 클라이언트에서 호출할 수 있도록 합니다.

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.SendAsync("ReceiveMessage", user, message);
}

참고 항목

Hubs는 일시적입니다.

  • 허브 클래스의 속성에 상태를 저장하지 마세요. 각 허브 메서드 호출은 새 허브 인스턴스에서 실행됩니다.
  • 종속성 주입을 통해 허브를 직접 인스턴스화하지 마세요. 애플리케이션의 다른 위치에서 클라이언트로 메시지를 보내려면 .IHubContext
  • 활성 상태를 유지하는 허브를 사용하는 비동기 메서드를 호출할 때 await를 사용합니다. 예를 들어 Clients.All.SendAsync(...)와 같은 메서드는 await 없이 호출되고 SendAsync가 완료되기 전에 허브 메서드가 완료되면 실패할 수 있습니다.

컨텍스트 개체

Hub 클래스에는 연결에 대한 정보가 있는 다음 속성을 포함하는 Context 속성이 포함됩니다.

속성 설명
ConnectionId SignalR에서 할당한 연결의 고유 ID를 가져옵니다. 각 연결에 대한 하나의 연결 ID가 있습니다.
UserIdentifier 사용자 식별자를 가져옵니다. 기본적으로 SignalR에서는 연결과 연결된 ClaimsPrincipalClaimTypes.NameIdentifier를 사용자 식별자로 사용합니다.
User 현재 사용자와 연결된 ClaimsPrincipal을 가져옵니다.
Items 이 연결 범위 내에서 데이터를 공유하는 데 사용할 수 있는 키/값 컬렉션을 가져옵니다. 데이터는 이 컬렉션에 저장될 수 있으며 서로 다른 허브 메서드 호출 간의 연결을 위해 유지됩니다.
Features 연결에서 사용할 수 있는 기능 컬렉션을 가져옵니다. 지금은 이 컬렉션이 대부분의 시나리오에서 필요하지 않으므로 아직 자세히 문서화되지 않았습니다.
ConnectionAborted 연결이 중단될 때 알리는 CancellationToken을 가져옵니다.

Hub.Context에는 다음 메서드도 포함되어 있습니다.

메서드 설명
GetHttpContext 연결에 대한 HttpContext를 반환하거나, 연결이 HTTP 요청과 연결되지 않은 경우 null을 반환합니다. HTTP 연결의 경우 이 메서드를 사용하여 HTTP 헤더 및 쿼리 문자열과 같은 정보를 가져옵니다.
Abort 연결을 중단합니다.

클라이언트 개체

Hub 클래스에는 서버와 클라이언트 간의 통신을 위한 다음 속성이 포함된 Clients 속성이 포함됩니다.

속성 설명
All 연결된 모든 클라이언트에서 메서드 호출
Caller 허브 메서드를 호출한 클라이언트에서 메서드를 호출합니다.
Others 메서드를 호출한 클라이언트를 제외한 연결된 모든 클라이언트에서 메서드를 호출합니다.

Hub.Clients에는 다음 메서드도 포함되어 있습니다.

메서드 설명
AllExcept 지정된 연결을 제외한 연결된 모든 클라이언트에서 메서드를 호출합니다.
Client 연결된 특정 클라이언트에서 메서드 호출
Clients 연결된 특정 클라이언트에서 메서드 호출
Group 지정된 그룹의 모든 연결에서 메서드 호출
GroupExcept 지정된 연결을 제외한 지정된 그룹의 모든 연결에서 메서드 호출
Groups 여러 연결 그룹에서 메서드 호출
OthersInGroup 허브 메서드를 호출한 클라이언트를 제외하고 연결 그룹에서 메서드 호출
User 특정 사용자와 연결된 모든 연결에서 메서드 호출
Users 지정된 사용자와 연결된 모든 연결에서 메서드 호출

이전 테이블에 있는 각 속성 또는 메서드는 SendAsync 메서드를 사용하여 개체를 반환합니다. SendAsync 메서드는 호출할 클라이언트 메서드의 이름과 매개 변수를 받습니다.

ClientCaller 메서드에서 반환된 개체에는 클라이언트의 결과를 기다리는 데 사용할 수 있는 InvokeAsync 메서드도 포함되어 있습니다.

클라이언트에게 메시지 보내기

특정 클라이언트를 호출하려면 Clients 개체의 속성을 사용합니다. 다음 예제에는 세 가지 허브 메서드가 있습니다.

  • SendMessageClients.All을 사용하여 연결된 모든 클라이언트에게 메시지를 보냅니다.
  • SendMessageToCallerClients.Caller를 사용하여 호출자에게 메시지를 다시 보냅니다.
  • SendMessageToGroupSignalR Users 그룹의 모든 클라이언트에게 메시지를 보냅니다.
public async Task SendMessage(string user, string message)
    => await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)
    => await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)
    => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);

강력한 형식의 허브

SendAsync 사용의 단점은 호출할 클라이언트 메서드를 지정하기 위해 문자열을 사용한다는 것입니다. 이렇게 하면 메서드 이름의 철자가 잘못되거나 클라이언트에서 누락된 경우 런타임 오류에 대한 코드가 열립니다.

SendAsync를 사용하는 것의 대안은 Hub<T>과 함께 Hub 클래스를 강력한 형식으로 입력하는 것입니다. 다음 예제에서는 ChatHub 클라이언트 메서드가 IChatClient라는 인터페이스로 추출되었습니다.

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

이 인터페이스는 강력한 형식으로 입력하도록 앞의 ChatHub 예제를 리팩터링하는 데 사용할 수 있습니다.

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.ReceiveMessage(user, message);

    public async Task SendMessageToCaller(string user, string message)
        => await Clients.Caller.ReceiveMessage(user, message);

    public async Task SendMessageToGroup(string user, string message)
        => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}

Hub<IChatClient>를 사용하면 클라이언트 메서드의 컴파일 시간 검사를 사용할 수 있습니다. 이렇게 하면 Hub<T>가 인터페이스에 정의된 메서드에만 액세스할 수 있기 때문에 문자열을 사용하여 발생하는 문제를 방지할 수 있습니다. 강력한 형식의 Hub<T>를 사용하면 SendAsync를 사용할 수 없습니다.

참고 항목

Async 접미사는 메서드 이름에서 제거되지 않습니다. 클라이언트 메서드가 .on('MyMethodAsync')로 정의되지 않는 한 MyMethodAsync을 이름으로 사용하지 마세요.

클라이언트 결과

서버는 클라이언트를 호출하는 것 외에도 클라이언트에서 결과를 요청할 수 있습니다. 이렇게 하려면 서버가 ISingleClientProxy.InvokeAsync를 사용하고 클라이언트는 .On 처리기에서 결과를 반환해야 합니다.

서버에서 API를 사용하는 방법에는 두 가지가 있습니다. 첫 번째는 Hub 메서드의 Clients 속성에서 Client(...) 또는 Caller을 호출하는 것입니다.

public class ChatHub : Hub
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        var message = await Clients.Client(connectionId).InvokeAsync<string>(
            "GetMessage");
        return message;
    }
}

두 번째 방법은 IHubContext<T>의 인스턴스에서 Client(...)를 호출하는 것입니다.

async Task SomeMethod(IHubContext<MyHub> context)
{
    string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
        "GetMessage");
}

강력한 형식의 허브는 인터페이스 메서드에서 값을 반환할 수도 있습니다.

public interface IClient
{
    Task<string> GetMessage();
}

public class ChatHub : Hub<IClient>
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        string message = await Clients.Client(connectionId).GetMessage();
        return message;
    }
}

클라이언트는 아래와 같이 .On(...) 처리기에서 결과를 반환합니다.

.NET 클라이언트

hubConnection.On("GetMessage", async () =>
{
    Console.WriteLine("Enter message:");
    var message = await Console.In.ReadLineAsync();
    return message;
});

Typescript 클라이언트

hubConnection.on("GetMessage", async () => {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("message");
        }, 100);
    });
    return promise;
});

Java 클라이언트

hubConnection.onWithResult("GetMessage", () -> {
    return Single.just("message");
});

허브 메서드의 이름 변경

기본적으로 서버 허브 메서드 이름은 .NET 메서드의 이름입니다. 특정 메서드에 대해 이 기본 동작을 변경하려면 HubMethodName 특성을 사용합니다. 클라이언트는 메서드를 호출할 때 .NET 메서드 이름 대신 이 이름을 사용해야 합니다.

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
    => await Clients.User(user).SendAsync("ReceiveMessage", user, message);

허브에 서비스 삽입

허브 생성자는 DI의 서비스를 매개 변수로 수락할 수 있으며, 허브 메서드에서 사용하기 위해 클래스의 속성에 저장할 수 있습니다.

다른 허브 메서드 또는 코드를 작성하는 다른 방법으로 여러 서비스를 삽입하는 경우 허브 메서드는 DI의 서비스를 수락할 수도 있습니다. 기본적으로 허브 메서드 매개 변수는 가능한 경우 DI에서 검사 및 확인됩니다.

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message, IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

서비스에서 매개 변수의 암시적 해결을 원하지 않는 경우 DisableImplicitFromServicesParameters를 사용하여 사용하지 않도록 설정합니다. 허브 메서드에서 DI에서 확인되는 매개 변수를 명시적으로 지정하려면 DisableImplicitFromServicesParameters 옵션을 사용하고 DI에서 확인해야 하는 허브 메서드 매개 변수에서 IFromServiceMetadata를 구현하는 [FromServices] 특성 또는 사용자 지정 특성을 사용합니다.

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();
services.AddSignalR(options =>
{
    options.DisableImplicitFromServicesParameters = true;
});

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message,
        [FromServices] IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

참고 항목

이 기능은 선택적으로 DI 구현에 의해 구현되는 IServiceProviderIsService를 사용합니다. 앱의 DI 컨테이너가 이 기능을 지원하지 않는 경우 허브 메서드에 서비스를 삽입하는 것은 지원되지 않습니다.

종속성 주입에서 키 지정된 서비스 지원

키 서비스는 키를 사용하여 DI(종속성 주입) 서비스를 등록하고 검색하는 메커니즘을 나타냅니다. 서비스는 키를 등록하기 위해 호출 AddKeyedSingleton (또는 AddKeyedScopedAddKeyedTransient)하여 키와 연결됩니다. 특성을 사용하여 키를 [FromKeyedServices] 지정하여 등록된 서비스에 액세스합니다. 다음 코드는 키 지정된 서비스를 사용하는 방법을 보여 줍니다.

using Microsoft.AspNetCore.SignalR;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddKeyedSingleton<ICache, BigCache>("big");
builder.Services.AddKeyedSingleton<ICache, SmallCache>("small");

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

var app = builder.Build();

app.MapRazorPages();
app.MapHub<MyHub>("/myHub");

app.Run();

public interface ICache
{
    object Get(string key);
}
public class BigCache : ICache
{
    public object Get(string key) => $"Resolving {key} from big cache.";
}

public class SmallCache : ICache
{
    public object Get(string key) => $"Resolving {key} from small cache.";
}

public class MyHub : Hub
{
    public void SmallCacheMethod([FromKeyedServices("small")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }

    public void BigCacheMethod([FromKeyedServices("big")] ICache cache)
    {
        Console.WriteLine(cache.Get("signalr"));
    }
}

연결에 대한 이벤트 처리

SignalR Hubs API는 연결을 관리하고 추적하는 OnConnectedAsyncOnDisconnectedAsync 가상 메서드를 제공합니다. 클라이언트가 Hub에 연결할 때 그룹에 추가하는 등의 작업을 수행하도록 OnConnectedAsync 가상 메서드를 재정의합니다.

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

클라이언트 연결이 끊어지면 작업을 수행하도록 OnDisconnectedAsync 가상 메서드를 재정의합니다. connection.stop()를 호출하는 등 클라이언트가 의도적으로 연결을 끊으면 exception 매개 변수가 null로 설정됩니다. 그러나 오류(예: 네트워크 오류)로 인해 클라이언트의 연결이 끊어지면 exception 매개 변수에 오류를 설명하는 예외가 포함됩니다.

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await base.OnDisconnectedAsync(exception);
}

RemoveFromGroupAsync 에서는 호출 OnDisconnectedAsync할 필요가 없으며 자동으로 처리됩니다.

오류 처리

허브 메서드에서 throw된 예외는 메서드를 호출한 클라이언트로 전송됩니다. JavaScript 클라이언트에서 invoke 메서드는 JavaScriptPromise를 반환합니다. 클라이언트는 반환된 promise에 catch 처리기를 연결하거나 try/catchasync/await와 함께 사용하여 예외를 처리할 수 있습니다.

try {
  await connection.invoke("SendMessage", user, message);
} catch (err) {
  console.error(err);
}

허브가 예외를 throw하는 경우 연결이 닫히지 않습니다. 기본적으로 SignalR는 다음 예제와 같이 클라이언트에 제네릭 오류 메시지를 반환합니다.

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.

예기치 않은 예외에는 데이터베이스 연결이 실패할 때 트리거된 예외의 데이터베이스 서버 이름과 같은 중요한 정보가 포함되어 있는 경우가 많습니다. SignalR에서는 기본적으로 이러한 자세한 오류 메시지를 보안 조치로 노출하지 않습니다. 예외 세부 정보를 표시하지 않는 이유에 대한 자세한 내용은 ASP.NET Core SignalR의 보안 고려 사항 문서를 참조하세요.

예외 조건이 클라이언트에 전파되어야 하는 경우 HubException 클래스를 사용합니다. HubException가 허브 메서드에서 throw되면 SignalR는 수정되지 않은 전체 예외 메시지를 클라이언트에 보냅니다.

public Task ThrowException()
    => throw new HubException("This error will be sent to the client!");

참고 항목

SignalR은 Message 예외의 속성만 클라이언트에 보냅니다. 예외에 대한 스택 추적 및 기타 속성은 클라이언트에서 사용할 수 없습니다.

추가 리소스

작성자: Rachel AppelKevin Griffin

SignalR Hubs API를 사용하면 연결된 클라이언트가 서버에서 메서드를 호출할 수 있습니다. 서버는 클라이언트에서 호출되는 메서드를 정의하고 클라이언트는 서버에서 호출되는 메서드를 정의합니다. SignalR는 실시간 클라이언트 간 통신 및 서버 간 통신을 가능하게 하는 데 필요한 모든 것을 처리합니다.

SignalR 허브 구성

SignalR 허브에 필요한 서비스를 등록하려면 Program.cs에서 AddSignalR를 호출합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

SignalR 엔드포인트를 구성하려면 Program.cs에서 MapHub을 호출합니다.

app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");

app.Run();

참고 항목

ASP.NET Core SignalR 서버 쪽 어셈블리는 이제 .NET Core SDK와 함께 설치됩니다. 자세한 내용은 공유 프레임워크의 어셈블리를 참조 SignalR 하세요.

허브 만들기 및 사용

Hub에서 상속되는 클래스를 선언하여 허브를 만듭니다. 클래스에 public 메서드를 추가하여 클라이언트에서 호출할 수 있도록 합니다.

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.SendAsync("ReceiveMessage", user, message);
}

참고 항목

Hubs는 일시적입니다.

  • 허브 클래스의 속성에 상태를 저장하지 마세요. 각 허브 메서드 호출은 새 허브 인스턴스에서 실행됩니다.
  • 종속성 주입을 통해 허브를 직접 인스턴스화하지 마세요. 애플리케이션의 다른 위치에서 클라이언트로 메시지를 보내려면 .IHubContext
  • 활성 상태를 유지하는 허브를 사용하는 비동기 메서드를 호출할 때 await를 사용합니다. 예를 들어 Clients.All.SendAsync(...)와 같은 메서드는 await 없이 호출되고 SendAsync가 완료되기 전에 허브 메서드가 완료되면 실패할 수 있습니다.

컨텍스트 개체

Hub 클래스에는 연결에 대한 정보가 있는 다음 속성을 포함하는 Context 속성이 포함됩니다.

속성 설명
ConnectionId SignalR에서 할당한 연결의 고유 ID를 가져옵니다. 각 연결에 대한 하나의 연결 ID가 있습니다.
UserIdentifier 사용자 식별자를 가져옵니다. 기본적으로 SignalR에서는 연결과 연결된 ClaimsPrincipalClaimTypes.NameIdentifier를 사용자 식별자로 사용합니다.
User 현재 사용자와 연결된 ClaimsPrincipal을 가져옵니다.
Items 이 연결 범위 내에서 데이터를 공유하는 데 사용할 수 있는 키/값 컬렉션을 가져옵니다. 데이터는 이 컬렉션에 저장될 수 있으며 서로 다른 허브 메서드 호출 간의 연결을 위해 유지됩니다.
Features 연결에서 사용할 수 있는 기능 컬렉션을 가져옵니다. 지금은 이 컬렉션이 대부분의 시나리오에서 필요하지 않으므로 아직 자세히 문서화되지 않았습니다.
ConnectionAborted 연결이 중단될 때 알리는 CancellationToken을 가져옵니다.

Hub.Context에는 다음 메서드도 포함되어 있습니다.

메서드 설명
GetHttpContext 연결에 대한 HttpContext를 반환하거나, 연결이 HTTP 요청과 연결되지 않은 경우 null을 반환합니다. HTTP 연결의 경우 이 메서드를 사용하여 HTTP 헤더 및 쿼리 문자열과 같은 정보를 가져옵니다.
Abort 연결을 중단합니다.

클라이언트 개체

Hub 클래스에는 서버와 클라이언트 간의 통신을 위한 다음 속성이 포함된 Clients 속성이 포함됩니다.

속성 설명
All 연결된 모든 클라이언트에서 메서드 호출
Caller 허브 메서드를 호출한 클라이언트에서 메서드를 호출합니다.
Others 메서드를 호출한 클라이언트를 제외한 연결된 모든 클라이언트에서 메서드를 호출합니다.

Hub.Clients에는 다음 메서드도 포함되어 있습니다.

메서드 설명
AllExcept 지정된 연결을 제외한 연결된 모든 클라이언트에서 메서드를 호출합니다.
Client 연결된 특정 클라이언트에서 메서드 호출
Clients 연결된 특정 클라이언트에서 메서드 호출
Group 지정된 그룹의 모든 연결에서 메서드 호출
GroupExcept 지정된 연결을 제외한 지정된 그룹의 모든 연결에서 메서드 호출
Groups 여러 연결 그룹에서 메서드 호출
OthersInGroup 허브 메서드를 호출한 클라이언트를 제외하고 연결 그룹에서 메서드 호출
User 특정 사용자와 연결된 모든 연결에서 메서드 호출
Users 지정된 사용자와 연결된 모든 연결에서 메서드 호출

이전 테이블에 있는 각 속성 또는 메서드는 SendAsync 메서드를 사용하여 개체를 반환합니다. SendAsync 메서드는 호출할 클라이언트 메서드의 이름과 매개 변수를 받습니다.

ClientCaller 메서드에서 반환된 개체에는 클라이언트의 결과를 기다리는 데 사용할 수 있는 InvokeAsync 메서드도 포함되어 있습니다.

클라이언트에게 메시지 보내기

특정 클라이언트를 호출하려면 Clients 개체의 속성을 사용합니다. 다음 예제에는 세 가지 허브 메서드가 있습니다.

  • SendMessageClients.All을 사용하여 연결된 모든 클라이언트에게 메시지를 보냅니다.
  • SendMessageToCallerClients.Caller를 사용하여 호출자에게 메시지를 다시 보냅니다.
  • SendMessageToGroupSignalR Users 그룹의 모든 클라이언트에게 메시지를 보냅니다.
public async Task SendMessage(string user, string message)
    => await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)
    => await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)
    => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);

강력한 형식의 허브

SendAsync 사용의 단점은 호출할 클라이언트 메서드를 지정하기 위해 문자열을 사용한다는 것입니다. 이렇게 하면 메서드 이름의 철자가 잘못되거나 클라이언트에서 누락된 경우 런타임 오류에 대한 코드가 열립니다.

SendAsync를 사용하는 것의 대안은 Hub<T>과 함께 Hub 클래스를 강력한 형식으로 입력하는 것입니다. 다음 예제에서는 ChatHub 클라이언트 메서드가 IChatClient라는 인터페이스로 추출되었습니다.

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

이 인터페이스는 강력한 형식으로 입력하도록 앞의 ChatHub 예제를 리팩터링하는 데 사용할 수 있습니다.

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.ReceiveMessage(user, message);

    public async Task SendMessageToCaller(string user, string message)
        => await Clients.Caller.ReceiveMessage(user, message);

    public async Task SendMessageToGroup(string user, string message)
        => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}

Hub<IChatClient>를 사용하면 클라이언트 메서드의 컴파일 시간 검사를 사용할 수 있습니다. 이렇게 하면 Hub<T>가 인터페이스에 정의된 메서드에만 액세스할 수 있기 때문에 문자열을 사용하여 발생하는 문제를 방지할 수 있습니다. 강력한 형식의 Hub<T>를 사용하면 SendAsync를 사용할 수 없습니다.

참고 항목

Async 접미사는 메서드 이름에서 제거되지 않습니다. 클라이언트 메서드가 .on('MyMethodAsync')로 정의되지 않는 한 MyMethodAsync을 이름으로 사용하지 마세요.

클라이언트 결과

서버는 클라이언트를 호출하는 것 외에도 클라이언트에서 결과를 요청할 수 있습니다. 이렇게 하려면 서버가 ISingleClientProxy.InvokeAsync를 사용하고 클라이언트는 .On 처리기에서 결과를 반환해야 합니다.

서버에서 API를 사용하는 방법에는 두 가지가 있습니다. 첫 번째는 Hub 메서드의 Clients 속성에서 Client(...) 또는 Caller을 호출하는 것입니다.

public class ChatHub : Hub
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        var message = await Clients.Client(connectionId).InvokeAsync<string>(
            "GetMessage");
        return message;
    }
}

두 번째 방법은 IHubContext<T>의 인스턴스에서 Client(...)를 호출하는 것입니다.

async Task SomeMethod(IHubContext<MyHub> context)
{
    string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
        "GetMessage");
}

강력한 형식의 허브는 인터페이스 메서드에서 값을 반환할 수도 있습니다.

public interface IClient
{
    Task<string> GetMessage();
}

public class ChatHub : Hub<IClient>
{
    public async Task<string> WaitForMessage(string connectionId)
    {
        string message = await Clients.Client(connectionId).GetMessage();
        return message;
    }
}

클라이언트는 아래와 같이 .On(...) 처리기에서 결과를 반환합니다.

.NET 클라이언트

hubConnection.On("GetMessage", async () =>
{
    Console.WriteLine("Enter message:");
    var message = await Console.In.ReadLineAsync();
    return message;
});

Typescript 클라이언트

hubConnection.on("GetMessage", async () => {
    let promise = new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve("message");
        }, 100);
    });
    return promise;
});

Java 클라이언트

hubConnection.onWithResult("GetMessage", () -> {
    return Single.just("message");
});

허브 메서드의 이름 변경

기본적으로 서버 허브 메서드 이름은 .NET 메서드의 이름입니다. 특정 메서드에 대해 이 기본 동작을 변경하려면 HubMethodName 특성을 사용합니다. 클라이언트는 메서드를 호출할 때 .NET 메서드 이름 대신 이 이름을 사용해야 합니다.

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
    => await Clients.User(user).SendAsync("ReceiveMessage", user, message);

허브에 서비스 삽입

허브 생성자는 DI의 서비스를 매개 변수로 수락할 수 있으며, 허브 메서드에서 사용하기 위해 클래스의 속성에 저장할 수 있습니다.

다른 허브 메서드 또는 코드를 작성하는 다른 방법으로 여러 서비스를 삽입하는 경우 허브 메서드는 DI의 서비스를 수락할 수도 있습니다. 기본적으로 허브 메서드 매개 변수는 가능한 경우 DI에서 검사 및 확인됩니다.

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message, IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

서비스에서 매개 변수의 암시적 해결을 원하지 않는 경우 DisableImplicitFromServicesParameters를 사용하여 사용하지 않도록 설정합니다. 허브 메서드에서 DI에서 확인되는 매개 변수를 명시적으로 지정하려면 DisableImplicitFromServicesParameters 옵션을 사용하고 DI에서 확인해야 하는 허브 메서드 매개 변수에서 IFromServiceMetadata를 구현하는 [FromServices] 특성 또는 사용자 지정 특성을 사용합니다.

services.AddSingleton<IDatabaseService, DatabaseServiceImpl>();
services.AddSignalR(options =>
{
    options.DisableImplicitFromServicesParameters = true;
});

// ...

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message,
        [FromServices] IDatabaseService dbService)
    {
        var userName = dbService.GetUserName(user);
        return Clients.All.SendAsync("ReceiveMessage", userName, message);
    }
}

참고 항목

이 기능은 선택적으로 DI 구현에 의해 구현되는 IServiceProviderIsService를 사용합니다. 앱의 DI 컨테이너가 이 기능을 지원하지 않는 경우 허브 메서드에 서비스를 삽입하는 것은 지원되지 않습니다.

연결에 대한 이벤트 처리

SignalR Hubs API는 연결을 관리하고 추적하는 OnConnectedAsyncOnDisconnectedAsync 가상 메서드를 제공합니다. 클라이언트가 Hub에 연결할 때 그룹에 추가하는 등의 작업을 수행하도록 OnConnectedAsync 가상 메서드를 재정의합니다.

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

클라이언트 연결이 끊어지면 작업을 수행하도록 OnDisconnectedAsync 가상 메서드를 재정의합니다. connection.stop()를 호출하는 등 클라이언트가 의도적으로 연결을 끊으면 exception 매개 변수가 null로 설정됩니다. 그러나 오류(예: 네트워크 오류)로 인해 클라이언트의 연결이 끊어지면 exception 매개 변수에 오류를 설명하는 예외가 포함됩니다.

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await base.OnDisconnectedAsync(exception);
}

RemoveFromGroupAsyncOnDisconnectedAsync에서 호출할 필요가 없으며 자동으로 처리됩니다.

오류 처리

허브 메서드에서 throw된 예외는 메서드를 호출한 클라이언트로 전송됩니다. JavaScript 클라이언트에서 invoke 메서드는 JavaScriptPromise를 반환합니다. 클라이언트는 반환된 promise에 catch 처리기를 연결하거나 try/catchasync/await와 함께 사용하여 예외를 처리할 수 있습니다.

try {
  await connection.invoke("SendMessage", user, message);
} catch (err) {
  console.error(err);
}

허브가 예외를 throw하는 경우 연결이 닫히지 않습니다. 기본적으로 SignalR는 다음 예제와 같이 클라이언트에 제네릭 오류 메시지를 반환합니다.

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.

예기치 않은 예외에는 데이터베이스 연결이 실패할 때 트리거된 예외의 데이터베이스 서버 이름과 같은 중요한 정보가 포함되어 있는 경우가 많습니다. SignalR에서는 기본적으로 이러한 자세한 오류 메시지를 보안 조치로 노출하지 않습니다. 예외 세부 정보를 표시하지 않는 이유에 대한 자세한 내용은 ASP.NET Core SignalR의 보안 고려 사항 문서를 참조하세요.

예외 조건이 클라이언트에 전파되어야 하는 경우 HubException 클래스를 사용합니다. HubException가 허브 메서드에서 throw되면 SignalR는 수정되지 않은 전체 예외 메시지를 클라이언트에 보냅니다.

public Task ThrowException()
    => throw new HubException("This error will be sent to the client!");

참고 항목

SignalR은 Message 예외의 속성만 클라이언트에 보냅니다. 예외에 대한 스택 추적 및 기타 속성은 클라이언트에서 사용할 수 없습니다.

추가 리소스

작성자: Rachel AppelKevin Griffin

SignalR Hubs API를 사용하면 연결된 클라이언트가 서버에서 메서드를 호출할 수 있습니다. 서버는 클라이언트에서 호출되는 메서드를 정의하고 클라이언트는 서버에서 호출되는 메서드를 정의합니다. SignalR는 실시간 클라이언트 간 통신 및 서버 간 통신을 가능하게 하는 데 필요한 모든 것을 처리합니다.

SignalR 허브 구성

SignalR 허브에 필요한 서비스를 등록하려면 Program.cs에서 AddSignalR를 호출합니다.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();
builder.Services.AddSignalR();

SignalR 엔드포인트를 구성하려면 Program.cs에서 MapHub을 호출합니다.

app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");

app.Run();

참고 항목

ASP.NET Core SignalR 서버 쪽 어셈블리는 이제 .NET Core SDK와 함께 설치됩니다. 자세한 내용은 공유 프레임워크의 어셈블리를 참조 SignalR 하세요.

허브 만들기 및 사용

Hub에서 상속되는 클래스를 선언하여 허브를 만듭니다. 클래스에 public 메서드를 추가하여 클라이언트에서 호출할 수 있도록 합니다.

public class ChatHub : Hub
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.SendAsync("ReceiveMessage", user, message);
}

참고 항목

Hubs는 일시적입니다.

  • 허브 클래스의 속성에 상태를 저장하지 마세요. 각 허브 메서드 호출은 새 허브 인스턴스에서 실행됩니다.
  • 종속성 주입을 통해 허브를 직접 인스턴스화하지 마세요. 애플리케이션의 다른 위치에서 클라이언트로 메시지를 보내려면 .IHubContext
  • 활성 상태를 유지하는 허브를 사용하는 비동기 메서드를 호출할 때 await를 사용합니다. 예를 들어 Clients.All.SendAsync(...)와 같은 메서드는 await 없이 호출되고 SendAsync가 완료되기 전에 허브 메서드가 완료되면 실패할 수 있습니다.

컨텍스트 개체

Hub 클래스에는 연결에 대한 정보가 있는 다음 속성을 포함하는 Context 속성이 포함됩니다.

속성 설명
ConnectionId SignalR에서 할당한 연결의 고유 ID를 가져옵니다. 각 연결에 대한 하나의 연결 ID가 있습니다.
UserIdentifier 사용자 식별자를 가져옵니다. 기본적으로 SignalR에서는 연결과 연결된 ClaimsPrincipalClaimTypes.NameIdentifier를 사용자 식별자로 사용합니다.
User 현재 사용자와 연결된 ClaimsPrincipal을 가져옵니다.
Items 이 연결 범위 내에서 데이터를 공유하는 데 사용할 수 있는 키/값 컬렉션을 가져옵니다. 데이터는 이 컬렉션에 저장될 수 있으며 서로 다른 허브 메서드 호출 간의 연결을 위해 유지됩니다.
Features 연결에서 사용할 수 있는 기능 컬렉션을 가져옵니다. 지금은 이 컬렉션이 대부분의 시나리오에서 필요하지 않으므로 아직 자세히 문서화되지 않았습니다.
ConnectionAborted 연결이 중단될 때 알리는 CancellationToken을 가져옵니다.

Hub.Context에는 다음 메서드도 포함되어 있습니다.

메서드 설명
GetHttpContext 연결에 대한 HttpContext를 반환하거나, 연결이 HTTP 요청과 연결되지 않은 경우 null을 반환합니다. HTTP 연결의 경우 이 메서드를 사용하여 HTTP 헤더 및 쿼리 문자열과 같은 정보를 가져옵니다.
Abort 연결을 중단합니다.

클라이언트 개체

Hub 클래스에는 서버와 클라이언트 간의 통신을 위한 다음 속성이 포함된 Clients 속성이 포함됩니다.

속성 설명
All 연결된 모든 클라이언트에서 메서드 호출
Caller 허브 메서드를 호출한 클라이언트에서 메서드를 호출합니다.
Others 메서드를 호출한 클라이언트를 제외한 연결된 모든 클라이언트에서 메서드를 호출합니다.

Hub.Clients에는 다음 메서드도 포함되어 있습니다.

메서드 설명
AllExcept 지정된 연결을 제외한 연결된 모든 클라이언트에서 메서드를 호출합니다.
Client 연결된 특정 클라이언트에서 메서드 호출
Clients 연결된 특정 클라이언트에서 메서드 호출
Group 지정된 그룹의 모든 연결에서 메서드 호출
GroupExcept 지정된 연결을 제외한 지정된 그룹의 모든 연결에서 메서드 호출
Groups 여러 연결 그룹에서 메서드 호출
OthersInGroup 허브 메서드를 호출한 클라이언트를 제외하고 연결 그룹에서 메서드 호출
User 특정 사용자와 연결된 모든 연결에서 메서드 호출
Users 지정된 사용자와 연결된 모든 연결에서 메서드 호출

이전 테이블에 있는 각 속성 또는 메서드는 SendAsync 메서드를 사용하여 개체를 반환합니다. SendAsync 메서드는 호출할 클라이언트 메서드의 이름과 매개 변수를 받습니다.

클라이언트에게 메시지 보내기

특정 클라이언트를 호출하려면 Clients 개체의 속성을 사용합니다. 다음 예제에는 세 가지 허브 메서드가 있습니다.

  • SendMessageClients.All을 사용하여 연결된 모든 클라이언트에게 메시지를 보냅니다.
  • SendMessageToCallerClients.Caller를 사용하여 호출자에게 메시지를 다시 보냅니다.
  • SendMessageToGroupSignalR Users 그룹의 모든 클라이언트에게 메시지를 보냅니다.
public async Task SendMessage(string user, string message)
    => await Clients.All.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToCaller(string user, string message)
    => await Clients.Caller.SendAsync("ReceiveMessage", user, message);

public async Task SendMessageToGroup(string user, string message)
    => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);

강력한 형식의 허브

SendAsync 사용의 단점은 호출할 클라이언트 메서드를 지정하기 위해 문자열을 사용한다는 것입니다. 이렇게 하면 메서드 이름의 철자가 잘못되거나 클라이언트에서 누락된 경우 런타임 오류에 대한 코드가 열립니다.

SendAsync를 사용하는 것의 대안은 Hub<T>과 함께 Hub 클래스를 강력한 형식으로 입력하는 것입니다. 다음 예제에서는 ChatHub 클라이언트 메서드가 IChatClient라는 인터페이스로 추출되었습니다.

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

이 인터페이스는 강력한 형식으로 입력하도록 앞의 ChatHub 예제를 리팩터링하는 데 사용할 수 있습니다.

public class StronglyTypedChatHub : Hub<IChatClient>
{
    public async Task SendMessage(string user, string message)
        => await Clients.All.ReceiveMessage(user, message);

    public async Task SendMessageToCaller(string user, string message)
        => await Clients.Caller.ReceiveMessage(user, message);

    public async Task SendMessageToGroup(string user, string message)
        => await Clients.Group("SignalR Users").ReceiveMessage(user, message);
}

Hub<IChatClient>를 사용하면 클라이언트 메서드의 컴파일 시간 검사를 사용할 수 있습니다. 이렇게 하면 Hub<T>가 인터페이스에 정의된 메서드에만 액세스할 수 있기 때문에 문자열을 사용하여 발생하는 문제를 방지할 수 있습니다. 강력한 형식의 Hub<T>를 사용하면 SendAsync를 사용할 수 없습니다.

참고 항목

Async 접미사는 메서드 이름에서 제거되지 않습니다. 클라이언트 메서드가 .on('MyMethodAsync')로 정의되지 않는 한 MyMethodAsync을 이름으로 사용하지 마세요.

허브 메서드의 이름 변경

기본적으로 서버 허브 메서드 이름은 .NET 메서드의 이름입니다. 특정 메서드에 대해 이 기본 동작을 변경하려면 HubMethodName 특성을 사용합니다. 클라이언트는 메서드를 호출할 때 .NET 메서드 이름 대신 이 이름을 사용해야 합니다.

[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
    => await Clients.User(user).SendAsync("ReceiveMessage", user, message);

연결에 대한 이벤트 처리

SignalR Hubs API는 연결을 관리하고 추적하는 OnConnectedAsyncOnDisconnectedAsync 가상 메서드를 제공합니다. 클라이언트가 Hub에 연결할 때 그룹에 추가하는 등의 작업을 수행하도록 OnConnectedAsync 가상 메서드를 재정의합니다.

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

클라이언트 연결이 끊어지면 작업을 수행하도록 OnDisconnectedAsync 가상 메서드를 재정의합니다. connection.stop()를 호출하는 등 클라이언트가 의도적으로 연결을 끊으면 exception 매개 변수가 null로 설정됩니다. 그러나 오류(예: 네트워크 오류)로 인해 클라이언트의 연결이 끊어지면 exception 매개 변수에 오류를 설명하는 예외가 포함됩니다.

public override async Task OnDisconnectedAsync(Exception? exception)
{
    await base.OnDisconnectedAsync(exception);
}

RemoveFromGroupAsyncOnDisconnectedAsync에서 호출할 필요가 없으며 자동으로 처리됩니다.

오류 처리

허브 메서드에서 throw된 예외는 메서드를 호출한 클라이언트로 전송됩니다. JavaScript 클라이언트에서 invoke 메서드는 JavaScriptPromise를 반환합니다. 클라이언트는 반환된 promise에 catch 처리기를 연결하거나 try/catchasync/await와 함께 사용하여 예외를 처리할 수 있습니다.

try {
  await connection.invoke("SendMessage", user, message);
} catch (err) {
  console.error(err);
}

허브가 예외를 throw하는 경우 연결이 닫히지 않습니다. 기본적으로 SignalR는 다음 예제와 같이 클라이언트에 제네릭 오류 메시지를 반환합니다.

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.

예기치 않은 예외에는 데이터베이스 연결이 실패할 때 트리거된 예외의 데이터베이스 서버 이름과 같은 중요한 정보가 포함되어 있는 경우가 많습니다. SignalR에서는 기본적으로 이러한 자세한 오류 메시지를 보안 조치로 노출하지 않습니다. 예외 세부 정보를 표시하지 않는 이유에 대한 자세한 내용은 ASP.NET Core SignalR의 보안 고려 사항 문서를 참조하세요.

예외 조건이 클라이언트에 전파되어야 하는 경우 HubException 클래스를 사용합니다. HubException가 허브 메서드에서 throw되면 SignalR는 수정되지 않은 전체 예외 메시지를 클라이언트에 보냅니다.

public Task ThrowException()
    => throw new HubException("This error will be sent to the client!");

참고 항목

SignalR은 Message 예외의 속성만 클라이언트에 보냅니다. 예외에 대한 스택 추적 및 기타 속성은 클라이언트에서 사용할 수 없습니다.

추가 리소스

작성자: Rachel AppelKevin Griffin

샘플 코드 보기 및 다운로드(다운로드 방법)

SignalR 허브의 정의

SignalR Hubs API를 사용하면 서버에서 연결된 클라이언트의 메서드를 호출할 수 있습니다. 서버 코드에서 클라이언트가 호출하는 메서드를 정의합니다. 클라이언트 코드에서 서버가 호출하는 메서드를 정의합니다. SignalR는 실시간 클라이언트-서버 및 서버-클라이언트 간 통신을 가능하게 하는 백그라운드의 모든 것을 처리합니다.

SignalR 허브 구성

SignalR 미들웨어에는 AddSignalR을 호출하여 구성되는 일부 서비스가 필요합니다.

services.AddSignalR();

ASP.NET Core 앱에 SignalR 기능을 추가할 때 Startup.Configure 메서드의 UseEndpoints 콜백에서 MapHub를 호출하여 SignalR 경로를 설정합니다.

app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapHub<ChatHub>("/chathub");
});

참고 항목

ASP.NET Core SignalR 서버 쪽 어셈블리는 이제 .NET Core SDK와 함께 설치됩니다. 자세한 내용은 공유 프레임워크의 어셈블리를 참조 SignalR 하세요.

허브 만들기 및 사용

Hub에서 상속되는 클래스를 선언하여 허브를 만들고 여기에 공용 메서드를 추가합니다. 클라이언트는 public으로 정의된 메서드를 호출할 수 있습니다.

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message)
    {
        return Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

C# 메서드에서와 마찬가지로 복합 형식 및 배열을 포함한 반환 형식 및 매개 변수를 지정할 수 있습니다. SignalR은 매개 변수 및 반환 값에서 복잡한 개체와 배열의 serialization 및 deserialization을 처리합니다.

참고 항목

Hubs는 일시적입니다.

  • 허브 클래스의 속성에 상태를 저장하지 마세요. 모든 허브 메서드 호출은 새 허브 인스턴스에서 실행됩니다.
  • 종속성 주입을 통해 허브를 직접 인스턴스화하지 마세요. 애플리케이션의 다른 위치에서 클라이언트로 메시지를 보내려면 .IHubContext
  • 활성 상태를 유지하는 허브를 사용하는 비동기 메서드를 호출할 때 await를 사용합니다. 예를 들어 Clients.All.SendAsync(...)와 같은 메서드는 await 없이 호출되고 SendAsync가 완료되기 전에 허브 메서드가 완료되면 실패할 수 있습니다.

컨텍스트 개체

Hub 클래스에는 연결에 대한 정보가 있는 다음 속성을 포함하는 Context 속성이 있습니다.

속성 설명
ConnectionId SignalR에서 할당한 연결의 고유 ID를 가져옵니다. 각 연결에 대한 하나의 연결 ID가 있습니다.
UserIdentifier 사용자 식별자를 가져옵니다. 기본적으로 SignalR에서는 연결과 연결된 ClaimsPrincipalClaimTypes.NameIdentifier를 사용자 식별자로 사용합니다.
User 현재 사용자와 연결된 ClaimsPrincipal을 가져옵니다.
Items 이 연결 범위 내에서 데이터를 공유하는 데 사용할 수 있는 키/값 컬렉션을 가져옵니다. 데이터는 이 컬렉션에 저장될 수 있으며 서로 다른 허브 메서드 호출 간의 연결을 위해 유지됩니다.
Features 연결에서 사용할 수 있는 기능 컬렉션을 가져옵니다. 지금은 이 컬렉션이 대부분의 시나리오에서 필요하지 않으므로 아직 자세히 문서화되지 않았습니다.
ConnectionAborted 연결이 중단될 때 알리는 CancellationToken을 가져옵니다.

Hub.Context에는 다음 메서드도 포함되어 있습니다.

메서드 설명
GetHttpContext 연결에 대한 HttpContext를 반환하거나, 연결이 HTTP 요청과 연결되지 않은 경우 null을 반환합니다. HTTP 연결의 경우 이 메서드를 사용하여 HTTP 헤더 및 쿼리 문자열과 같은 정보를 가져올 수 있습니다.
Abort 연결을 중단합니다.

클라이언트 개체

Hub 클래스에는 서버와 클라이언트 간의 통신을 위한 다음 속성이 포함된 Clients 속성이 있습니다.

속성 설명
All 연결된 모든 클라이언트에서 메서드 호출
Caller 허브 메서드를 호출한 클라이언트에서 메서드를 호출합니다.
Others 메서드를 호출한 클라이언트를 제외한 연결된 모든 클라이언트에서 메서드를 호출합니다.

Hub.Clients에는 다음 메서드도 포함되어 있습니다.

메서드 설명
AllExcept 지정된 연결을 제외한 연결된 모든 클라이언트에서 메서드를 호출합니다.
Client 연결된 특정 클라이언트에서 메서드 호출
Clients 연결된 특정 클라이언트에서 메서드 호출
Group 지정된 그룹의 모든 연결에서 메서드 호출
GroupExcept 지정된 연결을 제외한 지정된 그룹의 모든 연결에서 메서드 호출
Groups 여러 연결 그룹에서 메서드 호출
OthersInGroup 허브 메서드를 호출한 클라이언트를 제외하고 연결 그룹에서 메서드 호출
User 특정 사용자와 연결된 모든 연결에서 메서드 호출
Users 지정된 사용자와 연결된 모든 연결에서 메서드 호출

이전 테이블에 있는 각 속성 또는 메서드는 SendAsync 메서드를 사용하여 개체를 반환합니다. SendAsync 메서드를 사용하면 호출할 클라이언트 메서드의 이름과 매개 변수를 제공할 수 있습니다.

클라이언트에게 메시지 보내기

특정 클라이언트를 호출하려면 Clients 개체의 속성을 사용합니다. 다음 예제에는 세 가지 Hub 메서드가 있습니다.

  • SendMessageClients.All을 사용하여 연결된 모든 클라이언트에게 메시지를 보냅니다.
  • SendMessageToCallerClients.Caller를 사용하여 호출자에게 메시지를 다시 보냅니다.
  • SendMessageToGroupSignalR Users 그룹의 모든 클라이언트에게 메시지를 보냅니다.
public Task SendMessage(string user, string message)
{
    return Clients.All.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToCaller(string user, string message)
{
    return Clients.Caller.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToGroup(string user, string message)
{
    return Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
}

강력한 형식의 허브

SendAsync 사용의 단점은 호출할 클라이언트 메서드를 지정하기 위해 매직 문자열을 사용한다는 것입니다. 이렇게 하면 메서드 이름의 철자가 잘못되거나 클라이언트에서 누락된 경우 런타임 오류에 대한 코드가 열립니다.

SendAsync를 사용하는 것의 대안은 Hub<T>과 함께 Hub를 강력한 형식으로 입력하는 것입니다. 다음 예제에서는 ChatHub 클라이언트 메서드가 IChatClient라는 인터페이스로 추출되었습니다.

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

이 인터페이스는 앞의 ChatHub 예제를 리팩터링하는 데 사용할 수 있습니다.

    public class StronglyTypedChatHub : Hub<IChatClient>
    {
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.ReceiveMessage(user, message);
        }

        public Task SendMessageToCaller(string user, string message)
        {
            return Clients.Caller.ReceiveMessage(user, message);
        }
}

Hub<IChatClient>를 사용하면 클라이언트 메서드의 컴파일 시간 검사를 사용할 수 있습니다. 이렇게 하면 Hub<T>가 인터페이스에 정의된 메서드에만 액세스할 수 있기 때문에 매직 문자열을 사용하여 발생하는 문제를 방지할 수 있습니다.

강력한 형식의 Hub<T>를 사용하면 SendAsync를 사용할 수 없습니다. 인터페이스에 정의된 모든 메서드는 여전히 비동기식으로 정의할 수 있습니다. 실제로 이러한 각 메서드는 Task를 반환해야 합니다. 인터페이스이므로 async 키워드를 사용하지 마세요. 예시:

public interface IClient
{
    Task ClientMethod();
}

참고 항목

Async 접미사는 메서드 이름에서 제거되지 않습니다. 클라이언트 메서드가 .on('MyMethodAsync')로 정의되지 않는 한 MyMethodAsync를 이름으로 사용해서는 안 됩니다.

허브 메서드의 이름 변경

기본적으로 서버 허브 메서드 이름은 .NET 메서드의 이름입니다. 그러나 HubMethodName 특성을 사용하여 이 기본값을 변경하고 메서드의 이름을 수동으로 지정할 수 있습니다. 클라이언트는 메서드를 호출할 때 .NET 메서드 이름 대신 이 이름을 사용해야 합니다.

[HubMethodName("SendMessageToUser")]
public Task DirectMessage(string user, string message)
{
    return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}

연결에 대한 이벤트 처리

SignalR Hubs API는 연결을 관리하고 추적하는 OnConnectedAsyncOnDisconnectedAsync 가상 메서드를 제공합니다. 클라이언트가 Hub에 연결할 때 그룹에 추가하는 등의 작업을 수행하도록 OnConnectedAsync 가상 메서드를 재정의합니다.

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

클라이언트 연결이 끊어지면 작업을 수행하도록 OnDisconnectedAsync 가상 메서드를 재정의합니다. 클라이언트가 의도적으로 연결을 끊는 경우(예: connection.stop()를 호출하여) exception 매개 변수는 null이 됩니다. 그러나 오류(예: 네트워크 오류)로 인해 클라이언트의 연결이 끊어지면 exception 매개 변수에 오류를 설명하는 예외가 포함됩니다.

public override async Task OnDisconnectedAsync(Exception exception)
{
    await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", "I", "disconnect");
    await base.OnDisconnectedAsync(exception);
}

RemoveFromGroupAsyncOnDisconnectedAsync에서 호출할 필요가 없으며 자동으로 처리됩니다.

Warning

보안 경고: SignalR 서버 또는 클라이언트 버전이 ASP.NET Core 2.2 이하인 경우 ConnectionId를 노출하면 악의적인 가장이 발생할 수 있습니다.

오류 처리

허브 메서드에서 throw된 예외는 메서드를 호출한 클라이언트로 전송됩니다. JavaScript 클라이언트에서 invoke 메서드는 JavaScriptPromise를 반환합니다. 클라이언트가 catch를 사용하여 promise에 연결된 처리기가 있는 오류를 수신하면 이 처리기가 호출되고 JavaScript Error 개체로 전달됩니다.

connection.invoke("SendMessage", user, message).catch(err => console.error(err));

허브가 예외를 throw하는 경우 연결이 닫히지 않습니다. 기본적으로 SignalR는 일반 오류 메시지를 클라이언트에 반환합니다. 예시:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'MethodName' on the server.

예기치 않은 예외에는 데이터베이스 연결이 실패할 때 트리거된 예외의 데이터베이스 서버 이름과 같은 중요한 정보가 포함되어 있는 경우가 많습니다. SignalR에서는 기본적으로 이러한 자세한 오류 메시지를 보안 조치로 노출하지 않습니다. 예외 세부 정보를 표시하지 않는 이유에 대한 자세한 내용은 ASP.NET Core SignalR의 보안 고려 사항 문서를 참조하세요.

클라이언트에 전파하려는 예외 조건이 있는 경우 HubException 클래스를 사용할 수 있습니다. 허브 메서드에서 HubException을 throw하는 경우 SignalR은 수정되지 않은 상태로 전체 메시지를 클라이언트에 보냅니다.

public Task ThrowException()
{
    throw new HubException("This error will be sent to the client!");
}

참고 항목

SignalR은 Message 예외의 속성만 클라이언트에 보냅니다. 예외에 대한 스택 추적 및 기타 속성은 클라이언트에서 사용할 수 없습니다.

추가 리소스

작성자: Rachel AppelKevin Griffin

샘플 코드 보기 및 다운로드(다운로드 방법)

SignalR 허브의 정의

SignalR Hubs API를 사용하면 서버에서 연결된 클라이언트의 메서드를 호출할 수 있습니다. 서버 코드에서 클라이언트가 호출하는 메서드를 정의합니다. 클라이언트 코드에서 서버가 호출하는 메서드를 정의합니다. SignalR는 실시간 클라이언트-서버 및 서버-클라이언트 간 통신을 가능하게 하는 백그라운드의 모든 것을 처리합니다.

SignalR 허브 구성

SignalR 미들웨어에는 AddSignalR을 호출하여 구성되는 일부 서비스가 필요합니다.

services.AddSignalR();

ASP.NET Core 앱에 SignalR 기능을 추가할 때 Startup.Configure 메서드의 UseSignalR을 호출하여 SignalR 경로를 설정합니다.

app.UseSignalR(route =>
{
    route.MapHub<ChatHub>("/chathub");
});

허브 만들기 및 사용

Hub에서 상속되는 클래스를 선언하여 허브를 만들고 여기에 공용 메서드를 추가합니다. 클라이언트는 public으로 정의된 메서드를 호출할 수 있습니다.

public class ChatHub : Hub
{
    public Task SendMessage(string user, string message)
    {
        return Clients.All.SendAsync("ReceiveMessage", user, message);
    }
}

C# 메서드에서와 마찬가지로 복합 형식 및 배열을 포함한 반환 형식 및 매개 변수를 지정할 수 있습니다. SignalR은 매개 변수 및 반환 값에서 복잡한 개체와 배열의 serialization 및 deserialization을 처리합니다.

참고 항목

Hubs는 일시적입니다.

  • 허브 클래스의 속성에 상태를 저장하지 마세요. 모든 허브 메서드 호출은 새 허브 인스턴스에서 실행됩니다.
  • 종속성 주입을 통해 허브를 직접 인스턴스화하지 마세요. 애플리케이션의 다른 위치에서 클라이언트로 메시지를 보내려면 .IHubContext
  • 활성 상태를 유지하는 허브를 사용하는 비동기 메서드를 호출할 때 await를 사용합니다. 예를 들어 Clients.All.SendAsync(...)와 같은 메서드는 await 없이 호출되고 SendAsync가 완료되기 전에 허브 메서드가 완료되면 실패할 수 있습니다.

컨텍스트 개체

Hub 클래스에는 연결에 대한 정보가 있는 다음 속성을 포함하는 Context 속성이 있습니다.

속성 설명
ConnectionId SignalR에서 할당한 연결의 고유 ID를 가져옵니다. 각 연결에 대한 하나의 연결 ID가 있습니다.
UserIdentifier 사용자 식별자를 가져옵니다. 기본적으로 SignalR에서는 연결과 연결된 ClaimsPrincipalClaimTypes.NameIdentifier를 사용자 식별자로 사용합니다.
User 현재 사용자와 연결된 ClaimsPrincipal을 가져옵니다.
Items 이 연결 범위 내에서 데이터를 공유하는 데 사용할 수 있는 키/값 컬렉션을 가져옵니다. 데이터는 이 컬렉션에 저장될 수 있으며 서로 다른 허브 메서드 호출 간의 연결을 위해 유지됩니다.
Features 연결에서 사용할 수 있는 기능 컬렉션을 가져옵니다. 지금은 이 컬렉션이 대부분의 시나리오에서 필요하지 않으므로 아직 자세히 문서화되지 않았습니다.
ConnectionAborted 연결이 중단될 때 알리는 CancellationToken을 가져옵니다.

Hub.Context에는 다음 메서드도 포함되어 있습니다.

메서드 설명
GetHttpContext 연결에 대한 HttpContext를 반환하거나, 연결이 HTTP 요청과 연결되지 않은 경우 null을 반환합니다. HTTP 연결의 경우 이 메서드를 사용하여 HTTP 헤더 및 쿼리 문자열과 같은 정보를 가져올 수 있습니다.
Abort 연결을 중단합니다.

클라이언트 개체

Hub 클래스에는 서버와 클라이언트 간의 통신을 위한 다음 속성이 포함된 Clients 속성이 있습니다.

속성 설명
All 연결된 모든 클라이언트에서 메서드 호출
Caller 허브 메서드를 호출한 클라이언트에서 메서드를 호출합니다.
Others 메서드를 호출한 클라이언트를 제외한 연결된 모든 클라이언트에서 메서드를 호출합니다.

Hub.Clients에는 다음 메서드도 포함되어 있습니다.

메서드 설명
AllExcept 지정된 연결을 제외한 연결된 모든 클라이언트에서 메서드를 호출합니다.
Client 연결된 특정 클라이언트에서 메서드 호출
Clients 연결된 특정 클라이언트에서 메서드 호출
Group 지정된 그룹의 모든 연결에서 메서드 호출
GroupExcept 지정된 연결을 제외한 지정된 그룹의 모든 연결에서 메서드 호출
Groups 여러 연결 그룹에서 메서드 호출
OthersInGroup 허브 메서드를 호출한 클라이언트를 제외하고 연결 그룹에서 메서드 호출
User 특정 사용자와 연결된 모든 연결에서 메서드 호출
Users 지정된 사용자와 연결된 모든 연결에서 메서드 호출

이전 테이블에 있는 각 속성 또는 메서드는 SendAsync 메서드를 사용하여 개체를 반환합니다. SendAsync 메서드를 사용하면 호출할 클라이언트 메서드의 이름과 매개 변수를 제공할 수 있습니다.

클라이언트에게 메시지 보내기

특정 클라이언트를 호출하려면 Clients 개체의 속성을 사용합니다. 다음 예제에는 세 가지 Hub 메서드가 있습니다.

  • SendMessageClients.All을 사용하여 연결된 모든 클라이언트에게 메시지를 보냅니다.
  • SendMessageToCallerClients.Caller를 사용하여 호출자에게 메시지를 다시 보냅니다.
  • SendMessageToGroupSignalR Users 그룹의 모든 클라이언트에게 메시지를 보냅니다.
public Task SendMessage(string user, string message)
{
    return Clients.All.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToCaller(string user, string message)
{
    return Clients.Caller.SendAsync("ReceiveMessage", user, message);
}

public Task SendMessageToGroup(string user, string message)
{
    return Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message);
}

강력한 형식의 허브

SendAsync 사용의 단점은 호출할 클라이언트 메서드를 지정하기 위해 매직 문자열을 사용한다는 것입니다. 이렇게 하면 메서드 이름의 철자가 잘못되거나 클라이언트에서 누락된 경우 런타임 오류에 대한 코드가 열립니다.

SendAsync를 사용하는 것의 대안은 Hub<T>과 함께 Hub를 강력한 형식으로 입력하는 것입니다. 다음 예제에서는 ChatHub 클라이언트 메서드가 IChatClient라는 인터페이스로 추출되었습니다.

public interface IChatClient
{
    Task ReceiveMessage(string user, string message);
}

이 인터페이스는 앞의 ChatHub 예제를 리팩터링하는 데 사용할 수 있습니다.

    public class StronglyTypedChatHub : Hub<IChatClient>
    {
        public async Task SendMessage(string user, string message)
        {
            await Clients.All.ReceiveMessage(user, message);
        }

        public Task SendMessageToCaller(string user, string message)
        {
            return Clients.Caller.ReceiveMessage(user, message);
        }
}

Hub<IChatClient>를 사용하면 클라이언트 메서드의 컴파일 시간 검사를 사용할 수 있습니다. 이렇게 하면 Hub<T>가 인터페이스에 정의된 메서드에만 액세스할 수 있기 때문에 매직 문자열을 사용하여 발생하는 문제를 방지할 수 있습니다.

강력한 형식의 Hub<T>를 사용하면 SendAsync를 사용할 수 없습니다. 인터페이스에 정의된 모든 메서드는 여전히 비동기식으로 정의할 수 있습니다. 실제로 이러한 각 메서드는 Task를 반환해야 합니다. 인터페이스이므로 async 키워드를 사용하지 마세요. 예시:

public interface IClient
{
    Task ClientMethod();
}

참고 항목

Async 접미사는 메서드 이름에서 제거되지 않습니다. 클라이언트 메서드가 .on('MyMethodAsync')로 정의되지 않는 한 MyMethodAsync를 이름으로 사용해서는 안 됩니다.

허브 메서드의 이름 변경

기본적으로 서버 허브 메서드 이름은 .NET 메서드의 이름입니다. 그러나 HubMethodName 특성을 사용하여 이 기본값을 변경하고 메서드의 이름을 수동으로 지정할 수 있습니다. 클라이언트는 메서드를 호출할 때 .NET 메서드 이름 대신 이 이름을 사용해야 합니다.

[HubMethodName("SendMessageToUser")]
public Task DirectMessage(string user, string message)
{
    return Clients.User(user).SendAsync("ReceiveMessage", user, message);
}

연결에 대한 이벤트 처리

SignalR Hubs API는 연결을 관리하고 추적하는 OnConnectedAsyncOnDisconnectedAsync 가상 메서드를 제공합니다. 클라이언트가 Hub에 연결할 때 그룹에 추가하는 등의 작업을 수행하도록 OnConnectedAsync 가상 메서드를 재정의합니다.

public override async Task OnConnectedAsync()
{
    await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
    await base.OnConnectedAsync();
}

클라이언트 연결이 끊어지면 작업을 수행하도록 OnDisconnectedAsync 가상 메서드를 재정의합니다. 클라이언트가 의도적으로 연결을 끊는 경우(예: connection.stop()를 호출하여) exception 매개 변수는 null이 됩니다. 그러나 오류(예: 네트워크 오류)로 인해 클라이언트의 연결이 끊어지면 exception 매개 변수에 오류를 설명하는 예외가 포함됩니다.

public override async Task OnDisconnectedAsync(Exception exception)
{
    await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", "I", "disconnect");
    await base.OnDisconnectedAsync(exception);
}

RemoveFromGroupAsyncOnDisconnectedAsync에서 호출할 필요가 없으며 자동으로 처리됩니다.

Warning

보안 경고: SignalR 서버 또는 클라이언트 버전이 ASP.NET Core 2.2 이하인 경우 ConnectionId를 노출하면 악의적인 가장이 발생할 수 있습니다.

오류 처리

허브 메서드에서 throw된 예외는 메서드를 호출한 클라이언트로 전송됩니다. JavaScript 클라이언트에서 invoke 메서드는 JavaScriptPromise를 반환합니다. 클라이언트가 catch를 사용하여 promise에 연결된 처리기가 있는 오류를 수신하면 이 처리기가 호출되고 JavaScript Error 개체로 전달됩니다.

connection.invoke("SendMessage", user, message).catch(err => console.error(err));

허브가 예외를 throw하는 경우 연결이 닫히지 않습니다. 기본적으로 SignalR는 일반 오류 메시지를 클라이언트에 반환합니다. 예시:

Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'MethodName' on the server.

예기치 않은 예외에는 데이터베이스 연결이 실패할 때 트리거된 예외의 데이터베이스 서버 이름과 같은 중요한 정보가 포함되어 있는 경우가 많습니다. SignalR에서는 기본적으로 이러한 자세한 오류 메시지를 보안 조치로 노출하지 않습니다. 예외 세부 정보를 표시하지 않는 이유에 대한 자세한 내용은 ASP.NET Core SignalR의 보안 고려 사항 문서를 참조하세요.

클라이언트에 전파하려는 예외 조건이 있는 경우 HubException 클래스를 사용할 수 있습니다. 허브 메서드에서 HubException을 throw하는 경우 SignalR은 수정되지 않은 상태로 전체 메시지를 클라이언트에 보냅니다.

public Task ThrowException()
{
    throw new HubException("This error will be sent to the client!");
}

참고 항목

SignalR은 Message 예외의 속성만 클라이언트에 보냅니다. 예외에 대한 스택 추적 및 기타 속성은 클라이언트에서 사용할 수 없습니다.

추가 리소스