Skip to content

Dto — Real-time with SignalR

Add [SignalRHub] to an entity to generate a strongly-typed SignalR hub and client interface. When paired with [CrudApi], the generated POST/PATCH/DELETE endpoints automatically inject IHubContext and push notifications to connected clients after each store operation.

[SignalRHub]
public class Order
{
public int Id { get; set; }
public required string Product { get; set; }
public decimal Total { get; set; }
}

This generates:

// Generated hub + client interface
public interface IOrderHubClient
{
Task OnCreated(OrderResponse item);
Task OnUpdated(OrderResponse item);
Task OnDeleted(int id);
}
public class OrderHub : Hub<IOrderHubClient> { }

When both attributes are present, the generated CRUD endpoints inject IHubContext<OrderHub, IOrderHubClient> and push automatically:

[CrudApi]
[SignalRHub]
public class Order
{
[DtoIgnore(DtoTarget.Create | DtoTarget.Update | DtoTarget.Query)]
public int Id { get; set; }
public required string Product { get; set; }
public decimal Total { get; set; }
public OrderStatus Status { get; set; }
}

The generated endpoints behave as:

EndpointStore operationSignalR push
POST /api/ordersCreateClients.All.OnCreated(response)
PATCH /api/orders/{id}UpdateClients.All.OnUpdated(response)
DELETE /api/orders/{id}DeleteClients.All.OnDeleted(id)

Map the generated hub in Program.cs:

builder.Services.AddSignalR();
var app = builder.Build();
app.MapOrderEndpoints(); // CRUD endpoints (generated)
app.MapHub<OrderHub>("/api/orders/hub"); // SignalR hub

The generated IOrderHubClient always follows the same pattern:

public interface I{Entity}HubClient
{
Task OnCreated({Entity}Response item);
Task OnUpdated({Entity}Response item);
Task OnDeleted(int id); // key type matches the entity's primary key
}

Connect from a JavaScript/TypeScript client using the @microsoft/signalr package:

import { HubConnectionBuilder } from "@microsoft/signalr";
const connection = new HubConnectionBuilder()
.withUrl("/api/orders/hub")
.withAutomaticReconnect()
.build();
connection.on("OnCreated", (order) => {
console.log("New order:", order);
});
connection.on("OnUpdated", (order) => {
console.log("Updated order:", order);
});
connection.on("OnDeleted", (id) => {
console.log("Deleted order:", id);
});
await connection.start();
  • [SignalRHub] without [CrudApi] only generates the hub and interface — you call IHubContext yourself in custom endpoints or services.
  • The hub class is partial, so you can add OnConnectedAsync/OnDisconnectedAsync overrides in a separate file.
  • Group-based broadcasting (e.g. per-tenant) is not generated — use the partial hub to add group logic when needed.