From 18766f2de9f611002deab6c96d07d4c8fee3fbaa Mon Sep 17 00:00:00 2001 From: Akshaya Arivoli Date: Tue, 1 Sep 2026 19:30:22 +0530 Subject: [PATCH 1/2] 1042366: UG content for common collaborator --- Document-Processing-toc.html | 17 +++ .../Collaborator/collaboration-client.md | 91 +++++++++++ .../Collaborator/collaboration-server.md | 141 ++++++++++++++++++ Document-Processing/Collaborator/faq.md | 53 +++++++ Document-Processing/Collaborator/overview.md | 92 ++++++++++++ 5 files changed, 394 insertions(+) create mode 100644 Document-Processing/Collaborator/collaboration-client.md create mode 100644 Document-Processing/Collaborator/collaboration-server.md create mode 100644 Document-Processing/Collaborator/faq.md create mode 100644 Document-Processing/Collaborator/overview.md diff --git a/Document-Processing-toc.html b/Document-Processing-toc.html index a51280b771..f9799a7946 100644 --- a/Document-Processing-toc.html +++ b/Document-Processing-toc.html @@ -8473,6 +8473,23 @@
  • Font Manager
  • + + +
  • + Collaborator +
  • diff --git a/Document-Processing/Collaborator/collaboration-client.md b/Document-Processing/Collaborator/collaboration-client.md new file mode 100644 index 0000000000..af661864b8 --- /dev/null +++ b/Document-Processing/Collaborator/collaboration-client.md @@ -0,0 +1,91 @@ +# Collaboration Client + +The Collaboration Client (@syncfusion/ej2\-collaborator) is a browser\-side library that enables real\-time collaborative editing in Syncfusion Essential JS 2 (EJ2) components such as **Document Editor**, **PDF Viewer**, and **Spreadsheet**. + +It connects the client application to a Collaboration Server, synchronizes user actions across participants, and applies remote updates in real time. + +## Package Information + +- **Package:** @syncfusion/ej2\-collaborator + +- **Runtime:** Browser\-based applications (Angular, React, Vue, JavaScript, and TypeScript) + +- **Supported Transports:** + + - SignalR + + - WebSocket + +## Key Responsibilities + +The Collaboration Client: + +- Connects to the Collaboration Server. + +- Joins and leaves collaboration sessions. + +- Sends local editing actions to the server. + +- Receives remote actions from other participants. + +- Keeps content synchronized across all connected users. + + +## Supported Backends + +The same client can be used with different Collaboration Server implementations. +|**Connection Type**|**Supported Server**| +|:---|:---| +|SignalR|ASP.NET Core| +|WebSocket|ASP.NET Core| +|WebSocket|ASP.NET MVC| +|WebSocket|Node.js| + + + +## Installation +|npm install @syncfusion/ej2\-collaborator | +|:---| + + + +## Configuration + +The Collaboration Client requires the following configuration: +|**Option**|**Description**| +|:---|:---| +|serviceUrl|URL of the Collaboration Server endpoint| +|connectionType|Transport type (signalr or websocket)| +|currentUser|Display name of the current user| + +```ts +const client = new CollaborationClient(adapter, { + serviceUrl: 'https://localhost:5001', + connectionType: 'signalr', + currentUser: 'John' + }); + await client.joinRoomAsync(roomname); +``` + + + +## Adapter Integration + +The Collaboration Client is designed to be control\-agnostic. Each supported EJ2 component integrates through an adapter that implements ICollaborationProvider. + +The adapter acts as a bridge between the Collaboration Client and the EJ2 component by: + +- Sending local editing operations to the Collaboration Client. + +- Receiving remote collaboration actions. + +- Applying those actions to the host component. + +**Adapter Example** +```ts +public applyRemoteAction( action: string, data: ICollaborationActionData ): void { // Apply the remote action to the host component } +``` + + + +Because of this architecture, the same Collaboration Client can be reused across **Document Editor**, **PDF Viewer**, and **Spreadsheet**, with only the adapter implementation changing for each component. diff --git a/Document-Processing/Collaborator/collaboration-server.md b/Document-Processing/Collaborator/collaboration-server.md new file mode 100644 index 0000000000..5ee09c7a85 --- /dev/null +++ b/Document-Processing/Collaborator/collaboration-server.md @@ -0,0 +1,141 @@ +# Collaboration Server + +The Collaboration Server is the back\-end component of the Collaborator framework. It manages collaboration sessions, synchronizes editing actions, persists changes, and broadcasts updates to connected participants in real time. + +The same Common Collaborator framework is shared across all supported server platforms, allowing the collaboration infrastructure to be reused across EJ2 components such as **Document Editor**, **PDF Viewer**, and **Spreadsheet**. + +## Packages +|**Package**|**Description**| +|:---|:---| +|Syncfusion.Collaborator.Server|Collaboration server for ASP.NET Core and ASP.NET MVC| +|ej2\-collaborator\-server|Collaboration server for Node.js| + + + +**Note**: The Node.js Collaboration Server currently supports PDF Viewer collaborative editing only. Document Editor and Spreadsheet require the ASP.NET\-based web service implementation for document processing, operation transformation, and save operations. + +## Key Features + +- Real\-time synchronization of editing actions. + +- Support for SignalR and WebSocket transports. + +- Redis\-based storage and messaging for scalable deployments. + +- Shared collaboration services across supported EJ2 components. + +## Redis Requirement + +The Collaboration Server uses Redis for operation storage, session synchronization, and scalable multi\-server deployments. + +## Adapter Integration + +The Collaboration Server is control\-agnostic. Each supported EJ2 component integrates through a server adapter that translates component\-specific actions into the common collaboration format. The same collaboration infrastructure can therefore be reused across Document Editor, PDF Viewer, and Spreadsheet with only the adapter implementation changing. + +# ASP.NET Core Server + +The ASP.NET Core Collaboration Server is provided through the Syncfusion.Collaborator.Server package. It supports both SignalR and WebSocket transports and is recommended for modern .NET applications. + +**Installation** +|dotnet add package Syncfusion.Collaborator.Server| +|:---| + + + +**Supported Transports** + +- **SignalR(** default **)** + +- **WebSocket** + +**Configuration**: + +Register the Collaboration Server and configure the Redis connection string during application start. + +**SignalR (Default)** +```c# +builder.Services.AddCollaborationServer(options => { options.ConnectionString = "localhost:6379"; }); +``` +**WebSocket** +```c# +builder.Services.AddCollaborationServer(options => { options.ConnectionString \= "localhost:6379"; options.ConnectionType = CollaborationConnectionType.WebSocket; }); +``` +**Configuration Options** +|**Property**|**Description**| +|:---|:---| +|ConnectionString|Redis connection string| +|ConnectionType|SignalR or WebSocket transport| +|SaveThreshold|Number of operations before triggering a save| + + + +**Adapter Integration** + +Register a control\-specific adapter to translate between the EJ2 component and the Common Collaborator framework. +|builder.Services.AddSingleton\(); | +|:---| + + + +# ASP.NET MVC Server + +The ASP.NET MVC Collaboration Server provides the collaboration capabilities as the ASP.NET Core server for applications built on .NET Framework and ASP.NET MVC 5. + +**Requirements** + +- .NET Framework 4.6.2 or later + +- ASP.NET MVC 5 + +- Redis + +**Installation** + +Install\-Package Syncfusion.Collaborator.Server + + +**Transport Support** + +The ASP.NET MVC Collaboration Server supports **WebSocket** communication for real\-time synchronization between connected users. + +**Configuration** + +Configure the Collaboration Server with a Redis connection string and register the required adapter implementation. +```c# +ServiceCollectionExtensions.RegisterAdapter( new DocumentEditorCollaborationAdapter()); ServiceCollectionExtensions.AddCollaborationServer(options => { options.ConnectionString = ""; options.ConnectionType = CollaborationConnectionType.WebSocket; }); +``` +**Adapter Integration** + +Register a control\-specific adapter to connect the EJ2 component with the Common Collaborator framework. + +# Node.js Server + +The Node.js Collaboration Server provides real\-time collaboration capabilities for JavaScript and TypeScript applications. + +**Note:** The Node.js Collaboration Server currently supports PDF Viewer collaborative editing only. Document Editor and Spreadsheet require the ASP.NET\-based web service implementation for document processing, operation transformation, and save operations. + +**Requirements** + +- Node.js 18 or later + +- Redis + +**Installation** +npm install ej2\-collaborator\-server + +**Transport Support** + +The Node.js Collaboration Server supports **WebSocket** communication for real\-time synchronization and collaboration between connected users. + +**Configuration** +```ts +const server = new CollaborationServer({ + redis: { host: "", + port: 6379 }, + adapter + }); +``` +**Adapter Integration** + +Register a control\-specific adapter to connect the EJ2 component with the Common Collaborator framework. + diff --git a/Document-Processing/Collaborator/faq.md b/Document-Processing/Collaborator/faq.md new file mode 100644 index 0000000000..3bcb7ebad8 --- /dev/null +++ b/Document-Processing/Collaborator/faq.md @@ -0,0 +1,53 @@ +# FAQ + +## 1. How should Redis be configured for collaborative editing? + +In collaborative editing, Redis is used to store temporary data that helps queue editing operations and resolve conflicts using the *Operational Transformation* algorithm. + +All editing operations are stored in the Redis cache. To prevent memory buildup, a *SaveThreshold* limit can be configured at the application level. For example, if the SaveThreshold is set to 100, up to twice that number of editing operations are retained in Redis per document. When this limit is exceeded, the first 100 operations (as defined by the save threshold) are removed from the cache and automatically saved to the source document. + +The configuration and storage size of the Redis cache can be adjusted based on the following considerations: + +- *Storage Requirements*: A minimum of 400 KB of cache memory is required to edit a single document, with the capacity to store up to 100 editing operations. Storage requirements may increase based on the following factors: + + - *Images*: Increases with the number of images added to the document. + + - *Pasted content*: Depends on the size of the SFDT content. + +- *Connection Limits*: Redis has a limit on concurrent connections. The Redis configuration should be selected based on the user base to ensure optimal performance. + +**Note**: For better performance, a minimum *SaveThreshold* value of 100 is recommended. + +## 2. Why does the Collaborator use Redis instead of a database? + +To support collaborative editing, it’s crucial to have a backing system that temporarily stores the editing operations of all active users. There are two primary options: + +- *Distributed Cache*: Handles more HTTP requests per second than a database approach. For example, a server with 2 vCPUs and 8GB of RAM can process up to 125 requests per second using a distributed cache. We are using distributed cache as a backing system over a database. + +- *Database*: With the same server configuration, it can handle up to 50 requests per second. + + +## 3. How do I estimate the server capacity required for collaborative editing? + +To calculate the average requests per second of your application, assume the DOCX Editor in your live application is actively used by 1000 users, and each user’s edit can trigger 2 to 5 requests per second. The total requests per second of your application will be around 2000 to 5000. In this case, you can finalize a configuration to support around 5000 average requests per second. + +**NOTE:** The above metrics are based solely on the collaborative editing module. Actual throughput may decrease depending on other server\-side interactions, such as document importing, pasting formatted content, editing restrictions, and spell checking. Therefore, it is advisable to monitor your app’s traffic and choose a configuration that best suits your needs. + + +## 4. What transport protocols are supported by the Collaborator framework? + +The Collaborator framework supports: + +- **SignalR** (ASP.NET Core) + +- **WebSocket** (ASP.NET Core, ASP.NET MVC, and Node.js) + +Both provide real\-time communication between clients and the Collaboration Server. + +## 5. Which EJ2 components are currently supported by the Node.js Collaboration Server? + +The Node.js Collaboration Server currently supports **PDF Viewer** collaborative editing. + +**Document Editor** and **Spreadsheet** require the ASP.NET Core or ASP.NET MVC Collaboration Server for document processing, operation transformation, and save operations. + + diff --git a/Document-Processing/Collaborator/overview.md b/Document-Processing/Collaborator/overview.md new file mode 100644 index 0000000000..2fc308836b --- /dev/null +++ b/Document-Processing/Collaborator/overview.md @@ -0,0 +1,92 @@ +# Collaborator + +The Syncfusion **Collaborator** is a real\-time collaboration framework that enables multiple users to edit content simultaneously in EJ2 components such as Document Editor, PDF Viewer, and Spreadsheet. + +The Collaborator consists of: + +- **Collaboration Client** (@syncfusion/ej2\-collaborator) integrated into the EJ2 component. + +- **Collaboration Server** available for ASP.NET Core, ASP.NET MVC, and Node.js. + +Both client and server are built on a shared, control\-agnostic common collaborator framework, enabling the same collaboration infrastructure to be reused across supported EJ2 components. + +## Key Features + +- Real\-time collaborative editing with automatic synchronization across multiple users. + +- Support for both SignalR and WebSocket communication. + +- Scalable architecture with Redis support for multi\-server deployments. + +- Reusable collaboration framework for Document Editor, PDF Viewer, and Spreadsheet. + +- Built\-in support for session management, version tracking, and content persistence. + +## How the Collaborator Works + +1. A user opens content in an EJ2 component configured with the Collaboration Client and joins a collaboration session. + +1. User actions are sent to the Collaboration Server in real time. + +1. The server processes and synchronizes those actions with all connected participants. + +1. Other users receive the updates and their content is refreshed automatically. + +1. Changes are periodically persisted to ensure the latest version is available to all participants. + +## Collaboration Packages + + +The Collaborator is available as client and server packages. Install the client package in your application and choose the appropriate server package based on your hosting platform. +|**Package**|**Description**| +|:---|:---| +|@syncfusion/ej2\-collaborator|Client library| +|Syncfusion.Collaborator.Server|ASP.NET Core / ASP.NET MVC server| +|ej2\-collaborator\-server\-nodejs|Node.js server| + + + +### @syncfusion/ej2\-collaborator — Client + +A JavaScript/TypeScript client library that enables collaborative editing in EJ2 components such as Document Editor, PDF Viewer, and Spreadsheet. + +### Syncfusion.Collaborator.Server — Server (ASP.NET) + +A collaboration server for ASP.NET Core and ASP.NET MVC that synchronizes edits, manages collaboration sessions, and broadcasts updates between connected users. + +### ej2\-collaborator\-server\-nodejs — Server (Node.js) + +A Node.js\-based collaboration server that provides the same real\-time collaboration capabilities as the ASP.NET server using WebSockets and Redis. It is suitable for JavaScript and TypeScript\-based applications. \ + \ +**Note:** The Node.js Collaboration Server currently supports **PDF Viewer** collaborative editing only. **Document Editor** and **Spreadsheet** require the ASP.NET\-based web service implementation for document processing, operation transformation, and save operations. + + +## Prerequisites + +- A Redis instance reachable from the server + +- One of the supported server runtimes: + + - .NET 8 SDK (for ASP.NET Core) + + - .NET Framework 4.6.2 or later with ASP.NET MVC 5 (for ASP.NET MVC) + + - Node.js 18 or later (for Node.js) + +- An EJ2 content editor component (such as the Document Editor, PDF Viewer, or Spreadsheet) configured with the Collaboration Client. + +## What's Next + +- Collaboration Client + +- Collaboration Server + +- Getting Started + + - With ASP.NET Core Server + + - With ASP.NET MVC Server + + - With Node.js Server + +- FAQs \ No newline at end of file From e08397b47a417a90e0e4f5790a0a545c56958322 Mon Sep 17 00:00:00 2001 From: Akshaya Arivoli Date: Wed, 2 Sep 2026 07:47:59 +0530 Subject: [PATCH 2/2] 1042366: Added getting started core --- Document-Processing-toc.html | 3 + .../Collaborator/collaboration-client.md | 32 +- .../Collaborator/collaboration-server.md | 85 +++ .../getting-started-with-core.md | 699 ++++++++++++++++++ 4 files changed, 816 insertions(+), 3 deletions(-) create mode 100644 Document-Processing/Collaborator/getting-started/getting-started-with-core.md diff --git a/Document-Processing-toc.html b/Document-Processing-toc.html index f9799a7946..84785cad5e 100644 --- a/Document-Processing-toc.html +++ b/Document-Processing-toc.html @@ -8489,6 +8489,9 @@
  • FAQ +
  • +
  • + Getting started with core
  • diff --git a/Document-Processing/Collaborator/collaboration-client.md b/Document-Processing/Collaborator/collaboration-client.md index af661864b8..8723f4b95a 100644 --- a/Document-Processing/Collaborator/collaboration-client.md +++ b/Document-Processing/Collaborator/collaboration-client.md @@ -44,10 +44,37 @@ The same client can be used with different Collaboration Server implementations. ## Installation -|npm install @syncfusion/ej2\-collaborator | -|:---| +npm install @syncfusion/ej2\-collaborator +## Public API +Product teams touch exactly two surfaces: the **`ICollaborationProvider`** interface they implement, and the **`CollaborationClient`** they instantiate. Everything else (`CollaborationConnection`, `ICollaborationOptions`, `ICollaborationTransport`, `TransportFactory`, `SignalRTransport`, `WebSocketTransport`, `CollaborationEvents`) is internal to the common package. + +### `ICollaborationProvider` — implement this in your adapter + +| Member | Purpose | +|---|---| +| `applyRemoteAction(action: string, data: ICollaborationActionData): void` | Apply a remote action received from the collaboration backend to the local editor. The action payload is exposed under `data.payload`. **This is the only method the common package calls on the adapter at runtime.** | + +### `CollaborationClient` — call this from your app + +| Member | Purpose | +|---|---| +| `constructor(adapter: ICollaborationProvider, options: CollaborationClientOptions)` | Bind an adapter to a transport endpoint and current user. Constructs the underlying transport — does **not** start it. | +| `joinRoomAsync(roomName: string): Promise` | Connects the transport and sends a `JoinGroup` for `roomName`. Re-emits the server's `connectionId` / `addUser` / `removeUser` / `action` events. **Does not fetch the document** — do that in your adapter or app first if needed. | + + +#### `CollaborationClientOptions` — constructor argument + +| Field | Type | Purpose | +|---|---|---| +| `serviceUrl` | `string` | URL of the real-time collaboration backend. **Required.** Shape depends on `connectionType` For Example : serviceUrl:"ws://localhost:8080", //node server serviceUrl:"ws://localhost:62870", //ASP.NET Core + Webscoket ServiceUrl:"http://localhost:62870", //ASP.NET Core + SignalR +| `connectionType` | `'signalr' \| 'websocket'` | Selects the backend. Defaults to `'signalr'`. | +| `currentUser` | `string` | Display name broadcast to peers when joining the room. **Required.** | +| `onUserJoined?` | `(user: UserInfo) => void` | Fired when a remote peer enters the same room. **Optional.** | +| `onUserLeft?` | `(user: UserInfo) => void` | Fired when a remote peer leaves the room. **Optional.** | + +> NOTE: `serviceUrl` is the **transport** URL, not a product REST API URL. Each product passes its own REST endpoint through a separate field on its own configuration. ## Configuration @@ -87,5 +114,4 @@ public applyRemoteAction( action: string, data: ICollaborationActionData ): void ``` - Because of this architecture, the same Collaboration Client can be reused across **Document Editor**, **PDF Viewer**, and **Spreadsheet**, with only the adapter implementation changing for each component. diff --git a/Document-Processing/Collaborator/collaboration-server.md b/Document-Processing/Collaborator/collaboration-server.md index 5ee09c7a85..ca727b2d62 100644 --- a/Document-Processing/Collaborator/collaboration-server.md +++ b/Document-Processing/Collaborator/collaboration-server.md @@ -48,6 +48,46 @@ The ASP.NET Core Collaboration Server is provided through the Syncfusion.Collabo - **WebSocket** +## Public API + +The interfaces below are what product teams **implement or call**. Everything else is internal. + +### `ICollaborationAdapter` — the interface (provided by the common package) + +| Member | Purpose | +|---|---| +| `MapControlToGenericAction(object controlAction)` | Pack control action → common `CollaborationAction` | +| `MapGenericToControlAction(CollaborationAction action)` | Unpack common → control action | +| `TransformOperations(List actions)` | Run your control's OT over a batch of actions | +| `SaveOperationsAsync(actions, roomName, partialSave)` | Hand off save to background queue | +| `ProcessSaveRequestAsync(SaveRequest, cancellationToken)` | Runs the actual save (called by the background hosted service) | + +### `IActionService` (common, you call) + +| Method | Purpose | +|---|---| +| `AddOperationAsync(action, adapter)` | Persist action + return transformed version | +| `GetPendingOperationsAsync(room, from, to)` | Fetch stored actions in a range | +| `GetEffectivePendingVersionAsync(room, version)` | Fetch newer-than-version actions for a joining client | +| `ClearRecordsAsync(roomName, partialSave)` | Flush after save completes | + +### `IActiveTransport` (common, transport-agnostic broadcast) + +| Member | Purpose | +|---|---| +| `SendToGroupAsync(roomName, eventName, payload)` | Broadcast to every client in a room. | + +### `CollaborationOptions` (registration configuration) + +| Property | Default | Purpose | +|---|---|---| +| `ConnectionString` | `localhost:6379` | Redis connection string used for storage and pub/sub. | +| `ConnectionType` | `CollaborationConnectionType.SignalR` | Pick **either** `SignalR` **or** `WebSocket`. | +| `SaveThreshold` | `CollaborativeEditingHelper.SaveThreshold` | Operation list size that triggers a background save. | + +> `ConnectionType` is a single enum choice — set it to `SignalR` **or** `WebSocket`. + + **Configuration**: Register the Collaboration Server and configure the Redis connection string during application start. @@ -127,6 +167,51 @@ npm install ej2\-collaborator\-server The Node.js Collaboration Server supports **WebSocket** communication for real\-time synchronization and collaboration between connected users. +## Public API + +The members below are what product teams **call or implement**. Everything +else is internal to this package. + +### `ICollaborationAdapter` (base class — implemented by the consumer) + +| Member | Purpose | +| --- | --- | +| `mapControlToGenericAction(controlAction)` | Pack a control action into the common `CollaborationAction`. | +| `mapGenericToControlAction(collaborationAction)` | Unpack a common `CollaborationAction` into the control action shape. | +| `transformOperations(actions)` | Run your control's OT over a batch of actions. Returns the transformed array. | +| `saveOperationsAsync(actions, roomName, partialSave)` | Hand off save to the background queue. | +| `processSaveRequestAsync(request)` | Runs the actual save (called by `DocumentSaveWorker`). | + +### `CollaborationServer` (the package entry point) + +| Member | Purpose | +| --- | --- | +| `new CollaborationServer({ port, redis, adapter, saveThreshold? })` | Construct the server. The `port` can be overridden by `process.env.PORT`. | +| `start()` | Start the HTTP + WebSocket server, mount REST routes, and run the background save worker. | +| `app` | The underlying `express.Express` instance — attach product-specific routes (e.g. `server.app.get('/api/test', ...)`). | +| `actionService` | The `ActionService` instance wired for you; useful when mounting custom Edit-Control routes. | + + +### `ActionService` + +| Member | Purpose | +| --- | --- | +| `addAction(roomName, action)` | Persist a `CollaborationAction` and bump the room version. | +| `getActions(roomName)` | Fetch the current action list for a room. | +| `addOperation(action, adapter)` | Run the Lua-script flow: assign version, transform prior ops via `adapter.transformOperations`, persist, and enqueue a partial save when the threshold is reached. | +| `getPendingOperations(roomName, startIndex, endIndex)` | Fetch stored actions in a range. | +| `getEffectivePendingVersion(roomName, startIndex)` | Fetch newer-than-`startIndex` actions for a joining client. | +| `clearRecords(roomName, partialSave)` | Flush actions after save completes. | + +### Configuration (constructor options) + +| Property | Default | Purpose | +| --- | --- | --- | +| `port` | `process.env.PORT` or `8080` | HTTP/WS port. | +| `redis` | — | `ioredis` connection options (`host`, `port`, `username`, `password`, `tls`, ...). | +| `adapter` | — | The `ICollaborationAdapter` instance used by the save worker and REST controller. | +| `saveThreshold` | `Helper.SAVE_THRESHOLD` (100) | Operations per partial save. A flush fires when the Redis list length is a multiple of `2 * saveThreshold`, moving the first `saveThreshold` actions into the staging list handed to `adapter.processSaveRequestAsync`. Lower values shorten recovery, higher values reduce write pressure. Must be a positive integer. | + **Configuration** ```ts const server = new CollaborationServer({ diff --git a/Document-Processing/Collaborator/getting-started/getting-started-with-core.md b/Document-Processing/Collaborator/getting-started/getting-started-with-core.md new file mode 100644 index 0000000000..55b7bcad7f --- /dev/null +++ b/Document-Processing/Collaborator/getting-started/getting-started-with-core.md @@ -0,0 +1,699 @@ +# Getting Started with ASP.NET Core Collaboration Server + +This walk\-through creates a collaborative Document Editor backed by the ASP.NET Core Collaboration Server (SignalR transport, the default). It uses the Common Collaborator services on the server and the shared @syncfusion/ej2\-collaborator client on the browser. + +The walk\-through uses the **DOCX Editor** as the reference editor component. The same pattern applies to the **PDF Viewer** and the **Spreadsheet** only the control\-specific adapter class changes. + +# Client Side + +## Step 1 — Install the client packages + +In your front\-end project: +{% tabs %} +{% highlight bash tabtitle="npm" %} + +npm install @syncfusion/ej2\-documenteditor +npm install @syncfusion/ej2\-collaborator + +{% endhighlight %} +{% endtabs %} + +@microsoft/signalr is installed automatically as a transitive dependency. + + +## Step 2 — Reference Adapter (DocumentEditorAdapter.ts) + +The control\-specific translator on the client side. It implements ICollaborationProvider for the EJ2 Document Editor. PDF Viewer / Spreadsheet teams replace this with their own, but the shape is identical. + ```ts +import { + + DocumentEditor, + + DocumentEditorContainer, + + Operation + +} from '@syncfusion/ej2\-documenteditor'; + + +import { + + ICollaborationProvider, + + ICollaborationActionData + +} from '@syncfusion/ej2\-collaborator'; + + +export class DocumentEditorAdapter implements ICollaborationProvider { + + + constructor( + + private container: DocumentEditorContainer, + + private serviceUrl: string, + + ) { } + + + // Fetch the document from the product's REST API and return the room name. + + public async loadFromServer(fileName: string): Promise\ { + + const roomName: string \= this.getRoomName(fileName); + + const response: Response \= await fetch( + + this.serviceUrl \+ 'api/CollaborativeEditing/ImportFile', + + { + + method: 'POST', + + headers: { 'Content\-Type': 'application/json' }, + + body: JSON.stringify({ fileName, roomName }) + + } + + ); + + if (!response.ok) { + + throw new Error('Failed to load document'); + + } + + const responseText: string \= await response.text(); + + await this.open(responseText, roomName); + + return roomName; + + } + + + // Seed the editor and bridge local edits to the editor's sender. + + public async open(responseText: string, roomName: string): Promise\ { + + const data: any \= JSON.parse(responseText); + + this.container?.documentEditor.collaborativeEditingHandlerModule + + ?.updateRoomInfo(roomName, data.version, this.serviceUrl \+ 'api/CollaborativeEditing/'); + + this.container.documentEditor.open(data.sfdt); + + this.container.contentChange \= (args: any) \=\> { + + this.container.documentEditor.collaborativeEditingHandlerModule + + ?.sendActionToServer(args.operations as Operation\[]); + + }; + + } + + + // The only ICollaborationProvider method — applied for every remote action. + + public applyRemoteAction(action: string, data: ICollaborationActionData): void { + + this.container.documentEditor.collaborativeEditingHandlerModule + + ?.applyRemoteAction(action, data.payload); + + } + + + private getRoomName(fileName: string): string { + + const urlParams: URLSearchParams \= new URLSearchParams(window.location.search); + + let roomId: string | null \= urlParams.get('id'); + + + if (!roomId) { + + roomId \= Math.random().toString(32).slice(2); + + window.history.replaceState({}, '', '?id\=' \+ roomId); + + } + + return roomId; + + } + +} +``` + +## Step 3 — Client Wiring (app.ts) + +```ts + +import { DocumentEditorContainer, DocumentEditor, Toolbar, CollaborativeEditingHandler } + + from '@syncfusion/ej2\-documenteditor'; + +import { CollaborationClient, UserInfo } from '@syncfusion/ej2\-collaborator'; + +import { DocumentEditorAdapter } from '../collaboration/DocumentEditorAdapter'; + +import { TitleBar } from './title\-bar'; + + +DocumentEditor.Inject(CollaborativeEditingHandler); + +DocumentEditorContainer.Inject(Toolbar); + + +const serviceUrl: string \= 'http://localhost:62870/'; + + +const documenteditor: DocumentEditorContainer \= new DocumentEditorContainer({ + + enableToolbar: true, + + height: '590px', + + currentUser: currentUser, + + serviceUrl: serviceUrl \+ 'api/documenteditor' // product REST API (open/save SFDT) + +}); + +documenteditor.appendTo('\#DocumentEditor'); + + +documenteditor.documentEditor.enableCollaborativeEditing \= true; + + +const titleBar: TitleBar \= new TitleBar( + + document.getElementById('documenteditor_titlebar') as HTMLElement, + + documenteditor.documentEditor, + + true + +); + +titleBar.updateDocumentTitle(); + + +const adapter: DocumentEditorAdapter \= new DocumentEditorAdapter(documenteditor, serviceUrl); + + +const client: CollaborationClient \= new CollaborationClient(adapter, { + + serviceUrl: "http://localhost:62870", + + connectionType: "signalr", // default + + currentUser: currentUser, + + onUserJoined: (user: UserInfo) => { + + console.log("User Joined", user); + + titleBar.addUser(user); + + }, + + onUserLeft: (user: UserInfo) => { + + console.log("User Left", user); + + titleBar.removeUser(user); + + } + +}); + + +(async () => { + + const roomName: string \= await adapter.loadFromServer("Giant Panda.docx"); + + await client.joinRoomAsync(roomName); + +})(); + +``` +## Step 4 — Serve the client + +Build and serve the front\-end application so the page is reachable at, for example, [http://localhost:4000](http://localhost:4000). + +# Integrate Collaboration Server + +## Step 5 — Install the NuGet packages + +In your ASP.NET Core project, add the Collaboration Server and the Document Editor server\-side helper + +dotnet add package Syncfusion.Collaborator.Server + +dotnet add package [Syncfusion.EJ2.WordEditor.AspNet.Core](https://www.nuget.org/packages/Syncfusion.EJ2.WordEditor.AspNet.Core). + + +## Step 6 — Configure Redis + +Add the connection string in appsettings.json +```C# + +{ + + "ConnectionStrings": { + + "Redis": "" + + } + +} +``` +**Step 7 — Register the Collaboration Server** + +Register the Collaboration Server and configure the Redis connection string during application startup. +```C# +using Syncfusion.Collaboration.Core.Extensions; + + +var builder = WebApplication.CreateBuilder(args); +.. +.. +builder.Services.AddCollaborationServer(options => + +{ + + options.ConnectionString = + + builder.Configuration.GetConnectionString("Redis") + + ?? "localhost:6379"; + + // ConnectionType = ConnectionType.SignalR is the default. Switch to WebSocket to use /ws. + +}); + + +builder.Services.AddSingleton(); + + +builder.Services.AddControllers(); + + +var app = builder.Build(); + + +app.UseStaticFiles(); + +app.UseRouting(); + +app.MapControllers(); + +app.MapCollaborationServer(); // maps /collaborationhub + + +app.Run(); +``` +By default, the ASP.NET Core Collaboration Server uses SignalR. To use WebSocket transport, configure ConnectionType as WebSocket and enable WebSocket support in the application pipeline and  and call app.UseWebSockets(); before MapCollaborationServer() + +## Step 8 — Add the Document Editor adapter + +DocumentEditorAdapter is the control\-specific translator on the server side. PDF Viewer and Spreadsheet applications provide their own adapter implementation, but the overall structure remains the same. + +```C# +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using EJ2DocumentEditorServer.Controllers; +using Microsoft.AspNetCore.Hosting; +using Newtonsoft.Json; +using Syncfusion.Collaboration.Core.Interfaces; +using Syncfusion.Collaboration.Core.Models; +using Syncfusion.Collaboration.Core.Services; +using Syncfusion.EJ2.DocumentEditor; + +namespace EJ2DocumentEditorServer.Adapters; + +public class DocumentEditorCollaborationAdapter : ICollaborationAdapter +{ + private readonly IActionService actionService; + private readonly IBackgroundTaskQueue saveTaskQueue; + static string fileLocation; + private readonly IWebHostEnvironment _hostingEnvironment; + public DocumentEditorCollaborationAdapter(IWebHostEnvironment hostingEnvironment, IBackgroundTaskQueue saveTaskQueue) + { _hostingEnvironment = hostingEnvironment; + fileLocation = _hostingEnvironment.WebRootPath; + this.saveTaskQueue = saveTaskQueue; + } + public CollaborationAction MapControlToGenericAction(object controlAction) + { + var action = (Syncfusion.EJ2.DocumentEditor.ActionInfo)controlAction; + + return new CollaborationAction + { + RoomName = action.RoomName, + ConnectionId = action.ConnectionId, + CurrentUser = action.CurrentUser, + Version = action.Version, + ClientVersion = action.ClientVersion, + IsTransformed = action.IsTransformed, + Data = JsonConvert.SerializeObject(action.Operations) + }; + } + + public object MapGenericToControlAction(CollaborationAction action) + { + return new Syncfusion.EJ2.DocumentEditor.ActionInfo + { + RoomName = action.RoomName, + ConnectionId = action.ConnectionId, + CurrentUser = action.CurrentUser, + Version = action.Version, + ClientVersion = action.ClientVersion, + IsTransformed = action.IsTransformed, + Operations = JsonConvert.DeserializeObject>(action.Data) + }; + } + + + public void TransformOperations(List actions) + { + var documentActions = actions.Select(x => (Syncfusion.EJ2.DocumentEditor.ActionInfo)MapGenericToControlAction(x)).ToList(); + + documentActions.Where(x => !x.IsTransformed).ToList().ForEach(x => CollaborativeEditingHandler.TransformOperation(x, documentActions)); + } + + public async Task SaveOperationsAsync(List actions, string roomName, bool partialSave) + { + var documentActions = actions.Select(x => (Syncfusion.EJ2.DocumentEditor.ActionInfo)MapGenericToControlAction(x)).ToList(); + + var message = new SaveRequest + { + Actions = actions, + PartialSave = partialSave, + RoomName = roomName + }; + + await saveTaskQueue.QueueBackgroundWorkItemAsync(message); + + + } + public async Task ProcessSaveRequestAsync(SaveRequest request, CancellationToken ct) + { + Console.WriteLine("save called"); + // You can get the document master document + Syncfusion.EJ2.DocumentEditor.WordDocument document = CollaborativeEditingController.GetSourceDocument(); + CollaborativeEditingHandler handler = new CollaborativeEditingHandler(document); + //Get actions from Redis + var actions = request.Actions.Select(x => (Syncfusion.EJ2.DocumentEditor.ActionInfo)MapGenericToControlAction(x)).ToList(); + + if (actions.Count > 0) + { + foreach (var action in actions) + { + if (!action.IsTransformed) + { + CollaborativeEditingHandler.TransformOperation(action, actions); + } + } + //Apply the actions to document + foreach (var action in actions) + { + handler.UpdateAction(action); + } + + MemoryStream stream = new MemoryStream(); + //save the updated document in the loaction as per your need. + + Syncfusion.DocIO.DLS.WordDocument doc = WordDocument.Save(Newtonsoft.Json.JsonConvert.SerializeObject(handler.Document)); + + doc.Save(stream, Syncfusion.DocIO.FormatType.Docx); + + SaveDocument(stream, "Getting Started.docx"); + + stream.Close(); + } + + document.Dispose(); + + await actionService.ClearRecordsAsync(request.RoomName, request.PartialSave); + } + + //Document is store in file stream, We can modify the code to store the document to any location based on your requirment. + private void SaveDocument(Stream document, string fileName) + { + string filePath; + if (Path.IsPathRooted(fileName)) + { + filePath = fileName; + } + else + { + filePath = Path.Combine(fileLocation, fileName); + } + + // Ensure target directory exists + var dir = Path.GetDirectoryName(filePath); + if (!string.IsNullOrEmpty(dir) && !Directory.Exists(dir)) + { + Directory.CreateDirectory(dir); + } + + using (FileStream file = new FileStream(filePath, FileMode.Create, FileAccess.Write)) + { + document.Position = 0; // Ensure the stream is at the start + document.CopyTo(file); + } + } + +} + + +``` + + +## Step 9 — Add the collaborative editing controller (web service methods) + +CollaborativeEditingController is the HTTP bridge between the client control and the Common Collaborator. Every EJ2 content editor component that supports collaboration (Document Editor, PDF Viewer, Spreadsheet) exposes the same three web service methods on its collaboration controller. Each method is required: +|**Web service method**|**Why it is needed**| +|:---|:---| +|ImportFile|Called by a joining client to load the source document and replay any pending actions. Returns the document content and current server version.| +|UpdateAction|Called by an editing client to send a new collaboration action. The server persists and transforms the action, then broadcasts it to everyone in the same room.| +|GetActionsFromServer|Called by a joining client to fetch actions newer than its last\-known version, so it can catch up to the current document state.| + +```C# + +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Newtonsoft.Json; +using Syncfusion.Collaboration.Core.Interfaces; +using Syncfusion.Collaboration.Core.Models; +using Syncfusion.Collaboration.Core.Services; +using Syncfusion.Collaboration.Core.Transports; +using Syncfusion.EJ2.DocumentEditor; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +namespace EJ2DocumentEditorServer.Controllers; + +[Route("api/[controller]")] +[ApiController] +public class CollaborativeEditingController : ControllerBase +{ + private static string fileLocation; + private readonly IWebHostEnvironment _hostingEnvironment; + private readonly IActionService actionService; + private readonly ICollaborationAdapter adapter; + private readonly IActiveTransport _transport; + + // Constructor for the CollaborativeEditingController + public CollaborativeEditingController(IWebHostEnvironment hostingEnvironment, + IConfiguration config, IActionService actionService, ICollaborationAdapter adapter, IActiveTransport transport) + { + _hostingEnvironment = hostingEnvironment; + fileLocation = _hostingEnvironment.WebRootPath; + this.adapter = adapter; + this.actionService = actionService; + _transport = transport; + } + + //Import document from wwwroot folder in web server. + [HttpPost] + [Route("ImportFile")] + [EnableCors("AllowAllOrigins")] + public async Task ImportFile([FromBody] FileInfo param) + { + try + { + // Create a new instance of DocumentContent to hold the document data + DocumentContent content = new DocumentContent(); + + Syncfusion.EJ2.DocumentEditor.WordDocument document = GetSourceDocument(); + // Get the list of pending operations for the document + List collaborationActions = await actionService.GetPendingOperationsAsync(param.roomName, 0, -1); + + List actions = + collaborationActions.Select(x => (Syncfusion.EJ2.DocumentEditor.ActionInfo)adapter.MapGenericToControlAction(x)).ToList(); + + if (actions != null && actions.Count > 0) + { + // If there are any pending actions, update the document with these actions + document.UpdateActions(actions); + } + // Serialize the updated document to SFDT format + string sfdt = Newtonsoft.Json.JsonConvert.SerializeObject(document); + content.version = 0; + content.sfdt = sfdt; + // Dispose of the document to free resources + document.Dispose(); + + // Return the serialized content as a JSON string + return Newtonsoft.Json.JsonConvert.SerializeObject(content); + } + catch + { + return null; + } + } + + [HttpPost] + [Route("UpdateAction")] + [EnableCors("AllowAllOrigins")] + public async Task UpdateAction(Syncfusion.EJ2.DocumentEditor.ActionInfo param) + { + // Convert DocumentEditor ActionInfo to CollaborationAction + CollaborationAction collaborationAction = (CollaborationAction)adapter.MapControlToGenericAction(param); + // Process through common package + CollaborationAction modifiedAction = await actionService.AddOperationAsync(collaborationAction, adapter); + // Convert back to DocumentEditor ActionInfo + var documentAction = (Syncfusion.EJ2.DocumentEditor.ActionInfo)adapter.MapGenericToControlAction(modifiedAction); + + await _transport.SendToGroupAsync(param.RoomName, "action", documentAction); + return documentAction; + + } + + [HttpPost] + [Route("GetActionsFromServer")] + [EnableCors("AllowAllOrigins")] + public async Task GetActionsFromServer(Syncfusion.EJ2.DocumentEditor.ActionInfo param) + { + try + { + // Initialize necessary variables from the parameters and helper class + //int saveThreshold = CollaborativeEditingHelper.SaveThreshold; + string roomName = param.RoomName; + int lastSyncedVersion = param.Version; + int clientVersion = param.Version; + + // Retrieve the database connection + // IDatabase database = _redisConnection.GetDatabase(); + + // Fetch actions that are effective and pending based on the last synced version + List collaborationActions = await actionService.GetEffectivePendingVersionAsync(roomName, lastSyncedVersion); + + + List actions = collaborationActions.Select(x => (Syncfusion.EJ2.DocumentEditor.ActionInfo)adapter.MapGenericToControlAction(x)).ToList(); + + // Increment the version for each action sequentially + actions.ForEach(action => action.Version = ++clientVersion); + + // Filter actions to only include those that are newer than the client's last known version + actions = actions.Where(action => action.Version > lastSyncedVersion).ToList(); + + // Transform actions that have not been transformed yet + actions.Where(action => !action.IsTransformed).ToList() + .ForEach(action => CollaborativeEditingHandler.TransformOperation(action, actions)); + + // Serialize the filtered and transformed actions to JSON and return + return Newtonsoft.Json.JsonConvert.SerializeObject(actions); + } + catch + { + // In case of an exception, return an empty JSON object + return "{}"; + } + } + + internal static Syncfusion.EJ2.DocumentEditor.WordDocument GetSourceDocument() + { + string path = fileLocation + "\\Giant Panda.docx"; + int index = path.LastIndexOf('.'); + string type = index > -1 && index < path.Length - 1 ? + path.Substring(index) : ".docx"; + Stream stream = System.IO.File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read); + Syncfusion.EJ2.DocumentEditor.WordDocument document = Syncfusion.EJ2.DocumentEditor.WordDocument.Load(stream, FormatType.Docx); + stream.Dispose(); + return document; + } + public class DocumentContent + { + public int version { get; set; } + + public string sfdt { get; set; } + + } + public class FileInfo + { + public string fileName + { + get; + set; + } + public string roomName + { + get; + set; + } + } +} +``` +## Step 10 - Run the Application + +After completing the client and server setup: + +1. Start the Redis server. +2. Run the ASP.NET Core application. + +```bash +dotnet run +``` + +3. Run the client application. + +```bash +npm start +``` + +4. Open the application in multiple browser windows or tabs. + +Example: + +```text +http://localhost:4000/?name=User1 +``` + +```text +http://localhost:4000/?name=User2 +``` + +5. Open the same document and make changes in one window. + +### Result + +- Changes are synchronized automatically across all connected users. +- User join and leave events are reflected in real time. +- Editing operations are stored in Redis and processed by the Collaboration Server. +- Document changes are automatically saved when the configured `SaveThreshold` is reached.