TX Text Control .NET Server

Import and Export

Topic

Keeping review results in your database or sharing annotations between users means storing the annotations of the Document Viewer and restoring them. The building blocks are the JSON format, exporting and loading annotations, storing them for each document on your server and processing them on the server.

The examples extend the application of the ASP.NET Core tutorial and use the file App_Data/template.docx.

How Annotations Are Stored

The viewer keeps annotations separately from the document. export returns all annotations as a JSON string, and load loads annotations from such a string. The document stays unchanged, and your application decides where the annotations are stored, who may see them and how long they are kept. A common design stores the original document once and the annotations in a table of their own, with the ID of the document, the user and the time of the last change.

The format is the same for all document types, so the same review process works for DOCX, PDF and the other formats. Store annotations with the document they belong to: the positions in the JSON refer to the pages and text of that document.

The JSON Format

The JSON is an array with an inner array for each page of the document, in the order of the pages. Pages without annotations have an empty array. Every annotation is an object with these properties:

PropertyContains
penThe type of the annotation and its attributes, such as the color
userThe user who created the annotation
timeThe time of creation in milliseconds since January 1, 1970
commentsAn array of comments; see Comments
idA unique identifier
statusA status such as accepted, when one is set

The pen and the other properties depend on the type:

Annotationpen.typeFurther properties
Notecommentlocation with x and y; the pen has objectWidth and objectHeight
Text highlighthighlightThe pen has color, start, end and the highlighted text; no location
Texttexttext and location; the pen has the color and the font attributes
Signaturesignaturelocation and image, the signature as a data URL
Drawingnonelines, an array of points; the pen has width, color and other line attributes

Some properties have changed between versions of the viewer, so read only the properties your application needs and check that they exist.

Store and Load Annotations for a Document

The following example stores the annotations of template.docx in a JSON file on the server and loads them again when the page opens. Create Controllers/AnnotationsController.cs:

using Microsoft.AspNetCore.Mvc;

public class AnnotationsController : Controller
{
    private static readonly string StorageFolder = Path.Combine("App_Data", "Annotations");

    // Return the stored annotations of a document, or an empty array
    [HttpGet]
    public IActionResult Load(string documentId)
    {
        string path = GetPath(documentId);

        if (!System.IO.File.Exists(path))
        {
            return Content("[]", "application/json");
        }

        return Content(System.IO.File.ReadAllText(path), "application/json");
    }

    // Store the annotations that the viewer exported
    [HttpPost]
    public async Task<IActionResult> Save(string documentId)
    {
        using var reader = new StreamReader(Request.Body);
        string json = await reader.ReadToEndAsync();

        Directory.CreateDirectory(StorageFolder);
        await System.IO.File.WriteAllTextAsync(GetPath(documentId), json);

        return Ok();
    }

    private static string GetPath(string documentId) =>
        Path.Combine(StorageFolder, Path.GetFileName(documentId) + ".json");
}

Replace the content of Views/Home/Index.cshtml:

@using TXTextControl.Web.MVC.DocumentViewer

<div style="width: 800px; height: 600px;">

@Html.TXTextControl().DocumentViewer(settings => {
    settings.DocumentPath = "App_Data/template.docx";
    settings.Dock = DocumentViewerSettings.DockStyle.Fill;
}).Render()

</div>

<input type="button" id="saveAnnotations" value="Save Annotations" />

<script>
    var documentId = "template";

    window.addEventListener("documentViewerLoaded", async function () {
        TXDocumentViewer.annotations.showToolbar(true);

        var response = await fetch("/Annotations/Load?documentId=" + encodeURIComponent(documentId));
        TXDocumentViewer.annotations.load(await response.text());
    });

    document.getElementById("saveAnnotations").addEventListener("click", async function () {
        await fetch("/Annotations/Save?documentId=" + encodeURIComponent(documentId), {
            method: "POST",
            headers: { "Content-Type": "application/json" },
            body: TXDocumentViewer.annotations.export()
        });
    });
</script>

The code works as follows:

  • The Load action returns the stored JSON of a document, or [] if nothing has been stored yet. The Save action writes the posted JSON into a file for each document. In your application, replace the files with a table of your database.
  • When the viewer is loaded, the script requests the annotations of the document and passes them to load.
  • Save Annotations posts the result of export to the Save action.

Run the application, add a note and click Save Annotations. Reload the page: the note appears again.

To save automatically, post the annotations in a handler of the annotationsChanged event instead of a button, as the ASP.NET Core tutorial shows with the console.

Store the Annotations in the Document

Instead of storing annotations separately, your code can save the document with them: saveDocument with embedAnnotations set to true stores the annotations in the saved document. Saving Documents shows how the saved document is sent to your server.

When users place signatures as annotations, your server can also merge them into the document as images with ServerTextControl. The blog article Merging Signature Annotations into Documents reads the signature annotations from the JSON and inserts their images at their positions.

Share Annotations Live

Because the JSON is small, it can be sent to other users while they view the same document. In a handler of annotationsChanged, the viewer of one user exports the annotations and sends them, for example over SignalR, and the viewers of the other users load them. The blog article DocumentViewer Collaboration: Live Share Document Annotations builds this with a sample on GitHub.

Next Steps

  • Comments: read the comments of the exported annotations.
  • Saving Documents: save the document with its annotations and form values.
  • Events: react when annotations change.