Pages

Thursday, May 24, 2012

LINQ and the ASP.NET Repeater control


I was recently asked a question about the ASP.NET Repeater control and I thought it would make a good article. One of my colleagues had a repeater control, and in each row they had a radio button, along with several columns of data to display to the user. One of the columns of data was a unique identifier that identified which customer the user was browsing. My colleague couldn’t work out how to find the unique identifier for the selected customer. I said use LINQ! To demonstrate this I’m going to create some fake data to show you how to handle this scenario. 
To begin with open Visual Studio 2008 and choose File > New > Web > ASP.NET Web Application. The first thing to do is to create a class that will be the structure of our data. Add a new class to the project and name it People. Add the following code to the People class:
 
C#
 
public class People
{
public int UniqueId { getset; }
      public string GivenName { getset; }
      public string Surname { getset; }
      public int Height { getset; }
      public int ShoeSize { getset; }
      public int Age { getset; }       
}
  
VB.NET
 
Public Class People
Private privateUniqueId As Integer
Public Property UniqueId() As Integer
      Get
            Return privateUniqueId
      End Get
      Set(ByVal value As Integer)
            privateUniqueId = value
      End Set
End Property
       Private privateGivenName As String
       Public Property GivenName() As String
             Get
                   Return privateGivenName
             End Get
             Set(ByVal value As String)
                   privateGivenName = value
             End Set
       End Property
       Private privateSurname As String
       Public Property Surname() As String
             Get
                   Return privateSurname
             End Get
             Set(ByVal value As String)
                   privateSurname = value
             End Set
       End Property
       Private privateHeight As Integer
       Public Property Height() As Integer
             Get
                   Return privateHeight
             End Get
             Set(ByVal value As Integer)
                   privateHeight = value
             End Set
       End Property
       Private privateShoeSize As Integer
       Public Property ShoeSize() As Integer
             Get
                   Return privateShoeSize
             End Get
             Set(ByVal value As Integer)
                   privateShoeSize = value
             End Set
       End Property
       Private privateAge As Integer
       Public Property Age() As Integer
             Get
                   Return privateAge
             End Get
             Set(ByVal value As Integer)
                   privateAge = value
             End Set
       End Property
End Class
 
The UniqueId property will be the unique identifier for the user, so naturally we do not want to show this to the user. The next step is to add a repeater control to the default.aspx page. Add the following code:
 
<asp:Repeater ID="rptPeople" runat="server"
            OnItemDataBound="rptPeople_ItemDataBound">
<HeaderTemplate>
            <table>
</HeaderTemplate>
      <ItemTemplate>
            <tr>
            <td>
                  <asp:RadioButton ID="rdoSelected" GroupName="Person"
                            TextAlign="Right" runat="server"
                            Text='<%# Eval("GivenName") %>' />
                  <asp:HiddenField ID="hdnUniqueId" runat="server"
                            Value='<%# Eval("UniqueId") %>' />
</td>
            <td>
                  <asp:Label ID="Label3" runat="server"
                            Text='<%# Eval("Surname") %>' />
</td>
            <td>
                  <asp:Label ID="lblSurname" runat="server"
                            Text='<%# Eval("Height") %>' />
</td>
            <td>
                  <asp:Label ID="Label1" runat="server"
                            Text='<%# Eval("ShoeSize") %>' />
</td>
            <td>
                  <asp:Label ID="Label2" runat="server"
                            Text='<%# Eval("Age") %>' />
</td>
</tr>
</ItemTemplate>
<FooterTemplate>
            </table>
</FooterTemplate>
</asp:Repeater>
<asp:Button ID="btnSubmit" runat="server"
                Text="Submit" OnClick="Button1_Click" />
 
If you ran the code right now, the user would be able to select every radio button, even though I have specified a GroupName. This is because the repeater control mangles the GroupName when it is rendered to the browser. The workaround for this is the following JavaScript I found here:
 
<script language="javascript" type="text/javascript">
        function SetUniqueRadioButton(nameregex, current) {
            re = new RegExp(nameregex);
            for (i = 0; i < document.forms[0].elements.length; i++) {
                elm = document.forms[0].elements[i]
                if (elm.type == 'radio') {
                    if (re.test(elm.name)) {
                        elm.checked = false;
                    }
                }
            }
            current.checked = true;
        }       
</script>
 
This code needs to be set when an item is bound to the repeater. Add the following ItemDataBound code:
 
C#
 
protected void rptPeople_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType != ListItemType.Item && e.Item.ItemType !=ListItemType.AlternatingItem)
      {
            return;
}
RadioButton rdo = e.Item.FindControl("rdoSelected"as RadioButton;
      string script = "SetUniqueRadioButton('rptPeople.*Person',this)";
      rdo.Attributes.Add("onclick", script);
}
 
VB.NET
 
Protected Sub rptPeople_ItemDataBound(ByVal sender As ObjectByVal e AsRepeaterItemEventArgs)
If e.Item.ItemType <> ListItemType.Item AndAlso e.Item.ItemType <> ListItemType.AlternatingItem Then
             Return
End If
Dim rdo As RadioButton = TryCast(e.Item.FindControl("rdoSelected"), RadioButton)
       Dim script As String = "SetUniqueRadioButton('rptPeople.*Person',this)"
       rdo.Attributes.Add("onclick", script)
End Sub
 
That will ensure the user can only select one radio button at a time, which is normal functionality. To add data to the people class, add the following code to the page load event:
 
C#
 
protected void Page_Load(object sender, EventArgs e)
{
if (!IsPostBack)
      {
            List<People> people = new List<People>();
            people.Add(new People()
            {
                  GivenName = "Malcolm",
                  Surname = "Sheridan",
                  Age = 32,
                  Height = 185,
                  ShoeSize = 13,
                  UniqueId = 1
});
            people.Add(new People()
            {
                  GivenName = "Suprotim",
                  Surname = "Argawal",
                  Age = 28,
                  Height = 175,
                  ShoeSize = 10,
                  UniqueId = 2
});
           rptPeople.DataSource = people;
            rptPeople.DataBind();
}     
}
 
VB.NET
 
Protected Sub Page_Load(ByVal sender As ObjectByVal e As EventArgs)
If (Not IsPostBack) Then
             Dim people As New List(Of People)()
                  people.Add(New People() With {.GivenName = "Malcolm", .Surname = "Sheridan", .Age = 32, .Height = 185, .ShoeSize = 13, .UniqueId = 1})
                  people.Add(New People() With {.GivenName = "Suprotim", .Surname = "Argawal", .Age = 28, .Height = 175, .ShoeSize = 10, .UniqueId = 2})
                  rptPeople.DataSource = people
                  rptPeople.DataBind()
End If
End Sub
 
The last thing to do is to find out which row the user has selected. For this task I’m going to use LINQ. Add the following code to find the selected user:
 
C#
 
protected void Button1_Click(object sender, EventArgs e)
{
var query = (from p in rptPeople.Items.OfType<RepeaterItem>()
                  let o = p.Controls[1] as RadioButton
                  let hid = p.Controls[3] as HiddenField
                  where o.Checked
                  select new
                  {
                        GivenName = o.Text,
                        UniqueId = hid.Value
}).Single();
Response.Write(query.UniqueId);
}
 
VB.NET
 
Protected Sub Button1_Click(ByVal sender As ObjectByVal e As EventArgs)
Dim query = ( _
    From p In rptPeople.Items.OfType(Of RepeaterItem)() _
    Let o = TryCast(p.Controls(1), RadioButton) _
    Let hid = TryCast(p.Controls(3), HiddenField) _
    Where o.Checked _
    Select New With {Key .GivenName = o.Text, Key .UniqueId = hid.Value}).Single()
Response.Write(query.UniqueId)
End Sub
 
To see which radio button is selected, I am using the let keyword. This creates a temporary variable only available to the LINQ query. I also have another local variable that finds the UniqueId, which assigns the value to the hid variable. Finally the Single method is called to return the single record. If you run the project now and select a customer, the UniqueId will be displayed:
 

No comments:

Post a Comment