I will provide as much information as I can about the project, followed by the relevant source code, followed by information regarding what I have tried already (I will try to include code snippets for what I tried where I have them, in case I did them wrong).
I am pretty sure my problem is related to the serialization/deserialization of the data coming back from the report server, but I will admit that it is entirely possible that I am wrong about that.
I have two separate projects (in Visual Studio 2013). The 'client' project is a WPF application which is attempting to display the contents of a ServerReport using ReportViewer. The 'service' project is a WCF application which is attempting to return the contents of the report to the client project after making the call to Microsoft's ReportServer. The previous version of this software has the client software making the request directly to the report server. The changes I have made are to send the parameters for the request to the service project, which gets the authentication information from our database and makes the call to the report server. The goal is that the client side of our application should not have knowledge of or access to the authentication information, but only of the data.
I am open to any solution to this which accomplishes this goal, even if it is completely different from what I have set up so far.
The application populates a list of available reports from the current user's data. Upon clicking of the 'View' button, the report's details should be displayed using report viewer.
Inside the click event for the view button, the parameters for the report server request are populated prior to the call to RefreshReport(). This code has not been altered and is not affected by the new process.
public partial class CurrentReport : (Our base page object)
{
public ReportViewer _report;
private string _reportPath;
public CurrentReport()
{
try
{
InitializeComponent();
_report = new ReportViewer();
BuildReportViewer();
}
catch (Exception ex)
{
// Log Exception
}
}
public void BuildReportViewer()
{
try
{
// wfh is an WindowsFormsHost property which
// CurrentReport inherits from its parent
if (wfh.Child == null)
{
_report = new ReportViewer();
wfh.Child = _report;
}
catch (Exception ex)
{
// Log Exception
}
}
public bool RefreshReport(string reportPath, List<ReportParameter> parameters = null)
{
try
{
if ((parameters != null) && (!String.IsNullOrEmpty(reportPath)))
{
// Parameters passed to this method are of the type
// Microsoft.Reporting.WinForms.ReportParameter
// Parameters the cloud service is expecting are of the type
// Microsoft.Reporting.WebForms.ReportParameter
// The parameters accepted by the method are mapped to a list
// of parameters of web forms type before being added to
// the data transfer object
List<CloudService.Service.ReportParameter> cloudParameters = new List<CloudService.RTRService.ReportParameter>();
if (parameters.Count > 0)
{
foreach (ReportParameter rp in parameters)
{
List<object> cloudValues = new List<object>();
foreach (string s in rp.Values)
cloudValues.Add(s);
cloudParameters.Add(new CloudService.Service.ReportParameter { m_name = rp.Name, m_value = cloudValues, m_visible = rp.Visible });
}
}
CloudService.Service.ReportDTO rdto = new CloudService.Service.ReportDTO();
rdto.reportParameters = cloudParameters;
rdto.reportPath = reportPath;
rdto.reportProcessingMode = CloudService.Service.ProcessingMode.Remote;
ServiceRequests.ServiceRequests.service = new ServiceRequests.ServiceRequests(MyApp.Authentication);
MemoryStream stream = service.Report(rdto);
DataTable reportData = new DataTable { TableName = "Report" };
BinaryFormatter formatter = new BinaryFormatter();
reportData = (DataTable)formatter.Deserialize(stream);
_report.LocalReport.DataSources.Add(new ReportDataSource("Report", reportData));
_reportPath = reportPath;
_report.RefreshReport();
}
// The code making the call to this method is checking for an error
return false;
}
catch (Exception ex)
{
// Log Exception
}
}
The service request service.Report(ReportDTO) is in a separate file for service requests
public MemoryStream Report(ReportDTO rdto)
{
ServiceClient service = null;
try
{
service = new ServiceClient();
service.InnerChannel.OperationTimeout = new TimeSpan(0,5,0);
service.Open();
ReportDTORequest request = new ReportDTORequest();
request.Authentication = _authentication; // global property
request.Entities = new List<ReportDTO>();
request.Entities.Add(rdto);
return service.Report(request).Entities.FirstOrDefault();
}
catch (Exception ex)
{
throw ex;
}
finally
{
if (service != null)
{
service.Close();
}
}
}
The request is received by an operation contract in the cloud project.
[WebInvoke(Method = "POST")]
[OperationContract]
public Response<MemoryStream> Report(Request<ReportDTO> request)
{
Response<MemoryStream> response = new Response<MemoryStream>();
response.Status = ResponseStatus.FAILED;
try
{
if ((request != null) && (request.Entities != null))
{
if (request.Authentication != null)
{
// I know this part is unusual but it is working around a complication between an old custom response object and a new custom response object to replace the old one, which is still being used elsewhere
KeyValuePair<ResponseStatus, string> kvp = request.Authentication.Authenticate(_globalAuthenticationToken);
response.Status = kvp.Key;
response.Messages.Add(kvp.Value);
if (response.Status == ResponseStatus.SUCCESS)
{
ReportDTO rdto = request.Entities.FirstOrDefault();
if ((rdto != null) && (!String.IsNullOrEmpty(rdto.reportPath)))
{
// Get settings from database and populate in string variables username, password, domain, and uri
Microsoft.Reporting.WebForms.ReportViewer rv = new Microsoft.Reporting.WebForms.ReportViewer();
rv.ServerReport.ReportPath = rdto.reportPath;
rv.ServerReport.ReportServerUrl = new Uri(uri);
rv.ServerReport.ReportServerCredentials = new CustomReportCredentials(username, password, domain);
rv.ServerReport.Refresh();
if ((rdto.reportParameters != null) && (rdto.reportParameters.Count > 0))
{
rv.ServerReport.SetParameters(rdto.reportParameters);
}
string mimeType;
string encoding;
string extension;
string[] streamIDs;
Microsoft.Reporting.WebForms.Warning[] warnings;
byte[] bytes = rv.ServerReport.Render("Excel", null, out mimeType, out encoding, out extension, out streamIDs, out warnings);
if ((bytes != null) && (bytes.Count() > 0))
{
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
formatter.Serialize(stream, bytes);
response.Entites.Add(stream);
stream.Close();
response.Status = ResponseStatus.SUCCESS;
}
else
{
response.Messages.Add("Unable to render server report");
foreach (Microsoft.Reporting.WebForms.Warning warning in warnings)
{
response.Messages.Add(warning.ToString());
}
response.Status = ResponseStatus.FAILED;
}
}
else
{
response.Messages.Add("Invalid request data");
response.Status = ResponseStatus.FAILED;
}
}
}
else
{
response.Messages.Add("Unable to authenticate user request");
response.Status = ResponseStatus.FAILED;
}
}
else
{
response.Messages.Add("Invalid request object");
response.Status = ResponseStatus.FAILED;
}
}
catch (Exception ex)
{
// Log Exception
}
return response;
}
According to GotReportViewer, a DataTable can be set as a data source for ReportViewer.LocalReport so I have been trying to return this byte array to my client project and get it into a DataTable format to be displayed in the ReportViewer.
While I have not been able to view the actual data coming back from the call to the ReportServer, I know that the reports I have been testing with are not corrupted, as they were being loaded fine in the old version of this project. Additionally, the byte array coming back from the call to ServerReport.Render is a little over 98k bytes in size, so I assume that the report is being correctly returned from the ReportServer to my cloud project. This is why I am fairly certain my problem is with the serialization/deserialization.
The error I am getting comes when control returns to the client project, at the line reportData = (DataTable)formatter.Deserialize(stream);
.
The error thrown is Binary stream '0' does not contain a valid BinaryHeader
.
I have found many questions on StackOverflow regarding this binary header error, but they have all been either not related to my specific situation, or ended in the assumption that it is a data issue, which I am as close to positive as I am willing to claim to be that this is not.
Most questions I have found regarding making the request to the report server from a wcf application basically said it is difficult, and there were some alternative methods provided, but there were none that I could find which addressed the issue I am having, or which approached the issue the way I have, or which avoided giving the WPF application access to the authentication information.
So far I have tried:
- Making the call as just
rv.ServerReport.Render("Excel");
- Returning the byte[] to the client project directly instead of as a MemoryStream
- Probably a few other variations on casting to a data table (it's been a long week and I don't remember all of the things I've tried precisely)
I have been unable to find a manner in which to directly convert the results of rv.ServerReport.Render
into a DataTable.
Please let me know if more (or just different) information would be helpful.