Skip to main content
Any Framework

Syncfusion® Collaborator Server is a reusable server-side package that enables real-time collaborative editing capabilities in web applications. It provides a collaboration infrastructure built on Redis-backed synchronization and supports both SignalR and native WebSocket communication transports.

Published
Oct 6, 2026
17d ago
Total Downloads
462
462

Install

dotnet add package Syncfusion.Collaborator.Server.AspNet.Core --version 35.1.39

No framework information available.

README

Syncfusion.Collaborator.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 DOCX Editor, PDF Viewer, and Spreadsheet.


Overview of the Project

The ASP.NET Core Collaboration Server is provided through the Syncfusion.Collaborator.Server.AspNet.Core package. It supports both SignalR and WebSocket transports and is recommended for modern .NET applications.

Using this package with an AI coding agent

Before generating Syncfusion code, ask your AI coding agent to run the Syncfusion setup using the following prompt:

Set up this project for Syncfusion before generating code. Fetch and follow the official instructions at https://ai.syncfusion.com/ then report the detected platform, installed skill pack, MCP status, and any required licensing action.

The setup detects your project’s platform and loads the appropriate Syncfusion skill pack. No Syncfusion account or MCP key is required to install or read Syncfusion agent skills.

Platform reference: https://ai.syncfusion.com/aspnet-core/llms.txt

Supported Transports

  • SignalR (default)

  • WebSocket


Integration Guidelines

  1. Reference the common package in your server project: dotnet add package Syncfusion.Collaborator.Server.AspNet.Core
  2. Register services in Program.cs and pick a transport:
    • AddCollaborationServer(o => { /* defaults: ConnectionType = SignalR */ }), or
    • AddCollaborationServer(o => o.ConnectionType = CollaborationConnectionType.WebSocket).
  3. Map endpoints in Program.cs: app.MapCollaborationServer(); routes the right endpoint automatically based on the chosen transport (/collaborationhub for SignalR or /ws for WebSocket).
  4. Create your adapter (DocumentEditorAdapter.cs / PdfViewerAdapter.cs / SpreadsheetAdapter.cs):
    • Implements ICollaborationAdapter (provided by the common package).
    • Maps your control's native action ↔ CollaborationAction via MapControlToGenericAction / MapGenericToControlAction.
    • Plugs in your control's OT/transform routine inside TransformOperations.
  5. Create your controller (CollaborativeEditingController.cs):
    • Inject IActiveTransport (transport-agnostic, recommended) or IHubContext<CollaborationHub> (SignalR-specific).
    • Convert in → call service → convert out → broadcast.

Public API

The interfaces below are what product teams implement or call. Everything else is internal.

ICollaborationAdapter — adapter contract

Member Purpose
MapControlToGenericAction(object controlAction) Convert a control-specific action into a CollaborationAction.
MapGenericToControlAction(CollaborationAction action) Convert a CollaborationAction into a control-specific action.
TransformOperations(List<CollaborationAction> actions) Run your control's OT over a batch of actions
SaveOperationsAsync(actions, roomName, partialSave) Queues pending actions for document save processing.
ProcessSaveRequestAsync(SaveRequest, cancellationToken) Processes the queued save request and persists the document.

IActionService — common service API

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 ConnectionType is a single enum choice — set it to SignalR or WebSocket.
SaveThreshold 100 The save threshold (in actions) after which a pending save is flushed to the document.

Sample Usage

This section shows how a consumer application wires up and integrates the common package. The reference controller and adapter below are the actual implementations from the Document Editor project, provided as a template for PDF Viewer / Spreadsheet teams.

Install the NuGet package

In your ASP.NET Core project, add the Collaboration Server package.

Syncfusion.Collaborator.Server.AspNet.Core

1. Program.cs Setup

The choice of transport is made here. Set ConnectionType to one of the two enum values.

SignalR transport (default)

using Syncfusion.Collaboration.Core.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCollaborationServer(options =>
{
    options.ConnectionString =
        builder.Configuration.GetConnectionString("Redis")
        ?? "localhost:6379";
    // ConnectionType = SignalR is the default.
});

builder.Services.AddSingleton<ICollaborationAdapter, DocumentEditorAdapter>();

var app = builder.Build();
app.MapCollaborationServer(); // maps /collaborationhub
app.Run();

WebSocket transport (ASP.NET Core)

using Syncfusion.Collaboration.Core.Extensions;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddCollaborationServer(options =>
{
    options.ConnectionString =
        builder.Configuration.GetConnectionString("Redis")
        ?? "localhost:6379";
    options.ConnectionType = CollaborationConnectionType.WebSocket;    
});

builder.Services.AddSingleton<ICollaborationAdapter, DocumentEditorAdapter>();

var app = builder.Build();
app.UseWebSockets();          // required for the WebSocket transport
app.MapCollaborationServer(); // maps /ws
app.Run();

2. Reference Controller — CollaborativeEditingController.cs

The HTTP bridge between the client control and the common package. Inject IActiveTransport (transport-agnostic) or IHubContext<CollaborationHub> (SignalR-specific) — both are wired automatically.

public class CollaborativeEditingController : ControllerBase
{
    private readonly IActiveTransport transport; // transport-agnostic
    private readonly IActionService actionService;
    private readonly ICollaborationAdapter adapter;

    public CollaborativeEditingController(
        IActiveTransport transport,
        IActionService actionService,
        ICollaborationAdapter adapter)
    {
        this.transport = transport;
        this.actionService = actionService;
        this.adapter = adapter;
    }
    // ...ImportFile / UpdateAction / GetActionsFromServer as below
}

ImportFile

[HttpPost]
[Route("ImportFile")]
public async Task<string> ImportFile([FromBody] FileInfo param)
{
    DocumentContent content = new DocumentContent();
    Syncfusion.EJ2.DocumentEditor.WordDocument document = GetSourceDocument();

    // Fetch pending actions for the room and reapply them on top of the source document
    List<CollaborationAction> collaborationActions =
        await actionService.GetPendingOperationsAsync(param.roomName, 0, -1);

    List<Syncfusion.EJ2.DocumentEditor.ActionInfo> actions =
        collaborationActions
            .Select(x => (Syncfusion.EJ2.DocumentEditor.ActionInfo)adapter.MapGenericToControlAction(x))
            .ToList();

    if (actions != null && actions.Count > 0)
        document.UpdateActions(actions);

    content.version = 0;
    content.sfdt = Newtonsoft.Json.JsonConvert.SerializeObject(document);
    document.Dispose();
    return Newtonsoft.Json.JsonConvert.SerializeObject(content);
}

UpdateAction

[HttpPost]
[Route("UpdateAction")]
public async Task<Syncfusion.EJ2.DocumentEditor.ActionInfo> UpdateAction(
    Syncfusion.EJ2.DocumentEditor.ActionInfo param)
{
    // Control action -> common model
    CollaborationAction collaborationAction = adapter.MapControlToGenericAction(param);

    // Hand off to the common package (persist + transform)
    CollaborationAction modifiedAction = await actionService.AddOperationAsync(collaborationAction, adapter);

    // Common model -> control action
    var documentAction = (Syncfusion.EJ2.DocumentEditor.ActionInfo)adapter.MapGenericToControlAction(modifiedAction);

    // Broadcast to everyone in the room (transport-agnostic — works for SignalR and WebSocket)
    await transport.SendToGroupAsync(param.RoomName, "action", documentAction);

    return documentAction;
}

GetActionsFromServer

[HttpPost]
[Route("GetActionsFromServer")]
public async Task<string> GetActionsFromServer(Syncfusion.EJ2.DocumentEditor.ActionInfo param)
{
    string roomName = param.RoomName;
    int lastSyncedVersion = param.Version;
    int clientVersion = param.Version;

    List<CollaborationAction> collaborationActions =
        await actionService.GetEffectivePendingVersionAsync(roomName, lastSyncedVersion);

    List<Syncfusion.EJ2.DocumentEditor.ActionInfo> actions =
        collaborationActions
            .Select(x => (Syncfusion.EJ2.DocumentEditor.ActionInfo)adapter.MapGenericToControlAction(x))
            .ToList();

    actions.ForEach(action => action.Version = ++clientVersion);
    actions = actions.Where(action => action.Version > lastSyncedVersion).ToList();

    actions.Where(action => !action.IsTransformed).ToList()
        .ForEach(action => CollaborativeEditingHandler.TransformOperation(action, actions));

    return Newtonsoft.Json.JsonConvert.SerializeObject(actions);
}

3. Reference Adapter — DocumentEditorAdapter.cs

The control-specific translator. PDF Viewer / Spreadsheet teams replace this with their own, but the shape is identical.

public class DocumentEditorAdapter : ICollaborationAdapter
{
    private readonly IBackgroundTaskQueue saveTaskQueue;

    public DocumentEditorAdapter(IBackgroundTaskQueue saveTaskQueue)
    {
        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) // control-specific operations, serialised
        };
    }

    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<List<DocumentOperation>>(action.Data)
        };
    }

    public void TransformOperations(List<CollaborationAction> 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<CollaborationAction> actions, string roomName, bool partialSave)
    {
        var message = new SaveRequest
        {
            Actions = actions,
            PartialSave = partialSave,
            RoomName = roomName
        };
        await saveTaskQueue.QueueBackgroundWorkItemAsync(message);
    }

    public async Task ProcessSaveRequestAsync(SaveRequest request, CancellationToken cancellationToken)
    {
        // Build up the document by replaying the queued actions, then persist it.
        // Concrete format (docx / pdf / xlsx) is your responsibility.
        // Remember to call actionService.ClearRecordsAsync(request.RoomName, request.PartialSave) once saved.
    }
}

Replace Syncfusion.EJ2.DocumentEditor.ActionInfo and DocumentOperation with your control's native action types — the method signatures stay the same.

Changes from Previous Version

No structural changes between v35.1.37 and v35.1.39.

Adoption Assessment

A verified publisher signal is available. Assess fit from the package's supported frameworks, license and dependency graph rather than popularity alone.

No SPDX license expression is published. Review the license text and your organisation's policy before redistribution.No target framework metadata is available. Confirm compatibility in a representative project before standardising on this package.

Related Guides & Workflows

Learn how to evaluate, integrate, or audit this package in your .NET project.