Blog / Web
How to Create AngularJS Autocomplete in .Net Framework MVC
  • Dec 23, 2020
  • 85
  • 99

In the previous tutorial, we have talked about how to create jQuery Autocomplete in .Net Core MVC project. In this post, we will try to do it with AngularJS in regular .Net Framework MVC project.

Note: Code can be downloaded at my Github.


Advertisement
 


1. How to Create AngularJS Autocomplete in .Net Framework MVC

Note: This demo is using AngularJS integrated with .Net Framework MVC project, not Angular 2.0 or higher. I will walk through each step to create this demo project.

Step 1: Create an ASP.Net Web Application (.Net Framework)

Open Visual Studio 2019, search for ASP.Net Web Application (.Net Framework) and create on Next

Name the project AutoCompleteAngularJSDemo, select .Net Framework 4.5.2 and click on Create:


Select MVC and then click on Create

Below is the project structure after creating successfully:



Step 2: Add AngularJS to .Net Framework MVC project

Download below files from my Github:

Under Scripts folder, create 2 sub-folders: app and vendors. Move all JavaScript files under Scripts folder into vendors folder. In app folder, create sub-folders and files like below:

In app.js: This file is to initialize the AngularJS app in .Net Framework MVC project

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
(function () {
    'use strict';
 
    angular.module('AutoCompleteAngularJSDemo', ['common.core', 'common.ui'])
        .config(config)
        .run(run);
 
    config.$inject = ['$locationProvider', '$httpProvider'];
    function config($locationProvider, $httpProvider) {
        $locationProvider.html5Mode(true);
 
        //initialize get if not there
        if (!$httpProvider.defaults.headers.get) {
            $httpProvider.defaults.headers.get = {};
        }
 
        // Answer edited to include suggestions from comments
        // because previous version of code introduced browser-related errors
 
        //disable IE ajax request caching
        $httpProvider.defaults.headers.get['If-Modified-Since'] = 'Mon, 26 Jul 1997 05:00:00 GMT';
        // extra
        $httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
        $httpProvider.defaults.headers.get['Pragma'] = 'no-cache';
    }
 
    run.$inject = ['$rootScope', '$location', '$http', '$route'];
 
    function run($rootScope, $location, $http, $route) {
        //handle page refreshes
         
    }
 
})();

In common.core.js: This file is to inject all core library into the AngularJS app

1
2
3
4
5
6
(function () {
    'use strict';
 
    angular.module('common.core', ['ngRoute']);
 
})();

In common.ui.js: This file is to inject all ui library into the AngularJS app

1
2
3
4
5
(function () {
    'use strict';
 
    angular.module('common.ui', ['ui.bootstrap']);
})();

In rootCtrl.js: This file is the root controller of the project, will be injected in _Layout.cshtml page

1
2
3
4
5
6
7
8
9
10
11
12
(function (app) {
    'use strict';
 
    app.controller('rootCtrl', rootCtrl);
 
    rootCtrl.$inject = ['$scope'];
 
    function rootCtrl($scope) {
 
    }
 
})(angular.module('AutoCompleteAngularJSDemo'));

In homeCtrl.js: This file is the controller of the Home/Index.cshtml page

1
2
3
4
5
6
7
8
9
10
11
12
(function (app) {
    'use strict';
 
    app.controller('homeCtrl', homeCtrl);
 
    homeCtrl.$inject = ['$scope'];
 
    function homeCtrl($scope) {
 
    }  
 
})(angular.module('AutoCompleteAngularJSDemo'));

In apiService.js: This file is a service to call (get/post) API methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(function (app) {
    'use strict';
 
    app.factory('apiService', apiService);
 
    apiService.$inject = ['$http', '$rootScope'];
 
    function apiService($http, $rootScope) {
        var service = {
            get: get,
            post: post
        };
 
        function get(url, config, success, failure) {
            return $http.get(url, config)
                    .then(function (result) {
                        success(result);
                    }, function (error) {
                        failure(error);
                    });
        }
 
        function post(url, data, success, failure) {
            return $http.post(url, data)
                    .then(function (result) {
                        success(result);
                    }, function (error) {
                        failure(error);
                    });
        }
 
        return service;
    }
 
})(angular.module('common.core'));

In inputDropdown.directive.js: This file is the directive for the Autocomplete part

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
(function (app) {
    'use strict';
 
    app.directive('inputDropdown', inputDropdown);
 
    inputDropdown.$inject = ['apiService'];
 
    function inputDropdown(apiService) {
        return {
            restrict: 'E',
            scope: {
                inputName: '@',
                inputPlaceholder: '@',
                customModel: '=ngModel',
                selectedItem: '=selectedItem',
                remoteUrl: '@',
                required: '@'
            },
            templateUrl: '/scripts/app/shared/inputDropdown.html',
 
            link: function ($scope, $element, $attrs) {
                $scope.dropdownVisible = false;
 
                $scope.inputChange = function () {
                    var value = btoa(unescape(encodeURIComponent($scope.customModel)));
                    apiService.get($scope.remoteUrl + '/' + value, null,
                    categoryLoadCompleted,
                    categoryLoadFailed);
                };
 
                $scope.inputFocus = function () {
                    var value = btoa(unescape(encodeURIComponent($scope.customModel)));
                    apiService.get($scope.remoteUrl + '/' + value, null,
                    categoryLoadCompleted,
                    categoryLoadFailed);
                };
 
                function categoryLoadCompleted(result) {
                    $scope.dropdownItems = result.data;
                    if (result.data.length > 0)
                        showDropdown();
                    else
                        hideDropdown();
                }
 
                function categoryLoadFailed(result) {
                    hideDropdown();
                }
 
                $scope.inputBlur = function (event) {
                    if ($scope.pressedDropdown) {
                        // Blur event is triggered before click event, which means a click on a dropdown item wont be triggered if we hide the dropdown list here.
                        $scope.pressedDropdown = false;
                        return;
                    }
                    hideDropdown();
                };
 
                $scope.dropdownPressed = function () {
                    $scope.pressedDropdown = true;
                }
 
                var showDropdown = function () {
                    $scope.dropdownVisible = true;
                };
                var hideDropdown = function () {
                    $scope.dropdownVisible = false;
                }
 
                $scope.selectItem = function (item) {
                    $scope.selectedItem = item;
                    $scope.customModel = item.Name;
                    hideDropdown();
                    $scope.dropdownItems = [item];
                };
            }
        }
    }
 
})(angular.module('common.ui'));

In inputDropdown.html: This file is the template for the Autocomplete

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<div class="input-dropdown" style="width:100%;">
    <input type="text" class="form-control"
        name="{{inputName}}"
        id="{{inputName}}"
        placeholder="{{inputPlaceholder}}"
        ng-model="customModel"
        ng-change="inputChange()"
        ng-focus="inputFocus()"
        ng-blur="inputBlur($event)"
        validate-on="blur" ng-required="{{required}}" />
    <ul ng-show="dropdownVisible">
        <li ng-repeat="item in dropdownItems"
            ng-click="selectItem(item)"
            ng-mouseenter="setActive($index)"
            ng-mousedown="dropdownPressed()"
            ng-class="{'active': activeItemIndex === $index}">
            <span class="name">{{item.Name}}</span> <br />
            <span class="sub-name">{{item.Sub}}</span>
        </li>
    </ul>
</div>

The Scripts folder will look like below after finishing all steps above:

Once you are done with the Scripts folder, we need to include all newly created JavaScript files in the Bundle. Update App_Start/BundleConfig.cs as below:

In App_Start/BunderConfig.cs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using System.Web;
using System.Web.Optimization;
 
namespace AutoCompleteAngularJSDemo
{
    public class BundleConfig
    {
        // For more information on bundling, visit https://go.microsoft.com/fwlink/?LinkId=301862
        public static void RegisterBundles(BundleCollection bundles)
        {
            bundles.Add(new ScriptBundle("~/bundles/jquery").Include(
                        "~/Scripts/vendors/jquery-{version}.js"));
 
            bundles.Add(new ScriptBundle("~/bundles/jqueryval").Include(
                        "~/Scripts/vendors/jquery.validate*"));
 
            // Use the development version of Modernizr to develop with and learn from. Then, when you're
            // ready for production, use the build tool at https://modernizr.com to pick only the tests you need.
            bundles.Add(new ScriptBundle("~/bundles/modernizr").Include(
                        "~/Scripts/vendors/modernizr-*"));
 
            bundles.Add(new ScriptBundle("~/bundles/bootstrap").Include(
                      "~/Scripts/vendors/bootstrap.js"));
 
            bundles.Add(new ScriptBundle("~/bundles/angularjs").Include(
                      "~/Scripts/vendors/angular.js",
                      "~/Scripts/vendors/angular-route.js",
                      "~/Scripts/vendors/ui-bootstrap-tpls-3.0.6.js"));
 
            bundles.Add(new ScriptBundle("~/bundles/app").Include(
                      "~/Scripts/app/modules/common.core.js",
                      "~/Scripts/app/modules/common.ui.js",
                      "~/Scripts/app/app.js",
                      "~/Scripts/app/controllers/rootCtrl.js",
                      "~/Scripts/app/controllers/home/homeCtrl.js",
                      "~/Scripts/app/services/apiService.js",
                      "~/Scripts/app/shared/inputDropdown.directive.js"));
 
            bundles.Add(new StyleBundle("~/Content/css").Include(
                      "~/Content/bootstrap.css",
                      "~/Content/site.css"));
        }
    }
}

Now, we need to initialize AngularJS app in html and specify the controller for each page.

In _Layout.cshtml: This is to initialize AngularJS app and specify rootCtrl for the shared layout

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<!DOCTYPE html>
<html ng-app="AutoCompleteAngularJSDemo">
<head>
    <base href="/" />
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - My ASP.NET Application</title>
    @Styles.Render("~/Content/css")
    @Scripts.Render("~/bundles/modernizr")
</head>
<body ng-controller="rootCtrl">
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("Application name", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">
                    <li>@Html.ActionLink("Home", "Index", "Home")</li>
                    <li>@Html.ActionLink("About", "About", "Home")</li>
                    <li>@Html.ActionLink("Contact", "Contact", "Home")</li>
                </ul>
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>&copy; @DateTime.Now.Year - My ASP.NET Application</p>
        </footer>
    </div>
 
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/angularjs")
    @Scripts.Render("~/bundles/app")
    @Scripts.Render("~/bundles/bootstrap")
    @RenderSection("scripts", required: false)
</body>
</html>

In Home/Index.cshtml: specify homeCtrl for this page as well as add AutoComplete field:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@{
    ViewBag.Title = "Home Page";
}
 
<div ng-controller="homeCtrl">
    <div style="padding-top:50px; padding-bottom: 50px;">
        <div class="row">
            <div class="col-lg-3"></div>
            <div class="col-lg-6">
                <h3>Autocomplete with .Net 4.5 MVC and AngularJS</h3>
                <div class="form-group">
                    <label for="inputUser">Users</label>
                    <input-dropdown input-name="inputUser"
                                    ng-model-options="{ allowInvalid: true}"
                                    input-placeholder="" required="false"
                                    ng-model="user.Name"
                                    selected-item="user"
                                    remote-url="/api/AutoComplete/SearchUsersByFilter">
                    </input-dropdown>
                </div>
                <div class="form-group">
                    Selected User: {{user}}
                </div>
            </div>
            <div class="col-lg-3"></div>
        </div>
    </div>
</div>

Step 3: Add Web API to .Net Framework MVC project

This API feature is to help getting data for the AutoComplete field while user is typing. 

In Controller folder, create a subfolder called WebAPI, create an ApiController inside this folder and call it AutoCompleteController. The folder will look like below:



In AutoCompleteController.cs: this Controller has SearchUsersByFilter to get data for the autocomplete field while user is typing.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using AutoCompleteAngularJSDemo.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Web.Http;
 
namespace AutoCompleteAngularJSDemo.Controllers.WebAPI
{
    [RoutePrefix("api/AutoComplete")]
    public class AutoCompleteController : ApiController
    {
        [AllowAnonymous]
        [HttpGet]
        [Route("SearchUsersByFilter/{filter?}")]
        public HttpResponseMessage SearchUsersByFilter(string filter = null)
        {
            HttpResponseMessage response = null;
 
            if (!string.IsNullOrEmpty(filter))
            {
                byte[] data = Convert.FromBase64String(filter);
                filter = Encoding.UTF8.GetString(data);
            }
 
            List<SearchItemVM> searchItems = SearchItemsByFilter(filter);
 
            HttpRequestMessage request = new HttpRequestMessage();
            var configuration = new HttpConfiguration();
            request.Properties[System.Web.Http.Hosting.HttpPropertyKeys.HttpConfigurationKey] = configuration;
 
            response = request.CreateResponse(HttpStatusCode.OK, searchItems);
 
            return response;
        }
 
        private List<SearchItemVM> SearchItemsByFilter(string filter)
        {
            List<SearchItemVM> users = new List<SearchItemVM>();
            users.Add(new SearchItemVM() { ID = 1, Name = "Lucas Nguyen", Sub = "[email protected]", Type = "User" });
            users.Add(new SearchItemVM() { ID = 2, Name = "Eric Lu", Sub = "[email protected]", Type = "User" });
            users.Add(new SearchItemVM() { ID = 3, Name = "Rachel Horseman", Sub = "[email protected]", Type = "User" });
            return users.Where(u => u.Name.ToLower().Contains(filter.ToLower())).ToList();
        }
    }
}

Create a SearchItemVM in Models folder to hold the data for AutoComplete and pass it to front-end.

In Models/SearchItemVM

1
2
3
4
5
6
7
8
9
10
namespace AutoCompleteAngularJSDemo.Models
{
    public class SearchItemVM
    {
        public int ID { get; set; }
        public string Name { get; set; }
        public string Sub { get; set; }
        public string Type { get; set; }
    }
}

Update App_Start/WebApiConfig.cs for the API routing as below:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
using System.Web.Http;
 
namespace AutoCompleteAngularJSDemo
{
    public static class WebApiConfig
    {
        public static void Register(HttpConfiguration config)
        {
            config.MapHttpAttributeRoutes();
 
            config.Routes.MapHttpRoute(
                name: "DefaultApi",
                routeTemplate: "api/{controller}/{action}/{id}",
                defaults: new { id = RouteParameter.Optional }
            );
        }
    }
}

We need to invoke WebApiConfig in Global.asax also:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
 
namespace AutoCompleteAngularJSDemo
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            WebApiConfig.Register(GlobalConfiguration.Configuration);
 
            GlobalConfiguration.Configuration.EnsureInitialized();
        }
    }
}

Done, the AutoComplete is implemented and it will show suggestions while user is typing. You can run the project and test the feature.

2. Conclusion

Many applications are using Autocomplete fields to enhance better User Experience. I hope this tutorial help you in your current or future projects. Please let me know your thoughts in the comment section below. See you next time!

Read more in this series:


If you have a Website or a Web API developed by using .Net Core and looking for a way to publish your applications, this post will explain how to do it using GoDaddy Windows Hosting.Note: at this mome ...

Search text in Stored Procedure in SQL SELECT DISTINCT o.name AS Object_Name, o.type_desc FROM sys.sql_modules m INNER JOIN sys.objects o ON m.object_id = o ...

Using cherry-pick to select specific commits for your Pull Request.1. Create a new branch based on the target of the Pull Requestgit branch cherry-branch origin/master2. Switch to a new branchgit chec ...

After deployment Angular and API on IIS, it's working fine unless I refresh the page. Once refreshed, the web encountered 404 error. In this article, I will explain how to resolve this.Since Angular i ...

There are some benefits of keeping both UI and API parts in the same place for small projects. In this article, I will explain how I did to deploy Angular Web and ASP .Net Core API in the same folder ...

I got CORS error after publishing my API and Angular app to IIS even though CORS is enabled and the origins of the Angular app is added. Below is how I resolved this issue.Just simple, make sure you s ...

In Object-Oriented Programming, S.O.L.I.D refers to the first five design principle for the purpose of making software designs more understandable, flexible, and maintainable. The principles was first ...

1. The Situation:Error Message:&nbsp;Pulse Secure Application failed to load Java. Please install correct JRE version.Description: This issue happens when I'm using a M1 Mac with a correct version of ...

Accelerated Mobile Pages (AMP)&nbsp;focuses on delivering static content from publishers as quickly as possible and possibly rewards early adopters with a boost in rank. Let's see how to implement it ...