Quantcast
Channel: Brian Hartman's Report Viewer Blog
Viewing all 31 articles
Browse latest View live

Client Print Fails to Load After Microsoft Update 956391

$
0
0

A number of people have reported problems using the ActiveX print control in the report viewer after installing Microsoft Update 956391.  Specifically, users receive the error "Unable to load client print control" when clicking on the print icon in the report viewer toolbar.  I would like to explain what is happening, why we did it, and how to fix the problems that you or your customers are experiencing.

What’s a Kill Bit?

Let’s start with what Microsoft Update 956391 actually does.  This update includes a kill bit for the ActiveX client print control used by Reporting Services.  This print control is distributed with a number of components, including SQL Server 2000, SQL Server 2005, and Visual Studio 2008.  A kill bit is an instruction to Internet Explorer to prevent it from loading an ActiveX control.  In this case, Internet Explorer has been told not to load the Reporting Services client print control.  As a result, when attempting to do so, the report viewer fails and displays the "unable to load" error.

Setting the kill bit is typically done only in extreme cases, such as security vulnerabilities.  That’s what happened in this case.  The security vulnerability is not actually in the print control itself, but rather the GDI+ DLL that we ship with the print control.  The details of that vulnerability are described in MS08-052.  Even though we have patched the vulnerability, the kill bit is a defense in depth option to prevent malicious users from sending the Microsoft signed client print control and then exploiting the vulnerability.  The kill bit prevents the client print control from loading regardless of where it comes from.

Which Client Print Controls are Affected?

The client print controls that shipped with SQL Server 2000, SQL Server 2005, Visual Studio 2008, and the Report Viewer 2008 redistributable all use the same CLSID and are therefore all disabled by the update.  The table below shows the different CLSIDs that are used by the different versions of Reporting Services:

ProductClient (IE) ArchitectureCLSID
SQL 2000, SQL 2005, VS 2008 (old)X86{FA91DF8D-53AB-455D-AB20-F2F023E498D3}
SQL 2000, SQL 2005, VS 2008 (new)X86{41861299-EAB2-4DCC-986C-802AE12AC499}
SQL 2008X86{5554DCB0-700B-498D-9B58-4E40E5814405}
SQL 2008X64, IA64{60677965-AB8B-464f-9B04-4BA871A2F17F}

The previously shipped versions (using the first CLSID in the table) have been updated with a new version of GDI+ and a new CLSID to allow IE to load the control.  If you are using Report Server 2000, Report Server 2005, or the web control from VS 2008 in local mode, you will need to download an update so that your servers distribute the new print control.

Notice that SQL Server 2008 is not affected by this update and uses a different CLSID.  The print control that ships with SQL Server 2008 does not include the GDI+ DLL.  This DLL was included in earlier versions due to the requirement that they work on Windows 98 and Windows 2000.  But with that requirement dropped from SQL Server 2008, we no longer need to ship GDI+.  The new CLSID for SQL Server 2008 prevents Internet Explorer from replacing a working client print control on Windows 98 or 2000 with the newer version that does not work on that operating system.

Which Report Viewers are Affected?

Now that we’ve established which print controls are affected, we also need to talk about the report viewer control itself.  Even though the ReportViewer control in Visual Studio 2005 does not ship a print control, it still needs to be updated.  To understand why, let’s take a look at the HTML that is sent to the browser when you click on the print button:

<OBJECT ID="RSClientPrint" CLASSID="CLSID:FA91DF8D-53AB-455D-AB20-F2F023E498D3" CODEBASE="/ReportServer/Reserved.ReportViewerWebControl.axd?<report specific values>&amp;OpType=PrintCab#Version=2005,090,3042,00" VIEWASTEXT></OBJECT>

The object tag specifically references the disabled CLSID.  Even though an updated report server will return a new client print control, the ReportViewer web control still generates the old CLSID.  In hindsight, it would have been better for the Report Server to expose the CLSID for its client print control and for the ReportViewer ASP.Net control to use that when generating its HTML.  But those products have already shipped.  So we have also updated all versions of the ReportViewer (including the ReportViewer Web Part for use in SharePoint integrated mode) to write out the new CLSID.

We also support using the client print control directly in your application without a ReportViewer control.  If you do that, you will need to update it to write out the new CLSID as well.

What do you need to download?

To get to a working state, you will need to do the following:

  1. Update your report server to distribute the updated client print control.
  2. Update your ReportViewer redistributable so that it references the new CLSID.  The ReportViewer 2008 redistributable also includes an updated client print control for use in local mode.
  3. Update Visual Studio.  This is for the same reason as the redistributable.  The ReportViewer is installed with Visual Studio for design time.
  4. Update your Reporting Services SharePoint Add-In if you are using the report server in SharePoint integrated mode.

Below is a list of available updates (also available from MS08-052):

SQL Server 2000: http://www.microsoft.com/downloads/details.aspx?familyid=5F9E7F78-7439-414B-A9DC-A779B89427DB

SQL Server 2005: http://www.microsoft.com/downloads/details.aspx?familyid=5148B887-F323-4ADB-9721-61E1C0CFD213

Visual Studio 2005: http://www.microsoft.com/downloads/details.aspx?familyid=A7BF790B-3249-4EE8-9440-FA911EBBC08A

Visual Studio 2008: http://www.microsoft.com/downloads/details.aspx?familyid=A8C80B29-6D00-4949-A005-5D706122919A

Report Viewer 2005 Redistributable: http://www.microsoft.com/downloads/details.aspx?familyid=82833F27-081D-4B72-83EF-2836360A904D

Report Viewer 2008 Redistributable: http://www.microsoft.com/downloads/details.aspx?familyid=6AE0AA19-3E6C-474C-9D57-05B2347456B1

Reporting Services SharePoint Add-In: http://www.microsoft.com/downloads/details.aspx?FamilyID=1e53f882-0c16-4847-b331-132274ae8c84&displaylang=en


Custom Credentials in the Report Viewer

$
0
0

When using the ReportViewer control in server mode, you will need to consider how you want to authenticate to the report server.  By default, the viewer connects as the current thread user by supplying CredentialCache.DefaultCredentials to its underlying WebRequest.  One of the questions I am frequently asked is why there isn’t a simple credentials property on the ReportViewer.  That is, why can’t you simply write reportViewer1.ServerReport.Credentials = new NetworkCredential(…)?  Let’s take a look at that question and the best practices around it.

IReportServerCredentials

When you are using the ASP.Net version of the ReportViewer control, supplying custom credentials means you need to implement IReportServerCredentials.  An instance of this interface is then typically passed to ServerReport.ReportServerCredentials.  The interface is defined as:

public interfaceIReportServerCredentials
{
    WindowsIdentity ImpersonationUser { get; }
    ICredentials NetworkCredentials { get; }
    bool GetFormsCredentials(outCookie authCookie, outstring userName, outstring password, outstring authority);
}

IReportServerCredentials allows you three different ways to control the authentication to the report server.  These methods are not mutually exclusive, so you can use any combination of them if needed.

ImpersonationUser defines a WindowsIdentity that the ReportViewer will impersonate before each WebRequest call to the server.  In effect, the viewer will call WindowsIdentity.Impersonate on the identity returned from this property, make the call to the server, then revert the thread identity immediately afterwards.  If you return null from this method, the server call is made as the current thread user.

The value of the NetworkCredentials property is passed directly to the WebRequest object via the WebRequest.Credentials property.  As noted earlier, if you return null for this property, the viewer will default to CredentialCache.DefaultCredentials.

The final method, GetFormsCredentials, is the only report server specific authentication mechanism on the interface.  If you return true from this method, the out parameters are used to call LogonUser on the report server.  This method is designed for use with custom security extensions installed on the report server.

This still doesn’t explain why we have the interface at all.  These properties and methods could have easily been included on the ServerReport object.  So why have the interface?  That comes down to when the report viewer uses the credentials.

With the ASP.Net report viewer, calls to the report server don't just happen when the ASPX page is executing.  There are a number of times when the browser calls back to the web server via the ReportViewer HTTP handler.  The most common case is to retrieve images in the report.  Images are retrieved after the HTML for the report page has been sent to the client.  When the viewer receives a request for an image, it must relay that request to the report server to get the image.  That report server request must be authenticated as the same user that ran the report initially.  Other "out of band" cases include printing and exporting the report.

There are two ways an HTTP handler can get information: via the incoming request or from stored state on the web server.  Sending credentials on the request URL would require sending the credentials to the client, which is an obvious security problem.  So the viewer opts to use stored state on the web server.  The instance of this interface that you give to the viewer is placed in ASP.Net SessionState when the ASPX page is executing so that it can be retrieved by the HTTP handler later.

Storing credentials anywhere is something that should be done with caution.  By using an interface, we can provide you with a great deal of flexibility over what actually gets stored in SessionState.  Unfortunately, the most common implementation I see for this interface tends to look something like this:

[Serializable]
classMyCredentials : IReportServerCredentials
{
    privatestring m_userName;
    privatestring m_password;

    public MyCredentials(string userName, string password)
    {
        m_userName = userName;
        m_password = password;
    }

    publicWindowsIdentity ImpersonationUser
    {
        get { returnnull; }
    }

    publicICredentials NetworkCredentials
    {
        get { returnnewNetworkCredential(m_userName, m_password); }
    }

    publicbool GetFormsCredentials(outCookie authCookie, outstring userName, outstring password, outstring authority)
    {
        authCookie = null;
        userName = null;
        password = null;
        authority = null;
    }
}

In this case, you are storing the credentials directly in SessionState.  While not necessarily insecure, there is often a more secure way to do it.  Let’s say you always read the user name and password from the web.config file.  In that case, you could read the credentials directly from the config file from within your implementation.  This version accomplishes the same goal but without storing the actual credentials in SessionState:

[Serializable]
classMyConfigFileCredentials : IReportServerCredentials
{
    public MyConfigFileCredentials()
    {
    }
 
    publicWindowsIdentity ImpersonationUser
    {
        get { returnnull; }
    }
 
    publicICredentials NetworkCredentials
    {
        get
        {
            returnnewNetworkCredential(
                ConfigurationManager.AppSettings["MyUserName"],
                ConfigurationManager.AppSettings["MyPassword"]);
        }
    }
 
    publicbool GetFormsCredentials(outCookie authCookie, outstring userName, outstring password, outstring authority)
    {
        authCookie = null;
        userName = null;
        password = null;
        authority = null;
    }
}

One additional point to note is that because this class is being stored in SessionState, it must be marked as serializable if your application sets the SessionState mode to anything other than inproc.

What if you don’t want to use session?

There are plenty of cases when you don’t want SessionState enabled at all in your application.  To handle this scenario, there is a second way to supply credentials for server mode.  You can implement IReportServerConnection or IReportServerConnection2 instead:

publicinterfaceIReportServerConnection : IReportServerCredentials
{
    Uri ReportServerUrl { get; }
    int Timeout { get; }
}

IReportServerConnection derives from IReportServerCredentials.  So everything noted above is equally applicable here.  In addition to credentials, you supply the report server url and timeout for the server.  This information was being stored in SessionState too.  But with SessionState disabled, it needs to come from your object instead.  In order to make your object available to the report viewer HTTP handler without storing it, you must register your class in your web.config file:

<configuration>
    <appSettings>
        <add key="ReportViewerServerConnection" value="MyNamespace.MyServerConnectionClass, MyAssembly"/>
    </appSettings>
</configuration>

The viewer instantiates your object each time it needs connection information, either while executing the ASPX page or the HTTP handler.  With this approach, you shouldn’t set the ServerReport.ReportServerCredentials property at all.  Using the web.config file can be also beneficial even if you have SessionState available to you because it prevents the need to place an instance of your object in SessionState for each active session that has accessed the viewer.  The tradeoff to this approach is that you only get one config file setting.  The same IReportServerConnection implementation will be used for all report viewers anywhere on your site.  You can use any HttpContext information you want to return different values in different situations, but you will need to code that into the one registered type.

What about winforms?

With all of this talk about HTTP handlers and SessionState, we should also touch on how credentials work with the winforms report viewer.  Since everything is sitting in memory on the client all of the time, there is no need to implement any interfaces.  The ServerReport.ReportServerCredentials property in the winforms namespace doesn’t have a set accessor.  Instead, it provides an implementation that includes set accessors for each of the authentication properties and methods.  So you can set the ImpersonationUser or any other authentication property directly on the ServerReport.ReportServerCredentials object.

SQL Server 2008 and the ReportViewer Controls

$
0
0

In August, we released SQL Server 2008 with a number of new features for Reporting Services.  There have been questions lately about how this impacts the report viewer controls.  The answer depends on whether you use server mode or local mode.

Server Mode

We spent a great deal of time making sure that the existing report viewer controls (both the Visual Studio 2005 and 2008 versions) are capable to rendering reports from a SQL Server 2008 Report Server.  If you publish a report to the server (in any of the SQL 2000, 2005, or 2008 definition schemas), then point a report viewer control to that report, it will render it.  There are, however, two limitations to viewing any report from the 2008 server in the existing viewer controls.

One of the new features of the report processing engine is that it now evaluates the report on demand.  To the extent possible, the processing engine will only perform the calculations necessary to return the report page that has been requested by the client.  A side effect of this approach is that the processing engine does not always know the actual page count of the report when rendering any given page.  We added a new SOAP API to accommodate this so that clients can detect this condition and display an appropriate UI.  But the existing report viewer controls use the old SOAP API.  The old API always returns the actual page count, maintaining backwards compatibility.  But this necessitates performing additional calculations and therefore does not take advantage of all of the performance improvements we made in SQL Server 2008.  Other processing engine performance improvements are still used, so you will see benefits regardless of this limitation.

The second new feature that impacts the report viewer is the use of rich text.  The new rich textbox allows you to specify multiple formats and actions in a single RDL textbox.  But due to limitations in the rendering format used by the existing ReportViewer winforms control, only the first format in a rich textbox will be applied when displaying the report through the winforms viewer.  Additionally, report actions (drillthoughs, bookmarks, and hyperlinks) will be ignored if they are applied to individual text runs rather than the textbox as a whole.  These limitations do not apply to the ASP.Net report viewer.

Aside from these two limitations, all other new features of the report definition will work through the existing report viewer controls in server mode.  You can use the new charts, gauges, tablix, and the new Word renderer for exporting, all without any updates to your client.

Local Mode

Local mode is a different story.  When using local mode with the VS 2005 or VS 2008 viewer controls, you are using the same report processing engine that was shipped with SQL Server 2005.  This engine does not understand the new report definition schema and attempting to load a report created with one of the new SQL Server 2008 authoring tools will result in this error:

The report definition is not valid. Details: The report definition has an invalid target namespace 'http://schemas.microsoft.com/sqlserver/reporting/2008/01/reportdefinition' which cannot be upgraded.

Looking Forward 

We are working on updated versions of both the winforms and ASP.Net report viewer controls to support the new report processing engine in local mode and therefore all of the new RDL features as well (new charts, tablix, rich text, etc).  This upgrade should also remove the limitations described above for server mode.  The current plan is to release the updated viewer controls with Visual Studio 2010.  We are also adding lots of new features to the viewers, which I will be talking about more in the coming months.  Disclaimer: Please be aware that while this is our current plan, it is always possible that plans change along the way.

Report Viewer in Visual Web Developer 2008 Express

$
0
0

After the initial release of the ReportViewer ASP.Net control with Visual Studio 2005, we also released a separate add-on to the Visual Web Developer 2005 Express sku.  But when Visual Studio 2008 was released, there was no equivalent add-in for the web developer sku.  I’m happy to announce that this add-in is now available for Visual Web Developer 2008 Express Edition.

The ReportViewer control included in this download is the same one that is available with the other Visual Studio 2008 skus.  This does not add new functionality to the design time or run time experience beyond making it available in a new sku.

English, French, and Japanese versions are available now.  Additional languages will be available soon.

Download Details: http://www.microsoft.com/downloads/details.aspx?FamilyID=b67b9445-c206-4ff7-8716-a8129370fa1d

Why is ReportViewer Ignoring BindingSource Operations?

$
0
0

I’ve seen several posts in the Report Viewer forum from frustrated users who are trying to use a BindingSource as a data source to the ReportViewer in local mode.  Problems typically manifest when setting sorting or filtering options on the BindingSource object, but those operations are not reflected in your report.  So what’s going on?

The short answer is that it’s a bug in the ReportViewer.  When presented with a BindingSource, the viewer is reaching in and referencing the underlying data source, which bypasses any transformations the BindingSource applies.  So instead of displaying filtered data, the report viewer still shows you the raw data in the report.

We’ve fixed this bug for the next release of the viewer.  But until that is released, there’s an easy way to code around this problem.  Since the viewer uses the underlying data source rather than the transformed output, adding an extra BindingSource layer fixes the problem.  In you form load event, you can add the following code:

BindingSource tempBindingSource = newBindingSource(this.OriginalBindingSource, "");
this.reportViewer1.LocalReport.DataSources[0].Value = tempBindingSource;

You will need to replace "OriginalBindingSource" and "reportViewer1" with the equivalent names of objects on your form.  The index into the DataSources collection may also be different, depending on your report.

With this workaround, when the ReportViewer processes the report, it sees the tempBindingSource as the data source.  Our bug causes the viewer to reference the underlying data source of tempBindingSource, which is the filtered output from the original BindingSource object.  As a result, you get the expected data in the report.

On a side note, I want to mention that this bug was first reported to us via Microsoft Connect.  If you believe you are running into a bug in the report viewer or have a request for a new feature, Connect is a great way to contact us.  We track these issues directly and the site allows us to communicate back and forth if we have trouble reproducing the problem.  It also allows you to vote on existing feature requests or bugs to help us prioritize the work.

Did Your Session Really Expire?

$
0
0

The ASP.Net ReportViewer relies on ASP.Net session state to store critical data that can’t always be easily regenerated.  In local mode, it stores the report snapshot, which contains a compiled form of the report definition, some or all of the data used in the report, and some report state information such as which toggles have been expanded.  In server mode, depending on your configuration, session state may be used to store connection information to the report server.  The report viewer stores data in session state when the ASPX page is executing and retrieves it during future postbacks or from the report viewer’s HTTP handler when rendering the report asynchronously, retrieving report images, exporting the report, or printing.

The report viewer will throw an AspNetSessionExpiredException when information it previously placed in session is not available during a postback or HTTP handler operation.  The exception message is "ASP.NET session has expired".  But this can be misleading.  Fundamentally, the exception means that the expected data wasn’t in session state.  But this can be for a number of different reasons:

1. Your session really did expire
The report viewer will connect to its HTTP handler one minute prior to session expiring as long as the web page is still being displayed in the browser.  This keeps session from expiring while the report is still being viewed.  It tends to work well, but it isn’t a guarantee.  The web server may be under load or the connection may not be reliable.  If the user has been inactive for a long time and this "ping" does fail, the session will expire and future requests will fail with this exception.  In this situation, there generally isn’t much a user can do other than refresh the page to start report processing over again.

2. The session isn’t available to the server handling the request
I often see session state configured incorrectly when load balancing the web front end across multiple machines or a web garden.  If your load balancing doesn’t have client affinity, you’ll need to make sure that session state is shared across all nodes.  ASP.Net session defaults to inproc mode, meaning that it is stored in memory in a single process.  A separate process in a web garden or a separate machine handling a request to render the report will have its own instance of session state that does not contain the information the report viewer stored while executing the page.  This tends to manifest as a seemingly random failure - some images or charts will fail to load or the report will fail to render non-deterministically.  The requests that get routed to the process that handled the original ASPX request succeed but requests routed elsewhere fail.  The solution to this problem is to use a session state mode that can be shared by all nodes in the farm.

3. Cookies are disabled on the client
ASP.Net keeps track of a session with an HTTP cookie.  If a client has cookies disabled, postbacks or requests to the report viewer HTTP handler will not load the existing session, resulting in this exception.  In this case, you can turn on cookieless session mode.  ASP.Net will redirect the first request to the ASPX page to a URL that contains a session identifier rather than storing the identifier in a cookie.

Manually Printing a Report

$
0
0

Even though the ReportViewer has built-in print functionality, people often wish to implement their own version to provide significant customizations or a deeper integration with their application.  There are a number of samples out on the web, but I thought it might be useful to provide one here since the request is so common.  Attached to this post is a sample PrintDocument that will programmatically print a given ServerReport or LocalReport.

Generating a report for printing uses the image renderer, which is a hard page break renderer.  Unlike viewing a report within the report viewer UI, a hard page break renderer must cut off a page at a specific size.  In the report viewer UI, a page can be enlarged to make a table or chart fit.  You can’t do that with paper.

As a result, calculating the content for an arbitrary page can be very expensive.  When the image renderer receives a request to generate page 10, for example, it internally generates pages 1 through 9 and throws them out.  So asking for one page at a time from the image renderer will be prohibitively slow, even for small reports.  To address this performance issue, both local and server mode have the ability to request all the pages at once.

In local mode, the sample uses the Render overload that takes a CreateStreamCallback.  This causes the renderer to keep generating pages until it hits the end of the report.  The callback is invoked once for each page to generate a stream to store the image for that page.

Server mode is a little different - the server can’t ask the client for a callback for each page.  Instead, the report server supports the concept of persisted streams.  The URL access parameter rs:PersistStreams is used to tell the server to generate all of the pages and to persist those pages on the server.  This request returns the first page.  Each subsequent page can be retrieved using the rs:GetNextStream parameter.  When you try to go beyond the last page, the server returns an empty response.

This sample is just a starting point to demonstrate the ReportViewer APIs.  There are a number of ways it can be improved.  Given that both rendering a page and sending it to the printer can be long running operations, you might consider rendering the pages in a background thread, allowing you to continue generating pages while sending completed pages to the printer.  You also might wish to extend it to use printers other than the default printer.  Or you could extend OnBeginPrint to handle printing a specific range of pages rather than all of them.  Or you could use file system backed streams, since using MemoryStreams could consume a lot of memory on a large report.

Where’s the new Report Viewer?

$
0
0

Previously, I posted that we were working on an updated version of the report viewer control for Visual Studio 2010.  With beta 1 now available, I have naturally started receiving questions wondering why the report viewer that is included in beta 1 is essentially the same thing that shipped with Visual Studio 2008.

I want assure everyone that our plans have not changed.  We will ship an updated report viewer in VS 2010.  Unfortunately, the timing just didn’t work out for us to get this update included in beta 1.  Our current plan is for the updated viewer to appear in beta 2.

I’ll be talking a lot more about the new report viewer features once beta 2 is available.  But I do want to at least provide an overview of the work we’re doing.  Below are some of the new features or changes we’re making.  This is certainly not an exhaustive list.

  • Support for the 2008 RDL schema in local mode.  This will give you all of the new features available in RDL in SQL Server 2008, including tablix, rich text, updated chart visualizations, gauge, and many others.  We’ll be including the updated report design surface for local mode as well.
  • Support for ASP.Net AJAX.  The report viewer will use AJAX to update its various regions (report, toolbar, etc).  You will also allow be able to include the entire ReportViewer control in an UpdatePanel.
  • Updated API.  We’ve received a lot of feedback on missing APIs or scenarios that are difficult given the current API.  We’ve added a number of new events and methods to help, including a new JavaScript API for interacting with the ReportViewer in the browser.
  • Significantly improved browser compatibility.  We’ve put a huge amount of effort into improving our support across browsers.  We’ve seen plenty of reports of extra scrollbars or other problematic renderings in Firefox, Safari, and standards mode in general.  The initial feedback on these changes has been very positive.
  • Usability and “look and feel” enhancements.  The viewer needed a minor facelift.  It got one.

As always, if there is a particular feature or problem you’d like us to address, we want to know.  Please send feedback via Microsoft Connect.  Our team reviews that feedback weekly.


SQL Server 2008 R2 August CTP is Available

$
0
0

The SQL Server 2008 R2 August CTP is now available for download.  Reporting Services has a number of new features available in this CTP which are being discussed on the Reporting Services team blog.

This is the first release of SQL Server to include portions of the ASP.Net AJAX work we have done for the report viewer.  SQL Server doesn’t ship with a standalone ReportViewer control, so you won’t be able to use it in Visual Studio just yet.  But you can get a preview of the updated UI, some of our usability enhancements, and the more fluid page navigation and report interactivity experience.  The standalone ReportViewer control will be available in Visual Studio 2010 Beta 2.

Some of the new UI features you will see in this release:

  • The buttons to collapse the prompt area and document map have been replaced with splitter bars, similar to the UI we have been using in the report viewer webpart.
  • Multi-value parameters are now resizable!
  • The export drop down is now a drop down menu, taking up less space on the toolbar
  • All page navigation and report interactivity is done with AJAX.  You will no longer need to press the back button repeatedly in the browser to return from a drillthrough report.
  • Report interactivity features such as search and toggle will no longer reposition the report (i.e. adjust the report scroll position unexpectedly).  The viewer will maintain the scroll position of the report to the extent possible.

As always, we want to hear your feedback.

Visual Studio 2010 Beta 2 is Now Available

$
0
0

Visual Studio 2010 Beta 2 is now available for download!  This beta includes plenty of new features, but it is particularly noteworthy for the ReportViewer control.  Beta 2 contains several key updates, including:

ASP.Net AJAX - The report viewer will now use AJAX internally to update its own content.  It can also be placed inside an ASP.Net AJAX UpdatePanel.

RDLC 2008 – The report viewer can now consume RDLC in the SQL Server 2008 RDL schema.  Local mode now supports the same RDL features that are currently available in SQL Server 2008, including tablix, chart, gauge, and rich text.

Improved browser support – The ReportViewer control supports IE 6, 7, and 8 (in quirks and standards mode for all versions) as well as Firefox 3.5 and Safari 4.0.

I’ll be posting additional information and tutorials on these and other new features over the next few weeks.  As always, please let us know of any feedback you have.  We’re still working on a few more features and bug fixes for this release (particularly in the browser support area), so we definitely want to hear about any problems or issues you run into with the beta.

JavaScript API

$
0
0

One of the new features we added to the ASP.Net Report Viewer in Visual Studio 2010 is a JavaScript API to allow you to interact with the viewer on client.  In reading many of the posts on the report controls forum, we found that many people struggle when implementing a custom toolbar or replacing portions of the toolbar functionality.  The new JavaScript API is intended to make it easier for you to provide the same functionality available through the built-in toolbar with a minimum amount of effort.

The JavaScript API is exposed through the client side ReportViewer object.  Specifically, it’s the Microsoft.Reporting.WebFormsClient.ReportViewer class.  An instance of this class is created on the client for each instance of the ReportViewer control on the page.

Referencing the client side viewer

The ReportViewer client side object inherits from Sys.UI.Control.  To obtain a reference to the client side viewer, use the $find method as follows:

    var clientViewer = $find("ReportViewer1");

The identifier you pass to $find corresponds to the ClientID of the ReportViewer server control.

Checking the state of the viewer

Once you have a reference to the client side viewer, you will want to check the state of the viewer before invoking most of the various methods and properties it exposes.  When the viewer is in a loading state, most of the functionality of the client viewer is unavailable and will throw an exception if called.  The viewer is loading whenever an asynchronous postback is in progress.  It is also in the loading state while retrieving a report page.  This usually happens during an asynchronous postback, but can also extend beyond the lifetime of the postback, such as while retrieving images displayed on the report page.  To check the state of the viewer, use the isLoading property:

    var isLoading = clientViewer.get_isLoading();

Once you have determined that the viewer is no longer loading new data, you can determine what is being displayed using the reportAreaContentType property.  It will indicate if the viewer is blank, displaying a report, or displaying an error message:

    if (!isLoading)
    {
        var reportAreaContentType = clientViewer.get_reportAreaContentType();
        if (reportAreaContentType ===
            Microsoft.Reporting.WebFormsClient.ReportAreaContent.ReportPage)
        {    
            // Perform various operations
        }
    }

Both the isLoading and the reportAreaContentType properties as well as all of the other properties on the client viewer fire the propertyChanged event.  Listening to that event can be useful for determining when you should enable or disable your custom UI.

Client side operations

The client side report viewer supports a number of operations, similar to those available with the built-in toolbar.  MSDN has a list of public properties and methods available on the client side report viewer.  It includes everything from invoking the print control and exporting a report to setting the zoom and scroll position of the report.  The built-in toolbar uses the public JavaScript API to perform its operations, so invoking the exportReport method on the client viewer will look exactly the same as selecting an export format from the built-in dropdown.

I have also included a sample with the article which demonstrates some of the new JavaScript APIs.  The sample was written with Visual Studio 2010 Beta 2.

Report Viewer at PDC 2009

AsyncRendering and all the Baggage that Comes With It

$
0
0

The AsyncRendering property on the ASP.Net ReportViewer control is one of the most misunderstood properties on the ReportViewer.  And that’s our fault.  There are a lot of side effects to setting this property that you wouldn’t expect from the name.  In fact, most of the time that I see users setting this property they are doing it for the side effects rather than for its true intention.  Those side effects are gone in Visual Studio 2010 because you can get the effects you want in either mode.  But to understand how things have changed, let’s first go through some background information.

The intention of AsyncRendering

Traditionally, the HTML of a web page is not sent to the browser until all of the web controls on the page have generated their content.  For controls such as text boxes and buttons, this makes perfect sense.  But the ReportViewer is much more complicated than that.  It can take the viewer a long time to generate the HTML for a report.  In most cases, it makes more sense to send the rest of the page back to the browser and then make another request to get the report content asynchronously.  This allows the user to interact with the rest of the page as well as see a "loading indicator" so that they know the server is doing something.  This is the default behavior - AsyncRendering = true.

But there are also cases where you want the block the entire page until the report is processed.  A good example is a dashboard type of page that is rendering several small reports, perhaps each one containing a single chart or small table.  In this case, you may not want the user to be bombarded with multiple wait indicators.  If you know the reports are quick to process, blocking the page for a short time may be a better overall experience.  This is the intention of AsyncRendering = false.

Asynchronous mode in Visual Studio 2005 and 2008

The mode you choose has a significant effect on the HTML that is ultimately generated.  The ReportViewer control was originally designed long before the appearance of ASP.Net AJAX.  When you render a report synchronously, the HTML for the report content is embedded directly in the entire page.  But when you render asynchronously, the ReportViewer uses a frame.  The frame content is retrieved by the browser separately from the main page, so it allows the main page to be visible while the report is generated in a separate request to the web server.

Frames are at the root of all of the side effects to AsyncRendering.  The use of frames results in the following differences between the two modes:

  • The document map is only visible in asynchronous mode, in part because it relies on frames to handle resizing relative to the report area.
  • Because the report is rendered in a frame and not part of the ASP.Net page that hosts the viewer, there is no chance for developers to handle events that occur while processing the report.  The most frequent complaint we receive in this area is the inability to handle the ReportError event when rendering asynchronously.
  • The size of the frame is difficult for the viewer to calculate and is therefore usually wrong.  It’s based on the sizing mode of the viewer (percentage or fixed size), the height of the toolbar, and the existence of parameters.  This is the leading cause to seeing an excessive number of scrollbars in the viewer, particularly when using standards mode or non-IE browsers.  Developers often switch to synchronous rendering to alleviate this.
  • Along a similar line to sizing of the frame is the fact that the SizeToReportContent property is ignored in asynchronous mode.  The frame does not adjust its size based on the content, so there is no easy way to show an arbitrary report embedded in a page without scrollbars, unless you switch to synchronous mode.

These side effects tend to rank higher in terms of requirements when building an application than whether the report shows up synchronously.  So it’s no surprise that these issues became the driving factor in which mode developers choose.

The Story in Visual Studio 2010

One of the biggest features of the ASP.Net ReportViewer in VS 2010 is the fact that it relies heavily on ASP.Net AJAX.  By default, the viewer will use asynchronous postbacks for all of its operations.  This means that we no longer need to rely on frames to retrieve report data asynchronously from the rest of the ASP.Net page.  With VS 2010, once a report has finished loading, the HTML displayed in the browser will be the same, regardless of whether you use synchronous or asynchronous rendering.

All of the previous side effects of AsyncRendering are now gone.  Both modes support the document map.  Both modes support the SizeToReportContent property.  Asynchronous postbacks are generally treated the same as traditional postbacks, so you can handle events that occur during report processing.  And because of the extensive work we’ve put into this release for standards mode HTML and Firefox and Safari rendering, you should never see double (or triple!) scrollbars in the viewer.

With VS 2010, AsyncRendering has returned to its true intention – it controls whether the initial processing of the report blocks the entire ASP.Net page, and nothing else.

 

Expression Evaluation in Local Mode

$
0
0

RDL and RDLC files contain expressions written in VB.Net.  As .Net code, expressions can potentially perform any operations, including accessing the file system or running native code.  Code access security (CAS) for expressions is therefore critically important.

When a report is deployed to a report server, the report definition goes through a publishing phase that extracts the expressions and compiles them into an assembly.  The assembly, often referred to as the expression host assembly, is then used when the report is executed to evaluate the expressions.  When using local mode, the same process happens in memory when a report definition is supplied to LocalReport.

Visual Studio 2005 and 2008

In Visual Studio 2005 and 2008, there are two ways to load the expression host assembly: into the current AppDomain or into a sandbox AppDomain.

Loading the expression host into the current AppDomain means that it is loaded into the same AppDomain in which the ReportViewer is running.  This has an advantage of improved performance as well as giving you more flexibility over the CAS policy.  Since you create the AppDomain in which the viewer runs, you can also grant a specific set of permissions to any external assemblies loaded by the expression host.  One clear disadvantage, however, is that the assembly will not be unloaded until the entire AppDomain is unloaded, typically when the application shuts down.  If you process lots of reports during in your application, the result is in an effective memory leak each time a new report is processed.  This may be ok for a short lived winforms application that displays a small number of reports, but it doesn’t make as much sense for an ASP.Net application.

Running in the current AppDomain is the default.  However, if you have switched to another mode, you can switch back using ExecuteReportInCurrentAppDomain.  If your report references external assemblies, the viewer will not load them unless you specifically allow it via AddTrustedCodeModuleInCurrentAppDomain.

The sandbox AppDomain is a more restrictive environment for evaluating expressions.  Any assembly loaded into the sandbox AppDomain is granted only Execution permission.  You can switch execution to the sandbox using ExecuteReportInSandboxAppDomain.  In theory, using a separate AppDomain prevents the memory leak.  However, due to the implementation of the sandbox AppDomain in the VS 2005 and 2008 report viewers, the sandbox AppDomain is not unloaded.

Visual Studio 2010

The ReportViewer in VS 2010 is a multi-targeted assembly.  That means that is can be loaded into a .Net 3.5 or a .Net 4.0 application.  When running under .Net 3.5, all of the same rules and methods noted above are applicable.  However, the CLR security team made changes to code access security in .Net 4.0, so there are corresponding changes in the ReportViewer as well:

  • Executing in the current AppDomain is no longer supported, so using a sandbox is the default mode when running in .Net 4.0.  Two new methods have been added to better control the permissions of the sandbox AppDomain so that you don’t see any loss of functionality:  AddFullTrustModuleInSandboxAppDomain and SetBasePermissionsForSandboxAppdomain.
  • ExecuteReportInCurrentAppDomain, AddTrustedCodeModuleInCurrentAppDomain, and ExecuteReportInSandboxAppDomain have been marked as obsolete.  While it is perfectly acceptable to use them in a .Net 3.5 application, they won’t have any effect when you compile for .Net 4.0.  Going forward, we recommend using the sandbox AppDomain even under .Net 3.5 because setting the correct security policy on the current AppDomain is an error prone task.  The new methods will work under both frameworks and is the best approach if you want to write in a framework agnostic way.

If you do nothing, the default behavior under .Net 4.0 will result in the same permissions for the expression host as the default behavior in VS 2008.  You will be running in a sandbox AppDomain (versus the default of the current AppDomain in VS 2008), but the permissions for the expression host will be the same.

As part of the security changes to the CLR in .Net 4.0, an application configuration flag, NetFx40_LegacySecurityPolicy, can be specified to use the .Net 3.5 security model in .Net 4.0.  If this flag is specified, the report viewer will behave just as it would under .Net 3.5, meaning that the obsolete methods will function correctly.

Regardless of which framework version you use with the VS 2010 report viewer, the issue surrounding the sandbox AppDomain leak has been fixed.  The sandbox AppDomain is shared resource across multiple instances of the viewer.  A new AppDomain is created periodically (if there is a need) and used by all local mode requests in the process.  Once all reports referencing the old AppDomain have completed, the old domain will unload.

Reports Never Stop Loading With VS 2010

$
0
0

I’ve received a number of questions from people who have run into problems after upgrading their web application from VS 2008 to VS 2010.  Once upgraded, the report viewer shows the loading indicator indefinitely - the report never loads.  So what happened?

In an earlier post, I talked about the changes we made to AsyncRendering.  Asynchronous rendering no longer renders the report content in an iframe.  Instead, it uses ASP.Net AJAX to perform an asynchronous postback to get the report data.  In most of the cases I’ve seen, this change is at the heart of the problem.

With the iframe model, the report data was retrieved through the report viewer’s HTTP handler.  This request is distinct from a request to the ASPX page itself that contains the report viewer control.  The communication between the browser and the web front end would be something like this:

  1. Browser makes a GET request to the ASPX page to get the page content and a loading indicator for the report
  2. Browser makes a GET request to the HTTP handler to get the HTML for the report (this is the iframe content)
  3. Browser makes GET requests to the HTTP handler to get all the images in the report

But with VS 2010, there are no more frames.  Instead, we use ASP.Net postbacks to render the report.  The new communication sequence is:

  1. Browser makes a GET request to the ASPX page to get the page content and a loading indicator for the report.
  2. Browser makes a POST request to the ASPX page to get the HTML for the report (this content is in an UpdatePanel).
  3. Browser makes GET requests to the HTTP handler to get all the images in the report

In step 2, previously only the ReportViewer code would run to get the report content.  But now the request to get the report content runs the ASP.Net page, including any code you have placed in the page.

Why does this matter?  In the cases I’ve seen in the forums and on Connect, code was added to the load event of the page that altered the state of the report viewer.   The most common example I’ve seen is user code calling SetParameters in the load event, though there are several methods and properties that will trigger this.  Changing the parameter values tells the ReportViewer that it needs to restart report processing.  Effectively, it tells the viewer to return to step 1 – put the loading indicator in the browser and restart report processing.  If you do this during every postback, the viewer never successfully completes step 2.  It just goes into an infinite loop.

Calling methods like SetParameters isn’t cheap.  Each call triggers a round trip to the report server.  So it’s a call you want to minimize anyway.  By only calling SetParameters during the initial GET request or only when parameter values have actually changed, you can improve the performance of your application and break the loop.  A simple check of IsPostBack before calling SetParameters is usually sufficient.


Visual Studio 2010 is Now Available

$
0
0
As many of you know, Visual Studio 2010 was released last week.  We’ve done a lot of work to the ReportViewer control in this release:
  • Support for ASP.Net AJAX
  • Support for the SQL 2008 RDLC schema, including tablix, rich text, and enhanced charts and gauges
  • Significant improvement to support for standards mode and non-IE browsers
  • A new javascript API for the ASP.Net viewer for use on the client
  • Many API updates to both the ASP.Net and winforms controls

The runtime redistributable has been posted and is available from http://www.microsoft.com/downloads/details.aspx?FamilyID=a941c6b2-64dd-4d03-9ca7-4017a0d164fd.  The language packs will be posted as the localized versions of Visual Studio become available.

With every release, we prioritize our efforts based on the feedback we receive from you.  As always, we look forward to your feedback and suggestions on areas we can improve.

Upgrading Web Application Projects to VS 2010

$
0
0

A number of people have posted questions about upgrading their VS 2008 web applications using the ReportViewer control to VS 2010.  The upgrade process is not automatic.  Before I get into the steps required to upgrade your applications to the VS 2010 ReportViewer, I’d like to take a moment to explain why this upgrade process is not automatic.

The VS 2008 ReportViewer can be used in the VS 2010 shell.  While you won’t get the design time experience associated with the 2008 viewer, it will function correctly at runtime.  Certainly, we want everyone to upgrade to the new version, but there are a few reasons you might not want to, and therefore why we don’t upgrade everything automatically:

  • Using the VS 2010 ASP.Net ReportViewer requires ASP.Net AJAX.  While this has not been an issue for most people, it is a new requirement and requires some changes to your ASP.Net pages.
  • The VS 2010 ReportViewer will not work against a SQL 2005 report server in server mode.  If you plan to connect to a SQL 2005 report server, you will need to stay on the VS 2008 version.
  • There are a few API changes that break backwards compatibility in the strict sense.  In response to customer feedback, some methods changed from a return value of void to something more useful.  These changes don’t require you to change your code, but they do require a recompilation.  Assembly version redirection won’t work.

After you load your web application in VS 2010, you will need to perform the following manual steps to get your application back to a fully working state:

  1. In the assembly references section of the solution explorer, remove any references to 9.0 ReportViewer assemblies and add their 10.0 equivalent.  Depending on the versions of the ReportViewer you have installed on your development machine, this may have been done for you.
  2. In the web.config file, locate all references to the ReportViewer assembly and replace the 9.0.0.0 version string with 10.0.0.0.  The public key token did not change in 10.0.  In a typical web.config file, there are four references to the report viewer:
    1. A reference to Microsoft.ReportViewer.WebForms in the system.web/compilation/assemblies section
    2. A reference to Microsoft.ReportViewer.Common in the system.web/compilation/assemblies section
    3. A reference to the RdlBuildProvider in system.web/compilation/buildProviders section
    4. A reference to the HTTP handler in the system.web/httpHandlers section.  Depending on the version of IIS that you are targeting, a similar reference may also be in the system.webServer/handlers section.
    5. In each ASPX file that references the ReportViewer control, a register tag was added when you first inserted a ReportViewer control on the page.  This reference also needs to be updated from 9.0.0.0 to 10.0.0.0.
    6. If you did not already have an ASP.Net AJAX ScriptManager on the page, you will need to add one to the top of the page.  It is located in the AJAX Extensions section of the Toolbox.

    ASP.Net ReportViewer Samples

    $
    0
    0

    We have recently posted a number of samples that use the ASP.Net ReportViewer. These sample applications cover a wide range of topics. You’ll see sample code for long standing ReportViewer concepts such as hooking up data sources to subreports and implementing a custom toolbar. There are also several samples covering areas that are new to the Visual Studio 2010 ReportViewer, such as the JavaScript API.

    Take a look, and as always, let us know of other samples you would like to see.

    http://code.msdn.microsoft.com/reportviewer

    Nested Objects in Local Mode

    $
    0
    0

    As I have mentioned before, the Reporting Services team regularly reviews the bug reports and feature requests that come in via Connect.  Shortly after the release of Visual Studio 2010, we started to receive a few requests and questions through Connect.  One of them, however, stood out from all the others in terms of volume – nested objects in local mode were failing with #Error.  It wasn’t long before calls started to come in through customer support as well.

    As we started to investigate the problem, it became clear that this wasn’t a simple issue.  The further we dug into the code, the more we realized that this wasn’t going to be easy to fix.  There was a lot of pressure to publish a hotfix to immediately fix the problem.  However the architecture change needed was of sufficiently high risk that we didn’t want to rush this out and break something else.  Since this has been such a high volume problem, I want to dedicate an article to not only explaining the fix, but also some history into the problem.

    Report Processing in VS 2005 and VS 2008

    The processing engine used by the VS 2005/2008 ReportViewer in local mode is based on the SQL Server 2005 report processing engine.  That engine reads the entire data set and processes the entire report before rendering the first page.  That means that any data needed to evaluate expressions on any of the pages is calculated up front.  When you are using an object data source, the object sits in memory from the time you hand it to the Report Viewer up until it is used to evaluate expressions.  The pre-calculated expressions are stored in a report snapshot along with a serialized version original object data.

    During most interactivity with the report, such as navigating pages or expanding drilldowns, only the calculated expression values are referenced.  The original data isn’t touched.  This works just fine for the vast majority of cases.  If the original data isn’t touched, then why do we store it?  There is one case in VS 2005/2008 where we do need to recalculate some of the expression values – during an end user sort.  Resorting the data has the potential to change the report in far more significant ways than any other interactivity.  The original data is needed to recalculate the expression values in this case.

    During our investigation into the nested objects problem, we learned that there is one case that doesn’t work in VS 2005/2008 – nested objects in local mode after a user sort.  The processing engine in those releases was correctly serializing all of the top level properties into the report snapshot.  But it failed to do this correctly for nested objects.  Enabling user sort is not the most frequently used feature in RDL, but it seems that in all of the years local mode was available, no one, including us, used it local mode with an object data source that uses nested objects.  In VS 2005/2008, this scenario produces the same #Error that has become such a hot topic in VS 2010.

    Report Processing in VS 2010

    Something obviously changed in VS 2010 to make this problem much more apparent.  The change is rooted in the fact that the VS 2010 local mode is based on a newer version of the report processing engine - the SQL Server 2008 version.  One of the major updates in that processing engine is that it is an on-demand processing engine.  Unlike previous versions where all report expressions were calculated up front, this engine only calculates the expressions that are needed at the moment.  This tends to improve performance since we never spend time calculating values that you don’t ask to see.

    One of the consequences to this workflow change is that the snapshot is created first, with processing and rendering always executing off of that snapshot.  The in-memory objects from the object data source are never directly used by the renderer as was the case before.  The same serialization problem we had in earlier versions was unknowingly carried forward into VS 2010.  But now it happens with any rendering of the report, not just after a user sort.

    The Fix

    We spent a great deal of time evaluating this problem and quickly realized that it wasn’t going to be an easy fix.  It was certainly too big to address in a hotfix.  But we have fixed this in Visual Studio 2010 SP1, with one caveat.  In order to make nested objects available to the processing engine, they must be placed in the report snapshot.  Therefore, in order to get nested objects working again, we are adding the requirement that the objects in your data hierarchy be serializable.  This allows us to place them in the snapshot and make them available to the processing engine for expression evaluation.  This has the added benefit of not only bringing us to parity with VS 2005/2008, but also fixing the long broken user sort scenario.

    Thank you for your patience with this issue and thanks to all of you who have worked with us directly in analyzing this problem.   I certainly apologize for the delay in getting this fix out to you, but I hope that this delay has allowed us to deliver the cleanest and least impactful fix.

    The Invisible ReportViewer

    $
    0
    0

    An interesting bug report came in recently regarding toggling the Visible property on the ASP.Net ReportViewer.  It’s fairly subtle and I don’t believe many people are running into it.  But the solution isn’t obvious, so I want to make the information available in case you run into it.

    The bug report placed a ReportViewer control on a page and initialized it in a standard way, such as declaratively in the ASPX file or in the Init event at the page level.  The only thing that was unique to this particular page was that the viewer was initially hidden (ReportViewer.Visible = false).  The viewer was made visible in response to a user action on the page, such as a button click.  The problem happens during the button click event: while the control itself became visible, the report content did not.

    The underlying bug comes from the way the ReportViewer tracks changes internally.  In this case, the report definition is specified during the initial GET request and persisted by the report viewer.  On a later postback, the viewer is made visible.  The bug in the viewer is that while it does track the fact that the report definition has changed, it doesn’t keep that information across postbacks.  Since the viewer becomes visible during a different postback than the one in which the report definition is specified, the ReportViewer doesn’t recognize that it needs to initiate processing of the report when it becomes visible.

    Fortunately, there are two easy ways to work around this problem:

    1. Postpone setting the report definition until you make the ReportViewer visible.
    2. When making the ReportViewer visible, also make a trivial change to the ReportViewer to trigger a definition change.  The easiest way to do this is to toggle the value of ReportViewer.ProcessingMode (e.g. set it from Remote to Local then back again).
    Viewing all 31 articles
    Browse latest View live


    <script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>