ActiveReportsを動作させるワーカーロールコード
PDFはワーカーロール側で生成するため、ActiveReports関連のコードはすべてワーカーロール側に記述します。サンプルプログラム作成開始時点では、もちろんWindows Azure特有の問題やその対応方法も分からなかったので、ActiveReportsを使用するクラスライブラリの作成時と同じ手順でプログラミングしてみました。その時の作業を順番に追ってみます。
ActiveReportsの参照設定
まず、ソリューションエクスプローラーで「CZ1009CloudWorkerRoleワーカーロール」を右クリックして、「Graprcity ActiveReports for .NET」「Document」「PDF Export」「Viewer」のコンポーネントへの参照を追加します。
ActiveReports 6.0Jファイルの追加
参照の追加が完了したら、再びソリューションエクスプローラーで「CZ1009CloudWorkerRoleワーカーロール」を右クリックして、[追加]-[新しい項目]を選択し、ActiveReportsの帳票定義体ファイルを追加します。
図13を見れば分かるように、ActiveReportsの定義体には、2つのファイル形式があります。「ActiveReports 6.0Jファイル」を選択した場合には、クラスファイルとして定義体が追加され、WindowsフォームやWebフォームのようにビルド後はアセンブリの一部となります。もちろん、IDEで該当ファイルをダブルクリックすれば、ActiveReportsの帳票デザイナが開きます。
もう1つの「ActiveReports 6.0Jファイル(XMLベース)」を選択した場合は、rpxファイルとしてプロジェクトに追加され、ビルド後もアセンブリとは別のrpxファイルとして動作環境に配置されます。つまり、動作環境上のrpxファイルを編集することで、再ビルドなしに帳票定義体の修正ができるという利点があります。業務アプリケーションでは、帳票のちょっとした修正というのは意外と多いため、筆者はこのファイル形式をよく使っています。
しかし、この形式は実行時にrpxファイルの読み込みが必要なため、動作環境のどこにrpxファイルがあるかが分かっていなくてはなりません。ASP.NET Webロールの場合は、「My.Request.PhysicalApplicationPath」プロパティで位置が判明しますが、ワーカーロールの場合は、どのようにファイルパスを取ればよいか明確には分からなかったため、今回はアセンブリの一部となる「ActiveReports 6.0Jファイル」形式を採用しました。
ActiveReports 6.0J帳票定義体の定義
ActiveReportsの帳票定義体デザイナは、非常に使いやすい操作性になっています。基本的には、Windowsフォームデザイナと同じようにツールボックスからコントロールをドラッグ&ドロップし、配置するときにはスナップ線を頼りに最適な位置を決めることができます。Windowsフォームデザイナと異なるのは、コントロールとコントロールの間が0ピクセル、つまり開きがない状態が最適な位置と設定されている点です。
一覧表などを作る際、表のタイトルをPageHeaderセクションに配置し、データ部分をDetailセクションに配置するような場合でも、セクションをまたがってスナップ線が表示できるため、タイトル位置とデータ位置を容易に合わせることができます。
また、CrossSectionLineコントロールやCrossSectionBoxコントロールを使えば、セクション間をまたがった罫線も簡単に引くことができ、一覧表の罫線部分も簡単に定義できます。さらに、PageFooterセクションまで含めておけば、“データがなくてもページの下までいつも同じ大きさで枠線を引いておく”といった設定も簡単に実現できます。
ただし、CrossSectionLineコントロールとCrossSectionBoxコントロールのStartプロパティとEndプロパティのプロパティ設定値はcm(センチメートル)単位ではなく、インチ単位なので注意が必要です。プロパティで位置を決めるときは、cmからインチへの単位変換を行い、その値を小数点以下まで含めてプロパティに設定するようにしないと、Lineなど他のコントロールがcmでプロパティを設定しているので、微妙にずれることがあります。
HTTP内部エンドポイントの追加
Webロールからワーカーロールを呼び出すには、Windows Azure Storage Serviceのキューを使って非同期に連携する方法もありますが、非同期生成したPDFデータの扱いなどの考慮も必要になるため、今回はワーカーロールにHTTPで呼び出す内部インターフェース(内部エンドポイント)を定義して、それを同期的に呼び出すシンプルな方式にしました。
HTTPを使った内部エンドポイントを定義するには、ソリューションエクスプローラーからクラウドサービス定義(サンプルでは「CZ1001Cloud」)にあるワーカーロール定義のプロパティで、エンドポイントの追加を行います。今回定義するエンドポイントは、種類を「internal」としてAzure内部でのみ使用するように定義し、プロトコルに「http」を指定してWebロールからHTTPで呼び出せるようにします。
HTTP内部エンドポイントの設定
HTTP内部エンドポイントを設定したら、生成直後のワーカーロールにはRun
メソッド、OnStart
メソッド、RoleEnviromentChanging
メソッドのコードが既に記載されているので、Run
メソッドにエンドポイントが呼び出された時の処理を追加します。
Private Shared listener As New HttpListener Public Overrides Sub Run() Dim endpoint As IPEndPoint = RoleEnvironment.CurrentRoleInstance.InstanceEndpoints("HttpEndpoint").IPEndpoint listener.Prefixes.Add(String.Format("http://{0}:{1}/", endpoint.Address, endpoint.Port)) listener.Start() Do While (True) Dim context As HttpListenerContext = listener.GetContext() Using mes As System.IO.Stream = context.Response.OutputStream Try Dim byteMsg As Byte() = GetPdfDatas(False) mes.Write(byteMsg, 0, byteMsg.Length) Catch ex As Exception Dim msg As String = ex.Message Dim byteMsg As Byte() = System.Text.Encoding.UTF8.GetBytes(msg) mes.Write(byteMsg, 0, byteMsg.Length) End Try End Using Loop End Sub
エンドポイントの処理は、エンドポイントに対してlistener
を定義して呼び出しを待ち合わせし、呼び出されたら対応した処理を行うということになります。
listener
の定義は、エンドポイント名を「HttpEndPoint
」とした場合、InstanceEndpoints("HttpEndpoint")
でエンドポイントを取得して、HttpListener
に追加(Add
)します。後は、Start
メソッドでlistener
を開始してGetContext
メソッドで呼び出しを待ちます。
PDF生成コードの作成
GetContext
メソッドで待ち合わせていた呼び出しが行われたら、PDF作成用のGetPdfDatas
メソッドを呼び出します。GetPdfDatas
メソッドには、次のようなコードを記述します。
: (中略) : Imports DataDynamics.ActiveReports Imports DataDynamics.ActiveReports.Export.Pdf Public Class WorkerRole Inherits RoleEntryPoint : (中略) : Private block As Object = Nothing ''' <summary> ''' PDFデータを取得する ''' </summary> Public Function GetPdfDatas() As Byte() Dim memStream As System.IO.MemoryStream Dim ds As DataSet = GetRecords Using _rpt As New CZ1009Reports _rpt.Document.Printer.PrinterName = "" _rpt.PageSettings.PaperKind = System.Drawing.Printing.PaperKind.A4 _rpt.PageSettings.Orientation = Document.PageOrientation.Portrait _rpt.PageSettings.Margins.Top = ActiveReport.CmToInch(0.5F) _rpt.PageSettings.Margins.Bottom = ActiveReport.CmToInch(0.5F) 'データを割り当てる _rpt.DataSource = ds.Tables("Enviroment") ' レポートを作成します _rpt.Run(False) ' PDFエクスポートオブジェクトを生成します Using _pdf = New PdfExport ' PDFの出力用のメモリストリームを作成します memStream = New System.IO.MemoryStream ' メモリストリームにPDFエクスポートを行います _pdf.Security.Use128Bit = True _pdf.Security.OwnerPassword = "hatsune" _pdf.Security.Permissions = PdfPermissions.AllowPrint _pdf.Security.Encrypt = True _pdf.Export(_rpt.Document, memStream) End Using End Using Return memStream.ToArray() End Function : (中略) : End Class
メソッドの中のコードは、リスト1で紹介したASP.NET Webアプリケーションのコードとまったく同じです。
ActiveReportsを動作させるWebロールコード
ASP.NET Webロールからワーカーロールを呼び出す
今回のサンプルでは、Default.aspxの[PDF出力]ボタンをクリックすると「My.Response.Redirect("CZ1009Reports.aspx")
」を実行して、CZ1009Reports.aspxページを呼び出します。このCZ1009Reports.aspxページで、ワーカーロールを呼び出します。
ワーカーロールの呼び出し方は、動的に決定される内部エンドポイントのIPアドレスとポート番号を取得し、webClient.DownloadData
メソッドを使って呼び出します。
Public Class CZ1009Reports Inherits System.Web.UI.Page Private block As Object = Nothing Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) _ Handles Me.Load If Not Me.IsPostBack Then Call GetRecords(isEmbedFonts) End If End Sub Private Function GetRecords(ByVal isEmbedFonts As Boolean) As Boolean Using webClient As New System.Net.WebClient Dim userID As String = My.User.Name Dim workerEndPoint = Microsoft.WindowsAzure.ServiceRuntime.RoleEnvironment.Roles("CZ1009CloudWorkerRole").Instances(0).InstanceEndpoints("HttpEndpoint") Dim pdfStream() As Byte Dim pdf() As Byte = System.Text.Encoding.UTF8.GetBytes("%PDF") pdfStream = webClient.DownloadData("http://" & workerEndPoint.IPEndpoint.ToString) If pdfStream(0) = pdf(0) AndAlso pdfStream(1) = pdf(1) AndAlso pdfStream(2) = pdf(2) AndAlso pdfStream(3) = pdf(3) Then Response.ContentType = "application/pdf" Response.AddHeader("content-disposition", "inline; filename=AzureActiveReports.PDF") End If Response.BinaryWrite(pdfStream) Response.End() End Using Return True End Function End Class