SSRS2.NETCore
1.6.0
dotnet add package SSRS2.NETCore --version 1.6.0
NuGet\Install-Package SSRS2.NETCore -Version 1.6.0
<PackageReference Include="SSRS2.NETCore" Version="1.6.0" />
<PackageVersion Include="SSRS2.NETCore" Version="1.6.0" />
<PackageReference Include="SSRS2.NETCore" />
paket add SSRS2.NETCore --version 1.6.0
#r "nuget: SSRS2.NETCore, 1.6.0"
#:package SSRS2.NETCore@1.6.0
#addin nuget:?package=SSRS2.NETCore&version=1.6.0
#tool nuget:?package=SSRS2.NETCore&version=1.6.0
SSRS2.NETCore
Cross-platform RDLC report engine for .NET Framework 4.7.2 / 4.8 and .NET 8 / 9 / 10. Drop-in replacement for the LocalReport of Microsoft's Report Viewer — generate PDF, Excel, Word, HTML, and image reports from existing .rdlc files on Windows, Linux, macOS, Docker, Kubernetes, and cloud environments.
No GDI+ and no libgdiplus. On Linux, SSRS2 needs fontconfig, which most systems have but
the official .NET Docker images do not; add it to your image:
RUN apt-get update && apt-get install -y --no-install-recommends libfontconfig1 && rm -rf /var/lib/apt/lists/*
(Alpine: apk add --no-cache fontconfig.)
Installation
dotnet add package SSRS2.NETCore
Basic Usage
using SSRS2.Reporting.NETCore;
// Load report definition
Stream reportDefinition; // your RDLC from file or resource
IEnumerable dataSource; // your data for the report
LocalReport report = new LocalReport();
report.LoadReportDefinition(reportDefinition);
report.DataSources.Add(new ReportDataSource("DataSet1", dataSource));
report.SetParameters(new[] { new ReportParameter("Parameter1", "value") });
// Render to PDF
byte[] pdf = report.Render("PDF");
// Or any other supported format
byte[] excel = report.Render("EXCELOPENXML"); // .xlsx
byte[] word = report.Render("WORDOPENXML"); // .docx
byte[] html = report.Render("HTML5"); // .html
byte[] png = report.Render("PNG"); // .png
Checking a report before running it
CheckDefinition() compiles the report definition, as rendering it would, and returns what the engine found, a
Warning per problem: its code, the item and the property it is about, the message. Nothing is rendered and no data is
needed, and it does not throw for a definition that is not valid.
LocalReport report = new LocalReport();
report.LoadReportDefinition(reportDefinition);
foreach (Warning problem in report.CheckDefinition())
{
// rsFieldReference | Textbox Title.Value | The Value expression for the text box 'Title' refers to the field 'Nmae'...
Console.WriteLine($"{problem.Severity} {problem.Code} | {problem.ObjectType} {problem.ObjectName}.{problem.PropertyName} | {problem.Message}");
}
An error means the report cannot run: XML that is not the RDL schema's, a field or a parameter that does not exist, a scope, an expression that does not compile. Useful before deploying a report, and where reports are generated or edited by a program.
CheckRendering() (.NET 8, 9, 10 and 11) goes further. It says the definition's problems as a person fixes them: the field,
parameter, scope or function meant ("Did you mean ‘Amount’? dataset Sales has Amount, Customer"), the expression and
the line of the definition they are at, and code a RestrictedCode report may not call. Then it renders the report as a PDF with
the data and parameters you gave it and returns what does not come out as written, a ReportProblem each with its item,
a message and how to fix it: text that a box that does not grow cuts off, characters drawn as boxes (the text's font
lacks them), text in a color that barely differs from what is under it, a body wider than its page, a deck's slide
holding an item past its edges or printing across pages, what the render warns of or fails on, in a flowing
document a heading or a table's header ending a page, a Lookup that finds no row for its key, a text field read as
a number or a date in the machine's culture (or formatted with Format, which prints its format code for text), and
text drawn with another font than the report names. GetFontsUsed() lists the fonts the text came out in:
report.DataSources.Add(new ReportDataSource("Orders", orders));
foreach (ReportProblem problem in report.CheckRendering())
Console.WriteLine(problem); // warning textCutOff (Summary): The text of 'Summary' does not fit its box...
Where items print, and what a change does to the pages
CheckRendering(RenderingCheckOptions) (since 1.5.0) gives the same problems, and in the same render where the items you
name print (page, top, left, width, height in cm, by name or by role: role:signature), whether placement rules hold,
how variants of the data and longer data paginate, and what moved since a baseline definition:
var options = new RenderingCheckOptions { BaselineDefinition = File.ReadAllText("Receipt.before.rdlc") };
options.Layout.Add("DepositTitle");
options.Expect.Add(ReportLayoutRule.Order("role:table", "DepositTitle?", "role:signature")); // "?": may be hidden
options.Expect.Add(ReportLayoutRule.MaxPages(1));
var noDeposit = new RenderingVariant("no deposit");
noDeposit.DataSources["Receipt"] = receiptWithoutDeposit;
options.Variants.Add(noDeposit);
options.RowCounts["Requests"] = "1..40"; // the rows repeated: from which count is a page added?
RenderingCheckResult result = report.CheckRendering(options);
Console.WriteLine(result.RulesHold); // every rule, in the report and in each variant
foreach (var count in result.RowCounts) Console.WriteLine(count); // Requests: 1-21: 1 page; 22-40: 2 pages
Rules: Order, SamePage, OnPage, Pages, MaxPages, NoOverlap, or ReportLayoutRule.Parse of their JSON. A
baseline's own problems are left out and counted. GetItemLayout() gives every item's places alone.
Reports your application did not write: RestrictedCode
A report's expressions and custom code run with your application's permissions: on .NET 8 and later nothing restrains
them. For reports that come from elsewhere (uploaded by users, written by a language model, converted from another
tool), RestrictedCode holds them to what a report computes with: the report object model (Fields, Parameters,
Globals...), Visual Basic's functions, and .NET's text, numbers, dates, collections, regular expressions and cultures.
LocalReport report = new LocalReport { RestrictedCode = true };
report.LoadReportDefinition(reportDefinition);
Warning[] problems = report.CheckDefinition();
// rsCompilerErrorInExpression | TextRun Title.Paragraphs[0].TextRuns[0].Value |
// ... contains an error: [SSRS2001] 'System.IO.File' is not available to a restricted report
// problems[0].Expression: =System.IO.File.ReadAllText("/etc/hosts")
Files, the environment, processes, threads, reflection (GetType), native libraries (Declare) and code modules are
refused: the report does not compile, and the error names what was refused and the item it is in. A member Visual Basic
only finds when the expression runs (of a value typed Object) is checked then, and shows as #Error with a warning.
It is a guard against mistakes and injected code, not a boundary against a determined attacker, and it does not limit
time or memory: render hostile reports in a process of their own. .NET 8 and later.
A viewer of your own: toggles, sorts, drillthrough
A report shown on screen is more than its pages: a group's rows open on a click, a column sorts, a link leads to
another report. Render the report as HTML5 with the ActionScript device setting: every such click then calls your
script function with what was clicked, viewerAction('Toggle', id, event). Send it to your application, tell the
report, and render it again:
byte[] page = report.Render("HTML5", "<DeviceInfo><ActionScript>viewerAction</ActionScript></DeviceInfo>");
report.Toggle(id); // 'Toggle': a +/- box
report.Sort(sortId, SortOrder.Descending); // 'Sort': the id ends with _A or _D, the order asked for
int pageNumber = report.FindBookmark(bookmark); // 'Bookmark': a "Go to bookmark" link
LocalReport detail = report.Drillthrough(id, out string reportPath); // 'Drillthrough'
Drillthrough returns the report the action opens, holding the parameter values the action gives it: load its
definition if yours came from a stream, give it its rows, render it. FindDocumentMapNode gives the page of an entry
of GetDocumentMap(). Each call is what the click does in Report Viewer.
Supported Export Formats
| Format | Render string | Extension |
|---|---|---|
"PDF" |
||
| Excel | "EXCELOPENXML" |
.xlsx |
| Word | "WORDOPENXML" |
.docx |
| PowerPoint, a slide per page | "PPTX" |
.pptx |
| HTML5 | "HTML5" |
.html |
| HTML 4.0 | "HTML4.0" |
.html |
| MHTML | "MHTML" |
.mhtml |
| PNG | "PNG" |
.png |
| JPEG | "JPEG" |
.jpg |
| WebP | "WEBP" |
.webp |
| TIFF, every page in one file | "IMAGE" (DeviceInfo OutputFormat: TIFF by default) |
.tif |
| PNG or JPEG, a stream per page | "IMAGE" (DeviceInfo OutputFormat: PNG, JPEG), as Report Viewer's code that prints a report reads them |
.png, .jpg |
| CSV (the report's data) | "CSV" |
.csv |
| XML (the report's data) | "XML" |
.xml |
PDF, Word, Excel, PowerPoint, HTML, MHTML, PNG and XML can carry the report they were rendered from, when the device
setting EmbedSource is true (not in PDF/A): ReportImport reads it back as that report (below).
PowerPoint: editable slides
Render("PPTX") writes a slide per page: text boxes, PowerPoint tables, pictures and shapes that people edit, placed
where the PDF places them. As in SQL Server Reporting Services' PowerPoint renderer, the slides are PowerPoint's 16:9
(13.333 × 7.5 in) and the report is paginated to them, unless the device information asks for the report's own page
or gives a PageWidth and PageHeight:
byte[] deck = report.Render("PPTX", "<DeviceInfo><UseReportPageSize>true</UseReportPageSize></DeviceInfo>");
The deck's title is the report's display name (or the device information's Title), its author the report's. A
page's first heading of the document map is the slide's title, which PowerPoint's outline and accessibility checker
read, and a rectangle as large as the page, of one color, is the slide's background. A page number in a page header
or footer (=Globals!PageNumber, or ="Page " & Globals!PageNumber & " of " & Globals!TotalPages) is PowerPoint's
slide number field, which follows the slides when they are moved. An image's or a text box's ToolTip is its
alternative text, a matrix's column and row headers are its table's header row and column for PowerPoint, and the
deck's master takes the report's looks, so a slide added in PowerPoint matches the rest. Right-to-left text boxes
(Direction RTL) are PowerPoint's right-to-left paragraphs, their lists indented on the right; a table whose
LayoutDirection is RTL is a right-to-left table in PowerPoint, its columns in reading order; and a deck whose titles
are right to left has a right-to-left master, so an Arabic or Hebrew slide added in PowerPoint reads right to left too.
A rectangle's custom property SSRS2.Notes is its slide's speaker notes, which PowerPoint's presenter view shows (a
paragraph each line; Report Viewer and the other formats ignore it):
<Rectangle Name="Slide1">
<CustomProperties>
<CustomProperty><Name>SSRS2.Notes</Name><Value>Open with the pilot's results.</Value></CustomProperty>
</CustomProperties>
...
</Rectangle>
Changing a report from code: ReportEditor
ReportEditor (since 1.5.0, .NET 8, 9, 10 and 11) changes an RDLC or RDL file as a person would in a designer, and keeps
everything else as it was: what no change touched stays byte for byte, with the file's encoding and line ends. It
outlines a report with the role of each item (title, table, totals, declaration, signature...), so one change can be
written once for many variants of a report, and applies short operations (text boxes, fields, table rows, a block that
leaves no gap when hidden, moves and swaps with what is under them following, an item copied from another report,
Lookup texts, styles, groups, totals, charts, pictures, pages) or exact replacements:
using SSRS2.Reporting.NETCore;
var editor = ReportEditor.OpenFile("Receipt_de-CH.rdlc");
Console.WriteLine(editor.Outline()); // the page, datasets, items top to bottom with their roles
ReportEdit edit = editor.Apply("""
[{"op": "field", "dataset": "Receipt", "name": "Deposit", "type": "Decimal"},
{"op": "textbox", "name": "DepositText", "before": "role:signature",
"text": "=Format(First(Fields!Deposit.Value, \"Receipt\"), \"N2\")",
"hidden": "=IsNothing(First(Fields!Deposit.Value, \"Receipt\"))"}]
""");
if (!edit.Applied) Console.WriteLine(edit.Problem); // all or nothing: the definition is unchanged
editor.SaveFile(); // refused when the file changed since it was read
ReportEditor.Operations lists the operations and ReportEditor.Help("textbox") their words. ToLocalReport() runs
the edited definition, for CheckRendering above. DefinitionStructure.GroupFiles(paths) groups many reports by their
structure and says how each group differs, to know which variants one change fits.
Reports of other tools, and Markdown: ReportImport
ReportImport (.NET 8, 9, 10 and 11) converts reports of other tools, and Markdown, to RDLC, and to a LocalReport ready to
render: ActiveReports (.rdlx, .rpx), Telerik Reporting (.trdx, .trdp), FastReport (.frx, .fr3), Stimulsoft (.mrt),
DevExpress (.repx), Crystal Reports (the XML that RptToXml exports from a .rpt), Microsoft Access (a report's SaveAsText
export), JasperReports (.jrxml), Eclipse BIRT (.rptdesign), QuickReport and ReportBuilder (.dfm, .rtm), Visual Basic 6
Data Reports (.dsr), PowerBuilder DataWindows (.srd) and Visual FoxPro (.frx with its .frt). The conversion is the same
every time, by rules, and it says what it did not carry over as it was:
ImportedReport imported = ReportImport.ImportFile("Invoice.frx");
foreach (ImportNote note in imported.Notes)
Console.WriteLine(note); // where it is in the other tool's report, and what became of it
File.WriteAllText("Invoice.rdlc", imported.Rdlc);
LocalReport report = imported.ToLocalReport(); // its datasets given the rows the report carried, if any
report.DataSources.Clear();
report.DataSources.Add(new ReportDataSource("Orders", orders)); // yours
byte[] pdf = report.Render("PDF");
Markdown is a document (headings, paragraphs, lists, tables, code, pictures, footnotes, Mermaid flowcharts) or slides
when its front matter says marp: true or layout: slides (Marp's directives, background pictures, fitted headings,
columns and cards, and the deck's CSS), which render as an editable PowerPoint deck:
LocalReport deck = ReportImport.FromMarkdown(File.ReadAllText("review.md")).ToLocalReport();
File.WriteAllBytes("review.pptx", deck.Render("PPTX"));
ImportFile reads the pictures a Markdown names from its folder and the folders under it only;
Import(fileName, bytes, readFile) and FromMarkdown(text, layout, readFile) take them from a function of yours. A
file the importers do not read, or cannot convert, is a ReportImportException.
Documents: PDF, Word, Excel, PowerPoint, HTML, CSV and XML
ReportImport also reads documents back into reports. A file SSRS2 wrote with the device setting EmbedSource carries
the report it was rendered from (its definition, parameters and rows: in the PDF as an attached file, in Word, Excel and
PowerPoint as a part of their package, in HTML, MHTML and XML as a comment, in a PNG as a text chunk), and comes back as
that report: its definition as it was, its parameters' values, and the rows it was rendered from (rows a data reader or a
sequence read once gave are not kept, and a note says so). A file is anyone's: ToLocalReport() renders such a report
with its expressions and code restricted (RestrictedCode), which you set to false for files whose author you trust.
byte[] pdf = report.Render("PDF", "<DeviceInfo><EmbedSource>true</EmbedSource></DeviceInfo>");
ImportedReport again = ReportImport.Import("invoice.pdf", pdf); // the definition, its parameters and its rows
Any other document becomes a report that looks as it does: a PDF of any producer (its text where it is printed, in its
fonts, its boxes and lines, its pictures; text right to left and text turned a quarter), Word (.docx: paragraphs, lists,
tables, pictures, headers and footers, Report Viewer's layout tables unfolded), Excel (.xlsx: each sheet's grid, its
formats, merges and formulas' values), PowerPoint (.pptx: a page per slide, its shapes, tables, charts and notes), HTML
and MHTML (the page's flow in its style sheet's looks, with its pictures). CSV and XML data become datasets and tables.
What the document shows as data becomes data, by rules: a table of values is a dataset of its rows (typed, with the
format that printed them), labels and their values (IBAN: CH93..., a term sheet's terms) are fields of a one-row dataset
named Fields, and a summary whose value is a column's total or last value (a statement's total debits, its closing
balance) is that aggregate. So a document someone sends you is a template you give your own rows:
ImportedReport statement = ReportImport.ImportFile("statement.pdf");
foreach (ImportNote note in statement.Notes)
Console.WriteLine(note); // e.g. "Closing balance is the table's last value: it is computed from its rows"
LocalReport report = statement.ToLocalReport(); // its datasets hold the rows the document showed
report.DataSources.Clear();
report.DataSources.Add(new ReportDataSource("Page1_Table_Data", transactions)); // yours, with its columns' names
report.DataSources.Add(new ReportDataSource("Fields", details));
File.WriteAllBytes("statement-october.pdf", report.Render("PDF"));
A scanned page keeps its picture (no text is read from pictures); a protected PDF is refused.
What a written file holds: ReportOutput
ReportOutput (.NET 8, 9, 10 and 11) reads a written file back as the application that opens it gets it: a deck slide by
slide (its layout, title and content placeholders, shapes and attached arrows, PowerPoint's own bullets and numbers,
tables and their header row, pictures without alternative text, footer, slide number, speaker notes), and a Word
document or a PDF as a whole (its page, title, language, outline, tables and whether their header row repeats, list
items, pictures without alternative text, footnotes, header and footer). For a test, or an agent, that checks a deck
or a document came out editable and accessible without opening PowerPoint or Word:
foreach (WrittenSlide slide in ReportOutput.ReadDeck(deck))
Console.WriteLine($"{slide.Number}: {slide.Layout}, \"{slide.Title}\", {slide.Bullets} bullets, {slide.PicturesWithoutDescription} pictures without alternative text");
WrittenDocument document = ReportOutput.ReadDocument(report.Render("WORDOPENXML")); // or a PDF
Console.WriteLine($"{document.Layout}, {document.Language}: " + string.Join("; ", document.Outline));
PDF: what it says of itself, passwords, PDF/A
A PDF carries the report's display name as its title and the report's Author, as Report Viewer's do. The device
information sets the rest, or other values:
report.DisplayName = "Sales by region";
byte[] pdf = report.Render("PDF", @"<DeviceInfo>
<Author>Finance</Author><Subject>Board pack</Subject><Keywords>sales, quarter</Keywords>
<UserPassword>to open it</UserPassword>
<OwnerPassword>to change what it permits</OwnerPassword>
<PermitPrinting>false</PermitPrinting><PermitCopying>false</PermitCopying>
</DeviceInfo>");
With a password the PDF is encrypted (AES-256). UserPassword is asked for when the file is opened; with an
OwnerPassword alone it opens freely and PermitPrinting, PermitCopying and PermitModifying say what its reader
may do.
<PdfA>true</PdfA> writes PDF/A-2b, for archives: the file holds its fonts, the profile of its colours and what it
says of itself, and cannot have a password. Text with characters its font lacks (drawn as boxes) keeps a file from
conforming: give such text a font that has them.
Barcodes and QR codes: ssrs2:Barcode
A report holds barcodes as SSRS2's own report item, in SSRS2's namespace. RDL's schema lets a report hold elements of another namespace among its items: Report Viewer and SSRS skip them, the report runs there without its barcodes. Everything the item holds is in SSRS2's namespace, its place and style too (Report Viewer refuses an RDL element inside an element of another namespace):
<Report xmlns="http://schemas.microsoft.com/sqlserver/reporting/2016/01/reportdefinition"
xmlns:ssrs2="http://ssrs2.net/schemas/2026/reportdefinition">
...
<ReportItems>
<ssrs2:Barcode Name="PaymentQr">
<ssrs2:Symbology>QRCode</ssrs2:Symbology>
<ssrs2:Value>=Fields!PaymentLink.Value</ssrs2:Value>
<ssrs2:ErrorCorrection>M</ssrs2:ErrorCorrection>
<ssrs2:Top>1cm</ssrs2:Top><ssrs2:Left>14cm</ssrs2:Left><ssrs2:Height>3cm</ssrs2:Height><ssrs2:Width>3cm</ssrs2:Width>
</ssrs2:Barcode>
</ReportItems>
Symbologies: QRCode, DataMatrix, GS1DataMatrix, PDF417, Aztec, Code128, GS1-128 (application identifiers written
(01)09501101530003(10)AB12), Code39, Code93, EAN-13, EAN-8, UPC-A, UPC-E, ITF, Codabar, MSI, Plessey, Pharmacode
(Laetus' one-track, a number from 3 to 131070). Value and
Symbology take expressions; ShowText (the text under bars, EAN and UPC with their check digit), ErrorCorrection,
QuietZone, Rotation (90, 180, 270) and Encoding (a 2D code's character set) are optional; the item's place, style
(the bars' Color, the text's font), visibility and action have RDL's names, with the ssrs2: prefix. PDF draws the modules as vectors,
images as pixels at the output's resolution; Word, Excel and HTML show the code's bitmap. EAN and UPC print the
retail way (the first digit outside, the guard bars reaching into the digits' line); Data Matrix is square. In a table's cell a barcode
takes the cell's size. Payment codes: SwissQrBill(...) writes a Swiss QR-bill's text for the symbology SwissQRCode
(the QR code with the Swiss cross), EpcPayment(...) a SEPA transfer's (draw it as a QR code at ErrorCorrection
M), both =SSRS2.Reporting.Barcodes.... in the value and checked as a bank checks them (IBAN, references). The
schema is at https://ssrs2.net/schemas/2026/reportdefinition, and the online designer
(https://designer.ssrs2.net/designer) places barcodes from its toolbox.
Rounded corners: ssrs2:CornerRadius
A text box's or a rectangle's corners are rounded by SSRS2's own style element, in its Style: one length for the
four corners, or four (top left, top right, bottom right, bottom left), as CSS's border-radius writes them. Report
Viewer and SSRS skip it (RDL's schema takes another namespace's element in a style) and draw the box square:
<Rectangle Name="Card">
...
<Style>
<BackgroundColor>#1E5AC8</BackgroundColor>
<ssrs2:CornerRadius>8pt</ssrs2:CornerRadius>
</Style>
</Rectangle>
<Textbox Name="Tab">
...
<Style><BackgroundColor>#E8F5EC</BackgroundColor><ssrs2:CornerRadius>8pt 8pt 0pt 0pt</ssrs2:CornerRadius></Style>
</Textbox>
Lengths in pt, in, cm, mm, pc or px; a constant, not an expression. PDF, image output and PowerPoint round the box's background, its picture and its borders (sides that differ keep their own pen, half of each corner with them); a radius larger than its box allows is made smaller as CSS makes it, and a corner on an edge a page cuts is square. HTML, Word and Excel draw the box square, and so does a table's cell.
Arrowheads: ssrs2:StartArrow and ssrs2:EndArrow
A line's ends take PowerPoint's arrowheads, by SSRS2's own style elements in its Style: Triangle, Arrow (open),
Stealth, Diamond or Oval, three times the line's width long and wide. Report Viewer and SSRS skip them and draw the
line bare:
<Line Name="Flow">
<Top>1cm</Top><Left>1cm</Left><Height>0cm</Height><Width>5cm</Width>
<Style>
<Border><Color>#1E5AC8</Color><Style>Solid</Style><Width>2pt</Width></Border>
<ssrs2:EndArrow>Triangle</ssrs2:EndArrow>
</Style>
</Line>
The start is the line's Left and Top, its end Left plus Width and Top plus Height (either may be negative). PDF and image output draw the heads; PowerPoint draws its own line ends, which it lets people change. HTML, Word and Excel draw the line bare. In a PowerPoint deck, a line whose end is on a box's outline is glued to the box: when someone moves the box, the line follows it. Three lines in a row that make one way at right angles between two boxes are one elbow connector, which keeps its right angles when a box moves.
Shapes: ssrs2:Shape
A text box or a rectangle can be an ellipse, a diamond (a flowchart's decision) or a cylinder (a database), by SSRS2's
own style element in its Style. Report Viewer and SSRS skip it and draw the box:
<Textbox Name="Decision">
<Top>1cm</Top><Left>1cm</Left><Height>2cm</Height><Width>4cm</Width>
<Paragraphs><Paragraph><TextRuns><TextRun><Value>Valid?</Value></TextRun></TextRuns></Paragraph></Paragraphs>
<Style>
<BackgroundColor>#E8F5EC</BackgroundColor>
<Border><Color>#047857</Color><Style>Solid</Style></Border>
<VerticalAlign>Middle</VerticalAlign>
<ssrs2:Shape>Diamond</ssrs2:Shape>
</Style>
</Textbox>
PDF and image output fill, outline and clip the box in its shape (its top border's pen); PowerPoint draws its own ellipse, diamond and can, lines glued to them. HTML, Word and Excel draw the box, and so does a table's cell. The padding is measured from the box; PowerPoint keeps a shape's text in the shape (a diamond's middle half, an ellipse's inscribed rectangle, a can's body under its lid), so pad a diamond a quarter of its width and height, and the deck wraps its text as the PDF does.
Text past its box: ssrs2:Overflow
A text box that does not grow cuts the text it has no room for. SSRS2's own style element in its Style draws all of
it past the box instead, as the document a report was imported from shows it (lines a PDF sets closer than their font's
own line, a label a little longer than its box). Report Viewer and SSRS skip it and cut the text:
<Textbox Name="Label">
<CanGrow>false</CanGrow>
<Top>1cm</Top><Left>1cm</Left><Height>0.4cm</Height><Width>3cm</Width>
<Paragraphs><Paragraph><TextRuns><TextRun><Value>A label a little too long</Value></TextRun></TextRuns></Paragraph></Paragraphs>
<Style><ssrs2:Overflow>Visible</ssrs2:Overflow></Style>
</Textbox>
PDF and image output draw every line; a page's end and vertical text still cut. Clip, the default, cuts it.
Letter spacing: ssrs2:LetterSpacing
SSRS2's own style element in a text box's Style adds room after each character, as CSS's letter-spacing does: a
length (pt, in, cm, mm, pc, px, or a number alone in points), negative to set the letters closer. The text is
laid out with it, so lines wrap and boxes grow by it. Report Viewer and SSRS skip the element and set the text as its
font does:
<Textbox Name="Heading">
<Top>1cm</Top><Left>1cm</Left><Height>1cm</Height><Width>12cm</Width>
<Paragraphs><Paragraph><TextRuns><TextRun><Value>QUARTERLY REVIEW</Value>
<Style><FontSize>18pt</FontSize><FontWeight>Bold</FontWeight></Style></TextRun></TextRuns></Paragraph></Paragraphs>
<Style><ssrs2:LetterSpacing>2pt</ssrs2:LetterSpacing></Style>
</Textbox>
PDF and image output space the letters; PowerPoint, Word and HTML write their own character spacing. Excel has none. Text that needs shaping (Arabic, the Indic scripts) is set as its font sets it. A PDF's import sets it where the PDF's text is tracked, and Word's, PowerPoint's and CSS's character spacing come back as it.
A background picture sized to its box: ssrs2:BackgroundSizing
RDL draws a text box's or a rectangle's background picture at its own size, repeated (Repeat, RepeatX, RepeatY)
or once (Clip). SSRS2's own style element in its Style sizes it to the box instead: Fit stretches it to the box,
FitProportional draws it whole in the box's middle, its proportions kept, and Cover covers the box with it, its
proportions kept, centered, its sides cut. The picture is drawn once. Report Viewer and SSRS skip the element and draw
the picture as BackgroundRepeat says, so write Clip there to have it drawn once in both:
<Rectangle Name="Banner">
<Top>1cm</Top><Left>1cm</Left><Height>4cm</Height><Width>16cm</Width>
<Style>
<BackgroundImage><Source>Embedded</Source><Value>Photo</Value><BackgroundRepeat>Clip</BackgroundRepeat></BackgroundImage>
<ssrs2:BackgroundSizing>Cover</ssrs2:BackgroundSizing>
</Style>
</Rectangle>
PDF, image output, PowerPoint and HTML size it, and clip it to rounded corners. Items in the rectangle are drawn over it: a page's artwork under its text, without an image item beneath the text boxes.
Header rows on each page: ssrs2:HeaderRows
RDL repeats a table's header row on each page only beside a group (KeepWithGroup, RepeatOnNewPage): a table of
static rows, with no data to group, shows its header once. SSRS2's own element in the Tablix repeats its first rows
at the top of each page the table goes on to, and Word marks them as its header rows. Report Viewer and SSRS skip it:
<Tablix Name="Requirements">
...
<ssrs2:HeaderRows>1</ssrs2:HeaderRows>
</Tablix>
It applies to a table without groups, row or column headers, whose header rows have a cell for each column, none merged. Each row under them stays on one page where it fits (a page starting inside a row repeats no header).
A flowing Word document: FlowLayout
Report Viewer lays each page of a Word export out in tables, a text box in a cell, so a report opens in Word as tables
in tables. For a document whose blocks stand one under the other, the Word device setting FlowLayout (or the report's
custom property SSRS2.WordFlow set to true) writes them as Word's own, in a document of today's Word (no
compatibility mode):
- a text box's paragraphs in the document (its place and padding their indents, its fill and borders theirs); a
heading (a text box with
SSRS2.MapLevel, below) in Word's Heading style of its level, the styles taking the document's heading looks, so that a heading added in Word looks like the others; - a table a Word table (its header rows repeating, its rows kept whole), a picture in a paragraph, a line across a paragraph's border, the blocks of a rectangle that draws nothing one under the other (kept together when it is);
- a drawing of boxes and lines (a rectangle holding text boxes and lines, with SSRS2's arrowheads or box shapes: a Markdown's Mermaid diagram) Word's drawing canvas, its lines connectors glued to the boxes they end on, so that a box moved in Word brings its arrows along;
- the page header and footer Word's paragraphs, a page number Word's field;
- a Markdown's footnotes Word's footnotes;
- right-to-left text (
DirectionRTL) Word's right-to-left paragraphs, and a table whoseLayoutDirectionis RTL a right-to-left Word table, its cells in reading order; in a report whoseLanguageis Arabic, Hebrew or another right-to-left language, the section is right to left and the styles carry the size and weight Word draws that script with.
Blocks side by side keep Report Viewer's layout. Word then lays the pages out itself.
var docx = report.Render("WORDOPENXML", "<DeviceInfo><FlowLayout>true</FlowLayout></DeviceInfo>");
A document's outline: SSRS2.MapLevel
A report's document map (the PDF's bookmarks, Word's navigation pane) nests its entries by the items holding them:
headings standing side by side in the body are all at one level. A text box's custom property SSRS2.MapLevel (1 to
9) gives its document map label a level of its own: the labels after it of a deeper level go under it, as an H2
under its H1. Report Viewer ignores it.
<CustomProperties>
<CustomProperty><Name>SSRS2.MapLevel</Name><Value>2</Value></CustomProperty>
</CustomProperties>
Platform Support
- Windows x64
- Linux x64 and ARM64
- macOS
- Docker containers
- Azure App Service (where ssrs2.net runs)
Target Frameworks
- .NET Framework 4.7.2
- .NET Framework 4.8
- .NET 8.0 (LTS)
- .NET 9.0
- .NET 10.0 (LTS)
- .NET 11.0 (release candidate 2, supported in production by Microsoft; SSRS2 follows its release)
The same package works for both legacy .NET Framework apps and modern cross-platform .NET, so you can adopt SSRS2 in place on Windows today and migrate to .NET 10 / Linux when ready.
The importers (ReportImport) need .NET 8, 9, 10 or 11.
Licensing
SSRS2.NETCore is commercially licensed: one licence per company, USD 799 a year or USD 1,999 for three years, bought at ssrs2.net/pricing. A key keeps working forever with every version released until its date. Without a license key, output renders with a trial watermark (no exceptions thrown). See LICENSE.md and the licence terms.
Documentation
For full documentation, migration guides, and live demos, visit ssrs2.net.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. net11.0 is compatible. |
| .NET Framework | net472 is compatible. net48 is compatible. net481 was computed. |
-
.NETFramework 4.7.2
- Microsoft.CodeAnalysis.VisualBasic (>= 4.8.0)
- PdfSharp (>= 6.2.0)
- SkiaSharp (>= 2.88.8)
- SkiaSharp.HarfBuzz (>= 2.88.8)
- System.Security.Cryptography.Pkcs (>= 8.0.2)
- System.Text.Json (>= 8.0.5)
- ZXing.Net (>= 0.16.11)
-
.NETFramework 4.8
- Microsoft.CodeAnalysis.VisualBasic (>= 4.8.0)
- PdfSharp (>= 6.2.0)
- SkiaSharp (>= 2.88.8)
- SkiaSharp.HarfBuzz (>= 2.88.8)
- System.Security.Cryptography.Pkcs (>= 8.0.2)
- System.Text.Json (>= 8.0.5)
- ZXing.Net (>= 0.16.11)
-
net10.0
- HarfBuzzSharp.NativeAssets.Linux (>= 8.3.1.5)
- Markdig (>= 1.4.0)
- Microsoft.CodeAnalysis.VisualBasic (>= 4.14.0)
- PdfSharp (>= 6.2.0)
- SkiaSharp (>= 3.119.4)
- SkiaSharp.HarfBuzz (>= 3.119.4)
- SkiaSharp.NativeAssets.Linux (>= 3.119.4)
- Svg.Skia (>= 5.1.1)
- System.CodeDom (>= 10.0.0)
- System.IO.Packaging (>= 10.0.0)
- System.Security.Cryptography.Pkcs (>= 10.0.13)
- System.Security.Cryptography.Xml (>= 10.0.13)
- System.ServiceModel.Http (>= 10.0.652802)
- ZXing.Net (>= 0.16.11)
-
net11.0
- HarfBuzzSharp.NativeAssets.Linux (>= 8.3.1.5)
- Markdig (>= 1.4.0)
- Microsoft.CodeAnalysis.VisualBasic (>= 4.14.0)
- PdfSharp (>= 6.2.0)
- SkiaSharp (>= 3.119.4)
- SkiaSharp.HarfBuzz (>= 3.119.4)
- SkiaSharp.NativeAssets.Linux (>= 3.119.4)
- Svg.Skia (>= 5.1.1)
- System.CodeDom (>= 10.0.0)
- System.IO.Packaging (>= 10.0.0)
- System.Security.Cryptography.Pkcs (>= 10.0.13)
- System.Security.Cryptography.Xml (>= 10.0.13)
- System.ServiceModel.Http (>= 10.0.652802)
- ZXing.Net (>= 0.16.11)
-
net8.0
- HarfBuzzSharp.NativeAssets.Linux (>= 8.3.1.5)
- Markdig (>= 1.4.0)
- Microsoft.CodeAnalysis.VisualBasic (>= 4.8.0)
- PdfSharp (>= 6.2.0)
- SkiaSharp (>= 3.119.4)
- SkiaSharp.HarfBuzz (>= 3.119.4)
- SkiaSharp.NativeAssets.Linux (>= 3.119.4)
- Svg.Skia (>= 5.1.1)
- System.CodeDom (>= 8.0.0)
- System.IO.Packaging (>= 8.0.1)
- System.Security.Cryptography.Pkcs (>= 8.0.2)
- System.Security.Cryptography.Xml (>= 8.0.5)
- System.ServiceModel.Http (>= 8.1.0)
- ZXing.Net (>= 0.16.11)
-
net9.0
- HarfBuzzSharp.NativeAssets.Linux (>= 8.3.1.5)
- Markdig (>= 1.4.0)
- Microsoft.CodeAnalysis.VisualBasic (>= 4.8.0)
- PdfSharp (>= 6.2.0)
- SkiaSharp (>= 3.119.4)
- SkiaSharp.HarfBuzz (>= 3.119.4)
- SkiaSharp.NativeAssets.Linux (>= 3.119.4)
- Svg.Skia (>= 5.1.1)
- System.CodeDom (>= 9.0.0)
- System.IO.Packaging (>= 9.0.0)
- System.Security.Cryptography.Pkcs (>= 9.0.21)
- System.Security.Cryptography.Xml (>= 9.0.21)
- System.ServiceModel.Http (>= 8.1.0)
- ZXing.Net (>= 0.16.11)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.6.0 | 78 | 10/19/2026 |
| 1.5.0 | 234 | 10/14/2026 |
| 1.4.1 | 96 | 10/8/2026 |
| 1.4.0 | 96 | 10/8/2026 |
| 1.3.0 | 117 | 10/3/2026 |
| 1.2.0 | 91 | 10/1/2026 |
| 1.1.0 | 98 | 9/29/2026 |
| 1.0.7 | 92 | 9/28/2026 |
| 1.0.6 | 96 | 9/28/2026 |
| 1.0.5 | 99 | 9/27/2026 |
| 1.0.4 | 157 | 8/22/2026 |
| 1.0.3 | 2,411 | 5/15/2026 |
| 1.0.2 | 254 | 2/10/2026 |
| 1.0.1 | 147 | 2/10/2026 |
1.6.0: documents back into reports. ReportImport reads PDF, Word, Excel, PowerPoint, HTML, MHTML, CSV and XML: a file SSRS2 wrote with the device setting EmbedSource comes back as the report it was rendered from, any other as a report that looks as it does, what it shows as data its datasets and fields. .NET 11 (release candidate 2): lib/net11.0, decimals made of doubles converted as .NET Framework converts them. Letter spacing (ssrs2:LetterSpacing), gradients on text boxes and rectangles, background pictures sized to their box, Aptos at its own widths. With 1.5.1's changes, which was not published: patched cryptography packages (no NU1902 or NU1903 on install). Details: https://ssrs2.net/