报表服务 2008 R2 - 传递扩展用户控件






4.95/5 (6投票s)
本文介绍如何创建报表服务的自定义交付扩展,使用用户控件(.ascx)而不是服务器控件。
引言
创建报表服务的自定义交付扩展非常直接,互联网上有很多示例可供参考(例如 CodePlex 上的打印交付、FTP 交付)。但是,如果您想在订阅创建期间获取用户输入(例如渲染格式、位置等),所有这些示例都会创建一个自定义服务器控件。由于创建服务器控件既耗时又繁琐,因此本文将介绍如何改用用户控件(*.ascx*)。用户控件相对容易创建、调试、测试和格式化。
背景
对于那些想了解什么是交付扩展的人,请参阅以下 MSDN 文章:
基本上,要创建交付扩展,您需要实现三个接口:ISubscriptionBaseUIUserControl
、IDeliveryExtension
和 IExtension
。如果要实现非 UI 扩展,则需要 IDeliveryExtension
和 IExtension
接口。但如果您想捕获一些用户信息,您还必须实现 ISubscriptionBaseUIUserControl
。除此之外,该类还应继承自 System.Web.UI.WebControls.WebControl
类。
Using the Code
附带的解决方案有两个项目:
- Extensions - 包含所有必需类文件和用户控件的主项目。我的自定义交付扩展将报表保存到物理文件位置,然后通过电子邮件发送渲染后的报表,之后再将相同的报表 FTP 到 FTP 服务器。随附的代码已注释掉电子邮件和 FTP 交付部分。如果您希望提供的报表服务具有所有必需的配置,例如 FTP 服务器、用户名、密码、电子邮件服务器、发件人和收件人电子邮件地址等,则可以取消注释。
- UI Test - 这是用于测试用户控件在普通 ASPX 页面中是否正常工作的测试项目。
两个项目的 .NET Framework 均为 **3.5。请勿使用 4.0。** SQL Server Reporting Services 2008 R2 不支持 4.0。第一个 DLL(Extensions)需要是强签名程序集。对程序集进行签名,然后从 *.snk* 文件中提取公钥和公共 Blob。要提取公钥,请打开 Visual Studio 命令提示符,然后使用 强命名工具(SN.exe)。
sn -p
[签名程序集时创建的 snk 文件的路径] [要创建的新 public.snk 文件的路径]
sn -t
[上面创建的 public snk 文件的路径]
复制公钥 Blob 并将其保存在文本文件中。稍后将使用它在报表服务上部署扩展。
部署交付扩展
我们只需要将一个 DLL(Extensions)部署到报表服务器。另一个 DLL 仅用于测试,请忽略它。
- 将 *Extensions.dll* 复制到 C:\Program Files\Microsoft SQL Server\MSRS10_50.MSSQLSERVER\Reporting Services\ReportServer\bin 和 C:\Program Files\Microsoft SQL Server\MSRS10_50.MSSQLSERVER\Reporting Services\ReportManager\Bin
- 将自定义用户控件 *ExtensionUserControl.ascx* 复制到 C:\Program Files\Microsoft SQL Server\MSRS10_50.MSSQLSERVER\Reporting Services\ReportManager\Pages 文件夹。
配置交付扩展
注意:在修改配置文件之前,请创建原始配置文件的副本,以便在需要时可以恢复。
打开 C:\Program Files\Microsoft SQL Server\MSRS10_50.MSSQLSERVER\Reporting Services\ReportServer\rsreportserver.config 中的 rsreportserver.config 文件,并在 **Extensions -> Delivery** 和 **Extension -> Delivery UI** 下添加新扩展。
<Extension Name="FTP Email FileShare" Type="Extensions.FtpDeliveryExtension,Extensions">
<MaxRetries>3</MaxRetries>
<SecondsBeforeRetry>900</SecondsBeforeRetry>
<Configuration>
<RSFTPEmailFileShareConfig>
<ExportPath>E:\ReportSubscription</ExportPath>
<FTPServer></FTPServer>
<FTPPort></FTPPort>
<FTPUserName></FTPUserName>
<FTPPassword></FTPPassword>
<FTPDefaultFolder>CCX_Report</FTPDefaultFolder>
<SMTPServer></SMTPServer>
<SMTPSendUsing></SMTPSendUsing>
<SMTPToAddress></SMTPToAddress>
</RSFTPEmailFileShareConfig>
</Configuration>
</Extension>
同时,在两个位置,提供您的 FTP 服务器、SMTP 服务器或 SMTP 发送方式以及 SMTP 收件人地址详细信息(如果您想使用 Mail 和 FTP 交付来测试交付扩展)。否则,请保持原样。
打开 C:\Program Files\Microsoft SQL Server\MSRS10_50.MSSQLSERVER\Reporting Services\ReportManager\rsmgrpolicy.config 中的 rsmgrpolicy.config 文件,以及 C:\Program Files\Microsoft SQL Server\MSRS10_50.MSSQLSERVER\Reporting Services\ReportServer\rssrvpolicy.config 中的 rsservpolicy.config 文件。
在末尾添加一个新的 Codegroup
元素,并提供以下详细信息,以便为您的程序集授予完全信任:
<CodeGroup
class="UnionCodeGroup"
version="1"
PermissionSetName="FullTrust"
Name="Custom_FTPDeliveryExtension_Strong_Name"
Description="This code group grants FTP Delivery full trust. ">
<IMembershipCondition
class="StrongNameMembershipCondition"
version="1"
PublicKeyBlob="0024000004800000940000000602000000240000525341310004000001000100253fe4877d545619e033
2231a9527fdf8fc023f1f52e5956ac8254cc29c91323db7b0c493dff2386879f6f081d3c8b836b916953a906d938629100712f37
595f081f1709c1946e3c7a2800303e963ece5228f3f134175fc5155b8bc4ff933dc167c75d7491e656109ee183d13da8c92f05c244b8647971593354c09d7c218fab"
/>
</CodeGroup>
这里,PublicKeyBlob
是上面使用 SN 工具提取的 public
密钥。
测试
创建新订阅时,我的自定义交付扩展看起来如下。
实现
自定义交付类定义
public class FtpDeliveryExtension : WebControl,
ISubscriptionBaseUIUserControl, IDeliveryExtension, IExtension
{
}
这里,我们的自定义交付类名为 FTPDeliveryExtension
,它实现了三个接口并继承自 WebControl
。
ISubscriptionBaseUIUserControl 和 WebControl 实现
FTPDeliveryExtension
继承自 WebControl
类。RenderUIControls
方法在控件初始化期间被调用,并从报表管理器 的 pages 文件夹加载 ExtensionUserControl.ascx。控件加载后,将其添加到 Controls
集合进行渲染。
public FtpDeliveryExtension()
{
base.Init += new EventHandler(FtpDeliveryExtension_Init);
}
void FtpDeliveryExtension_Init(object sender, EventArgs e)
{
RenderUIControls();
}
private void RenderUIControls()
{
userControl = (ExtensionUserControl)this.Page.LoadControl("ExtensionUserControl.ascx");
Controls.Add(userControl);
}
ISubscriptionBaseUIUserControl
接口的用户数据属性由报表服务器使用,以获取用户将在自定义表单中填写的信息。例如,在我们的例子中,文件夹路径和渲染格式是我们希望用户在创建订阅时提供的两项信息。UserData
属性在创建/编辑报表订阅期间获取/设置用户在报表服务器中提供的信息。
public Setting[] UserData
{
get
{
SubscriptionSettingsData subscriptionData = new SubscriptionSettingsData();
subscriptionData.WindowFolderLocation = userControl.FolderLocation;
subscriptionData.RenderFormat = userControl.SelectedRenderFormat;
subscriptionData.RenderExtension = userControl.RenderFormatExtension;
return subscriptionData.ToSettingArray();
}
set
{
SubscriptionSettingsData subscriptionData = new SubscriptionSettingsData();
subscriptionData.SetFromSettings(value);
}
}
这里创建了自定义的 SubscriptionSettingData
类,用于返回设置数组并保存设置值。用户控件的属性,如 FolderLocation
、SelectedRenderFormat
,包含了用户提交的输入。该类的实现包含在提供的代码中。
IExtension 接口实现
这非常简单。本地化名称是扩展的名称。在本例中是 **"FTP Email FileShare"**。SetConfiguration
方法从 RSReportServer.Config 文件读取上面创建的配置节(RSFTPEmailFileShareConfig
)。
#region IExtension Implementation and Common
public string LocalizedName
{
get { return _localizedName; }
}
public void SetConfiguration(string configuration)
{
try
{
XmlDocument doc = new XmlDocument();
Common.LoadXMLConfiguration(doc, configuration);
if (doc.DocumentElement.Name == "RSFTPEmailFileShareConfig")
{
foreach (XmlNode node in doc.DocumentElement.ChildNodes)
{
string str;
if (((str = node.Name) != null) && (str == "ExportPath"))
{
_exportPath = node.InnerText;
}
if (((str = node.Name) != null) && (str == "FTPServer"))
{
_ftpServer = node.InnerText;
}
if (((str = node.Name) != null) && (str == "FTPPort"))
{
_ftpPort = node.InnerText;
}
if (((str = node.Name) != null) && (str == "FTPUserName"))
{
_ftpUserName = node.InnerText;
}
if (((str = node.Name) != null) && (str == "FTPPassword"))
{
_ftpPassword = node.InnerText;
}
if (((str = node.Name) != null) && (str == "FTPDefaultFolder"))
{
_ftpDefaultFolder = node.InnerText;
}
if (((str = node.Name) != null) && (str == "SMTPServer"))
{
_smtpServer = node.InnerText;
}
if (((str = node.Name) != null) && (str == "SMTPSendUsing"))
{
_smtpSendUsing = node.InnerText;
}
if (((str = node.Name) != null) && (str == "SMTPToAddress"))
{
_smtpToAddress = node.InnerText;
}
}
}
}
catch (Exception exception)
{
throw new ApplicationException("Error reading RS Configuration", exception);
}
finally
{
}
}
#endregion
IDeliveryExtension 接口实现
有多个属性和方法,但最重要的是 Deliver
方法。此方法根据通知的内容实际将报表交付给用户。
public bool Deliver(Notification notification)
{
notification.Retry = false;
notification.Status = "Processing...";
try
{
Setting[] userData = notification.UserData;
SubscriptionSettingsData data = new SubscriptionSettingsData();
data.SetFromSettings(notification.UserData);
DeliverToRequiredLocation(notification, data);
notification.Status = "Success";
}
catch (Exception e)
{
notification.Status = "Error: " + e.Message;
//notification.Retry = true;
}
finally
{
notification.Save();
}
return notification.Retry;
}
private void DeliverToRequiredLocation(Notification notification, SubscriptionSettingsData settings)
{
char qualfier = '"';
char delim = ',';
string renderFormat = string.IsNullOrEmpty(settings.RenderFormat) ||
settings.RenderFormat.Equals("Text") ? "CSV" : settings.RenderFormat;
string fileExtension = string.IsNullOrEmpty(settings.RenderExtension) ?
".Csv" : settings.RenderExtension;
//string deviceInfo = string.Format("<DeviceInfo><FieldDelimiter>{0}<" +
"/FieldDelimiter><Qualifier>{1}</Qualifier></DeviceInfo>", delim, qualfier);
RenderedOutputFile _outputFile = notification.Report.Render(renderFormat, null)[0];
//Render Report to Export Path First
string fileName = DateTime.Now.ToString("dd-MM-yy-HH:mm") + "_" + notification.Report.Name ;
fileName = Common.GetValidFileName(fileName) + fileExtension;
string filePath = "";
if (string.IsNullOrEmpty(settings.WindowFolderLocation))
{
filePath = Path.Combine(_exportPath, fileName);
}
else
{
filePath = Path.Combine(settings.WindowFolderLocation, fileName);
}
_outputFile.Data.Seek(0L, SeekOrigin.Begin);
byte[] arr = new byte[((int)_outputFile.Data.Length) + 1];
_outputFile.Data.Read(arr, 0, (int)_outputFile.Data.Length);
MemoryStream stream = new MemoryStream(arr);
File.WriteAllBytes(filePath, arr);
//For now do not deliver it anywhere
//SendMail(notification, stream, fileName);
//FtpFileToServer(settings, fileName, filePath);
}
这里,报表根据用户提供的格式进行渲染,并在订阅创建期间保存文件夹位置。
ExtensionUserControl 实现
这是所有中最简单的。只需在单独的项目中创建一个用户控件,进行测试,然后将其添加到您的交付扩展类库项目。为简单起见,请保持命名空间相同。
<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="ExtensionUserControl.ascx.cs"
Inherits="Chenoa.CCX.Extensions.ExtensionUserControl" %>
<table>
<tr>
<td>
<asp:Label ID="lblWindowsFolder" runat="server" Text="Folder Path: "></asp:Label>
</td>
<td>
<asp:TextBox ID="txtWindowsFolder" runat="server" Width="300px"></asp:TextBox>
</td>
</tr>
<tr>
<td>
<asp:Label ID="lblRenderFormat" runat="server" Text="Render Format: "></asp:Label>
</td>
<td>
<asp:DropDownList ID="dropDownRenderFormat" runat="server">
<asp:ListItem Text="CSV" Value=".Csv" Selected="True"></asp:ListItem>
<asp:ListItem Text="Excel" Value=".Xls"></asp:ListItem>
<asp:ListItem Text="PDF" Value=".Pdf"></asp:ListItem>
<asp:ListItem Text="Text" Value=".Txt"></asp:ListItem>
</asp:DropDownList>
</td>
</tr>
</table>
ExtensionUserControl.ascx.cs 代码
public partial class ExtensionUserControl : System.Web.UI.UserControl
{
public string FolderLocation
{
get { return txtWindowsFolder.Text.Trim(); }
set { txtWindowsFolder.Text = value; }
}
public string SelectedRenderFormat
{
get { return dropDownRenderFormat.SelectedItem.Text; }
set { dropDownRenderFormat.Items.FindByText(value).Selected = true; }
}
public string RenderFormatExtension
{
get { return dropDownRenderFormat.SelectedItem.Value; }
}
protected void Page_Load(object sender, EventArgs e)
{
}
protected void btnClick_Click(object sender, EventArgs e)
{
lblMessage.Text = string.Format("Folder Path is {0}", txtWindowsFolder.Text);
}
}
呼!就这样。
结论
这看起来可能有点多,但相信我,它比创建服务器控件要容易得多,服务器控件需要创建每一个表/行/单元格等。这是一个简单的用户控件,但如果您想根据报表格式收集设备信息,它可能会变得复杂。
附带的代码包含可用的交付扩展和用户控件。样本配置文件也在那里供您参考。请尝试一下,并告诉我是否可以进一步改进。同时,我将将其作为一个功能齐全的项目进行开发。到目前为止,它只是一个 POC(概念验证)。