This solution is using OpenXML as document processing.
Who may also find this helpful
- Output Word Documents with Dynamic Section Header
- Port SSRS Report to the Word Open XML file types like docm, dotx, dotm...
Limitations:
- Only XML formated file is supported (.docx for SSRS), limited by OpenXML
- Report Size <~= 32MB (text), limited by docx format
- Increased report generation time cost
- Additional logic need to be done to header if the same report need to provide .PDF format
- The table header consume Header Section
- Alignment issue if fields data cause field to grow
Assumptions:
- You already successfully prepared your grouped data in Tablix
- Page break in between data group has been done
Summary of the Solution:
- Prepare the report using specific identifier that the header appears on every page
- Render the report in .DOCX
- Prepare your data for replacement
- Open the report using OpenXML
- Prepare Section Header
- Find the Page Break between record group and replace it with Section Break
- Bind the section header to each section accordingly
- Done
Key Steps
1. Prepare the report using specific identifier that the header appears on every page
Put and align you group header and table header in the Header Section (not inside Tablix)

[GroupHeaderText1] is the header text you want to repeat and different for every group.
It will appears on every page in the Word document.
3. Prepare your data for replacement
Depends on your report and data structure, prepare your data accordingly.
Example
public class SampleData
{
public string GroupHeaderField { get; set; }
public string Data1 { get; set; }
public string Data2 { get; set; }
public string Data3 { get; set; }
}
Part of function for report generation
ReportViewer rv = new ReportViewer();
ReportDataSource reportDataSource = new ReportDataSource();
reportDataSource.Name = "SampleData";
List<SampleData> dataForReport = GetData();
reportDataSource.Value = dataForReport;
rv.LocalReport.DataSources.Add(reportDataSource);
List<string> lstReplace = new List<string>();
//Prepare the data
foreach (SampleData sa in dataForReport)
{
if (!lstReplace.Contains(sa.GroupHeaderField))
{
lstReplace.Add(sa.GroupHeaderField);
}
}
lstReplace now contains the value for dynamic group headers.
5. Prepare Section Header
Get the header we prepared at Step 1 as template, we are using it
for Section Header
WordprocessingDocument wordDoc = WordprocessingDocument.Open(docPath, true);
MainDocumentPart mainDocPart = wordDoc.MainDocumentPart;
HeaderPart defaultHeaderPart = mainDocPart.HeaderParts.FirstOrDefault();
Now we create header part for each section (group)
List<string> newSectionHeaderIds = new List<string>();
foreach (string groupHeaderText in lstReplace)
{
HeaderPart newGroupHeaderPart = mainDocPart.AddNewPart<HeaderPart>();
string sId = mainDocPart.GetIdOfPart(newGroupHeaderPart);
Header newHeader = (Header)defaultHeaderPart.Header.Clone();
foreach (Paragraph p in newHeader.Descendants<Paragraph>())
{
foreach (Run r in p.Descendants<Run>())
{
foreach (Text t in r.Descendants<Text>())
{
t.Text = t.Text.Replace("[GroupHeaderText1]", groupHeaderText);
}
}
}
newHeader.Save(newGroupHeaderPart);
newSectionHeaderIds.Add(sId);
}
This code basically
- Create a new Header
- Save the Id to use later for section binding
- Clone the content in template to this header
- Replace the identifier with group header text
To understand better, you should use Open XML SDK 2.5 for Microsoft Office and study a DOCX document structure.
6. Find the Page Break between record group and replace it with Section Break
Section Headers are ready. Now create some sections.
By default, report generated by SSRS has Page Break between groups.
We just need to replace them.
Create a SectionProperties template to create sections in Word Document, such that we can have different headers between groups. (You can change PageSize and etc. using this property)
SectionProperties defaultProperties = mainDocumentPart.Document.Body.Descendants<SectionProperties>().FirstOrDefault();
defaultProperties.AppendChild(new SectionType { Val = SectionMarkValues.NextPage });
Now find and replace page break
foreach (Paragraph p in document.Body.Descendants<Paragraph>())
{
foreach (Run r in p.Elements<Run>())
{
foreach (Break b in r.Descendants<Break>())
{
if (b.Type != null)
{
if (b.Type.Value == BreakValues.Page)
{
b.Remove(); //Remove the page break
p.Descendants<ParagraphProperties>().FirstOrDefault().AppendChild(defaultProperties.CloneNode(true)); //Replace by a cloned section break
}
}
}
}
}
Note that properties like page size, width and height is independent for each section, you should set it in Step 5
7. Bind the section header to each section accordingly
Remember the newSectionHeaderIds List we created in Step 5? Now we bind them.
int i = 0;
foreach (SectionProperties sp in document.Body.Descendants<SectionProperties>())
{
//Replace them
sp.RemoveAllChildren<HeaderReference>();
sp.PrependChild(new HeaderReference() { Id = newSectionHeaderIds[i], Type = HeaderFooterValues.Default });
i++;
}
8. Done
The most important Step of all... Save it.
wordDoc.Save();
You should be good to go, let me know it you have questions.