Tvorba dokumentů pomoci Reporting Services Local Report

Jan Holan       17. 12. 2013       ASP.NET WebForms, ASP.NET/IIS, Tisk       9348 zobrazení

Možná znáte reportovací nástroj Microsoftu - SQL Server Reporting Services. Jeho samostatná komponenta Microsoft Report Viewer ale kromě využití serverového řešení obsahuje i část Local Report. Pomoci Local Report je možné vyvářet reporty (tiskové sestavy) v .NET aplikacích bez nutnosti Report Serveru nebo dalších komponent. Report lze buď tisknout, zobrazovat do náhledu nebo exportovat do Wordu, Excelu a PDF. A to je právě ideální řešení jak v nejen Desktop ale i Webových aplikacích generovat výstupy do těchto formátů.

Ukážeme si postup vytvoření jednoduché Webové ASP.NET aplikace, která bude pomoci tiskové sestavy vytvářet výstup dat do PDF.

Report Viewer

Jak už jsem psal, jediné co potřebujeme na počítač (v případě ASP.NET na webový server) doinstalovat je komponenta Report Viewer. Ta se instaluje pomoci samostatného balíčku Report Viewer Redistributable. Ale pozor jakou verzi použijete, od Visual Studia 2005 (verze 8) nám skoro s každou verzí Visual Studia (kromě té poslední VS 2013) přišla i nová verze této komponenty.

Poslední je tedy verze 11, její balíček Microsoft Report Viewer 2012 Runtime - ReportViewer.msi můžete stáhnout zde. K němu je ještě potřeba součást Microsoft SQL Server 2012 Feature Pack - balíček Microsoft System CLR Types for SQL Server 2012 zde (x64) nebo zde (x86).

Založení projektu

Do existujícího nebo nového projektu Webové aplikace (vystačíme si klidně jen s Empty project šablonou) přidáme reference na Report Viewer. Jedná se o tyto assembly:

Microsoft.ReportViewer.Common.dll (C:\Windows\assembly\GAC_MSIL\Microsoft.ReportViewer.Common\11.0.0.0__89845dcd8080cc91\Microsoft.ReportViewer.Common.dll)

Microsoft.ReportViewer.WebForms.dll (C:\Windows\assembly\GAC_MSIL\Microsoft.ReportViewer.WebForms\11.0.0.0__89845dcd8080cc91\Microsoft.ReportViewer.WebForms.dll)

Datový objekt

Report může jako datový zdroj používat přímo databázi, Local Reports kromě toho ale můžeme napojit i na libovolný .NET objekt. To může být výhodné, pokud v projektu již takové objekty (modely) používáme, například při použití Entity Framework apod. Napojením reportu na tyto objekty tak zajistíme jednotný přístup k datům jak pro reporty, tak pro zbytek aplikace. Ukážeme si tento způsob.

Já jsem si pro můj testovací projekt vytvořil tuto třídu modelu:

public class Person
{
    public int ID { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public int? Age { get; set; }
}

Pozn.: Všimněte si, že jednu vlastnost mám nullable a nebude problém jí v Reporting Services použít. To je velká výhoda, vzpomínám si že například takový Crystal Reports ani ve verzi 13 (verze pro VS 2010) nullable typy nepodporoval.

Dále si vyrobíme třídu, přes kterou bude report k datům přistupovat. Třída nesmí být static a musí mít výchozí konstruktor:

namespace LocalReportSample.Reports
{
    public class TestReportDataSource
    {
        #region action methods
        public IEnumerable<Person> GetPersons()
        {
            //Make some test data
            return new List<Person>
            {
                new Person() { ID = 1, FirstName = "John", LastName= "Smith", Age = 23 },
                new Person() { ID = 2, FirstName = "Steven", LastName= "Thompson", Age = 48 },
                new Person() { ID = 3, FirstName = "Alice", LastName= "Walters" },
                new Person() { ID = 4, FirstName = "Thomas", LastName= "Bradley" },
                new Person() { ID = 5, FirstName = "Edward", LastName= "Wideman", Age = 36 },
                new Person() { ID = 6, FirstName = "Megan", LastName= "Gibson" }
            };
        }
        #endregion
    }
}

Aby šla připravená metoda GetPersons v reportu použít, musí být instanční a musí vracet IEnumerable datového objektu. V reálné aplikaci by v této metodě bylo volání například LINQ dotazu při použití EF datové vrstvy apod.

Vytvoření reportu

Nyní do projektu přidáme report (.rdlc soubor) – položka Report (nebo Report Wizard) v sekci Reporting dialogu Add New Item. Dále v reportu zvolíme volbu New / Dataset. Pokud vytváříte report proti datovému objektu, je možné, že jako já narazíte na následující problém. V případě, že report přidáváme přímo do ASP.NET projektu, tak dialog vyvolaný tlačítkem New... nabízí rovnou vytvoření připojení k databázi a nelze vybrat typ datového zdroje. Je proto potřeba dostat naší třídu s datovým zdrojem přímo do výběru Data source/Available datasets. Mě se to povedlo přidáním do projektu nejprve alespoň jedné Web Form aspx stránky (nevím jak se to řeší v MVC). Po zkompilování projektu již výběr Data source nabízí příslušný namespace a metodu GetPersons v mojí třídě TestReportDataSource.

image

V případě přidávání reportu do projektu typu Class Library se při vytváření datového zdroje (tlačítkem New…) zobrazí navíc výběr typu datového zdroje (Databáze, Služba, Objekty, SharePoint). Zde pak vybereme typ Object.

image

Po vytvoření datového zdroje reportu již můžeme podle potřeby vytvořit jeho design, v kontrolu tablix přitom vybereme pojmenovaný Dataset.

Volání reportu

Pro volání reportu si vytvoříme pomocnou třídu ReportDocument, která provede načtení definice reportu a volání exportu v daném formátu.

/// <summary>
/// Podporované formáty pro export reportu
/// </summary>
public enum ReportExportFormat
{
    /// <summary>
    /// Export do MS Excelu (.xls)
    /// </summary>
    Excel = 1,
    /// <summary>
    /// Export do PDF
    /// </summary>
    PDF = 2,
    /// <summary>
    /// Export do MS Wordu (.doc)
    /// </summary>
    Word = 3,
    /// <summary>
    /// Export do MS Excelu (.xlsx)
    /// </summary>
    ExcelOpenXml = 4,
    /// <summary>
    /// Export do MS Wordu (.docx)
    /// </summary>
    WordOpenXml = 5
}

/// <summary>
/// Třída pro načtení a operace s reportem
/// </summary>
public class ReportDocument
{
    #region member varible and default property initialization
    private string m_ReportName;

    /// <summary>
    /// WebForms <c>LocalReport</c> object
    /// </summary>
    public LocalReport LocalReport { get; private set; }

    /// <summary>
    /// Kolekce parametrů reportu
    /// </summary>
    public ReportParameterCollection Parameters { get; private set; }
    #endregion

    #region constructors and destructors
    /// <summary>
    /// Inicializace reportu
    /// </summary>
    /// <param name="reportName">Název reportu</param>
    /// <param name="reportsAssembly"><see cref="Assembly"/> obsahující definice reportů</param>
    /// <param name="reportsNamespace">Namespace definic reportů</param>
    /// <param name="localReport">WebForms <c>LocalReport</c> object (volitelný)</param>
    public ReportDocument(string reportName, Assembly reportsAssembly, string reportsNamespace, LocalReport localReport = null)
    {
        if (reportName == null)
        {
            throw new ArgumentNullException("reportName");
        }
        if (reportsAssembly == null)
        {
            throw new ArgumentNullException("reportsAssembly");
        }
        if (reportsNamespace == null)
        {
            throw new ArgumentNullException("reportsNamespace");
        }
        if (reportsNamespace.Length == 0)
        {
            throw new ArgumentException("reportsNamespace is empty string", "reportsNamespace");
        }

        if (localReport == null)
        {
            localReport = new LocalReport();
        }
        localReport.EnableExternalImages = true;

        //Načtení reportu
        localReport.LoadReportDefinition(GetReportDefinition(reportName, reportsAssembly, reportsNamespace));

        m_ReportName = reportName;
        this.LocalReport = localReport;
        this.Parameters = GetParameters(localReport.GetParameters());

        if (this.Parameters.ContainsKey("ReportName"))
        {
            this.Parameters["ReportName"].Value = reportName;
        }
    }
    #endregion

    #region action methods
    /// <summary>
    /// Export reportu do <see cref="byte"/> array
    /// </summary>
    /// <param name="outputFormat">Výstupní formát</param>
    /// <returns><see cref="byte"/> array s exportovaný reportem</returns>
    public byte[] Export(ReportExportFormat outputFormat)
    {
        if (outputFormat == 0)
        {
            throw new ArgumentException("Invalid outputFormat", "outputFormat");
        }

        //Synchronizace kolekce parametrů do reportu
        SetParameters(this.LocalReport, this.Parameters);

        Warning[] warnings;
        string[] streamids;
        string mimeType;
        string encoding;
        string extension;

        return this.LocalReport.Render(outputFormat.ToString(), null, out mimeType,
                                        out encoding, out extension, out streamids, out warnings);
    }
    #endregion

    #region property getters/setters
    /// <summary>
    /// Název reportu
    /// </summary>
    public string ReportName
    {
        get { return m_ReportName; }
    }

    /// <summary>
    /// Titulek reportu
    /// </summary>
    public string ReportTitle
    {
        get { return this.LocalReport.DisplayName; }
    }
    #endregion

    #region private member functions
    private static System.IO.Stream GetReportDefinition(string reportName, Assembly reportsAssembly, string reportsNamespace)
    {
        if (!reportName.EndsWith(".rdlc", StringComparison.Ordinal) && !reportName.EndsWith(".rdl", StringComparison.Ordinal))
        {
            reportName = reportName + ".rdlc";
        }

        return reportsAssembly.GetManifestResourceStream(reportsNamespace + "." + reportName);
    }

    private static ReportParameterCollection GetParameters(ReportParameterInfoCollection parameterInfoCollection)
    {
        var parameters = new ReportParameterCollection();

        foreach (var parameterInfo in parameterInfoCollection)
        {
            var param = new ReportParameter()
            {
                ParameterName = parameterInfo.Name,
                AllowBlank = parameterInfo.AllowBlank,
                Nullable = parameterInfo.Nullable,
                DataType = (ParameterDataType)parameterInfo.DataType,
                MultiValue = parameterInfo.MultiValue,
                Prompt = parameterInfo.Prompt,
                PromptUser = parameterInfo.PromptUser,
                ErrorMessage = parameterInfo.ErrorMessage,
                Visible = parameterInfo.Visible
            };
            param.SetValueInternal(parameterInfo.Values);

            parameters.Add(parameterInfo.Name, param);
        }

        return parameters;
    }

    private static void SetParameters(LocalReport localReport, ReportParameterCollection parameters)
    {
        localReport.SetParameters(from p in parameters.Values
                                    select new Microsoft.Reporting.WebForms.ReportParameter(p.ParameterName, p.GetValueInternal(), p.Visible));
    }
    #endregion
}

Pro jednodušší práci s parametry reportu ještě třída používá třídy ReportParameter a ReportParameterCollection, ty najdete v přiloženém příkladu.

Http Handler

Export reportu budeme v ASP.NET aplikaci volat přes HttpHandler. Způsobů je více (Generic Handler, ASP.NET Handler), já jsem si ale oblíbil ten, vytvořit handler jako třídu a její registraci provést přes RouteTable. Již jsem to popisoval například zde, provedeme to takto:

  • Do projektu přidáme pomocnou třídu RoutingExtensions, zdroj najdete v přiloženém příkladu (implementace třídy původně pochází z blogu Phil Haacka zde).
  • Do konfigurace Routes (v Global.asax nebo RouteConfig.cs) vložíme tuto registraci:
//Register routes
RouteTable.Routes.MapHttpHandler<ReportHttpHandler>("ReportHttpHandler", "test-report.pdf");
  • Protože jako URL pro handler používám jinou než ASP.NET koncovku (v tomto případě přímo .pdf), aby se nám handler zavolal, musíme do web.config přidat toto nastavení:
<system.webServer>
  <modules runAllManagedModulesForAllRequests="true" />
</system.webServer>
  • A nyní již vytvoříme třídu handleru ReportHttpHandler odvozenou z IHttpHandler.

Implementace třídy ReportHttpHandler může být následující:

/// <summary>
/// Handler pro export sestavy do PDF
/// </summary>
public class ReportHttpHandler : IHttpHandler
{
    #region action methods
    /// <summary>
    /// Enables processing of HTTP Web requests by a custom HttpHandler that implements the System.Web.IHttpHandler interface.
    /// </summary>
    /// <param name="context">An System.Web.HttpContext object</param>
    public void ProcessRequest(HttpContext context)
    {
        context.Response.Cache.SetExpires(DateTime.MinValue);

        bool open = context.Request.QueryString["Export"] != "1";
        string reportUrl = context.Request.Url.Segments[context.Request.Url.Segments.Length - 1];

        Byte[] data = null;
        string exportFileName = null;
        if (reportUrl.StartsWith("test-report.", StringComparison.OrdinalIgnoreCase))
        {
            exportFileName = "TestReport.pdf";
            data = GenerateTestReport();
        }

        if (data == null)
        {
            context.Response.StatusCode = 404;
            context.Response.StatusDescription = "Not Found";
            return;
        }

        DownloadHandlerUtil.WriteFileToResponse(context.Response, data, exportFileName, !open, "application/pdf");
    }

    /// <summary>
    /// Gets a value indicating whether another request can use the System.Web.IHttpHandler instance.
    /// </summary>
    public bool IsReusable
    {
        get { return true; }
    }
    #endregion

    #region private member functions
    private Byte[] GenerateTestReport()
    {
        var report = new ReportDocument("TestReport", System.Reflection.Assembly.GetExecutingAssembly(), "LocalReportSample.Reports");
        report.LocalReport.DataSources.Add(new ReportDataSource("Persons", new TestReportDataSource().GetPersons()));

        return report.Export(ReportExportFormat.PDF);
    }
    #endregion
}

Handler nejprve rozhodne, na který report byl zavolán (mohl by obsloužit volání více reportů) a poté pomoci metody GenerateTestReport vrátí na výstup vyrenderovaný PDF dokument. Výstup je vrácen pomocnou třídou DownloadHandlerUtil, která zajistí správné nastavení hlavičky content-disposition a ContentType pro PDF soubor, třída je včetně popisu dostupná v článku Nastavení hlaviček pro download souboru v HTTP handleru.

Vlastní metoda GenerateTestReport nejprve vytvoří objekt ReportDocument pro TestReport. Dále se do kolekce DataSources nastaví datový objekt vrácen metodou GetPersons. Zde je důležité aby uvedené jméno v DataSources kolekci (parametr name, zde hodnota ”Persons”) odpovídalo jménu zvolenému při vytváření datasetu v reportu v okně Dataset Properties. Pokud by report používal více datových zdrojů, tak je nutné zde nastavit všechny.

Také by tento handler mohl v případě potřeby zpracovat předané parametry (buď z parametru routy nebo z querystringu) a použít je při načítání datového zdroje, nebo pro nastavení Value report parametrů v kolekci ReportDocument.Parameters.

Testovací stránka

A máme hotovo, posledním úkolem je vytvoření volání handleru pro otestování. Já jsem v mém příkladu použil WebForms stránku Default.aspx s následujícími odkazy:

<form id="form1" runat="server">
<div>
    <a href="~/test-report.pdf" runat="server">Open</a>&nbsp;
    <a href="~/test-report.pdf?Export=1" runat="server">Export</a>
</div>
</form>

image

Celý příklad je možné stáhnout zde:

 

hodnocení článku

0       Hodnotit mohou jen registrované uživatelé.

 

Nový příspěvek

 

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

Nejdou mi přidat reporty

Mám takový problém. Používám Visual Studio 2012 Express a v něm mi nejdou editovat ani vytvářet reporty. Z nějakého důvodu tato možnost chybí. Přidám do projektu ReportViewer, zvolím možnost "Design a new report" a...nic se neděje. ReportViewer mám nainstalovaný, zmiňovaný Microsoft System CLR Types for SQL Server 2012 mám také. Nemáte někdo tuchu, co s tím?

Díky moc za jakýkoliv nápad!

Honza

nahlásit spamnahlásit spam 0 odpovědětodpovědět

V Expess edicích Local Reporting není.

Našel jsem pouze tento postup http://www.vbforums.com/showthread.php?7..., tím možná půjde report zobrazit (nezkoušel jsem), ale designer tam stejně nebude.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

Vytváření sestav

Jen bych poznamenal, že samotné sestavy se dají vizuálně navrhovat jak přímo ve Visual Studiu, tak bez nutnosti nainstalovaného Visual Studia pomocí nástroje SQL Server Business Intelligence Development Studio, které se dá volitelně instalovat s SQL Serverem. Je to v podstatě holé Visual Studio 2008 přizpůsobené pro návrh těchto sestav a několika dalších věcí týkajících se Reporting Services.

nahlásit spamnahlásit spam 0 odpovědětodpovědět

No to bych si dovolil nesouhlasit.

SQL Server Business Intelligence Development Studio jsem sice nezkoušel, ale myslím si, že to umí dělat pouze ty serverové reporty tj. soubor s příponou .rdl. Kdyžto pro Local Reporty jsou potřeba soubory s příponou .rdlc, což je .rdl s nějakými elementy navíc (tuším že s informacemi o datovým zdroji).

Také .rdl nejde převést na .rdlc, opačně jo.

nahlásit spamnahlásit spam 0 odpovědětodpovědět
                       
Nadpis:
Antispam: Komu se občas házejí perly?
Příspěvek bude publikován pod identitou   anonym.

Nyní zakládáte pod článkem nové diskusní vlákno.
Pokud chcete reagovat na jiný příspěvek, klikněte na tlačítko "Odpovědět" u některého diskusního příspěvku.

Nyní odpovídáte na příspěvek pod článkem. Nebo chcete raději založit nové vlákno?

 

  • Administrátoři si vyhrazují právo komentáře upravovat či mazat bez udání důvodu.
    Mazány budou zejména komentáře obsahující vulgarity nebo porušující pravidla publikování.
  • Pokud nejste zaregistrováni, Vaše IP adresa bude zveřejněna. Pokud s tímto nesouhlasíte, příspěvek neodesílejte.

Příspěvky zaslané pod tento článek se neobjeví hned, ale až po schválení administrátorem.

přihlásit pomocí externího účtu

přihlásit pomocí jména a hesla

Uživatel:  
Heslo:  

zapomenuté heslo

 

založit nový uživatelský účet

zaregistrujte se

 
zavřít

Nahlásit spam

Opravdu chcete tento příspěvek nahlásit pro porušování pravidel fóra?

Nahlásit Zrušit

Chyba

zavřít

feedback