Scenario
Upon a simple asp:Button click event (postback), iterate the rows of a GridView finding the checked RadioButton
ASP.NET - RadioButtonSpike2.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="RadioButtonSpike2.aspx.cs"
Inherits="RadioButtonSpike2" %>
<%@ Register Assembly="ConwayControls" Namespace="ConwayControls.Web" TagPrefix="ccwc" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Radio Button Spike - Scenario 2</title>
<style type="text/css" media="all">
div {padding-top: 10px;}
h3 {display: inline;}
</style>
</head>
<body>
<form id="RadioButtonSpikeForm" runat="server">
<div>
<asp:GridView ID="SurveyGrid" runat="server" AutoGenerateColumns="False">
<Columns>
<asp:TemplateField HeaderText="Language">
<ItemTemplate>
<asp:Label ID="NameLabel" runat="server" Text='<%# Eval("Name") %>' />
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Favorite">
<ItemTemplate>
<ccwc:RadioButton ID="FavoriteButton" runat="server" GroupName="FavoriteGroup"
Value='<%# Eval("Id") %>' />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<div>
<asp:Button ID="ForPostBackButton" runat="server" Text="for post back"
OnClick="ForPostBackButton_Click" />
</div>
<div>
<asp:Label ID="ResultsLabel" runat="server" />
</div>
</div>
</form>
</body>
</html>
C# (code behind) - RadioButtonSpike2.aspx.cs
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Generic;
using CCW = ConwayControls.Web;
public partial class RadioButtonSpike2 : Page {
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
if (this.IsPostBack) return;
this.BindGrid();
}
protected void BindGrid() {
List<SurveyItem> items = new List<SurveyItem>();
items.Add(new SurveyItem(1, "C#"));
items.Add(new SurveyItem(2, "VB"));
items.Add(new SurveyItem(3, "Perl"));
items.Add(new SurveyItem(4, "Java"));
items.Add(new SurveyItem(5, "Ruby"));
this.SurveyGrid.DataSource = items;
this.SurveyGrid.DataBind();
}
protected void ForPostBackButton_Click(object sender, EventArgs e) {
foreach (GridViewRow row in this.SurveyGrid.Rows) {
CCW.RadioButton radioButton = row.FindControl("FavoriteButton") as CCW.RadioButton;
if (radioButton == null || !radioButton.Checked) continue;
//the following label demonstrates the ability of grabbing other controls in the row and using it...
Label label = row.FindControl("NameLabel") as Label;
if (label != null) {
string format = "favorite language: <h3>{0} (Id: {1})</h3>";
this.ResultsLabel.Text = string.Format(format, label.Text, radioButton.Value);
break;
}
}
}
}
public class SurveyItem {
public SurveyItem(int id, string name) {
this.id = id;
this.name = name;
}
public int Id {
get { return this.id; }
set { this.id = value; }
} private int id;
public string Name {
get { return this.name; }
set { this.name = value; }
} private string name;
}
It is a good practice when iterating over a collection to continue after an unwanted test and break once you have found the item you were looking for.
As noted in the iteration of the GridViewRows inside the ForPostBackButton_Click method, I tested to ensure the RadioButton was found, that it was checked, and continued if it wasn't by using the continue keyword. Once the checked RadioButton was found, I broke the iteration using the break keyword. Both statements/uses prevent any unnecessary code execution which is even more important during iteration of a collection.
Please let me know if you have any questions with this particular scenario.
Thanks,
Jason Conway
Having META tags like Description and Keywords on the .aspx pages of our sites is very important. It is also important that the content of the tags be easily added and/or updated. Most online help will have you instantiate an HtmlMeta class or write tags by implementing some variance of either:
- Option 1 - create a base Page class with methods or properties and create an external persistence store for META content
- Option 2 - create a base Page class with virtual methods or properties and override those on the deriving page
- Option 3 - create an Interface or abstract class with methods or properties that the deriving page will employ
- Option 4 - use a third-party component or write a control
- Option 5 - on every page, write or use existing implementation to create META tags
While my solution also uses the HtmlMeta class, it differs in architecture and uses the following guidelines:
- create META tags on pages in a new or existing Site
- no base Page class, abstract class, or Interface that all existing or new pages inherit
- no database or external store for persisting Tag content
- no controls or third-party components
- just want META content and not the actual tag creation on every page
- must provide unique content per page
- must provide simple, maintainable content changes without recompile
- must provide the ability to change content with basic skills and without a special interface
In other words, I did not want to create a base Page, Interface, control, or third-party component that every single one of the pages in my site derives from or uses. In addition having to ensure that any newly created pages inherited the base Page or implement some Interface is not as easily maintainable. I can also use this solution on existing sites without having to worry about any Page inheritance issues.
What better place to keep information about an .aspx file than right in the .aspx file? Most markup resides in each .aspx file using ContentPlaceHolders anyway and when you create a new page, these ContentPlaceHolders will be included automatically! For this post, I am only going to demonstrate adding META Description and Keywords tags, but this process can be applied to as many tags as you need. One thing that is great about this approach, is that you will not need a database or external file (like xml) to store META content...making the content very easily updated with a simple text editor (like Notepad) and no additional programming or database skills!
DOWNLOAD - Meta Spike Files
This is how you do it (the source files are available for download above):
- drop 2 ContentPlaceHolders in a MasterPage's head tag, 1 for Description and 1 for Keywords making sure to set Visible="false"
- wire up Load events on the ContentPlaceHolders and LoadComplete on the MasterPage's Page property
- implement handled events
- create or update existing page with new Description and Keywords ContentPlaceHolders add content to place holders
- view page in browser and inspect rendered source
Step 1 - default.master
<%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Default.master.cs" Inherits="MetaSpike.Default" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title></title>
<asp:ContentPlaceHolder id="MetaDescriptionHolder" runat="server" Visible="false"></asp:ContentPlaceHolder>
<asp:ContentPlaceHolder ID="MetaKeywordsHolder" runat="server" Visible="false"></asp:ContentPlaceHolder>
</head>
<body>
<form id="DefaultMasterForm" runat="server">
<div>
<asp:ContentPlaceHolder ID="MainContentHolder" runat="server"></asp:ContentPlaceHolder>
</div>
</form>
</body>
</html>
Step 2 - default.master.cs
using System;
using System.Web.UI;
namespace MetaSpike {
public partial class Default : MasterPage {
protected override void OnInit(EventArgs e) {
base.OnInit(e);
this.MetaDescriptionHolder.Load += new EventHandler(MetaDescriptionHolder_Load);
this.MetaKeywordsHolder.Load += new EventHandler(MetaKeywordsHolder_Load);
this.Page.LoadComplete += new EventHandler(Page_LoadComplete);
}
private void MetaDescriptionHolder_Load(object sender, EventArgs e) {
throw new NotImplementedException();
}
private void MetaKeywordsHolder_Load(object sender, EventArgs e) {
throw new NotImplementedException();
}
private void Page_LoadComplete(object sender, EventArgs e) {
throw new NotImplementedException();
}
}
}
Step 3 - default.master.cs (continued)
using System;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;
namespace MetaSpike {
public partial class Default : MasterPage {
private HtmlMeta descriptionMeta;
private HtmlMeta keywordsMeta;
protected override void OnInit(EventArgs e) {
base.OnInit(e);
this.MetaDescriptionHolder.Load += new EventHandler(MetaDescriptionHolder_Load);
this.MetaKeywordsHolder.Load += new EventHandler(MetaKeywordsHolder_Load);
this.Page.LoadComplete += new EventHandler(Page_LoadComplete);
}
private void MetaDescriptionHolder_Load(object sender, EventArgs e) {
string content = this.ParseHolderContent(this.MetaDescriptionHolder);
if (string.IsNullOrEmpty(content)) return;
this.descriptionMeta = new HtmlMeta();
this.descriptionMeta.Name = "description";
this.descriptionMeta.Content = content;
}
private void MetaKeywordsHolder_Load(object sender, EventArgs e) {
string content = this.ParseHolderContent(this.MetaKeywordsHolder);
if (string.IsNullOrEmpty(content)) return;
this.keywordsMeta = new HtmlMeta();
this.keywordsMeta.Name = "keywords";
this.keywordsMeta.Content = content;
}
private void Page_LoadComplete(object sender, EventArgs e) {
Page page = sender as Page;
if (page == null) return;
if (this.descriptionMeta != null) page.Header.Controls.Add(this.descriptionMeta);
if (this.keywordsMeta != null) page.Header.Controls.Add(this.keywordsMeta);
}
private string ParseHolderContent(ContentPlaceHolder holder) {
if (holder == null || holder.Controls.Count == 0) return string.Empty;
LiteralControl control = holder.Controls[0] as LiteralControl;
if (control == null || string.IsNullOrEmpty(control.Text)) return string.Empty;
return control.Text.Trim();
}
}
}
Step 4 - home.aspx
<%@ Page Language="C#" MasterPageFile="~/Default.Master" AutoEventWireup="true"
CodeBehind="Home.aspx.cs" Inherits="MetaSpike.Home" Title="Meta Spike Home" %>
<asp:Content ID="MetaDescription" ContentPlaceHolderID="MetaDescriptionHolder" runat="server">description goes here</asp:Content>
<asp:Content ID="MetaKeywords" ContentPlaceHolderID="MetaKeywordsHolder" runat="server">keywords, .net, asp.net, meta</asp:Content>
<asp:Content ID="MainContent" ContentPlaceHolderID="MainContentHolder" runat="server">
this page now has a very simple mechanism for adding and updating meta tags! enjoy!
</asp:Content>
Step 5 - view source rendered home.aspx
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head>
<title>Meta Spike Home</title>
<meta name="description" content="description goes here" />
<meta name="keywords" content="keywords, .net, asp.net, meta" />
</head>
<body>
<form name="aspnetForm" method="post" action="Home.aspx" id="aspnetForm">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE"
value="/wEPDwUJNDMyNDU0NjAzZGQfTG4D56NhuIUPL9dxPlf1j85RXw==" />
</div>
<div>
this page now has a very simple mechanism for adding and updating meta tags! enjoy!
</div>
</form>
</body>
</html>
Some things to note:
- ContentPlaceHolders on the MasterPage have Visible set to false
- the use of the Page's LoadComplete event instead of Load
- only straight text in the Content tags on Home.aspx, ASP.NET will put that text into LiteralControls
- no text in the Content tags on Home.aspx will result in no META tags being output (good thing)
This will prevent you from having to write more code than you need and leaves the actual content on the page itself (where it belongs). In addition, you won't have a database or special component to worry about or pay for.
I hope you find this approach very simple, maintainable, and easily employed.
Jason Conway

Scenario
Upon a simple asp:Button click event (postback), capture the CheckChanged event of the RadioButton ASP.NET - RadioButtonSpike1.aspx
<%@ Page Language="C#" AutoEventWireup="true" CodeFile="RadioButtonSpike1.aspx.cs" Inherits="RadioButtonSpike1" %>
<%@ Register Assembly="ConwayControls" Namespace="ConwayControls.Web" TagPrefix="ccwc" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
<title>Radio Button Spike - Scenario 1</title>
<style type="text/css" media="all">
div {padding-top: 10px;}
h3 {display: inline;}
</style>
</head>
<body>
<form id="RadioButtonSpikeForm" runat="server">
<div>
<asp:GridView ID="SurveyGrid" runat="server" AutoGenerateColumns="False">
<Columns>
<asp:TemplateField HeaderText="Language">
<ItemTemplate>
<asp:Label ID="NameLabel" runat="server" Text='<%# Eval("Name") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Favorite">
<ItemTemplate>
<ccwc:RadioButton ID="FavoriteButton" runat="server" GroupName="FavoriteGroup"
OnCheckChanged="FavoriteButton_CheckChanged" Value='<%# Eval("Name") %>' />
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
<div>
<asp:Button ID="ForPostBackButton" runat="server" OnClick="ForPostBackButton_Click" Text="for post back" />
</div>
<div>
<asp:Label ID="ResultsLabel" runat="server"></asp:Label>
</div>
</div>
</form>
</body>
</html>
C# (code behind) - RadioButtonSpike1.aspx.cs
using System;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Collections.Generic;
using CCW = ConwayControls.Web;
public partial class RadioButtonSpike1 : Page {
protected override void OnLoad(EventArgs e) {
base.OnLoad(e);
if (this.IsPostBack) return;
this.BindGrid();
}
protected void BindGrid() {
List<SurveyItem> items = new List<SurveyItem>();
items.Add(new SurveyItem(1, "C#"));
items.Add(new SurveyItem(2, "VB"));
items.Add(new SurveyItem(3, "Perl"));
items.Add(new SurveyItem(4, "Java"));
items.Add(new SurveyItem(5, "Ruby"));
this.SurveyGrid.DataSource = items;
this.SurveyGrid.DataBind();
}
protected void ForPostBackButton_Click(object sender, EventArgs e) {
//do nothing...just needed the postback
}
protected void FavoriteButton_CheckChanged(object sender, EventArgs e) {
CCW.RadioButton radioButton = sender as CCW.RadioButton;
if (radioButton != null && radioButton.Checked) {
this.ResultsLabel.Text = string.Format("your favorite language is: <h3>{0}</h3>", radioButton.Value);
}
}
}
public class SurveyItem {
public SurveyItem(int id, string name) {
this.id = id;
this.name = name;
}
public int Id {
get { return this.id; }
set { this.id = value; }
} private int id;
public string Name {
get { return this.name; }
set { this.name = value; }
} private string name;
}
Now keep in mind that in this scenario and implementation, the CheckChanged event will fire if the RadioButton changes from being checked or unchecked on any postback. That means that if your grid has paging or a different unrelated button that posts, you are not going to want to ignore this event when it happens.
You will still use this implementation for grids that page or unrelated postbacks; you will just need to keep track of changes by using some sort of state mechanism. The other ways to use the RadioButton are:
- enable AutoPostBack
- iterate over all the rows in the grid (sledge hammer approach)
I will demonstrate these other scenarios, including a grid with paging, in posts to follow.
Please let me know if you have any questions with this particular scenario.
Thanks,
Jason Conway
ConwayControls.zip
The .zip file above contains a custom RadioButton control that works inside a DataGrid or GridView.
Can be used for both 1.1 or 2.0!
