Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 106 additions & 0 deletions connecting-to-backends/syncfusion-reactgrid-with-signalr/ReadMe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Syncfusion React Grid with SignalR

The Syncfusion<sup style="font-size:70%">&reg;</sup> React Grid supports real-time data binding using SignalR, a powerful library for bi-directional communication between servers and clients. This approach enables live data updates without page refreshes, making it ideal for applications that require instant information delivery such as stock tickers, live dashboards, and real-time notifications.

**What is SignalR?**

[SignalR](https://learn.microsoft.com/en-us/aspnet/signalr/) is an open-source .NET library that simplifies adding real-time web functionality to applications. It automatically handles the best transport method (WebSockets, Server-Sent Events, or Long Polling) and provides a high-level API for server-to-client and client-to-server communication. SignalR enables persistent two-way connections between clients and servers, allowing instant data synchronization without polling.

**Key benefits of SignalR**

- **Real-time communication**: Establish persistent connections for instant data updates across all connected clients.
- **Bidirectional**: Support both server-to-client (broadcasting) and client-to-server communication.
- **Automatic transport selection**: Intelligently choose the best transport protocol (WebSockets, SSE, Long Polling) based on browser and server capabilities.
- **Scalable broadcasting**: Efficiently broadcast updates to multiple clients simultaneously using SignalR groups.
- **Built-in reconnection**: Automatically handles client reconnection with exponential back off retry logic.
- **No page refresh required**: Update UI dynamically without reloading the page.
- **Cross-platform**: Works across browsers, mobile devices, and desktop applications.

## Prerequisites


| **Software / Package** | **Recommended version** | **Purpose** |
|-----------------------------|------------------------------|-------------------------------------- |
| Node.js | 20.x LTS or later | Runtime |
| npm / yarn / pnpm | 11.x or later | Package manager |
| Vite | 7.3.1 | Use this to create the React application |
| TypeScript | 5.x or later | Server‑side and client‑side type safety |

## Quick Start

1. **Clone the repository**

```bash
git clone <repository-url>
```

2. **Running the application**

**Run the Server:**

- Run the below commands to run the server.

```bash
cd SignalR.Server
dotnet run
```
- The server runs at **http://localhost:5083/** by default.

**Run the client**

- Execute the below commands to run the client application.

```bash
cd signalr.client
npm install
npm run dev
```
- Open **http://localhost:5173/** in the browser.


## Project Layout

| **File/Folder** | **Purpose** |
|-------------|---------|
| `signalr.client/package.json` | Client package manifest and dev/start scripts |
| `signalr.client/tsconfig.json` / `tsconfig.app.json` | TypeScript configuration files for the client |
| `signalr.client/src/main.tsx` | React application entry point |
| `signalr.client/src/index.css` | Global styles for the client app |
| `signalr.client/src/components/StockGrid.tsx` | React component that renders the Syncfusion Grid and uses SignalR for live updates |
| `signalr.client/src/styles/StockGrid.css` | Styles for the `StockGrid` component (chips, colors, layout) |
| `SignalR.Server/Program.cs` | Server entry configuring services, middleware, and SignalR hubs (maps `/stockHub`) |
| `SignalR.Server/Controllers/StockController.cs` | API endpoints for initial grid datasource |
| `SignalR.Server/Hubs/StockHub.cs` | Lightweight SignalR hub; injects `StockDataService`, manages group membership (`StockTraders`) and sends initial `InitializeStocks` |
| `SignalR.Server/Models/Stock.cs` | Server-side `Stock` model with raw fields and `*Display` formatted fields |
| `SignalR.Server/Services/StockUpdateService.cs` | Background service that simulates price updates and broadcasts updates to the `StockTraders` group |
| `SignalR.Server/Services/StockDataService.cs` | Small service wrapper around `Stock.GetAllStocks()` used by `StockHub` |
| `SignalR.Server/appsettings.json` / `appsettings.Development.json` | Server configuration files |
| `SignalR.Server/SignalR.Server.csproj` | Server project file with dependencies and build settings |


## Common Tasks

### Search / Filter / Sort
- Use the **Search** box (toolbar) to match across configured columns
- Use column filter icons for equals/contains/date filters
- Click column headers to sort ascending/descending

## Steps to download GitHub samples using DownGit

1. **Open the DownGit Website**

Go to the official DownGit tool: https://downgit.github.io/#/home

2. **Copy the GitHub URL**

- Navigate to the sample folder you want to download and copy its URL.
- Example : https://github.com/SyncfusionExamples/ej2-react-grid-samples/tree/master/connecting-to-backends/syncfusion-reactgrid-with-django-server

3. **Paste the URL into DownGit**

In the DownGit input box, paste the copied GitHub URL.

4. **Download the ZIP**

- Click **Download**.
- DownGit will generate a ZIP file of the selected folder, which you can save and extract locally.
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
using System;
using System.Collections.Generic;
using System.Linq;
using SignalR.Server.Models;
using Microsoft.AspNetCore.Mvc;
using Syncfusion.EJ2.Base;
using System.Collections;

namespace SignalR.Server.Controllers
{
[ApiController]
[Route("api/[controller]")]
public class StockController : ControllerBase
{
/// <summary>
/// Fetch stock data with support for Syncfusion DataManager operations
/// Supports: Search, Sorting, Filtering
/// </summary>
[HttpPost("UrlDatasource")]
public IActionResult UrlDatasource([FromBody] DataManagerRequest dm)
{
try
{
// Get all stocks from the static collection
IEnumerable DataSource = Stock.GetAllStocks().ToList();
DataOperations operation = new DataOperations();

// Search operation
if (dm.Search != null && dm.Search.Count > 0)
{
DataSource = operation.PerformSearching(DataSource, dm.Search);
}

// Sorting operation
if (dm.Sorted != null && dm.Sorted.Count > 0)
{
DataSource = operation.PerformSorting(DataSource, dm.Sorted);
}

// Filtering operation
if (dm.Where != null && dm.Where.Count > 0)
{
DataSource = operation.PerformFiltering(DataSource, dm.Where, dm.Where[0].Operator);
}

// Get total count before paging
int count = DataSource.Cast<Stock>().Count();

// Paging operations
if (dm.Skip != 0)
{
DataSource = operation.PerformSkip(DataSource, dm.Skip);
}

if (dm.Take != 0)
{
DataSource = operation.PerformTake(DataSource, dm.Take);
}

// Return result with count if required
return dm.RequiresCounts
? Ok(new { result = DataSource, count = count })
: Ok(DataSource);
}
catch (Exception ex)
{
return BadRequest(new { error = ex.Message });
}
}

/// <summary>
/// Get all stocks
/// </summary>
[HttpGet("GetAll")]
public IActionResult GetAll()
{
try
{
var stocks = Stock.GetAllStocks();
return Ok(stocks);
}
catch (Exception ex)
{
return BadRequest(new { error = ex.Message });
}
}

/// <summary>
/// Get a single stock by StockId
/// </summary>
[HttpGet("GetById/{id}")]
public IActionResult GetById(int id)
{
try
{
var stock = Stock.GetAllStocks().FirstOrDefault(s => s.StockId == id);
if (stock == null)
{
return NotFound(new { error = $"Stock with ID {id} not found" });
}
return Ok(stock);
}
catch (Exception ex)
{
return BadRequest(new { error = ex.Message });
}
}

/// <summary>
/// Get stocks by symbol (e.g., "AAPL")
/// </summary>
[HttpGet("GetBySymbol/{symbol}")]
public IActionResult GetBySymbol(string symbol)
{
try
{
var stock = Stock.GetAllStocks()
.FirstOrDefault(s => s.Symbol.Equals(symbol, StringComparison.OrdinalIgnoreCase));

if (stock == null)
{
return NotFound(new { error = $"Stock with symbol {symbol} not found" });
}
return Ok(stock);
}
catch (Exception ex)
{
return BadRequest(new { error = ex.Message });
}
}



/// <summary>
/// Get stock statistics (min, max, average price, etc.)
/// </summary>
[HttpGet("GetStatistics")]
public IActionResult GetStatistics()
{
try
{
var stocks = Stock.GetAllStocks();

if (stocks.Count == 0)
{
return NotFound(new { error = "No stocks available" });
}

var stats = new
{
totalStocks = stocks.Count,
minPrice = stocks.Min(s => s.CurrentPrice),
maxPrice = stocks.Max(s => s.CurrentPrice),
averagePrice = stocks.Average(s => s.CurrentPrice),
positiveChanges = stocks.Count(s => s.Change > 0),
negativeChanges = stocks.Count(s => s.Change < 0),
totalVolume = stocks.Sum(s => s.Volume),
averageChangePercent = stocks.Average(s => s.ChangePercent),
lastUpdated = DateTime.UtcNow
};

return Ok(stats);
}
catch (Exception ex)
{
return BadRequest(new { error = ex.Message });
}
}
}

/// <summary>
/// Where clause model for filtering operations
/// </summary>
public class Wheres
{
public List<Predicates>? predicates { get; set; }
public string? field { get; set; }
public bool ignoreCase { get; set; }
public bool isComplex { get; set; }
public string? value { get; set; }
public string? Operator { get; set; }
}

/// <summary>
/// Predicates model for complex filtering
/// </summary>
public class Predicates
{
public string? value { get; set; }
public string? field { get; set; }
public bool isComplex { get; set; }
public bool ignoreCase { get; set; }
public string? Operator { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using Microsoft.AspNetCore.Mvc;

namespace SignalR.Server.Controllers
{
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
};

private readonly ILogger<WeatherForecastController> _logger;

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

[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
TemperatureC = Random.Shared.Next(-20, 55),
Summary = Summaries[Random.Shared.Next(Summaries.Length)]
})
.ToArray();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
using Microsoft.AspNetCore.SignalR;
using SignalR.Server.Models;
using SignalR.Server.Services;

namespace SignalR.Server.Hubs
{
public class StockHub : Hub
{
private readonly StockDataService _stockDataService;

public StockHub(StockDataService stockDataService)
{
_stockDataService = stockDataService;
}

public override async Task OnConnectedAsync()
{
await base.OnConnectedAsync();
var stocks = _stockDataService.GetAllStocks();
await Clients.Client(Context.ConnectionId).SendAsync("InitializeStocks", stocks);
}

public async Task SubscribeToStocks()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "StockTraders");
var stocks = _stockDataService.GetAllStocks();
await Clients.Caller.SendAsync("InitializeStocks", stocks);
}

public async Task UnsubscribeFromStocks()
{
await Groups.RemoveFromGroupAsync(Context.ConnectionId, "StockTraders");
}
}
}


Loading