Form Views

This is a lengthy tutorial looking at creating a FormView with multiple viewing modes for read-only, editable records, new records, and no records found.

We will be swapping between mark-up and C# code behind throughout, and relying on Bootstrap CSS for styling.

Start by dragging the FormView control from the Toolbox (can be found under 'Data').

Alternatively type it in as you see below.

First, and foremost, change the 'ID' property to an appropriate name, such as 'FvSiteView'.

We can set some properties using IntelliSense, such as: AllowPaging and DataKeyNames. The latter sets the FormView's unique identifier.

We will also want an 'OnItemCommand' to trigger what will happen when we click a command button.

Start typing 'OnIte'... and select the property from the IntelliSense list.

Type '=' and then just select the '<Create New Event>' option from the IntelliSense options.

This will create an empty subroutine for you in the code behind that will be called on every item command. More to come on that later.

If we are to allow for record creation (insertion) and editing (updating) then we are likely to need a number of other subroutines to be called on other events.

Using the same method as above, taking advantage of IntelliSense to create the events for you, add the following properties:

OnModeChanging, OnItemInserting, OnItemUpdating, and OnDataBound.

You may like to list items vertically as shown below (code provided below the image):

<h2><%: Title %>.</h2>

<section class="container">
  <asp:FormView 
      ID="FvSiteView" 
      runat="server" 
      CssClass="col-md-12"
      AllowPaging="false" 
      DataKeyNames="SiteID" 
      OnItemCommand="FvSiteView_ItemCommand" 
      OnModeChanging="FvSiteView_ModeChanging"
      OnItemInserting="FvSiteView_ItemInserting"
      OnItemUpdating="FvSiteView_ItemUpdating"
      OnDataBound="FvSiteView_DataBound">


    <!-- Enter all item templates here: -->


  </asp:FormView>
</section>

Your code behind should be automatically populated with the associated subroutines (assuming you selected '<Create New Event>' option from IntelliSense.) See below.

Back to the code-behind...

The FormView will have multiple viewing modes (read-only, editable, new record, no record found) and we can achieve this through ItemTemplates, thus saving us creating separate web forms for each mode.

Declare your templates as shown below, establishing an open and closed tag for:

  • ItemTemplate
  • EditItemTemplate
  • InsertItemTemplate
  • EmptyDataTemplate

We can populate each template with some basic section blocks and apply the Bootstrap CSS classes as appropriate. 

Below we have created an initial row to how two columns, the first will hold the sub-heading and the second will contain our hyperlink-controls to navigate the different viewable modes.

Code and screenshot shown below:

<section class="row">

  <section class="col-xs-8">
  </section>

  <section class="col-xs-4 text-right">
  </section>

</section>

We can use <h3> tags for the sub-heading (page title is a <h2> tag) and each template will have a variant on the text to indicate what mode we are in.

For the hyperlink controls we will be using asp:LinkButtons and using the CssClass properties to set them to Bootstrap glyphicons (https://getbootstrap.com/docs/3.3/components/).

We have added a 'disabled' CssClass to the hyperlinks that need deactivating (such as a new record cannot be edited, and to restrict linking to the same mode they're already in).

'CssClass' is used as a property instead of 'class' as these are ASP.NET controls and not part of HTML.

<ItemTemplate>
  <section class="row">
    <section class="col-xs-8">
      <h3>Form View (Read-Only)</h3>
    </section>
    <section class="col-xs-4 text-right">
      <h3>
        <asp:linkbutton id="btnInsertView" CommandName="New" runat="server" CssClass="glyphicon glyphicon-plus btn"/>
        <asp:linkbutton id="btnEditView" CommandName="Edit" runat="server" CssClass="glyphicon glyphicon-pencil btn"/>
        <asp:linkbutton id="btnListView" CommandName="ListView" runat="server" CssClass="glyphicon glyphicon-th-list btn"/>
      </h3>
    </section>
  </section>
</ItemTemplate>
<EditItemTemplate>
  <section class="row">
    <section class="col-xs-8">
      <h3>Form View (Edit)</h3>
    </section>
    <section class="col-xs-4 text-right">
      <h3>
        <asp:linkbutton id="btnInsertView" CommandName="New" runat="server" CssClass="glyphicon glyphicon-plus btn"/>
        <asp:linkbutton id="btnEditView" CommandName="Edit" runat="server" CssClass="glyphicon glyphicon-pencil btn disabled"/>
        <asp:linkbutton id="btnListView" CommandName="ListView" runat="server" CssClass="glyphicon glyphicon-th-list btn"/>
      </h3>
    </section>
  </section>
</EditItemTemplate>
<InsertItemTemplate>
  <section class="row">
    <section class="col-xs-8">
      <h3>Form View (Insert)</h3>
    </section>
    <section class="col-xs-4 text-right">
      <h3>
        <asp:linkbutton id="btnInsertView" CommandName="New" runat="server" CssClass="glyphicon glyphicon-plus btn disabled"/>
        <asp:linkbutton id="btnEditView" CommandName="Edit" runat="server" CssClass="glyphicon glyphicon-pencil btn disabled"/>
        <asp:linkbutton id="btnListView" CommandName="ListView" runat="server" CssClass="glyphicon glyphicon-th-list btn"/>
      </h3>
    </section>
  </section>
</InsertItemTemplate>
<EmptyDataTemplate>
  <section class="row">
    <section class="col-xs-12">
      <h3>(No record found.)</h3>
    </section>
  </section>
</EmptyDataTemplate>

For reference, the mark-up should now look like this:

Now lets switch out to the C# code-behind (SiteView.aspx.cs).

Firstly, add a new import (using) statement to reference our Dataset and TableAdapters. The format of the import is as such:

using <namespace>.<DatasetName>TableAdapters;

using AssetManagementSystem._DAL_AMSTableAdapters;

Populate the pre-existing Page_Load subroutine with the IF statement and call to PageDataRefresh as shown below.

Under the Page_Load procedure we need to create the PageDataRefresh subroutine we have just called.

This will take an 'id' query parameter from the URL and save it to a variable, or if there is no query it will assume '0' (zero).

If the id parameter is zero, then the view mode is changed to the InsertItemTemplate (new record).

In any event, we establish an instance of our SiteTableAdapter (called siteAdapter). We then set the FormViews data source to the appropriate function (GetByID) and pass in the 'id' query parameter previously collected from the URL. Finally we bind the data to the FormView control.

protected void Page_Load(object sender, EventArgs e)
{
  if (!IsPostBack)
  {
    PageDataRefresh();
  }
}

private void PageDataRefresh()
{
  string sID = Request.QueryString["id"];
  if (sID == null || sID == "0")
  {
    sID = "0";
    FvSiteView.ChangeMode(FormViewMode.Insert);
  }

  // If sID needs to be integer...
  // int siteID = Convert.ToInt32(sID.ToString());

  /* *********** Configure DAL *********** */
  SiteTableAdapter siteAdapter = new SiteTableAdapter();
  FvSiteView.DataSource = siteAdapter.GetByID(sID);
  FvSiteView.DataBind();
}

The code behind is shown below, with reference to where the names for the import/using statement and for selecting the appropriate TableAdapter:

We are not finished, but you should test that everything so far works. 

Loading the page without any query parameters in the URL will assume a new record is needed and bring the user to the 'Insert' view:

We can type a query parameter straight into the URL if we know a SiteID from the table. For example append the following to the end of the URL:

    ?id-ALPH

Selecting an id that doesn't exist will take us to a 'No Record Found' (EmptyDataTemplate). For example 'DELT' does not exist:

    ?id-DELT

In the event of no records being found to match the query parameters, you may decide to skip the EmptyDataTemplate and assume a new record is required.

You can achieve this by editing the _DataBound subroutine (previously created using IntelliSense on the FormView's creation).

This IF statement checks for how many data items have be found and presents an InsertItemTemplate (FormViewMode.Insert) if there are zero items, else the EditItemTemplate (FormViewMode.Edit).

protected void FvSiteView_DataBound(object sender, EventArgs e)
{
  if (FvSiteView.DataItemCount == 0)
  {
    FvSiteView.ChangeMode(FormViewMode.Insert);
  }
  else
  {
    FvSiteView.ChangeMode(FormViewMode.Edit);
  }
}

Save and retest this change. Again try the non-existent SiteID, 'DELT':

    ?id-DELT

You should now see the Insert view:

When we created the FormView we established some properties to call subroutines on some events. These subroutines were automatically created, but are currently empty.

In the _ItemCommand procedure create a selection statement (we use a 'switch...case' statement below) to consider the command buttons that may be triggered.

The 'New' command needs to change the mode to Insert, but this does not change the parameter in the URL and this is a problem. So instead we can just redirect the user back to this page with the parameter altered to trigger the appropriate view mode on page load.

The 'ListView' button will redirect the user to the List View web form.

protected void FvSiteView_ItemCommand(object sender, FormViewCommandEventArgs e)
{
  switch (e.CommandName)
  {
    case "New":
      Response.Redirect("~/Restricted/SiteView.aspx?id=0");
      break;
    case "Cancel":
      FvSiteView.ChangeMode(FormViewMode.ReadOnly);
      PageDataRefresh();
      break;
     case "ListView":
      Response.Redirect("~/Restricted/SiteList.aspx");
      break;
  }
}

To ensure mode changes take appropriate effect we will call our PageDataRefresh subroutine we created earlier. 

Navigate to the generated _ModeChanging subroutine and have it reflect the code below:

protected void FvSiteView_ModeChanging(object sender, FormViewModeEventArgs e)
{
  // Enable a FormView mode change (Read-Only, Edit/Update, New/Insert, Empty)
  FvSiteView.ChangeMode((FormViewMode)e.NewMode);
  PageDataRefresh();
}

You may like to now test that these hyperlink buttons work.

Back to the mark-up...

Starting with the ItemTemplate we need set of new section blocks for a Bootstrap row and column. This will contain a HTML table to provide some structure to our form.

The first cell (<td>... </td>) is our label or plain text informing the user what data is in the text box.

The second cell will need an asp:TextBox.

You can type your asp:TextBox directly, or drag in from the toolbox and thus ensuring the correct syntax.

You can set its properties directly in the markup, or use the Properties pane in Visual Studio.

You need to change the Textbox's ID (these must be unique to the ItemTemplate and the wider web form, but may re-appear in neighbouring Template views for the same FormView).

As this is the 'Read-Only' view lets set the Enabled property to 'False'. This will visibly grey-out the textbox to users.

Finally the Text property needs to be bound to the appropriate field in the TableAdapter. this is achieved thusly:

    <%#Eval("SiteID")%>

Note: Eval is used to pull in read-only data, whereas Bind is used when data needs to be pushed back into the database (such as with the Edit and Insert views).

 

You will need new table rows for each field in the table, and each Textbox should be bound to the exact field name. Its useful to have the Server Explorer open to check these names and ensure you include everything you need:

Within the <ItemTemplate> the new row will look like this:

Note: Enabled="False" and use of Eval.

  <section class="row">
    <section class="col-xs-12">
      <table>
        <tr><td>Site ID:</td><td><asp:TextBox ID="txtSiteID" runat="server" Enabled="False" Text='<%#Eval("SiteID") %>'></asp:TextBox></td></tr>
        <tr><td>Site Name:</td><td><asp:TextBox ID="txtSiteName" runat="server" Enabled="False" Text='<%#Eval("SiteName") %>'></asp:TextBox></td></tr>
        <tr><td>Address Line 1:</td><td><asp:TextBox ID="txtAddress1" runat="server" Enabled="False" Text='<%#Eval("[Address 1]") %>'></asp:TextBox></td></tr>
        <tr><td>Address Line 2:</td><td><asp:TextBox ID="txtAddress2" runat="server" Enabled="False" Text='<%#Eval("[Address 2]") %>'></asp:TextBox></td></tr>
        <tr><td>City:</td><td><asp:TextBox ID="txtCity" runat="server" Enabled="False" Text='<%#Eval("City") %>'></asp:TextBox></td></tr>
        <tr><td>Post Code:</td><td><asp:TextBox ID="txtPostCode" runat="server" Enabled="False" Text='<%#Eval("PostCode") %>'></asp:TextBox></td></tr>
        <tr><td>Status:</td><td><asp:TextBox ID="txtStatus" runat="server" Enabled="False" Text='<%#Eval("Status") %>'></asp:TextBox></td></tr>
      </table>
    </section>
  </section>

</ItemTemplate>

Within the <EditItemTemplate> the new row will look like this:

Note: Enabled="True" and use of Bind.

  <section class="row">
    <section class="col-xs-12">
      <table>
        <tr><td>Site ID:</td><td><asp:TextBox ID="txtSiteID" runat="server" Enabled="True" Text='<%#Bind("SiteID") %>'></asp:TextBox></td></tr>
        <tr><td>Site Name:</td><td><asp:TextBox ID="txtSiteName" runat="server" Enabled="True" Text='<%#Bind("SiteName") %>'></asp:TextBox></td></tr>
        <tr><td>Address Line 1:</td><td><asp:TextBox ID="txtAddress1" runat="server" Enabled="True" Text='<%#Bind("[Address 1]") %>'></asp:TextBox></td></tr>
        <tr><td>Address Line 2:</td><td><asp:TextBox ID="txtAddress2" runat="server" Enabled="True" Text='<%#Bind("[Address 2]") %>'></asp:TextBox></td></tr>
        <tr><td>City:</td><td><asp:TextBox ID="txtCity" runat="server" Enabled="True" Text='<%#Bind("City") %>'></asp:TextBox></td></tr>
        <tr><td>Post Code:</td><td><asp:TextBox ID="txtPostCode" runat="server" Enabled="True" Text='<%#Bind("PostCode") %>'></asp:TextBox></td></tr>
        <tr><td>Status:</td><td><asp:TextBox ID="txtStatus" runat="server" Enabled="True" Text='<%#Bind("Status") %>'></asp:TextBox></td></tr>

        <tr><td></td><td><asp:LinkButton ID="btnUpdate" runat="server" CommandName="Update" Text="Save" CssClass="btn btn-primary"/></td></tr>
      </table>
    </section>
  </section>

</EditItemTemplate>

Within the <InsertItemTemplate> the new row will look like this:

Note: Enabled="True" and use of Bind.

  <section class="row">
    <section class="col-xs-12">
      <table>
        <tr><td>Site ID:</td><td><asp:TextBox ID="txtSiteID" runat="server" Enabled="True" Text='<%#Bind("SiteID") %>'></asp:TextBox></td></tr>
        <tr><td>Site Name:</td><td><asp:TextBox ID="txtSiteName" runat="server" Enabled="True" Text='<%#Bind("SiteName") %>'></asp:TextBox></td></tr>
        <tr><td>Address Line 1:</td><td><asp:TextBox ID="txtAddress1" runat="server" Enabled="True" Text='<%#Bind("[Address 1]") %>'></asp:TextBox></td></tr>
        <tr><td>Address Line 2:</td><td><asp:TextBox ID="txtAddress2" runat="server" Enabled="True" Text='<%#Bind("[Address 2]") %>'></asp:TextBox></td></tr>
        <tr><td>City:</td><td><asp:TextBox ID="txtCity" runat="server" Enabled="True" Text='<%#Bind("City") %>'></asp:TextBox></td></tr>
        <tr><td>Post Code:</td><td><asp:TextBox ID="txtPostCode" runat="server" Enabled="True" Text='<%#Bind("PostCode") %>'></asp:TextBox></td></tr>
        <tr><td>Status:</td><td><asp:TextBox ID="txtStatus" runat="server" Enabled="True" Text='<%#Bind("Status") %>'></asp:TextBox></td></tr>

        <tr><td></td><td><asp:LinkButton ID="btnInsert" runat="server" CommandName="Insert" Text="Submit" CssClass="btn btn-primary"/></td></tr>
      </table>
    </section>
  </section>

</InsertItemTemplate>

The <EmptyDataTemplate> could be customised to reflect the mark-up below:

(Below the code is an image showing the FormView with the column section blocks collapsed so you can see the general layout).

<EmptyDataTemplate>
  <section class="row">
    <section class="col-xs-12">
      <h3>(No record found.)</h3>
    </section>
  </section>
</EmptyDataTemplate>

Returning to the C# code behind...

We need some similar code between the _ItemInserting and _ItemUpdating subroutines that were automatically generated. To be able to repeat some code we are going to call a new subroutine from each of these and within this new subroutine we will determine whether to insert or update.

As the Textboxes are inside Templates our C# cannot refer directly to their ID's, so instead we will create temporary duplicates of these in code using a FindControl method, and we will assign the text properties of these to variables (we could bypass the variables and use the Textboxes directly).

An IF statement determines whether an UPDATE or INSERT was triggered and runs the appropriate method from the TableAdapter.

If its an UPDATE, then view mode will return to ReadOnly and a JavaScript message will display 'Record Edited'.

If its an INSERT, then the user will be redirected to the page to present the Read-Only view of the new record and a JavaScript message will display 'Record Added'.

We encase these connections to the database in a try-catch block and present an error message if something fails during this process.

protected void FvSiteView_ItemInserting(object sender, FormViewInsertEventArgs e)
{
  FvSiteView_CallInsertOrUpdate("Insert");
}

protected void FvSiteView_ItemUpdating(object sender, FormViewUpdateEventArgs e)
{
  FvSiteView_CallInsertOrUpdate("Update");
}

protected void FvSiteView_CallInsertOrUpdate(string CallCommand)
{
  // Code versions of all controls
  TextBox siteID_txt = (TextBox)FvSiteView.FindControl("txtSiteID");
  TextBox siteName_txt = (TextBox)FvSiteView.FindControl("txtSiteName");
  TextBox address1_txt = (TextBox)FvSiteView.FindControl("txtAddress1");
  TextBox address2_txt = (TextBox)FvSiteView.FindControl("txtAddress2");
  TextBox city_txt = (TextBox)FvSiteView.FindControl("txtCity");
  TextBox postCode_txt = (TextBox)FvSiteView.FindControl("txtPostCode");
  TextBox status_txt = (TextBox)FvSiteView.FindControl("txtStatus");

  // Assign all text properties of controls to variables
  // Or skip this and assign straight into INSERT/UPDATE parameters
  string siteID = siteID_txt.Text;
  string siteName = siteName_txt.Text;
  string address1 = address1_txt.Text;
  string address2 = address2_txt.Text;
  string city = city_txt.Text;
  string postCode = postCode_txt.Text;
  string status = status_txt.Text;

  SiteTableAdapter siteAdapter = new SiteTableAdapter();

  try
  {
    if (CallCommand == "Update")
    {
      string originalID = Request.QueryString["id"].ToString();

      // Conduct Update
      siteAdapter.UpdateRecord(siteID, siteName, address1, address2, city, postCode, status, originalID);

      Response.Write("<script LANGUAGE='JavaScript' >alert('Record Edited')</script>");

      // Return to Read Only mode
      FvSiteView.ChangeMode(FormViewMode.ReadOnly);
      PageDataRefresh();
    }
    else if (CallCommand == "Insert")
    {
      siteAdapter.InsertRecord(siteID, siteName, address1, address2, city, postCode, status);
      //string newID = siteAdapter.InsertAndReturnID(siteID, siteName, address1, address2, city, postCode, status).ToString();
      Response.Write("<script LANGUAGE='JavaScript' >alert('Record Added')</script>");
      // Redirect User
      Response.Redirect("~/Restricted/SiteView.aspx?id=" + siteID);
    }
  }
  catch (System.Exception ex)
  {
    Response.Write("<script LANGUAGE='JavaScript' >alert('An ERROR occurred connecting to the database.')</script>");
  }
}

Testing the standard FormView with no query parameters in the URL. Notice which buttons are disabled and active. Textboxes should be blank and you should be able to add new records from here.

Try creating a new record such as DELT for the Delta site using the 'Submit' button when you have chosen appropriate values.

(Note: We have assumed a SiteID that is set by the user, in another tutorial we will consider automatically generated IDs as these pose another problem).

Upon creation of the new record you should get a Read-Only view with greyed-out textboxes. You can change the URL parameters to other IDs to test it further.

Select the edit button and the textboxes should display the same information but in enabled controls and with a 'Save' button.

There we have the Form View. A lot of markup, and a lot of C# code, all to ensure 3 main views are accepted (Read-Only, New Record, and Edit) and to ensure appropriate actions are taken when no records are found.

If IDs are generated by the database there are some changes required to our Insert and Update statements. This will be explored later.