在没有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等。閱讀它們之后,您的選擇似乎是:
這是第三種選擇的實現。
先決條件
戰略
.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 itif (reportFullPath != null){//map the reporting engine to the .rdl/.rdlc filervReportViewer.LocalReport.ReportPath = reportFullPath.FullName; // 1. Clear Report DatarvReportViewer.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 reportforeach (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 reportSystem.Data.DataTable tbl = ds.GetDataTable(this.DBConnectionString);//attach the data/table to the Report's dataset(s), by nameReportDataSource rds = new ReportDataSource();rds.Name = ds.Name; //This refers to the dataset name in the RDLC filerds.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 typeforeach (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 QueryParametersre.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 reportforeach (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 itif (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 ? marksusing (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”按鈕,在處理過程中很容易報錯。從中,我學到了一些東西:
最后,我最近更新了這個示例以使用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
總結
以上是生活随笔為你收集整理的在没有SSRS的ASP.NET中运行RDL/RDLC(SQL报告)的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: python 检查图品二维码,识别图片二
- 下一篇: android模拟器 平安行,携手MuM
