Showing posts with label Reporting Services. Show all posts
Showing posts with label Reporting Services. Show all posts

Thursday, July 7, 2011

Report Rendering Fails in Local Mode

A desktop program developed using .NET 3.5 framework and was using RDLC report in local mode and a ReportViewer control  was throwing what might seem a weird exception the exception looked something like the below

The definition of the report 'Main Report' is invalid. 
Microsoft.ReportingServices.ReportProcessing.ReportProcessingException: 
An unexpected error occurred in Report Processing. 
System.UnauthorizedAccessException: Access to the path 'C:\Documents and Settings\\Local Settings\Temp\expression_host_05cd5e1af4624646bc2dd846b60460f0.dll' is denied.

What is this expression_host_....dll file and Why is The Report Rendering Engine Trying to Access It?

When you write an expression in your report definition (any expression even something that looks like =Fields!FirstName.Value + " " + Fields!LastName.Value) or when you use Custom Code, the report rendering engine does two things; it seems to me. One the rendering engine extracts all custom expressions and custom code and, based on these custom expressions and code, it dynamically creates VB.NET classes which are then compiled on the fly to a .NET assembly. These files and the resulting assembly are placed in the %TEMP% directory; they are deleted once they are no longer needed by the current instance of the running report. The second thing the rendering engine does is referencing the dynamically created assembly (which is named expression_host_.dll) when rending your report and making the necessary calls to this assembly as dictated by your report definition. 

How is The Above Explanation Related to The Error In Question?


Some anti-malware/anti-virus software do not like programs creating dynamic DLLs, it seems. In my case I verified that the dynamically created assembly was indeed blocked by the anti-malware program so that the ReportViewer and the hosting application could not use it and the even rendering engine program could not delete the expression_host dll file; which it normally does once the report is done rendering.

As soon as stopped the anti-malware program or just told to trust certain pattern of files, the report rendered perfectly without a problem.

Finally, it's worth mentioning that this problem was sporadic and had no predictable pattern. That is, one out 20 trials  the report would render fine but most of the time it fails. Something which leads to believe that there was a "race" between the report rendering engine and its application and the anti-malware program. Sometimes the anti-malware program would miss the host_expression dll and the report would render fine but most of the time the dll files is blocked.

The Bottom Line:


If you're running a program that use a RLDC report in local mode and the report uses expressions and custom code, rendering the report could fail due to an anti-virus/ anti-malware program blocking the custom assembly created to host your expressions and/or custom code.


Tuesday, May 31, 2011

Renaming Excel Sheets Exported from Reporting Services 2005 via an Excel Add-In


Intro

When a Reporting Services 2005 report with multiple pages is exported to excel to creates an excel document with multiple sheets where the first sheet is an index sheet or document map and the remaining sheets are report pages. Reporting Services 2005 gives these sheets generic names Sheetxy. Unlike the situation with Reporting Services 2008 [R2] there's no straight forward way of telling Reporting Services 2005 how name the report pages. One way to rename this sheets in excel to match the entries in the document map (which are more meaningful than sheetXY) is through an excel add-in. Below is an Excel add-in written in VB to rename sheets based on the document map. The code is tested on Windows XP/7 with Office 2003 Professional/Basic. If you decide to use the code below, make sure it has no unwanted side-effects in your environment and make sure you test it.

The basic idea of the code is:


  • Each document map entry points to a named range (which is stored in the built-in array Names
  • Using the range name we can find out the sheet that's being pointed to by an entry in the document map
  • Once the sheet is found, its name is changed to that of the hyperlink's DisplayText property of the pointing document map entry.

Steps to Create Excel Add-In using VBA

  1. Open a new excel workbook and go to the Visual Basic Editor
  2. In the left pane, right-click on VBAProjects (Book1) and Insert -> module. This module will have the code that will rename the sheets. The code in this module will be called when the custom menu item is clicked.
  3. This code does the renaming (you can paste in the newly created module
    Private Const INVALID_CHARS As String = "[]*/\?:"
    Private Const SHEET_NAME_MAX_LENGTH As Integer = 31
    Private Const DOCMAP_SHEET_NAME As String = "document map"
    Private Const DOCMAP_SHEET_NEW_NAME As String = "__INDEX__"
    Public Sub RunRenameSheetsCode()
        If Not Application.ActiveWorkbook Is Nothing Then
            Call RenameSheets(Application.ActiveWorkbook)
        End If
    End Sub
    Public Sub RenameSheets(ByRef wb As Workbook)
        On Error GoTo Error
        Dim sh As Worksheet
        Dim row As Integer
        Dim cell As Range
        Dim newName As String
        row = 1
        If Not IsFirstSheetDocMap(wb) Then
            Exit Sub
        End If
        Do While True
            Set cell = wb.Sheets(1).Range("$A$" & CStr(row))
            If Len(cell.Value) <= 0 Then
                Exit Do
            End If
            If cell.Hyperlinks.Count > 0 Then
                Set sh = GetSheetByRangeName(wb, cell.Hyperlinks(1).SubAddress)
                If Not sh Is Nothing Then
                    newName = CleanSheetName(cell.Value)
                    sh.name = newName
                End If
            End If
            row = row + 1
        Loop
        wb.Worksheets(1).name = DOCMAP_SHEET_NEW_NAME
        Exit Sub
    Error:
         MsgBox ("An error occurred while trying to rename sheets")
    End Sub
    Public Function CleanSheetName(ByVal name As String)
        Dim newName As String
        newName = name
        For i = 1 To Len(INVALID_CHARS)
           newName = Replace(newName, Mid(INVALID_CHARS, i, 1), "")
        Next
        newName = Left(newName, SHEET_NAME_MAX_LENGTH)
        CleanSheetName = newName
    End Function
    Public Function GetSheetByRangeName(ByRef wb As Workbook, rangeName As String) As Worksheet
        Dim i As Integer
        Dim sheetName As String
        Dim charIndex As Integer
        For i = 1 To wb.Names.Count
        If wb.Names(i).name = rangeName Then
            sheetName = wb.Names(i).RefersTo
            charIndex = InStr(1, sheetName, "!")
            If charIndex > 0 Then
                sheetName = Left(sheetName, charIndex - 1)
                sheetName = Right(sheetName, Len(sheetName) - 1)
                Exit For
            End If
        End If
        Next
        charIndex = InStr(1, sheetName, "'")
        If charIndex > 0 Then
            sheetName = Right(Left(sheetName, Len(sheetName) - 1), Len(sheetName) - 2)
        End If
        If Len(sheetName) > 0 Then
            Set GetSheetByRangeName = wb.Sheets(sheetName)
        End If
    End Function
    Public Function IsFirstSheetDocMap(ByRef wb As Workbook) As Boolean
        If wb.Worksheets.Count > 0 Then
            If LCase(wb.Worksheets(1).name) = DOCMAP_SHEET_NAME Then
                IsFirstSheetDocMap = True
                Exit Function
            End If
        End If
        IsFirstSheetDocMap = False
    End Function
    
    
      
  4. Go back to the left pane and expand VBAProjects and also expand Microsoft Excel Objects and double click the ThisWorkBook node . The ThisWorkBook will host the code responsible for creating the custom menu and hooking the OnAction event to the code responsible for renaming the sheets.
    Private Const MENU_CAPTION As String = "&Custom Menu"
    Private Const MENU_ITEM_CAPTION As String = "&Rename Sheets"
    Private Sub AddMenus()
    'ResetMenuBar
     On Error Resume Next
     Me.Application.CommandBars("Worksheet Menu Bar").Controls(MENU_CAPTION).Delete
     Dim mainMenu As CommandBar
     Dim customMenu As CommandBarControl
     Dim customMenuItem As CommandBarButton
     Dim helpMenuIndex As Integer
     Set mainMenu = Me.Application.CommandBars("Worksheet Menu Bar")
     helpMenuIndex = mainMenu.Controls("Help").Index
     Set customMenu = mainMenu.Controls.Add(Type:=msoControlPopup, Before:=helpMenuIndex)
     customMenu.Caption = MENU_CAPTION
     Set customMenuItem = customMenu.Controls.Add(Type:=msoControlButton)
     customMenuItem.Caption = MENU_ITEM_CAPTION
     customMenuItem.OnAction = "RunRenameSheetsCode"
    End Sub
    Private Sub ResetMenuBar()
        Application.CommandBars("Worksheet Menu Bar").Reset
    End Sub
    Private Sub Workbook_Open()
        Call AddMenus
    End Sub
    
  5. Save the current workbook as an excel add-in. From File menu select save as and in the save-as dialog in the "Save as type" list find and select Microsoft Office Excel Add-in (*.xla).
    By default excel saves new addins in the add-ins folder designated for the current user which is %USERPROFILE%\Application Data\Microsoft\AddIns. You can also place the add-in in C:\Program Files\Microsoft Office\OFFICE11\XLSTART causes excel to load your add-in when excel is started regardless of the user who started excel.
  6. Every time you start excel now you'll see your custom menu. If you the first sheet is name "document map" and the user clicks on the menu item which we created to rename sheets, the add-in will attempt renaming sheets based on what's in the document map.

Wednesday, May 4, 2011

Reporting Services Exception: Execution cannot be found

The rsExceutionNotFound Problem:

On an instance of SQL Server Reporting Services 2005 I noticed for a good while that the service is throwing rsExecutionNotFound exception and logging those as warnings to Windows application event log. A typical warning may look like


Event Type: Warning
Event Source: ASP.NET 2.0.50727.0
Event Category: Web Event
Event ID: 1309
Date:
Time:
User:
Computer: SERVERNAME
Description:
Event code: 3005
Event message: An unhandled exception has occurred.
Event time:
Event time
Event ID: 5be391324153445f9e5e3ace93334c55
Event sequence: 38
Event occurrence: 1
Event detail code: 0

Application information:
    Application domain: /LM/W3SVC/1/Root/Reports-1-129490022321612580
    Trust level: RosettaMgr
    Application Virtual Path: /Reports
    Application Path: ...\Reporting Services\ReportManager\
    Machine name: SERVERNAME

Process information:
    Process ID:
    Process name: w3wp.exe
    Account name:

Exception information:
    Exception type: ReportServerException
    Exception message: Execution '<SESSION ID>' cannot be found (rsExecutionNotFound)

Request information:
    Request URL: http://SERVERNAME/Reports/Reserved.ReportViewerWebControl.axd?ReportSession=SessionID really long URL
    Request path: %21 {I guess these %numbers are supposed to substituted }
    User host address: %22
    User: %23
    Is authenticated: %24
    Authentication Type: %25
    Thread account name: %26

After some searching I found this blog entry http://blogs.msdn.com/b/jgalla/archive/2006/10/11/session-timeout-during-execution.aspx. But the problem described in the mentioned blog is different and the suggested solution does not work in the cases I am describing here. The problem here is not that the report execution takes too long that the reporting services session times out.

The Cause of the Exception:

After some tracking and playing around I can say with a very high degree of certainty that the cause of the problem is  Internet Explorer [8]'s way of saving URLs when a user bookmarks (adds to favorites) a report's reporting services' URL.


I noticed that the exception (rsExecutionNotFound) is always thrown and the warning is logged in Windows when the user requests a report via a click on a favorite's URL. Of course the exception won't be thrown if the user visits the bookmarked report before its Reporting Services session expires. But typically with bookmarks they're visited days after the sessions are deleted and will for sure causes an rsExecutionException to be thrown.

If you look at an Internet Explorer 8 favorites shortcut file (*.url) you'll see some addition information saved along side the base URL of the bookmarked page. I believe this additional information is saved because when the report is rendered in the browser the page typically contains many iframes so the IE save their URLs as well. And in this case the additional information includes the Reporting Services SessionID. So when the user clicks on the favorite report, IE does try to make requests with old Reporting Services SessionIDs.

Here's a snippet of *.url file


[DEFAULT]
BASEURL=http://SERVERNAME/Reports/Pages/Report.aspx?ItemPath=/some/report/here
[DOC_ctl140TouchSession0]
BASEURL=http://SERVERNAME/Reports/Reserved.ReportViewerWebControl.axd?ReportSession=SessionID really long URL {the same shown in event log}
ORIGURL=javascript:''
[DOC_ctl140_ctl00_ctl03_ctl01]
....


A Solution:


Luckily if the cause of the exception is what I described here the user does not see on their screens and the exception itself is nothing serious although it might be bothersome to see those warnings in the events log. So in reality it does not affect the user's experience.

If the cause of the exception is that fact that the report takes tremendously long time to run to the point its session on Reporting Services times out, then the above mentioned link offers an easy solution.

Wednesday, December 15, 2010

Add Page Breaks Conditionally

Sometimes after developing a report you may have a requirement to conditionally add a page break after or before each logical portion of the report. Some users may need to see all of the logical units in consecutive pages and some prefer to see each logical unit in its own page.

Here is a simple trick to conditionally add the page break. Two simple steps:


  • Put your data control(s) that you'd like to conditionally paginate within a list control.










  • Edit the Detail Group of the list control and set the Group Expression to a condition a statement that uses a parameter or some other flag to determine whether to group the list based on a variable or a constant. Here is an example: =Iif(Parameters!PageBreak.Value, Fields!ProductID.Value,"")
  • Make sure you check the "Add page break at end" in the Grouping and Sorting Properties dialog box of the enclosing list dialog; see screen shot below.



Hope this helps.

Monday, November 1, 2010

Consuming Reporting Services 2005 Web Service

Reporting Service 2005 Web Service is a very useful API that lets do many tasks programmatically  such as rendering reports, uploading them, creating folders, setting properties etc.

In here I will try to show a brief tutorial on how to write a simple client that uses the SSRS 2005 Web Services. I assume you're using VS2008. The main point of this post is that when working Reporting Services 2005, you have to add a Web Service reference and NOT a Service reference. Doing so will alleviate you from having to apply the work-around shown here http://alsaydi.blogspot.com/2009/01/using-report-services-2005-web-service.html 

-- Create your project and start adding the references to the Reporting Services Web Services (ReportExecution and ReportService)
  1. Right click on your project References and select Add Service Reference the following dialg box show come up:


Now hold on. Click on Advanced because what we're trying to add is a Web Service Reference and not a Service Reference. Clicking on advanced gets you the below


Now for compatibility's sake, you have to click on "Add Web Reference ..." button  to get the below dialog box


and in the above dialog, you can now enter the URL for asmx file normally something like

http://ReportingServices_ServerName_Here/ReportServer/ReportService2005.asmx


The service you just added in the above steps will enable your project to call the Web Service to do things such as managing folder, listing reports, setting properties etc. To be able to render reports you need to add the Reporting Service Execution reference which you can do by repeating the above steps but use the URL below instead
 http://ReportingServices_ServerName_Here/ReportServer/ReportExecution2005.asmx

Now your project should be able to use the generated proxy classes to make calls against the reporting services Web Service API. There are some classes that have the same name in ReportingServices namespace and in the ReportingExecution for this reason you'll have to fully qualify your classes when you instantiate; otherwise you get "class name is ambiguous reference between ReportService2005 and ReportExecution2005". Sample code below will illustrate


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using ReportingServicesClient.ReportExecution2005;
using ReportingServicesClient.ReportService2005 ;
using System.Web.Services.Protocols;
namespace ReportingServicesClient
{
    
    public class RS2005Client
    {
        private ReportingService2005 rs = null;
        private ReportExecutionService rsExec = null;
        private readonly string htmlformat = "HTML4.0";
        private readonly string excelformat = "EXCEL";
        private readonly string xmlformat = "XML";
        public RS2005Client()
        {
            rs = new ReportingService2005();
            rsExec = new ReportExecutionService();
            rs.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
            rsExec.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
            
        }
        public byte[] RenderReport(string path,ReportFormat frmt)
        {
            string format = null;
            switch (frmt)
            {
                case ReportFormat.HTML:
                    format = htmlformat;
                    break;
                case ReportFormat.Excel:
                    format = excelformat;
                    break;
                case ReportFormat.XML :
                    format = xmlformat;
                    break;
                default:
                    throw new Exception("RenderReport: invalid format supplied");
                    break;
            }
            return RenderReport(path, format);
        }
        private byte[] RenderReport(string path,string format)
        {
            byte[] result = null;
            string reportPath = path;// "/Cosmetic Tracking/Events";
            string historyID = null;
            string devInfo = @"False";
            #region parameters
            // Prepare report parameter.
            /*ParameterValue[] parameters = new ParameterValue[3];
            parameters[0] = new ParameterValue();
            parameters[0].Name = "EmpID";
            parameters[0].Value = "288";
            parameters[1] = new ParameterValue();
            parameters[1].Name = "ReportMonth";
            parameters[1].Value = "6"; // June
            parameters[2] = new ParameterValue();
            parameters[2].Name = "ReportYear";
            parameters[2].Value = "2004";*/
            #endregion                        
            string encoding;
            string mimeType;
            string extension;
            ReportExecution2005.Warning[] warnings = null;
            ReportExecution2005.ParameterValue[] reportHistoryParameters = new ReportExecution2005.ParameterValue[1];
            string[] streamIDs = null;
            ExecutionInfo execInfo = new ExecutionInfo();


            ExecutionHeader execHeader = new ExecutionHeader();
            rsExec.ExecutionHeaderValue = execHeader;
            execInfo = rsExec.LoadReport(reportPath, historyID);

            //rs.SetExecutionParameters(parameters, "en-us"); 
            try
            {
                //rsExec.SetExecutionParameters(reportHistoryParameters,"US-en");                
                result = rsExec.Render(format, devInfo, out extension, out encoding, out mimeType, out warnings, out streamIDs);
                            
            }
            catch (SoapException e)
            {
                throw e;                
            }
            return result;
        }
    }

    public enum ReportFormat
    {
        HTML
        ,Excel
        ,XML
    }
}

Tuesday, March 10, 2009

Report Builder 2.0 & Reporting Services 2008

We're still on reporting services 2005 but I've playing with Report Builder 2.0 in which you can see some of the features of Reporting Services 2008 such as the long-awaited control tablix. Very Nice! you can download it from here [http://www.microsoft.com/downloads/details.aspx?familyid=9f783224-9871-4eea-b1d5-f3140a253db6&displaylang=en] The builder looks really cool and the look-and-feel of Office 2007 and many more improvements of the VS Designer for RS 2005. 

Of of most annoying bugs in 2005 was that when you make a change to a query in the report DataSet+ and save the report using CTRL+S (or click save icon), the DataTime parameters were converted to Strings. But so far I haven't seen this happening in RB 2008.

One way I worked around the bug I mentioned (DateTime converted to Strings) is by building the query after making the changes and before CTRL+S  ( or saving).

Other welcomed additions to Reporting Services 2008 are the better charting capabilities and the Gauge controls.

Monday, January 26, 2009

Using Report Services 2005 Web Service

EDIT: Your problem may be easily solved if you read this first http://alsaydi.blogspot.com/2010/11/consuming-reporting-services-2005-web.html 

Today I wrote a very simple application that connects to Reporting Services 2005 web service.
The Web Services URL is:

http://[servername]/ReportServer/ReportService2005.asmx

Many sources give the following lines of code as a sample (assuming you have all the proper using statements)

ReportingService2005 rs = new ReportingService2005();
rs.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;
CatalogItem [] items = rs.ListChildren("/Sales Reports", true);

I first tried this with Visual Studio 2008 and noticed that there is no ReportingService2005 class. The closes class ReportingService2005SoapClient. So I switched to Visual Studio 2005 and the VS IntelliSense showed ReportingService2005 and my code worked.

After a some time of playing around I found a way to get the code to work in VS2008.

The app.config file of t he VS2008 a couple of changes needs to be made:

the binding tage , the property allowCookies has to be set true (I think not sure)


<security mode="TransportCredentialOnly">                                          
<transport clientCredentialType="Windows" proxyCredentialType="Windows"
realm="" />
</security>



This simple will use Windows integrated authentication.
One more line of code is need to allow impersonation:
rs.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;

assuming rs is an instance of ReportingService2005SoapClient.

So a sample code looks something like the following:

ReportingService2005SoapClient rs = new ReportingService2005SoapClient();
rs.ClientCredentials.Windows.AllowedImpersonationLevel = System.Security.Principal.TokenImpersonationLevel.Impersonation;
CatalogItem[] items;
rs.ListChildren("/", true, out items);
dataGridView1.DataSource = items;

Cool things can be done through RS2005 Web Service!