SQL Report Server & ASP.MVC

Standard

I have used MS SQL Report server a lot and one of the most common scenarios is to render a report and have it download a PDF / Excel etc. I have not seen many posts regarding how to do this end-to-end using MVC. So I thought I share my method.

The demo project will be split into 3 parts.

  1. The Report Server Execution Service.
    2. The Report Server Repository Layer.
    3. The Report Server Service Layer.
    4. The User Interface.
    You’ll need
  1. Visual Studio 2008
  2. ASP.MVC RTM
  3. SQL Server 2005/2008

You’ll also have installed and working the MSSQL Report Server and a test report.Create a New Blank Solution SQL Server Report Solution

imageAnd then add 2 Class Libraries and a MVC Web Site – You can choose whether or not to add the MVC Test projects.

  • Demos.SqlReports.Data
  • Demos.SqlReports.Services
  • Demos.SqlReports.Web

I’ll also add a test project for the Data and Service Layer, I’m just using the default unit test framework that comes with VS 2008. You can of course use something else (nUnit, xUnit etc).

  • Demos.SqlReports.Tests
    You should have something like this now.

image 

We need to add a Web Reference to the Report Server at the data layer. My Report Server Execution Service URL is

http://localhost/ReportServer$SQLSERVER_001/ReportExecution2005.asmx

Yours may differ so check it in IIS.

Also, I’m adding a web reference – not a service reference. You can do this though the add service reference dialog box by clicking Advanced | Add Web Reference.

image

I always postfix Proxy on the end of the Web Reference Name for clarity.

Once added rename Class 1  to ReportServerRepository.cs and also create an Interface Class IReportsRepository.cs

Move these files into a sub folder Data so they are organised.

image

Let’s add some code.

using System;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;
using System.Web.Services.Protocols;
using Demos.SqlReports.Data.ReportExecution2005Proxy;

namespace Demos.SqlReports.Data
{

    ///
    /// Contains the response data from a RenderReport request
    /// to the report server.
    ///
    public sealed class RenderReportResponse
    {
        public byte[] ReportData { get; set; }
        public ExecutionInfo ExecutionInfo { get; set; }
        public ReportExecution2005Proxy.Warning[] Warnings { get; set; }
        public string SessionId { get; set; }
        public string ContentType { get; set; }
        public string HistoryID { get; set; }
        public string DevInfo { get; set; }
        public string Encoding { get; set; }
        public string MimeType { get; set; }
        public string Extension { get; set; }
        public string[] StreamIds { get; set; }
    }

    ///
    /// Report Server Repository
    ///
    public sealed class ReportServerRepository : IReportsRepository
    {

        #region Constructors

        ///
        /// Initializes a new instance of the  class.
        ///
        public ReportServerRepository()
        {
            this.Initialise();
        }

        #endregion

        #region Declarations

        private NetworkCredential netCredential;
        private ReportExecutionService webServiceProxy;

        #endregion

        #region Private Members

        ///
        /// Initialises this instance and set the credentials for
        /// the report server.
        ///
        private void Initialise()
        {

            webServiceProxy = new ReportExecutionService();
            netCredential = new NetworkCredential("MyUserName", "MyPassword");
            Uri uri = new Uri(webServiceProxy.Url);
            ICredentials credentials = netCredential.GetCredential(uri, "Basic");
            webServiceProxy.Credentials = credentials;
            webServiceProxy.PreAuthenticate = true;

        }

        ///
        /// Gets the type of the content.
        ///
        ///
The file extension.
        ///
        private static string GetContentType(string fileExtension)
        {
            switch (fileExtension.ToLower())
            {
                case ".pdf": return "application/pdf";
                case ".tif": return "image/tiff";
                case ".xls": return "application/ms-excel";
                case ".xml": return "text/xml";
                default: return "text/plain";
            }

        }

        ///
        /// Renders the report.
        ///
        ///
The parameters.
        ///
The report path.
        ///
The format.
        ///
        private static RenderReportResponse RenderReport(ReportExecution2005Proxy.ParameterValue[] parameters, string reportPath, string format)
        {

            using (var rs = new ReportExecutionService())
            {

                try
                {

                    rs.Credentials = CredentialCache.DefaultCredentials;

                    // Render arguments
                    //
                    const string _historyID = null;
                    const string _devInfo = @"False";
                    string _encoding;
                    string _mimeType;
                    string _extension;
                    ReportExecution2005Proxy.Warning[] _warnings;
                    string[] _streamIDs;

                    ExecutionInfo _execInfo = new ExecutionInfo();
                    ExecutionHeader execHeader = new ExecutionHeader();

                    rs.ExecutionHeaderValue = execHeader;
                    _execInfo = rs.LoadReport(reportPath, _historyID);

                    rs.SetExecutionParameters(parameters, "en-us");
                    String SessionId = rs.ExecutionHeaderValue.ExecutionID;

                    var _reportData = rs.Render(format, _devInfo, out _extension, out _encoding, out _mimeType, out _warnings, out _streamIDs);

                    var _response = new RenderReportResponse
                                        {
                                            ReportData = _reportData,
                                            ExecutionInfo = _execInfo,
                                            Warnings = _warnings,
                                            SessionId = SessionId,
                                            ContentType = GetContentType(format),
                                            HistoryID = _historyID,
                                            DevInfo = _devInfo,
                                            Encoding = _encoding,
                                            MimeType = _mimeType,
                                            Extension = _extension,
                                            StreamIds = _streamIDs
                                        };
                    return _response;

                }
                catch (SoapException e)
                {

                    // handle exception
                    //
                    throw new ApplicationException(
                        string.Format("Error occurred rendering the report : {0}", reportPath), e);

                }

            }

        }

        #endregion

        #region IReportServerRepository Members

        ///
        /// Renders the Demo Report.
        ///
        ///
The reportParameter.
        ///
        public RenderReportResponse RenderDemoReport(string reportParameter)
        {

            var _params = new[] { new ReportExecution2005Proxy.ParameterValue { Name = "p_ReportParameter", Value = reportParameter } };

            return RenderReport(_params, "My Demo Report", "PDF");
        }

        #endregion

    }

}

And add some to the interface class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Demos.SqlReports.Data
{
    interface IReportsRepository
    {

        ///
        /// Renders the Demo Report.
        ///
        ///
The reportParameter.
        ///
        RenderReportResponse RenderDemoReport(string reportParameter);

    }
}

The reason for returning a class of RenderReportResponse back is that we can have the whole response delivered to the UI and make use of it if required.

Ok let’s add some code to the service class.

Rename Class 1  to ReportService.cs and also create an Interface Class IReportService.cs

The purpose of the interfaces and the service class is to abstract away the implementation of the repository and to decouple as much as possible.

The UI should only ever call the service class. You can then swap out repositories easily when testing code or using an alternative repository. It also lends its self well to a plug-in architecture or if you plan to implement IoC / Dependency Injection later on.

Code for the Service Class

(make sure you have added a project reference for Demos.SqlReports.Data)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Demos.SqlReports.Data;

namespace Demos.SqlReports.Services
{
    public partial class ReportService : IReportService
    {

        readonly IReportsRepository _repository;

        ///
        /// Creates a ReportService based on the passed-in repository
        ///
        ///
An IReportsRepository
        public ReportService(IReportsRepository repository)
        {
            _repository = repository;

            if (_repository == null)
                throw new InvalidOperationException("Repository cannot be null");

        }

        ///
        /// Creates a ReportService based on the default repository
        ///
        public ReportService()
        {
            _repository = new ReportServerRepository();
        }

        #region IReportService Members

        public RenderReportResponse RenderDemoReport(string reportParameter)
        {
            return _repository.RenderDemoReport(reportParameter);
        }

        #endregion
    }
}

 

Code for the Service Interface Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Demos.SqlReports.Data;

namespace Demos.SqlReports.Services
{
    public interface IReportService
    {

        ///
        /// Renders the Demo Report.
        ///
        ///
The reportParameter.
        ///
        RenderReportResponse RenderDemoReport(string reportParameter);

    }

}

 

Move these files into a sub directory i.e. Reports. Build the project make sure there are no errors.

 

Implementing the UI

 

In the MVC Project we are going to use the default HomeController for our demo. Add a project reference for the Data and Service Layers.

The first thing we need to do is create a class for the ActionResultHelper.cs. Add the class file in a subdirectory Helpers (keep the class namespace flat i.e. Demos.SqlReports.Web remove any namespace additions if you create the sub directory first.

Add the code.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Demos.SqlReports.Services;
using Demos.SqlReports.Data;

namespace Demos.SqlReports.Web
{
    public class ReportServerResult : ActionResult
    {
        public RenderReportResponse ReportData { get; set; }
        public string FileName { get; set; }

        public override void ExecuteResult(ControllerContext context)
        {

            context.HttpContext.Response.Clear();
            context.HttpContext.Response.ClearHeaders();
            context.HttpContext.Response.ContentType = ReportData.ContentType;
            context.HttpContext.Response.AddHeader("content-disposition", "attachment; filename="" + FileName + """);
            context.HttpContext.Response.BinaryWrite(ReportData.ReportData);
            context.HttpContext.Response.Flush();

        }
    }
}

Let me explain a little what’s going on here. By inheriting the ActionResult base class we can pass the report servers response directly to the response stream of the Controller Action. This is a neat way because it also allows you to Unit Test the response and you can reuse it anywhere you render a report.

Let’s create an Action in Home Controller.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Demos.SqlReports.Services;
using Demos.SqlReports.Data;

namespace Demos.SqlReports.Web.Controllers
{
    [HandleError]
    public class HomeController : Controller
    {

        private readonly IReportService _reportService;

        public HomeController()
        {
            _reportService = new ReportService(new ReportServerRepository());
        }

        public ActionResult Index()
        {
            ViewData["Message"] = "Welcome to ASP.NET MVC!";

            return View();
        }

        public ActionResult About()
        {
            return View();
        }

        ///
        /// Gets the demo report.
        ///
        ///
The report parameter.
        ///
        public ActionResult GetDemoReport(string reportparameter)
        {
            // call the report service and render the report
            //

            var _response = _reportService.RenderDemoReport(reportparameter);

            // returnn the result using a custom action result class.
            //
            return new ReportServerResult { FileName = "Demo Report.pdf", ReportData = _response };

        }

    }
}

As you can see the code is nice and concise. The Response Data from service layer is passed straight into the ReportServerResult  which outputs the report to the response stream.

Add an ActionLink to the Index.aspx file

< %@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

    Home Page

< %= Html.Encode(ViewData["Message"]) %>

To learn more about ASP.NET MVC visit http://asp.net/mvc. < %= Html.ActionLink("Render Report","GetDemoReport", "HomeController",new{ reportparameter = "ParameterValue"}) %>

 

That’s it. normally I’d build the unit tests before jumping into the code but this isn’t a session on TDD. I’ll leave that bit to you. Next I’d like to get the Report Viewer control working with ASP.MVC. You can download the test solution here.

Download Solution – SQL Server Report Solution.zip

8 thoughts on “SQL Report Server & ASP.MVC

  1. Michel Murphy

    Hi, I am looking for an SSSMS/MVC sample such as this. I downloaded the solution, but you are missing quite a few files. Please repost or advise. THANKS, Murph

  2. Ray

    Hi, this is really cool! I have tried several times and it works finally.
    But i really want to know how to get the report viewer control to work with MVC.
    Please kindly advise.

  3. Gema

    Could you already make the Report Viewer control work with ASP.MVC?? If so, please let me know how

Comments are closed.