11

I'm working on an MVC4 application that needs to render a remote report from SSRS using the ReportViewer. With help from this forum, I've managed to get the page to render under MVC, but callbacks won't work (loads the initial page). Exporting the report works fine (and gives all pages). When I inspect the page, I noticed the following error after changing pages:

Uncaught Sys.WebForms.PageRequestManagerParserErrorException: Sys.WebForms.PageRequestManagerParserErrorException: The message received from the server could not be parsed.

I found this article on combining MVC and Web Forms, but it appears outdated since there are no more Master layout pages. This is related to but not a duplicate of How can I use a reportviewer control in an asp.net mvc 3 razor view? since that article is only for local reports. I've tried changing AsyncRendering to true and false. When true, it doesn't load at all. Any suggestions would be greatly appreciated.

Update: AsyncRendering behavior appears to have changed between previous versions of Visual Studio.

Community
  • 1
  • 1
Elsimer
  • 1,878
  • 3
  • 18
  • 33
  • Thinking your solution may be found in this possible duplicate: [How can I use a reportviewer control in an asp.net mvc 3 razor view?](http://stackoverflow.com/questions/6144513/how-can-i-use-a-reportviewer-control-in-an-asp-net-mvc-3-razor-view) – Jeroen Mar 15 '13 at 10:27
  • not a duplicate - other post deals only with local reports. I've revised the question appropriately – Elsimer Mar 18 '13 at 20:23

3 Answers3

6

In the end, I ended up having to ditch both my original answer and the callbacks criteria due to the unacceptable security risks. In my case, I wrote controller code rendering the report as HTML to a byte array and from there to a FileContentResult that MVC was kind enough to render as a static HTML page. Exporting as PDF, Excel, or any other options will eventually be implemented in a similar method by changing the Render parameter from HTML4.0 to whatever is appropriate (PDF,XLS) and the MIME type. This approach works with SQL Server 2008R2 and beyond. I haven't tried it with previous versions of SQL Server.

[OutputCache(Duration = 120, VaryByParam = "id")]
public ActionResult ExportHTML(int id)
{
    // we need to add code to check the user's access to the preliminary report.
    // Also need to consolidate code between ExportHTML and ExportPDF.
    var userid = <userid>;
    var password = <password>;
    var domain = <domain>;
    IReportServerCredentials irsc = new myApp.Models.CustomReportCredentials(userid,
        password, domain);
    var parametersCollection = new List<ReportParameter>();
    parametersCollection.Add(new ReportParameter("Snapshot", id.ToString(), false));
    ReportViewer rv = new Microsoft.Reporting.WebForms.ReportViewer();
    rv.ProcessingMode = ProcessingMode.Remote;
    rv.ServerReport.ReportServerCredentials = irsc;
    rv.ServerReport.ReportPath = <reportpath>;
    rv.ServerReport.ReportServerUrl = new Uri("http://localhost/ReportServer");
    rv.ServerReport.SetParameters(parametersCollection);

    rv.ServerReport.Refresh();
    byte[] streamBytes = null;
    string mimeType = "";
    string encoding = "";
    string filenameExtension = "";
    string[] streamids = null;
    Warning[] warnings = null;

    streamBytes = rv.ServerReport.Render("HTML4.0", null, out mimeType, out encoding,
                                         out filenameExtension, out stream ids,
                                         out warnings);
    var HTMLReport = File(streamBytes, "text/html");
    return HTMLReport;
}
Diego Mijelshon
  • 52,548
  • 16
  • 116
  • 154
Elsimer
  • 1,878
  • 3
  • 18
  • 33
0

I'm still hoping for a better answer but in the meantime, my solution appears to satisfy the criteria. It makes use of the Kendo Web Window (so I suppose you could theoretically write your own using jQuery). I haven't modified it yet to pass parameters, but it's a start. I also haven't secured the redirect action, so it's currently possible for a user to view the source, grab the URL from the jQuery load, go to that address and get the underlying report URL from there. I'm going to look into marking it as a ChildActionOnly or some other means of insuring that the action is only available to my window. I also found that I can render the report to HTML4.0, stuff that in a FileResult and load the content that way as well - but then the report is static HTML.

View:

    @(Html.Kendo().Grid(Model)
    .Name("IndexGrid")
    .Columns(col => 
    {
        col.Bound(c => c.SchoolYear);
        col.Bound(c => c.SubmissionTypeDesc);
        col.Bound(c => c.EntityDesc);
        col.Bound(c => c.SubmissionDate);
        col.Bound(c => c.UserName);
        col.Bound(c => c.Certified);
        col.Command(c => 
            {
                c.Custom("Edit")
                    .Text("View")
                    .Action("Edit", "Draft");
                c.Custom("Preview")
                    .Click("windowOpen");
                c.Custom("Certify")
                    .Action("Certify", "Draft");
                c.Custom("Download")
                    .Action("DumpExcel", "Draft");
            }
            ).Title("<b>Actions</b>")
            .HtmlAttributes(new { style = "width:200px;" });
    })
    .DataSource(ds => ds.Server()
        .Model(model => model.Id(pk => pk.snapshot_id))
        )
    .Sortable(sort => sort.Enabled(true).SortMode(GridSortMode.MultipleColumn).AllowUnsort(true))
    .Reorderable(reorder => reorder.Columns(true))
    .Groupable(group => group.Enabled(true))
    )
</article>

@(Html.Kendo().Window()
      .Name("window") //The name of the window is mandatory. It specifies the "id" attribute of the widget.
      .Title("Preliminary Report") //set the title of the window
      .LoadContentFrom("Redir", "Reports") //define the Action and Controller name
      .Visible(false)
      .Iframe(true)
      .Resizable()
      .Width(750)
      .Height(500)
      .Scrollable(false)
      .Draggable()
          .Actions(a =>
          {
              a.Refresh();
              a.Minimize();
              a.Maximize();
              a.Close();
          })

)
<script>
    function windowOpen(e) {
        e.preventDefault();
        var window = $("#window").data("kendoWindow");
        window.open();
    }
</script>

ReportController snippet:

public ActionResult Redir()
{
    return RedirectPermanent("../ASPReports/ReportForm.aspx");
}

ReportForm.aspx:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="~/ASPReports/ReportForm.aspx.cs" Inherits="MyApp.Reports.ReportForm"%>

<%@ Register assembly="Microsoft.ReportViewer.WebForms, Version=11.0.0.0, Culture=neutral, PublicKeyToken=89845dcd8080cc91" namespace="Microsoft.Reporting.WebForms" tagprefix="rsweb" %>

<!DOCTYPE html>

<html xmlns="http://www.w3.org/1999/xhtml">
<head id="Head1" runat="server">
    <title></title>
</head>
<body>
    <form id="reportForm" runat="server">
    <asp:ScriptManager ID="ScriptManager1" runat="server">
    </asp:ScriptManager>
    <div>
        <rsweb:ReportViewer ID="mainReportViewer" runat="server"  SizeToReportContent="true">
        </rsweb:ReportViewer>
    </div>
    </form>
</body>
</html>

ReportForm.aspx.cs (code-behind):

protected void Page_Load(object sender, EventArgs e)
{
    if (!IsPostBack)
    {
        // credentials - could pull from config
        var userid = ""; 
        var password = "";
        var domain = "";

        IReportServerCredentials irsc = new CustomReportCredentials(userid, password, domain);
        mainReportViewer.ServerReport.ReportServerCredentials = irsc;

        //mainReportViewer.ServerReport.ReportServerUrl =
        //    new Uri(ConfigurationManager.AppSettings["ReportServerUrl"]);
        mainReportViewer.ServerReport.ReportServerUrl =
            new Uri("http://localhost/ReportServer");
        mainReportViewer.ServerReport.ReportPath = "Path";


        mainReportViewer.ProcessingMode = ProcessingMode.Remote;
        mainReportViewer.ShowParameterPrompts = false;
        mainReportViewer.ShowRefreshButton = false;
        mainReportViewer.ShowWaitControlCancelLink = false;
        mainReportViewer.ShowBackButton = false;
        mainReportViewer.ShowCredentialPrompts = false;
        var parametersCollection = new List<ReportParameter>();
        //parametersCollection.Add(new ReportParameter("Snapshot", "##", false));
        mainReportViewer.ServerReport.SetParameters(parametersCollection);
        mainReportViewer.ServerReport.Refresh();
    }
}
Elsimer
  • 1,878
  • 3
  • 18
  • 33
-1

Just use IFRAME. Create another web site or virtual directory, create application using Web Forms and then show his report viewer pages inside IFRAME on MVC application. You can set report parameters using query string. I have many times placed the report viewer into different systems using this way.

Boris Gappov
  • 2,483
  • 18
  • 23
  • the problem with this approach is that the URL in the iframe is visible by viewing the source, which is a security risk. It would be like allowing anybody to see everybody's receipt with credit card information just by changing the URL – Elsimer Mar 20 '13 at 14:53
  • Do you using authentication? When both applications are in same domain, then no problem. But when in different - to, look here for example http://stackoverflow.com/questions/2056686/asp-net-forms-authentication-and-multiple-domains – Boris Gappov Mar 20 '13 at 15:06
  • SSRS is set to use windows authentication (network credentials). My application uses forms authentication and SimpleMembership to determine which reports a user should have access to. However, if I use an iframe and a redirect then that completely bypasses the application security. That means I have to implement security on the remote server, which is outside the scope of 'Just use an IFRAME'. – Elsimer Mar 26 '13 at 19:19
  • So what the problem? http://msdn.microsoft.com/en-us/library/cc281383.aspx; and http://msdn.microsoft.com/en-us/library/aa902691(v=sql.80).aspx – Boris Gappov Mar 27 '13 at 05:39