Introduction
In the following article, we'll demonstate an approach on how to implement a indexed search filter for a simple phone book web application developed using ASP.NET MVC. During the discussion we'll learn how to use jQuery's AJAX requests to dynamically update the contents of the phone book list making it responsive to the user input. The web application being discussed retrieves the list of phone numbers from the database and based on the data being retrieved generates a web page containing the list of persons which phone numbers are arranged into a table as well as a textbox in which user can what particular person or phone number he'd like to find. The following phone book application allows to find a person or phone number by a partitial match. When user inputs in the textbox field what person or number he's about to find, the table containing the list of person's phone numbers is modified by displaying the phone numbers or names of only those persons that exatcly match the search criteria.
Using the code
PhoneBookController.cs: The following fragment of code implements PhoneBookController controller that contains the following actions: the Search action method retrieves the data by connecting to the local database by executing an SQL-query being constructed during the following action method execution. It stores the data being retrived to the list defined within the data model. Another action method Modify is used to insert phone book entries into the database by performing the specific SQL-query:
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace IndexedSearch.Controllers
{
public class PhoneBookController : Controller
{
private Models.SearchModel SearchModel = new Models.SearchModel();
public ActionResult Index()
{
return View();
}
string SqlGetConnectionString(string ConfigPath, string ConnectionStringName)
{
System.Configuration.Configuration rootWebConfig =
System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration(ConfigPath);
System.Configuration.ConnectionStringSettings connectionString =
rootWebConfig.ConnectionStrings.ConnectionStrings[ConnectionStringName];
if (connectionString == null || string.IsNullOrEmpty(connectionString.ConnectionString))
throw new Exception("Fatal error: Connection string is missing from web.config file");
return connectionString.ConnectionString;
}
public ActionResult Search(string text)
{
using (SqlConnection connection =
new SqlConnection(this.SqlGetConnectionString("/Web.Config", "PhoneBookDB")))
{
try
{
string SqlQuery = @"SELECT dbo.Contacts.* FROM dbo.Contacts";
if (Request.IsAjaxRequest() && text != "")
SqlQuery += " WHERE dbo.Contacts.ContactName LIKE @text OR dbo.Contacts.Phone LIKE @text";
SqlCommand command = new SqlCommand(SqlQuery, connection);
command.Parameters.AddWithValue("@text", String.Format("%{0}%", text));
connection.Open();
using (SqlDataReader reader = command.ExecuteReader())
{
while (reader.Read() && reader.HasRows != false)
{
Models.PhoneBookEntry PhoneEntry = new Models.PhoneBookEntry();
PhoneEntry.ContactID = Int32.Parse(reader["ContactID"].ToString());
PhoneEntry.ContactName = reader["ContactName"].ToString();
PhoneEntry.Phone = reader["Phone"].ToString();
if ((!PhoneEntry.ContactID.Equals("")) &&
(!PhoneEntry.ContactName.Equals("")) && (!PhoneEntry.Phone.Equals("")))
SearchModel.PhoneList.Add(PhoneEntry);
}
reader.Close();
}
}
catch (Exception ex) { Console.WriteLine(ex.Message); }
}
return PartialView(SearchModel.PhoneList);
}
public ActionResult Create(string person, string phone)
{
using (SqlConnection connection =
new SqlConnection(this.SqlGetConnectionString("/Web.Config", "PhoneBookDB")))
{
try
{
string SqlQuery = @"INSERT INTO dbo.Contacts VALUES (@person, @phone)";
SqlCommand command = new SqlCommand(SqlQuery, connection);
command.Parameters.AddWithValue("@person", person);
command.Parameters.AddWithValue("@phone", phone);
connection.Open();
command.ExecuteNonQuery();
connection.Close();
}
catch (Exception ex) { Console.WriteLine(ex.Message); }
}
return RedirectToAction("Index");
}
}
}
SearchModel.cs: SearchModel class instantinates the generic List<T> class used to store the set of phone book entries. As the parameter type T we use another class _PhoneBookEntry which represents an entry to the phone book list. SearchModel is used as the main data model of the following ASP.NET MVC project:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace IndexedSearch.Models
{
public class PhoneBookEntry
{
private int contact_id;
private string contact_name;
private string phone;
public int ContactID {
get { return (!contact_id.Equals(-1)) ? contact_id : -1; }
set { contact_id = (!value.Equals(-1)) ? value : -1; } }
public string ContactName {
get { return (!contact_name.Equals("")) ? contact_name : ""; }
set { contact_name = (!value.Equals("")) ? value : ""; } }
public string Phone
{
get { return (!phone.Equals("")) ? phone : ""; }
set { phone = (!value.Equals("")) ? value : ""; }
}
}
public class SearchModel
{
private List<PhoneBookEntry> phone_list = null;
public SearchModel()
{
if (phone_list == null)
phone_list = new List<PhoneBookEntry>();
}
public List<PhoneBookEntry> PhoneList
{ get { return (phone_list != null) ? phone_list : null; } }
}
}
Index.cshtml: The following code snippet is a web-page contaning the two forms. The first form is the SearchForm that contains the editable textbox area in which user inputs the text to find a phone book entry that partitially matches the creteria of search. In the Scripts section of the following web-page we normally define jQuery code that implement the textbox area event handling. When user modifies the textbox area the paste/keyup event is fired the following code normally dispatches an AJAX-request to the specific controller's action that retrieves the data from the database and generates the HTML contents. After that another jQuery clause dynamically modifies the phone book's main page.
@{
ViewBag.Title = "Index";
}
<table border="1" align="center" width="500" cellpadding="0" cellspacing="0">
@using (Html.BeginForm("SearchForm", "PhoneBookController", FormMethod.Get))
{
@Html.ValidationSummary(true);
<tr><td>
<table align="center" width="300" cellpadding="0" cellspacing="0">
<tr><td><p><b>Find a contact:</b><br />@Html.TextBox("SearchBox",
null, new { @class = "search_box", @id = "search" })</p></td></tr></table>
</td></tr>
<tr><td>
<div id="search_results">@{Html.RenderAction("Search", new { Text = "Empty" });}</div>
</td></tr>
}
@{Html.EndForm();}
<tr><td><br />
<form method="get" action=@Url.Action("Create", "PhoneBook") id="createform" autocomplete="off">
<table border="1" align="center">
<tr>
<td><b>Person:</b></td>
<td>
@Html.TextBox("Person",
null, new { @class = "person_box", @id = "person" })
</td>
</tr>
<tr><td colspan="2"><div id="invalid_person" style="color: red; visibility: hidden">Specify the correct person's name 3-31 characters</div></td></tr>
<tr>
<td><b>Phone:</b></td>
<td>
@Html.TextBox("Phone",
null, new { @class = "phone_box", @id = "phone" })
</td>
</tr>
<tr><td colspan="2"><div id="invalid_phone" style="color: red; visibility: hidden">Specify the correct phone number e.g. +x(xxx)xxx-xxxx</div></td></tr>
<tr><td colspan="2"><input type="button" id="submit_btn" value="Submit>" /></td></tr>
</table>
</form>
</td></tr>
</table>
@section Scripts {
<script type="text/javascript">
$("#person").on("keyup paste", function () {
var person_match = $("#person").val().match(/^[a-zA-Z\.\s]*$/);
var is_visible = (person_match == null) && ($("#person").val()) ? "visible" : "hidden";
$("#invalid_person").css({ 'visibility': is_visible });
});
$("#phone").on("keyup paste", function () {
var is_visible = ($("#phone").val()) && ($("#phone").val().length > 15) ||
($("#phone").val().match(/^[a-zA-Z\.\s]*$/) != null) ? "visible" : "hidden";
$("#invalid_phone").css({ 'visibility': is_visible });
});
$("#submit_btn").click(function () {
var person_match = $("#person").val().match(/^[a-zA-Z\.\s]*$/);
var phone_match = $("#phone").val.length > 0 && $("#phone").val().match(/^(?:(?:\+?1\s*(?:[.-]\s*)?)?(?:\(\s*([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9])\s*\)|([2-9]1[02-9]|[2-9][02-8]1|[2-9][02-8][02-9]))\s*(?:[.-]\s*)?)?([2-9]1[02-9]|[2-9][02-9]1|[2-9][02-9]{2})\s*(?:[.-]\s*)?([0-9]{4})(?:\s*(?:#|x\.?|ext\.?|extension)\s*(\d+))?$/);
var is_invalid_phone = (phone_match == null) || (!$("#phone").val()) || ($("#phone").val().length < 10) ? "visible" : "hidden";
var is_invalid_person = (person_match == null) && ($("#person").val()) ? "visible" : "hidden";
$("#invalid_phone").css({ 'visibility': is_invalid_phone });
if (person_match != null && phone_match != null) $("#createform").submit();
});
$("#search").on("keyup paste", function () {
$.ajax({
type: "GET",
url: '@Url.Action("Search","PhoneBook")'+"/?text="+$("#search").val(),
dataType: 'html',
contentType: 'application/html; charset=utf-8',
success: function (content, ajaxObj) {
$("#search_results").html(content);
},
});
})
</script>
}
Search.cshtml: The following fragment of code implements a web-page, in which we're using ASP.NET script language to generate and display the list of phone book entries obtained from the data model.
@model List<IndexedSearch.Models.PhoneBookEntry>
<table border="0" align="center" width="500" cellpadding="0" cellspacing="0">
<tr>
<td style="text-align: center; background-color: black; color: white; width: 90px"><b>Contact ID</b></td>
<td style="text-align: left; background-color: black; color: white; width: 250px"><b>Contact Name</b></td>
<td style="text-align: center; background-color: black; color: white; width: 150px"><b>Phone</b></td>
</tr>
@{ int line_id = 0; string bgcolor = ""; }
@foreach (var PhoneBookEntry in Model)
{
<tr>
@{ bgcolor = (line_id++ % 2) == 0 ? "white" : "lightgrey"; }
<td style="background-color: @bgcolor; text-align: center; width: 90px">@line_id</td>
<td style="background-color: @bgcolor; text-align: left; width: 250px">@PhoneBookEntry.ContactName</td>
<td style="background-color: @bgcolor; text-align: center; width: 150px">@PhoneBookEntry.Phone</td>
</tr>
}
</table>
History
- December 13, 2016 - The first version of the article has been published.