Почему моя веб-форма не обнаруживает содержимое моего listbox до OnSaveStateComplete? Как я могу обнаружить их раньше?

#c# #asp.net #drop-down-menu #listbox #page-lifecycle

#c# #asp.net #выпадающее меню #listbox #жизненный цикл страницы

Вопрос:

У меня есть ASP.NET веб-форма с двумя выпадающими списками и listbox, каскадирующими друг от друга в таком порядке:

Отдел (ddl) -> Название должности (ddl) -> Шаблон безопасности (listbox)

При загрузке список отделов заполняется. Выберите отдел, и название должности DDL заполняется заданиями, которые соответствуют этому отделу. Аналогично, как только вы выбираете опцию Job Title, тогда список шаблонов безопасности заполняется соответствующими шаблонами безопасности, доступными для этого названия должности.

Пользовательский интерфейс, похоже, работает правильно в том смысле, что, как только я выбираю название должности, шаблоны безопасности появляются в listbox. Однако я знаю, что они появляются только ближе к самому концу жизненного цикла страницы из-за вывода нескольких Debug.WriteLines ; для названия должности, содержащего четыре шаблона безопасности, эти четыре шаблона отображаются в listbox, когда я выбираю указанное название должности на своей странице, но в моем окне вывода (отладка в VS 2019) отображается только одна запись (заполняющая запись по умолчанию, прежде чем она будет заполнена реальными параметрами, «Выберите название должности»).

Я написал несколько дополнительных Debug.WriteLines и создал функции для каждого состояния жизненного цикла страницы, и они не запускались с правильным количеством до OnSaveStateComplete() , что слишком поздно в жизненном цикле страницы, чтобы что-либо делать на основе значений в listbox.

Что заставляет значения «появляться» в моей веб-форме так поздно, и как я могу изменить это, чтобы они появлялись раньше в жизненном цикле, где я мог бы что-то с ними делать?

Другими словами, как только listbox заполняется параметрами (например, как только я выбираю название должности), я хочу иметь возможность выполнять действия, основанные (условно) на новом содержимом listbox.

Если я устанавливаю autopostback="true" в listbox, то, естественно, оно обновляется и показывает правильное количество в окне вывода при выборе элемента listbox, но мне нужно, чтобы оно определяло правильное количество при выборе названия должности, а не при выборе шаблона безопасности, потому что я полагаюсь на заполнение шаблона безопасности для возникновения другого условного события (в зависимости от содержимого шаблонов безопасности, которые отображаются в listbox).

Мне кажется странным, что второй DDL работает так, как я ожидаю, при выборе чего-либо в первом DDL, но listbox не работает таким же образом, когда я выбираю что-либо во втором DDL. Я списываю это на непонимание ASP.NET жизненный цикл страницы достаточно хорошо.

Я использую C # 7.3 и нацелен на .NET Framework 4.6.1, если это имеет значение.

Form.aspx:

 <asp:Label ID="lblDeptList1" AssociatedControlID="ddlDeptList1" runat="server" Text="Employee's Department: ">
</asp:Label>
<asp:DropDownList ID="ddlDeptList1" runat="server" 
    DataSourceID="SqlDeptList1" 
    DataTextField="DepartmentName" 
    DataValueField="Department" 
    OnDataBound="ddlDeptList1_DataBound" 
    OnSelectedIndexChanged="ddlDeptList1_SelectedIndexChanged" 
    AutoPostBack="True" >
</asp:DropDownList>
<asp:SqlDataSource ID="SqlDeptList1" runat="server" 
    ConnectionString="<%$ ConnectionStrings:ConnectionString1 %>" 
    SelectCommand="SELECT [Department], [DepartmentName] FROM [Departments] ORDER BY [DepartmentName]">
</asp:SqlDataSource>

<asp:Label ID="lblJobTitle1" AssociatedControlID="ddlJobTitle1" runat="server" Text="Job Title: ">
</asp:Label>
<asp:DropDownList ID="ddlJobTitle1" runat="server" 
    DataSourceID="SqlJobList1" 
    DataTextField="JobTitle" 
    DataValueField="JobCode" 
    OnDataBound="ddlJobTitle1_DataBound" 
    OnSelectedIndexChanged="ddlJobTitle1_SelectedIndexChanged" 
    AutoPostBack="True">
</asp:DropDownList>
<asp:SqlDataSource ID="SqlJobList1" runat="server" 
    ConnectionString="<%$ ConnectionStrings:ConnectionString1 %>">
</asp:SqlDataSource>

<asp:Label ID="lblSecurityTemplates1" AssociatedControlID="lstSecurityTemplates1" runat="server" Text="Select Security Template(s): ">
</asp:Label>
<asp:ListBox ID="lstSecurityTemplates1"
    SelectionMode="Multiple" runat="server" 
    DataSourceID="SqlSecurityList1" 
    DataTextField="Template" 
    DataValueField="Template" 
    OnDataBound="lstSecurityTemplates1_DataBound">
</asp:ListBox>
<asp:SqlDataSource ID="SqlSecurityList1" runat="server" 
    ConnectionString="<%$ ConnectionStrings:ConnectionString1 %>">
</asp:SqlDataSource>
  

Соответствующие биты Form.aspx.cs:

 public partial class NewForm : Page {

    protected void ddlDeptList1_DataBound(object sender,EventArgs e) {
        ddlDeptList1.Items.Insert(0,new ListItem("Choose Department",""));
    }

    protected void ddlJobTitle1_DataBound(object sender,EventArgs e) {
        //using this to avoid the Job Title list from having two "Choose Job Title" entries if a user selects a department and then selects "Choose Department" 
        if (!(ddlJobTitle1.Items.Count == 1 amp;amp; ddlJobTitle1.Items[0].Text == "Choose Job Title")) {
            ddlJobTitle1.Items.Insert(0,new ListItem("Choose Job Title",""));
        }
    }

    protected void lstSecurityTemplates1_DataBound(object sender,EventArgs e) {
        if (!Page.IsPostBack) {
            lstSecurityTemplates1.Items.Insert(0,new ListItem("Choose a Department first",""));
        }
    }

    protected void ddlDeptList1_SelectedIndexChanged(object sender,EventArgs e) {
        SqlJobList1.SelectParameters.Clear();

        if (ddlDeptList1.SelectedIndex == 0) {
            ddlJobTitle1.Items.Clear();
            ddlJobTitle1.Items.Insert(0,new ListItem("Choose Job Title",""));
            //we want to clear the Security Templates listbox when the department changes, not just when the job title changes.
            lstSecurityTemplates1.Items.Clear();
            lstSecurityTemplates1.Items.Insert(0,new ListItem("Choose a Department first",""));
        }
        else {
            SqlJobList1.SelectParameters.Add("chosenDepartment",ddlDeptList1.SelectedItem.Value);
            SqlJobList1.SelectCommand = "SELECT JobCode, Department, JobTitle FROM JobTitles WHERE Department = @chosenDepartment ORDER BY JobTitle ASC";
            lstSecurityTemplates1.Items.Clear();
            lstSecurityTemplates1.Items.Insert(0,new ListItem("Choose a Job Title first",""));
        }
    }

    protected void ddlJobTitle1_SelectedIndexChanged(object sender,EventArgs e) {
        SqlSecurityList1.SelectParameters.Clear();

        if (ddlJobTitle1.SelectedItem.Text == "Choose Job Title") {
            lstSecurityTemplates1.Items.Clear();
            lstSecurityTemplates1.Items.Insert(0,new ListItem("Choose a Job Title first",""));
        }
        else {
            SqlSecurityList1.SelectParameters.Add("chosenJobTitle",ddlJobTitle1.SelectedItem.Text);
            SqlSecurityList1.SelectParameters.Add("chosenDepartment",ddlDeptList1.SelectedValue);
            SqlSecurityList1.SelectCommand = "SELECT DISTINCT SecurityTemplate   ' | '   SecurityTemplateName As Template FROM SecurityTemplateDB stdb INNER JOIN JobTitles jt ON jt.JobCode = stdb.JobCode WHERE jt.JobTitle = @chosenJobTitle AND jt.Department = @chosenDepartment GROUP BY stdb.SecurityTemplate, stdb.SecurityTemplateName ORDER BY Template";
            //SqlSecurityList1.DataBind(); - I tried adding this here, but it doesn't seem to make a difference.
            
            //these are the debug statements that should report 4 options when 4 are shown, but instead are reporting 1 option (the default option) when 4 are shown.
            Debug.WriteLine("Security List Item Count:");
            Debug.WriteLine(lstSecurityTemplates1.Items.Count);

        }
    }
}
  

Ответ №1:

Причина, по которой вы получаете неправильное количество результатов в вашем коде, довольно проста: вы проверяете количество элементов ДО того, как они появятся в lstSecurityTemplates1 , потому что фактическая привязка элементов к данным выполняется в .aspx, а не .aspx.cs (исходный код). Это приводит к тому, что привязка кода происходит позже в жизненном цикле.

Чтобы исправить это, вам нужно внести следующие изменения:

  1. Удалите или закомментируйте весь код SqlSecurityList1 из вашего else условия в ddlJobTitle1_SelectedIndexChanged .

  2. Удалите существующий DataSourceID="SqlSecurityList" параметр из вашего lstSecurityTemplates1 listbox на странице .aspx.

  3. Добавьте новое SqlConnection и SqlDataSource в ddlJobTitle1_SelectedIndexChanged событие в коде позади:

     SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString1"].ConnectionString);
    SqlDataSource source = new SqlDataSource();
    source.ConnectionString = conn.ConnectionString;
    source.SelectParameters.Add("chosenJobTitle",ddlJobTitle1.SelectedItem.Text);
    source.SelectParameters.Add("chosenDepartment",ddlDeptList1.SelectedValue);
    source.SelectCommand = "SELECT DISTINCT SecurityTemplate   ' | '   SecurityTemplateName As Template FROM SecurityTemplateDB stdb INNER JOIN JobTitles jt ON jt.JobCode = stdb.JobCode WHERE jt.JobTitle = @chosenJobTitle AND jt.Department = @chosenDepartment GROUP BY stdb.SecurityTemplate, stdb.SecurityTemplateName ORDER BY Template";
    
    lstSecurityTemplates1.DataSource = source;
    lstSecurityTemplates1.DataBind();
    
    Response.Write("Items in lstSecurityTemplates1 = "   lstSecurityTemplates1.Items.Count);
      

Теперь в ответе будет отображаться правильное число для названия должности, которое вы только что выбрали, как и ожидалось, а не сумма, указанная ранее в списке шаблонов.