Home
Ostatnio poznałem nieznany mi wcześniej, a prosty sposób tworzenia raportów i zapisywania ich do formatu PDF czy Excel. Działa on zarówno w kontekście aplikacji ASP.NET jak i w aplikacjach Windows Forms i innych. Mam tutaj na myśli klasy z przestrzeni nazw
Microsoft.Reporting.WebForm (w przypadku aplikacji stacjonarnych chodzi o przestrzeń
Microsoft.Reporting.WinForms).
W przestrzeni
Microsoft.Reporting.WebForm znajdziemy wiele rzeczy, najważniejsze to po pierwsze kontrolka
ReportViewer do prezentowania raportów. Po drugie klasy
LocalReport oraz
ServerReport służące odpowiednio wykonywaniu raportów lokalnie oraz zdalnie na serwerze. Źródłem danych dla raportów może być oczywiście relacyjna baza danych ale również obiekty biznesowe. Definicja raportu to dokument XML stworzony przy pomocy języka
RDL (ang.
Report Definition Language).
Zanim przeję dalej podam założenia/wymagania jakimi się kierowałem:
- Raporty chcę zapisywać do formatu PDF i Excel.
- Proces generowania raportu ma odbywać się lokalnie bez połączenia z bazą danych.
- Dane mam zapisane w obiekcie klasy DataTable.
Po pierwsze musiałem znaleźć sposób dynamicznego generowania definicji raportów. Język
RDL nie wygląda na trudny ale nie zmienia to faktu, że go nie znam. Po krótkich poszukiwaniach napotkałem na ten artykuł
Lesson 4: Creating Code to Generate the Report Definition File. Zaprezentowany tam kod tworzy plik z definicją raportu jako dane wejściowe przyjmując: listę pól/kolumn, zapytanie do bazy danych oraz
connection string. Jak widać kod jest przydatny ale nie do końca ponieważ nie odpowiada postawionym wymaganiom. Jak sie jednak okazało konieczne modyfikacje były bardzo proste.
Pomijając zmianę argumentów wejściowych metody, delikatną modyfikację logiki wystarczyło, że jako zapytanie do bazy danych oraz
connection string przekazałem pusty ciąg znaków
String.Empty. Cała metoda jest dość długa ale w gruncie rzeczy nie zawiera niczego skomplikowanego. Dopowiem tylko, że liczba kolumn generowanego raportu zależy od liczby kolumn w przekezanym do metody obiekcie klasy
DataTable. Pełny kod został pokazany poniżej.
Pokaż/Ukryj kod
///
/// Generuje definicję raportu dla podanej tabeli.
///
/// Strumień w jakim zostanie umieszczona definicja
/// Tabela z danymi wejściowymi dla raportu. Definicja raportu będzie miała tyle
/// kolumn co ta tabela.
/// stream == null
/// dt == null
/// Nie można pisać do strumienia
/// Na podstawie: http://msdn.microsoft.com/en-us/library/ms170239(SQL.90).aspx
public static void GenerateRdl(Stream stream, DataTable dt)
{
if (stream == null)
throw new ArgumentNullException("stream");
if (dt == null)
throw new ArgumentNullException("dt");
if (!stream.CanWrite)
throw new InvalidOperationException("Cannot write to stream!");
XmlTextWriter writer = new XmlTextWriter(stream, Encoding.UTF8);
writer.Formatting = Formatting.Indented;
// Report element
writer.WriteProcessingInstruction("xml", "version=\"1.0\" encoding=\"utf-8\"");
writer.WriteStartElement("Report");
writer.WriteAttributeString("xmlns", null, "http://schemas.microsoft.com/sqlserver/reporting/2003/10/reportdefinition");
writer.WriteElementString("Width", "6in");
writer.WriteElementString("TopMargin", "2cm");
writer.WriteElementString("BottomMargin", "2cm");
writer.WriteElementString("LeftMargin", "2cm");
writer.WriteElementString("RightMargin", "2cm");
// DataSource element
writer.WriteStartElement("DataSources");
writer.WriteStartElement("DataSource");
writer.WriteAttributeString("Name", null, "DummyDataSource");
writer.WriteStartElement("ConnectionProperties");
writer.WriteElementString("DataProvider", "SQL");
writer.WriteElementString("ConnectString", String.Empty);
writer.WriteElementString("IntegratedSecurity", "true");
writer.WriteEndElement(); // ConnectionProperties
writer.WriteEndElement(); // DataSource
writer.WriteEndElement(); // DataSources
// DataSet element
writer.WriteStartElement("DataSets");
writer.WriteStartElement("DataSet");
writer.WriteAttributeString("Name", null, "DummyDataSet");
// Query element
writer.WriteStartElement("Query");
writer.WriteElementString("DataSourceName", "DummyDataSource");
writer.WriteElementString("CommandType", "Text");
writer.WriteElementString("CommandText", String.Empty);
writer.WriteElementString("Timeout", "30");
writer.WriteEndElement(); // Query
// Fields elements
writer.WriteStartElement("Fields");
foreach (DataColumn dc in dt.Columns)
{
writer.WriteStartElement("Field");
writer.WriteAttributeString("Name", null, dc.ColumnName);
writer.WriteElementString("DataField", null, dc.ColumnName);
writer.WriteEndElement(); // Field
}
// End previous elements
writer.WriteEndElement(); // Fields
writer.WriteEndElement(); // DataSet
writer.WriteEndElement(); // DataSets
// Body element
writer.WriteStartElement("Body");
writer.WriteElementString("Height", "5in");
// ReportItems element
writer.WriteStartElement("ReportItems");
// Title
writer.WriteStartElement("Textbox");
writer.WriteAttributeString("Name", null, "Title");
writer.WriteStartElement("Style");
writer.WriteElementString("FontWeight", "700");
writer.WriteEndElement(); // Style
writer.WriteElementString("Top", "0.5cm");
writer.WriteElementString("Left", "2cm");
writer.WriteElementString("Height", "1cm");
writer.WriteElementString("Value", "Raport");
writer.WriteEndElement(); // Textbox
// Table element
writer.WriteStartElement("Table");
writer.WriteAttributeString("Name", null, "DummyTable");
writer.WriteElementString("DataSetName", "DummyDataSet");
writer.WriteElementString("Top", ".5in");
writer.WriteElementString("Left", ".5in");
writer.WriteElementString("Height", ".5in");
writer.WriteElementString("Width", (dt.Columns.Count * 1.5) + "in");
// Table Columns
writer.WriteStartElement("TableColumns");
foreach (DataColumn dc in dt.Columns)
{
writer.WriteStartElement("TableColumn");
writer.WriteElementString("Width", "1.5in");
writer.WriteEndElement(); // TableColumn
}
writer.WriteEndElement(); // TableColumns
// Header Row
writer.WriteStartElement("Header");
writer.WriteStartElement("TableRows");
writer.WriteStartElement("TableRow");
writer.WriteElementString("Height", ".25in");
writer.WriteStartElement("TableCells");
foreach (DataColumn dc in dt.Columns)
{
writer.WriteStartElement("TableCell");
writer.WriteStartElement("ReportItems");
// Textbox
writer.WriteStartElement("Textbox");
writer.WriteAttributeString("Name", null, "Header" + dc.ColumnName);
writer.WriteStartElement("Style");
writer.WriteElementString("TextDecoration", "Underline");
writer.WriteEndElement(); // Style
writer.WriteElementString("Top", "0in");
writer.WriteElementString("Left", "0in");
writer.WriteElementString("Height", ".5in");
writer.WriteElementString("Width", "1.5in");
writer.WriteElementString("Value", dc.Caption);
writer.WriteEndElement(); // Textbox
writer.WriteEndElement(); // ReportItems
writer.WriteEndElement(); // TableCell
}
writer.WriteEndElement(); // TableCells
writer.WriteEndElement(); // TableRow
writer.WriteEndElement(); // TableRows
writer.WriteEndElement(); // Header
// Details Row
writer.WriteStartElement("Details");
writer.WriteStartElement("TableRows");
writer.WriteStartElement("TableRow");
writer.WriteElementString("Height", ".25in");
writer.WriteStartElement("TableCells");
foreach (DataColumn dc in dt.Columns)
{
writer.WriteStartElement("TableCell");
writer.WriteStartElement("ReportItems");
// Textbox
writer.WriteStartElement("Textbox");
writer.WriteAttributeString("Name", null, dc.ColumnName);
writer.WriteElementString("Top", "0in");
writer.WriteElementString("Left", "0in");
writer.WriteElementString("Height", ".5in");
writer.WriteElementString("Width", "1.5in");
writer.WriteElementString("Value", "=Fields!" + dc.ColumnName + ".Value");
writer.WriteElementString("HideDuplicates", "DummyDataSet");
writer.WriteEndElement(); // Textbox
writer.WriteEndElement(); // ReportItems
writer.WriteEndElement(); // TableCell
}
// End Details element and children
writer.WriteEndElement(); // TableCells
writer.WriteEndElement(); // TableRow
writer.WriteEndElement(); // TableRows
writer.WriteEndElement(); // Details
// End table element and end report definition file
writer.WriteEndElement(); // Table
writer.WriteEndElement(); // ReportItems
writer.WriteEndElement(); // Body
writer.WriteEndElement(); // Report
// Flush the writer and close the stream
writer.Flush();
stream.Flush();
stream.Close();
}
Proces generowania raportu wynikowego w żądanym formacie również jest prosty. Poniższy fragment kody generuje raport w formacie PDF. Żeby wytworzyć arkusz programu Excel to metody
Render należy przekazać ciąg znaków
"Excel". Zmienna
reportPath powinna zawierać ścieżkę do pliku z definicją raportu stworzonego przez pokazaną wcześniej metodę. Zmienna
dataTable to z kolei tabela z danymi do zapisania w żądanym formacie. Powinna to być ta sama tabela, która została przekazana do metody
GenerateRdl.
...
FileStream fs = File.OpenWrite( reportPath);
GenerateRdl(fs, dataTable);
LocalReport lr = new LocalReport();
lr.ReportPath = reportPath;
lr.DataSources.Add(new ReportDataSource("DummyDataSet", dataTable));
string deviceInfo = "<DeviceInfo><SimplePageHeaders>True</SimplePageHeaders></DeviceInfo>";
string mimeType = null;
string encoding = null;
string ext = null;
string[] streamids = null;
Warning[] warnings = null;
byte[] bytes = lr.Render("PDF", deviceInfo, out mimeType, out encoding, out ext, out streamids, out warnings);
...