我将尽可能多地提供有关该项目的信息,然后是相关的源代码,然后是有关我已经尝试过的信息(我将尝试在我拥有它们的地方包含我尝试过的代码片段,以防万一他们错了)。
我很确定我的问题与从报表服务器返回的数据的序列化/反序列化有关,但我承认我完全有可能错了。
我有两个独立的项目(在 Visual Studio 2013 中)。“客户端”项目是一个 WPF 应用程序,它试图使用 ReportViewer 显示 ServerReport 的内容。“服务”项目是一个 WCF 应用程序,它在调用 Microsoft 的 ReportServer 后尝试将报告的内容返回给客户端项目。该软件的先前版本具有直接向报表服务器发出请求的客户端软件。我所做的更改是将请求的参数发送到服务项目,该服务项目从我们的数据库中获取身份验证信息并调用报表服务器。目标是我们应用程序的客户端不应该知道或访问身份验证信息,而应该只知道数据。
我愿意接受任何能够实现这一目标的解决方案,即使它与我迄今为止所建立的完全不同。
应用程序根据当前用户的数据填充可用报告的列表。单击“查看”按钮后,应使用报告查看器显示报告的详细信息。
在视图按钮的单击事件中,报表服务器请求的参数在调用 RefreshReport() 之前填充。此代码未更改,不受新流程的影响。
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
}
}
服务请求 service.Report(ReportDTO) 位于服务请求的单独文件中
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();
}
}
}
该请求由云项目中的运营合同接收。
[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;
}
根据GotReportViewer,可以将 DataTable 设置为 ReportViewer.LocalReport 的数据源,因此我一直在尝试将此字节数组返回到我的客户端项目并将其转换为 DataTable 格式以显示在 ReportViewer 中。
虽然我无法查看从调用 ReportServer 返回的实际数据,但我知道我一直在测试的报告没有损坏,因为它们在该项目的旧版本中加载良好。此外,从调用 ServerReport.Render 返回的字节数组的大小略高于 98k 字节,因此我假设报告正从 ReportServer 正确返回到我的云项目。这就是为什么我相当肯定我的问题在于序列化/反序列化。
当控制权返回到客户端项目时,我得到的错误出现在reportData = (DataTable)formatter.Deserialize(stream);
.
抛出的错误是Binary stream '0' does not contain a valid BinaryHeader
.
我在 StackOverflow 上发现了很多关于此二进制标头错误的问题,但它们要么与我的具体情况无关,要么以假设这是一个数据问题而告终,我愿意尽可能接近积极声称这不是。
我发现的大多数关于从 wcf 应用程序向报表服务器发出请求的问题基本上都说这很困难,并且提供了一些替代方法,但是我找不到解决我遇到的问题或解决问题的方法我遇到的问题,或者避免让 WPF 应用程序访问身份验证信息的问题。
到目前为止,我已经尝试过:
- 拨打电话
rv.ServerReport.Render("Excel");
- 将 byte[] 直接返回到客户端项目,而不是作为 MemoryStream
- 可能还有其他一些关于转换为数据表的变化(这是漫长的一周,我不记得我精确尝试过的所有事情)
我一直无法找到将结果直接转换rv.ServerReport.Render
为 DataTable 的方式。
如果更多(或只是不同)信息会有所帮助,请告诉我。