0

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);
        }
mstiver2018
  • 107
  • 9
  • Why are you saving to 'C:\WINDOWS\system32\....' ? I would expect 'C:\Users\username\downloads\..." – JazzmanJim Jul 18 '19 at 18:38
  • 1
    Your code is running _server_-side. When you resolve `%USERPROFILE%\Downloads` that is according to the _server_ (specifically, the user on the server under which the code is executing) and has no bearing on where the _client_ saves the download. That it worked on `localhost` suggests that you were running the web server as the same user with which you logged in to Windows and, thus, that user's `Downloads` directory is the same as your `Downloads` directory (because they're the same user). – Lance U. Matthews Jul 18 '19 at 21:28
  • What is the **exact** value of `sFilePath`? – mjwills Jul 18 '19 at 21:28
  • @JazzmanJim %USERPROFILE% is producing that path, I assume. – mstiver2018 Jul 19 '19 at 19:18
  • @mjwills from the error message: C:\WINDOWS\system32\config\systemprofile\Downloads\ – mstiver2018 Jul 19 '19 at 19:19

3 Answers3

1

Use this method to get a folder path

    Environment.GetFolderPath(Environment.SpecialFolder.Yourspecialfoldernamehere, System.Environment.SpecialFolderOption.None)

For example

    Environment.GetFolderPath(Environment.SpecialFolder.System));

In above example System is a special folder.

Prakash Mhasavekar
  • 574
  • 1
  • 5
  • 16
1

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);
    }
}
Edward
  • 28,296
  • 11
  • 76
  • 121
0

You can't determine where on the client machine the file can be saved. The only reason it appears to work on your machine is because your machine acts as the server. All you can do is to force a Save or Open dialog when the user downloads the file, which is achieved by setting the content type to application/octet-stream: Do I need Content-Type: application/octet-stream for file download?

Mike Brind
  • 28,238
  • 6
  • 56
  • 88
  • The one exception to this would be if you're in an environment where the user the web application is running as has write access to the folder you're trying to save to on the client machine (As in, they're on the same network and your IIS site has far too powerful permissions). Then it can save it, but you're completely bypassing the HTTP protocol to do so. – mason Jul 18 '19 at 20:43