Simple ASP.Net Gridview Sorting and Paging Example

The simplest way to perform paging and sorting in an ASP.Net webforms application is to use the SQLDatasource control. If you do not use the suite of DataSource controls paging and sorting can manually be achieved without too much extra work.

The page contains a GridView control that has paging and sorting enabled by setting AllowPaging and AllowSorting attributes to true. Height and width are optional.

<asp:GridView ID="GridView1" runat="server" AllowPaging="True" AllowSorting="True" Height="292px" 
OnDataBound="GridView1_DataBound" OnPageIndexChanging="GridView1_PageIndexChanging" OnSorting="GridView1_Sorting" 
Width="600px"></asp:GridView>

As I am not using any data access methods, I have created a private method to generate and return a DataTable of data.

private DataTable GetTableData()
{
	DataTable dt = new DataTable();
	dt.Columns.Add("ProductId", typeof(int));
	dt.Columns.Add("Name", typeof(string));
	dt.Columns.Add("ProductNumber", typeof(string));
	dt.Columns.Add("Quantity", typeof(int));
	dt.Columns.Add("UnitPrice", typeof(decimal));

	Random rnd = new Random();
	for (var i = 1; i <= 100; i++)
	{
		var qty = rnd.Next(1, 100);
		DataRow dr = dt.NewRow();
		dr["ProductId"] = i;
		dr["Name"] = $"Product{i}";
		dr["ProductNumber"] = $"A-{qty}";
		dr["Quantity"] = qty;
		dr["UnitPrice"] = i;
		dt.Rows.Add(dr);
	}
	return dt;
}

The webform has two properties that are used as accessors to values stored in viewstate, the name of the sort column and the sort direction.

public string SortColumn
{
	get {return Convert.ToString(ViewState["SortColumn"]);}
	set {ViewState["SortColumn"] = value;}
}

public string SortDirection
{
	get { return Convert.ToString(ViewState["SortDirection"]); }
	set { ViewState["SortDirection"] = value; }
}

In the page load method, if the page load was not from a postback the default sort direction and sort column is set and the BindGrid method is called.

protected void Page_Load(object sender, EventArgs e)
{
	if (!Page.IsPostBack)
	{
		SortDirection = "ASC";
		SortColumn = "ProductId";
		BindGrid();
	}
}

The Bind Grid method calls the GetTableData method to generate the data and the data is bound to the grid.

private void BindGrid()
{
	var dt = GetTableData();
	if (dt != null)
	{
		//Sort the data.
		dt.DefaultView.Sort = SortColumn + " " + SortDirection;
		GridView1.DataSource = dt;
		GridView1.DataBind();
	}
}

The PageIndexChanging event is raised when one of the pager options is clicked, but before the GridView handles the paging operation. Notice the BindGrid method is called causing the grid to be rebound to the data.

protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
	GridView1.PageIndex = e.NewPageIndex;
	BindGrid();
}

The Sorting event is raised when the column header link is clicked, but before the GridView control handles the sort operation. Here the sort expression, in this case the data column to sort by and the sort order are set. The data is then rebound by calling the BindGrid method.

		
protected void GridView1_Sorting(object sender, GridViewSortEventArgs e)
{
	SortDirection = (SortDirection == "ASC") ? "DESC" : "ASC";
	SortColumn = e.SortExpression ;
	BindGrid();
}

The DataBound event fires when all the databinding for the GridView is finished. All we do here is find the index of the GridView column that matches the sort expression and add an image to the column header to indicate the current sort direction.

protected void GridView1_DataBound(object sender, EventArgs e)
{
	int columnIndex = 0;
	foreach (DataControlFieldHeaderCell headerCell in GridView1.HeaderRow.Cells)
	{
		if (headerCell.ContainingField.SortExpression == SortColumn)
		{
			columnIndex = GridView1.HeaderRow.Cells.GetCellIndex(headerCell);
			break;
		}
	}

	Image sortImage = new Image();
	sortImage.ImageUrl = string.Format("images/sort-{0}ending.png", SortDirection);
	GridView1.HeaderRow.Cells[columnIndex].Controls.Add(sortImage);
}

That is pretty much it.

ASP.NET Web Form – Validate User Agrees Terms and Conditions

Just a quick how-to for anyone wanting to add a conditional check box on an ASP.NET Webform.
You need a checkbox with a prompt. There is a custom validator with a client and server side validation method specified.

<p>
    * By ticking the box below you confirm that you are authorised to do so on behalf of your business & you agree to our terms & conditions (set out above).
    <div>
        <asp:CheckBox  runat="server" ID="AcceptTerms" name="AcceptTerms" ClientIDMode="Static"/> I accept the terms & conditions.
    </div>
    <asp:CustomValidator runat="server" ID="CheckBoxRequired" EnableClientScript="true" SetFocusOnError="true" 
	CssClass="field-validation-error" Display="Dynamic" OnServerValidate="AcceptTermsValidation" 
	Text="You must accept the terms & conditions before continuing." ClientValidationFunction="AcceptTermsValidate" 
	ValidationGroup="MainValidationGroup" />
</p>
<asp:Button id="Button2" Text="Accept Terms and Create Account" OnClick="ContinueBtn_OnClick" 
OnClientClick="return showLoader(this);" runat="server" CssClass="input-form-button"
			  ValidationGroup="MainValidationGroup" />

Code for the javascript validator.

function AcceptTermsValidate(source, args) {
    args.IsValid = $("#AcceptTerms").is(':checked');
}

Code for the server side validator.

protected void AcceptTermsValidation(object sender, ServerValidateEventArgs args)
{
	args.IsValid = AcceptTerms.Checked;
}

If the user does not tick the checkbox and clicks on the button the custom validator Text message will be displayed.

Setting up a Visual Studio project for testing with NUnit and SpecFlow

In this article I am going to run through my currently preferred set up for Specflow and NUnit. I have already added a new Class Library project named DemoProject.Tests to an empty solution.


Step 1. Install SpecFlow extension for Visual Studio

Once the SpecFlow extension is installed you will be able to add SpecFlow items to a project from the Installed Templates list of the Add Item dialog box.

  1. In the Tools menu select Extension and Updates…. This will open the Extensions Manager dialog.
  2. In the dialog choose the Online gallery in the left pane.
  3. In the right pane type specflow in the search box.
  4. In the middle pane find Specflow for Visual Studio 2013 in the results list and click the Download button. If you already have it installed a tick will be displayed instead of a download button.

specflow-visualstudio-extension
The extension will be downloaded.
specflow-download
Click Install to complete the installation.
specflow-install

You will be prompted to restart visual studio.


Step 2. Install NUnitTestAdapter

This step is optional.  I like to run NUnit tests from within the Test Explorer in Visual Studio rather than running the tests using the NUnit external application.  At this moment NUnitTestAdapter is not compatible with NUnit 3.0 so in the following steps I will ensure NUnit 2.6.* is installed.

Open Package Manager Console and run the following command.

Install-Package NunitTestAdapter -version 2.0.0 -projectname DemoProject.Tests

Step 3. Install the Specflow

Open Package Manager Console and run the following command.

Install-Package SpecFlow -ProjectName DemoProject.Tests

The relevant SpecFlow assemblies will now installed in the specified project.


Step 4. Install Specflow.NUnit

If you run this command without the specific version parameter this will install Specflow.NUnit 2.0.0 which includes NUnit 3.0.0.  As I intend using NUnitTestAdapter (currently only compatible with NUnit 2.6.4) I do not want NUnit version 3 installed so I have specified the -version 1.1.1 parameter, this will install Specflow.NUnit 1.1.1 which includes NUnit 2.6.0.

Open Package Manager Console and run the following command.

Install-Package SpecFlow.NUnit -version 1.1.1 -ProjectName DemoProject.Tests

Step 5. Update NUnit Version
Open Package Manager Console and run the following command.

Install-Package NUnit -version 2.6.4 -ProjectName DemoProject.Tests

This will have updated the NUnit version from 2.6.0 to 2.6.4.


For additional information see the following links:
http://www.specflow.org/resources/
http://www.specflow.org/getting-started/
http://www.specflow.org/documentation/Install-IDE-Integration/
http://ralucasuditu-softwaretesting.blogspot.co.uk/2015/06/write-your-first-test-with-specflow-and.html

How Disabled and ReadOnly State Affect Postback of Textboxes In ASP.Net

There are certain scenarios where Textbox webcontrols will not post the Text value enter by the user back to the server. To understand these scenarios I created the following form consisting of 4 textboxes. On the first textbox (ReadOnlyTextBox) I set the ReadOnly property to true using the properties window. On the second textbox (DisabledTextBox) I set the enabled property to false using the properties window. On the next two textboxes I did not set the ReadOnly or Eanbled properties, but I did add attributes via C# code in the Form_Load event as will be highlighted later. Here is the complete form.

 
<p>
    <asp:TextBox ID="ReadOnlyTextBox" ClientIDMode="Static" runat="server" ReadOnly="True"></asp:TextBox>
</p>
<p>
    <asp:TextBox ID="DisabledTextBox" ClientIDMode="Static" runat="server" Enabled="False"></asp:TextBox>
</p>
<p>
    <asp:TextBox ID="ReadOnlyByAttributeTextBox" ClientIDMode="Static" runat="server"></asp:TextBox>
</p>
<p>
    <asp:TextBox ID="DisabledByAttributeTextBox" ClientIDMode="Static" runat="server"></asp:TextBox>
</p>
<p>
    <asp:Button ID="SubmitButton" runat="server" OnClick="SubmitButton_Click" Text="Submit" /> 
    <asp:Button ID="ClearButton" runat="server" Text="Clear Fields" OnClick="ClearButton_Click" />
</p>
<p>
    <asp:Button ID="PopulateFieldsButton" runat="server" Text="Populate Fields (ServerSide)" OnClick="PopulateFieldsButton_Click" />
    <input type="button" value="Populate Fields (ClientSide)" onclick="Populate();" />
</p>
<asp:Literal ID="OutputDisplay" runat="server"></asp:Literal>

I also added a basic javascript function to the page to set the Text value of the 4 text boxes for comparing the difference in postback results when the textboxes are populated via server side code or client side code.

<script type="text/javascript">
    function Populate() {
        var readOnly = document.getElementById("ReadOnlyTextBox");
        var disabled = document.getElementById("DisabledTextBox");
        var readOnly2 = document.getElementById("ReadOnlyByAttributeTextBox");
        var disabled2 = document.getElementById("DisabledByAttributeTextBox");

        readOnly.value = "ReadOnly By Property";
        disabled.value = "Disabled By Property";
        readOnly2.value = "ReadOnly By Atribute";
        disabled2.value = "Disabled By Attribute";
    }
</script>

Here is the C# code for the ASP.NET page. In the Page_Load event the readonly and disabled attributes are set for the relevant two textboxes. When the form is submitted the values of the tesxtboxes are read and displayed in a Literal webcontrol. There is also a PopulateFieldsButton_Click event to populate the tesxtboxes from server side.

protected void Page_Load(object sender, EventArgs e)
{
    ReadOnlyByAttributeTextBox.Attributes.Add("readonly", "readonly");
    DisabledByAttributeTextBox.Attributes.Add("disabled", "disabled");

	// uncomment below to compare effect of populating TextBoxes in form load at server side
    //SetTextBoxes();
}
      
protected void SubmitButton_Click(object sender, EventArgs e)
{
    var output = new StringBuilder();
    output.Append("Posted Values<br/>");
    output.AppendFormat("ReadOnly By Property TextBox: {0}<br/>", ReadOnlyTextBox.Text);
    output.AppendFormat("Disabled By Property TextBox: {0}<br/>", DisabledTextBox.Text);
    output.AppendFormat("ReadOnly By Attribute TextBox: {0}<br/>", ReadOnlyByAttributeTextBox.Text);
    output.AppendFormat("Disabled By Attribute TextBox: {0}<br/>", DisabledByAttributeTextBox.Text);
    OutputDisplay.Text = output.ToString();
}

protected void PopulateFieldsButton_Click(object sender, EventArgs e)
{
    SetTextBoxes();
}

protected void ClearButton_Click(object sender, EventArgs e)
{
    DisabledTextBox.Text = String.Empty;
    ReadOnlyTextBox.Text = String.Empty;
    ReadOnlyByAttributeTextBox.Text = String.Empty;
    DisabledByAttributeTextBox.Text = String.Empty;
}

private void SetTextBoxes()
{
    ReadOnlyTextBox.Text = "ReadOnly By Property";
    DisabledTextBox.Text = "Disabled By Property";
    ReadOnlyByAttributeTextBox.Text = "ReadOnly By Attribute";
    DisabledByAttributeTextBox.Text = "Disabled By Attribute";
}

On to the tests.
Test 1 – Populate server side.

  • Ensure the fields are empty by clicking the Clear Fields button
  • Click the Populate Fields (ServerSide)
  • Click the Submit Button

The results show the Text value was posted back to all except the textbox with the disabled attribute set by C# code.

ReadOnly By Property TextBox: ReadOnly By Property
Disabled By Property TextBox: Disabled By Property
ReadOnly By Attribute TextBox: ReadOnly By Attribute
Disabled By Attribute TextBox: 

Test 2 – Populate client side

  • Ensure the fields are empty by clicking the Clear Fields button
  • Click the Populate Fields (ClientSide)
  • Click the Submit Button

The results show the Text value was posted back to the server only on the textbox with the readoly attribute set by C# code.

ReadOnly By Property TextBox: 
Disabled By Property TextBox: 
ReadOnly By Attribute TextBox: ReadOnly By Atribute
Disabled By Attribute TextBox: 

So, if you want the Text value of the textbox to be posted to the server and you do not want the user to modify the contents of the textbox use C# code to set the ReadOnly attribute.

Using a shared page to retrieve and display a pdf file

We had a situtation recently where we needed to allow users to download a pdf file from our server but we did not want to show the URL in the browser window. Although there are many ways to do this we opted to use an aspx page to retrieve the file and present it to the user. Knowing we will probably use this method againe in the application we decided to make sure the page was reusable.

The code below is of a webform load event. There are two querystring parameters, obviously these parameters are relevant to our requirements and you may user different parameters. The t parameter is the token, which is passed to the backend server as an identifier e.g. eventid, certificateid or something similar. The r parameter is to identify which url should be called.

protected void Page_Load(object o, EventArgs e)
{
    var token = Request.QueryString["t"];
    var report = Request.QueryString["r"];
    var URL = string.Empty;

    switch (report.ToLower())
    {
        case "u1":
            URL = System.Configuration.ConfigurationManager.AppSettings["url1"];
            break;
        case "u2":
            URL = System.Configuration.ConfigurationManager.AppSettings["url2"];
            break;
    }

    var fullURL = string.Format("{0}{1}", URL, token);
    var client = new System.Net.WebClient();
    try
    {
        Byte[] buffer = client.DownloadData(fullURL);
        if (buffer != null)
        {
            Response.ContentType = "application/pdf";
            Response.AddHeader("content-length", buffer.Length.ToString());
            Response.BinaryWrite(buffer);
        }
    }
    catch (System.Net.WebException ex)
    {
        if (((System.Net.HttpWebResponse)ex.Response).StatusCode == System.Net.HttpStatusCode.NotFound)
        {
            Server.Transfer("/404.aspx", false);
        }
    }
}

For us this page was called pdf.aspx, possible URLs this page will be called with are;

http://www.somedomain.com/pdf.aspx?t=1234&r=u1
http://www.somedomain.com/pdf.aspx?t=RY6N-UX6M&r=u2

It is possible to leave the token parameter blank if an identifier is not needed.

http://www.somedomain.com/pdf.aspx?t=&r=u2

The URLs of the backend server are stored in the web.config appSetting section, possible examples are.

<appSettings>
    <add key="url1" value="http://somebackendserver/{0}" />
    <add key="url2" value="http://anotherserver.addomain.net/certificates.aspx?ref={0}" />
</appSettings>

I have replaced the actual appSettings used with url1 and url2, your appSettings will have meaningful names.
The pdf file is downloaded to a byte array and presented to the user. If a WebException occurs the status of the response is checked and if a HttpStatusCode.NotFound status was returned from the backend server the applications 404 page is displayed.

There are a number of ways to extend this code and functionality relevant to any purpose.