Search code examples
c#asp.net-corefilestreamrazor-pages

Saving excel file in user's downloads folder producing error (.NET Core Razor Pages)


In my razor page application, I have a button that you click which creates an excel file and should automatically save it to your downloads folder.

The code below works great in localhost - I click the button, it saves to MY downloads folder, and I can view it.

However, once I publish and try, I receive an error that states "Could not find a part of the path 'C:\WINDOWS\system32\config\systemprofile\Downloads\PartCommentHistory.xlsx'.".

I would also be perfectly fine changing this code to instead pull up the save file dialog window and allow the user to pick where the file gets saved in the first place - but I'm not sure how. Google isn't helping much, so here we are!

If I physically navigate to this path, I noticed that there is no Downloads folder. I tried adding an if statement in my code that says if the Downloads folder doesn't exist here, create it first and then save the file there. However, that produces another error which is that I don't have access to the path.

public async Task<IActionResult> OnPostExportAsync(string currentFilter)
        {
            string sFilePath = Path.Combine(Environment.ExpandEnvironmentVariables("%USERPROFILE%"),"Downloads");
            string sFileName = @"PartCommentHistory.xlsx";
            string URL = string.Format("{0}://{1}/{2}", Request.Scheme, Request.Host, sFileName);
            FileInfo file = new FileInfo(Path.Combine(sFilePath, sFileName));
            var memory = new MemoryStream();
            using (var fs = new FileStream(Path.Combine(sFilePath, sFileName), FileMode.Create, FileAccess.Write))
            {
                ExcelPackage pck = new ExcelPackage();
                ExcelWorksheet ws = pck.Workbook.Worksheets.Add("Worksheet1");

                List<CmtPartComment> commentlist = _context.CmtPartComments.Select(x => new CmtPartComment
                {
                    SupplierNo = x.SupplierNo,
                    PartNo = x.PartNo,
                    Comment = x.Comment,
                    EnterBy = x.EnterBy,
                    EnteredDt = x.EnterDt.ToString("yyyy-MM-dd HH:mm:ss tt"),
                    CompletedDt = x.CompleteDt.ToString("yyyy-MM-dd HH:mm:ss tt")
                }).Include(c => c.System).OrderByDescending(x => x.EnterDt).Where(x => x.PartNo == currentFilter).ToList();

                ws.Cells[1, 1].Value = "SupplierNo";
                ws.Cells[1, 2].Value = "PartNo";
                ws.Cells[1, 3].Value = "Comment";
                ws.Cells[1, 4].Value = "EnterBy";
                ws.Cells[1, 5].Value = "EnterDt";
                ws.Cells[1, 6].Value = "CompleteDt";

                int recordIndex = 2;
                foreach (var item in commentlist)
                {
                    ws.Cells[recordIndex, 1].Value = item.SupplierNo;
                    ws.Cells[recordIndex, 2].Value = item.PartNo;
                    ws.Cells[recordIndex, 3].Value = item.Comment;
                    ws.Cells[recordIndex, 4].Value = item.EnterBy;
                    ws.Cells[recordIndex, 5].Value = item.EnteredDt;
                    ws.Cells[recordIndex, 6].Value = item.CompletedDt;
                    recordIndex++;
                }

                ws.Cells["A:AZ"].AutoFitColumns();

                pck.SaveAs(fs);
            }

            using (var stream = new FileStream(Path.Combine(sFilePath, sFileName), FileMode.Open))
            {
                await stream.CopyToAsync(memory);
            }
            memory.Position = 0;
            return File(memory, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", sFileName);
        }

Solution

  • For your issue, it is caused by that you are creating a temp file in the server side by using (var fs = new FileStream(Path.Combine(sFilePath, sFileName), FileMode.Create, FileAccess.Write)) which may not exist in the server side.

    For your requirement, you are trying to create a file and return it to client side. If so, there is no need to create the local file in the server side, you could return the byte of the file like below:

    public async Task<IActionResult> OnPostExportByInMemoryAsync(string currentFilter)
    {
        string sFileName = @"PartCommentHistory.xlsx";
    
        using (var pck = new ExcelPackage())
        {
            ExcelWorksheet ws = pck.Workbook.Worksheets.Add("Worksheet1");
            ws.Cells[1, 1].Value = "SupplierNo";
            ws.Cells[1, 2].Value = "PartNo";
            ws.Cells[1, 3].Value = "Comment";
            ws.Cells[1, 4].Value = "EnterBy";
            ws.Cells[1, 5].Value = "EnterDt";
            ws.Cells[1, 6].Value = "CompleteDt";
            ws.Cells["A:AZ"].AutoFitColumns();
            return File(pck.GetAsByteArray(), "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", sFileName);
        }
    }