Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Export to Excel using ReportViewer built-in feature

I'd like to know if it is possible to set the Excel output as "Locked", in the sense that when we try to change a Cell's value, then there will be a warning indicating that we can not change it unless we remove the Sheet's protection.

I know that we can develop a custom Excel automation code, and set a Password to protect the sheet just before we save it. But, is there any easy way to accomplish this using ReportViewer's built-in feature?

like image 645
Keith Avatar asked Feb 19 '23 07:02

Keith


1 Answers

After doing some research, I've managed to find the solution :) The idea is to intercept the Export Report function of ReportViewer, and then run our own process. This process will get output which is the Excel file being generated, and then read it and apply whatever changes necessary, and save it again before it is sent as a Download to User. It should be noted however, that the interception method will be different based on what type of Reporting that we use. In my case, my ReportViewer is using WebForm instead of WinForm, and most explanation out there is explaining about ReportExport event which is only available in WinForm.

For those using WinForm, you can override the ReportExport event like this :

void reportViewer_ReportExport(object sender, Microsoft.Reporting.WinForms.ReportExportEventArgs e)
{
    e.Cancel = true;
    // insert your own code to export excel
}

In WebForm, there is no event handler of ReportExport. The options that I can think of are creating a custom button in .aspx that will execute our custom code, or directly render the excel without needing to preview the report. I decided to render the excel file directly. I will use a dataset and get the data from Stored Procedure. Then, I assign the dataset into RDLC and call the Render method to get the output. The output format is in byte[], and I use FileStream to write it. After it is done, I open the Excel file using Interop and apply protection. Here is the code :

// Setup DataSet (Adapter and Table)
YourTableAdapters.ATableAdapter ds = new YourTableAdapters.ATableAdapter();
YourDataSet.ADataTable dt = new YourDataSet.ADataTable ();

ds.Fill(dt, outlet, period);

// Create Report DataSource
ReportDataSource rds = new ReportDataSource("DataSet1", (System.Data.DataTable)dt);

// Variables needed for ReportViewer Render method
Warning[] warnings;
string[] streamIds;
string mimeType = string.Empty;
string encoding = string.Empty;
string extension = string.Empty;

// Setup the report viewer object and get the array of bytes
ReportViewer viewer = new ReportViewer();
viewer.ProcessingMode = ProcessingMode.Local;
viewer.LocalReport.ReportPath = "YourReport.rdlc";
viewer.LocalReport.DataSources.Add(rds); // Add datasource here

byte[] bytes = viewer.LocalReport.Render("Excel", null, out mimeType,
                                          out encoding, out extension,
                                          out streamIds, out warnings);

// Prepare filename and save_path, and then write the Excel using FileStream.
String temp_path = Path.Combine(Server.MapPath(Config.ReportPath), "FileName.xls");
FileStream fs = new FileStream(temp_path, FileMode.Create);
fs.Write(bytes, 0, bytes.Length);
fs.Close();

// Open the Excel file created, and add password protection.
PIDExcel pidexcel = new PIDExcel();
pidexcel.CollectExcelPID();

Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel.Range lock_range = null;

int excelid = pidexcel.GetNewExcelID();

Microsoft.Office.Interop.Excel.Workbook xlWorkBook = null;

try
{
    //xlApp.Visible = true;
    xlWorkBook = (Microsoft.Office.Interop.Excel.Workbook)xlApp.Workbooks.Open(temp_path,
                  Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                  Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                  Type.Missing, Type.Missing, Type.Missing, Type.Missing,
                  Type.Missing, Type.Missing);

    foreach(Microsoft.Office.Interop.Excel.Worksheet displayWorksheet in xlApp.ActiveWorkbook.Worksheets)
    {
        lock_range = xlApp.Cells;
        lock_range.Select();
        lock_range.EntireColumn.Locked = true;
        displayWorksheet.Protect("<your password here>");
    }

}
catch (Exception ex)
{
    throw new Exception(ex.Message.Replace("'", "")); ;
}
finally
{
    // Set First Sheet Active
    xlWorkBook.Sheets[1].Select();
    xlApp.DisplayAlerts = false;
    xlWorkBook.Save();
    xlWorkBook.Close(Type.Missing, Type.Missing, Type.Missing);
    xlApp.Quit();

    System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlWorkBook);
    System.Runtime.InteropServices.Marshal.FinalReleaseComObject(xlApp);

    GC.WaitForPendingFinalizers();
    GC.Collect();

    pidexcel.KillExcel(excelid);

}

By using this concept, I can easily design the report output since I'm using RDLC as a template to populate the data supplied by SP, and then render it. Imagine the hassle if we manually code the report using Excel (setting borders, merge cells, groupings).

like image 184
Keith Avatar answered Feb 27 '23 19:02

Keith