Introduction
To create a Word document in C#, the best approach for most scenarios is the Open XML SDK (DocumentFormat.OpenXml), which generates .docx files without requiring Microsoft Word to be installed. For simpler needs, the DocX library (Xceed.Document.NET) provides a fluent API. Microsoft Office Interop (Microsoft.Office.Interop.Word) offers the most features but requires Word to be installed and is not suitable for server-side applications. Choose Open XML SDK for server/cloud deployments and Interop for desktop automation.
Open XML SDK (Recommended for Server/Cloud)
1// Install: dotnet add package DocumentFormat.OpenXml
2
3using DocumentFormat.OpenXml;
4using DocumentFormat.OpenXml.Packaging;
5using DocumentFormat.OpenXml.Wordprocessing;
6
7public void CreateDocument(string filePath)
8{
9 using var doc = WordprocessingDocument.Create(filePath, WordprocessingDocumentType.Document);
10
11 // Add main document part
12 var mainPart = doc.AddMainDocumentPart();
13 mainPart.Document = new Document();
14 var body = mainPart.Document.AppendChild(new Body());
15
16 // Add a heading
17 var heading = new Paragraph(
18 new ParagraphProperties(
19 new ParagraphStyleId() { Val = "Heading1" }
20 ),
21 new Run(new Text("Monthly Report"))
22 );
23 body.AppendChild(heading);
24
25 // Add a paragraph with formatted text
26 var para = new Paragraph(
27 new Run(
28 new RunProperties(new Bold()),
29 new Text("Summary: ")
30 ),
31 new Run(new Text("All metrics are within expected ranges."))
32 );
33 body.AppendChild(para);
34
35 // Add a bulleted list
36 string[] items = { "Revenue up 12%", "Costs down 5%", "Customer satisfaction at 94%" };
37 foreach (var item in items)
38 {
39 var listItem = new Paragraph(
40 new ParagraphProperties(
41 new NumberingProperties(
42 new NumberingLevelReference() { Val = 0 },
43 new NumberingId() { Val = 1 }
44 )
45 ),
46 new Run(new Text(item))
47 );
48 body.AppendChild(listItem);
49 }
50
51 mainPart.Document.Save();
52}
Adding a Table with Open XML
1public void AddTable(Body body)
2{
3 var table = new Table();
4
5 // Table properties (borders)
6 var tblProperties = new TableProperties(
7 new TableBorders(
8 new TopBorder() { Val = BorderValues.Single, Size = 4 },
9 new BottomBorder() { Val = BorderValues.Single, Size = 4 },
10 new LeftBorder() { Val = BorderValues.Single, Size = 4 },
11 new RightBorder() { Val = BorderValues.Single, Size = 4 },
12 new InsideHorizontalBorder() { Val = BorderValues.Single, Size = 4 },
13 new InsideVerticalBorder() { Val = BorderValues.Single, Size = 4 }
14 )
15 );
16 table.AppendChild(tblProperties);
17
18 // Header row
19 var headerRow = new TableRow();
20 foreach (var header in new[] { "Name", "Department", "Salary" })
21 {
22 var cell = new TableCell(
23 new Paragraph(new Run(
24 new RunProperties(new Bold()),
25 new Text(header)
26 ))
27 );
28 headerRow.AppendChild(cell);
29 }
30 table.AppendChild(headerRow);
31
32 // Data rows
33 var data = new[] {
34 ("Alice", "Engineering", "$120,000"),
35 ("Bob", "Marketing", "$95,000"),
36 };
37
38 foreach (var (name, dept, salary) in data)
39 {
40 var row = new TableRow();
41 row.AppendChild(new TableCell(new Paragraph(new Run(new Text(name)))));
42 row.AppendChild(new TableCell(new Paragraph(new Run(new Text(dept)))));
43 row.AppendChild(new TableCell(new Paragraph(new Run(new Text(salary)))));
44 table.AppendChild(row);
45 }
46
47 body.AppendChild(table);
48}
DocX Library (Xceed) — Simpler API
1// Install: dotnet add package DocX
2
3using Xceed.Document.NET;
4using Xceed.Words.NET;
5
6public void CreateWithDocX(string filePath)
7{
8 using var doc = DocX.Create(filePath);
9
10 // Title
11 doc.InsertParagraph("Monthly Report")
12 .FontSize(24)
13 .Bold()
14 .Alignment = Alignment.center;
15
16 // Body text
17 doc.InsertParagraph("All metrics are within expected ranges.")
18 .FontSize(12)
19 .SpacingAfter(10);
20
21 // Bulleted list
22 var list = doc.AddList("Revenue up 12%", 0, ListItemType.Bulleted);
23 doc.AddListItem(list, "Costs down 5%");
24 doc.AddListItem(list, "Customer satisfaction at 94%");
25 doc.InsertList(list);
26
27 // Table
28 var table = doc.AddTable(3, 3);
29 table.Rows[0].Cells[0].Paragraphs[0].Append("Name").Bold();
30 table.Rows[0].Cells[1].Paragraphs[0].Append("Department").Bold();
31 table.Rows[0].Cells[2].Paragraphs[0].Append("Salary").Bold();
32 table.Rows[1].Cells[0].Paragraphs[0].Append("Alice");
33 table.Rows[1].Cells[1].Paragraphs[0].Append("Engineering");
34 table.Rows[1].Cells[2].Paragraphs[0].Append("$120,000");
35 doc.InsertTable(table);
36
37 doc.Save();
38}
Microsoft Office Interop (Desktop Only)
1// Requires Microsoft Word installed
2// Add reference: Microsoft.Office.Interop.Word
3
4using Word = Microsoft.Office.Interop.Word;
5
6public void CreateWithInterop(string filePath)
7{
8 var app = new Word.Application();
9 var doc = app.Documents.Add();
10
11 try
12 {
13 // Add title
14 var titlePara = doc.Paragraphs.Add();
15 titlePara.Range.Text = "Monthly Report";
16 titlePara.Range.Font.Size = 24;
17 titlePara.Range.Font.Bold = 1;
18 titlePara.Range.InsertParagraphAfter();
19
20 // Add body text
21 var bodyPara = doc.Paragraphs.Add();
22 bodyPara.Range.Text = "All metrics are within expected ranges.";
23 bodyPara.Range.Font.Size = 12;
24 bodyPara.Range.Font.Bold = 0;
25 bodyPara.Range.InsertParagraphAfter();
26
27 doc.SaveAs2(filePath);
28 }
29 finally
30 {
31 doc.Close();
32 app.Quit();
33
34 // Release COM objects
35 System.Runtime.InteropServices.Marshal.ReleaseComObject(doc);
36 System.Runtime.InteropServices.Marshal.ReleaseComObject(app);
37 }
38}
Comparison
| Feature | Open XML SDK | DocX (Xceed) | Office Interop |
| Word required | No | No | Yes |
| Server-safe | Yes | Yes | No |
| API complexity | Verbose | Simple | Medium |
| .docx support | Yes | Yes | Yes |
| .doc support | No | No | Yes |
| NuGet package | DocumentFormat.OpenXml | DocX | N/A (COM) |
| License | MIT | Free (community) | Office license |
Common Pitfalls
Using Office Interop on a server: Microsoft.Office.Interop.Word requires a full Word installation and launches a Word process. It is not thread-safe, leaks memory if COM objects are not released, and Microsoft explicitly does not support server-side Interop. Use Open XML SDK or DocX for server applications.
Not disposing WordprocessingDocument: WordprocessingDocument implements IDisposable. Failing to call Dispose() (or use using) leaves the file locked and may produce a corrupted .docx file. Always wrap in a using statement.
Open XML text spaces being collapsed: By default, Open XML strips leading/trailing spaces from Text elements. To preserve spaces, set Space = SpaceProcessingModeValues.Preserve on the Text element: new Text(" indented") { Space = SpaceProcessingModeValues.Preserve }.
Missing numbering definitions for lists: Open XML bulleted/numbered lists require a NumberingDefinitionsPart with abstract and concrete numbering definitions. Without this, list items render as plain paragraphs. The DocX library handles this automatically.
Forgetting to release COM objects with Interop: Each Interop call creates COM objects that must be released with Marshal.ReleaseComObject(). Failing to release them keeps Word processes running in the background. Always release in a finally block and call GC.Collect() after releasing.
Summary
Use Open XML SDK for server/cloud applications — no Word installation required, full .docx control
Use DocX (Xceed) for a simpler API when complex Open XML structures are not needed
Use Office Interop only for desktop applications that need full Word feature access (track changes, macros)
Always dispose WordprocessingDocument with using to prevent file corruption
Never use Office Interop on servers — it is unsupported, not thread-safe, and leaks resources