TL;DR: Auto open, edit, and save Word documents in React with Syncfusion’s Document Editor, integrating AWS S3 for secure storage. Explore S3 setup, ASP.NET Core API, and React components, including File Manager and toolbar customization, for seamless cloud document workflows.
Leveraging a reliable cloud storage solution significantly enhances document access, sharing, security, and performance in a React application. However, setting up and integrating cloud storage isn’t always straightforward; it requires careful configuration to ensure efficiency and security. In this blog, we will explore how to achieve seamless functionality using Amazon S3 (AWS) Storage.
With Syncfusion® React Document Editor, you can effortlessly open, edit, and auto-save Word documents while utilizing Amazon S3 (AWS) for secure file storage and retrieval.
We will examine key functionalities such as auto-save, application-level file management for Amazon S3 using Syncfusion® FileManager, and a customizable toolbar to enhance your document editing experience.
Amazon S3 (Simple Storage Service) is a scalable, secure, and highly available cloud storage solution from AWS, ideal for web applications, backups, and content delivery.
Setting up Amazon S3 involves three key steps:
Sign in to the AWS Management Console and navigate to Amazon S3 via the AWS services menu. Click Create bucket, then provide a unique name, select a region, and configure the public access settings based on your requirements. Finally, click Create bucket to complete the process.
Open your newly created S3 bucket and go to the Permissions tab. Configure the Bucket Policy to control read/write access and adjust CORS settings if necessary.
Navigate to the IAM service and create a new IAM user with Programmatic access. Attach the AmazonS3FullAccess policy or customize permissions as needed. Save the Access Key ID and Secret Access Key for authentication in your application.
Create an ASP.NET Core Web API project using Visual Studio or the .NET CLI. Next, install the AWSSDK.S3 NuGet package to enable interaction with Amazon S3, which allows for file uploads, downloads, and storage management.
Add the Amazon S3 credentials and bucket details to the appsettings.json file. Include the Access Key, Secret Key, Bucket Name, and Region as shown below:
appsettings.json
{ "Logging": { "LogLevel": { "Default": "Warning" } }, "AllowedHosts": "*", "AccessKey": "", "SecretKey": "", "BucketName": "", "AWS": { "Region": "" } }
Create a service class, AmazonS3DocumentStorageService.cs, to handle file operations. This service includes two key methods:
Note: The service class also includes additional methods for file management operations, which you can explore in the GitHub example project.
The following code examples demonstrate the key functionalities of file operations in AmazonS3DocumentStorageService.cs.
public class AmazonS3DocumentStorageService : IAmazonS3DocumentStorageService { private readonly string _accessKey; private readonly string _secretKey; private readonly string _bucketName; private readonly RegionEndpoint _region; private readonly IAmazonS3 _s3Client; private readonly ILogger _logger; private readonly AmazonS3DocumentManager _fileProvider; private readonly string _rootFolderName; public string basePath; public AmazonS3DocumentStorageService( IAmazonS3 s3Client, IWebHostEnvironment hostingEnvironment, IConfiguration configuration, ILogger logger) { _s3Client = s3Client; _logger = logger; _accessKey = configuration["AccessKey"]; _secretKey = configuration["SecretKey"]; _bucketName = configuration["BucketName"]; _region = RegionEndpoint.GetBySystemName(configuration["AWS:Region"]); // Folder name created inside the container _rootFolderName = "Files"; // Initialize basePath from the hosting environment and clean it up basePath = hostingEnvironment.ContentRootPath; basePath = basePath.Replace("../", ""); _fileProvider = new AmazonS3DocumentManager(); _fileProvider.RegisterAmazonS3(_bucketName, _accessKey, _secretKey, _region.SystemName); } /// /// Loads a document from Amazon S3 and returns the serialized document. /// ///The name of the document to load. /// An IActionResult containing the serialized document if successful, or an error status code. public async Task FetchDocumentAsync(string documentName) { try { // Create a new S3 client for this operation. using var s3Client = new AmazonS3Client(_accessKey, _secretKey, _region); using var stream = new MemoryStream(); // Get the document object from the S3 bucket. var response = await s3Client.GetObjectAsync(_bucketName, $"{_rootFolderName}/{documentName}"); await response.ResponseStream.CopyToAsync(stream); stream.Seek(0, SeekOrigin.Begin); // Load the document using Syncfusion's WordDocument loader. var document = WordDocument.Load(stream, FormatType.Docx); // Serialize the document to JSON format. return new OkObjectResult(JsonConvert.SerializeObject(document)); } catch (AmazonS3Exception ex) { _logger.LogError(ex, "S3 error loading document"); return new StatusCodeResult(500); } } /// /// Saves a document to Amazon S3 using the provided form file. /// ///The uploaded form file to be saved. ///The name under which the document will be stored in S3. /// An IActionResult indicating whether the save operation was successful. public async Task UploadDocumentAsync(IFormFile file, string documentName) { try { // Create a new S3 client instance to upload the document. using var s3Client = new AmazonS3Client(_accessKey, _secretKey, _region); using var stream = new MemoryStream(); // Copy the uploaded file content to a memory stream. await file.CopyToAsync(stream); stream.Seek(0, SeekOrigin.Begin); // Construct the Put object request with the necessary details. var request = new PutObjectRequest { BucketName = _bucketName, Key = $"{_rootFolderName}/{documentName}", InputStream = stream }; // Upload the file to the S3 bucket. await s3Client.PutObjectAsync(request); return new OkResult(); } catch (AmazonS3Exception ex) { _logger.LogError(ex, "S3 error saving document"); return new StatusCodeResult(500); } } }
Create an API controller (Controllers/AmazonS3DocumentStorageController.cs) to handle file operations. This controller includes two key methods that internally invoke the corresponding service methods:
Note: This controller also contains an additional file operations method. For more details, please refer to the GitHub example project.
The code example below demonstrates the key controller methods for handling file operations in AmazonS3DocumentStorageController.cs.
/// <summary> /// Controller for handling AWS file operations and document management /// </summary> [Route("api/[controller]")] [EnableCors("AllowAllOrigins")] public class AmazonS3DocumentStorageController : ControllerBase { private readonly IAmazonS3DocumentStorageService _documentStorageService; /// <summary> /// Constructor injecting the file provider service dependency. /// </summary> /// <param name="documentStorageService">Service for performing file operations</param> public AmazonS3DocumentStorageController(IAmazonS3DocumentStorageService documentStorageService) { _documentStorageService = documentStorageService; } /// <summary> /// Retrieves a document from Amazon S3 storage in JSON format. /// </summary> /// <param name="args">File operation parameters including path and action type</param> /// <returns>Result of the file operation</returns> [HttpPost("FetchDocument")] public async Task FetchDocument([FromBody] Dictionary<string, string=""> jsonObject) { if (!jsonObject.TryGetValue("documentName", out var docName)) return BadRequest("Document name required"); return await _documentStorageService.FetchDocumentAsync(docName); } /// <summary> /// Saves uploaded document to Amazon S3 storage. /// </summary> /// <param name="data">Form data containing file and document name</param> /// <returns>Action result indicating success or failure.</returns> [HttpPost("UploadDocument")] public async Task UploadDocument([FromForm] IFormCollection data) { if (data.Files.Count == 0) return BadRequest("No file provided"); var documentName = data.TryGetValue("documentName", out var values) && values.Count > 0 ? values[0] : string.Empty; return await _documentStorageService.UploadDocumentAsync(data.Files[0], documentName); } }</string,>
Create a React app and integrate the Syncfusion® components, Document Editor, and File Manager, to interact with Amazon S3 storage. This integration enables file uploads, downloads, and storage management within your application.
Create a new TypeScript JSX file named AmazonS3FileManager.tsx to implement the File Manager, allowing users to display, browse, and manage files stored in Amazon S3. The example code below demonstrates this implementation:
import * as React from 'react'; import { FileManagerComponent, Inject, NavigationPane, DetailsView, Toolbar } from '@syncfusion/ej2-react-filemanager'; import { DialogComponent } from '@syncfusion/ej2-react-popups'; interface AmazonS3Props { // Callback function triggered when a file is selected in the file manager onFileSelect: (filePath: string, fileType: string, fileName: string) => void; } const AmazonS3FileManager: React.FC<AmazonS3Props> = ({ onFileSelect }) => { // Base URL for backend API handling Azure file operations const hostUrl: string = "http://localhost:62869/"; // State management for file manager dialog visibility const [showFileManager, setShowFileManager] = React.useState(true); // Reference to access the FileManager component methods let fileManagerRef = React.useRef<FileManagerComponent>(null); // Shows the file manager when the open button is clicked and clears the previous selection item const handleOpenButtonClick = () => { // Clear the previous selection if (fileManagerRef.current) { fileManagerRef.current.clearSelection(); setTimeout(() => { fileManagerRef.current.refreshFiles(); }, 100); } setShowFileManager(true); }; // Handles file open event from file manager const handleFileOpen = (args: any) => { if (args.fileDetails.isFile) { const selectedPath = args.fileDetails.path || args.fileDetails.filterPath + args.fileDetails.name; const fileType = args.fileDetails.type; const fileName = args.fileDetails.name; onFileSelect(selectedPath, fileType, fileName); // Pass the file path and file type to load in the Document Editor setShowFileManager(false); // Close the File Manager Dialog } }; return ( <div> <button id="openAmazonS3BucketStorage" onClick={handleOpenButtonClick}> Open AWS file manager </button> {/* File Manager Dialog */} <DialogComponent id="dialog-component-sample" header="File Manager" visible={showFileManager} width="80%" height="80%" showCloseIcon={true} closeOnEscape={true} target="body" beforeClose={() => setShowFileManager(false)} onClose={() => setShowFileManager(false)} // Close the dialog when closed > <FileManagerComponent id="aws-file" ref={fileManagerRef} ajaxSettings={{ url: hostUrl + 'api/AmazonS3DocumentStorage/ManageDocument', downloadUrl: hostUrl + 'api/AmazonS3DocumentStorage/DownloadDocument', }} toolbarSettings={{ items: ['SortBy', 'Copy', 'Paste', 'Delete', 'Refresh', 'Download', 'Selection', 'View', 'Details'] }} contextMenuSettings={{ file: ['Open', 'Copy', '|', 'Delete', 'Download', '|', 'Details'], layout: ['SortBy', 'View', 'Refresh', '|', 'Paste', '|', '|', 'Details', '|', 'SelectAll'], visible: true }} fileOpen={handleFileOpen} // Attach the fileOpen event > <Inject services={[NavigationPane, DetailsView, Toolbar]} /> </FileManagerComponent> </DialogComponent> </div> ); }; export default AmazonS3FileManager;
Create a new TypeScript JSX file named DocumentEditor.tsx to implement the Document Editor functionalities, which include creating, opening, editing, and saving documents.
import React, { useRef, useState } from 'react'; import { CustomToolbarItemModel, DocumentEditorContainerComponent, Toolbar } from '@syncfusion/ej2-react-documenteditor'; import AmazonS3FileManager from './AmazonS3FileManager.tsx'; import { ClickEventArgs } from '@syncfusion/ej2-navigations/src/toolbar/toolbar'; import { DialogUtility } from '@syncfusion/ej2-react-popups'; DocumentEditorContainerComponent.Inject(Toolbar); function DocumentEditor() { // Backend API host URL for document operations const hostUrl: string = "http://localhost:62869/"; // Reference to document editor container component const containerRef = useRef<DocumentEditorContainerComponent>(null); // Reference for the dialog component let dialogObj: any; // State to hold the current document name const [currentDocName, setCurrentDocName] = useState<string>('None'); // Track document modifications for auto-save functionality const contentChanged = React.useRef(false); // Custom toolbar button configuration for "New" document const newToolItem: CustomToolbarItemModel = { prefixIcon: "e-de-ctnr-new", tooltipText: "New", text: "New", id: "CustomNew" }; // Custom toolbar button configuration for opening the Amazon S3 file manager const openToolItem: CustomToolbarItemModel = { prefixIcon: "e-de-ctnr-open", tooltipText: "Open Amazon S3 file manager", text: "Open", id: "OpenAmazonS3FileManager" }; // Custom toolbar button configuration for downloading the document const downloadToolItem: CustomToolbarItemModel = { prefixIcon: "e-de-ctnr-download", tooltipText: "Download", text: "Download", id: "DownloadToLocal" }; // Customize the SystemClipboard API name let settings = { systemClipboard: 'ProcessClipboardContent' }; // Combined toolbar items including custom buttons and built-in features const toolbarItems = [ newToolItem, openToolItem, downloadToolItem, 'Separator', 'Undo', 'Redo', 'Separator', 'Image', 'Table', 'Hyperlink', 'Bookmark', 'TableOfContents', 'Separator', 'Header', 'Footer', 'PageSetup', 'PageNumber', 'Break', 'InsertFootnote', 'InsertEndnote', 'Separator', 'Find', 'Separator', 'Comments', 'TrackChanges', 'Separator', 'LocalClipboard', 'RestrictEditing', 'Separator', 'FormFields', 'UpdateFields', 'ContentControl' ]; return ( <div> <div> <AmazonS3FileManager onFileSelect={loadFileFromFileManager} /> </div> <div id="document-editor-div" style={{ display: "block" }}> <div id="document-header"> {currentDocName || 'None'} </div> <DocumentEditorContainerComponent ref={containerRef} id="container" height={'650px'} serviceUrl={hostUrl + "api/AmazonS3DocumentStorage/"} enableToolbar={true} toolbarItems={toolbarItems} toolbarClick={handleToolbarClick} contentChange={handleContentChange} // Listen to content changes serverActionSettings={settings} /> </div> </div> ); } export default DocumentEditor;
// Callback function to load the file selected in the file manager const loadFileFromFileManager = (filePath: string, fileType: string, fileName: string): void => { if (!containerRef.current) { console.error('Document Editor is not loaded yet.'); return; } containerRef.current.documentEditor.documentName = fileName; // Update state with the current document name setCurrentDocName(fileName); if (fileType === '.docx' || fileType === '.doc' || fileType === '.txt' || fileType === '.rtf') { // Handle document files fetch(hostUrl + 'api/AmazonS3DocumentStorage/FetchDocument', { method: 'POST', headers: { 'Content-Type': 'application/json;charset=UTF-8' }, body: JSON.stringify({ documentName: fileName }) }) .then(response => { if (response.status === 200 || response.status === 304) { return response.json(); } else { throw new Error('Error loading document'); } }) .then(json => { const documentEditorDiv = document.getElementById("document-editor-div"); if (documentEditorDiv) { documentEditorDiv.style.display = "block"; } // Open the document using the JSON data received containerRef.current.documentEditor.open(JSON.stringify(json)); }) .catch(error => { console.error('Error loading document:', error); }); } else { alert('The selected file type is not supported for the document editor.'); } };
// Automatically saves document to Amazon S3 storage const autoSaveDocument = async (): Promise<void> => { if (!containerRef.current) return; try { // Save as Blob using Docx format const blob: Blob = await containerRef.current.documentEditor.saveAsBlob('Docx'); let exportedDocument = blob; let formData: FormData = new FormData(); formData.append('documentName', containerRef.current.documentEditor.documentName); formData.append('data', exportedDocument); let req = new XMLHttpRequest(); // Send document to backend API for Amazon S3 storage req.open( 'POST', hostUrl + 'api/AmazonS3DocumentStorage/UploadDocument', true ); req.onreadystatechange = () => { if (req.readyState === 4 && (req.status === 200 || req.status === 304)) { // Auto save completed // Success handler can be added here if needed } }; req.send(formData); } catch (error) { console.error('Error saving document:', error); } }; // Runs auto-save every second when content changes are detected React.useEffect(() => { const intervalId = setInterval(() => { if (contentChanged.current) { autoSaveDocument(); contentChanged.current = false; } }, 1000); return () => clearInterval(intervalId); }); // Handles document content change detection const handleContentChange = (): void => { contentChanged.current = true; // Set the ref's current value };
// Handles document editor toolbar button click events const handleToolbarClick = async (args: ClickEventArgs): Promise<void> => { // Get a reference to the file manager open button const openButton = document.getElementById('openAmazonS3BucketStorage'); // Get the current document name from the editor let documentName = containerRef.current?.documentEditor.documentName || 'Untitled'; // Remove any extension from the document name using regex const baseDocName = documentName.replace(/\.[^/.]+$/, ''); // Always check if containerRef.current exists before using it if (!containerRef.current) return; switch (args.item.id) { case 'OpenAmazonS3FileManager': // Programmatically trigger Amazon S3 file manager if (openButton) { // Save the changes before opening a new document await autoSaveDocument(); openButton.click(); // Sets the focus to the document editor within the current container reference containerRef.current.documentEditor.focusIn(); } break; case 'DownloadToLocal': // Initiate client-side download containerRef.current.documentEditor.save(baseDocName, 'Docx'); // Sets the focus to the document editor within the current container reference containerRef.current.documentEditor.focusIn(); break; case 'CustomNew': // If dialogObj exists, show the dialog; otherwise, prompt for a file name. if (dialogObj) { dialogObj.show(); // Display the dialog. } else { showFileNamePrompt(); // Prompt the user for a file name. } break; default: break; } };
// Prompt dialog for entering a new document filename const showFileNamePrompt = (errorMessage?: string) => { const randomDefaultName = getRandomDefaultName(); dialogObj = DialogUtility.confirm({ title: 'New Document', width: '350px', cssClass: 'custom-dialog-prompt', content: ` <p>Enter document name:</p> <div id="errorContainer" style="color: red; margin-top: 4px;"> ${errorMessage ? errorMessage : ''} </div> <input id="inputEle" type="text" class="e-input" value="${randomDefaultName}"/> `, okButton: { click: handleFileNamePromptOk }, cancelButton: { click: handleFileNamePromptCancel }, }); // After the dialog is rendered, focus and select the input text. setTimeout(() => { const input = document.getElementById("inputEle") as HTMLInputElement; if (input) { input.focus(); input.select(); } }, 100); dialogObj.close = () => { setTimeout(() => { // Sets the focus to the document editor within the current container reference containerRef.current.documentEditor.focusIn(); }, 100); }; }; // Handler for the OK button in the file name prompt dialog with file existence check and save // The new file will be automatically saved to Azure Storage by the auto-save functionality, which is managed within the setInterval method. const handleFileNamePromptOk = async () => { const inputElement = document.getElementById("inputEle") as HTMLInputElement; let userFilename = inputElement?.value.trim() || "Untitled"; const baseFilename = `${userFilename}.docx`; // Check if the document already exists on the backend const exists = await checkDocumentExistence(baseFilename); if (exists) { // If the document exists, display an error message in the dialog const errorContainer = document.getElementById("errorContainer"); if (errorContainer) { errorContainer.innerHTML = 'Document already exists. Please choose a different name.'; } // Re-focus the input for correction if (inputElement) { inputElement.focus(); inputElement.select(); } return; } // Proceed with new document if (dialogObj) dialogObj.hide(); containerRef.current.documentEditor.documentName = baseFilename; setCurrentDocName(baseFilename); containerRef.current.documentEditor.openBlank(); }; // Handler for the Cancel button in the prompt dialog const handleFileNamePromptCancel = () => { if (dialogObj) { dialogObj.hide(); } };
To run the project, launch the server-side ASP.NET Core API and start the client-side React application to test its functionality. The results of the file operations performed on the client side will be displayed as shown in the image below.
For a complete project, refer to the GitHub demo.
Thanks for reading! In this blog, we have explored how to efficiently open, edit, and auto-save Word documents in Amazon S3 (AWS) Storage using Syncfusion®’s Document Editor and File Manager within a React application. By following these steps, you can create a seamless and secure document management system, enabling users to work with Word documents directly in the cloud.
Whether you choose Amazon S3, Azure Blob Storage, or any other cloud storage, Syncfusion®’s Document Editor provides a scalable and reliable solution for document handling based on your application’s needs.
The new version is available for current customers to download from the License and Downloads page. If you are not a Syncfusion customer, you can try our 30-day free trial for our newest features.
You can also contact us through our support forums, support portal, or feedback portal. We are always happy to assist you!