0

I'm working on an ASP.NET webforms app and I want to add an option for uploading files into the server (a SQL Server database), but I haven't got it to work yet.

Using FileUpload inside an UpdatePanel, it requires postback to do the upload, but if I change the trigger of the upload modal button from AsyncPostBackTrigger to PostBackTrigger, the page reloads and the modal doesn't appear. Also, if I leave it as async, FileUpload.HasFile is null, i.e. the file doesn't get uploaded.

This is my modal

<div id="modalCargarArchivos" class="modal fade" tabindex="-1" role="dialog">
    <div class="modal-dialog modal-dialog-centered" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <h6 class="modal-title"></h6>
                <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
            </div>
            <asp:UpdatePanel ID="upModalCargarArchivos" runat="server" UpdateMode="Conditional">
                <ContentTemplate>
                    <div class="modal-body">
                        <div class="row">
                            <div class="col-12 col-md-12">
                                <div class="form-floating mb-3">
                                    <asp:FileUpload id="FileUpload1" runat="server"></asp:FileUpload>
                                    <asp:Button ID="btnCargarArchivo" OnClick="btnCargarArchivos_Click" OnClientClick="MostrarLoading();" CssClass="d-none" runat="server" />
                                </div>
                            </div> 
                        </div>
                        <div class="row">
                        </div>
                        <div id="divMensajeModalCargarArchivos" runat="server" class="">
                            <asp:Label ID="lblMensajeModalCargarArchivos" runat="server" CssClass="alert-danger"></asp:Label>
                        </div>
                    </div>
                    <div class="modal-footer">
                        <div class="container-fluid">
                            <div class="row">
                                <div class="col-12 col-md-5"></div>
                                <div class="col-12 col-md-7 text-end">
                                    <asp:Button ID="btnCargarArchivos" runat="server" CssClass="btn btn-primary" Text="Cargar" OnClientClick="return cargarArchivos();" OnClick="btnCargarArchivos_Click" />
                                    <button type="button" class="btn btn-danger" data-bs-dismiss="modal">Cancelar</button>
                                </div>
                            </div>
                        </div>
                    </div>
                </ContentTemplate>
                <Triggers>
                    <asp:AsyncPostBackTrigger ControlID="btnCargarArchivos"/>
                </Triggers>
            </asp:UpdatePanel>
        </div>
    </div>
</div>

And this is the server-side event to handle the file upload

protected void btnCargarArchivos_Click(object sender, EventArgs e)
{
    if (FileUpload1.HasFile)
    {
        lblMensajeModalCargarArchivos.Text = "Has file.";
    }
    else
    {
        lblMensajeModalCargarArchivos.Text = "No file selected.";
    }
}

I tried the first answer to this question, but FileUpload1 is null.

Any suggestions are appreciated.

5
  • 1
    Sounds like your question is not concerning SQL Server yet... since you aren't getting the file on the server. If so please remove the tag to avoid attracting experts who are unable to assist. Commented Feb 23, 2024 at 1:01
  • try set UpdateMode to Always and add PostBackTrigger in UpdatePanel pointing to btnCargarArchivo. oh, don't forget to ensure the form element up there to be multipart/form-data. Commented Feb 23, 2024 at 1:11
  • 1
    also, tag this with webform and remove the sql-server tag. Commented Feb 23, 2024 at 1:13
  • 3
    Do yourself a favour: don't store files in a database. Commented Feb 23, 2024 at 2:26
  • I tried the solution of @BagusTesa, but where should I place the multipart/form-data? Aside from that, changing the UpdateMode and the PostbackTrigger didn't help. Commented Feb 23, 2024 at 16:45

1 Answer 1

2

Ok, the issue of course is that a standard FileUpLoad control needs a page post-back or it will NOT work.

And thus, of course if you place the FileUpLoad inside of an update panel, then it will not work, since the VERY purpose and goal of a UpdatePanel is to avoid a whole page post back. So, you can't use a FileUpLoad control inside of an update panel.

What you can do however is adopt one of many "ajax" enabled file upload controls, and these don't require a post-back, and thus such up-loaders work inside of an update panel. Also, since such "ajax" file upload controls don't post-back, then they in near all cases provide a nice progress bar during uploading. And even better, in most cases, the file upload size is unlimited, since there not one large file being posted back to the server. And during such a post-back, it will appear to the end user that the browser has frozen. And with a nice progress bar, these ajax uploaders also allow a very nice cancel option, and I find they response "instant" when a user decides to cancel a file upload.

And even better, is such file uploaders also tend to have a hot spot area on the form in which you can drag + drop files, and they allow multiple file uploads.

The next issue?

Are you 100%, 200%, and 300% REALLY sure you want to save the file into the database? You can do this, and I think some valid use cases exist. However, in near all cases, you are much better off to create a new row in the database with information about the file, but still save a path name to the file on disk. This allows you to open the files, copy the files, delete the files, and use all kinds of 3rd party software against such files. The instant you place such files inside of the database means you are locked out from using all kinds of file utilities such as zipping files, deleting files, back up of files to an off-line archive server, and this is my short list of advantages.

So, I'm going to assume you REALLY but BEYOND really given this issue much thought, and after all "reasonable" choices have been eliminated, then saving such files in the database can be done, but should be your last option and resort.

So, first up, is the adoption of a better file upload control. There are many free and very good up-loaders you can adopt. And thus, in this day and age, I thus suggest you adopt such an up-loading library.

I currently use the one from the AjaxToolKit. The AjaxFileUpload has many options.

So, in use the control looks like this:

enter image description here

So, the markup looks is not all that much.

I often use a RadioButton list for a tab like control.

So, markup:

<asp:RadioButtonList ID="RadioButtonList1" runat="server" Height="40px"
    RepeatDirection="Horizontal" CssClass="rMyChoice" ClientIDMode="Static"
    AutoPostBack="true" OnSelectedIndexChanged="RadioButtonList1_SelectedIndexChanged">
    <asp:ListItem>Upload Files</asp:ListItem>
    <asp:ListItem>My Files</asp:ListItem>
</asp:RadioButtonList>
<br />

<div id="uploadfiles" runat="server">
    <ajaxToolkit:AjaxFileUpload ID="AjaxFileUpload1" runat="server"
        OnUploadComplete="AjaxFileUpload1_UploadComplete"
        OnClientUploadCompleteAll="AllDone" />
</div>

<div id="myfiles" runat="server">
    <asp:GridView ID="GridFiles" runat="server"
        AutoGenerateColumns="False" ShowHeaderWhenEmpty="true"
        CssClass="table table-hover" OnRowDataBound="GridFiles_RowDataBound">
        <Columns>
            <asp:BoundField DataField="FileName" HeaderText="FileName" />
            <asp:BoundField DataField="FileDate" HeaderText="UpLoaded" />
            <asp:BoundField DataField="FileSize" HeaderText="Size" />
            <asp:TemplateField HeaderText="Preview" ItemStyle-HorizontalAlign="Center" >
                <ItemTemplate>
                    <asp:Image ID="imgPreview" runat="server" Height="80" />
                </ItemTemplate>
            </asp:TemplateField>
            <asp:TemplateField HeaderText="Download" ItemStyle-HorizontalAlign="Center">
                <ItemTemplate>
                    <asp:LinkButton ID="cmdDownLoad"
                        runat="server" CssClass="btn"
                        OnClick="cmdDownLoad_Click"> 
                    <span aria-hidden="true" class="glyphicon glyphicon-cloud-download"></span>
                    </asp:LinkButton>
                </ItemTemplate>
            </asp:TemplateField>

            <asp:TemplateField HeaderText="Delete" ItemStyle-HorizontalAlign="Center">
                <ItemTemplate>
                    <asp:LinkButton ID="cmdDelete"
                        runat="server" CssClass="btn"
                        OnClick="cmdDelete_Click"> 
                    <span aria-hidden="true" class="glyphicon glyphicon-trash"></span>
                    </asp:LinkButton>
                </ItemTemplate>
            </asp:TemplateField>
        </Columns>
    </asp:GridView>
    <br />
</div>

And the code behind is this:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            RadioButtonList1.SelectedIndex = 0;
            LoadGridFiles();
            ShowArea();
        }
    }

    void LoadGridFiles()
    {
        DataTable MyTable = new DataTable();
        MyTable.Columns.Add("FileName", typeof(string));
        MyTable.Columns.Add("FileDate", typeof(string));
        MyTable.Columns.Add("FileSize", typeof(string));
        MyTable.Columns.Add("FullFile", typeof(string));

        var strFolder = Server.MapPath("~/UpLoadFiles");

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

        foreach (FileInfo MyFile in MyFiles)
        {
            MyTable.Rows.Add(MyFile.Name,
                MyFile.LastAccessTime.ToShortDateString(),
                MyFile.Length.ToString(),
                MyFile.FullName);
        }

        GridFiles.DataSource= MyTable;  
        GridFiles.DataBind();   
    }

And the code for saving an uploaded file is this:

    protected void AjaxFileUpload1_UploadComplete(object sender, AjaxControlToolkit.AjaxFileUploadEventArgs e)
    {
        // save file to up-loads folder
        string strSaveFile = Server.MapPath("~/UpLoadFiles/" + e.FileName);
        AjaxFileUpload1.SaveAs(strSaveFile);
    }

And the row delete and download code is this:

    protected void cmdDelete_Click(object sender, EventArgs e)
    {
        LinkButton myBut = (LinkButton)sender;
        GridViewRow gRow = (GridViewRow)myBut.NamingContainer;
        string strFile = gRow.Cells[0].Text;
        string strFullFile = Server.MapPath($"~/UpLoadFiles/{strFile}");
        File.Delete(strFullFile);   
        LoadGridFiles();


    }

    protected void cmdDownLoad_Click(object sender, EventArgs e)
    {
        LinkButton myBut = (LinkButton)sender;
        GridViewRow gRow = (GridViewRow)myBut.NamingContainer; // get current grid row

        string strFile = gRow.Cells[0].Text;
        string strFullFile = Server.MapPath($"~/UpLoadFiles/{strFile}");
        string sMineType = MimeMapping.GetMimeMapping(strFile);

        // pretend file is in memory
        byte[] FileData = File.ReadAllBytes(strFullFile);

        Response.ContentType = sMineType;
        Response.AppendHeader("Content-Disposition", "attachment; filename=" + strFile);
        Response.BinaryWrite(FileData);
        Response.End();

    }

Ok, so with above, how about we save the file(s) in question to a database?

Ok, so, in place of hitting the folder and files for the GridView?

Let's assume a database table of this:

id FileName UpLoaded User_Id MineType
6 backcover1.pdf 2024-02-22 19:03:59.717 NULL application/pdf

And one more column to above of FileB - a varbinary column for the file.

So, now our code to save a file becomes this:

    protected void AjaxFileUpload1_UploadComplete(object sender, AjaxControlToolkit.AjaxFileUploadEventArgs e)
    {
        // save file to database

        string strSQL =
            @"INSERT INTO tblFiles (FileName, UpLoaded, MineType, FileB)
            VALUES (@FileName, @UpLoaded, @MineType, @FileB)";

        SqlCommand cmdSQL = new SqlCommand(strSQL);
        cmdSQL.Parameters.Add("@FileName", SqlDbType.NVarChar).Value = e.FileName;
        cmdSQL.Parameters.Add("@UpLoaded", SqlDbType.DateTime).Value = DateTime.Now;
        cmdSQL.Parameters.Add("@MineType", SqlDbType.NVarChar).Value =
            MimeMapping.GetMimeMapping(e.FileName);

        cmdSQL.Parameters.Add("@FileB", SqlDbType.Binary).Value = e.GetContents();
        General.MyRstE(cmdSQL);
    }

And our download button from the Grid is this:

    protected void cmdDownLoad_Click(object sender, EventArgs e)
    {
        LinkButton myBut = (LinkButton)sender;
        GridViewRow gRow = (GridViewRow)myBut.NamingContainer; // get current grid row
        int PK = (int)GridFiles.DataKeys[gRow.RowIndex]["ID"];

        SqlCommand cmdSQL = new SqlCommand("SELECT * FROM tblFiles WHERE ID = @ID");
        cmdSQL.Parameters.Add("@ID", SqlDbType.Int).Value = PK;

        DataTable dt = General.MyRstP(cmdSQL);
        string strFile = dt.Rows[0]["FileName"].ToString();
        string sMineType = MimeMapping.GetMimeMapping(strFile);

        byte[] FileData = (byte[])dt.Rows[0]["FileB"];

        Response.ContentType = sMineType;
        Response.AppendHeader("Content-Disposition", "attachment; filename=" + strFile);
        Response.BinaryWrite(FileData);
        Response.End();

    }

So, above gives a good bunch of ideas here. The code to now load the grid is this:

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            RadioButtonList1.SelectedIndex = 0;
            LoadGridFiles();
            ShowArea();
        }
    }
    void LoadGridFiles ()
    {
        string strSQL =
            "SELECT * FROM tblFiles ORDER BY UpLoaded DESC";

        GridFiles.DataSource = General.MyRst(strSQL);
        GridFiles.DataBind();
    }

And the GridRow click to delete the one file, which is now just a database row is this:

    protected void cmdDelete_Click(object sender, EventArgs e)
    {
        LinkButton myBut = (LinkButton)sender;
        GridViewRow gRow = (GridViewRow)myBut.NamingContainer;
        int PK = (int)GridFiles.DataKeys[gRow.RowIndex]["ID"];

        SqlCommand cmdSQL = new SqlCommand("DELETE FROM tblFiles WHERE ID = @ID");
        cmdSQL.Parameters.Add("@ID", SqlDbType.Int).Value = PK;

        General.MyRstE(cmdSQL);
        LoadGridFiles();

    }

So, really, it is not really more work to save an uploaded file to the database, or that to some folder and file.

The only JavaScript on the page?

Well, it turns out I do need a "final" post back when all the files are done. So, I have this:

            <ajaxToolkit:AjaxFileUpload ID="AjaxFileUpload1" runat="server"
                OnUploadComplete="AjaxFileUpload1_UploadComplete"
                OnClientUploadCompleteAll="AllDone" />

Note the client-side event for above (a quirk in above, don't include the () for the function name).

So, the JavaScript code is this:

        function AllDone() {

            $('#uploadfiles').hide()
            $('#myfiles').show()
            __doPostBack();
        }

    </script>

Now "often" one will use a "hidden" standard asp.net button, style="display:none", and for above then I click that button, say like this:

        function AllDone() {


            $('#cmdButDone').click()

So, above gives me a nice button + code behind server event to run after all files are uploaded.

So, there are "lots" of ideas in above, but all of this becomes quite easy due to having adopted a great file upload control. I suggest you do the same.

Sign up to request clarification or add additional context in comments.

1 Comment

Incredible answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.