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);
...