0

It's been a long time since I've had to work with WebForms but I'm having to update an old website. I'm trying to make a page that will allow the user to download a file to their machine. I can get the list of files in a directory as well as display it in a table, however, I'm trying to assign the file name to the command argument of a button so that I'll know the name of the file to go get in the button event. I can't seem to find the correct way to get the value out of the folder object to populate the command argument. No errors, it's just blank. Any suggestions?

var listFiles = dir.GetFiles();

<% 
    foreach(var file in listFiles) { %>
    <tr>
        <td>&nbsp;</td>
        <td><%= file.Name %></td>
        <td><%= file.Length %></td>
        <td><%= file.CreationTime.ToString("MM/dd/yyyy HH:mm") %></td>
        <td>
            <asp:LinkButton ID="btnDownload" runat="server" 
                CommandArgument='<%#Eval("file.Name") %>'
                CommandName="DownloadTechFile" 
                OnCommand="DownloadFile"
                ToolTip="Downloaded file">
                <span aria-hidden="true" class="fa fa-download"></span>
            </asp:LinkButton>
        </td>
    </tr>
<% } %>


protected void DownloadFile(object sender, CommandEventArgs e)
        {
            Alert.Hide();

            var fileName = e.CommandArgument.ToString();

            var fileFullPath = Path.Combine(FileFolderPath, fileName);

            if (string.IsNullOrWhiteSpace(fileName) == false)
            {
                WebClient req = new WebClient();
                HttpResponse response = HttpContext.Current.Response;
                try
                {
                    response.Clear();
                    response.ClearContent();
                    response.ClearHeaders();
                    response.Buffer = true;
                    response.AddHeader("Content-Disposition", "attachment;filename=" + fileName);
                    byte[] data = req.DownloadData(fileFullPath);
                    //byte[] data = req.DownloadData(Server.MapPath(fileFullPath));
                    response.BinaryWrite(data);
                    response.End();

                    Alert.Show(AlertType.Success, "File downloaded.");

                }
                catch (Exception ex)
                {
                    logger.Error("Error downloading file from {0}. {1} | {2} | {3}", fileFullPath, ex.Message, ex.StackTrace, ex.InnerException);
                    Alert.Show(AlertType.Error, string.Format("Error trying to download file: {0}", fileName));
                }
            }
        }

UPDATE: This is what I came up with in case someone else could use it.

 <asp:GridView ID="gvFiles" runat="server" AutoGenerateColumns="false" CssClass="table borderless" HeaderStyle-CssClass="fileheader">
            <Columns>
                <asp:BoundField DataField="FileName" HeaderText="File Name" />
                <asp:BoundField DataField="FileSize" HeaderText="File Size" />
                <asp:BoundField DataField="Date" HeaderText="Created On" />
                <asp:TemplateField HeaderText="">
                    <ItemTemplate>
                        <asp:LinkButton ID="btnDownload" runat="server"
                            CommandArgument='<%# Eval("FileName") %>'
                            OnClick="DownloadFile"
                            ToolTip="Downloaded file">
                            <span aria-hidden="true" class="fa fa-download"></span>
                        </asp:LinkButton>
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>


void LoadGrid()
        {
            // create a table for the files, populate, and then bind.
            DataTable dtFiles = new DataTable();
            dtFiles.Columns.Add("Date", typeof(string));
            dtFiles.Columns.Add("FileSize", typeof(string));
            dtFiles.Columns.Add("FileName", typeof(string));

            DirectoryInfo fileDirectory = new DirectoryInfo(FileFolderPath);

            foreach (FileInfo file in fileDirectory.GetFiles("*.txt"))
            {
                DataRow dr = dtFiles.NewRow();
                dr["Date"] = file.CreationTime.ToString("MM/dd/yyyy HH:mm");
                dr["FileSize"] = Utility.GetBytesReadable(file.Length);
                dr["FileName"] = file.Name;
                dtFiles.Rows.Add(dr);
            }

            dtFiles.DefaultView.Sort = "Date DESC";
            gvFiles.DataSource = dtFiles;
            gvFiles.DataBind();
        }

        protected void DownloadFile(object sender, EventArgs e)
        {
            var fileName = (sender as LinkButton).CommandArgument;

            var fileFullPath = Path.Combine(FileFolderPath, fileName);

            string mineType = MimeMapping.GetMimeMapping(fileFullPath);

            if (string.IsNullOrWhiteSpace(fileName) == false)
            {
                byte[] binFile = File.ReadAllBytes(fileFullPath);
                Response.ContentType = mineType;
                Response.AppendHeader("Content-Disposition", "attachment; filename=" + fileName);
                Response.BinaryWrite(binFile);
                Response.End();
            }
        }
Caverman
  • 3,371
  • 9
  • 59
  • 115
  • 1
    Better use a GridView and place the LinkButton in the Template. – VDWWD May 30 '22 at 16:16
  • are you going to provide a link to the file, or are you going to load the file and stream it down to the client when the button is pressed? – Albert D. Kallal May 30 '22 at 17:24
  • I have a download button that will stream it down to the the client. I think I might have found a solution although it might not be the best. I'm going to loop through the list of files I get back from the folder and then create a new list from that with only the details I'm looking for. Then I can use that list to populate the table. – Caverman May 30 '22 at 17:40

1 Answers1

1

You can say use a gridview, and say like this:

        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false" CssClass="table">
            <Columns>
                <asp:BoundField DataField="File Name" HeaderText="File"  />
                <asp:BoundField DataField="Date" HeaderText="Date" />
                <asp:BoundField DataField="File Size" HeaderText="Size" />
                <asp:TemplateField HeaderText="Select"  ItemStyle-HorizontalAlign="Center">
                    <ItemTemplate>
                        <asp:HyperLink ID="HyperLink1" runat="server" CssClass="btn btn-default"
                            NavigateUrl='<%# Eval("Path") %>'  >Down Load</asp:HyperLink>
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>

And code behind can be this:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
            LoadGrid();
    }

    void LoadGrid()
    {
        // create a table for the files
        DataTable MyTable = new DataTable();

        MyTable.Columns.Add("Date", typeof(string));
        MyTable.Columns.Add("File Size", typeof(string));
        MyTable.Columns.Add("File Name", typeof(string));
        MyTable.Columns.Add("Path", typeof(string));

        string strURLFolder = "~/Content/Animals/";
        string strFolder = Server.MapPath(strURLFolder);

        DirectoryInfo MyDir = new DirectoryInfo(strFolder);
        FileInfo[] MyFiles = MyDir.GetFiles("*.*");


        foreach (FileInfo MyFile in MyDir.GetFiles("*.*"))
        {
            DataRow OneRow = MyTable.NewRow();
            OneRow["Date"] = MyFile.LastAccessTime;
            OneRow["File Size"] = (MyFile.Length / 1024).ToString() + " KB";
            OneRow["File Name"] = MyFile.Name;
            OneRow["Path"] = strURLFolder + MyFile.Name;

            MyTable.Rows.Add(OneRow);

        }

        MyTable.DefaultView.Sort = "Date";
        GridView1.DataSource = MyTable;
        GridView1.DataBind();

    }

And now we get this:

enter image description here

Now, I suppose you could replace the "hyper-link" with a button, and perhaps read the files as byte stream, and send that to the client, but above should get you started here.

Edit: The user notes they don't want to use a hyper-link (simple link) to the file.

So, we can do it this way:

We drop in a plane jane button into the grid (why then suggest, hint, talk about a "link" when you NOW stated you DO NOT want to use a link? - are we giving out awards for confusing here?).

If you don't want a link, then obviously we don't need nor want to care about, and talk about links then right?????

Ok, so lets remove the hyper link, and drop in a plane jane button into the grid view like this:

        <asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="false" CssClass="table">
            <Columns>
                <asp:BoundField DataField="File Name" HeaderText="File"  />
                <asp:BoundField DataField="Date" HeaderText="Date" />
                <asp:BoundField DataField="File Size" HeaderText="Size" />
                <asp:TemplateField HeaderText="Select"  ItemStyle-HorizontalAlign="Center">
                    <ItemTemplate>
                        <asp:Button ID="cmdDownLoad" runat="server" Text="Download" cssclass="btn"
                            OnClick="cmdDownLoad_Click" />
                    </ItemTemplate>
                </asp:TemplateField>
            </Columns>
        </asp:GridView>

So, code to load as before, and now we get/have this:

    protected void cmdDownLoad_Click(object sender, EventArgs e)
    {
        Button btn = sender as Button;
        GridViewRow gRow = btn.NamingContainer as GridViewRow;

        string sFileOnly = gRow.Cells[0].Text;
        string sFile = Server.MapPath("~/Content/Animals/" + sFileOnly);

        string sMineType = MimeMapping.GetMimeMapping(sFile);

        byte[] binFile = File.ReadAllBytes(sFile);
        Response.ContentType = sMineType;
        Response.AppendHeader("Content-Disposition", "attachment; filename=" + sFileOnly);
        Response.BinaryWrite(binFile);
        Response.End();

    }

So, we should see say this:

enter image description here

Albert D. Kallal
  • 42,205
  • 3
  • 34
  • 51
  • Thanks for this. I'm giving it a try as well but I think I need to use a LinkButton instead of hyperlink so that I can call a function and pass it the file name as a parameter. I've added my Download function to my original post to see what I'm trying to do. I'm struggling getting the CommandArgument on the LinkButton to get populated. If I hard code a file name in the argument then the code works. – Caverman May 31 '22 at 15:02
  • Ok, so you don't want to use a link, then that should be crystal clear in your post. see my edit, I show how to drop in a button into the grid, and then we click on the button, and we read the file as bytes and send it to the client. And why would command argument be required? All we need is the grid row we clicked the button on. From that we can get the file name, and ultimate the whole path name. I suppose we could add to the command argument of the button a full path name to the file, but I think it is REALLY bad idea to expose server path name in the client side markup. – Albert D. Kallal May 31 '22 at 17:38
  • Thanks for the post. I used a combination of your code and code from this article. https://www.aspsnippets.com/Articles/Display-list-of-files-from-Server-folder-in-ASPNet-GridView.aspx – Caverman May 31 '22 at 21:05
  • All good then. As noted if the goal is to hide and not allow direct URL to such files, then you probably don't want to expose file path names client side - that includes use of command argument. Not a huge deal, but users would then see + know that path name if you do. – Albert D. Kallal May 31 '22 at 21:10