Charting and plotting on ASP.NET websites using the Highcharts JQuery library

 

There are several JQuery library’s out there to show graphs and charts on websites using JQuery. I’ve personally worked with the Highcharts library and found it to be very extensive and easy to use. Highcharts is an open source JQuery library that you can get from http://www.highcharts.com.
It’s free for non-commercial use and the extensive demo-gallery (that also includes the jquery code used to generate the different graphs) is great coding help.

Example: generating a dynamic graph with highcharts

The example code for a line, column an pie chart can found here. The example below only describes the line graph creation.
The method to create the different graphs is the same. The format of the series string may differ depending on the graph.

An example of the result when running the downloaded example

image

Steps on how to create the line chart example:

1. Create a new webapplication

2. Add the highcharts javascript to the solution. (They can be downloaded from http://www.highcharts.com)

image

3. Add a javascript file to the solution containing the code for the chart as shown below. Note that I have substituted some values with a variable name that will be filled from the ASP.Net code and replaced the code for the series with a unique string that can be searched a replaced. The reason for this is that the file contains brackets ( { } ) so the preferred method of using string.Format() to replace the hard coded values will not work.

   1:  var columnchart;
   2:  $(document).ready(function () {
   3:      columnchart = new Highcharts.Chart({
   4:          chart: {
   5:              renderTo: columnContainerVar,
   6:              defaultSeriesType: 'column'
   7:          },
   8:          title: {
   9:              text: columnTitleVar
  10:          },
  11:          xAxis: {
  12:              categories: [
  13:                              'Jan',
  14:                              'Feb',
  15:                              'Mar',
  16:                              'Apr',
  17:                              'May',
  18:                              'Jun',
  19:                              'Jul',
  20:                              'Aug',
  21:                              'Sep',
  22:                              'Oct',
  23:                              'Nov',
  24:                              'Dec'
  25:                          ]
  26:          },
  27:          yAxis: {
  28:              min: 0,
  29:              title: {
  30:                  text: 'Ammount'
  31:              }
  32:          },
  33:          tooltip: {
  34:              formatter: function () {
  35:                  return '' +
  36:                                  this.x + ': €' + this.y ;
  37:              }
  38:          },
  39:          plotOptions: {
  40:              column: {
  41:                  pointPadding: 0.2,
  42:                  borderWidth: 0
  43:              }
  44:          },
  45:          series: [%seriesString%]
  46:      });
  47:   
  48:   
  49:  });

4. Add code to generate a series string. An example is shown in the code snippets below.

Support class to hold and generate valid data:

   1:   public class Expense
   2:      {
   3:          public int Amount { get; set; }
   4:          public DateTime Date { get; set; }
   5:   
   6:          public static List<Expense> GetAmountRandomAmountList()
   7:          {
   8:              Random randomizer = new Random();
   9:              List<Expense> result = new List<Expense>();
  10:              DateTime startDate = new DateTime(2009, 1, 1);
  11:   
  12:              for (int index = 0; index < 24; index++)
  13:              {
  14:                  result.Add(new Expense { 
  15:                      Amount = randomizer.Next(100),
  16:                      Date = startDate
  17:                  });
  18:                  startDate = startDate.AddMonths(1);
  19:              }
  20:   
  21:              return result;
  22:          }
  23:      }

Support function in default.aspx to set the gerated list into the correct format for the graph:

   1:  private string GetSeriesString()
   2:  {
   3:      List<Expense> expenses = Expense.GetAmountRandomAmountList();
   4:   
   5:      List<string> expensesLinePoints = new List<string>();
   6:      StringBuilder seriesString = new StringBuilder();
   7:   
   8:      foreach (Expense expenseItem in expenses)
   9:      {
  10:          expensesLinePoints.Add(expenseItem.Amount.ToString());
  11:   
  12:          if (expenseItem.Date.Month == 12)
  13:          {
  14:              if (seriesString.Length > 0)
  15:              {
  16:                  seriesString.Append(",");
  17:              }
  18:              seriesString.Append("{ ");
  19:              seriesString.AppendFormat(@"name: {0},
  20:                      data: [{1}]", expenseItem.Date.Year, string.Join(",", expensesLinePoints.ToArray()));
  21:              seriesString.Append("  }");
  22:   
  23:              expensesLinePoints = new List<string>();
  24:          }
  25:      }
  26:   
  27:      return seriesString.ToString(); 
  28:  }

5. Finally add the code to generate the graph to the Default.aspx.

   1:  <%@ Page Language="C#" AutoEventWireup="true" 
   2:      CodeBehind="Default.aspx.cs" Inherits="WebApplication1._Default" %>
   3:   
   4:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
   5:      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   6:  <html xmlns="http://www.w3.org/1999/xhtml">
   7:  <head runat="server">
   8:      <title></title>
   9:      <script src="/Scripts/jquery-1.4.1.js" type="text/javascript"></script>
  10:      <script src="/Scripts/jquery-1.4.1.min.js" type="text/javascript"></script>
  11:      <script type="text/javascript" src="/Scripts/highcharts/highcharts.js"></script>
  12:      <script type="text/javascript" src="/Scripts/highcharts/modules/exporting.js"></script>
  13:  </head>
  14:  <body>
  15:      <form id="form1" runat="server">
  16:      <div id="linecontainer" style="width: 500px; height: 250px; margin: 0 auto">
  17:      </div>
  18:      </form>
  19:  </body>
  20:  </html>

   1:  protected void Page_Load(object sender, EventArgs e)
   2:  {
   3:      // create a new stringbuilder for generating the highcharts javascript
   4:      StringBuilder jqueryString = new StringBuilder();
   5:      // create variables for simple editing of the chart. 
   6:      // (I have substituted several hard coded settings with the variable name in the 
   7:      //   javascript file containing the code for the graph)
   8:      jqueryString.Append(" var lineContainerVar = 'linecontainer';");
   9:      jqueryString.Append(" var lineTitleVar = 'Expenses';");
  10:      // Read in the javascript file containing the javascript
  11:      // The method of using resource files makes for cleaner code and easier editing of the graph javascript
  12:      // Because of the use of brackets ({ }) in javascript you will find that you cannot use a string.Format()
  13:      // to edit the javascript in code but have to resort to creating javascript parameters and Replace()
  14:      jqueryString.Append(File.ReadAllText(Server.MapPath(Settings.Default.linechartlocation)));
  15:      // Load in the values of the graph in the format [value,value,value,value],[value,value,value,value]
  16:      jqueryString.Replace("[%seriesString%]", string.Concat("[", GetSeriesString(), "]"));
  17:      // Add the javascript to the page load to load the graph at page startup
  18:      Page.ClientScript.RegisterStartupScript(this.GetType(), "jquerylinechart", jqueryString.ToString(), true);
  19:  }

6. Run the solution.

Working with strings


string
or String

There isn’t any difference between the string or String keywords. Both keywords translate back to System.String, string is just an alias.

String instantiation & comparison

When instantiating a string use the below syntax as a best practice in stead of the allocation of “” as a new string. “” will make a pointer to a new memory location for the empty string and string.Empty does not. string.Empty will just create a pointer to the already static empty string location.

   1:  string myVar = string.Empty;

When comparing strings use the syntax below as a best practice. This will avoid mistakes when comparing strings the are case insensitive or have to be deliberate case sensitive.

   1:  // case insensitive comparison
   2:  bool result = myVar.Equals(myVar2, StringComparison.InvariantCultureIgnoreCase);
   3:   
   4:  // case sensitive comparison
   5:  result = myVar.Equals(myVar2,StringComparison.CurrentCulture);

Use the syntax below as a best practice to check if a string is null or empty. This will avoid 2 separate statements and the possible instantiation of a “” string.

   1:  if(string.IsNullOrEmpty(myVar) )
   2:  {
   3:      // do something ...
   4:  }

String concatenation

There are several ways of pasting strings together to form 1 larger string. When concatenating strings you should always remember that a string is a reference type and not a value type and concatenating a value will always result in a new string object in memory. If your not using variables you can use the syntax below. The compiler will eventually concatenate it to a single string without performance costs.

   1:  string tmp = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " +
   2:                  "Donec laoreet dictum sapien nec mattis. " +
   3:                  "Aenean lobortis dui eu leo facilisis et dictum erat commodo." +
   4:                  "Aliquam erat volutpat. Pellentesque sed ultricies neque." +
   5:                  "In vitae libero at dolor gravida adipiscing." +
   6:                  "Vestibulum sed dui vitae ipsum egestas consequat." +
   7:                  "Maecenas gravida felis sit amet leo bibendum luctus. " +
   8:                  "Curabitur eleifend consequat semper. Donec sit amet tempor sapien. ";


When using variables you can use a number of options. When you only concatenate a few strings you can use the syntaxes below for improved readability.

   1:  string tmp = string.Format(" {0} : € {1} ,- ", productname, price);
   2:  string tmp1 = string.Concat(productname, " : € ", price, ",-");

When you concatenate a lot strings the best performance choice is always the StringBuilder class. The StringBuilder class also supports the Format function. See the example below.

   1:  StringBuilder builder = new StringBuilder();
   2:   
   3:  for (int index = 0; index < 1000; index++)
   4:  {
   5:      builder.Append(myVar);
   6:  }
   7:   
   8:  builder.AppendFormat(" test :", myVar);

String literals

A regular string is a string between 2 quote (“) characters where the 2 quote (“) must be on the same line. Some special characters must be escaped with a

(\ ‘ ” etc) to be correctly interpreted at runtime. See this msdn article for more escape sequences.

   1:  string tmp01 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. " + Environment.NewLine +
   2:      "Donec laoreet dictum sapien nec mattis. " + Environment.NewLine + 
   3:      "Some path C:\\\\temp\\test.txt";

Some long strings with a lot of escaped characters are hard to read within the source code. To make them more readable you can use a literal string. By placing an @ character before the quote you can turn the string into a string literal. You can now write the string over multiple lines and you only need to escape the double quote characters ( “ ).

   1:  string tmp01 = @"Lorem ipsum dolor sit amet, consectetur adipiscing elit.  
   2:      Donec laoreet dictum sapien nec mattis.
   3:      Some path C:\\temp\test.txt";

Short post: Installed SQL Server 2008 r2 and forgot to add current windows user

When installing SQL server 2008 you have to add your own account to the users that have access to the sql instance. Starting sql 2008 local administrators can’t automatically access the instance any more.

I recently ran into this problem with a new imaged laptop with SQL server 2008 r2 but I found a solution that didn’t require a reinstall of SQL server. The link below will lead you to a post that explains how you can add your windows user to the SQL server instance with a few commandline commands.
http://beyondrelational.com/blogs/chintak/archive/2010/07/20/sql-2008-r2-new-installation-and-login-password-unknown.aspx

Creating a blocking load indicator using AJAX Extensions and JQuery

In a webpage using AJAX extensions you often get the request to build some kind of waiting indicator to show the user a his request is being handled because users might get the idea the page isn’t doing anything. Another feature you may be asked to build is to block user input. Some impatient users may cause repeating requests because they don’t see the page reloading. Blocking the user interface to avoid redundant requests will reduce load on underlying services and data pollution.

Blocking the user interface during a partial postback

 In this example I will use the BlockUI plugin found here.

1. Create a new Web application and add a scriptmanager and an updatepanel to the Masterpage.

   1:  <%@ Master Language="C#" AutoEventWireup="true" CodeBehind="Site.master.cs" Inherits="LoadIndicatorProject.SiteMaster" %>
   2:   
   3:  <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
   4:  <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
   5:  <head runat="server">
   6:      <title></title>
   7:      <link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
   8:      <asp:ContentPlaceHolder ID="HeadContent" runat="server">
   9:      </asp:ContentPlaceHolder>
  10:  </head>
  11:  <body>
  12:      <form runat="server">
  13:      <div class="page">
  14:          <div class="header">
  15:              <div class="title">
  16:                  <h1>
  17:                      My ASP.NET Application
  18:                  </h1>
  19:              </div>
  20:              <div class="loginDisplay">
  21:                  <asp:LoginView ID="HeadLoginView" runat="server" EnableViewState="false">
  22:                      <AnonymousTemplate>
  23:                          [ <a href="~/Account/Login.aspx" id="HeadLoginStatus" runat="server">Log In</a>
  24:                          ]
  25:                      </AnonymousTemplate>
  26:                      <LoggedInTemplate>
  27:                          Welcome <span class="bold">
  28:                              <asp:LoginName ID="HeadLoginName" runat="server" />
  29:                          </span>! [
  30:                          <asp:LoginStatus ID="HeadLoginStatus" runat="server" LogoutAction="Redirect" LogoutText="Log Out"
  31:                              LogoutPageUrl="~/" />
  32:                          ]
  33:                      </LoggedInTemplate>
  34:                  </asp:LoginView>
  35:              </div>
  36:              <div class="clear hideSkiplink">
  37:                  <asp:Menu ID="NavigationMenu" runat="server" CssClass="menu" EnableViewState="false"
  38:                      IncludeStyleBlock="false" Orientation="Horizontal">
  39:                      <Items>
  40:                          <asp:MenuItem NavigateUrl="~/Default.aspx" Text="Home" />
  41:                          <asp:MenuItem NavigateUrl="~/About.aspx" Text="About" />
  42:  </Items>
 43:  </asp:Menu>
 44:  </div>
 45:  </div>
 46:  <asp:ScriptManager ID="ScriptManager1" runat="server">
 47:  </asp:ScriptManager>
 48:  <asp:UpdatePanel ID="UpdatePanel1" runat="server">
 49:  <ContentTemplate>
 50:  <div class="main">
 51:  <asp:ContentPlaceHolder ID="MainContent" runat="server" />
 52:  </div>
 53:  </ContentTemplate>
 54:  </asp:UpdatePanel>
 55:  <div class="clear">
 56:  </div>
 57:  </div>
 58:  <div class="footer">
 59:  </div>
 60:  </form>
 61: </body>
 62: </html>

2. Include the references to Jquery and the BlockUI plugin.

 1: <head runat="server">
 2:  <title></title>
 3:  <link href="~/Styles/Site.css" rel="stylesheet" type="text/css" />
 4:  <asp:ContentPlaceHolder ID="HeadContent" runat="server">
 5:  </asp:ContentPlaceHolder>
 6:  <script src="include/jquery-1.5.1.js" type="text/javascript"></script>
 7:  <script src="include/jquery.blockUI.js" type="text/javascript"></script>
 8: </head>

3. Add a label and a button to the Default.aspx page.

 1: <asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
 2:  <h2>
 3:  Welcome to ASP.NET!
 4:  </h2>
 5:  <p>
 6:  <asp:Label ID="labelCounter" runat="server" Text=""></asp:Label> <br />
 7:  <asp:Button ID="buttonCount" runat="server" Text="Add 1" />
 8:  </p>
 9: </asp:Content>

4. Create code to show a counter in the label and increase the value of the counter by one if the button is clicked.

 1:  public partial class _Default : System.Web.UI.Page
 2:  {
 3:  int count = 0;
 4:  
 5:  protected void Page_Load(object sender, EventArgs e)
 6:  {
 7:  if (ViewState["Count"] != null)
 8:  {
 9:  int.TryParse(ViewState["Count"].ToString(), out count);
 10:  }
 11:  }
 12:  
 13:  protected override void OnPreRender(EventArgs e)
 14:  {
 15:  labelCounter.Text = count.ToString();
 16:  base.OnPreRender(e);
 17:  }
 18:  
 19:  protected void buttonCount_Click(object sender, EventArgs e)
 20:  {
 21:  count++;
 22:  ViewState["Count"] = count;
 23:  }
 24:  }

5. Add code to stall the increase of the counter with 5 seconds to imitate a long-running process.

 1:  protected void buttonCount_Click(object sender, EventArgs e)
 2:  {
 3:  count++;
 4:  ViewState["Count"] = count;
 5:  Thread.Sleep(5000);
 6:  }

6. While the counter is still loading hit the button a few more times. As you may notice another postback gets queued and executed.

7. Now we attach an event to the beginrequest event . The event handler will ensure that the UI is blocked when the page is postback and then released when we receive the answer. Add the code below to the masterpage to accomplish this.

 1:  </form>
 2:  <script language="javascript">
 3:  Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginRequestHandler);
 4:  
 5:  function BeginRequestHandler(sender, args) {
 6:  if (($('#<%= UpdatePanel1.ClientID %>') != null)) {
 7:  $('#<%= UpdatePanel1.ClientID %>').block();
 8:  }
 9:  } 
 10:  </script>

8. We change the look of the blockUI to blend in better to the test project. Change the opacity setting of the layover to 0 in the BlockUI.js.

 1: // styles for the overlay
 2:  overlayCSS: {
 3:  backgroundColor: '#000',
 4:  opacity: 0,
 5:  cursor: 'wait'
 6:  },

9. Try out the solution again and hit the button a few more times while the page is still reloading. As you can see you can’t hit the button while the page is reloading.

    The BlockUI shows a default loadindicator on the middle of the page as seen below. This way the user knows that the page isn’t stuck.

image

Showing a custom load indicator

In this example I want to show how you can place a custom load indicator next to the button causing the postback.

In the example below I use the solution I created in the last example as the starting point.

1. First we disable the general load indicator of the BlockUI plugin by changing the code in the BlockUI.js.

 1: $.blockUI.defaults = {
 2:  // message displayed when blocking (use null for no message)
 3:  message: null,

2. Then we add a panel with a animated gif to the masterpage below the form. (Tip: you can find some free animated loader gifs here)

 1: <asp:panel ID="panelLoader" runat="server" style="display:none">
 2:  <img src="images/ajax-loader.gif" width="15px" height="15px" />
 3: </asp:panel>

3. We add an extra non-existing property to the button to recognize the button when it causes the postback. (When building an actual website where you want the load indicator only shown next to buttons that cause a postback and not every control that causes a postback, this is the easiest solution)

 1: <asp:Button ID="buttonCount" runat="server" Text="Add 1" OnClick="buttonCount_Click" isButton="" />

4. Now we add some more code to the beginrequest eventhandler to show the indicator when the button is clicked and add an endrequest eventhandler to ensure the loader gets hidden when the updatepanel is refreshed. (The code below works in IE7, IE8, Firefox and Google Chrome)

 1:  <script language="javascript">
 2:  Sys.WebForms.PageRequestManager.getInstance().add_beginRequest(BeginRequestHandler); 
 3:  Sys.WebForms.PageRequestManager.getInstance().add_endRequest(EndRequestHandler);
 4:  
 5:  var tmpPostBackControl = null;
 6:  
 7:  function BeginRequestHandler(sender, args) { 
 8:  if (($('#<%= UpdatePanel1.ClientID %>') != null)) {
 9:  $('#<%= UpdatePanel1.ClientID %>').block();
 10:  }
 11:  
 12:  tmpPostBackControl = args._postBackElement; // get the control that caused the postback
 13:  
 14:  
 15:  if (typeof (tmpPostBackControl.attributes['isButton']) != typeof (undefined)) //Check if the control has the isbutton property
 16:  {
 17:  var point = Sys.UI.DomElement.getLocation(tmpPostBackControl); // get the upper left coordinates of the control
 18:  var controlSize = tmpPostBackControl.clientWidth; // get the width of the control
 19:  
 20:  Sys.UI.DomElement.setLocation(document.getElementById('<%= panelLoader.ClientID %>'),
 21:  point.x + controlSize + 10, point.y + 5 ); // set the loader to the right of the button
 22:  document.getElementById('<%= panelLoader.ClientID %>')['style']['display'] = 'block'; // show the loadindicator
 23:  }
 24:  }
 25:  
 26:  function EndRequestHandler(sender, args) {
 27:  if (tmpPostBackControl != null) {
 28:  document.getElementById('<%= panelLoader.ClientID %>')['style']['display'] = 'none'; // hide the load indicator
 29:  }
 30:  } 
 31:  
 32:  </script>

5. Try out the solution

The load indicator will look like the picture below.

image

The AJAX updatepanel and partial postbacks

The AJAX updatepanel is a very common way to improve the usability of your site when using validation or multiform pages. Starting with Visual Studio 2008 the more common AJAX controls are available in the toolbox. If you have an updatepanel on the page or masterpage a postback will cause the content of the update panel to reload instead of the entire page.  Meaning that a user on the website does not see the page go blank and re-render entirely but only sees the switch of old data to new data in the page. An updatepanel accomplishes this by using partial postbacks. More information on how to use the control and a little tutorial look here.

The downside of the control is that not all code works 100% with the updatepanel.

The FileUpload control

First of I want to talk about the file upload control. When a partial postback happens the FileBytes property in the upload control will stay empty instead of getting filled with a byte array containing the uploaded file. To fix this you will have to make an actual full postback occur when you press the upload button. First of lets make a little test program to show the problem.

1. Create a webpage with a fileupload and an upload button.

   1: <%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
   2:     CodeBehind="Default.aspx.cs" Inherits="UpdatePanelProject._Default" %>
   3:  
   4: <asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
   5: </asp:Content>
   6: <asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
   7:     <asp:FileUpload ID="FileUpload1" runat="server" /><br />
   8:     <asp:Button ID="buttonSubmit" runat="server" Text="Submit" />
   9: </asp:Content>

2. Create the code to save the uploaded file to the C:\ directory. (Note that my example below saves a text file.)

   1: public partial class _Default : System.Web.UI.Page
   2:    {
   3:        protected void Page_Load(object sender, EventArgs e)
   4:        {
   5:  
   6:        }
   7:  
   8:        protected override void OnPreRender(EventArgs e)
   9:        {
  10:            if (FileUpload1.HasFile)// check if a file is present
  11:            {
  12:                FileUpload1.SaveAs("C:/temp.txt"); //save it to disk
  13:            }
  14:            
  15:            base.OnPreRender(e);
  16:        }
  17:  
  18:    }

3 Try out the application.

4. Add an AJAX scriptmanager above and an AJAX updatepanel around the file upload.

   1: <%@ Page Title="Home Page" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true"
   2:     CodeBehind="Default.aspx.cs" Inherits="UpdatePanelProject._Default" %>
   3:  
   4: <asp:Content ID="HeaderContent" runat="server" ContentPlaceHolderID="HeadContent">
   5: </asp:Content>
   6: <asp:Content ID="BodyContent" runat="server" ContentPlaceHolderID="MainContent">
   7:     <asp:ScriptManager ID="ScriptManager1" runat="server">
   8:     </asp:ScriptManager>
   9:     <asp:UpdatePanel ID="UpdatePanel1" runat="server">
  10:         <ContentTemplate>
  11:             <asp:FileUpload ID="FileUpload1" runat="server" /><br />
  12:             <asp:Button ID="buttonSubmit" runat="server" Text="Submit" />
  13:         </ContentTemplate>
  14:     </asp:UpdatePanel>
  15: </asp:Content>

5. Try out the application again

Notice that the file is not uploaded. Check the debugger to see that the HasFile property stays false.

debugger updatepanel

So how can we fix this? We can tell the scriptmanager that handles the partial postbacks that the submit button has to trigger a full postback to get the FileBytes property filled. Add the code below to the Page_Load event to accomplish this. The users will get a full page reload but their file gets uploaded.

   1: protected void Page_Load(object sender, EventArgs e)
   2:         {
   3:             ScriptManager.GetCurrent(this.Page).RegisterPostBackControl(buttonSubmit);
   4:         }

 

Javascript code and a multiview

A problem with javascript in a multiview inside a mutliview is that only the javascript in the first view is loaded. This gives errors if you have an onclick event in a later view that calls a function that is defined in that view. Also when you have javascript code that’s triggered by the page loading it will only trigger such code on the first view that is loaded not on the next views.

Lets make another test application to illustrate the problem.

1. Create a webpage with a multiview and 3 views as shown in example below.

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="MultiViewPage.aspx.cs"
   2:     Inherits="UpdatePanelProject.MultiViewPage" %>
   3:  
   4: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   5: <html xmlns="http://www.w3.org/1999/xhtml">
   6: <head runat="server">
   7:     <title></title>
   8: </head>
   9: <body>
  10:     <form id="form1" runat="server">
  11:     <div>
  12:         <asp:MultiView ID="MultiViewForms" runat="server">
  13:             <asp:View runat="server" ID="viewStep1">
  14:                 <h1>
  15:                     Step 1</h1>
  16:                 <p>
  17:                     Some content and maybe input.
  18:                     <br />
  19:                     <br />
  20:                     <br />
  21:                 </p>
  22:                 <div style="text-align:right; width:200px">
  23:                     <asp:LinkButton ID="linkbuttonNext1" runat="server" OnClick="linkbuttonNext_Click">
  24:                         Next
  25:                     </asp:LinkButton>
  26:                 </div>
  27:             </asp:View>
  28:             <asp:View runat="server" ID="viewStep2">
  29:                 <h1>
  30:                     Step 2</h1>
  31:                 <p>
  32:                     Some content and maybe input.
  33:                     <br />
  34:                     <br />
  35:                     <br />
  36:                 </p>
  37:                 <div style="text-align:right; width:200px">
  38:                     <asp:LinkButton ID="linkbuttonNext2" runat="server"  OnClick="linkbuttonNext_Click">
  39:                         Next
  40:                     </asp:LinkButton>
  41:                 </div>
  42:             </asp:View>
  43:             <asp:View runat="server" ID="viewStep3">
  44:                 <h1>
  45:                     Step 3</h1>
  46:                 <p>
  47:                     Some content and maybe input.
  48:                     <br />
  49:                     <br />
  50:                     <br />
  51:                 </p>
  52:             </asp:View>
  53:         </asp:MultiView>
  54:     </div>
  55:     </form>
  56: </body>
  57: </html>

2. Add the code to the code behind to navigate through the form.

   1: public partial class MultiViewPage : System.Web.UI.Page
   2:     {
   3:  
   4:         int currentView = 0; // variable to save the active index of the multiview 
   5:         const string VIEWSTATE_CURRENTVIEW = "currentView"; // constant to avoid spelling mistakes
   6:  
   7:         protected void Page_Load(object sender, EventArgs e)
   8:         {
   9:             if (ViewState[VIEWSTATE_CURRENTVIEW] != null) // get the last active index from the viewstate
  10:             {
  11:                 int.TryParse(ViewState[VIEWSTATE_CURRENTVIEW].ToString(), out currentView); // read the value into the currentview variable
  12:             }
  13:         }
  14:  
  15:         /// <summary>
  16:         /// set the active view of the multiview.
  17:         /// notice it's placed in the onPrerender to fire after the button click event
  18:         /// </summary>
  19:         protected override void OnPreRender(EventArgs e)
  20:         {         
  21:             MultiViewForms.ActiveViewIndex = currentView; 
  22:  
  23:             base.OnPreRender(e);
  24:         }
  25:  
  26:         /// <summary>
  27:         /// Increase the currentView index with 1 
  28:         /// Save the value into the viewstate so it can be retrieved after the next postback
  29:         /// </summary>        
  30:         protected void linkbuttonNext_Click(object sender, EventArgs e)
  31:         {
  32:             currentView++;
  33:             ViewState[VIEWSTATE_CURRENTVIEW] = currentView;
  34:         }
  35:  
  36:     }

3. Add javascript to each view to show a popup when each view is loaded. Also add an onclientclick event that calls a javascript function to show a click alert when that button is pressed. The function is declared in the second view.

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="MultiViewPage.aspx.cs"
   2:     Inherits="UpdatePanelProject.MultiViewPage" %>
   3:  
   4: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   5: <html xmlns="http://www.w3.org/1999/xhtml">
   6: <head runat="server">
   7:     <title></title>
   8: </head>
   9: <body>
  10:     <form id="form1" runat="server">
  11:     <div>
  12:         <asp:MultiView ID="MultiViewForms" runat="server">
  13:             <asp:View runat="server" ID="viewStep1">
  14:                 <h1>
  15:                     Step 1</h1>
  16:                 <p>
  17:                     Some content and maybe input.
  18:                     <br />
  19:                     <br />
  20:                     <br />
  21:                 </p>
  22:                 <div style="text-align: right; width: 200px">
  23:                     <asp:LinkButton ID="linkbuttonNext1" runat="server" OnClick="linkbuttonNext_Click">
  24:                         Next
  25:                     </asp:LinkButton>
  26:                 </div>
  27:                 <script type="text/javascript" language="javascript">
  28:                     alert("view 1 loaded");
  29:                 </ script>
  30:             </asp:View>
  31:             <asp:View runat="server" ID="viewStep2">
  32:                 <h1>
  33:                     Step 2</h1>
  34:                 <p>
  35:                     Some content and maybe input.
  36:                     <br />
  37:                     <br />
  38:                     <br />
  39:                 </p>
  40:                 <div style="text-align: right; width: 200px">
  41:                     <asp:LinkButton ID="linkbuttonNext2" runat="server" OnClick="linkbuttonNext_Click"
  42:                         OnClientClick="Javascript:alertClick('Click on linkButtonNext2!')">
  43:                         Next
  44:                     </asp:LinkButton>
  45:                 </div>
  46:                 <script type="text/javascript" language="javascript">
  47:                     alert("view 2 loaded");
  48:  
  49:                     function alertClick(msg) {
  50:                         alert(msg);
  51:                     }
  52:                 </ script>
  53:             </asp:View>
  54:             <asp:View runat="server" ID="viewStep3">
  55:                 <h1>
  56:                     Step 3</h1>
  57:                 <p>
  58:                     Some content and maybe input.
  59:                     <br />
  60:                     <br />
  61:                     <br />
  62:                 </p>
  63:                 <script type="text/javascript" language="javascript">
  64:                     alert("view 3 loaded");
  65:                 </ script>
  66:             </asp:View>
  67:         </asp:MultiView>
  68:     </div>
  69:     </form>
  70: </body>
  71: </html>

4. Run the application to test the result.

5. Add a scripmanager above and an updatepanel around the multiview. Rerun the application to test the result.

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="MultiViewPage.aspx.cs"
   2:     Inherits="UpdatePanelProject.MultiViewPage" %>
   3:  
   4: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   5: <html xmlns="http://www.w3.org/1999/xhtml">
   6: <head runat="server">
   7:     <title></title>
   8: </head>
   9: <body>
  10:     <form id="form1" runat="server">
  11:     <div>
  12:         <asp:ScriptManager runat="server">
  13:         </asp:ScriptManager>
  14:         <asp:UpdatePanel ID="UpdatePanel1" runat="server">
  15:             <ContentTemplate>
  16:                 <asp:MultiView ID="MultiViewForms" runat="server">
  17:                     <asp:View runat="server" ID="viewStep1">
  18:                         <h1>
  19:                             Step 1</h1>
  20:                         <p>
  21:                             Some content and maybe input.
  22:                             <br />
  23:                             <br />
  24:                             <br />
  25:                         </p>
  26:                         <div style="text-align: right; width: 200px">
  27:                             <asp:LinkButton ID="linkbuttonNext1" runat="server" OnClick="linkbuttonNext_Click">
  28:                         Next
  29:                             </asp:LinkButton>
  30:                         </div>
  31:                         <script type="text/javascript" language="javascript">
  32:                             alert('view 1 loaded');
  33:                         </ script>
  34:                     </asp:View>
  35:                     <asp:View runat="server" ID="viewStep2">
  36:                         <h1>
  37:                             Step 2</h1>
  38:                         <p>
  39:                             Some content and maybe input.
  40:                             <br />
  41:                             <br />
  42:                             <br />
  43:                         </p>
  44:                         <div style="text-align: right; width: 200px">
  45:                             <asp:LinkButton ID="linkbuttonNext2" runat="server" OnClick="linkbuttonNext_Click"
  46:                                 OnClientClick="Javascript:alertClick('Click on linkButtonNext2!')">
  47:                         Next
  48:                             </asp:LinkButton>
  49:                             <script type="text/javascript" language="javascript">
  50:                                 alert('view 2 loaded');
  51:  
  52:                                 function alertClick(msg) {
  53:                                     alert(msg);
  54:                                 }
  55:                             </ script>
  56:                         </div>
  57:                     </asp:View>
  58:                     <asp:View runat="server" ID="viewStep3">
  59:                         <h1>
  60:                             Step 3</h1>
  61:                         <p>
  62:                             Some content and maybe input.
  63:                             <br />
  64:                             <br />
  65:                             <br />
  66:                         </p>
  67:                         <script type="text/javascript" language="javascript">
  68:                             alert('view 3 loaded');
  69:                         </ script>
  70:                     </asp:View>
  71:                 </asp:MultiView>
  72:             </ContentTemplate>
  73:         </asp:UpdatePanel>
  74:     </div>
  75:     </form>
  76: </body>
  77: </html>

As you have noticed the popup only shows on the first view and the buttonclick of linkButtonNext2 generates an error.

To fix the error on the linkButtonNext2 move the function declaration outside the multiview (see the html below).

   1: <%@ Page Language="C#" AutoEventWireup="true" CodeBehind="MultiViewPage.aspx.cs"
   2:     Inherits="UpdatePanelProject.MultiViewPage" %>
   3:  
   4: <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
   5: <html xmlns="http://www.w3.org/1999/xhtml">
   6: <head runat="server">
   7:     <title></title>
   8: </head>
   9: <body>
  10:     <form id="form1" runat="server">
  11:     <div>
  12:         <asp:ScriptManager runat="server">
  13:         </asp:ScriptManager>
  14:         <asp:UpdatePanel ID="UpdatePanel1" runat="server">
  15:             <ContentTemplate>
  16:                 <script type="text/javascript" language="javascript">
  17:                     function alertClick(msg) {
  18:                         alert(msg);
  19:                     }
  20:                 </ script>
  21:                 <asp:MultiView ID="MultiViewForms" runat="server">
  22:                     <asp:View runat="server" ID="viewStep1">
  23:                         <h1>
  24:                             Step 1</h1>
  25:                         <p>
  26:                             Some content and maybe input.
  27:                             <br />
  28:                             <br />
  29:                             <br />
  30:                         </p>
  31:                         <div style="text-align: right; width: 200px">
  32:                             <asp:LinkButton ID="linkbuttonNext1" runat="server" OnClick="linkbuttonNext_Click">
  33:                         Next
  34:                             </asp:LinkButton>
  35:                         </div>
  36:                         <script type="text/javascript" language="javascript">
  37:                             alert("view 1 loaded");
  38:                         </ script>
  39:                     </asp:View>
  40:                     <asp:View runat="server" ID="viewStep2">
  41:                         <h1>
  42:                             Step 2</h1>
  43:                         <p>
  44:                             Some content and maybe input.
  45:                             <br />
  46:                             <br />
  47:                             <br />
  48:                         </p>
  49:                         <div style="text-align: right; width: 200px">
  50:                             <asp:LinkButton ID="linkbuttonNext2" runat="server" OnClick="linkbuttonNext_Click"
  51:                                 OnClientClick="Javascript:alertClick('Click on linkButtonNext2!')">
  52:                         Next
  53:                             </asp:LinkButton>
  54:                         </div>
  55:                         <script type="text/javascript" language="javascript">
  56:                             alert("view 2 loaded");                          
  57:                         </ script>
  58:                     </asp:View>
  59:                     <asp:View runat="server" ID="viewStep3">
  60:                         <h1>
  61:                             Step 3</h1>
  62:                         <p>
  63:                             Some content and maybe input.
  64:                             <br />
  65:                             <br />
  66:                             <br />
  67:                         </p>
  68:                         <script type="text/javascript" language="javascript">
  69:                             alert("view 3 loaded");
  70:                         </ script>
  71:                     </asp:View>
  72:                 </asp:MultiView>
  73:             </ContentTemplate>
  74:         </asp:UpdatePanel>
  75:     </div>
  76:     </form>
  77: </body>
  78: </html>

To fix the onload javascript add the code below to the OnPreRender() of the code-behind.

   1: /// <summary>
   2: /// set the active view of the multiview.
   3: /// notice it's placed in the onPrerender to fire after the button click event
   4: /// </summary>
   5: protected override void OnPreRender(EventArgs e)
   6: {         
   7:     MultiViewForms.ActiveViewIndex = currentView;
   8:  
   9:     switch (currentView)
  10:     {
  11:         case 0:
  12:             ScriptManager.RegisterStartupScript(this.Page, this.Page.GetType(), "loadevent1", "alert('view 1 loaded')", true);
  13:             break;
  14:         case 1:
  15:             ScriptManager.RegisterStartupScript(this.Page, this.Page.GetType(), "loadevent2", "alert('view 2 loaded')", true);
  16:             break;
  17:         case 2:
  18:             ScriptManager.RegisterStartupScript(this.Page, this.Page.GetType(), "loadevent3", "alert('view 3 loaded')", true);
  19:             break; 
  20:     }
  21:  
  22:     base.OnPreRender(e);
  23: }

With the 2 above changes to the code it should work as it did before the updatepanel was added.

Showing editable lists with the ASP.NET Repeater control

The listview and repeater controls are a great way to show a datalist on a website. In combination with a multiview you can easily create a master/detail page with editing abilities. The advantage of having a listview or repeater is that you can determine the layout of the controls unlike the gridview.

The repeater was introduced in framework 1.0. The listview was introduced in framework 3.5. The listview is an improved version of the repeater with more functionality. A nice article comparing the most used datalist controls and their functionality can be found here:
http://weblogs.asp.net/anasghanem/archive/2008/09/06/comparing-listview-with-gridview-datalist-and-repeater.aspx

How to create a simple data mutation list using a repeater
A working example of the application made using the steps below can be found here.
The solution uses the Adventureworks example database that can be downloaded from codeplex. You may have to edit the connectionstring in the web.config.

1. Create a new web application project.

2. Add a repeater control to the page.

3. Create an ItemTemplate with a custom layout. The code example below shows an ItemTemplate that uses the Eval statement that reads the data directly from the databound object. I also added some label controls that will be filled from the code-behind.


<asp:Repeater ID="repeaterContact" runat="server" >
	<HeaderTemplate>
		<table cellspacing="0px" cellpadding="5px">
			<tr class="tableHeader">
				<td>
					ID
				</td>
				<td>
					Title
				</td>
				<td>
					Firstname
				</td>
				<td>
					Middlename
				</td>
				<td>
					Lastname
				</td>
				<td>
					Suffix
				</td>
				<td>
					Emailadress
				</td>
				<td>
					Phone
				</td>
				<td>

 				</td>
			</tr>
	</HeaderTemplate>
	<ItemTemplate>
		<tr>
			<td>
				<%# Eval("ID") %>
			</td>
			<td>
				<%# Eval("Title") %>
			</td>
			<td>
				<%# Eval("FirstName") %>
			</td>
			<td>
				<%# Eval("MiddleName") %>
			</td>
			<td>
				<%# Eval("LastName") %>
			</td>
			<td>
				<%# Eval("Suffix") %>
			</td>
			<td>
				<asp:Label ID="labelEmail" runat="server" />
			</td>
			<td>
				<asp:Label ID="labelPhone" runat="server" />
			</td>
			<td>
			</td>
		</tr>
	</ItemTemplate>
	<FooterTemplate>
		</table>
	</FooterTemplate>
</asp:Repeater>

4. Add an OnItemDataBound event to your repeater. Add the implementation of the event to the code-behind of the page. (see the code example below for my implementation)


protected void repeaterContact_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
	// check if there's an actual object in the item
	if ((e.Item.DataItem as Contact) != null)
	{
		Contact contactPerson = (e.Item.DataItem as Contact);

		Label label = (e.Item.FindControl("labelEmail") as Label);
		if (label != null)
		{
			label.Text = contactPerson.EmailAdress;
		}

		label = (e.Item.FindControl("labelPhone") as Label);
		if (label != null)
		{
			label.Text = contactPerson.Phone;
		}
	}
}

5. Bind a list of data to the control. (see code example below)


protected void Page_Load(object sender, EventArgs e)
{
	repeaterContact.DataSource = buContact.getContacts(0, 0);
	repeaterContact.DataBind();
}

6. Run the application to see the result

7. Add a multiview control to the page.

8. Add a view to the multiview called viewOverview.

9. Move the repeater control to the viewOverview view.

10. Add a new view to the multiview called viewDetail view

11. Add input controls that will be used to edit the data from your list.
The code example below shows the front end code for steps 7 – 11.


<asp:MultiView runat="server" ID="multiViewRepeaterApplication">
	<asp:View ID="viewOverview" runat="server">
		<asp:Repeater ID="repeaterContact" runat="server" OnItemDataBound="repeaterContact_ItemDataBound">
			<HeaderTemplate>
				<table cellspacing="0px" cellpadding="5px">
					<tr class="tableHeader">
						<td>
							ID
						</td>
						<td>
							Title
						</td>
						<td>
							Firstname
						</td>
						<td>
							Middlename
						</td>
						<td>
							Lastname
						</td>
						<td>
							Suffix
						</td>
						<td>
							Emailadress
						</td>
						<td>
							Phone
						</td>
						<td>

						</td>
					</tr>
			</HeaderTemplate>
			<ItemTemplate>
				<tr>
					<td>
						<%# Eval("ID") %>
					</td>
					<td>
						<%# Eval("Title") %>
					</td>
					<td>
						<%# Eval("FirstName") %>
					</td>
					<td>
						<%# Eval("MiddleName") %>
					</td>
					<td>
						<%# Eval("LastName") %>
					</td>
					<td>
						<%# Eval("Suffix") %>
					</td>
					<td>
						<asp:Label ID="labelEmail" runat="server" />
					</td>
					<td>
						<asp:Label ID="labelPhone" runat="server" />
					</td>
					<td>
					</td>
				</tr>
			</ItemTemplate>
			<FooterTemplate>
				</table>
			</FooterTemplate>
		</asp:Repeater>
	</asp:View>
	<asp:View ID="viewEdit" runat="server">
		<table>
			<tr>
				<td>
					Title
				</td>
				<td>
					<asp:TextBox ID="textBoxTitle" runat="server" />
				</td>
			</tr>
			<tr>
				<td>
					FirstName
				</td>
				<td>
					<asp:TextBox ID="textBoxFirstName" runat="server" />
				</td>
			</tr>
			<tr>
				<td>
					MiddleName
				</td>
				<td>
					<asp:TextBox ID="textBoxMiddleName" runat="server" />
				</td>
			</tr>
			<tr>
				<td>
					LastName
				</td>
				<td>
					<asp:TextBox ID="textBoxLastName" runat="server" />
				</td>
			</tr>
			<tr>
				<td>
					Suffix
				</td>
				<td>
					<asp:TextBox ID="textBoxSuffix" runat="server" />
				</td>
			</tr>
			<tr>
				<td>
					EmailAdress
				</td>
				<td>
					<asp:TextBox ID="textBoxEmaiLAdress" runat="server" />
				</td>
			</tr>
			<tr>
				<td>
					Pone
				</td>
				<td>
					<asp:TextBox ID="textBoxPhone" runat="server" />
				</td>
			</tr>
		</table>
	</asp:View>
</asp:MultiView>

12. Add code to your code-behind to remember the active view in the viewstate. This is needed because you lose the value of the selected view when the page posts back to server.


private int selectedView = 0;

private const int VIEW_OVERVIEW = 0;
private const int VIEW_DETAIL = 1;

protected void Page_Load(object sender, EventArgs e)
{
	if (ViewState["SelectedView"] != null)
	{
		int.TryParse(ViewState["SelectedView"].ToString(), out selectedView);
	}

	if (selectedView == VIEW_OVERVIEW)
	{
		repeaterContact.DataSource = buContact.getContacts(0, 0);
		repeaterContact.DataBind();
	}
} 

protected override void OnPreRender(EventArgs e)
{
	// set active view
	multiViewRepeaterApplication.ActiveViewIndex = selectedView;

	base.OnPreRender(e);
}

13. Add a button to the ItemTemplate of your repeater that will trigger the detail view and fill its CommandName property with the value “Update”. Go to your OnItemDatabound event implementation in the code-behind and fill the CommandArgument of the linkbutton with the ID of the record.


	<asp:LinkButton ID="linkButtonUpdate" runat="server" CommandName="Update">Update</asp:LinkButton><br />

LinkButton linkButton = (e.Item.FindControl("linkButtonUpdate") as LinkButton);
if (linkButton != null)
{
	linkButton.CommandArgument = contactPerson.ID.ToString();
}

14. Add an OnItemCommand event to your repeater. This event will catch the buttonclick with the CommandName property filled.

15. Go to your code-behind and implement the OnItemCommand event that will read the commandname of the button and switch to the correct view. Also store the ID of the dataitem that was clicked in the viewstate. We store the value in the viewstate so we can retrieve the selected record even after a postback.


private int selectedRecord = -1;

// added for later support for an insert editmode
private string editMode = string.Empty;
private const string EDITMODE_UPDATE = "update";

protected void Page_Load(object sender, EventArgs e)
{
	if (ViewState["EditMode"] != null)
	{
		editMode = ViewState["EditMode"].ToString();
	}

	if (ViewState["SelectedView"] != null)
	{
		int.TryParse(ViewState["SelectedView"].ToString(), out selectedView);
	}

	if (ViewState["SelectedRecord"] != null)
	{
		int.TryParse(ViewState["SelectedRecord"].ToString(), out selectedRecord);
	}

	if (selectedView == VIEW_OVERVIEW)
	{
		repeaterContact.DataSource = buContact.getContacts(0, 0);
		repeaterContact.DataBind();
	}
}

protected void repeaterContact_ItemCommand(object sender, RepeaterCommandEventArgs e)
{
	if (e.CommandName == "Update")
	{
		editMode = EDITMODE_UPDATE;
		ViewState["EditMode"] = editMode;
		selectedView = VIEW_DETAIL;
		ViewState["SelectedView"] = selectedView;
		int.TryParse(e.CommandArgument.ToString(), out selectedRecord);
		ViewState["SelectedRecord"] = selectedRecord;
	}
}

16. Go to the OnPreRender event and implement the code to load the detailview with the correct data.


protected override void OnPreRender(EventArgs e)
{
	if (selectedView == VIEW_DETAIL)
	{
		ClearInputControls();
		if (editMode == EDITMODE_UPDATE)
		{
			Contact contact = buContact.getContactByID(selectedRecord);
			if (contact != null)
			{
				textBoxTitle.Text = contact.Title;
				textBoxFirstName.Text = contact.FirstName;
				textBoxMiddleName.Text = contact.MiddleName;
				textBoxLastName.Text = contact.LastName;
				textBoxSuffix.Text = contact.Suffix;
				textBoxEmaiLAdress.Text = contact.EmailAdress;
				textBoxPhone.Text = contact.Phone;
			}
		}
	}
	multiViewRepeaterApplication.ActiveViewIndex = selectedView;

	base.OnPreRender(e);
}

private void ClearInputControls()
{
	textBoxTitle.Text = string.Empty;
	textBoxFirstName.Text = string.Empty;
	textBoxMiddleName.Text = string.Empty;
	textBoxLastName.Text = string.Empty;
	textBoxSuffix.Text = string.Empty;
	textBoxEmaiLAdress.Text = string.Empty;
	textBoxPhone.Text = string.Empty;
}

17. Next add a save button to your detailview and add a click event to the save button. Implement it to save the changes to your item and return to the overview view. Make sure to refresh the databound list to show the changes you’ve made.


protected void linkButtonSave_Click(object sender, EventArgs e)
{
	Contact contact = new Contact()
	{
		Title = textBoxTitle.Text,
		FirstName = textBoxFirstName.Text,
		MiddleName = textBoxMiddleName.Text,
		LastName = textBoxLastName.Text,
		Suffix = textBoxSuffix.Text,
		EmailAdress = textBoxEmaiLAdress.Text,
		Phone = textBoxPhone.Text
	};

	if (editMode == EDITMODE_UPDATE)
	{
		contact.ID = selectedRecord;
	}

	//When ID is pressent Save will update the existing record otherwise a new record is inserted
	buContact.Save(contact);

	// go to overview list
	editMode = string.Empty;
	ViewState["EditMode"] = editMode;
	selectedView = VIEW_OVERVIEW;
	ViewState["SelectedView"] = selectedView;
	int.TryParse("-1", out selectedRecord);
	ViewState["SelectedRecord"] = selectedRecord;
	//reload list
	repeaterContact.DataSource = buContact.getContacts(0, 0);
	repeaterContact.DataBind();
}

18. Run the solution to try out your update-able list.

19. Add a new button below the repeater.


<p>
	<asp:LinkButton ID="linkButtonInsert" runat="server" OnClick="linkButtonInsert_Click">Add new contact</asp:LinkButton><br />
</p>	

20. Add a click-event to the button to go to the detailview using a new editmode called insert.


private const string EDITMODE_INSERT = "insert";

protected void linkButtonInsert_Click(object sender, EventArgs e)
{
	// go to detail view
	editMode = EDITMODE_INSERT;
	ViewState["EditMode"] = editMode;
	selectedView = VIEW_DETAIL;
	ViewState["SelectedView"] = selectedView;
}

21. If you have not used the implementations I use in the examples or have a different buContact class, you may have to alter some of the implementations in the OnPreRender and/or Save button click to facilitate the insert functionality.

22. Run the solution to try out the new insert functionality.

23. Add a new button to the repeater item template. Fill the CommandName value with “Delete”.
Go to the OnItemDataBound implementation and fill the CommandArgument value of the new button with the ID of the record.


<asp:LinkButton ID="linkButtonDelete" runat="server" CommandName="Delete">Delete</asp:LinkButton>

linkButton = (e.Item.FindControl("linkButtonDelete") as LinkButton);
if (linkButton != null)
{
	linkButton.CommandArgument = contactPerson.ID.ToString();
}

24. Alter your OnItemCommand event to handle the delete command and delete the selected record. Remember to reload the data in the list to show the changes made.


protected void repeaterContact_ItemCommand(object sender, RepeaterCommandEventArgs e)
{
	if (e.CommandName == "Update")
	{
		editMode = EDITMODE_UPDATE;
		ViewState["EditMode"] = editMode;
		selectedView = VIEW_DETAIL;
		ViewState["SelectedView"] = selectedView;
		int.TryParse(e.CommandArgument.ToString(), out selectedRecord);
		ViewState["SelectedRecord"] = selectedRecord;
	}
	else if (e.CommandName == "Delete")
	{
		int.TryParse(e.CommandArgument.ToString(), out selectedRecord);
		buContact.DeleteContact(selectedRecord);
		// go to overview list
		editMode = string.Empty;
		ViewState["EditMode"] = editMode;
		selectedView = VIEW_OVERVIEW;
		ViewState["SelectedView"] = selectedView;
		int.TryParse("-1", out selectedRecord);
		ViewState["SelectedRecord"] = selectedRecord;
		//reload list
		repeaterContact.DataSource = buContact.getContacts(0, 0);
		repeaterContact.DataBind();
	}
}

25. Run your solution to try out the new delete functionality.

After following the above steps you have a simple data mutation list using the repeater control.

Visual studio web developer express 2010 Part 4: Master pages, Nested masterpages and themes

ASP.NET masterpages are a great way to create a consistent look for your site and to handle generic code that needs to be executed on all the pages of your website.

Creating and using a simple masterpage
In visual studio 2010 a default web application already has a masterpage. In this section I’ll explain how to create a new masterpage. Open a new or existing web application. Use the “Add New Item…” option and select a Masterpage. Give the masterpage a default name like MyMaster and create the page. Notice the filextension “.master” that indicates that the file is a masterpage.
The default markup of a masterpage is shown in code example 1 below.


<%@ Master Language='C#' AutoEventWireup='true' CodeBehind='MyMaster.master.cs' Inherits='MasterPages.MyMaster' %>

<!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></title>
    <asp:ContentPlaceHolder ID='head' runat='server'>
    </asp:ContentPlaceHolder>
</head>
<body>
    <form id='form1' runat='server'>
    <div>
        <asp:ContentPlaceHolder ID='ContentPlaceHolder1' runat='server'>

        </asp:ContentPlaceHolder>
    </div>
    </form>
</body>
</html>

Code example 1

Notice the Master tag instead of a Page tag at the file declaration. Also notice the ContentPlaceHolder controls in the markup. A ContentPlaceHolder is a container that is filled with content from the markup in a webform that has references the materpage file. The webform has Content controls that have a “ContentPlaceHolderID” property. The ContentPlaceHolderID is filled with the ID of a content placeholder. The html and controls declared in the  Content tags of a webform are rendered in the contentplaceholder.

 To create a webform that references our masterpage call the “Add New Item…” option. Select the “Webform using a Master Page” name the webform whatever you like and select the “Add” option. Now a new dialog will pop up allowing you to select the master page you want to reference. Click the “Ok” option to use the selected masterpage. As seen in figure 2 below, the new webform already has content controls that relate to the masters ContentPlaceHolders. Also notice the reference to your masterpage in the MasterPageFile property in the Page deceleration at the top of the file.


<%@ Page Title='' Language='C#' MasterPageFile='~/MyMaster.Master' AutoEventWireup='true' CodeBehind='MyWebForm.aspx.cs' Inherits='MasterPages.MyWebForm' %>
<asp:Content ID='Content1' ContentPlaceHolderID='head' runat='server'>
</asp:Content>
<asp:Content ID='Content2' ContentPlaceHolderID='ContentPlaceHolder1' runat='server'>
</asp:Content>

Code example 2

To see how the masterpage and the webform interact follow the example below.
    1. Add a title to your masterpage and a little introduction tekst..
    2. Create a table in your masterpage with 1 row and 2 cells.
    3. In the left cell type: “Controls in the web form:”
    4. In the right cell add a contentplaceholder named “ContentPlaceHolderMyControl”
After the above steps your masterpage should look like code example 3 shown below.


<%@ Master Language='C#' AutoEventWireup='true' CodeBehind='MyMaster.master.cs' Inherits='MasterPages.MyMaster' %>

<!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></title>
    <asp:ContentPlaceHolder ID='head' runat='server'>
    </asp:ContentPlaceHolder>
</head>
<body>
    <form id='form1' runat='server'>
    <div>
        <h1>Masterpage example</h1>
        On this page a masterpage is used to display this text.
    </div>
    <div>
        <table style='border: 1px solid black'>
            <tr>
                <td>
                    Controls in the web form:
                </td>
                <td>
                    <asp:ContentPlaceHolder ID='ContentPlaceHolderMyControl' runat='server'>

                    </asp:ContentPlaceHolder>
                </td>
            </tr>
        </table>
    </div>
    </form>
</body>
</html>

Code example 3

     5. Go to you webform.
     6. Create a Content control referencing the ContentPlaceHolderMyControl contentplaceholder.
     7. Fill the content tag with a textbox and a button.
After the above steps your masterpage shouls look like code example 4 shown below.


<%@ Page Title='' Language='C#' MasterPageFile='~/MyMaster.Master' AutoEventWireup='true'
    CodeBehind='MyWebForm.aspx.cs' Inherits='MasterPages.MyWebForm' %>
<asp:Content ID='Content1' ContentPlaceHolderID='head' runat='server'>
</asp:Content>
<asp:Content ID='Content2' ContentPlaceHolderID='ContentPlaceHolderMyControl' runat='server'>
    <asp:TextBox ID='TextBox1' runat='server'></asp:TextBox><br />
    <asp:Button ID='Button1' runat='server' Text='Button' />

Code example 4

Now we will run the solution so far. Be sure to set the correct webform as the startup document. To do this right click the file in the solution explorer and click “Set as start page”. When the solution is run the page looks like below (figure 1). As you can see the web form and masterpage have merged to 1 document at runtime. The controls specified in your control have been rendered inside the contentplaceholder specified.

masterpage example
figure 1

The strength of the masterpage lies in the fact that you can specify multiple webforms to reference the same masterpage. Each web form has its own content specified in the content controls.

You can also choose to specify default markup within a contentplaceholder in the masterpage. If a webform referencing the masterpage has a matching content control the default markup will be replaced with the markup in the content control. If the matching content tag is missing then the default markup as specified in the masterpage will be loaded. An example of this will be demonstrated by the example below. This example continues from the situation we created from the  previous example.

    1. Go to you masterpage and find your ContentPlaceHolderMyControl contentplaceholder.
    2. Type the text “No controls specified” into the contentplaceholder.
After following the above steps the masterpage should look like code example 5 below.


<%@ Master Language='C#' AutoEventWireup='true' CodeBehind='MyMaster.master.cs' Inherits='MasterPages.MyMaster' %>

<!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></title>
    <asp:ContentPlaceHolder ID='head' runat='server'>
    </asp:ContentPlaceHolder>
</head>
<body>
    <form id='form1' runat='server'>
    <div>
        <h1>Masterpage example</h1>
        On this page a masterpage is used to display this text.
    </div>
    <div>
        <table style='border: 1px solid black'>
            <tr>
                <td>
                    Controls in the web form:
                </td>
                <td>
                    <asp:ContentPlaceHolder ID='ContentPlaceHolderMyControl' runat='server'>
                    No controls specified
                    </asp:ContentPlaceHolder>
                </td>
            </tr>
        </table>
    </div>
    </form>
</body>
</html>

Code example 5

    3. Create a new web form referencing the masterpage. (here called WebForm2)
    4. Delete the ContentPlaceHolderMyControl content control from the webform.
    5.  Run the solution and view both the webforms you created to view the results at runtime.

The second webform shows the “no controls” text and the first webform shows the controls but not the text we specified in the masterpage.

When writing server side code for webforms referencing masterpages notice that the code for controls and events relating to the masterpage are written in the code behind of the masterpage file and the code relating to the controls and events of the webform are written in the code behind of the webform. You can’t reference controls, functions or variables from the masterpage in your webform normally. You will have to call the this.Master property and cast it to the correct masterpage type to use controls, functions or property’s declared in you masterpage. Of course you can only reference and call public controls, functions and property’s. Code example 6 shows the code in a webform that fills a public labelText property of a masterpage.


protected void Button1_Click(object sender, EventArgs e)
{
	MyMaster masterpage = (this.Master as MyMaster);
	if (masterpage != null)
	{
		masterpage.labelText = "Webform button clicked";
	}
}

Code example 6

You can also use the Page.FindControl() function to directly access any control in the masterpage. Be mindfull to catch possible null exceptions. By using a cast by using the “as” keyword you can catch the null values. A simple example is shown below.


protected void Page_Load(object sender, EventArgs e)
{
	Label mylabel = (Page.Master.FindControl("Label1") as Label);
	if (mylabel != null)
	{
		mylabel.Text = "Text from webform";
	}
}

Code example 7

When working with masterpages you need to remember that the page lifecycle (see the previous post) handles events of the masterpage first and then the events of the webform. This is important when reading or setting values in your masterpage.

Themes
A theme is a special folder that contains style sheets, images and skin files. These files are used to define the way your page looks.  The masterpage is usually the place where you include your styles. Instead of including the style sheets or skins as you usually would, you can put these files within a theme and set the theme to your webform. A stylesheet is only activated in the theme if the stylesheet name is the same as the theme folder. Follow the steps below to create a simple theme and set it to your webform.

    1. Add a new theme to you website do this by adding the App_Themes folder (Add ASP.NET Folder option) and creating a new folder in it with the name of your theme.
    2. Add a stylesheet that has the same name as your theme containing the following style


  body
  {
   background-color:#D6FFF0;
  }

 3. Add a skin file containing the following style


  <asp:button runat="server" forecolor="#007F46" />

 4. Go to a webform back-end and add the code as shown below. (Themes are always set in the PreInit event.)


  protected override void OnPreInit(EventArgs e)
  {
   this.Theme = "Theme01";
   base.OnPreInit(e);
  }

 5. Run you website and view the results.

If you don’t see any changes in the button or background colours, you should check the masterpage and webform pages and see if the EnableTheming property is set to True.

Nested masterpages
When you have need of multiple masterpages you can consider creating nested masterpages.
A situation where multiple layouts could be used is when you want distinct parts of your website to look different and have multiple pages in the same layout, but use the same base logic. A nested masterpage is a masterpage that has a masterpage itself. You can fill the placeholders of the base masterpage and add more content placeholders to the nested masterpage that will be filled by the web forms.

You can create a nested masterpage through the “Add New Item” option. Notice that the masterfile property is filled after creating the nested masterpage.
An simple example solution using nested masterpages can be downloaded here
My sample solution uses a masterpage baseMasterPage.master that has a base layout with a header.
This masterpage contains the logic to login and to remember the user. The nested masterpages contain a menu and a content block and the reference to their own stylesheet. The solution contains 2 very simple nested masterpages with their own layout but the nested masterpages could also have contained distinct logic relating to that distinct part of the website.

Visual studio web developer express 2010 Part 3: Events and the page life cycle

In this post I’ll explain how events are created and handled. I’ll also talk about the all important page life cycle that defines when events are handled.
In my last post I showed how to create a simple button click event by using the properties window. You can also create an event handler in the code behind and then link it you your front end control.
A basic event handler is usually formed as show below in code example 1. 


protected void EventHandler_Name (object sender, EventArgs e)
{
       //event handling here;
}

Code example 1

Notice the protection level ‘protected’. If you declare a public or private handler it will not be interpeted correctly by the compiler. If you run the website the control will throw an error saying that the specified event handler for that control is not found. A basic event handler will have 2 properties namely Object sender and EventArgs e. At runtime the sender will be filled with the control that generated the event. So when a click handler of a button is fired, the sender object will be filled with the button (You will have to cast the sender object to a type Button to access all the fields). The EventArgs e is an empty class. Some eventhandlers substitute the EventArgs with their own type (inherted from EventArgs) containing extra properties to store eventdata. 

To link you eventhandler to the desired control use the markup view. Type the eventname (use code completion or msdn event references to see which events link to a certain control) followed by a = sign then type the name you gave to the eventhandler between quotes. See code example 2. If you run your website and trigger the event your event handler will be executed. 


<asp:Button ID="Button1" runat="server" onclick="EventHandler_Name" Text="Button" />

Code example 2

 Practise 1: Creating a simple button event

1. We’ll be starting this practise by creating a web application as explained in my first post.
2. Clear all text from the default page.
3. Add a label and a button from the toolbox in the default page as explained in my last post. Clear the label text from the label in the properties window. Change the button text to ‘Click!’.
4. We’ll be adding an event handler by writing it ourselfs instead of generating it by using the event view of the properties window. Right click the designer view and select View Code from the context menu. Code example 3 shows the code of the new event handler. It’s a simple event handler to change the tekst of the label into ‘Event triggered’. 


protected void button1_Click(object sender, EventArgs e)
{
label1.Text = "Event triggered";
}

Code example 3

5. After you finish the evenhandler code. Link the eventhandler to the Click event of the button. as shown in code example 4 below. 


<p>
<asp:Label ID="label1" runat="server" Text=""></asp:Label><br />
<asp:Button ID="button1" runat="server" Text="Click!" <strong>OnClick="button1_Click"</strong> />
</p>

Code example 4

6. Run your website using f5 or selecting the start debugging option from the menu. Click the button to see the text appear in the label. 

The ASP.NET page lifecycle
The page lifecycle in ASP.NET defines the order in which events are handled. MSDN has a good discription of the page lifecycle and the most importent events.
The explanation of the lifecycle can be found here.The most important considerations are listed below. 

- Each time a control triggers a postback  (sends data from the client to the server) by the means of an event the page reloads completly and goes through the entire page life cycle again. Most variables that had a value will be their default values again. Input controls on the page will usually remember their state.
- Only the onPreInit event can be used to set your theme or masterpage programaticly (both explained my next posts)
- Use the page_load mainly to retrieve the page variables used. (Remember that the values stored in the global page variables are forgotten after a postback). Remember that events like a button click (control events )will be executed AFTER this event. This will influance the correct execution of conditional statements in your eventhandlers.
- Binding data to a datasource should be done in the Page_Load event. The reason for this is that some events like the OnItemCommand on a repeater or listview will not be triggered if their datasource is not set.
- Use the OnPreRender event to write all the data needed to the correct controls. This event fires after the control events so all the data mutations have alrealdy happend when the OnPreRender event triggers. 

A little tip:
Sometime you can encounter the following error : “ Invalid postback or callback argument.  Event validation is enabled using <pages enableEventValidation=”true”/> in configuration or <%@ Page EnableEventValidation=”true” %> in a page.  For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that orignally rendered them.  If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation. “ 

This error can be triggered by a control when you alter it (set a value) before the processing of the page got to the control events. The event validation sees that the expected value of the control is not the same as at the postback and thus it generates the above error.

Two ways to fix this error are:

1. Encapsulate the control changing code within a check for a postback as shown below in code example 5.


protected void Page_Load(object sender, EventArgs e)
{
if (!Page.IsPostBack)
{
// handle your code here
}
}

Code example 5
2. Move the code from the page_load event to the OnPreRender event. 

In my next post I’ll explain masterpages, nested masterpages and themes.

Visual studio web developer express 2010 Part 2: Simple editing of your website

In this post I will explain the more important windows that are used to edit your project and website.
If the windows I’m talking about are not showing in the Visual Studio IDE you can make them visible by using the “view” menu in the top menu of the IDE (figure 1).

view menu
figure 1

The most importent window is called the solution explorer. Here you can see all the files in your solution (figure 2).
You can also use various options from the context menu to manage the items in your solution. The “Show all files” option (marked red in figure 2) at the top of the solution explorer will show all the files in the solution directory even if they’re not included in the solution file. This makes adding new existing files in the solution directory easier.

solution explorer
figure 2

The middle off the IDE is used to open and edit your pages. In the left bottom corner of the working area you can choose different views.
The source option shows the HTML code or markup off the page. You can type your HTML , Javascript or front end C# or VB.NET code in this window to edit the active page. The designer view shows the graphic representation of the code in your control. If the IDE can resolve all the links and references in your solution, it’ll load all settings of the stylesheets and master pages for you in the designer view. With complex websites the designer view can sometimes be a bit slow or even throw an error. When the designer won’t load, use your Solution Explorer to open the page in markup (front end source) view. (Choosing source view in the solution explorer will open the code behind and not the front end of your page.) The option split in the bottom corner of the working area will show the markup and designer in a split screen mode. Figure 3 below shows a default.aspx page in it’s split mode view demonstrating the markup and designer view of the page.

working area
figure 3

The toolbox is used to drag and drop standard controls onto a designer or markup view. If you like you can also just type in the code for new controls in the Markup view.
The toolbox contains the controls that can be used for the current type of control or page. By right clicking the toolbox you can choose the option “Show all” to show all availlable controls or “Choose items” to add custom controls or controls of a specific category (figure 4).

toolbox
figure 4

When selecting a control on your page or select a file in the solution explorer you can view that items properties in the properties window. The properties window is accessed by right clicking the control or page and selecting the “Properties” option. The properties window shows the standard attributes of a file or control. When selecting a control like a button that can respond to certain events like a click the properties window will show a lightning bolt icon at the top of the properties window (marked red in figure 5). By clicking the lightning bolt icon the attribute view will change to the event view, showing all the availlable server side events of the control. You can also add client side events in the markup view of a page.

properties
figure 5

By double clicking an event, an event handler will be linked to the event and an empty event handler will be generated in your code behind (figure 6). In my next post I’ll explain more about using events and the page life cycle.

event example
figure 6

When you select the properties option of a visual studio project file, a project properties tab will appear in the working area (figure 7). You can use the various options to alter the way the project is build or run. You can also alter the standard namespace and framework used in the project.

project properties
figure 7

Visual studio web developer express 2010 Part 1: Web projects

I want to start a little series explaining basic ASP.Net programming for people who don’t have much experience with it. You can use the free visual studio web developer express availlable from download here to create websites. I will assume you have basic C# and VB.Net coding skills and knowledge.

In visual studio you have multiple choices about how you set-up and develop a website.  The 2 most basic types of web projects are the web site project and the basic web application. The main difference is that a web site project is based on a loose file stucture and can contain multiple base programming languages (VB.Net/C#). This file structure allows single pages of the website to be updated and published to your website creating greater flexibility.  

To create a new web site follow the instructions shown in the screenshots (figure 1) below.

create website image 1

create website image 2
figure 1

The web application project is based on code compiled into dll’s with a loose front end page. A web application only supports one base programming language for the code behind pages. You will have to build and publish your entire dll and changed front end files to update your site. This is less flexible when dealing with more people building the on the same website. The compiled code of the web appliction is more compact and faster to build and deploy. Based on your needs you should choose the template for you. 

To create a new web application follow the instructions shown in the screenshots (figure 2) below.

create webapplication image 1

create webapplication image 2
figure 2

For more information on which project you should choose check out the decision chart on msdn.

When you create your new project you see in the solution explorer that visual studio 2010 adds basic website parts to start with. If you choose the option start debugging (figure 3) in visual studio it will build and run website. As you can see you will already have a runnable editable website. In previous versions of visual studio you only started out with a default empty page. You can still choose to create an empty web site or web application if you wish by choosing the appropriate project template.

start debugging
figure 3

A new introduction to the web application projects, is the choice to create a MVC 2 web application. This type of application is based on the model view controller design pattern.
This type of project offers a new way to create an ASP.Net website. In this series I wish to focus on the standard ASP.Net forms development, but I will get into this subject at a later point.