1

I'm retrofitting an older web forms site with Model Binding using asp.net 4.5.

In a DetailsView I have a drop down list that allows selection of a particular 'client' and another that allows selection of a particular 'project' that belongs to that client. So the drop down for project has to be filtered on the client number and if the user changes the client selection, I want to filter the project list by client number.

I couldn't figure out how to get the SelectedIndexChanged method on the client ddl to fire the Select method for the Project, so I concluded the only way to do it was to filter the project ddl by client number in all cases. I am getting an error message when the client selection is made:

NullReferenceException was Unhandled by User Code

which point in my aspx directly to the ddl for Projects.

This is an abbreviated version of the details view, you can see both the clients ddl and the projects ddl (I am operating in Edit mode):

<asp:DetailsView ID="AdministratorDetailsView" runat="server" AutoGenerateRows="False"
     DataKeyNames="AdministratorNumber" ItemType="BusinessLogic.Administrator" 
     Width="99%" 
     SelectMethod="AdministratorDetailsView_GetItem" 
     UpdateMethod="AdministratorDetailsView_UpdateItem" 
     DeleteMethod="AdministratorDetailsView_DeleteItem" 
     FieldHeaderStyle-Width="30%" EditRowStyle-Width="99%" 
     InsertRowStyle-Width="70%" RowStyle-Width="99%" CssClass="admin" 
     AutoGenerateDeleteButton="true" AutoGenerateEditButton="true" >
     <Fields>
         <asp:TemplateField HeaderText="AdministratorCode" SortExpression="AdministratorCode">
             <EditItemTemplate>
                 <asp:Label ID="AdministratorCode" Text="<%# Item.AdministratorCode%>" runat="server" />
             </EditItemTemplate>
             <ItemTemplate>
                 <asp:Label ID="AdministratorCode" Text="<%# Item.AdministratorCode%>" runat="server" />
             </ItemTemplate>
         </asp:TemplateField>
         <asp:TemplateField HeaderText="ClientNumber" SortExpression="ClientNumber">
             <EditItemTemplate>
                  <asp:DropDownList ID="ddClients" runat="server" 
                       AutoPostBack="true" 
                       DataTextField="ClientName" DataValueField="ClientNumber" 
                       ItemType="BusinessLogic.Client"  
                       SelectMethod="ddClients_GetList"  
                       SelectedValue="<%# Item.ClientNumber%>" 
                       OnSelectedIndexChanged="AdministratorDetailsView_ddlClients_SelectedIndexChanged"/>
             </EditItemTemplate>
             <ItemTemplate>
                 <asp:DropDownList ID="ddClients" runat="server" Enabled="false" 
                      DataTextField="ClientName" DataValueField="ClientNumber" 
                      ItemType="BusinessLogic.Client"  
                      SelectMethod="ddClients_GetList"  
                      SelectedValue="<%# Item.ClientNumber%>"/>
             </ItemTemplate>
         </asp:TemplateField>

         <asp:TemplateField HeaderText="Projects" >
             <EditItemTemplate>
                 <asp:DropDownList ID="ddProjects" runat="server" AutoPostBack="true" 
                      DataTextField="ProjectName" DataValueField="ProjectNumber" 
                      ItemType="BusinessLogic.Project"  
                      SelectMethod="ddProjects_GetList"  
                      SelectedValue="<%# Item.ProjectNumber%>"  />
             </EditItemTemplate>
             <ItemTemplate>
                  <asp:DropDownList ID="ddProjects" runat="server" Enabled="false" 
                       DataTextField="ProjectName" DataValueField="ProjectNumber" 
                       ItemType="BusinessLogic.Project"  
                       SelectMethod="ddProjects_GetList"  
                       SelectedValue="<%# Item.ProjectNumber%>"/>
             </ItemTemplate>
         </asp:TemplateField>
     </Fields>
 </asp:DetailsView>

There are two SelectMethods, one for each drop down list, and a selectedindexchaneged method for clients:

Public Function ddClients_GetList() As List(Of BusinessLogic.Client)
    Dim special As New List(Of Client)
    special = CurrentClient.ClientList() 'add whole list
    Dim NullClient As New Client()
    NullClient.Load(0)
    NullClient.ClientName = "<-Not Selected-->"
    special.Add(NullClient) 'Had to have a client with 0 in the list since most admins don't have anything but 0 inthis field

    Return special
End Function

Public Function ddProjects_GetList(<Control("ddClients")> ByVal ClientNumber As Integer) As List(Of BusinessLogic.Project)
    Dim special As New List(Of Project)

    If ClientNumber = 0 Then
        special = CurrentProject.ProjectList() 'add whole list, refine it if Clients Drop Down selected
    Else
        special = CurrentProject.ProjectList(ClientNumber) 'add whole list, refine it if Clients Drop Down selected
    End If

    Dim NullProject As New Project()
    NullProject.Load(0)
    NullProject.ProjectName = "<-Not Selected-->"
    special.Add(NullProject) 'Had to have a Project with 0 in the list since most admins don't have anything but 0 inthis field
    Return special
End Function

Protected Sub AdministratorDetailsView_ddlClients_SelectedIndexChanged(sender As Object, e As EventArgs)
    Dim ddlProj As DropDownList
    ddlProj = AdministratorDetailsView.FindControl("ddProjects")
    ddlProj.ItemType = "BusinessLogic.Project"
    ddlProj.DataBind()

End Sub

All is well until the user selects a different client which triggers the SelectIndexChanged event and then we get to the DataBind(), where we get the null exception (ddlProj is found).

Need some ideas on how to refresh the projects list based on the new client selection.

How do I force ddProjects to run its SelectMethod again, to avoid the null reference and reload the control?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
PhilM
  • 55
  • 1
  • 9
  • Almost all cases of `NullReferenceException` are the same. Please see "[What is a NullReferenceException in .NET?](http://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-in-net)" for some hints. – John Saunders Dec 29 '13 at 05:21
  • Thanks, one of the issues is it is not possible to determine exactly what is null, although it's very likely it is the control ddProjects because DataBinding takes place 'under the covers' and the exception points to ddProjects in the aspx. So I rephrased my question a bit. Thanks – PhilM Dec 29 '13 at 14:33

2 Answers2

1

Turns out this issue is not unique to ModelBinding, but is a General WebForms Issue. You cannot have cascading dropdowns within another data control where they depend on each other unless you add a bit of code. In my case, I was trying to bind both dropdowns to the underlying detailsview, and at the same time setting the selected values. Doesn't work because of timing. It seems right until you try to change the selection on the parent dropdown, and then you get null exception on the second because it is not yet loaded. It's all timing. If you try to work around it with saved values, it gets too messy. My solution is, Bind another item to the underlying details view (you can make it hidden if you wish). Then add some code to keep it in sync with the actual value. DO NOT Bind SelectedValue on the secondary drop down , this is what causes the problem. Manage Selected Value in code using the added field.

<asp:DetailsView ID="AdministratorDetailsView" runat="server" AutoGenerateRows="False"
                    DataKeyNames="AdministratorNumber" ItemType="BusinessLogic.Administrator" 
                    Width="99%" SelectMethod="AdministratorDetailsView_GetItem" UpdateMethod="AdministratorDetailsView_UpdateItem" DeleteMethod="AdministratorDetailsView_DeleteItem" 
                    FieldHeaderStyle-Width="30%" EditRowStyle-Width="99%" InsertRowStyle-Width="70%" RowStyle-Width="99%" CssClass="admin" AutoGenerateDeleteButton="true" AutoGenerateEditButton="true" 
                   >
                  <Fields>

                      <asp:TemplateField HeaderText="Client Name" SortExpression="ClientName">
                          <EditItemTemplate><asp:DropDownList ID="ddClients" runat="server" AutoPostBack="true" DataTextField="ClientName" DataValueField="ClientNumber" ItemType="BusinessLogic.Client"  SelectMethod="ddClients_GetList"  SelectedValue="<%# Item.ClientNumber%>" OnDataBound="ddClients_DataBound" OnSelectedIndexChanged="AdministratorDetailsView_ddlClients_SelectedIndexChanged" /> </EditItemTemplate>
                          <ItemTemplate><asp:DropDownList ID="ddClients" runat="server" Enabled="false" DataTextField="ClientName" DataValueField="ClientNumber" ItemType="BusinessLogic.Client"  SelectMethod="ddClients_GetList"  SelectedValue="<%# Item.ClientNumber%>"/></ItemTemplate>
                      </asp:TemplateField>

                      <asp:TemplateField HeaderText ="Project Number" >
                          <EditItemTemplate><asp:Label ID="ProjectNumberLabel" runat="server" Text="<%# BindItem.ProjectNumber%>"></asp:Label></EditItemTemplate>
                      </asp:TemplateField>
                      <asp:TemplateField HeaderText="Project Name" SortExpression="ProjectNumber">
                          <EditItemTemplate><asp:DropDownList ID="ddProjects" runat="server" AutoPostBack="true" DataTextField="ProjectName" DataValueField="ProjectNumber" ItemType="BusinessLogic.Project"  SelectMethod="ddProjects_GetList"  OnDataBound="ddProjects_DataBound"  OnSelectedIndexChanged="ddProjects_SelectedIndexChanged"  />
                          </EditItemTemplate>
                          <ItemTemplate><asp:DropDownList ID="ddProjects" runat="server" Enabled="false" DataTextField="ProjectName" DataValueField="ProjectNumber" ItemType="BusinessLogic.Project"  SelectMethod="ddProjects_GetList" SelectedValue="<%# Item.ProjectNumber%>" />

                          </ItemTemplate>
                      </asp:TemplateField>

                </asp:DetailsView>

And in the code behind, not how we set and get the value of the bound projectnumber: Protected Sub AdministratorDetailsView_ddlClients_SelectedIndexChanged(sender As Object, e As EventArgs) ''If this selection changes we need to change some flags and perhaps theProject ddl ''This causes null exception for Project ddl, can't figure out how to get it to reload. Tried DataBind(), same effect as having a ValueProvider in the parameters to the Select

    Dim ProjectNumberLabel As Label = AdministratorDetailsView.FindControl("ProjectNumberLabel")
    ProjectNumberLabel.Text = "0" 'Synchronize to no selection
    Dim ddProjects As New DropDownList
    ddProjects = AdministratorDetailsView.FindControl("ddProjects") 'Have to use Find Control because ddl is buried in DetailsView
    ddProjects.SelectedValue = 0 'Cause it to read Select an Item
    ddProjects.DataBind() 'Rebinding it causes the SelectMethod to run.

End Sub


Protected Sub ddProjects_DataBound(sender As Object, e As EventArgs)
    'Because of the problem with cascading references, we had to remove the SelectValue declaration from ddProjects.
    'So we must make sure that once theddProjects is databound, it points to the selected value from the data
    Dim ddlProjects As DropDownList
    ddlProjects = AdministratorDetailsView.FindControl("ddProjects")
    Dim ProjectNumberLabel As Label = AdministratorDetailsView.FindControl("ProjectNumberLabel")
    ddlProjects.SelectedValue = CInt(ProjectNumberLabel.Text)

End Sub

Hope someone else sees this as useful. I couldn't find it anywhere.

PhilM
  • 55
  • 1
  • 9
0

Great posting, as I faced a similar conundrum and it made me think. I have done this before outside of the model-binding approach, and the solution is essentially the same, as described. I used a ListView, and added the method:

OnItemDataBound="lvWhatever_ItemDataBound"

to fire an event from which to go and get the relevant ID from another bound control in the list view data item:

        if (e.Item.ItemType == ListViewItemType.DataItem)
        {
            // get the ID from from another control in this item record: lblItemID           
            ListViewDataItem lvdi = (ListViewDataItem)e.Item;
            if (lvdi != null)
            {
                Label iID = (Label)lvdi.FindControl("lblItemID");
                if (iID != null)

where lblItemID.Text contains the relevant ID. Then from that I can query to get the relevant list, and manually bind it to the DDL. Brilliant - thanks very much.

Ian_G
  • 1
  • 1