SSRS2.NETCore 1.6.0

dotnet add package SSRS2.NETCore --version 1.6.0
                    
NuGet\Install-Package SSRS2.NETCore -Version 1.6.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="SSRS2.NETCore" Version="1.6.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="SSRS2.NETCore" Version="1.6.0" />
                    
Directory.Packages.props
<PackageReference Include="SSRS2.NETCore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add SSRS2.NETCore --version 1.6.0
                    
#r "nuget: SSRS2.NETCore, 1.6.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package SSRS2.NETCore@1.6.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=SSRS2.NETCore&version=1.6.0
                    
Install as a Cake Addin
#tool nuget:?package=SSRS2.NETCore&version=1.6.0
                    
Install as a Cake Tool

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 "PDF" .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 (Direction RTL) Word's right-to-left paragraphs, and a table whose LayoutDirection is RTL a right-to-left Word table, its cells in reading order; in a report whose Language is 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 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

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/