Click here to Skip to main content
15,878,809 members
Articles / Web Development / HTML
Tip/Trick

Auto Complete in ASP.NET MVC

Rate me:
Please Sign up or sign in to vote.
4.40/5 (7 votes)
25 Jun 2014CPOL2 min read 45.1K   1.5K   11   8
This is an article / tip that I am trying to giving an idea of creating a general auto complete helper for asp.net MVC 5 developers

Introduction

This is an article / tip that I am trying to giving an idea of creating a general auto complete helper for asp.net MVC 5 developers

When i started my new project in ASP.NET MVC, I had requirement to provide a input control like auto complete for many number of domain entities (department, employee, product, categories etc.) and for general purpose. I just wanted to use auto completes instead of dropdown list

This is useful when there is a requirement to provide auto suggestion kind of data entry to end user

Two helpers with various overloads in terms of having attributes, auto complete type, is required, on select call back and source URL. One helper used for strongly type view and other one for view with no model binding

How it works

Helper creates two html input elements one type of hidden and other type of text. Id and name of hidden would be the developer give name of autocomplete or model propertyname. Id and name for the textbox would be the name of hidden _AutoComplete (ex: hidden name is ProductID, textbox name is ProductID_Auto Complete). Selected text shown in text box and selected value goes to hidden control. On submission of the current form both the values from hidden and text will be submited to server.

If we specify the source url to the control, the it gets the data from specified url. In other case if we specify the type of auto complete the data loads from a predefined url.

A JavaScript callback function can be specify to execute after select an item from autocomplete

Client side required validation can be added by using parameter IsRequired = true

Test requirements

  • Visual Studio 2013
  • ASP.NET MVC 5
  • jquery-1.10.2
  • jquery-ui-1.8.23.custom.min AutoComplete plugin

Using the code

Code blocks consists of following resources

  • Department, Employee ( c# classes domain entities of this attached source )

  • AutocompleteHelper ( c# statick class to define the helpers)

  • TestAutoCompleteController ( c# MVC controller class to return data to auto completes request)

  • CustomAutoComplete script ( Jquery script to handle auto complete request and responses from client)

  • View ( Index.cshtml to design the sample view)

First define an Enum to specify the type of data to load in terms of entity names

using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;
using System.Web.Mvc;
using System.Web.Mvc.Html;

namespace AutoCompleteHelper_MVC.Extensions
{

    public enum AutoCompleteType
    {
        None,
        Department,
        Employee,
    }
    public static class AutoCompleteHelper
    {

        public static MvcHtmlString Autocomplete(this HtmlHelper helper, string name, string value, string text, string actionUrl, bool? isRequired = false, IDictionary<string, object> viewhtmlAttributes = null, string onselectfunction = "")
        {
            return GetAutocompleteString(helper, name, value, text, AutoCompleteType.None, actionUrl, isRequired, viewhtmlAttributes, onselectfunction: onselectfunction);
        }
        public static MvcHtmlString Autocomplete(this HtmlHelper helper, string name, string value, string text, AutoCompleteType autoCompleteType, bool? isRequired = false, IDictionary<string, object> viewhtmlAttributes = null,string onselectfunction="")
        {
            string actionUrl=string.Empty;  
                UrlHelper url = new UrlHelper(helper.ViewContext.RequestContext);
                string acpath = url.Content("~/TestAutoComplete/");
                if (autoCompleteType == AutoCompleteType.Department)
                    actionUrl = acpath+"GetDepartments";
                else if (autoCompleteType == AutoCompleteType.Employee)
                    actionUrl = acpath + "GetEmployees";

                return GetAutocompleteString(helper, name, value, text, autoCompleteType, actionUrl, isRequired: isRequired, viewhtmlAttributes: viewhtmlAttributes,onselectfunction: onselectfunction);
        }
        private static MvcHtmlString GetAutocompleteString(HtmlHelper helper, string name, string value, string text, AutoCompleteType autoCompleteType, string actionUrl = "", bool? isRequired = false, IDictionary<string, object> viewhtmlAttributes = null, string onselectfunction = "")
        {
            if(viewhtmlAttributes==null)
                viewhtmlAttributes=new Dictionary<string, object>();
           
            viewhtmlAttributes.Add("data-autocomplete", true);

            viewhtmlAttributes.Add("data-autocompletetype", autoCompleteType.ToString().ToLower());

            viewhtmlAttributes.Add("data-sourceurl", actionUrl);

           
            viewhtmlAttributes.Add("data-valuetarget", name);

            if (!string.IsNullOrEmpty(onselectfunction))
            {
                viewhtmlAttributes.Add("data-electfunction", onselectfunction);
            }
            if (isRequired.HasValue && isRequired.Value)
            {
                viewhtmlAttributes.Add("data-val", "true");
                viewhtmlAttributes.Add("data-val-required", name + " is required");
            }

            var hidden = helper.Hidden(name, value);

            var textBox = helper.TextBox(name + "_AutoComplete", text, viewhtmlAttributes);

            var builder = new StringBuilder();

            builder.AppendLine(hidden.ToHtmlString());

            builder.AppendLine(textBox.ToHtmlString());

            return new MvcHtmlString(builder.ToString());
        }
        public static MvcHtmlString AutocompleteFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string DisplayProperty, string actionUrl, bool? isRequired = false, IDictionary<string, object> viewhtmlAttributes = null, string onselectfunction = "")
        {
            return GetAutocompleteForString(helper, expression, DisplayProperty, AutoCompleteType.None, actionUrl, isRequired, viewhtmlAttributes, onselectfunction: onselectfunction);
        }
        public static MvcHtmlString AutocompleteFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string DisplayProperty, AutoCompleteType autoCompleteType, bool? isRequired = false, IDictionary<string, object> viewhtmlAttributes = null, string onselectfunction = "")
        {
            string actionUrl = string.Empty;
            UrlHelper url = new UrlHelper(helper.ViewContext.RequestContext);
            string acpath = url.Content("~/TestAutoComplete/");
            if (autoCompleteType == AutoCompleteType.Department)
                actionUrl = acpath + "GetDepartments";
            else if (autoCompleteType == AutoCompleteType.Employee)
                actionUrl = acpath + "GetEmployees";

            return GetAutocompleteForString(helper, expression, DisplayProperty, autoCompleteType, actionUrl, isRequired: isRequired, viewhtmlAttributes: viewhtmlAttributes,onselectfunction: onselectfunction);
        }

        private static MvcHtmlString GetAutocompleteForString<TModel, TValue>(HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, string DisplayText, AutoCompleteType autoCompleteType, string actionUrl = "", bool? isRequired = false, IDictionary<string, object> viewhtmlAttributes = null, string onselectfunction = "")
        {
               if(viewhtmlAttributes==null)
                viewhtmlAttributes=new Dictionary<string, object>();

            viewhtmlAttributes.Add("data-autocomplete", true );

            viewhtmlAttributes.Add("data-autocompletetype", autoCompleteType.ToString().ToLower() );

            viewhtmlAttributes.Add("data-sourceurl", actionUrl );
           
           
            if (!string.IsNullOrEmpty(onselectfunction))
            {
                viewhtmlAttributes.Add("data-electfunction", onselectfunction);
            }
            Func<TModel, TValue> method = expression.Compile();
            object value = null;
            if (helper.ViewData.Model != null)
                value = method((TModel)helper.ViewData.Model);            

            string modelpropname = ((MemberExpression)expression.Body).ToString();

            modelpropname = modelpropname.Substring(modelpropname.IndexOf('.') + 1);

            viewhtmlAttributes.Add("data-valuetarget", modelpropname);

           
            if (isRequired.HasValue && isRequired.Value)
            {   viewhtmlAttributes.Add("data-val", "true");
                viewhtmlAttributes.Add("data-val-required", modelpropname + " is required");           
            }

            MvcHtmlString hidden = helper.HiddenFor(expression);

            MvcHtmlString textBox = helper.TextBox(modelpropname+"_AutoComplete", DisplayText, viewhtmlAttributes);          

            var builder = new StringBuilder();

            builder.AppendLine(hidden.ToHtmlString());

            builder.AppendLine(textBox.ToHtmlString());

            return new MvcHtmlString(builder.ToString());
        }

    }
    
}

Models/Department.cs

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace AutoCompleteHelper_MVC.Models
{
    public class Department
    {
        public int DepartmentID { get; set; }
        public string DepartmentName { get; set; }
        public static List<Department> TotalDepartments
        {
            get
            {
                return new List<Department>{
                                            new Department{DepartmentID=1,DepartmentName="Accounts"},
                                            new Department{DepartmentID=2,DepartmentName="Advertisement"},
                                            new Department{DepartmentID=3,DepartmentName="Sales"},
                                            new Department{DepartmentID=4,DepartmentName="Shipment"},
                                            new Department{DepartmentID=5,DepartmentName="Production"},
                                            new Department{DepartmentID=6,DepartmentName="Marketing"}
                                            };
            }
        }
        public static List<Department> GetDepartmentsLikeName(string namestring)
        {
            var x=TotalDepartments.Where<Department>(d=>d.DepartmentName.ToLower().StartsWith(namestring.ToLower()));
            return x.ToList();

        }
    }
}

Models/Employee.cs

C#
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Web;

namespace AutoCompleteHelper_MVC.Models
{
    public class Employee
    {
        public int EmployeeID { get; set; }
        [Required]
        public string EmployeeName { get; set; }
        public static List<Employee> TotalEmployees
        {
            get
            {
                return new List<Employee>{
                                            new Employee{EmployeeID=1,EmployeeName="Addison",DepartmentID=1},
                                            new Employee{EmployeeID=2,EmployeeName="Ashwin",DepartmentID=1},
                                            new Employee{EmployeeID=3,EmployeeName="Alden",DepartmentID=1},
                                            new Employee{EmployeeID=4,EmployeeName="Anthony",DepartmentID=6},
                                            new Employee{EmployeeID=5,EmployeeName="Bailee",DepartmentID=5},
                                            new Employee{EmployeeID=6,EmployeeName="Baileigh",DepartmentID=4},
                                            new Employee{EmployeeID=7,EmployeeName="Banjamin",DepartmentID=2},
                                            new Employee{EmployeeID=8,EmployeeName="Cadan",DepartmentID=3},
                                            new Employee{EmployeeID=9,EmployeeName="Cadimhe",DepartmentID=2},
                                            new Employee{EmployeeID=10,EmployeeName="Carissa",DepartmentID=1},
                                            new Employee{EmployeeID=11,EmployeeName="Ceara",DepartmentID=2},
                                            new Employee{EmployeeID=12,EmployeeName="Cecilia",DepartmentID=1}
                                            };
            }
        }
        public int DepartmentID { get; set; }

        public static List<Employee> GetEmployeesLikeName(string namestring)
        {
            var x = TotalEmployees.Where<Employee>(d => d.EmployeeName.ToLower().StartsWith(namestring.ToLower()));
            return x.ToList();

        }
    }
}

Controllers/ TestAutoCompleteController.cs

C#
using AutoCompleteHelper_MVC.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace AutoCompleteHelper_MVC.Controllers
{
   
 public class TestAutoCompleteController : Controller
 {
  //
  // GET: /AutoComplete/
        public ActionResult Index()
        {
            var empObj = new Employee { EmployeeID = 1, EmployeeName = "Michale" };
            return View(empObj);
        }
       
  public ActionResult GetEmployees(string searchHint)
  {
   return Json(Employee.GetEmployeesLikeName(searchHint),JsonRequestBehavior.AllowGet);
  }
  // GET: /AutoComplete/
  public ActionResult GetDepartments(string searchHint)
  {
            return Json(Department.GetDepartmentsLikeName(searchHint), JsonRequestBehavior.AllowGet);
  }
  // GET: /AutoComplete/
  public ActionResult GetGeneralItems(string searchHint)
  {
            return Json(Employee.GetEmployeesLikeName(searchHint), JsonRequestBehavior.AllowGet);
  }
 }
}

Views/TestAutoComplete/Index.cshtml

This view contains four controls to test all available options

  • First one is auto complete with data source url

  • Second is auto complete for employee type

  • Third is auto complete for Department type

  • Fourth is to load the data in to auto complete with employee id and name

HTML
@using AutoCompleteHelper_MVC.Extensions
@using AutoCompleteHelper_MVC.Models
@model AutoCompleteHelper_MVC.Models.Employee
@{
    ViewBag.Title = "Index";
    Layout = "~/Views/Shared/_Layout.cshtml";
}
<form>
    <div class="form-horizontal">
        <h4>AutoComplete</h4>
        <hr />
        @Html.ValidationSummary(true)

        <div class="form-group">
            @Html.Label("With Source URL:", new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(m => m.EmployeeName)
                @Html.ValidationMessageFor(m => m.EmployeeName)
            </div>
        </div>

        <div class="form-group">
            @Html.Label("With Source URL:", new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.Autocomplete("Empgeneral", "", "", actionUrl: "/TestAutoComplete/GetEmployees", isRequired: true, onselectfunction: "onEmpSelection")
                @Html.ValidationMessage("Empgeneral_AutoComplete")
            </div>
        </div>

        <div class="form-group">
            @Html.Label("With Employee type:", new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.Autocomplete("Emp", "", "", AutoCompleteType.Employee)
            </div>
        </div>

        <div class="form-group">
            @Html.Label("With Department type:", new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.Autocomplete("Dept", "", "", AutoCompleteType.Department)
            </div>
        </div>
        <div class="form-group">
            @Html.Label("Binding to view model:", new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.AutocompleteFor(m => m.EmployeeID, @Model.EmployeeName, AutoCompleteType.Employee, isRequired: true)
                @Html.ValidationMessage("EmployeeID_AutoComplete")
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="submit" class="btn btn-default" />
            </div>
        </div>
    </div>
</form>

@section scripts{
    <script>
     
        function onEmpSelection(event, ui)
        {  
            alert('selected Value=' + ui.item.selectedValue + ', Selected Text=' + ui.item.label);
        }
    </script>
    }

Scripts/CustomAutoComplete.js

JavaScript
//attached autocomplete widget to all the autocomplete controls
$(document).ready(function () {
    BindAutoComplete();
});
function BindAutoComplete() {

    $('[data-autocomplete]').each(function (index, element) {
        var sourceurl = $(element).attr('data-sourceurl');
        var autocompletetype = $(element).attr('data-autocompletetype');
        $(element).autocomplete({
            source: function (request, response) {
                $.ajax({
                    url: sourceurl,
                    dataType: "json",
                    data: { searchHint: request.term },
                    success: function (data) {
                        response($.map(data, function (item) {
                            if (autocompletetype == 'none') {
                                return {
                                    label: item.EmployeeName,
                                    value: item.EmployeeName,
                                    selectedValue: item.EmployeeID
                                };
                            }
                            else if (autocompletetype == 'department') {
                                return {
                                    label: item.DepartmentName,
                                    value: item.DepartmentName,
                                    selectedValue: item.DepartmentID

                                };//
                            }
                            else if (autocompletetype == 'employee') {
                                return {
                                    label: item.EmployeeName,
                                    value: item.EmployeeName,
                                    selectedValue: item.EmployeeID

                                };//
                            }
                        }));
                    },
                    error: function (data) {
                        alert(data);
                    },
                });
            },
            select: function (event, ui) {
                var valuetarget = $(this).attr('data-valuetarget');
                $("input:hidden[name='" + valuetarget + "']").val(ui.item.selectedValue);

                var selectfunc = $(this).attr('data-electfunction');
                if (selectfunc != null && selectfunc.length > 0) {
                    window[selectfunc](event, ui);
                    //funName();
                }
                //    selectfunc(event, ui);
            },
            change: function (event, ui) {
                var valuetarget = $(this).attr('data-valuetarget');

                $("input:hidden[name='" + valuetarget + "']").val('');
            },
        });
    });
}

Points of Interest

Interested in Microsoft .Net Technologies, specially in asp.net  and sharing my Ideas, Articles and tips

Tested screens

 

Image 1

 

Accessing the selected item detail through control specific onselect callback function

Image 2

 

Image 3

Validation on submission

Image 4

 

Fiddler is showing the submited values from four autocompletes

 

Image 5

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Technical Lead
India India
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Questiondesign Pin
teddddddddddd31-Jul-16 22:04
teddddddddddd31-Jul-16 22:04 
Bug$(element).autocomplete is not a function Pin
ujshah26-Feb-15 20:09
ujshah26-Feb-15 20:09 
Generalgreat work! thanks Pin
zhangtai8-Dec-14 12:40
zhangtai8-Dec-14 12:40 
GeneralMy vote of 2 Pin
Broken Pipe1-Nov-14 3:24
Broken Pipe1-Nov-14 3:24 
QuestionGreat start. 5* Pin
Mr_T11-Sep-14 20:50
professionalMr_T11-Sep-14 20:50 
AnswerRe: Great start. 5* Pin
Sreenivas Chinni18-Sep-14 17:04
Sreenivas Chinni18-Sep-14 17:04 
That's right
GeneralMy vote of 5 Pin
Lydia Gabriella16-Jul-14 10:52
Lydia Gabriella16-Jul-14 10:52 
GeneralRe: My vote of 5 Pin
Sreenivas Chinni16-Jul-14 23:54
Sreenivas Chinni16-Jul-14 23:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.