问题
I am having difficulty displaying the contents of a DataTable object in a ReportViewer control. There are no errors, just a blank report viewer being shown on the page. I have looked at the solutions presented in various similar questions found here, here and here - that last one is particularly frustrating as the last comment says "let's continue this discussion in chat" with no answer provided and it is essentially my exact issue.
Page code:
<div class="panel-body">
<rsweb:ReportViewer ID="ReportViewer1" runat="server" Width="100%">
<LocalReport ReportPath="reports\Report1.rdlc">
</LocalReport>
</rsweb:ReportViewer>
</div>
Codebehind:
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
{
string repstr = Request.Form["repId"];
int repId = -1;
int.TryParse(repstr, out repId);
CustomReport rpt = new CustomReport(repId);
data = rpt.ReportResult.Copy();//The report result property is a DataTable object that can have varying # of columns
data.TableName = "CustomReport";
ReportViewer1.ProcessingMode = ProcessingMode.Local;
ReportDataSource source = new ReportDataSource("CustomReport", data);
ReportViewer1.LocalReport.DataSources.Clear();
ReportViewer1.LocalReport.DataSources.Add(source);
ReportViewer1.DataBind();
ReportViewer1.LocalReport.Refresh();
ReportViewer1.Visible = true;
}
}
I have verified that the DataTable is being populated with data and through debugging I've verified that the ReportViewer data properties appear to have the data as well - it's just not being displayed....
Any help would be greatly appreciated!
回答1:
So after a lot of research and googling and help from my awesome development colleagues I came across a solution that essentially involves having to reconstruct the xml structure of the .rdlc file in order to support dynamic columns. There are a couple of caveats to this approach. 1. You have to manually configure the xml referencing the datasource to point at your object method that provides the datatable as the report designer does not support this. I've included my xml changes below. 2. You have to add a table to the report definition with only one column and one row. Maybe somebody can do this better.
So the solution. First the rdlc xml datasource config:
<DataSet Name="DataSet1">
<Query>
<DataSourceName>DataLayer</DataSourceName>
<!--//Put the table name of your datatable object here-->
<CommandText>CustomReportsDs</CommandText>
</Query>
<!--//This is a single empty field for mapping to the table control-->
<Fields>
<Field Name="ReportTitle">
<DataField>ReportTitle</DataField>
<rd:TypeName>System.String</rd:TypeName>
</Field>
</Fields>
<rd:DataSetInfo>
<!--//This must be configured to point to the method that supplies the datatable
//In my instance the DataSetName is the namespace of the object,
//TableName is the object that provides the method and
//ObjectDataSourceSelectMethod is the method that provides the datatable-->
<rd:DataSetName>DataLayer</rd:DataSetName>
<rd:TableName>CustomReport</rd:TableName>
<rd:ObjectDataSourceSelectMethod>ReportResult</rd:ObjectDataSourceSelectMethod>
<rd:ObjectDataSourceType>DataLayer.CustomReport, DataLayer, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null</rd:ObjectDataSourceType>
</rd:DataSetInfo>
</DataSet>
Next you have to configure the report viewer to recieve the report definition from a memory stream:
//load the data
int repId = -1;
int.TryParse(Request.Form["repId"], out repId);
CustomReport rpt = new CustomReport(repId);
DataTable data = rpt.ReportResult.Copy();//The report result property is a DataTable object that can have varying # of columns
data.TableName = "CustomReportDs";
//setup the report viewer
ReportViewer1.ProcessingMode = ProcessingMode.Local;
ReportViewer1.LocalReport.ReportPath = string.Empty;
//prepare to load the report definition xml file
string ReportPath = Server.MapPath(@"\reports\Report1.rdlc");
//my datatable comes with "friendly" column headings - these don't work well as the xml requires cls compliant
//columnheadings so here we rename the columns in the data table but preserve the friendly names for use later
Dictionary<string, string> columns = new Dictionary<string, string>();
int counter = 0;
foreach(DataColumn col in data.Columns)
{
columns.Add("column"+counter.ToString(), col.ColumnName);
col.ColumnName = "column" + counter.ToString();
counter++;
}
//format the report definition xml
byte[] reportDefinitionBytes = Encoding.UTF8.GetBytes(ConfigurXMLReport(ReportPath, "CustomReportDs", columns).OuterXml);
//load the formatted report into the reportviewer
MemoryStream stream = new MemoryStream(reportDefinitionBytes);
ReportViewer1.LocalReport.LoadReportDefinition(stream);
//bind and display the data as normal
ReportDataSource source = new ReportDataSource("DataSet1", data);
ReportViewer1.LocalReport.DataSources.Clear();
ReportViewer1.LocalReport.DataSources.Add(source);
ReportViewer1.DataBind();
ReportViewer1.LocalReport.Refresh();
Finally I have this method, which is a modified version of several methods written by others that I found during my research on the problem
public static XmlDocument ConfigurXMLReport(string path, Dictionary<string, string> Columns)
{
XmlDocument objXmlDocument = new XmlDocument();
objXmlDocument.Load(path);
XmlNamespaceManager mgr = new XmlNamespaceManager(objXmlDocument.NameTable);
string uri = "http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition";
mgr.AddNamespace("df", uri);
//locate and create nodes for cloning and modification
XmlNode tablixCols = objXmlDocument.SelectSingleNode("/df:Report/df:Body/df:ReportItems/df:Tablix/df:TablixBody/df:TablixColumns", mgr);
XmlNode dataField = objXmlDocument.SelectSingleNode("/df:Report/df:DataSets/df:DataSet/df:Fields/df:Field", mgr);
XmlNode tablixMember = objXmlDocument.SelectSingleNode("/df:Report/df:Body/df:ReportItems/df:Tablix/df:TablixColumnHierarchy/df:TablixMembers/df:TablixMember", mgr);
XmlNode rowHeaders = objXmlDocument.SelectSingleNode("/df:Report/df:Body/df:ReportItems/df:Tablix/df:TablixBody/df:TablixRows/df:TablixRow", mgr);
XmlNode rowValues = rowHeaders.NextSibling.SelectSingleNode("./df:TablixCells", mgr);
rowHeaders = rowHeaders.SelectSingleNode("./df:TablixCells", mgr);
XmlNode sampleCol = tablixCols.SelectSingleNode("./df:TablixColumn", mgr);
XmlNode sampleHeader = rowHeaders.SelectSingleNode("./df:TablixCell", mgr);
XmlNode sampleValue = rowValues.SelectSingleNode("./df:TablixCell", mgr);
//Iterate through the column definitions and add nodes to the xml to support the columns
foreach (KeyValuePair<string,string> column in Columns)
{
//clone the sample nodes into new nodes
XmlNode newDataField = dataField.CloneNode(true);
newDataField.SelectSingleNode("./df:DataField", mgr).InnerText = column.Key;
newDataField.Attributes["Name"].Value = column.Key;
dataField.ParentNode.AppendChild(newDataField);
XmlNode newCol = sampleCol.CloneNode(true);
XmlNode newHeader = sampleHeader.CloneNode(true);
XmlNode newValue = sampleValue.CloneNode(true);
//update the new nodes with the column data
newHeader.SelectSingleNode("./df:CellContents/df:Textbox", mgr).Attributes["Name"].Value = "Header" + column.Key;
newValue.SelectSingleNode("./df:CellContents/df:Textbox", mgr).Attributes["Name"].Value = "Value" + column.Key;
//because I use friendly column names - modify the report output to use the friendly name as the display value
newHeader.SelectSingleNode("./df:CellContents/df:Textbox/df:Paragraphs/df:Paragraph/df:TextRuns/df:TextRun/df:Value", mgr).InnerText = column.Value;
newValue.SelectSingleNode("./df:CellContents/df:Textbox/df:Paragraphs/df:Paragraph/df:TextRuns/df:TextRun/df:Value", mgr).InnerText = string.Format("=Fields!{0}.Value", column.Key);
//add the new nodes to the document
tablixCols.AppendChild(newCol);
rowHeaders.AppendChild(newHeader);
rowValues.AppendChild(newValue);
tablixMember.ParentNode.AppendChild(tablixMember.CloneNode(true));
}
//remove the nodes used for cloning
objXmlDocument.SelectSingleNode("/df:Report/df:DataSets/df:DataSet/df:Fields", mgr).RemoveChild(dataField);
objXmlDocument.SelectSingleNode("/df:Report/df:Body/df:ReportItems/df:Tablix/df:TablixColumnHierarchy/df:TablixMembers", mgr).RemoveChild(tablixMember);
tablixCols.RemoveChild(sampleCol);
rowHeaders.RemoveChild(sampleHeader);
rowValues.RemoveChild(sampleValue);
//return the completed report definition
return objXmlDocument;
}
Many thanks to this article for getting me pointed in the right direction.
来源:https://stackoverflow.com/questions/29778769/cant-display-datatable-in-reportviewer-aspx-net