Core API Real Time Communication with SignalR

Standard HTTP is a one-way conversation — the client asks, the server answers, and the connection closes. For the BookStore API, this means a client cannot know when a new book is added unless it keeps polling the API repeatedly. SignalR solves this by keeping an open connection between the server and the client, allowing the server to push messages to connected clients instantly.

What Is SignalR?

SignalR is a library that adds real-time functionality to web applications. It manages the connection automatically, choosing the best transport method available:

TransportDescriptionUsed When
WebSocketsFull-duplex, persistent TCP connectionBrowser and server both support it
Server-Sent EventsOne-way stream from server to clientWebSockets unavailable
Long PollingClient keeps a request open until server respondsFallback for older browsers

SignalR selects WebSockets automatically if both sides support it — the best option for lowest latency.

Use Cases for SignalR in BookStore API

  • Notify all connected clients when a new book is added
  • Notify when a book is updated or marked unavailable
  • Live admin dashboard showing real-time book inventory counts

Step 1 – Install the SignalR Client Package

Server-side SignalR is already included in ASP.NET Core — no extra package needed on the server. The JavaScript client library is needed only in browser-based frontends.

// Server: no extra package needed

// JavaScript client (if using npm):
npm install @microsoft/signalr

// Or via CDN in HTML:
// <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/7.0.0/signalr.min.js"></script>

Step 2 – Create the Hub

A Hub is the central class in SignalR. Clients connect to a hub, and the hub can send messages to individual clients, groups, or all connected clients.

// Hubs/BookStoreHub.cs
using Microsoft.AspNetCore.SignalR;

namespace BookStoreAPI.Hubs
{
    public class BookStoreHub : Hub
    {
        private readonly ILogger<BookStoreHub> _logger;

        public BookStoreHub(ILogger<BookStoreHub> logger)
        {
            _logger = logger;
        }

        // Called when a client connects
        public override async Task OnConnectedAsync()
        {
            _logger.LogInformation("Client connected: {ConnectionId}", Context.ConnectionId);
            await base.OnConnectedAsync();
        }

        // Called when a client disconnects
        public override async Task OnDisconnectedAsync(Exception? exception)
        {
            _logger.LogInformation("Client disconnected: {ConnectionId}", Context.ConnectionId);
            await base.OnDisconnectedAsync(exception);
        }

        // Clients can join a group (e.g., a specific category channel)
        public async Task JoinCategory(string category)
        {
            await Groups.AddToGroupAsync(Context.ConnectionId, category);
            _logger.LogInformation("Client {ConnectionId} joined group: {Category}",
                Context.ConnectionId, category);
        }

        // Clients can leave a group
        public async Task LeaveCategory(string category)
        {
            await Groups.RemoveFromGroupAsync(Context.ConnectionId, category);
        }
    }
}

Step 3 – Register SignalR in Program.cs

// Program.cs
builder.Services.AddSignalR();

var app = builder.Build();

// ... other middleware ...

app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();

// Map the hub to a URL
app.MapHub<BookStoreHub>("/hubs/bookstore");

app.Run();

Step 4 – Send Notifications from the Book Service

When a book is created, updated, or deleted, the service uses IHubContext to push a message to all connected clients:

// Services/BookService.cs
using Microsoft.AspNetCore.SignalR;
using BookStoreAPI.Hubs;

public class BookService : IBookService
{
    private readonly IBookRepository _bookRepository;
    private readonly IHubContext<BookStoreHub> _hubContext;
    private readonly ILogger<BookService> _logger;

    public BookService(
        IBookRepository bookRepository,
        IHubContext<BookStoreHub> hubContext,
        ILogger<BookService> logger)
    {
        _bookRepository = bookRepository;
        _hubContext = hubContext;
        _logger = logger;
    }

    public async Task<Book> CreateAsync(Book book)
    {
        book.CreatedDate = DateTime.UtcNow;
        await _bookRepository.AddAsync(book);
        await _bookRepository.SaveAsync();

        // Notify all connected clients about the new book
        await _hubContext.Clients.All.SendAsync("BookAdded", new
        {
            book.Id,
            book.Title,
            book.Author,
            book.Price,
            book.Category,
            book.IsAvailable
        });

        _logger.LogInformation("Book created and clients notified: {Title}", book.Title);
        return book;
    }

    public async Task<bool> UpdateAsync(int id, Book updatedBook)
    {
        var book = await _bookRepository.GetByIdAsync(id);
        if (book == null) return false;

        book.Title = updatedBook.Title;
        book.Author = updatedBook.Author;
        book.Price = updatedBook.Price;
        book.Category = updatedBook.Category;
        book.IsAvailable = updatedBook.IsAvailable;

        await _bookRepository.UpdateAsync(book);
        await _bookRepository.SaveAsync();

        // Notify the specific category group
        await _hubContext.Clients.Group(book.Category).SendAsync("BookUpdated", new
        {
            book.Id,
            book.Title,
            book.Price,
            book.IsAvailable
        });

        return true;
    }

    public async Task<bool> DeleteAsync(int id)
    {
        var book = await _bookRepository.GetByIdAsync(id);
        if (book == null) return false;

        await _bookRepository.DeleteAsync(book);
        await _bookRepository.SaveAsync();

        // Notify all clients that a book was removed
        await _hubContext.Clients.All.SendAsync("BookRemoved", new { BookId = id });

        return true;
    }
}

SignalR Sending Options

MethodWho Receives It
Clients.All.SendAsync()Every connected client
Clients.Caller.SendAsync()Only the client that triggered the method
Clients.Others.SendAsync()All clients except the caller
Clients.Group("name")All clients in a specific group
Clients.Client(connectionId)One specific client
Clients.User(userId)All connections of one user

Step 5 – Connect from a JavaScript Client

<!-- Simple HTML example to listen for BookStore events -->
<script src="signalr.min.js"></script>
<script>
  const connection = new signalR.HubConnectionBuilder()
    .withUrl("https://localhost:7001/hubs/bookstore")
    .withAutomaticReconnect()
    .build();

  // Listen for new book added event
  connection.on("BookAdded", function(book) {
    console.log("New book added:", book.title, "by", book.author);
    // Update the UI to show the new book
  });

  // Listen for book updated event
  connection.on("BookUpdated", function(book) {
    console.log("Book updated:", book.id, "Price:", book.price);
  });

  // Listen for book removed event
  connection.on("BookRemoved", function(data) {
    console.log("Book removed with Id:", data.bookId);
  });

  // Start the connection
  connection.start()
    .then(() => {
      console.log("Connected to BookStore Hub!");

      // Join the Technology category group
      connection.invoke("JoinCategory", "Technology");
    })
    .catch(err => console.error("Connection failed:", err));
</script>

Real-Time Flow for BookStore API

Admin sends: POST /api/v2/books (add "Refactoring" by Martin Fowler)
                    |
                    v
         BookService.CreateAsync()
                    |
                    ├── Saves book to SQL Server
                    |
                    └── _hubContext.Clients.All.SendAsync("BookAdded", bookData)
                                    |
             ┌──────────────────────┼─────────────────────┐
             ↓                      ↓                      ↓
    Client A (browser)    Client B (mobile)    Client C (admin dashboard)
    "BookAdded" event     "BookAdded" event    "BookAdded" event
    UI updates instantly  App shows new book   Dashboard refreshes count

Key Points

  • SignalR enables the server to push messages to clients without the client needing to ask — unlike standard HTTP request-response.
  • A Hub is the central class — clients connect to it and the server sends messages through it.
  • IHubContext<T> is injected into services to send messages from outside the Hub (e.g., from the BookService after a save).
  • SignalR supports groups — clients can subscribe to channels like "Technology" and receive only relevant updates.
  • SignalR automatically selects WebSockets → Server-Sent Events → Long Polling as fallback transports.

Leave a Comment