ASP.NET Core SignalR을 사용한 실시간 웹앱
이 항목에서는 두 가지 모두에 대한 애플리케이션을 만드는 방법을 살펴보겠습니다. 스트리밍 그리고 전수 데이터 ASP.NET Core SignalR.
필요한 것:
- ASP.NET Core 및 Angular에 대한 기본 지식입니다.
- .NET Core 3.1이 설치되어 있고 Visual Studio와 같은 IDE가 설치되어 있습니다.
이 글이 끝나면 알게 될 내용은 다음과 같습니다.
- SignalR을 추가하고 사용하는 방법.
- 클라이언트 연결을 열고 메소드 호출 개념을 사용하여 클라이언트별로 데이터를 스트리밍하는 방법.
- Observable을 사용하여 Angular 애플리케이션과 함께 SignalR 서비스를 사용하는 방법입니다.
SignalR은 여러 전송을 활용하고 클라이언트와 서버의 기능(WebSocket, 서버 전송 이벤트 또는 Long-polling)에 따라 사용 가능한 최상의 전송을 자동으로 선택합니다.
클라이언트가 서버에 실시간으로 연결되어 있을 때 WebSocket (SSE 및 Long-polling 제외) 측면에서 이야기할 때, 어떤 일이 발생할 때마다 서버는 해당 WebSocket을 통해 클라이언트에 다시 메시지를 보내는 것을 알게 됩니다. 구식 클라이언트 및 서버에서는 Long-polling 전송이 사용됩니다.
이것이 SignalR이 최신 클라이언트와 서버를 처리하는 방법이며, 사용 가능한 경우 내부적으로 WebSocket을 사용하고 그렇지 않은 경우 다른 기술 및 기술로 우아하게 대체됩니다.
이는 악수와 같습니다. 클라이언트와 서버는 무엇을 사용할지에 동의하고 이를 사용합니다. 이를 프로세스 협상 이라고 합니다.
SignalR Example
이 데모의 목적은 ASP.NET Core SignalR을 사용하는 실시간 데이터 스트림이 포함된 금융 스크린 보드를 선보이는 것입니다.
SignalR Server Configuration
Create ASP.NET Core App
ASP.NET Core SignalR 애플리케이션을 설정하는 방법을 살펴보겠습니다. Visual Studio의 파일 >> 새 프로젝트에서 ASP.NET Core 웹 애플리케이션을 선택하고 설정을 따릅니다. 구성에 어려움이 있는 경우 공식 Microsoft 설명서 튜토리얼을 따르십시오.
SignalR Config Setup
Startup.cs 파일에 다음 코드를 추가합니다.
-
Configure
메서드의 끝점 부분입니다.
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapHub<StreamHub>("/streamHub");
});
-
ConfigureServices
메서드에 SignalR 사용을 추가합니다.
services.AddSignalR(options =>
{
options.EnableDetailedErrors = true;
});
위의 변경 사항은 ASP.NET Core 종속성 주입 및 라우팅 시스템에 SignalR을 추가하는 것입니다.
이제 추가적인 기본 구성을 설정해 보겠습니다. Properties/launchSettings.json 파일을 열고 그에 따라 수정합니다.
"profiles": {
"WebAPI": {
"commandName": "Project",
"launchBrowser": false,
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
우리의 서버측 프로젝트는 localhost:5001
에서 실행되고 클라이언트측은 localhost:4200
에서 실행됩니다. 따라서 이 둘 사이의 통신을 설정하려면 CORS를 활성화해야 합니다. Startup.cs 클래스를 열고 수정해 보겠습니다.
public void ConfigureServices(IServiceCollection services)
{
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.WithOrigins("http://localhost:4200"));
});
...
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
...
app.UseCors("CorsPolicy");
...
원본 간 리소스 공유를 활성화하는 데 특정 문제가 발생하는 경우 공식 Microsoft 항목을 확인하세요.
SignalR Hub Setup
SignalR 허브가 무엇인지부터 설명해 보겠습니다. SignalR Hub API를 사용하면 서버에서 연결된 클라이언트의 메서드를 호출할 수 있습니다. 서버 코드에서는 클라이언트가 호출하는 메서드를 정의합니다. SignalR에는 Invocation 이라는 개념이 있습니다. 실제로 특정 메서드를 사용하여 클라이언트에서 허브를 호출할 수 있습니다. 클라이언트 코드에서는 서버에서 호출되는 메서드를 정의합니다.
실제 허브는 서버 측에 있습니다. 클라이언트가 있고 그 사이에 허브가 있다고 상상해 보세요. 허브에서 메소드를 호출하여 Clients.All.doWork()
사용하여 모든 클라이언트에게 무언가를 말할 수 있습니다. 이는 연결된 모든 클라이언트로 이동됩니다. 또한 특정 메서드의 호출자이기 때문에 호출자라는 단 하나의 클라이언트와만 통신할 수 있습니다.
연결, 그룹 및 메시징 관리를 담당하는 기본 Hub 클래스를 상속하는 StreamHub 클래스를 만들었습니다. Hub 클래스는 상태 비저장이며 특정 메서드를 새로 호출할 때마다 이 클래스의 새 인스턴스에 있다는 점을 명심하는 것이 좋습니다. 인스턴스 속성에 상태를 저장하는 것은 쓸모가 없습니다. 오히려 정적 속성을 사용하는 것이 좋습니다. 우리의 경우 정적 키-값 쌍 컬렉션을 사용하여 연결된 각 클라이언트에 대한 데이터를 저장합니다.
이 클래스의 다른 유용한 속성은 Clients, Context 및 Groups 입니다. 고유한 ConnectionID를 기반으로 특정 동작을 관리하는 데 도움이 될 수 있습니다. 또한 이 클래스는 다음과 같은 유용한 메서드를 제공합니다.
- OnConnectedAsync() - 허브와 새 연결이 설정되면 호출됩니다.
- OnDisconnectedAsync(Exception) - 허브와의 연결이 종료되면 호출됩니다.
이를 통해 연결이 설정되거나 닫힐 때 추가 논리를 수행할 수 있습니다. 우리 애플리케이션에는 컨텍스트 연결 ID를 가져오고 이를 사용하여 특정 간격으로 데이터를 다시 보내는 UpdateParameters 메서드도 추가했습니다. 보시다시피 우리는 다른 클라이언트의 스트리밍 개입을 방지하는 고유한 ConnectionID를 통해 통신합니다.
public async void UpdateParameters(int interval, int volume, bool live = false, bool updateAll = true)
{
...
var connection = Context.ConnectionId;
var clients = Clients;
...
if (!clientConnections.ContainsKey(connection))
{
clientConnections.Add(connection, new TimerManager(async() =>
{
...
await Send(newDataArray, client, connection);
}, interval));
} else
{
clientConnections[connection].Stop();
clientConnections[connection] = new TimerManager(async () =>
{
var client = clients.Client(connection);
..
await Send(newDataArray, client, connection);
}, interval);
}
...
}
데이터가 준비되면 SendAsync
메서드를 사용하여 transferdata
이벤트를 내보내 데이터를 전송합니다.
public async Task Send(FinancialData[] array, IClientProxy client, string connection)
{
await client.SendAsync("transferdata", array);
}
...
// Called when a connection with the hub is terminated
public override Task OnDisconnectedAsync(Exception exception)
{
StopTimer();
clientConnections.Remove(Context.ConnectionId);
return base.OnDisconnectedAsync(exception);
}
클라이언트 애플리케이션은 등록된 이벤트를 수신합니다.
private registerSignalEvents() {
this.hubConnection.onclose(() => {
this.hasRemoteConnection = false;
});
this.hubConnection.on('transferdata', (data) => {
this.data.next(data);
})
}
ASP.NET Core 애플리케이션의 공개 GitHub 리포지토리는 여기에서 찾을 수 있습니다.
Create SignalR Client Library
SignalR 서비스를 사용하기 위해 Angular 프로젝트를 생성하겠습니다. 실제 애플리케이션이 포함된 Github 저장소는 여기에서 찾을 수 있습니다.
First, start by installing SignalR:
npm install @microsoft/signalr
HTTP 요청을 서버로 보낼 것이므로 HttpClientModule도 필요하다는 점을 명심하세요.
아래에서는 허브 연결 빌더를 처리하는 signal-r.service.ts 파일을 찾을 수 있습니다.
export class SignalRService implements OnDestroy {
public data: BehaviorSubject<any[]>;
public hasRemoteConnection: boolean;
private hubConnection: signalR.HubConnection;
...
constructor(private zone: NgZone, private http: HttpClient) {
this.data = new BehaviorSubject([]);
}
...
// Start Hub Connection and Register events
public startConnection = (interval = 500, volume = 1000, live = false, updateAll = true) => {
this.hubConnection = new signalR.HubConnectionBuilder()
.configureLogging(signalR.LogLevel.Trace)
.withUrl('https://ko.infragistics.com/angular-apis/webapi/streamHub')
.build();
this.hubConnection
.start()
.then(() => {
...
this.registerSignalEvents();
this.broadcastParams(interval, volume, live, updateAll);
})
.catch(() => { ... });
}
// Change the broadcast parameters like frequency and data volume
public broadcastParams = (frequency, volume, live, updateAll = true) => {
this.hubConnection.invoke('updateparameters', frequency, volume, live, updateAll)
.then(() => console.log('requestLiveData', volume))
.catch(err => {
console.error(err);
});
}
// Register events
private registerSignalEvents() {
this.hubConnection.onclose(() => {
this.hasRemoteConnection = false;
});
this.hubConnection.on('transferdata', (data) => {
this.data.next(data);
});
}
...
app.comComponent에서 새로 생성된 startConnection
메소드를 사용하세요.
constructor(public dataService: SignalRService) {}
public ngOnInit() {
this.dataService.startConnection(this.frequency, this.dataVolume, true, false);
}
...
Grid Data Binding
지금까지 클라이언트 코드에서 살펴본 것처럼 업데이트된 데이터 배열을 인수로 받는 transferdata
이벤트에 대한 리스너를 설정했습니다. 새로 수신된 데이터를 그리드에 전달하기 위해 우리는 Observable을 사용합니다. 이를 설정하려면 다음과 같이 그리드의 데이터 소스를 관찰 가능한 데이터에 바인딩해야 합니다.
<igx-grid [data]='data | async'> ... </igx-grid>
서버에서 클라이언트로 새로운 데이터가 수신될 때마다 관찰 가능한 데이터의 next()
메서드를 호출합니다.
this.hubConnection.on('transferdata', (data) => {
this.data.next(data);
})
Topic Takeaways
애플리케이션을 새로 고치지 않고 데이터가 업데이트되는 시기만 확인하려면 ASP.NET Core SignalR을 고려해야 합니다. 데이터가 크다고 생각되거나 끝없는 스피너를 표시하여 클라이언트를 차단하지 않고 원활한 사용자 경험을 원한다면 스트리밍 콘텐츠를 사용하는 것이 좋습니다.
SignalR Hub 통신을 사용하는 것은 쉽고 직관적이며 Angular Observables의 도움으로 WebSocket과 함께 데이터 스트리밍을 사용하는 강력한 애플리케이션을 만들 수 있습니다.