使用Reporting Services(SSRS)作为ASP.NET Core站点中的引用

时间:2017-05-18 00:50:31

标签: .net wcf reporting-services asp.net-core

我努力为此寻找解决方案好几天,所以我想分享我的情况。我将现有的ASP.NET MVC应用程序转换为ASP.NET Core MVC。然而,使用ASP.NET Core的最大变化是System.Web命名空间是不行的。但是,通常,SQL Server Reporting Services(SSRS)通常作为WebReference添加到项目中,该项目基于 - 您猜对了,System.Web

所以我需要找到另一种能够命中SSRS端点来执行报告的方法。对于我的场景,我主要想要PDF(虽然调用SSRS' Render方法允许您选择导出格式)。

这个问题的解决方案提出了它自己的问题,最明显的错误是:

  

Microsoft.ReportingServices.Diagnostics.Utilities.MissingSessionIdException:缺少会话标识符。此操作需要会话标识符。

所以我最后回答的两个问题可能对其他人有价值,如何在没有System.Web的情况下使用SSRS,以及如何解决有关"缺少会话标识符&#34的错误;

3 个答案:

答案 0 :(得分:10)

我用来解决这个问题的第一步是Visual Studio的连接服务和WCF。此方法生成一些类似于WebReferences但基于System.DataModel而不是System.Web的类。我应该注意,如果您没有使用Visual Studio,可以使用SvcUtil.exe工具生成这些相同的类。

使用VS2017< 15.5时,您需要获取用于从Visual Studio Marketplace添加WCF服务引用的扩展名。对于VS2017> = 15.5,它现在是built in。在此之后,当您右键单击已连接服务时,您应该有一个新条目,有用地称为添加已连接服务... 。下一个屏幕上的条目之一应该是 Microsoft WCF Web服务引用提供程序(在撰写本文时,扩展名为预览版)。 输入服务端点的URI,对我来说是http://[SERVERNAME-OR-IP]/ReportServer/ReportExecution2005.asmx?wsdl形式的URI,并将命名空间设置在底部。我保留了所有其他默认设置,然后点击完成。当我第一次进行此设置时,我不记得我在该领域中使用过的内容,但是当它完成时我希望它是[MyProjectNamespace].ReportingServices

那会给你你的班级。

enter image description here

同样,也可以使用 SvcUtil.exe 来完成。

除了我的新参考之外,我使用的代码/类如下。我尽力使代码尽可能全面。我的实际实现更加重构,但它只是增加了解这一切是如何工作所需的复杂性。所以我试图让这段代码尽可能线性化。如果我的代码有错误,请随时告诉我: - )

public async Task<byte[]> RenderReport(string report, IDictionary<string, object> parameters, string exportFormat = null)
{
    //My binding setup, since ASP.NET Core apps don't use a web.config file
    var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
    binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;
    binding.MaxReceivedMessageSize = 10485760; //I wanted a 10MB size limit on response to allow for larger PDFs

    //Create the execution service SOAP Client
    var rsExec = new ReportExecutionServiceSoapClient(binding, new EndpointAddress(reportingServicesUrl));

    //Setup access credentials. I use windows credentials, yours may differ
    var clientCredentials = new NetworkCredential(reportingServicesUserName, reportingServicesPassword, reportingServicesDomain);
    rsExec.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
    rsExec.ClientCredentials.Windows.ClientCredential = clientCredentials;

    //This handles the problem of "Missing session identifier"
    rsExec.Endpoint.Behaviors.Add(new ReportingServicesEndpointBehavior());

    //Load the report
    var taskLoadReport = await rsExec.LoadReportAsync(report, null);

    //Set the parameteres asked for by the report
    var reportParameters = taskLoadReport.Parameters.Where(x => parameters.ContainsKey(x.Name)).Select(x => new ParameterValue() { Name = x.Name, Value = parameters[x.Name].ToString() }).ToArray();
    await rsExec.SetExecutionParametersAsync(reportParameters, "en-us");

    //run the report
    const string deviceInfo = @"<DeviceInfo><Toolbar>False</Toolbar></DeviceInfo>";
    var response = await rsExec.RenderAsync(new RenderRequest(exportFormat ?? "PDF", deviceInfo));

    //spit out the result
    return response.Result;
}

大部分内容都是自我解释,但我想说出我添加的端点行为。请参阅,在加载报告详细信息并随后使用该信息使用我在参数... well参数中设置的值来设置报告的参数,然后呈现报告时,您需要设置会话标识符以连接调用是同一会话上下文的所有部分。它查找的会话标识符是一个名为ExecutionHeader的SOAP标头值,其值为&#39; ExecutionID&#39;。这是对我拨打LoadReportAsync的回复中提供的,但未自动转移到以后对API的所有调用。我已经尝试了多种方法来实现这一点,但由于固有的类试图将XML命名空间设置为我想要的以外的东西而遇到了问题。最终,EndpointBehavior是最不具侵入性的解决方案(也是唯一一个我工作的解决方案)。支持它的类看起来像这样。

using System.ServiceModel.Channels;
using System.ServiceModel.Description;
using System.ServiceModel.Dispatcher;

internal class ReportingServicesEndpointBehavior : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.ClientMessageInspectors.Add(new ReportingServicesExecutionInspector());
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }

    public void Validate(ServiceEndpoint endpoint) { }
}

internal class ReportingServicesExecutionInspector : IClientMessageInspector
{
    private MessageHeaders headers;

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        var index = reply.Headers.FindHeader("ExecutionHeader", "http://schemas.microsoft.com/sqlserver/2005/06/30/reporting/reportingservices");
        if (index >= 0 && headers == null)
        {
            headers = new MessageHeaders(MessageVersion.Soap11);
            headers.CopyHeaderFrom(reply, reply.Headers.FindHeader("ExecutionHeader", "http://schemas.microsoft.com/sqlserver/2005/06/30/reporting/reportingservices"));
        }
    }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        if(headers != null)
            request.Headers.CopyHeadersFrom(headers);

        return Guid.NewGuid(); //https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.iclientmessageinspector.beforesendrequest(v=vs.110).aspx#Anchor_0
    }
}

这里有两个班级;一个是EndpointBehavior,另一个是MessageInspector。 EndpointBehavior的唯一目的是连接MessageInspector。我无法找到额外的步骤。但MessageInspector所做的是,每次响应回来时,如果我们还没有从过去的响应中保存ExecutionHeader,我们会从响应中保存一个。随后,每次我们发送请求时,如果我们从过去的响应中保存了ExecutionHeader,我会将其附加到Headers以获取此新请求。通过这种方式,我确保命名空间和围绕此会话标识符的所有其他复杂性完全是服务提供它们开始的方式,因此我尽可能自信,因为它们将是有效的。

希望这可以帮助任何寻找解决方案的人。我在网上看到了很多关于这个话题的其他问题,但没有人得到我需要的答案/解释。

答案 1 :(得分:1)

您也可以使用加载报告响应来设置ExecutionID。

// Create execution header using load report response information.
var executionHeader = new ExecutionHeader
{
    ExecutionID = loadReportResponse.executionInfo.ExecutionID
};

答案 2 :(得分:0)

参考上述尼克的解决方案,我修复了运行中的“会话标识符丢失”的异常。

这里,我展示了调用Asp.net core 2.0 Web api来访问SSRS并在浏览器中返回pdf报告的完整解决方案。

    [HttpGet("getpdfreport")]
    public async Task<IActionResult> GetPDFReport()
    {
        string reportName = "YourReport";
        IDictionary<string, object> parameters = new Dictionary<string, object>();
        parameters.Add("companyId", "2");
        parameters.Add("customerId", "123");
        string languageCode = "en-us";

        byte[] reportContent = await this.RenderReport(reportName, parameters, languageCode, "PDF");

        Stream stream = new MemoryStream(reportContent);

        return new FileStreamResult(stream, "application/pdf");

    }

    /// <summary>
    /// </summary>
    /// <param name="reportName">
    ///  report name.
    /// </param>
    /// <param name="parameters">report's required parameters</param>
    /// <param name="exportFormat">value = "PDF" or "EXCEL". By default it is pdf.</param>
    /// <param name="languageCode">
    ///   value = 'en-us', 'fr-ca', 'es-us', 'zh-chs'. 
    /// </param>
    /// <returns></returns>
    private async Task<byte[]> RenderReport(string reportName, IDictionary<string, object> parameters,  string languageCode, string exportFormat)
    {
        //
        // SSRS report path. Note: Need to include parent folder directory and report name.
        // Such as value = "/[report folder]/[report name]".
        //
        string reportPath = string.Format("{0}{1}", ConfigSettings.ReportingServiceReportFolder, reportName);

        //
        // Binding setup, since ASP.NET Core apps don't use a web.config file
        //
        var binding = new BasicHttpBinding(BasicHttpSecurityMode.TransportCredentialOnly);
        binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Ntlm;
        binding.MaxReceivedMessageSize = this.ConfigSettings.ReportingServiceReportMaxSize; //It is 10MB size limit on response to allow for larger PDFs

        //Create the execution service SOAP Client
        ReportExecutionServiceSoapClient reportClient = new ReportExecutionServiceSoapClient(binding, new EndpointAddress(this.ConfigSettings.ReportingServiceUrl));

        //Setup access credentials. Here use windows credentials.
        var clientCredentials = new NetworkCredential(this.ConfigSettings.ReportingServiceUserAccount, this.ConfigSettings.ReportingServiceUserAccountPassword, this.ConfigSettings.ReportingServiceUserAccountDomain);
        reportClient.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
        reportClient.ClientCredentials.Windows.ClientCredential = clientCredentials;

        //This handles the problem of "Missing session identifier"
        reportClient.Endpoint.EndpointBehaviors.Add(new ReportingServiceEndPointBehavior());

        string historyID = null ;
        TrustedUserHeader trustedUserHeader = new TrustedUserHeader();
        ExecutionHeader execHeader = new ExecutionHeader();

        trustedUserHeader.UserName = clientCredentials.UserName;

        //
        // Load the report
        //
        var taskLoadReport = await reportClient.LoadReportAsync(trustedUserHeader, reportPath, historyID);
        // Fixed the exception of "session identifier is missing".
        execHeader.ExecutionID = taskLoadReport.executionInfo.ExecutionID;

         //
         //Set the parameteres asked for by the report
         //
         ParameterValue[] reportParameters = null;
        if (parameters != null && parameters.Count > 0)
        {
            reportParameters = taskLoadReport.executionInfo.Parameters.Where(x => parameters.ContainsKey(x.Name)).Select(x => new ParameterValue() { Name = x.Name, Value = parameters[x.Name].ToString() }).ToArray();
        }

        await reportClient.SetExecutionParametersAsync(execHeader, trustedUserHeader, reportParameters, languageCode);
        // run the report
        const string deviceInfo = @"<DeviceInfo><Toolbar>False</Toolbar></DeviceInfo>";

        var response = await reportClient.RenderAsync(new RenderRequest(execHeader, trustedUserHeader, exportFormat ?? "PDF", deviceInfo));

        //spit out the result
        return response.Result;
    }



/// <summary>
///  Work for reporting service.
/// </summary>
public class ReportingServiceEndPointBehavior : IEndpointBehavior
{
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) { }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
        clientRuntime.ClientMessageInspectors.Add(new ReportingServiceExecutionInspector());
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher) { }

    public void Validate(ServiceEndpoint endpoint) { }
}

public class ReportingServiceExecutionInspector : IClientMessageInspector
{
    private MessageHeaders headers;

    public void AfterReceiveReply(ref Message reply, object correlationState)
    {
        var index = reply.Headers.FindHeader("ExecutionHeader", "http://schemas.microsoft.com/sqlserver/2005/06/30/reporting/reportingservices");
        if (index >= 0 && headers == null)
        {
            headers = new MessageHeaders(MessageVersion.Soap11);
            headers.CopyHeaderFrom(reply, reply.Headers.FindHeader("ExecutionHeader", "http://schemas.microsoft.com/sqlserver/2005/06/30/reporting/reportingservices"));
        }
    }

    public object BeforeSendRequest(ref Message request, IClientChannel channel)
    {
        if (headers != null)
            request.Headers.CopyHeadersFrom(headers);

        return Guid.NewGuid(); //https://msdn.microsoft.com/en-us/library/system.servicemodel.dispatcher.iclientmessageinspector.beforesendrequest(v=vs.110).aspx#Anchor_0
    }
}