Question

I have a list box in an ASP.NET user control that I want to populate with a potentially large List<string> retrieved from the database (up to 50k items). As the list box is being populated (in the page load event), I don't want to block the UI. Using synchronous code the page isn't displayed until the list box population is completed:

List<string> items = GetItemsFromDB();    
foreach (var item in items) // can take some time, blocks UI
{
    if (shouldItemBeListed(item))
        ListBox1.Items.Add(item);
}

I come from desktop apps background where achieving this is simple using e.g. BackgroundWorker - the user will see the list box populated with new items in real-time. Web stuff seems to be a bit more complex and I'd like some advice on what would be the best way to go about this.

Here's what I tried:

1. Creating a background thread. Doesn't work very well - everything is still pretty unresponsive compared to the desktop (WinForms) version, and the list is not updated/refreshed as expected:

Thread thread = new Thread(new ThreadStart(Populate));
thread.IsBackground = true;
thread.Start(); 

private void Populate()
{
    List<string> items = GetItemsFromDB();    
    foreach (var item in items)
    {
        if (shouldItemBeListed(item))
            ListBox1.Items.Add(item);
    }
}

2. BackgroundWorker thread. Works a bit better then 1. (at least the list box is being updated with new items), but still very unresponsive and slow.

var bw = new BackgroundWorker();
bw.DoWork += (o, args) => Populate();
bw.RunWorkerAsync();

I can always break the list box items into smaller groups and populate more as requested by user, but how is this usually implemented properly?

Was it helpful?

Solution

You can't use this approach, because of the asp.net lifecycle (and the web in general). Basically, each request to the webserver will spawn you asp.net page class, generate the html, send it to the client, and close the instance. Using multithreading technique won't help, because you will change the control tree AFTER the server has sent the answer.

To solve your issue, you should use an <asp:hidden runat='server'> field.

Then, using some kind of client side script, dynamically populate a drop down list from a web service call.

Something like this using jQuery (from memory):

<select id='ddl' onchange='document.getElementById("<%= hidValue.ClientID %>").value=this.value'>
    <option value=''>Loading...</option>
</select>
<asp:Hidden runat='server' id='hidValue' />

$(function(){
   $.ajax("... your api endpoit").then(function(result){
       var items = result.items; // actual implementation will change
       var ddl = $(ddl);
       ddl.html(''); // remove the 'loading' option.
       foreach(var item in items) {
           var option = $("<option>");
           option.attr("value",item.value);
           option.text(item.text);
           ddl.append(option);
       }    
   });
});

The reason not to use an <asp:DropDownList> is that you can't change its children between postbacks. So we use a pure client one, and sync changes in an hidden control, which can be read on postback.

OTHER TIPS

Damon had it right in that you are looking for ajax, that should give you the functionality you are looking for. I found an example frmo MSDN using a gridview, but it should be adaptable:

<%@ Page Language="C#" %>
<%@ Import Namespace="System.Collections.Generic" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Enter New Employees</title>
<script runat="server">
    private List<Employee> EmployeeList;

    protected void Page_Load()
    {
        if (!IsPostBack)
        {
            EmployeeList = new List<Employee>();
            EmployeeList.Add(new Employee(1, "Jump", "Dan"));
            EmployeeList.Add(new Employee(2, "Kirwan", "Yvette"));
            ViewState["EmployeeList"] = EmployeeList;
        }
        else
            EmployeeList = (List<Employee>)ViewState["EmployeeList"];

        EmployeesGridView.DataSource = EmployeeList;
        EmployeesGridView.DataBind();
    }

    protected void InsertButton_Click(object sender, EventArgs e)
    {
        if (String.IsNullOrEmpty(FirstNameTextBox.Text) ||
           String.IsNullOrEmpty(LastNameTextBox.Text)) { return; }

        int employeeID = EmployeeList[EmployeeList.Count-1].EmployeeID + 1;

        string lastName = Server.HtmlEncode(FirstNameTextBox.Text);
        string firstName = Server.HtmlEncode(LastNameTextBox.Text);

        FirstNameTextBox.Text = String.Empty;
        LastNameTextBox.Text = String.Empty;

        EmployeeList.Add(new Employee(employeeID, lastName, firstName));
        ViewState["EmployeeList"] = EmployeeList;

        EmployeesGridView.DataBind();
        EmployeesGridView.PageIndex = EmployeesGridView.PageCount;
    }

    protected void CancelButton_Click(object sender, EventArgs e)
    {
        FirstNameTextBox.Text = String.Empty;
        LastNameTextBox.Text = String.Empty;
    }

    [Serializable]
    public class Employee
    {
        private int _employeeID;
        private string _lastName;
        private string _firstName;

        public int EmployeeID
        {
            get { return _employeeID; }
        }

        public string LastName
        {
            get { return _lastName; }
        }

        public string FirstName
        {
            get { return _firstName; }
        }

        public Employee(int employeeID, string lastName, string firstName)
        {
            _employeeID = employeeID;
            _lastName = lastName;
            _firstName = firstName;
        }
    }

</script>
</head>
<body>
<form id="form1" runat="server">
<div>
    &nbsp;</div>
    <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePartialRendering="true"      />
    <table>
        <tr>
            <td style="height: 206px" valign="top">
                <asp:UpdatePanel ID="InsertEmployeeUpdatePanel" runat="server"     UpdateMode="Conditional">
                    <ContentTemplate>
                      <table cellpadding="2" border="0" style="background-    color:#7C6F57">
                        <tr>
                          <td><asp:Label ID="FirstNameLabel" runat="server"     AssociatedControlID="FirstNameTextBox" 
                                         Text="First Name" ForeColor="White" /></td>
                          <td><asp:TextBox runat="server" ID="FirstNameTextBox" /></td>
                        </tr>
                        <tr>
                          <td><asp:Label ID="LastNameLabel" runat="server"     AssociatedControlID="LastNameTextBox" 
                                         Text="Last Name" ForeColor="White" /></td>
                          <td><asp:TextBox runat="server" ID="LastNameTextBox" /></td>
                        </tr>
                        <tr>
                          <td></td>
                          <td>
                            <asp:LinkButton ID="InsertButton" runat="server"     Text="Insert" OnClick="InsertButton_Click" ForeColor="White" />
                            <asp:LinkButton ID="Cancelbutton" runat="server"     Text="Cancel" OnClick="CancelButton_Click" ForeColor="White" />
                          </td>
                        </tr>
                      </table>
                      <asp:Label runat="server" ID="InputTimeLabel"><%=DateTime.Now %>    </asp:Label>
                    </ContentTemplate>
                </asp:UpdatePanel>
            </td>
            <td style="height: 206px" valign="top">
                <asp:UpdatePanel ID="EmployeesUpdatePanel" runat="server"     UpdateMode="Conditional">
                    <ContentTemplate>
                        <asp:GridView ID="EmployeesGridView" runat="server"     BackColor="LightGoldenrodYellow" BorderColor="Tan"
                             BorderWidth="1px" CellPadding="2" ForeColor="Black"     GridLines="None" AutoGenerateColumns="False">
                            <FooterStyle BackColor="Tan" />
                            <SelectedRowStyle BackColor="DarkSlateBlue"     ForeColor="GhostWhite" />
                            <PagerStyle BackColor="PaleGoldenrod"     ForeColor="DarkSlateBlue" HorizontalAlign="Center" />
                            <HeaderStyle BackColor="Tan" Font-Bold="True" />
                            <AlternatingRowStyle BackColor="PaleGoldenrod" />
                            <Columns>
                                <asp:BoundField DataField="EmployeeID"     HeaderText="Employee ID" />
                                <asp:BoundField DataField="LastName" HeaderText="Last     Name" />
                                <asp:BoundField DataField="FirstName" HeaderText="First     Name" />
                            </Columns>
                            <PagerSettings PageButtonCount="5" />
                        </asp:GridView>
                        <asp:Label runat="server" ID="ListTimeLabel"><%=DateTime.Now %>    </asp:Label>
                    </ContentTemplate>
                    <Triggers>
                        <asp:AsyncPostBackTrigger ControlID="InsertButton"     EventName="Click" />
                    </Triggers>
                </asp:UpdatePanel>
            </td>
        </tr>
    </table>
</form>
</body>
</html>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top