我将尽可能多地提供有关该项目的信息,然后提供相关的源代码,然后提供有关我已经尝试过的信息(我将尝试在我所拥有的内容中包含代码片段,我做错了。)
我很确定我的问题与从报表服务器返回的数据的序列化/反序列化有关,但我承认我完全有可能错了。 < / p>
我有两个独立的项目(在Visual Studio 2013中)。 &#39;客户&#39; project是一个WPF应用程序,它试图使用ReportViewer显示ServerReport的内容。服务&#39; project是一个WCF应用程序,它试图在调用Microsoft的ReportServer之后将报表的内容返回给客户端项目。该软件的先前版本具有客户端软件直接向报表服务器发出请求。我所做的更改是将请求的参数发送到服务项目,该服务项目从我们的数据库获取身份验证信息并调用报告服务器。目标是我们的应用程序的客户端不应该知道或访问身份验证信息,而只能了解数据。
我愿意接受任何能够实现这一目标的解决方案,即使它与我目前设定的完全不同。
应用程序从当前用户的数据中填充可用报告列表。点击查看&#39;按钮,报告的详细信息应使用报告查看器显示。
在视图按钮的单击事件中,在调用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");
我一直无法找到将rv.ServerReport.Render
的结果直接转换为DataTable的方式。
如果有更多(或只是不同的)信息有用,请告诉我。
答案 0 :(得分:0)
我认为Excel文件正在生成错误数据或垃圾数据。你会分享Excel内容吗
答案 1 :(得分:0)
ServerReport.Render生成一个表示excel报告的byte []数组。然后,您使用BinaryFormatter将字节数组转换为格式化的字节数组(我认为这是令人困惑的地方)。
BinaryFormaters用于从一种类型转换为字节并再次返回。在这种情况下这是有道理的,但它只能反序列化它自己也序列化的对象。就这样:
BinaryFormatter formatter = new BinaryFormatter();
MemoryStream stream = new MemoryStream();
formatter.Serialize(stream, bytes);
response.Entites.Add(stream);
stream.Close();
可以这样写,因此我们只将字节传输到客户端:
response.Entities.Add(bytes)
从而为您提供表示客户端上呈现的excel报告的字节数组。下一个技巧是弄清楚客户端如何理解这种格式并将其转换为可用数据。
我目前的赌注是,在客户端,您可以致电:
var report = new LocalReport();
using (var memStream = new MemoryStream(service.Report(rdto)))
report.LoadReportDefinition(memStream);
}
希望报告将是一份有效的报告。