Skip to main content
.NETFramework4.6.2

Syncfusion® Collaborative Editing Server for ASP.NET MVC enables real-time collaboration using native WebSocket communication and redis-backed synchronization.

Published
Sep 29, 2026
25d ago
Total Downloads
349
349

Install

dotnet add package Syncfusion.Collaborator.Server.AspNet.Mvc --version 35.1.37
ProductVersions
Other
.NETFramework4.6.2

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 MVC Collaboration Server is provided through the Syncfusion.Collaborator.Server.AspNet.Mvc package. It supports WebSocket transport 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-mvc/llms.txt

Supported Transport

  • WebSocket

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 MVC project, add the Collaboration Server package.

Syncfusion.Collaborator.Server.AspNet.Mvc

1.Register the Collaboration Server

Open Global.asax.cs and register the Collaboration Server services during application startup. Configure the Redis connection and select WebSocket as the transport type. For ASP.NET MVC, the WebSocket transport is selected by setting CollaborationConnectionType.WebSocket. The adapter is registered through ServiceCollectionExtensions.RegisterAdapter(...).

using Syncfusion.Collaboration.Core.Extensions;
using Syncfusion.Collaboration.Core.Interfaces;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using WebApplication1.Adapter;

namespace WebApplication1
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            GlobalConfiguration.Configure(WebApiConfig.Register);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            ServiceCollectionExtensions.RegisterAdapter(
                new DocumentEditorCollaborationAdapter());
            ServiceCollectionExtensions.AddCollaborationServer(options =>
            {
                options.ConnectionString =
                    System.Configuration.ConfigurationManager
                        .ConnectionStrings["Redis"]?.ConnectionString
                    ?? "localhost:6379";
                options.ConnectionType = CollaborationConnectionType.WebSocket;
            });
        }
    }
}

2. Mount the WebSocket endpoint (Startup.cs)

Add an OWIN startup class to expose the Collaboration Server WebSocket endpoint. Incoming WebSocket requests are forwarded to the Collaboration Server transport handler.

[assembly: OwinStartup(typeof(WebApplication1.Startup))]
namespace WebApplication1
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.Map("/ws", map =>
            {
                map.Run(context =>
                {
                    var httpContext = HttpContext.Current;
                    if (httpContext == null)
                    {
                        context.Response.StatusCode = 500;
                        return Task.CompletedTask;
                    }
                    if (!httpContext.IsWebSocketRequest)
                    {
                        context.Response.StatusCode = 400;
                        return context.Response.WriteAsync(
                            "WebSocket connection required.");
                    }                    
                    httpContext.AcceptWebSocketRequest(
     ServiceCollectionExtensions.MapCollaborationServer());
                    return Task.CompletedTask;
                });
            });
        }       
    }
}

3.Add the DOCX Editor adapter

The collaboration adapter acts as a bridge between control-specific collaboration actions and the Common Collaborator data model. Each collaborative component implements its own adapter while sharing the same server infrastructure.


using Newtonsoft.Json;
using Syncfusion.Collaboration.Core.Interfaces;
using Syncfusion.Collaboration.Core.Models;
using Syncfusion.EJ2.DocumentEditor;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web;

namespace WebApplication1.Adapter
{
    // Translates between Document Editor actions and the common collaboration model.
    public class DocumentEditorCollaborationAdapter : ICollaborationAdapter
    {
        // Converts a control-specific action into a generic collaboration action.
        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)
            };
        }

        // Converts a generic collaboration action back to a Document Editor action.
        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)
            };
        }

        // Saves incoming changes to the server-side store.
        public async Task ProcessSaveRequestAsync(SaveRequest request, CancellationToken cancellationToken)
        {
            // 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);
                    }
                }

                foreach (var action in actions)
                {
                    handler.UpdateAction(action);
                }

                MemoryStream stream = new MemoryStream();

                Syncfusion.DocIO.DLS.WordDocument doc =
                    WordDocument.Save(Newtonsoft.Json.JsonConvert.SerializeObject(handler.Document));

                doc.Save(stream, Syncfusion.DocIO.FormatType.Docx);
                stream.Close();
            }

            document.Dispose();

            // Clear persisted operations after save completes
            var actionService = CollaborationServiceContainer.Resolve<IActionService>();
            await actionService.ClearRecordsAsync(request.RoomName, request.PartialSave);
        }

  public async Task SaveOperationsAsync(List<CollaborationAction> actions, string roomName, bool partialSave)
  {
      var message = new SaveRequest
      {
          Actions = actions,
          PartialSave = partialSave,
          RoomName = roomName
      };

      var queue = CollaborationServiceContainer.Resolve<IBackgroundTaskQueue>();
      await queue.QueueBackgroundWorkItemAsync(message);
  }


        // Replays untransformed operations so concurrent edits stay in sync.
        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));

        }
    }
}

4. 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 (DOCX Editor, PDF Viewer, Spreadsheet) exposes the same three web service methods on its collaboration controller.


using Newtonsoft.Json;
using Syncfusion.Collaboration.Core.Extensions;
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.Net;
using System.Net.Http;
using System.Threading.Tasks;
using System.Web;
using System.Web.Hosting;
using System.Web.Http;
using System.Web.Http.Cors;
using EJ2WordDocument = Syncfusion.EJ2.DocumentEditor.WordDocument;
namespace WebApplication1.Controllers
{
     [RoutePrefix("api/CollaborativeEditing")]
    public class CollaborativeEditingController : ApiController
    {
        // Stores the source document location under App_Data.
        private static string fileLocation;

        // Handles collaboration operations for the current room.
        private readonly IActionService actionService;
        // Converts between control actions and common collaboration actions.
        private readonly ICollaborationAdapter adapter;
        // Broadcasts updates to all connected clients in the room.
        private readonly IActiveTransport transport;

        // Resolves shared services from the collaboration container.
        public CollaborativeEditingController()
        {
            fileLocation = HostingEnvironment.MapPath("~/App_Data");

            actionService =
                CollaborationServiceContainer.Resolve<IActionService>();

            adapter =
                CollaborationServiceContainer.Resolve<ICollaborationAdapter>();

            transport =
                CollaborationServiceContainer.Resolve<IActiveTransport>();
        }

        // Uses dependency injection when the controller is created by the framework.
        public CollaborativeEditingController(
            IActionService actionService,
            ICollaborationAdapter adapter,
            IActiveTransport transport)
        {
            fileLocation = HostingEnvironment.MapPath("~/App_Data");

            this.actionService = actionService;
            this.adapter = adapter;
            this.transport = transport;
        }

        // Loads the document and applies any pending collaboration changes.
        [HttpPost]
        [Route("ImportFile")]
        public async Task<HttpResponseMessage> ImportFile(FileInfo param)
        {
            try
            {
                DocumentContent content = new DocumentContent();

                EJ2WordDocument document = GetSourceDocument();

                List<CollaborationAction> collaborationActions =
                    await actionService.GetPendingOperationsAsync(
                        param.roomName,
                        0,
                        -1);

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

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

                string sfdt = JsonConvert.SerializeObject(document);

                content.version = 0;
                content.sfdt = sfdt;

                document.Dispose();

                return Request.CreateResponse(
    HttpStatusCode.OK,
    content);
            }
            catch (Exception ex)
            {
                return Request.CreateErrorResponse(
                    System.Net.HttpStatusCode.InternalServerError,
                    ex);
            }
        }



                // Accepts an editing action, stores it, and sends it to other participants.
        [HttpPost]
        [Route("UpdateAction")]
        public async Task<Syncfusion.EJ2.DocumentEditor.ActionInfo> 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 _hubContext.Clients.Group(param.RoomName).SendAsync("dataReceived", "action", documentAction);

            await transport.SendToGroupAsync(param.RoomName, "action", documentAction);
            return documentAction;

        }

        // Returns actions that the client has not yet synchronized.
        [HttpPost]
        [Route("GetActionsFromServer")]       
        public async Task<string> 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<CollaborationAction> collaborationActions = await actionService.GetEffectivePendingVersionAsync(roomName, lastSyncedVersion);


                List<Syncfusion.EJ2.DocumentEditor.ActionInfo> 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 "{}";
            }
        }
    }
}

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

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.Compatibility metadata lists 1 target framework; confirm the exact target used by your application.

Related Guides & Workflows

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