# .Net WebApi - SignalR & Angular

## Source code

Code available at [GitHub](https://github.com/mat-czernek/angular-signalR)

## About SignalR

Full documentation available [here](https://learn.microsoft.com/en-us/aspnet/signalr/overview/getting-started/introduction-to-signalr)

### Fundamental concepts

SignalR acts as an abstraction layer over remote procedure calls (RPC), streamlining their use. It primarily employs the WebSocket network protocol, but can fallback to other transport protocols like HTML5 or Comet when necessary. A key concept in SignalR is the Hub, which facilitates communication between clients and the server, allowing clients to invoke server methods and vice versa. Hubs are transient, meaning they should not store state. To call methods outside a hub, IHubContext is used. SignalR supports strongly typed hubs, reducing the risk of runtime errors - there is no need to use strings for method names. Hubs can target all connected clients, specific connections, groups, or individual users. Additionally, SignalR integrates with ASP.NET Core authentication, associating users with each connection. Authentication data is accessible within the hub via HubConnectionContext.User, similar to WebApi.

### Supported platforms

Server side:

* any server platform that ASP.NET Core supports
    

Client side:

* JavaScript/TypeScript
    
* .NET runs on ASP.NET Core
    
* Java
    
* Swift
    

## Use cases from sample project

### Retrieving status of executed tasks with single client

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748289044542/21537377-9818-41c9-9fe3-d8df6285187c.gif align="center")

### Retrieving status of executed tasks with two clients plus run request that returns result only to caller

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748289636662/d09f4f38-3f30-4623-bdb0-ee2f5e44d596.gif align="center")

### Client executes method on server side to get number of running tasks

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748376986654/ea8eeeca-b896-45e8-9fe9-d50754c2c460.gif align="center")

## Requirements

### .NET

Install SignalR NuGet package

```plaintext
Microsoft.AspNetCore.SignalR
```

### Angular

Install SignalR npm package

```plaintext
npm install @microsoft/signalr
```

## Configuration

This section describes the application setup details related specifically to SignalR - surprisingly there is not much to do in scope of configuring the app. Things like CORS, HTTPS redirection, etc. are not covered here. For sake of the sample project simplicity the authorization concept was ignored.

### .NET

> Program.cs

```csharp
// ...
// Add SignalR services
builder.Services.AddSignalR();

// ...
// Map incoming requests with a specific hub
app.MapHub<TasksHub>("/tasksHub");

app.Run();

// ...
```

## Core parts of the sample project

### .NET side

`webapi/Api/TasksApi.Core/TasksHub.cs`

Contract for strongly typed Hub

```csharp
public interface ITasksStatusClient
{
    // Called on client-side by server
    Task TasksStatuses(IReadOnlyCollection<TaskDto> tasks);

    // Called on client-side by server
    Task TaskStatus(TaskDto task);

    // Called on server-side by clients
    Task<int> RunningTasksCount(int tasksCount);
}
```

Hub implementation

```csharp
public class TasksHub : Hub<ITasksStatusClient>
{
    private readonly ITasksStatusService _taskStatusService;

    // Task service injected into the Hub to fetch data from in-memory storage
    public TasksHub(ITasksStatusService taskStatusService)
    {
        _taskStatusService = taskStatusService ?? throw new ArgumentNullException(nameof(taskStatusService));
    }
    
    // This method can be called by clients
    public async Task<int> RunningTasksCount()
    {
        return await Task.FromResult(_taskStatusService.RunningTasksCount);
    }
}
```

`webapi/Api/TasksApi.Core/TasksStatusService.cs`

Example of calling the method on client side

```csharp
// Call method for all clients
public Task ExecuteTask(TaskDto task)
{
	return ExecuteTaskInternal(task, (t) =>
		_tasksStatusHubContext.Clients.All.TaskStatus(t));
}

// Call method only for specific connection (caller)
public Task ExecuteTask(TaskDto task, string connectionId)
{
	return ExecuteTaskInternal(task, (t) =>
		_tasksStatusHubContext.Clients.Client(connectionId).TaskStatus(t));
}

// Call method for all clients
public async Task RemoveTask(int id)
{
	_taskStorage.Delete(id);
	await _tasksStatusHubContext.Clients.All.TasksStatuses(_taskStorage.GetAll());
}
```

### Angular side

`angular/src/app/services/signalR/tasks-signalr.service.ts`

Build hub connection

```typescript
constructor() {
    this.hubConnection = new HubConnectionBuilder()
      .withUrl(environment.signalRBaseUrl + "tasksHub")
      .build();
  }
```

Establish connection with hub, executed in root component under OnInit method.

```typescript
public start() {

    if (this.hubConnection == null) {
      return;
    }

    // Start Hub connection
    this.hubConnection
      .start()
      .then(() => {
        console.log('Connected to tasks hub.');
        // We can store connection ID to make further calls with response only for caller
        this.connectionId = this.hubConnection.connectionId ?? '';
      })
      .catch(error => console.log('Tasks hub connection error: ' + error));

    // Register hub method that can be called by server on client side
    this.hubConnection.on('TasksStatuses', (tasks: TaskDto[]) => {
      this.tasksStatusesSubject.next(tasks);
    })

    // Register hub method that can be called by server on client side
    this.hubConnection.on('TaskStatus', (task: TaskDto) => {
      this.taskStatusSubject.next(task);
    });
}
```

Close connection to hun, executed in root component under OnDestroy method

```typescript
public stop() {
    if (this.hubConnection == null) {
      return;
    }

    this.hubConnection.stop().then(() => {
      console.log('Disconnected from tasks hub.');
    });
}
```

Call method on service-side

```typescript
public getRunningTasksCount(): Observable<number> {
    return new Observable<number>((subscriber) => {
      this.hubConnection
        // Provide expected return type and method name
        // See webapi/Api/TasksApi.Core/TasksHub.cs for Hub contract
        .invoke<number>('RunningTasksCount')
        .then((count) => {
          subscriber.next(count);
          subscriber.complete();
        })
        .catch((err) => {
          subscriber.error(err);
        });
    });
  }
```
