From 112bb7080fb6bd637117ef719f4c3be4eb5a8fe9 Mon Sep 17 00:00:00 2001 From: Babu Periyasamy Date: Thu, 1 Oct 2026 21:06:31 +0530 Subject: [PATCH] 1055046: Updated the server files and README file. --- README.md | 206 ++++++++- .../SpreadsheetCollaborativeAdaptor.cs | 0 .../CollaborativeEditingController.cs | 0 ...eadsheetCollaborativeEditing.Server.csproj | 0 .../SpreadsheetCollaborativeAdaptor.cs | 195 ++++++++ .../CollaborativeEditingController.cs | 420 ++++++++++++++++++ .../Controllers/SpreadsheetController.cs | 106 +++++ .../aspnet-core/Controllers/TestController.cs | 44 ++ .../Models/SpreadsheetSelectionInfo.cs | 0 server/aspnet-core/NuGet.Config | 17 + .../Program.cs | 225 +++++----- .../Properties/launchSettings.json | 88 ++-- server/aspnet-core/SyncfusionLicense.txt | 1 + .../appsettings.Development.json | 20 +- .../appsettings.json | 38 +- .../aspnet-core/ej2-spreadsheet-server.csproj | 27 ++ server/aspnet-core/ej2-spreadsheet-server.sln | 25 ++ server/aspnet-core/web.config | 17 + .../wwwroot/Files/Sample.xlsx | Bin 19 files changed, 1242 insertions(+), 187 deletions(-) delete mode 100644 server/aspnet-core-redis/Adapters/SpreadsheetCollaborativeAdaptor.cs delete mode 100644 server/aspnet-core-redis/Controllers/CollaborativeEditingController.cs delete mode 100644 server/aspnet-core-redis/SpreadsheetCollaborativeEditing.Server.csproj create mode 100644 server/aspnet-core/Adapters/SpreadsheetCollaborativeAdaptor.cs create mode 100644 server/aspnet-core/Controllers/CollaborativeEditingController.cs create mode 100644 server/aspnet-core/Controllers/SpreadsheetController.cs create mode 100644 server/aspnet-core/Controllers/TestController.cs rename server/{aspnet-core-redis => aspnet-core}/Models/SpreadsheetSelectionInfo.cs (100%) create mode 100644 server/aspnet-core/NuGet.Config rename server/{aspnet-core-redis => aspnet-core}/Program.cs (95%) rename server/{aspnet-core-redis => aspnet-core}/Properties/launchSettings.json (96%) create mode 100644 server/aspnet-core/SyncfusionLicense.txt rename server/{aspnet-core-redis => aspnet-core}/appsettings.Development.json (94%) rename server/{aspnet-core-redis => aspnet-core}/appsettings.json (66%) create mode 100644 server/aspnet-core/ej2-spreadsheet-server.csproj create mode 100644 server/aspnet-core/ej2-spreadsheet-server.sln create mode 100644 server/aspnet-core/web.config rename server/{aspnet-core-redis => aspnet-core}/wwwroot/Files/Sample.xlsx (100%) diff --git a/README.md b/README.md index c19c241..7d39919 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,204 @@ -# Spreadsheet-Collaborative-Editing -This repository showcases real-time collaborative editing using the Syncfusion® Spreadsheet Editor, allowing multiple users to edit the same workbook simultaneously. +# Spreadsheet Collaborative Editing + +This repository showcases real-time collaborative editing using the Syncfusion® Spreadsheet Editor, allowing multiple users to edit the same workbook simultaneously. The project demonstrates how to implement collaborative features across multiple frontend frameworks (Angular, React, Vue, and TypeScript) with an ASP.NET Core backend. + +## Features + +- Real-time collaborative editing of spreadsheets +- Support for multiple frontend frameworks: + - Angular + - React + - Vue + - TypeScript (Vanilla) +- ASP.NET Core backend with SignalR for real-time communication +- Redis integration for collaboration state management +- Spreadsheet operations synchronization across multiple clients +- Selection highlighting for other users' cursor positions + +## Prerequisites + +### Backend +- .NET 10.0 SDK +- Redis server (for collaboration state management) +- Syncfusion license key + +### Frontend +- Node.js (v18 or later) +- npm or yarn + +## Project Structure + +``` +├── client/ +│ ├── angular/ # Angular implementation +│ ├── react/ # React implementation +│ ├── typescript/ # TypeScript/Vanilla implementation +│ └── vue/ # Vue implementation +├── server/ +│ └── aspnet-core/ # ASP.NET Core backend with SignalR +└── README.md +``` + +## Setup + +### Backend Setup + +1. Navigate to the server directory: + ```bash + cd server/aspnet-core + ``` + +2. Configure your Syncfusion license: + - Add your license key to `SyncfusionLicense.txt` file + - Or set the `SYNCFUSION_LICENSE_KEY` environment variable + +3. Configure Redis connection: + - Update the Redis connection string in `appsettings.json`: + ```json + { + "ConnectionStrings": { + "Redis": "your_redis_connection_string" + } + } + ``` + +4. Run the server: + ```bash + dotnet run + ``` + + The server will start on `https://localhost:5001` by default. + +### Frontend Setup + +Choose one of the client implementations: + +#### Angular + +1. Navigate to the Angular client: + ```bash + cd client/angular + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +3. Update the service URL in `src/components/spreadsheet-editor/spreadsheet-editor.component.ts`: + ```typescript + const serviceUrl: string = 'https://localhost:5001/'; + ``` + +4. Run the application: + ```bash + npm start + ``` + + The application will be available at `http://localhost:4200`. + +#### React + +1. Navigate to the React client: + ```bash + cd client/react + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +3. Update the service URL in `src/SpreadsheetEditorAdapter.ts`: + ```typescript + const serviceUrl: string = 'https://localhost:5001/'; + ``` + +4. Run the application: + ```bash + npm run dev + ``` + + The application will be available at `http://localhost:5173`. + +#### Vue + +1. Navigate to the Vue client: + ```bash + cd client/vue + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +3. Update the service URL in `src/SpreadsheetEditorAdapter.ts`: + ```typescript + const serviceUrl: string = 'https://localhost:5001/'; + ``` + +4. Run the application: + ```bash + npm run dev + ``` + + The application will be available at `http://localhost:5173`. + +#### TypeScript (Vanilla) + +1. Navigate to the TypeScript client: + ```bash + cd client/typescript + ``` + +2. Install dependencies: + ```bash + npm install + ``` + +3. Update the service URL in `src/spreadsheet-editor-adapter.ts`: + ```typescript + const serviceUrl: string = 'https://localhost:5001/'; + ``` + +4. Run the application: + ```bash + npm run dev + ``` + + The application will be available at `http://localhost:5173`. + +## Usage + +1. Start the backend server +2. Start one of the frontend clients +3. Open the application in multiple browser windows or devices +4. Each user will be assigned a unique room ID automatically +5. To collaborate in the same room, share the URL with the room ID parameter +6. All users in the same room will see real-time updates as others edit the spreadsheet + +## API Endpoints + +The backend provides the following API endpoints: + +- `POST /api/CollaborativeEditing/ImportFile` - Import a file for collaborative editing + +## Contributing + +Contributions are welcome! Please follow these steps: + +1. Fork the repository +2. Create a feature branch +3. Commit your changes +4. Push to the branch +5. Create a pull request + +## License + +This project uses Syncfusion Essential JS 2 components, which are licensed under either the Syncfusion Community License Program or the Syncfusion commercial license. + +Please refer to the [license](license) file for more information. + +To use this product, you must agree to and abide by Syncfusion's license containing all terms and conditions, which can be found at: +https://www.syncfusion.com/content/downloads/syncfusion_license.pdf diff --git a/server/aspnet-core-redis/Adapters/SpreadsheetCollaborativeAdaptor.cs b/server/aspnet-core-redis/Adapters/SpreadsheetCollaborativeAdaptor.cs deleted file mode 100644 index e69de29..0000000 diff --git a/server/aspnet-core-redis/Controllers/CollaborativeEditingController.cs b/server/aspnet-core-redis/Controllers/CollaborativeEditingController.cs deleted file mode 100644 index e69de29..0000000 diff --git a/server/aspnet-core-redis/SpreadsheetCollaborativeEditing.Server.csproj b/server/aspnet-core-redis/SpreadsheetCollaborativeEditing.Server.csproj deleted file mode 100644 index e69de29..0000000 diff --git a/server/aspnet-core/Adapters/SpreadsheetCollaborativeAdaptor.cs b/server/aspnet-core/Adapters/SpreadsheetCollaborativeAdaptor.cs new file mode 100644 index 0000000..2c222c1 --- /dev/null +++ b/server/aspnet-core/Adapters/SpreadsheetCollaborativeAdaptor.cs @@ -0,0 +1,195 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Syncfusion.Collaboration.Core.Interfaces; +using Syncfusion.Collaboration.Core.Models; +using Syncfusion.Collaboration.Core.Services; +using Syncfusion.EJ2.Spreadsheet; + +namespace EJ2SpreadsheetServer.Adapters +{ + public class SpreadsheetCollaborativeAdaptor : + ICollaborationAdapter + { + private static readonly JsonSerializerSettings + OperationSettings = new JsonSerializerSettings + { + NullValueHandling = + NullValueHandling.Ignore, + ContractResolver = + new DefaultContractResolver + { + NamingStrategy = + new CamelCaseNamingStrategy() + } + }; + + private readonly IServiceScopeFactory + serviceScopeFactory; + + private readonly IBackgroundTaskQueue + saveTaskQueue; + + public SpreadsheetCollaborativeAdaptor( + IBackgroundTaskQueue saveTaskQueue, + IServiceScopeFactory serviceScopeFactory) + { + this.saveTaskQueue = saveTaskQueue; + this.serviceScopeFactory = + serviceScopeFactory; + } + + public CollaborationAction + MapControlToGenericAction( + object controlAction) + { + ActionInfo action = + (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, + OperationSettings + ) + }; + } + + public object MapGenericToControlAction( + CollaborationAction action) + { + return new ActionInfo + { + RoomName = action.RoomName, + ConnectionId = action.ConnectionId, + CurrentUser = action.CurrentUser, + Version = action.Version, + ClientVersion = action.ClientVersion, + IsTransformed = action.IsTransformed, + Operations = + JsonConvert.DeserializeObject< + List>( + action.Data, + OperationSettings + ) + }; + } + + public void TransformOperations( + List actions) + { + if (actions == null || actions.Count < 2) + { + return; + } + + List spreadsheetActions = + actions + .Select(action => + MapGenericToControlAction( + action + ) as ActionInfo + ) + .Where(action => action != null) + .ToList(); + + if (spreadsheetActions.Count < 2) + { + return; + } + + bool transformed = + CollaborativeEditingHandler + .TransformOperations( + spreadsheetActions + ); + + if (!transformed) + { + return; + } + + ActionInfo transformedTarget = + spreadsheetActions[ + spreadsheetActions.Count - 1 + ]; + + CollaborationAction targetAction = + actions[actions.Count - 1]; + + targetAction.Data = + JsonConvert.SerializeObject( + transformedTarget.Operations, + OperationSettings + ); + + targetAction.IsTransformed = + transformedTarget.IsTransformed; + } + + public async Task SaveOperationsAsync( + List actions, + string roomName, + bool partialSave) + { + if (actions == null || + actions.Count == 0 || + string.IsNullOrWhiteSpace(roomName)) + { + return; + } + + SaveRequest saveRequest = new SaveRequest + { + Actions = actions, + PartialSave = partialSave, + RoomName = roomName + }; + + await saveTaskQueue + .QueueBackgroundWorkItemAsync( + saveRequest + ); + } + + public async Task ProcessSaveRequestAsync( + SaveRequest request, + CancellationToken cancellationToken) + { + if (request == null || + string.IsNullOrWhiteSpace( + request.RoomName + )) + { + return; + } + + cancellationToken + .ThrowIfCancellationRequested(); + + using (IServiceScope serviceScope = + serviceScopeFactory.CreateScope()) + { + IActionService actionService = + serviceScope.ServiceProvider + .GetRequiredService< + IActionService>(); + + await actionService.ClearRecordsAsync( + request.RoomName, + request.PartialSave + ); + } + } + } +} diff --git a/server/aspnet-core/Controllers/CollaborativeEditingController.cs b/server/aspnet-core/Controllers/CollaborativeEditingController.cs new file mode 100644 index 0000000..ed826f9 --- /dev/null +++ b/server/aspnet-core/Controllers/CollaborativeEditingController.cs @@ -0,0 +1,420 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; +using Newtonsoft.Json.Serialization; +using Syncfusion.Collaboration.Core.Interfaces; +using Syncfusion.Collaboration.Core.Models; +using Syncfusion.Collaboration.Core.Services; +using Syncfusion.Collaboration.Core.Transports; +using Syncfusion.EJ2.Spreadsheet; +using Syncfusion.XlsIO; + +namespace EJ2SpreadsheetServer.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class CollaborativeEditingController : ControllerBase + { + private static readonly ConcurrentDictionary< + string, + ConcurrentDictionary> + RoomSelections = + new ConcurrentDictionary< + string, + ConcurrentDictionary< + string, + SpreadsheetSelectionInfo>>(); + + private readonly IWebHostEnvironment hostingEnvironment; + private readonly IActionService actionService; + private readonly ICollaborationAdapter adapter; + private readonly IActiveTransport transport; + + private static readonly JsonSerializerSettings + ControllerJsonSettings = new JsonSerializerSettings + { + NullValueHandling = NullValueHandling.Ignore, + ContractResolver = new DefaultContractResolver + { + NamingStrategy = new CamelCaseNamingStrategy() + } + }; + + public CollaborativeEditingController( + IWebHostEnvironment hostingEnvironment, + IActionService actionService, + ICollaborationAdapter adapter, + IActiveTransport transport) + { + this.hostingEnvironment = hostingEnvironment; + this.actionService = actionService; + this.adapter = adapter; + this.transport = transport; + } + + [HttpPost] + [Route("ImportFile")] + [EnableCors("AllowAllOrigins")] + public async Task ImportFile( + [FromBody] FileInfo param) + { + if (param == null || + string.IsNullOrWhiteSpace(param.roomName)) + { + return null; + } + + string filePath = Path.Combine( + hostingEnvironment.WebRootPath, + "Files", + "Sample.xlsx" + ); + + if (!System.IO.File.Exists(filePath)) + { + return null; + } + + try + { + List collaborationActions = + await actionService.GetPendingOperationsAsync( + param.roomName, + 0, + -1 + ); + + List spreadsheetActions = + collaborationActions == null + ? new List() + : collaborationActions + .Select(action => + adapter.MapGenericToControlAction( + action + ) as ActionInfo + ) + .Where(action => action != null) + .OrderBy(action => action.Version) + .ToList(); + + using (ExcelEngine temporaryExcelEngine = + new ExcelEngine()) + { + IApplication application = + temporaryExcelEngine.Excel; + + IWorkbook temporaryWorkbook = + application.Workbooks.Open(filePath); + + try + { + if (spreadsheetActions.Count > 0) + { + CollaborativeEditingHandler handler = + new CollaborativeEditingHandler( + temporaryWorkbook + ); + + foreach (ActionInfo action in + spreadsheetActions) + { + handler.UpdateAction(action); + } + } + + using (MemoryStream workbookStream = + new MemoryStream()) + { + temporaryWorkbook.SaveAs( + workbookStream + ); + + workbookStream.Position = 0; + + string clientFileName = + string.IsNullOrWhiteSpace( + param.fileName + ) + ? "Sample" + : param.fileName; + + IFormFile formFile = new FormFile( + workbookStream, + 0, + workbookStream.Length, + clientFileName, + "Sample.xlsx" + ); + + OpenRequest openRequest = + new OpenRequest + { + File = formFile + }; + + string workbookJson = + Workbook.Open(openRequest); + + int currentVersion = + spreadsheetActions.Count > 0 + ? spreadsheetActions.Max( + action => action.Version + ) + : 0; + + DocumentContent content = + new DocumentContent + { + sfdt = workbookJson, + version = currentVersion + }; + + return JsonConvert.SerializeObject( + content + ); + } + } + finally + { + temporaryWorkbook.Close(); + } + } + } + catch (Exception exception) + { + Console.WriteLine( + "Spreadsheet import failed: " + + exception + ); + + return null; + } + } + + [HttpPost] + [Route("UpdateAction")] + [EnableCors("AllowAllOrigins")] + public async Task UpdateAction( + [FromBody] ActionInfo param) + { + if (param == null || + string.IsNullOrWhiteSpace(param.RoomName)) + { + return null; + } + + CollaborationAction collaborationAction = + adapter.MapControlToGenericAction(param); + + CollaborationAction modifiedAction = + await actionService.AddOperationAsync( + collaborationAction, + adapter + ); + + ActionInfo updatedAction = + modifiedAction == null + ? null + : adapter.MapGenericToControlAction( + modifiedAction + ) as ActionInfo; + + if (updatedAction == null) + { + return null; + } + + string payload = JsonConvert.SerializeObject( + updatedAction, + ControllerJsonSettings + ); + + await transport.SendToGroupAsync( + param.RoomName, + "action", + payload + ); + + return payload; + } + + [HttpPost] + [Route("UpdateSelection")] + [EnableCors("AllowAllOrigins")] + public async Task + UpdateSelection( + [FromBody] SpreadsheetSelectionInfo param) + { + if (param == null || + string.IsNullOrWhiteSpace(param.RoomName) || + string.IsNullOrWhiteSpace(param.ConnectionId)) + { + return param; + } + + ConcurrentDictionary< + string, + SpreadsheetSelectionInfo> selections = + RoomSelections.GetOrAdd( + param.RoomName, + _ => new ConcurrentDictionary< + string, + SpreadsheetSelectionInfo>() + ); + + selections.AddOrUpdate( + param.ConnectionId, + param, + (_, _) => param + ); + + await transport.SendToGroupExceptAsync( + param.RoomName, + param.ConnectionId, + "action", + param + ); + + return param; + } + + [HttpGet] + [Route("GetRoomSelections/{roomName}")] + [EnableCors("AllowAllOrigins")] + public ActionResult> + GetRoomSelections(string roomName) + { + if (string.IsNullOrWhiteSpace(roomName) || + !RoomSelections.TryGetValue( + roomName, + out ConcurrentDictionary< + string, + SpreadsheetSelectionInfo> selections + )) + { + return Ok( + new List() + ); + } + + return Ok(selections.Values.ToList()); + } + + [HttpPost] + [Route("RemoveUserSelection")] + [EnableCors("AllowAllOrigins")] + public ActionResult RemoveUserSelection( + [FromBody] RemoveSelectionRequest request) + { + if (request == null || + string.IsNullOrWhiteSpace( + request.RoomName + ) || + string.IsNullOrWhiteSpace( + request.ConnectionId + )) + { + return Ok(); + } + + if (RoomSelections.TryGetValue( + request.RoomName, + out ConcurrentDictionary< + string, + SpreadsheetSelectionInfo> selections + )) + { + selections.TryRemove( + request.ConnectionId, + out _ + ); + + if (selections.IsEmpty) + { + RoomSelections.TryRemove( + request.RoomName, + out _ + ); + } + } + + return Ok(); + } + + [HttpPost] + [Route("GetActionsFromServer")] + [EnableCors("AllowAllOrigins")] + public async Task>> + GetActionsFromServer( + [FromBody] ActionInfo param) + { + if (param == null || + string.IsNullOrWhiteSpace(param.RoomName)) + { + return Ok(new List()); + } + + int lastSyncedVersion = param.Version; + + List collaborationActions = + await actionService + .GetEffectivePendingVersionAsync( + param.RoomName, + lastSyncedVersion + ); + + List actions = + collaborationActions == null + ? new List() + : collaborationActions + .Select(action => + adapter.MapGenericToControlAction( + action + ) as ActionInfo + ) + .Where(action => + action != null && + action.Version > + lastSyncedVersion + ) + .OrderBy(action => action.Version) + .ToList(); + + string payload = JsonConvert.SerializeObject( + actions, + ControllerJsonSettings + ); + + return Ok(payload); + } + + public class RemoveSelectionRequest + { + public string RoomName { get; set; } + + public string ConnectionId { get; set; } + } + + 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; } + } + } +} diff --git a/server/aspnet-core/Controllers/SpreadsheetController.cs b/server/aspnet-core/Controllers/SpreadsheetController.cs new file mode 100644 index 0000000..077e4c3 --- /dev/null +++ b/server/aspnet-core/Controllers/SpreadsheetController.cs @@ -0,0 +1,106 @@ +using System; +using Microsoft.AspNetCore.Cors; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Syncfusion.EJ2.Spreadsheet; +using System.Text.Json; +using Syncfusion.XlsIO; +using System.IO; + +namespace EJ2SpreadsheetServer.Controllers +{ + [Route("api/[controller]")] + [ApiController] + public class SpreadsheetController : ControllerBase + { + private readonly IWebHostEnvironment _hostingEnvironment; + string path; + public SpreadsheetController(IWebHostEnvironment hostingEnvironment) + { + _hostingEnvironment = hostingEnvironment; + path = _hostingEnvironment.ContentRootPath; + } + + // To open excel file + [AcceptVerbs("Post")] + [HttpPost] + [EnableCors("AllowAllOrigins")] + [Route("Open")] + public IActionResult Open(IFormCollection openRequest) + { + OpenRequest open = new OpenRequest(); + if (openRequest != null) + { + if (openRequest.Files != null && openRequest.Files.Count > 0) + { + // Assigning the file. + open.File = openRequest.Files[0]; + if (open.File != null && open.File.GetType().FullName.Contains("FormFile")) + { + IFormFile formFile = openRequest.Files[0]; + if (formFile.FileName.EndsWith(".html")) + { + ExcelEngine excelEngine = new ExcelEngine(); + IApplication application = excelEngine.Excel; + IWorkbook workbook = application.Workbooks.Create(1); + IWorksheet worksheet = workbook.Worksheets[0]; + Stream fileStream = formFile.OpenReadStream(); + worksheet.ImportHtmlTable(fileStream, 1, 1); + worksheet.UsedRange.AutofitColumns(); + worksheet.UsedRange.AutofitRows(); + MemoryStream ms = new MemoryStream(); + workbook.SaveAs(ms); + ms.Position = 0; + workbook.Close(); + excelEngine.Dispose(); + open.File = new FormFile(ms, 0, ms.Length, "Sample", "Sample.xlsx"); + } + } + } + // Setting the calculation mode. + if (openRequest.ContainsKey("IsManualCalculationEnabled") && bool.TryParse(openRequest["IsManualCalculationEnabled"].ToString(), out bool flag)) + { + open.IsManualCalculationEnabled = flag; + } + // Assigning the parse options to control which properties to skip while loading Excel files. + if (openRequest.ContainsKey("ParseOptions")) + { + var parseOptions = openRequest["ParseOptions"]; + if (!string.IsNullOrEmpty(parseOptions)) + { + open.ParseOptions = JsonSerializer.Deserialize(parseOptions.ToString(), new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase }); + } + } + // Assigning the Workbook password. + if (openRequest.ContainsKey("Password")) + { + open.Password = openRequest["Password"]; + } + // Enable the below line of setting open.Guid, if you want use MaximumDataLimit or MaximumFileSize property + //Microsoft.Extensions.Primitives.StringValues guid; + //if (openRequest.TryGetValue("Guid", out guid)) + //{ + // open.Guid = guid; + //} + // Used to skip loading Excel files with maximum data or maximum file size + //open.ThresholdLimit.MaximumDataLimit = 50000; + //open.ThresholdLimit.MaximumFileSize = 2097152; + } + // Process the Excel file and return the workbook JSON result + var result = Workbook.Open(open); + return Content(result); + } + + // To save as excel file + [AcceptVerbs("Post")] + [HttpPost] + [EnableCors("AllowAllOrigins")] + [Route("Save")] + public IActionResult Save([FromForm] SaveSettings saveSettings) + { + // Process the workbook JSON and return as file stream result. + return Workbook.Save(saveSettings); + } + } +} diff --git a/server/aspnet-core/Controllers/TestController.cs b/server/aspnet-core/Controllers/TestController.cs new file mode 100644 index 0000000..94a1764 --- /dev/null +++ b/server/aspnet-core/Controllers/TestController.cs @@ -0,0 +1,44 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; + +namespace EJ2SpreadsheetServer.Controllers +{ + [Route("api/[controller]")] + public class TestController : Controller + { + // GET api/values + [HttpGet] + public IEnumerable Get() + { + return new string[] { "value1", "value2" }; + } + + // GET api/values/5 + [HttpGet("{id}")] + public string Get(int id) + { + return "value"; + } + + // POST api/values + [HttpPost] + public void Post([FromBody]string value) + { + } + + // PUT api/values/5 + [HttpPut("{id}")] + public void Put(int id, [FromBody]string value) + { + } + + // DELETE api/values/5 + [HttpDelete("{id}")] + public void Delete(int id) + { + } + } +} diff --git a/server/aspnet-core-redis/Models/SpreadsheetSelectionInfo.cs b/server/aspnet-core/Models/SpreadsheetSelectionInfo.cs similarity index 100% rename from server/aspnet-core-redis/Models/SpreadsheetSelectionInfo.cs rename to server/aspnet-core/Models/SpreadsheetSelectionInfo.cs diff --git a/server/aspnet-core/NuGet.Config b/server/aspnet-core/NuGet.Config new file mode 100644 index 0000000..79d46f2 --- /dev/null +++ b/server/aspnet-core/NuGet.Config @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/server/aspnet-core-redis/Program.cs b/server/aspnet-core/Program.cs similarity index 95% rename from server/aspnet-core-redis/Program.cs rename to server/aspnet-core/Program.cs index 7356400..265ed58 100644 --- a/server/aspnet-core-redis/Program.cs +++ b/server/aspnet-core/Program.cs @@ -1,112 +1,113 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using Microsoft.AspNetCore; -using Microsoft.AspNetCore.Hosting; -using Microsoft.Extensions.Configuration; -using Microsoft.Extensions.Logging; -using Microsoft.AspNetCore.Builder; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.AspNetCore.ResponseCompression; -using Microsoft.AspNetCore.Http.Features; -using Newtonsoft.Json.Serialization; -using Syncfusion.Licensing; -using Microsoft.Extensions.Hosting; -using Syncfusion.Collaboration.Core.Extensions; -using Syncfusion.Collaboration.Core.Interfaces; - -var builder = WebApplication.CreateBuilder(args); - -// Configuration Setup -var env = builder.Environment; -builder.Configuration.SetBasePath(env.ContentRootPath) - .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) - .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) - .AddEnvironmentVariables(); - -// Syncfusion License Registration -string licenseKey = string.Empty; -string licenseFilePath = Path.Combine(Directory.GetCurrentDirectory(), "SyncfusionLicense.txt"); -if (File.Exists(licenseFilePath)) -{ - // Assigning Syncfusion LICENSE_KEY from local file - licenseKey = File.ReadAllText(licenseFilePath).Trim(); -} -if (string.IsNullOrEmpty(licenseKey)) -{ - // Access LICENSE_KEY from environment variables - licenseKey = builder.Configuration["SYNCFUSION_LICENSE_KEY"]; -} - -SyncfusionLicenseProvider.RegisterLicense(licenseKey); - -builder.Services.AddCollaborationServer(options => -{ - options.ConnectionString = builder.Configuration.GetConnectionString("Redis"); - options.ConnectionType = CollaborationConnectionType.SignalR; -}); -builder.Services.AddSingleton(); -builder.Services.AddSignalR(); - -// Configure services -var MyAllowSpecificOrigins = "AllowAllOrigins"; -builder.Services.AddCors(options => -{ - options.AddPolicy(name: MyAllowSpecificOrigins, - builder => { - builder.AllowAnyOrigin() - .AllowAnyMethod() - .AllowAnyHeader(); - }); -}); - -builder.Services.Configure(options => -{ - options.MultipartBodyLengthLimit = int.MaxValue; - options.ValueLengthLimit = int.MaxValue; -}); - -builder.WebHost.ConfigureKestrel(options => -{ - options.Limits.MaxRequestBodySize = int.MaxValue; -}); - -builder.Services.AddMemoryCache(); -builder.Services.AddMvc(endPoint => endPoint.EnableEndpointRouting = false); -builder.Services.AddEndpointsApiExplorer(); - -builder.Services.Configure(options => -{ - options.Level = System.IO.Compression.CompressionLevel.Optimal; -}); -builder.Services.AddResponseCompression(); - -builder.Services.AddControllers().AddNewtonsoftJson(options => -{ - options.SerializerSettings.ContractResolver = new DefaultContractResolver(); -}); - -// Build app -var app = builder.Build(); - -// Middleware -if (!app.Environment.IsDevelopment()) -{ - app.UseExceptionHandler("/Error"); - app.UseHsts(); -} -else -{ - app.UseDeveloperExceptionPage(); -} - -app.UseHttpsRedirection(); -app.UseRouting(); -app.UseCors(MyAllowSpecificOrigins); -app.UseAuthorization(); -app.UseResponseCompression(); -app.MapControllers(); -app.MapCollaborationServer(); -app.Run(); +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.AspNetCore.ResponseCompression; +using Microsoft.AspNetCore.Http.Features; +using Newtonsoft.Json.Serialization; +using Syncfusion.Licensing; +using Microsoft.Extensions.Hosting; +using Syncfusion.Collaboration.Core.Extensions; +using Syncfusion.Collaboration.Core.Interfaces; +using EJ2SpreadsheetServer.Adapters; + +var builder = WebApplication.CreateBuilder(args); + +// Configuration Setup +var env = builder.Environment; +builder.Configuration.SetBasePath(env.ContentRootPath) + .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true) + .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true) + .AddEnvironmentVariables(); + +// Syncfusion License Registration +string licenseKey = string.Empty; +string licenseFilePath = Path.Combine(Directory.GetCurrentDirectory(), "SyncfusionLicense.txt"); +if (File.Exists(licenseFilePath)) +{ + // Assigning Syncfusion LICENSE_KEY from local file + licenseKey = File.ReadAllText(licenseFilePath).Trim(); +} +if (string.IsNullOrEmpty(licenseKey)) +{ + // Access LICENSE_KEY from environment variables + licenseKey = builder.Configuration["SYNCFUSION_LICENSE_KEY"]; +} + +SyncfusionLicenseProvider.RegisterLicense(licenseKey); + +builder.Services.AddCollaborationServer(options => +{ + options.ConnectionString = builder.Configuration.GetConnectionString("Redis"); + options.ConnectionType = CollaborationConnectionType.SignalR; +}); +builder.Services.AddSingleton(); +builder.Services.AddSignalR(); + +// Configure services +var MyAllowSpecificOrigins = "AllowAllOrigins"; +builder.Services.AddCors(options => +{ + options.AddPolicy(name: MyAllowSpecificOrigins, + builder => { + builder.AllowAnyOrigin() + .AllowAnyMethod() + .AllowAnyHeader(); + }); +}); + +builder.Services.Configure(options => +{ + options.MultipartBodyLengthLimit = int.MaxValue; + options.ValueLengthLimit = int.MaxValue; +}); + +builder.WebHost.ConfigureKestrel(options => +{ + options.Limits.MaxRequestBodySize = int.MaxValue; +}); + +builder.Services.AddMemoryCache(); +builder.Services.AddMvc(endPoint => endPoint.EnableEndpointRouting = false); +builder.Services.AddEndpointsApiExplorer(); + +builder.Services.Configure(options => +{ + options.Level = System.IO.Compression.CompressionLevel.Optimal; +}); +builder.Services.AddResponseCompression(); + +builder.Services.AddControllers().AddNewtonsoftJson(options => +{ + options.SerializerSettings.ContractResolver = new DefaultContractResolver(); +}); + +// Build app +var app = builder.Build(); + +// Middleware +if (!app.Environment.IsDevelopment()) +{ + app.UseExceptionHandler("/Error"); + app.UseHsts(); +} +else +{ + app.UseDeveloperExceptionPage(); +} + +app.UseHttpsRedirection(); +app.UseRouting(); +app.UseCors(MyAllowSpecificOrigins); +app.UseAuthorization(); +app.UseResponseCompression(); +app.MapControllers(); +app.MapCollaborationServer(); +app.Run(); diff --git a/server/aspnet-core-redis/Properties/launchSettings.json b/server/aspnet-core/Properties/launchSettings.json similarity index 96% rename from server/aspnet-core-redis/Properties/launchSettings.json rename to server/aspnet-core/Properties/launchSettings.json index bb27f4d..d50b7dc 100644 --- a/server/aspnet-core-redis/Properties/launchSettings.json +++ b/server/aspnet-core/Properties/launchSettings.json @@ -1,45 +1,45 @@ -{ - "profiles": { - "EJ2SpreadsheetServer": { - "commandName": "Project", - "launchBrowser": true, - "launchUrl": "api/test", - "environmentVariables": { - "SYNCFUSION_LICENSE_KEY": "", - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "dotnetRunMessages": true, - "applicationUrl": "https://localhost:7002;http://localhost:5209" - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "launchUrl": "api/test", - "environmentVariables": { - "SYNCFUSION_LICENSE_KEY": "", - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Container (Dockerfile)": { - "commandName": "Docker", - "launchBrowser": true, - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/api/test", - "environmentVariables": { - "SYNCFUSION_LICENSE_KEY": "", - "ASPNETCORE_URLS": "https://+:443;http://+:80", - "ASPNETCORE_ENVIRONMENT": "Development" - }, - "publishAllPorts": true, - "useSSL": true - } - }, - "$schema": "https://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:37531", - "sslPort": 44318 - } - } +{ + "profiles": { + "EJ2SpreadsheetServer": { + "commandName": "Project", + "launchBrowser": true, + "launchUrl": "api/test", + "environmentVariables": { + "SYNCFUSION_LICENSE_KEY": "", + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "dotnetRunMessages": true, + "applicationUrl": "https://localhost:7002;http://localhost:5209" + }, + "IIS Express": { + "commandName": "IISExpress", + "launchBrowser": true, + "launchUrl": "api/test", + "environmentVariables": { + "SYNCFUSION_LICENSE_KEY": "", + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "Container (Dockerfile)": { + "commandName": "Docker", + "launchBrowser": true, + "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/api/test", + "environmentVariables": { + "SYNCFUSION_LICENSE_KEY": "", + "ASPNETCORE_URLS": "https://+:443;http://+:80", + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "publishAllPorts": true, + "useSSL": true + } + }, + "$schema": "https://json.schemastore.org/launchsettings.json", + "iisSettings": { + "windowsAuthentication": false, + "anonymousAuthentication": true, + "iisExpress": { + "applicationUrl": "http://localhost:37531", + "sslPort": 44318 + } + } } \ No newline at end of file diff --git a/server/aspnet-core/SyncfusionLicense.txt b/server/aspnet-core/SyncfusionLicense.txt new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/server/aspnet-core/SyncfusionLicense.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/server/aspnet-core-redis/appsettings.Development.json b/server/aspnet-core/appsettings.Development.json similarity index 94% rename from server/aspnet-core-redis/appsettings.Development.json rename to server/aspnet-core/appsettings.Development.json index 723c096..cb1818e 100644 --- a/server/aspnet-core-redis/appsettings.Development.json +++ b/server/aspnet-core/appsettings.Development.json @@ -1,10 +1,10 @@ -{ - "Logging": { - "IncludeScopes": false, - "LogLevel": { - "Default": "Debug", - "System": "Information", - "Microsoft": "Information" - } - } -} +{ + "Logging": { + "IncludeScopes": false, + "LogLevel": { + "Default": "Debug", + "System": "Information", + "Microsoft": "Information" + } + } +} diff --git a/server/aspnet-core-redis/appsettings.json b/server/aspnet-core/appsettings.json similarity index 66% rename from server/aspnet-core-redis/appsettings.json rename to server/aspnet-core/appsettings.json index 00d7350..fdc0a9a 100644 --- a/server/aspnet-core-redis/appsettings.json +++ b/server/aspnet-core/appsettings.json @@ -1,19 +1,19 @@ -{ - "SYNCFUSION_LICENSE_KEY": "", - "ConnectionStrings": { - "Redis": "redis-276188.redis.cache.windows.net:6380,password=pRIeJHlNcxzuD1Yz1ClZ00Ol3X4H2YdkmAzCaBuDaQY=,ssl=True,abortConnect=False" - }, - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Warning" - } - }, - "Console": { - "LogLevel": { - "Default": "Warning" - } - } - } -} +{ + "SYNCFUSION_LICENSE_KEY": "", + "ConnectionStrings": { + "Redis": "Your_Connection_string" + }, + "Logging": { + "IncludeScopes": false, + "Debug": { + "LogLevel": { + "Default": "Warning" + } + }, + "Console": { + "LogLevel": { + "Default": "Warning" + } + } + } +} diff --git a/server/aspnet-core/ej2-spreadsheet-server.csproj b/server/aspnet-core/ej2-spreadsheet-server.csproj new file mode 100644 index 0000000..2ca0901 --- /dev/null +++ b/server/aspnet-core/ej2-spreadsheet-server.csproj @@ -0,0 +1,27 @@ + + + + net10.0 + false + enable + enable + Linux + ..\docker-compose.dcproj + + + + + + + + + + + + + + + + + + diff --git a/server/aspnet-core/ej2-spreadsheet-server.sln b/server/aspnet-core/ej2-spreadsheet-server.sln new file mode 100644 index 0000000..516c280 --- /dev/null +++ b/server/aspnet-core/ej2-spreadsheet-server.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.9.34622.214 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ej2-spreadsheet-server", "ej2-spreadsheet-server.csproj", "{CB706B6A-FDB5-4EAF-8290-1F588048453F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CB706B6A-FDB5-4EAF-8290-1F588048453F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CB706B6A-FDB5-4EAF-8290-1F588048453F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CB706B6A-FDB5-4EAF-8290-1F588048453F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CB706B6A-FDB5-4EAF-8290-1F588048453F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D51E39EC-E8E9-4FB2-ABE6-8D78A8239A09} + EndGlobalSection +EndGlobal diff --git a/server/aspnet-core/web.config b/server/aspnet-core/web.config new file mode 100644 index 0000000..39f10da --- /dev/null +++ b/server/aspnet-core/web.config @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/server/aspnet-core-redis/wwwroot/Files/Sample.xlsx b/server/aspnet-core/wwwroot/Files/Sample.xlsx similarity index 100% rename from server/aspnet-core-redis/wwwroot/Files/Sample.xlsx rename to server/aspnet-core/wwwroot/Files/Sample.xlsx