0

I have an older legacy web application. We've recently moved it from an old Windows 2008 r2 server to a new Windows 2019 server and a few issues have shown up. I've worked through most of them, however there's one that still causing me some problems. This web application is needing to export data in a DataTable to an Excel sheet for the user to view. However when it exports the Excel file they receive this message

The file format and extension of 'Report.xls' don't match. The file could be corrupted or unsafe. Unless you trust its source, don't open it. Do you want to open it anyway?

If they click "yes" then it gives then another message

The file is corrupt and cannot be opened.

There weren't any problems with the older 2008 r2 server.

I've tried changing the Response.ContentType = "application/vnd.xls"; to Response.ContentType = "application/vnd.ms-excel"; as well as Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; and i still receive the same messages. I need to be able to provide an Excel sheet, and I can't pay for an Excel package.

Here's the code that generates the Excel file:

public void Create_Spreadsheet(string sheetField, string[] arrExcelColumns, DataTable table)
{
    using (MemoryStream stream = new MemoryStream())
    {
        string pathToXsl = Server.MapPath(relToXsl);
        string reportFileName = "Report.xls";
        string fieldName = "Field";
        string worksheetName = "Sheet";
        string prevWorksheet = "";
        DataView dv = new DataView();
        DataTable dt = new DataTable();

        dt = table;

        // sort data for displaying with worksheets
        dv = dt.DefaultView;
        if (dv.Table.Columns.Contains(sheetField)) dv.Sort = sheetField;
        DataTable dtSorted = dv.ToTable();

        // get columns array
        ArrayList arrColumns = new ArrayList();
        for (int k = 0; k < dtSorted.Columns.Count; ++k)
        {
            if (Array.IndexOf(arrExcelColumns, dtSorted.Columns[k].ColumnName) >= 0)
                arrColumns.Add(dtSorted.Columns[k].ColumnName);
        }

        XmlWriterSettings settings = new XmlWriterSettings();
        settings.Indent = true;
        settings.Encoding = System.Text.Encoding.UTF8;

        StringBuilder sb = new StringBuilder();

        // Create the XmlWriter object and write some content.
        XmlWriter xmlWriter = XmlWriter.Create(sb, settings);
        xmlWriter.WriteStartDocument();

        // Write our first XML header (root)
        xmlWriter.WriteStartElement("Root");

        if (dtSorted.Rows.Count > 0)
        {
            Regex rx = new Regex(@"^[a-zA-z_]+");
            foreach (DataRow dr in dtSorted.Rows)
            {
                worksheetName = arrColumns.Contains(sheetField) && !String.IsNullOrEmpty(dr[sheetField].ToString()) ? dr[sheetField].ToString() : "New";
                if (!rx.IsMatch(worksheetName)) worksheetName = "_" + worksheetName;

                if (prevWorksheet != worksheetName)
                {
                    // end write worksheet
                    if (!String.IsNullOrEmpty(prevWorksheet)) xmlWriter.WriteEndElement();
                    // begin write worksheet
                    xmlWriter.WriteStartElement(worksheetName);
                }

                // begin write record
                xmlWriter.WriteStartElement("Record");


                foreach (Object o in arrColumns)
                {
                    fieldName = o.ToString();
                    //if (fieldName != sheetField)    // don't display worksheet field
                    xmlWriter.WriteElementString(fieldName, dr[fieldName].ToString());
                }

                // end write record
                xmlWriter.WriteEndElement();

                prevWorksheet = worksheetName;
            }

            // end write last worksheet
            xmlWriter.WriteEndElement();
        }
        else
        {
            // begin write worksheet
            xmlWriter.WriteStartElement(worksheetName);
            // begin write record
            xmlWriter.WriteStartElement("Record");

            foreach (Object o in arrColumns)
            {
                fieldName = o.ToString();
                xmlWriter.WriteElementString(fieldName, "");
            }
            //xmlWriter.WriteElementString("Result", "No records found.");

            // end write record
            xmlWriter.WriteEndElement();
            // end write worksheet
            xmlWriter.WriteEndElement();
        }

        // end root
        xmlWriter.WriteEndElement();
        // Finilize the XML document by writing any required closing tag.
        xmlWriter.WriteEndDocument();
        xmlWriter.Flush();

        // Load the style sheet.
        XslCompiledTransform xslt = new XslCompiledTransform();
        xslt.Load(pathToXsl);

        // Create the writer.
        XmlWriterSettings settings2 = new XmlWriterSettings();
        settings2.Indent = true;
        settings2.IndentChars = "\t";
        XmlWriter writer = XmlWriter.Create(stream, settings2);

        XmlReader xmlReader = XmlReader.Create(new StringReader(sb.ToString()));
        xmlReader.MoveToContent();

        // Execute the transformation.
        xslt.Transform(xmlReader, writer);

        writer.Close();
        xmlReader.Close();

        xmlWriter.Close();

        // Convert the memory stream to an array of bytes.
        byte[] byteArray = stream.ToArray();

        // Send the XML file to the web browser for download.
        Response.Clear();
        //Response.AppendHeader("Content-Disposition", "filename=Report.xml");
        //Response.AppendHeader("Content-Length", byteArray.Length.ToString());
        //Response.ContentType = "application/octet-stream";

        //Response.ContentType = "application/vnd.xls";
        Response.ContentType = "application/vnd.ms-excel";
        //Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
        Response.AddHeader("Content-Disposition", "attachment; filename=" + reportFileName);
        Response.AppendHeader("Content-Length", byteArray.Length.ToString());

        Response.BinaryWrite(byteArray);

        Response.End();
    }
}

I'm not sure where things are going wrong, or what has changed. This is a new Windows 2019 Server (which doesn't have office installed on it), and the Asp.net/C# code does not have a solution (it was created with the old "website" style). Any help would be greatly appreciated! let me know if there's anything more needed.

Edit:

using the EPPlus library and the following code:

public void ExportToExcel(DataTable dt, string FileName)
{
    //create a new byte array       
    byte[] bin;

    //create a new excel document
    using (ExcelPackage excelPackage = new ExcelPackage())
    {
        //create a new worksheet
        ExcelWorksheet ws = excelPackage.Workbook.Worksheets.Add(FileName);

        //add the contents of the datatable to the excel file
        ws.Cells["A1"].LoadFromDataTable(dt, true);

        //auto fix the columns
        ws.Cells[ws.Dimension.Address].AutoFitColumns();

        //loop all the columns
        for (int col = 1; col <= ws.Dimension.End.Column; col++)
        {
            //make all columns just a bit wider, it would sometimes not fit
            ws.Column(col).Width = ws.Column(col).Width + 1;

            var cell = ws.Cells[1, col];

            //make the text bold
            cell.Style.Font.Bold = true;

            //make the background of the cell gray
            var fill = cell.Style.Fill;
            fill.PatternType = ExcelFillStyle.Solid;
            fill.BackgroundColor.SetColor(ColorTranslator.FromHtml("#BFBFBF"));

            //make the header text upper case
            cell.Value = ((string)cell.Value).ToUpper();
        }

        //convert the excel package to a byte array
        bin = excelPackage.GetAsByteArray();
    }

    //clear the buffer stream
    Response.ClearHeaders();
    Response.Clear();
    Response.Buffer = true;

    //set the correct contenttype
    Response.ContentType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";

    //set the correct length of the data being send
    Response.AddHeader("content-length", bin.Length.ToString());

    //set the filename for the excel package
    Response.AddHeader("content-disposition", "attachment; filename=\"" + FileName + ".xlsx\"");

    //send the byte array to the browser
    Response.OutputStream.Write(bin, 0, bin.Length);

    //cleanup
    Response.Flush();
    HttpContext.Current.ApplicationInstance.CompleteRequest();
}

it now gives me an error:

C:\Users...\AppData\Local\Temp\RoIFPfvQ.xlsx.part could not be saved, because the source file could not be read.

however if I RDP to the web server and go to the web page and download the package and move it to local machine (since excel is not on the web server) the file opens just fine. What am I missing? where is this attempting to create this xlsx.part and could it be that the content is being flushed out/deleted before the file is actually saved/downloaded?

shadonar
  • 1,114
  • 3
  • 16
  • 40
  • 1
    Start using a specialized library for creating Excel files, like [EPPlus](https://github.com/JanKallman/EPPlus). [Example here](https://stackoverflow.com/a/47293207/5836671) and [here](https://stackoverflow.com/a/39513057/5836671). – VDWWD Jan 16 '20 at 20:24
  • @VDWWD how do you get the dll for EPPlus so that it can be added to references list? – shadonar Jan 16 '20 at 20:29
  • NuGet is the easiest. https://www.nuget.org/packages/EPPlus/. samples are in the github projects. – VDWWD Jan 16 '20 at 20:32
  • @VDWWD this doesn't have a .sln or project files as this was created in the old "Website" style. so how would I then obtain the dll file? – shadonar Jan 16 '20 at 20:35
  • Hmmm. Annoying. It seems you cannot download the dll as a standalone library. it's NuGet or downloading the source and compiling it yourself. – VDWWD Jan 16 '20 at 20:42
  • 1
    I've added the dll to WeTransfer https://wetransfer.com/downloads/cec0aba80fcaac08cf6a3e98d77351cf20200116204140/60f86cbc92287d009825c4b45de2829920200116204140/8a76d2. Its version 2.0.50727 – VDWWD Jan 16 '20 at 20:43
  • Why don't you just... you know... *create* a solution and add the project to it? – Ian Kemp Jan 16 '20 at 20:59
  • @IanKemp that actually breaks the website. i've tried it before – shadonar Jan 16 '20 at 21:00
  • @VDWWD this worked as long as it's local to the web server. if attempting to export the file on a different machine i get this message: C:\Users\....\AppData\Local\Temp\j3H_3gJx.xlsx.part could not be saved, because the source file could not be read. Try again later, or contact the server administrator. – shadonar Jan 16 '20 at 21:01
  • In production, you cannot access folders outside the root of the website. So put it in a folder within the website itself with `Server.MapPath("/FolderForExcelFiles")` – VDWWD Jan 16 '20 at 21:03
  • @VDWWD I used the function from your [link](https://stackoverflow.com/a/47293207/5836671) and i guess i'm not seeing where it's trying to "save" the file – shadonar Jan 16 '20 at 21:06
  • You can get the bytes and write them to the stream with `package.GetAsByteArray()`, just like you do with `byte[] byteArray = stream.ToArray();` – VDWWD Jan 16 '20 at 21:08
  • @VDWWD i think the data is being deleted/cleaned up before my browser can download the content... i think that's might be what's happening. – shadonar Jan 16 '20 at 21:24
  • 1
    @VDWWD Even if they don't directly provide a standalone DLL, you can always extract them from the NuGet package (.nupkg). It's just a ZIP file with a different file extension. [FuGet](https://www.fuget.org/packages/EPPlus) will even tell you what DLL's are in the package. (Handy tip: when viewing a package on NuGet.org, simply change the domain to FuGet.org for much more comprehensive detail) – mason Jan 16 '20 at 21:39
  • @shadonar I see you're trying EPPlus now. It looks like you're creating a valid file, just having trouble sending it as an HTTP Response properly. Have you tried Response.BinaryWrite instead of Response.OutputStream.Write? – mason Jan 16 '20 at 21:52
  • @mason Yes, but it gives me the same problem. With Firefox (since i have to be multibrowser friendly) there's a pop up asking to open or save the file. chrome and IE just download it. now the download for chrome failed a couple times and has also succeeded a number of times. it's just not consistent – shadonar Jan 16 '20 at 21:54
  • Sounds like that's a different issue from what you're originally asking. Perhaps you should break it down into something simpler (download a pre-generated file) and see if you can consistently reproduce. If you can - perhaps ask another question here. – mason Jan 16 '20 at 22:56

0 Answers0