Search code examples
c#asp.netwebformslinkbuttonasplinkbutton

WebForm can't populate button command argument with list of files?


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();
            }
        }

Solution

  • 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