天天看點

在沒有SSRS的ASP.NET中運作RDL/RDLC(SQL報告)先決條件戰略使用代碼結論

目錄

介紹

先決條件

戰略

使用代碼

以終為始

故事的其餘部分

從RDLC檔案中擷取查詢

設定任何參數

重構

報表參數/查詢參數

網址參數

運作查詢并填寫資料表

跟進(重構)

更新:導緻錯誤的其他事情

結論

  • 下載下傳 VStudio 2010 的舊源 - 21.2 KB  或
  • 下載下傳 VStudio 2013 的源代碼 - 179.6 KB

這是一個常見的場景:您有一個用ASP.NET(或MVC或SharePoint)編寫的網站,并且您想顯示一些報告。您可能正計劃編寫一些新報告,并且您正在嘗試決定使用哪種技術,或者您可能有幾個之前已經制作的SSRS報告,并且您希望從您的ASP.NET站點運作它們。

CodeProject上有很多很好的文章,展示了如何使用RDLC檔案運作報表,無論是來自ASP.NET還是WinForms等。閱讀它們之後,您的選擇似乎是:

  1. 将報表放入SSRS并使用ReportViewer控件調用SSRS運作您的報表
  2. 将.RDL或.RDLC檔案添加到您的項目中,并建立一些對象來存放資料,以便報表具有與資料庫的接口,我想要第三種選擇:
  3. 隻需像SSRS一樣運作它,但無需安裝SSRS伺服器。我說的是最低限度的足迹。我隻想将.RDL或.RDLC檔案的名稱傳遞給ASPX頁面并讓它運作。這就是SSRS的做法。我應該也能做到。

這是第三種選擇的實作。

先決條件

  1. 要運作它,您仍然需要安裝報告運作時。您可以将其添加為Nuget包“Microsoft.ReportViewer.Runtime.Common”和“Microsoft.ReportViewer.WebForms”(或WinForms)。Microsoft 下載下傳站點上提供了可再發行元件。
  2. 我有一些舊報告,它們是用舊的商業智能開發工作室(2008)制作的。我還有一些使用ReportBuilder(内置SSRS網絡)制作的新報告。此解決方案适用于任何一個。

戰略

.RDLC檔案都是XML 。如果您使用記事本打開它并檢查内容,您會看到XML描述了顯示/設計,但它還包含其他一些有用的特征。其中之一是報表的查詢(擷取資料)。

我的政策是提取資料庫查詢,設定任何參數,運作查詢,将結果存儲在DataTable(s)中并将其提供給報告。

我的目标是從通用頁面運作我的報告,并通過URL QueryString傳遞報告名稱和任何查詢參數,如下所示:

.../View.aspx?Report=Example.rdlc&StartDate=1/1/2012&EndDate=12/31/2012
           

為簡單起見,我将隻使用應用程式其餘部分使用的相同DB連接配接字元串,但我會将其包裝在本地工廠方法中,以實作可維護性。

使用代碼

以終為始

這就是我開始的地方。這些示例效果很好,但無法使用嵌入在報告中的查詢。在代碼塊(如下)中,您可以看到我建立了一個名為Report (命名空間是RDL)的類來封裝RDLC的内容/結構。我的RDL.Report類還包含一個工廠方法來幫助将XML轉換為對象。

//View.aspx.cs
protected void ShowReport()
{
  System.IO.FileInfo reportFullPath = this.ReportFile;
  //check to make sure the file ACTUALLY exists, before we start working on it
  if (reportFullPath != null)
  {
     //map the reporting engine to the .rdl/.rdlc file
     rvReportViewer.LocalReport.ReportPath = reportFullPath.FullName;  

     // 1. Clear Report Data
     rvReportViewer.LocalReport.DataSources.Clear();

     // 2. Get the data for the report
     // Look-up the DB query in the "DataSets" 
     // element of the report file (.rdl/.rdlc which contains XML)
     RDL.ReportreportDef = RDL.Report.GetReportFromFile(reportFullPath.FullName);

     // Run each query (usually, there is only one) and attach it to the report
     foreach (RDL.DataSet ds in reportDef.DataSets)
     {
        //copy the parameters from the QueryString into the ReportParameters definitions (objects)
        ds.AssignParameters(this.ReportParameters);

        //run the query to get real data for the report
        System.Data.DataTable tbl = ds.GetDataTable(this.DBConnectionString);

        //attach the data/table to the Report's dataset(s), by name
        ReportDataSource rds = new ReportDataSource();
        rds.Name = ds.Name; //This refers to the dataset name in the RDLC file
        rds.Value = tbl;
        rvReportViewer.LocalReport.DataSources.Add(rds);
     }
     rvReportViewer.LocalReport.Refresh();
  }
}  
           

故事的其餘部分

(上面的)代碼塊顯示了應用程式的核心;運作查詢并将資料附加到報表,然後運作報表。現在,讓我們看看擷取資料的部分。

從RDLC檔案中擷取查詢

在.RDLC檔案中,查詢的XML如下所示(删除其他所有内容後):

<Report>
  <DataSets>
    <DataSet Name="IrrelevantToThisExample">
      <Query>
        <DataSourceName>DataTableName</DataSourceName>
        <CommandText>SELECT * FROM sys.Tables</CommandText>
      </Query>
    </DataSet>
  </DataSets>
</Report>
           

在我的第一次嘗試中,我使用XPath從XML(在RDLC檔案内部)中提取查詢。它适用于簡單的查詢。但是,我意識到如果查詢有任何參數(或存儲過程等),事情就會變得一團糟。

在我的第二次嘗試中,我采取了不同的方法。我意識到如果我将XML反序列化為一堆對象,代碼會更容易。這聽起來既複雜又可怕,但是一旦你看到它,你就會意識到XML序列化/反序列化是多麼簡單。

與此XML比對的(簡化的)類如下所示:

[Serializable(), System.Xml.Serialization.XmlRoot("Report")]
public class Report : SerializableBase
{
   public List<DataSet> DataSets = new List<DataSet>();
}

public class DataSet
{
   [System.Xml.Serialization.XmlAttribute]
   public string Name;
   public Query Query = new Query();
}

public class Query
{
   public string DataSourceName;
   public string CommandText;
}
           

反序列化XML後,您可以輕松提取查詢,如下所示:

Report report =Report.Deserialize(xml, typeof(RDL.Report));
String commandText = report.DataSets[0].Query.CommandText;
           

該SerializableBase對象是我從幾個項目中重複使用的東西。它使将任何對象序列化或反序列化為XML變得簡單,反之亦然。這是代碼:

[Serializable]
public class SerializableBase
{
   public static SerializableBase Deserialize(String xml, Type type)
   {
      //… some code omitted for brevity. See downloads.
      System.Xml.Serialization.XmlSerializer ser = 
         new System.Xml.Serialization.XmlSerializer(type);
      using (System.IO.StringReadersr = new System.IO.StringReader(xml))
      {
         return (SerializableBase)ser.Deserialize(sr);
      }
   }
}  
           

設定任何參數

正如我前面提到的,在我處理參數化查詢和存儲過程之前,代碼非常簡單。我不得不添加更多的反序列化類。為簡潔起見,我會将它們包含在下載下傳的代碼中,但可以免去您在此處閱讀代碼的麻煩。别擔心。它們是非常簡單(無聊)的類,與XML的結構相比對,就像上面的序列化類一樣。

重構

此代碼的其餘部分從實用程式類開始。看完之後,我意識到如果我将實用程式代碼封裝在序列化類中作為方法而不是作為外部輔助實用程式函數,那将更加純粹地面向對象。它使序列化類看起來更複雜。這就是為什麼在本文中,我首先以最簡單的形式描述原始類(如上)。

報表參數/查詢參數

不幸的是,在RDLC檔案中,查詢塊定義了它的參數,但沒有為它們定義類型。DB會阻塞不容易轉換的類型,例如:DateTime、Numeric和Integer。幸運的是,參數類型在RDLC的XML的單獨部分中定義。我隻需要将它們複制到查詢參數定義中。不幸的是,它使代碼看起來有點老套,但它确實可靠地完成了工作。

//Report.cs
private void ResolveParameterTypes()
{
   //for each report parameter, find the matching query parameter and copy-in the data type
   foreach (ReportParameter rParam in this.ReportParameters)
   {
      foreach (DataSet ds in this.DataSets)
      foreach (QueryParameter qParam in ds.Query.QueryParameters)
      {
         if (qParam.Value == "=Parameters!" + rParam.Name + ".Value")
         {
            qParam.DataType = rParam.DataType;
         }
      }
   }
}
// override the constructor so the report param types are always resolved to the query params
//as a bonus, now you don't have to cast it after deserializing it
public static Report Deserialize(string xml, Type type)
{
   Report re;
   re = (Report)SerializableBase.Deserialize(xml, type);

   //copy the type-names from the ReportParameters to the QueryParameters
   re.ResolveParameterTypes();

   return re;
}
           

網址參數

現在,我将(URL)QueryString中的參數複制到報告的param中。自然,我對與報告中的QueryString參數名稱比對的參數名稱做出了一些重大假設。如果它們不比對,則會出現錯誤,但應該很容易找出問題所在。我還可以添加一些診斷程式來檢測哪些參數沒有獲得配置設定給它們的值(可能稍後)。

//View.aspx.cs
private System.Collections.Hashtable ReportParameters
{
    get
    {
        System.Collections.Hashtable re = new System.Collections.Hashtable();
        //gather any params so they can be passed to the report
        foreach (string key in Request.QueryString.AllKeys)
        {
            if (key.ToLower() != "path")
            //ignore the "path" param. It describes the report’s file path
            {
                re.Add(key, Request.QueryString[key]);
            }
        }
        return re;
    }
}

//DataSet.cs
public void AssignParameters(System.Collections.HashtablewebParameters)
{
    foreach (RDL.QueryParameter param in this.Query.QueryParameters)
    {
        string paramName = param.Name.Replace("@", "");
        //if this report param was passed as an arg to the report, then populate it
        if (webParameters[paramName] != null)
           param.Value = webParameters[paramName].ToString();
    }
}
           

運作查詢并填寫資料表

這是很基本的。設定指令對象,添加參數,然後隻需使用DataAdapter來填充表格。

//DataSet.cs
public System.Data.DataTable GetDataTable(string DBConnectionString)
{
  System.Data.DataTable re = new System.Data.DataTable();
  using (System.Data.OleDb.OleDbDataAdapter da = 
     new System.Data.OleDb.OleDbDataAdapter(this.Query.CommandText, DBConnectionString))
  {
     if (this.Query.QueryParameters.Count > 0)
     {
        foreach (RDL.QueryParameter param in this.Query.QueryParameters)
        {
           string paramName = param.Name.Replace("@", "");
           //OLEDB chokes on the @symbol, it prefers ? marks
           using (System.Data.OleDb.OleDbCommand cmd = da.SelectCommand)
               cmd.CommandText = cmd.CommandText.Replace(param.Name, "?");

           using (System.Data.OleDb.OleDbParameterCollection params = da.SelectCommand.Parameters)
               switch (param.DataType)
               {
                   case "Text":
                       params.Add(new OleDbParameter(paramName, OleDbType.VarWChar) 
                       { Value = param.Value });
                            break;
                   case "Boolean":
                        params.Add(new OleDbParameter(paramName, OleDbType.Boolean) 
                        { Value = param.Value });
                            break;
                   case "DateTime":
                        params.Add(new OleDbParameter(paramName, OleDbType.Date) 
                        { Value = param.Value });
                            break;
                   case "Integer":
                        params.Add(new OleDbParameter(paramName, OleDbType.Integer) 
                        { Value = param.Value });
                            break;
                   case "Float":
                        params.Add(new OleDbParameter(paramName, OleDbType.Decimal) 
                        { Value = param.Value });
                            break;
                   default:
                        params.Add(new OleDbParameter(paramName, param.Value));
                            break;
                   }
           }
      }
      da.fill(re);
      re.TableName = this.Name;
      return re;
}
           

跟進(重構)

我确實重構了這段代碼(在下載下傳中),這使它有點混亂。我想讓它變得靈活,這樣我就可以在多個項目中使用它。由于我無法确定db連接配接字元串将始終是OLEDB或SqlClient連接配接,是以我檢查了連接配接字元串并為其中任何一個使用了适當的庫集(OLEDB/SQLClient)。代碼長度增加了一倍,但更便攜。

更新:導緻錯誤的其他事情

一位朋友幫我運作了一些測試報告,結果表明如果出現問題,該ReportViewer控件不會生成任何好的/有用的錯誤消息。相比之下,我放在View頁面的“Download”按鈕,在處理過程中很容易報錯。從中,我學到了一些東西:

  1. 如果報表具有外部圖形或報表部件,則這些檔案需要可用(也就是說,您也需要/reports檔案夾中的這些檔案)并且在正确的路徑中。對于我的測試示例,報告使用檔案夾/Reports/Web Parts/中的圖形。
  2. 如果報告具有必需的參數(或不是“可選的”),則必須提供這些參數,否則報告将不會運作。
  3. 參數可以區分大小寫。也許它隻是.NET。無論哪種方式,如果您的報告無法運作,請檢查以確定您提供的是“PrintID”或“PrintId”,而不僅僅是“printid”。

最後,我最近更新了這個示例以使用Visual Studio 2013運作。我添加了一個“診斷”頁面,以檢查一些設定。我改進了“下載下傳”選項。我改進了這個例子來處理外部圖像,并檢測所需的報告參數。

我希望你發現它對你更有效。如果沒有,我會很感激回報,甚至可能是一個示例檔案(.rdl),這樣我就可以解決任何錯誤。

結論

這就是從RDLC檔案中提取查詢并在ASP.NET中運作它所需的全部内容。

SSRS最初是由Microsoft編寫的,作為如何使用這些技術來完成我在此處展示的内容的示例。當然,SSRS的許多功能遠遠超出了我所展示的範圍,但如果您不需要所有這些豐富的功能,那麼此代碼對于您和您的.NET項目來說應該是快速且可移植的。

https://www.codeproject.com/Articles/607382/Running-a-RDL-RDLC-SQL-Report-in-ASP-NET-without-S

繼續閱讀