Thursday 28 April 2011

SSRS 2008 Release 2 Update 5

I said in my last post that after updating my SSRS R2 version to update 5, I started getting problems when running reports from the Reports Manager. In particular, I got the following JavaScript error:

"Type Microsoft.Reporting.WebFormsClient.ReportViewer has already been registered. The type may be defined multiple times or the script file that defines it may have already been loaded."

Once this error has occurred, there's a knock on effect which causes a few more JavaScript errors and the final result is that any parameters that need to be entered, can't. All the parameter fields are disabled.

After a bit of research, I found out the cause of this problem and I found a workaround. I'm now going to go through how I found out what caused the problem and suggest the fix I've implemented. If you want to skip how I found out what caused the problem then the source code can be found here.

So, going by the error, it seems that the JavaScript type Microsoft.Reporting.WebFormsClient.ReportViewer has been defined twice, so, it makes sense that that's where we should start. Let's try and find the script(s) that define that type.

I'm using Microsoft Internet Explorer 9 which includes a "Developer Tools" feature. This feature now includes a "Network" tab which, when capturing, will show which files are requested by the browser, how those files are retrieved (POST, GET etc.) and the header and body of each individual request and subsequent response. Using this feature when we click on the report, we get something that looks like this:




I've highlighted the two rows that look interesting, just by looking at the name of the resource, it seems like these two could be useful. So, if we look at the response body of those two requests, we find the following: ReportViewer.js and ViewerScript.

If you open those, and search for "Microsoft.Reporting.WebFormsClient.ReportViewer" you'll notice that they both create that type. As ReportViewer.js is the first to load, that creates it first, then ViewerScript runs and it tries to create it again. An exception is thrown and then we start getting all our problems.

So, how do we fix this? Well, if we look closely at the address, we'll see that it requests the file Reserved.ReportViewerWebControl.axd. Axd? That seems like an odd file extension. Well, it is, it's normally used for ASP.NET HTTP Handlers and this is no different. If you check the web.config file for the Report Manager, you'll find this:


<httpHandlers>
      <add verb="*" path="Reserved.ReportViewerWebControl.axd" type="Microsoft.Reporting.WebForms.HttpHandler, ReportingServicesWebUserInterface, Version=10.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" />      
      <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false" />
</httpHandlers>


The first add tag clearly maps the path Reserved.ReportViewerWebControl.axd to the type Microsoft.Reporting.WebForms.HttpHandler. For those of you that haven't come across HttpHandlers before, they're a nifty way of requesting resources that have been compiled into a DLL. The class, in this case, Microsoft.Reporting.WebForms.HttpHandler has to implement the IHttpHandler interface which defines a ProcessRequest method, this method allows you to return anything you want back to the browser, it could be a resource from a DLL or it could be some text that's generated programatically on the fly. As long as there's a mapping for it within the httpHandlers section of your web.config then every time a request is made to the path defined then instead of looking for the physical file on your web server, it'll call the class instead. I've skimmed over http handlers in a hope that you kind of understand their purpose but for more information, see here: http://msdn.microsoft.com/en-us/library/bb398986.aspx#Features

So, how can we use this knowledge to fix our little problem? Well, we know the contents of the JavaScript files that are causing the problem. In particular, we know it's the type defined in the ViewerScript that's being problematic. So, if we make a copy of that file but remove the Microsoft.Reporting.WebFormsClient.ReportViewer type from it, then, if we change the http handler mapping to point to a http handler created by us that, when asked for ViewerScript, returns our modified ViewerScript, rather than the original, then we might just have a fix.

It's not quite that easy though. The path defined within the web.config can only contain just that, the path, it cannot contain any of the query string, so, we'll need our http handler to function just as the original did apart from when requesting the ViewerScript. Well, we can do this pretty easily by wrapping the original within side our own. To very quickly walk you through this, if you create a new class library project, then add a new Resource.resx file to the project. Then, create a new JavaScript file and called it ViewerScript.js. Copy the contents of the original ViewerScript but without the Microsoft.Reporting.WebFormsClient.ReportViewer into the new file. Then, add the file to Resource.resx. Finally, you need to add a reference to the ReportingServicesWebUserInterface DLL which you can pull directly out of the Report Manager bin directory. This will give you access to the original HttpHandler that Microsoft used.

If all that's been set up correctly, then you can create a new class and copy and paste the following:


using System.Web;

namespace SSRSR2
{
    public class HttpHandler : IHttpHandler
    {
        private Microsoft.Reporting.WebForms.HttpHandler handler;
        public HttpHandler()
        {
            handler = new Microsoft.Reporting.WebForms.HttpHandler();
        }

        public void ProcessRequest(HttpContext context)
        {
            if (context.Request.QueryString["Name"] != null && context.Request.QueryString["Name"] == "ViewerScript")
            {
                context.Response.Write(JsHandlerFix.Resource.ViewerScript);
                context.Response.End();
            }
            else
            {
                handler.ProcessRequest(context);
            }
        }

        public bool IsReusable
        {
            get { return handler.IsReusable; }
        }
    }
}


Essentially, this just creates a new instance of the original http handler, if the name of the resource requested isn't "ViewerScript" then it just passes the request on to the original handler and lets that deal with it, which will ensure that the functionality between the original handler and our handler is exactly the same. Then, when "ViewerScript" is requested, we grab our modified ViewerScript from our Resource file and return it.

With all that done, if you compile everything and place the resulting DLL into the bin directory of the Reports Manager, then, all we have to do is change the http handler mapping within the web.config and we should be good to go.

Assuming the name of your DLL is "ViewerFix" then your http handler tag within the web.config file should look like this:


<httpHandlers>
      <add verb="*" path="Reserved.ReportViewerWebControl.axd" type="SSRSR2.HttpHandler, ViewerFix" />
      <add verb="GET,HEAD" path="ScriptResource.axd" type="System.Web.Handlers.ScriptResourceHandler, System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false" />
</httpHandlers>


And that's it. Restart your reports server service and you should now be able to change the parameters within the reports manager so you can actually run reports.

Just incase you didn't follow the above, all the source code can be found here, feel free to use it as you please.

Now I imagine this error isn't something that'll be around for long, it's too big an error for Microsoft not to address it quickly but, if you need a solution now and can't hang around, here you go.

Enjoy!

6 comments:

  1. Great post - but aren't you supposed to be on holiday?

    ReplyDelete
  2. yep. But there's only so much I can watch Mourinho moan about Barcelona on Sky Sports News.

    ReplyDelete
  3. Thanks for posting this information. I have followed your instructions below and am experiencing the same issue of receiving the javascript error:

    Sys.InvalidOperationException: Type Microsoft.Reporting.WebFormsClient.ReportViewer has already been registered. The type may be defined multiple times or the script file that defines it may have already been loaded. A possible cause is a change of settings during a partial update.

    and none of my parameter controls are enabled.

    I am running SSRS version 10.50.2425.0
    I used your sample code (which created JsHandlerFix.dll), placed the .dll in the bin directory and referenced JsHandlerFix in the Web.confg instead of ViewerFix.

    Any other pointers that I should try to get this to work?

    Thanks for your help!
    ShannonG

    ReplyDelete
  4. Nevermind! I needed to clear out my IE cache after deploying the fix. Thanks again for your posting. It saved the day :)
    Cheers,
    Shannon

    ReplyDelete
  5. After more investigation on this, I found the cause of the problem...

    In the Report Manager web.config file, the debug attribute has been set to true. If you set it to false, this problem goes away.

    ReplyDelete