惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

W
WeLiveSecurity
博客园 - 【当耐特】
Microsoft Azure Blog
Microsoft Azure Blog
WordPress大学
WordPress大学
Stack Overflow Blog
Stack Overflow Blog
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
IT之家
IT之家
Cloudbric
Cloudbric
The Register - Security
The Register - Security
小众软件
小众软件
PCI Perspectives
PCI Perspectives
G
Google Developers Blog
AI
AI
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
Google DeepMind News
Google DeepMind News
Google DeepMind News
Google DeepMind News
宝玉的分享
宝玉的分享
Recent Commits to openclaw:main
Recent Commits to openclaw:main
量子位
TaoSecurity Blog
TaoSecurity Blog
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
F
Full Disclosure
N
Netflix TechBlog - Medium
博客园_首页
Last Week in AI
Last Week in AI
A
Arctic Wolf
B
Blog RSS Feed
J
Java Code Geeks
C
Cybersecurity and Infrastructure Security Agency CISA
I
InfoQ
aimingoo的专栏
aimingoo的专栏
云风的 BLOG
云风的 BLOG
NISL@THU
NISL@THU
MyScale Blog
MyScale Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Jina AI
Jina AI
有赞技术团队
有赞技术团队
S
Schneier on Security
L
Lohrmann on Cybersecurity
P
Privacy & Cybersecurity Law Blog
T
Threat Research - Cisco Blogs
P
Palo Alto Networks Blog
S
Security @ Cisco Blogs
Security Archives - TechRepublic
Security Archives - TechRepublic
Security Latest
Security Latest
Vercel News
Vercel News
博客园 - 司徒正美
Webroot Blog
Webroot Blog
Hacker News: Ask HN
Hacker News: Ask HN
A
About on SuperTechFans

博客园 - 灵感之源

.NET的前世今生与将来 技术人生的职场众生相 - 十多年的经验与心得 爆栈之旅 - 从接触到成为经理,从中国到澳洲 - 我这10年来的开发历程 我的软件开发生涯 (10年开发经验总结和爆栈人生) 我的回忆录-青年 爆栈三部曲:数据库开发大系技术栈 (300多技术点) Web前端开发大系概览 (前端开发技术栈) .NET技术大系概览 (迄今为止最全的.NET技术栈) 澳洲生活宝典 (兼我的2013总结) 某连锁酒店泄露数据的分析 我的回忆录 WaterfallTree(瀑布树) 详细技术分析系列 C#开源磁盘/内存缓存引擎 基于STSdb和fastJson的磁盘/内存缓存 怎样记住Integer的最大值(有趣的思维和搞笑的回答) STSdb,最强纯C#开源NoSQL和虚拟文件系统 4.0 RC2 支持C/S架构 STSdb,最强纯C#开源NoSQL和虚拟文件系统 C#写的NoSQL开源项目/系统(系列) 老调重弹:年龄大了,码农何去何从
在ASP.NET MVC 无需Web Form和Report Viewer 预览SSRS报表解决方案
灵感之源 · 2013-06-07 · via 博客园 - 灵感之源

2013-06-07 20:46  灵感之源  阅读(9955)  评论()    收藏  举报

环境

ASP.NET MVC 4.0 + SQL Server Reporting Services

需求

在保存报表为文件(如PDF)之前,可以预览报表(支持图片)。

分析

网络上的解决方案,都是告诉你用最原始的办法:结合ASP.NET Web Form+Report Viewer控件。因为SQL Server Reporting Services (SSRS) 只有Web Form的Report Viewer控件,没对ASP.NET MVC进行特别支持。

我们不能直接在ASP.NET MVC用Report Viewer是因为Report Viewer依赖View State,而View State正是MVC要解决的问题。。。

因为不满意这种整合Web Form的解决方案,今晚思考了一下,既然SSRS支持渲染结果为MHTML(MIME HTML),那么,如果我们解决浏览器不显示MHTML内容(需要另存再本地打开),就可以实现预览的办法:先渲染成MHTML,再解释MIME,再把图片附件转成data嵌入。

步骤

1. 设计页面

因为预览结果是完整的HTML,我在报表页面嵌入了一个iframe。。。。。。src指向一个Web API,该Web API会返回渲染后的html内容(包括图片)。

2. 下载文件

添加一个Web API 函数,用以渲染MHTML并返回text/html结果。为了简化操作,我把报表参数拼接起来,键值用“|”分隔,参数用“`”分隔。实际使用请根据自己的情况修改。。。

        public HttpResponseMessage DownloadReportContent(string ReportFileName, string Parameters)
        {
            var parameters = new Dictionary<string, string>();
            Parameters.Split('`').ForEach(p =>
            {
                var parts = p.Split('|');
                parameters.Add(parts[0], parts[1]);
            });

            byte[] reportContent;
            string fileExtension, mimeType;
            ReportGenerator.GenerateReport("ReportServer", "ReportServerExecutionURL", "ReportServerUserName", "ReportServerPassword", "ReportServerDomain", ReportFileName, ExportFormat.MHTML, parameters, string.Empty, out reportContent, out fileExtension, out mimeType);
            reportContent = ReportConvertor.Convert(reportContent);

            var resultStream = new System.IO.MemoryStream(reportContent);
            resultStream.Position = 0;
            var response = new HttpResponseMessage();
            response.StatusCode = HttpStatusCode.OK;
            response.Content = new StreamContent(resultStream);
            return response;
        }

3. 渲染报表为MHTML

下面的方法可以用作渲染其它格式,如最常见的PDF

using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Principal;
//using YOUR_IMPORTED_WEB_REFERENCE_FOR_SSRS_EXECUTION_SERVICE_HERE

namespace Org.SSRSPreview
{
    /// <summary>
    /// Export Formats
    /// </summary>
    public enum ExportFormat
    {
        /// <summary>XML</summary>
        XML,
        /// <summary>Comma Delimitted File
        CSV,
        /// <summary>TIFF image</summary>
        Image,
        /// <summary>PDF</summary>
        PDF,
        /// <summary>HTML (Web Archive)</summary>
        MHTML,
        /// <summary>HTML 4.0</summary>
        HTML4,
        /// <summary>HTML 3.2</summary>
        HTML32,
        /// <summary>Excel</summary>
        Excel,
        /// <summary>Word</summary>
        Word
    }

    public class ReportGenerator
    {
        /// <summary>
        /// Gets the string export format of the specified enum.
        /// </summary>
        /// <param name="Format">export format enum</param>
        /// <returns>enum equivalent string export format</returns>
        private string GetExportFormatString(ExportFormat Format)
        {
            switch (Format)
            {
                case ExportFormat.XML: return "XML";
                case ExportFormat.CSV: return "CSV";
                case ExportFormat.Image: return "IMAGE";
                case ExportFormat.PDF: return "PDF";
                case ExportFormat.MHTML: return "MHTML";
                case ExportFormat.HTML4: return "HTML4.0";
                case ExportFormat.HTML32: return "HTML3.2";
                case ExportFormat.Excel: return "EXCEL";
                case ExportFormat.Word: return "WORD";
                default:
                    return "PDF";
            }
        }

        /// <summary>
        /// generate a report
        /// </summary>
        /// <param name="ReportServer">report server</param>
        /// <param name="ReportServerExecutionURL">rendering execution url of report server</param>
        /// <param name="ReportServerUserName">user name to access report server</param>
        /// <param name="ReportServerPassword">password of the user name</param>
        /// <param name="ReportServerDomain">domain of the report server (for active directory)</param>
        /// <param name="ReportServerSubDir">folder of the report in the report server</param>
        /// <param name="FileFormat">output of the report</param>
        /// <param name="Parameters">parameters for the report</param>
        /// <param name="ReportContent">rendered report content</param>
        /// <param name="FileExtension">file extension of the report</param>
        /// <param name="MimeType">mime type of the generated file</param>
        public void GenerateReport(string ReportServer,
            string ReportServerExecutionURL,
            string ReportServerUserName,
            string ReportServerPassword,
            string ReportServerDomain,
            string ReportServerSubDir,
            string ReportFileName,
            ExportFormat FileFormat,
            Dictionary<string, string> Parameters,
            string DeviceInfo,
            out byte[] ReportContent,
            out string FileExtension,
            out string MimeType)
        {

            using (var reportExecutionService = new ReportExecutionService.ReportExecutionServiceSoapClient("ReportExecutionServiceSoap", ReportServerExecutionURL))
            {
                reportExecutionService.ClientCredentials.Windows.AllowedImpersonationLevel = TokenImpersonationLevel.Impersonation;
                reportExecutionService.ClientCredentials.UserName.UserName = ReportServerUserName;
                reportExecutionService.ClientCredentials.UserName.Password = ReportServerPassword;
                reportExecutionService.ClientCredentials.Windows.ClientCredential = new System.Net.NetworkCredential(ReportServerUserName, ReportServerPassword, ReportServerDomain);

                var parameters = Parameters.Select(p => new ParameterValue { Name = p.Key, Value = p.Value }).ToList();
                // Init Report to execute
                ServerInfoHeader serverInfoHeader;
                ExecutionInfo executionInfo;
                ExecutionHeader executionHeader = reportExecutionService.LoadReport(null, ReportFileName, null, out serverInfoHeader, out executionInfo);

                // Attach Report Parameters
                reportExecutionService.SetExecutionParameters(executionHeader, null, parameters.ToArray(), null, out executionInfo);

                // Render
                string encoding;
                Warning[] warnings;
                string[] streamIds;
                reportExecutionService.Render(executionHeader, null, GetExportFormatString(FileFormat), DeviceInfo, out ReportContent, out FileExtension, out MimeType, out encoding, out warnings, out streamIds);

            }
        }
    }
}

4. 解释MIME,转换图片附件为data嵌入式

这是整个解决方案的核心思想。SSRS会把修改图片的标识,譬如<img src="cid:SOME_ID_HERE">,我们要做的就是取得图片附件的原始base64内容,构造成如下的格式:

data:image/png;base64,[ENCODED_DATA_HERE]

MIME解释用到了OpenPop.NET (通过Pop3协议收取邮件),这是我10年前参与开发的第一个开源项目,不过几年前把项目移交给别的开发人员,我当年写的代码部分都荡然无存了。。。在这里吐槽一下接手的开发人员,你太不给偶面子了。。。 :-)

为了优化性能,我改动了一下MessagePart.cs文件。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using OpenPop.Mime;

namespace Org.SSRSPreview
{
    public class ReportConvertor
    {
        public static byte[] Convert(byte[] Content)
        {
            var message = new Message(Content);
            var body = message.FindFirstHtmlVersion();
            var bodyText = body.BodyEncoding.GetString(body.Body, 0, body.Body.Length);
            message.FindAllAttachments().ForEach(a =>
                {
                    if (!a.IsText)
                    {
                        var key = "Content-Location";
                        var url = a.Headers.UnknownHeaders[key];
                        if (string.IsNullOrEmpty(url) && a.ContentDisposition != null)
                            url = a.ContentDisposition.Inline ? "cid:" + a.FileName : a.FileName;
                        var attachment = a.Body;

                        var embedImage = new StringBuilder("data:");
                        embedImage.Append(a.ContentType.MediaType + ";");
                        embedImage.Append(a.ContentTransferEncoding.ToString().ToLowerInvariant() + ",");
                        embedImage.Append(a.BodyEncoding.GetString(a.RawBody));
                        bodyText = bodyText.Replace(url, embedImage.ToString());
                    }
                });
            return body.BodyEncoding.GetBytes(bodyText);
        }
    }
}

限制

没有Report Viewer那些控制,譬如缩放,分页等。。。。我不需要啊。。。

代码

点击这里下载。代码不是完整的解决方案,只包括关键的代码,如果你打开运行,只会解释一个例子MHTML,因为相关的函数和类都在这里贴了。

总结

至此,大功告成。SQL Server团队对ASP.NET MVC不重视啊,这么多年还不考虑一下支持ASP.NET MVC。