1

I have inherited an application that runs small reports locally using Microsoft Web ReportViewer. Our application allows you to "Preview/Print" a report by clicking on a specific button that routes the user to a URL that allows them to download the report as a PDF. We have recently received the requirement to save these PDFs to the document table in our database. I have been able to get this to work successfully on localhost; however, when I publish the application to our IIS server, I get the following error:

System.Data.SqlClient.SqlException: Login failed for user 'Domain\Servername$'.

I've reviewed all of the sites that I could find involving this error (including this one) - most point to adding the server account to the SQL database; however, this shouldn't be an issue, since the button to preview/print the document is still functional and works as expected when the application is published and all of the data is held in a local object, which was previously pulled from the database (the model parameter below). The button and the auto-generation feature use the same two methods to create the PDF document(see below).

Here's some code:

   public static byte[] CreatePDFDocument(DocumentTemplateType template, Request model)
   {
        Warning[] warnings;
        string[] streamIds;
        string mimeType = string.Empty;
        string encoding = string.Empty;
        string extension = string.Empty;

        ReportViewer viewer = new ReportViewer();

        viewer.ProcessingMode = ProcessingMode.Local;
        viewer.LocalReport.ReportEmbeddedResource = "Xxx.Xxx.Bll.ReportViewerRDLCs." + template.RdlcFilename;

        switch ((DocumentType)template.DocumentTypeId)
        {
            case eDocumentType.Report1:
                viewer.LocalReport.SetParameters(GetForm1Parameters(model));
                break;
            /**
             * Several other reports are in this switch.  All reports have the
             * same issue - all but one are removed for brevity.
             */
        }

        byte[] bytes = viewer.LocalReport.Render("PDF", null, out mimeType, out encoding, out extension, out streamIds, out warnings);

        return bytes;

        //return new byte[5] {5,6,7,8,9}; - used for troubleshooting.
    }


    public static List<ReportParameter> GetReport1Parameters(Request model)
    { 
        List<ReportParameter> rptParams = new List<ReportParameter>();

        //Start comment
        rptParams.Add(new ReportParameter("EmployeeFullName", string.Format("{0:NN}", model.Employee)));
        rptParams.Add(new ReportParameter("EmployeePhoneNumber", string.Format("{0:(###) ###-####}", Convert.ToInt64(model.Employee.PhoneNumber))));
        rptParams.Add(new ReportParameter("HrchyShortDesc", model.Employee.HrchyShortDesc));
        rptParams.Add(new ReportParameter("RequestDate", model.RequestDate.ToShortDateString()));
        rptParams.Add(new ReportParameter("RequestRequested", model.RequestRequestType));
        rptParams.Add(new ReportParameter("ReasonForRequest", model.RequestRequestReason));
        rptParams.Add(new ReportParameter("LogNumber", model.CaseId));

        if (!string.IsNullOrWhiteSpace(model.TimeSensitiveReason)) rptParams.Add(new ReportParameter("TimeSensitiveReason", model.TimeSensitiveReason));

        var lastAction = model.LastActionOfType(WorkflowStateActionType.EmployeeConfirmation);
        if (lastAction != null)
        {
            rptParams.Add(new ReportParameter("TodaysDate", lastAction.ActionDate.ToShortDateString()));
            rptParams.Add(new ReportParameter("EmpConfirmed", "true"));
        }
        else rptParams.Add(new ReportParameter("TodaysDate", DateTime.Now.ToShortDateString()));
        //end comment
        return rptParams;
    }

Through a lot of commenting in and out and pushes to our server, I've deduced the following:

  1. From what I can tell, the error occurs on calling GetReport1Parameters. In the code above, I included a start and end comment - I've commented out everything in between, leaving only the list initialization and return statement (of an empty list) and still received the error.
  2. I've commented out the call to GetReport1Parameters and returned a nonsensical byte array and didn't receive an Exception.
  3. All functionality works fine on localhost and when I step through the functions, all of the variables seem to appear normal.

Things I've tried to do to remedy the situation: 1. Removed connection strings from the app.config, so that the application has to go to the web.config to get the correct strings (even though they were the same). 2. Commented in and out different sections of code to determine the problem area. 3. Tried calling the GetReport1Parameters method and returning null, leading to a null reference exception. 4. Tried calling the GetReport1Parameters with an empty parameter list, leading to the error mentioned above. 5. Tried running the report with no parameters (not even a blank list), got a ReportProcessingException for missing params.

Some additional information:

  • We use a service account for the application using impersonate identity in the web.config. That line is commented out on localhost, but is running on IIS.
  • All of other database interaction works correctly.
  • All of our database interaction is done using LINQ to SQL - model is an object based off of a database table, with some additional information that is calculated dynamically.

My desired outcome is that both the autogenerated documents and the preview/print documents both work. I have a feeling that this may be something simple that I'm overlooking, but I've already spent several hours today trying to fix this.

I can't think of any other pertinent information, but if you have questions I'll be more than happy to answer them.

Edit: Additional attempts to find solution:

  1. Tried setting LINQ Deferred Loading equal to false. This caused more problems than it solved.

  2. Implemented IReportServerCredentials and assigned the ReportViewer's ServerReport.ReportServerCredentials with the correct database credentials.

  3. Assigned all pertinent report parameters to a Dictionary, and then called .ToString() on every object to ensure that it is pulled from the database. Then assigned those strings from the dictionary to the report parameters, so that ReportViewer should be receiving the data from the string pool, as opposed to pulling it from the database.

Community
  • 1
  • 1
floppsb
  • 696
  • 2
  • 9
  • 15
  • have you attached a debugger and looked at rptParams right before they're returned? – Chris Jun 26 '13 at 02:06
  • I haven't tried debugging remotely, but when I debug on localhost, all of the parameters seem to be returning correctly. As I mention in the answer above, the object passed into the method creating report params is the same on both the print/preview option and the autogeneration option. – floppsb Jun 26 '13 at 13:13
  • And does this fail on localhost or only when it's been deployed? – Chris Jun 26 '13 at 13:23
  • Only when it has been deployed. The functionality works appropriately on localhost - this could be due to the fact that the application runs with my privileges on localhost. – floppsb Jun 26 '13 at 17:33

1 Answers1

0

Even though you are using an ObjectDataSource to pass data to your report, Report Viewer will still invoke the Select method, which in turn could cause database access to occur. So even though it may seem that the login is unnecessary, you would need to dig into the data access methods you supplied with your ObjectDataSource to know for sure.

The error you are getting is being caused by a bug in Report Viewer 2010 that is describe in the following Microsoft Connect article:

ReportViewer.LocalReport.Render and ReportViewer.LocalReport.SetParameters changes ImpersonationLevel to None

Although the article mentions this problem should be fixed in Service Pack 1, it does not appear to be the case. I have not verified if this problem is fixed in Report Viewer 2012.

I worked around the problem by changing my data access layer to compare the current identity against the one in my HttpContext and restore it if necessary using the following code snippet:

System.Security.Principal.IIdentity id = System.Web.HttpContext.Current.User.Identity
if (id.Name != System.Security.Principal.WindowsIdentity.GetCurrent().Name)
{
    context = (id as System.Security.Principal.WindowsIdentity).Impersonate()
}

I do this right before I connect to the database and undo it as soon as the connection is open.

I am not exactly thrilled with this workaround, mainly because now my data access layer is referencing the UI layer (System.Web).

madisonw
  • 836
  • 6
  • 7