# Comments

> Add, list, export and remove DOCX comments with ServerTextControl: CommentedText, replies, CommentCollection in all text parts, JSON reports and events.

- **Product:** TX Text Control .NET Server
- **Section:** Documentation
- **Kind:** Topic
- **Path:** TX Text Control .NET Server > Document Processing > Comments
- **4693 words**
- **Web URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/comments/
- **LLMs URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/comments/llms.txt
- **LLMs-Full URL:** https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/comments/llms-full.txt

---

Reviewers leave remarks in a document as comments: a note on a piece of text, with its author, the time it was written and replies from other reviewers. When such a document reaches the server, your code reads the comments into a report, adds comments of its own to text it finds and removes all comments before the document goes to a customer.

Commenting in the browser is described in [Comments](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/comments/llms-full.txt) in the Document Editor section. The examples below are console programs that load *Documents/review.docx*, a document with comments.

### How Comments Work

A comment is a [CommentedText](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/llms-full.txt) object: the commented piece of text together with the comment. Its properties describe both:

| Property | Contains |
|---|---|
| [Comment](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/comment-property/llms-full.txt) | The text of the comment; it can be changed |
| [Text](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/text-property/llms-full.txt), [Start](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/start-property/llms-full.txt), [Length](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/length-property/llms-full.txt) | The commented text, the one-based position of its first character and its number of characters |
| [UserName](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/username-property/llms-full.txt) | The author; `null` or an empty string for an unknown author |
| [CreationTime](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/creationtime-property/llms-full.txt) | The time the comment was written, in UTC |
| [Number](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/number-property/llms-full.txt) | The position of the comment in the text, starting with 1 |
| [Replies](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/replies-property/llms-full.txt), [RepliedComment](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/repliedcomment-property/llms-full.txt) | The replies to the comment and the comment it replies to, each `null` if there is none |

Keep these points in mind:

- **Replies are comments too.** A reply belongs to the same piece of text as the comment it answers, but it can have another author and time. Replies are contained in the collection like all other comments, so check `RepliedComment` to tell them apart.
- **One collection per text part.** [ServerTextControl.Comments](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/comments-property/llms-full.txt) returns the [CommentCollection](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentcollection/llms-full.txt) of the main text. Headers, footers and text frames have their own, [IFormattedText.Comments](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/iformattedtext/comments-property/llms-full.txt).
- **Formats.** Comments are saved and loaded with DOCX, RTF and the TX Text Control format, and with DOC on the Classic engine. They're compatible with the comments of MS Word.
- **Comments on tracked changes.** A comment attached to a tracked change is removed when the change is accepted or rejected, as [Track Changes](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/track-changes/#how-tracked-changes-work) describes.

A DOCX document with comments looks like this in MS Word: each comment is attached to a piece of text, and the first comment has a reply. The blog article [Extracting Comments from DOCX Files in .NET C#](https://www.textcontrol.com/blog/2025/01/17/extracting-comments-from-docx-files-in-net-csharp/) reads this document with `ServerTextControl`:

![A DOCX document in MS Word with three comments in the margin, each attached to a highlighted word; the first comment has a reply.](https://docs.textcontrol.com/assets/img/tx-text-control-dotnet-server/comments-replies-word.9107f76f.webp)

### List Comments and Their Replies

The following program prints each comment of the main text with its author, time and commented text, followed by its replies:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/review.docx", StreamType.WordprocessingML);

    // The collection contains the comments and their replies
    Console.WriteLine($"Comments and replies: {tx.Comments.Count}");

    foreach (CommentedText comment in tx.Comments)
    {
        // Replies are printed below the comment they answer
        if (comment.RepliedComment != null)
        {
            continue;
        }

        Console.WriteLine($"{AuthorOf(comment)} ({comment.CreationTime:yyyy-MM-dd HH:mm} UTC) on \"{comment.Text}\": {comment.Comment}");

        if (comment.Replies != null)
        {
            foreach (CommentedText reply in comment.Replies)
            {
                Console.WriteLine($"    {AuthorOf(reply)}: {reply.Comment}");
            }
        }
    }
}

// UserName is null or empty for an unknown author
static string AuthorOf(CommentedText comment) =>
    string.IsNullOrEmpty(comment.UserName) ? "Unknown author" : comment.UserName;
```

The loop skips the replies, because each reply is also printed through the `Replies` of its comment; without the check, every reply would appear twice. The blog article [Extracting Comments from DOCX Files in .NET C#](https://www.textcontrol.com/blog/2025/01/17/extracting-comments-from-docx-files-in-net-csharp/) filters comments by author and date with LINQ.

### Add a Comment

A comment is added to a range of text given by its position and length. The following program finds a date in a contract and comments on it:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/contract.docx", StreamType.WordprocessingML);

    string searchText = "December 31";

    // Find returns a zero-based position, or -1
    int position = tx.Find(searchText);

    if (position == -1)
    {
        Console.WriteLine($"\"{searchText}\" wasn't found.");
        return;
    }

    // The CommentedText constructor expects a one-based start position
    CommentedText comment = new CommentedText("Please confirm the date.", position + 1, searchText.Length);
    CommentCollection.AddResult result = tx.Comments.Add(comment);

    Console.WriteLine($"Comment added: {result}");
    tx.Save("Documents/contract-commented.docx", StreamType.WordprocessingML);
}
```

The code works as follows:

- The [CommentedText constructor](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/constructor/llms-full.txt) takes the comment, the one-based start position and the length of the commented text. A constructor with only the comment uses the current selection, and a constructor with a `CommentedText` as the second parameter creates a reply to that comment.
- [CommentCollection.Add](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentcollection/add-method/llms-full.txt) returns an [AddResult](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentcollection/addresult-enumeration/llms-full.txt): `PositionInvalid` for an invalid position or length, `SelectionTooComplex` when the text isn't a continuous sequence of characters, `NoSelection` when no text is selected, `Error` for an unexpected error and otherwise `Successful`.

#### Reply to a Comment

A reply is a `CommentedText` created with the comment it answers as the second parameter, and added to the same collection. The following program answers the first comment of *Documents/review.docx* that isn't a reply itself and changes the text of that comment:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/review.docx", StreamType.WordprocessingML);

    foreach (CommentedText comment in tx.Comments)
    {
        // Answer a comment, not a reply
        if (comment.RepliedComment != null)
        {
            continue;
        }

        CommentedText reply = new CommentedText("Checked on the server.", comment);
        CommentCollection.AddResult result = tx.Comments.Add(reply);
        Console.WriteLine($"Reply added: {result}");

        // Comment can be changed directly
        comment.Comment += " (reviewed)";
        break;
    }

    tx.Save("Documents/review-answered.docx", StreamType.WordprocessingML);
}
```

The loop ends with `break` after the first comment, because adding a reply changes the collection. [EditComment](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/editcomment-method/llms-full.txt) and [ReplyToComment](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentedtext/replytocomment-method/llms-full.txt) do the same through the user interface: they open a comment input control in which a user edits the comment or writes a reply, and return `false` if it can't be opened. `ReplyToComment` also returns `false` for a comment that is a reply itself. Without a user, set `Comment` and use the constructor as above.

### Export Comments to a Report

Collecting the comments of every part of a document gives a review report that another application can process. The following program writes the comments of all text parts with their replies to *comments.json*:

```csharp
using System.Text.Json;
using TXTextControl;

List<object> report = new List<object>();

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/review.docx", StreamType.WordprocessingML);

    // The main text, text frames, headers and footers
    foreach (IFormattedText textPart in tx.TextParts)
    {
        foreach (CommentedText comment in textPart.Comments)
        {
            if (comment.RepliedComment != null)
            {
                continue;
            }

            CommentedText[] replies = comment.Replies ?? Array.Empty<CommentedText>();

            report.Add(new
            {
                Part = textPart.GetType().Name,
                Author = comment.UserName,
                Created = comment.CreationTime,
                Text = comment.Text,
                Comment = comment.Comment,
                Replies = replies
                    .Select(reply => new { Author = reply.UserName, Created = reply.CreationTime, Comment = reply.Comment })
                    .ToList()
            });
        }
    }
}

string json = JsonSerializer.Serialize(report, new JsonSerializerOptions { WriteIndented = true });
File.WriteAllText("comments.json", json);
Console.WriteLine(json);
```

The values are copied into new objects inside the `using` block, while the `ServerTextControl` instance that the comments belong to still exists. [ServerTextControl.TextParts](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/textparts-property/llms-full.txt) contains the main text, the text frames and the headers and footers.

### Remove Comments Before Sending a Document

The following program removes the comments of one author and then all remaining comments of all text parts before it saves the document:

```csharp
using TXTextControl;

using (ServerTextControl tx = new ServerTextControl())
{
    tx.Create();
    tx.Load("Documents/review.docx", StreamType.WordprocessingML);

    tx.CommentDeleted += (sender, e) =>
        Console.WriteLine($"Removed: \"{e.CommentedText.Comment}\" by {e.CommentedText.UserName}");

    // Removing a comment removes its replies too
    string author = "Tim Typer";

    while (true)
    {
        CommentedText? comment = tx.Comments.Cast<CommentedText>()
            .FirstOrDefault(c => c.RepliedComment == null && c.UserName == author);

        if (comment == null || !tx.Comments.Remove(comment))
        {
            break;
        }
    }

    // Remove all remaining comments of all text parts
    foreach (IFormattedText textPart in tx.TextParts)
    {
        while (textPart.Comments.Count > 0)
        {
            if (!textPart.Comments.Remove(textPart.Comments[1]))
            {
                break;
            }
        }
    }

    tx.Save("Documents/final.docx", StreamType.WordprocessingML);
}
```

The code works as follows:

- [CommentCollection.Remove](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentcollection/remove-method/llms-full.txt) removes the comment and its replies and returns `true` if it was removed. The commented text stays in the document.
- The program searches the collection again after each removal, and the indexer [Item](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/commentcollection/item-property/llms-full.txt) with 1 always returns the first remaining comment.
- [CommentDeleted](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/commentdeleted-event/llms-full.txt) occurs for each removed comment. In the handler, only `Comment`, `UserName`, `CreationTime`, `ID` and the highlight settings of the comment are valid. [CommentCreated](https://docs.textcontrol.com/tx-text-control-dotnet-server/reference/txtextcontrol/servertextcontrol/commentcreated-event/llms-full.txt) occurs when text has been commented.

To keep the comments in your document and leave them out of a copy only, save the copy with `SaveSettings.OmittedContent` set to `OmittedContent.Comments`, as [Saving and Converting Documents](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/saving-converting/#leave-out-content-and-add-properties) shows. This setting can't be used for PDF.

### Next Steps

- [Track Changes](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/track-changes/llms-full.txt): process the tracked changes that reviewers made.
- [Comments](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-editor/comments/llms-full.txt) in the Document Editor: let users add and answer comments.
- [Text](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/text/#find-and-replace-text): find the text to comment on.

---

## Nearby

- **Previous:** [Content Controls](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/content-controls/llms-full.txt)
- **Next:** [Track Changes](https://docs.textcontrol.com/tx-text-control-dotnet-server/document-processing/track-changes/llms-full.txt)
