C#でMicrosoft RDLC reportを非同期で直接印刷する

# 1. はじめに
* C#でMicrosoft RDLC reportをプレビューなしに直接印刷したい
* 印刷処理に時間がかかることを想定して、非同期で印刷したい

# 2. 開発環境
* C#
* .NET 6
* Visual Studio 2022
* Microsoft RDLC Report Desinger 2022
* ReportViewerCore.WinForms (NuGet)

# 3. 事前準備

https://qiita.com/YoshijiGates/items/6fa29732c16985f4e84a

# 4. ```LocalReportExtensions```クラスの作成
* 参考文献のソースコードをそのまま活用させてもらった
```Csharp:LocalReportExtensions.cs
using Microsoft.Reporting.WinForms;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Printing;
using System.IO;

public static class LocalReportExtensions
{
    public static void Print(this LocalReport report, string printerName)
    {
        var pageSettings = new PageSettings();
        pageSettings.PaperSize = report.GetDefaultPageSettings().PaperSize;
        pageSettings.Landscape = report.GetDefaultPageSettings().IsLandscape;
        pageSettings.Margins = report.GetDefaultPageSettings().Margins;
        Print(report, pageSettings, printerName);
    }

    public static void Print(this LocalReport report, PageSettings pageSettings, string printerName)
    {
        string deviceInfo =
            $@"<DeviceInfo>
                <OutputFormat>EMF</OutputFormat>
                <PageWidth>{pageSettings.PaperSize.Width * 100}in</PageWidth>
                <PageHeight>{pageSettings.PaperSize.Height * 100}in</PageHeight>
                <MarginTop>{pageSettings.Margins.Top * 100}in</MarginTop>
                <MarginLeft>{pageSettings.Margins.Left * 100}in</MarginLeft>
                <MarginRight>{pageSettings.Margins.Right * 100}in</MarginRight>
                <MarginBottom>{pageSettings.Margins.Bottom * 100}in</MarginBottom>
            </DeviceInfo>";

        Warning[] warnings;
        var streams = new List<Stream>();
        var currentPageIndex = 0;

        report.Render("Image", deviceInfo,
            (name, fileNameExtension, encoding, mimeType, willSeek) =>
            {
                var stream = new MemoryStream();
                streams.Add(stream);
                return stream;
            }, out warnings);

        foreach (Stream stream in streams)
            stream.Position = 0;

        if (streams == null || streams.Count == 0)
            throw new Exception("Error: no stream to print.");

        var printDocument = new PrintDocument();
        printDocument.DefaultPageSettings = pageSettings;
        if (!printDocument.PrinterSettings.IsValid)
            throw new Exception("Error: cannot find the default printer.");
        else
        {
            printDocument.PrintPage += (sender, e) =>
            {
                Metafile pageImage = new Metafile(streams[currentPageIndex]);
                Rectangle adjustedRect = new Rectangle(
                    e.PageBounds.Left - (int)e.PageSettings.HardMarginX,
                    e.PageBounds.Top - (int)e.PageSettings.HardMarginY,
                    e.PageBounds.Width,
                    e.PageBounds.Height);
                e.Graphics.FillRectangle(Brushes.White, adjustedRect);
                e.Graphics.DrawImage(pageImage, adjustedRect);
                currentPageIndex++;
                e.HasMorePages = (currentPageIndex < streams.Count);
                e.Graphics.DrawRectangle(Pens.Red, adjustedRect);
            };
            printDocument.EndPrint += (Sender, e) =>
            {
                if (streams != null)
                {
                    foreach (Stream stream in streams)
                        stream.Close();
                    streams = null;
                }
            };
            printDocument.PrinterSettings.PrinterName = printerName;
            printDocument.Print();
        }
    }
}
```

# 5. WinFormクライアントの作成
```Csharp:Form1.cs
using Microsoft.Reporting.WinForms;
using System.Diagnostics;

namespace WinFormsApp1
{
    public partial class Form1 : Form
    {

        public Form1()
        {
            InitializeComponent();
        }

        private async void ButtonStart_Click(object sender, EventArgs e)
        {
            // 非同期で印刷処理を実行する
            await ReportPrintAsync();
        }

        private async Task ReportPrintAsync()
        {

            Debug.Print("非同期処理開始");

            // 確認用にSleepする
            await Task.Delay(10000);

            // ReportViewerコントロールを追加する
            this.Controls.Add(this.reportViewer1);

            // ReportViewerコントロールを非表示にする
            reportViewer1.Visible = false;

            // ReportViewerコントロールにレポートを設定する
            reportViewer1.LocalReport.ReportPath = @"C:\XXXX\Report.rdlc";

            // レポートをプリンタを指定して直接印刷する
            reportViewer1.LocalReport.Print("Microsoft Print to PDF");


            Debug.Print("非同期処理終了");

        }

    }
}
```

# 6. 参考文献

https://stackoverflow.com/questions/57546529/printing-rdlc-to-a-pre-defined-printer-without-print-preview