ایجاد AutoComplete با استفاده از AngularJS با Web API در MVC

در این مقاله خواهیم آموخت چگونه می توان یک textbox با خاصیت AutoComplete به همراه داده هایی از SQL server ایجاد کرد. از معماری MVC به همراه Web API و AngularJS برای واکشی داده ها استفاده میکنیم.

 ایجاد AutoComplete با استفاده از AngularJS  با Web API در MVC

در این مقاله خواهیم آموخت چگونه می توان یک  textbox  با خاصیت  AutoComplete به همراه داده هایی از  SQL server ایجاد کرد. از معماری  MVC  به همراه Web API و AngularJS برای واکشی داده ها استفاده میکنیم.

 یک پروژه  MVC با انتخاب گزینه های  MVC  و  Web API به صورت زیر ایجاد میکنیم.

ساختار برنامه به صورت زیر خواهد بود:

قبل از رفتن به سراغ بخش کد نویسی، از نصب منابع و افزونه های مورد نیاز اطمینان حاصل کنید.  AngularJS و  jQuery را باید نصب کرد.

می توانید آنها را با استفاده از  NuGet و انتخاب گزینه  Manage NuGet Pakages نصب کنید.

همانطور که گفته شد  از  AngularJS برای عملیات های سمت سرویس گیرنده استفاده می شود ، بنا براین بهتر است اول فایل اسکریپت  AngularJS را ایجاد کنیم. برای این کار یک فایل جاوا اسکریپت با نام  Home.js که اسکریپتهای خود را در آن خواهیم نوشت ایجاد میکنیم. اکنون یک کنترلر  Web API ایجاد کرده و داده ها را با فرمت  JSON از دیتابیس دریافت میکنیم.

ابتدا دیتابیس خود را آماده میکنیم که بتوانیم  Entity Model  برای اپلیکیشن خود را ایجاد کنیم.

از کد زیر می توانید برای ایجاد یک دیتابیس در  SQL استفاده کنید.

USE [master]  
GO  
/****** Object: Database [TrialsDB]  
CREATE DATABASE [TrialsDB]  
CONTAINMENT = NONE  
ON PRIMARY  
( NAME = N'TrialsDB', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\TrialsDB.mdf' , SIZE = 3072KB , MAXSIZE = UNLIMITED, FILEGROWTH = 1024KB )  
LOG ON  
( NAME = N'TrialsDB_log', FILENAME = N'C:\Program Files\Microsoft SQL Server\MSSQL11.MSSQLSERVER\MSSQL\DATA\TrialsDB_log.ldf' , SIZE = 1024KB , MAXSIZE = 2048GB , FILEGROWTH = 10%)  
GO  
ALTER DATABASE [TrialsDB] SET COMPATIBILITY_LEVEL = 110  
GO  
IF (1 = FULLTEXTSERVICEPROPERTY('IsFullTextInstalled'))  
begin  
EXEC [TrialsDB].[dbo].[sp_fulltext_database] @action = 'enable'  
end  
GO  
ALTER DATABASE [TrialsDB] SET ANSI_NULL_DEFAULT OFF  
GO  
ALTER DATABASE [TrialsDB] SET ANSI_NULLS OFF  
GO  
ALTER DATABASE [TrialsDB] SET ANSI_PADDING OFF  
GO  
ALTER DATABASE [TrialsDB] SET ANSI_WARNINGS OFF  
GO  
ALTER DATABASE [TrialsDB] SET ARITHABORT OFF  
GO  
ALTER DATABASE [TrialsDB] SET AUTO_CLOSE OFF  
GO  
ALTER DATABASE [TrialsDB] SET AUTO_CREATE_STATISTICS ON  
GO  
ALTER DATABASE [TrialsDB] SET AUTO_SHRINK OFF  
GO  
ALTER DATABASE [TrialsDB] SET AUTO_UPDATE_STATISTICS ON  
GO  
ALTER DATABASE [TrialsDB] SET CURSOR_CLOSE_ON_COMMIT OFF  
GO  
ALTER DATABASE [TrialsDB] SET CURSOR_DEFAULT GLOBAL  
GO  
ALTER DATABASE [TrialsDB] SET CONCAT_NULL_YIELDS_NULL OFF  
GO  
ALTER DATABASE [TrialsDB] SET NUMERIC_ROUNDABORT OFF  
GO  
ALTER DATABASE [TrialsDB] SET QUOTED_IDENTIFIER OFF  
GO  
ALTER DATABASE [TrialsDB] SET RECURSIVE_TRIGGERS OFF  
GO  
ALTER DATABASE [TrialsDB] SET DISABLE_BROKER  
GO  
ALTER DATABASE [TrialsDB] SET AUTO_UPDATE_STATISTICS_ASYNC OFF  
GO  
ALTER DATABASE [TrialsDB] SET DATE_CORRELATION_OPTIMIZATION OFF  
GO  
ALTER DATABASE [TrialsDB] SET TRUSTWORTHY OFF  
GO  
ALTER DATABASE [TrialsDB] SET ALLOW_SNAPSHOT_ISOLATION OFF  
GO  
ALTER DATABASE [TrialsDB] SET PARAMETERIZATION SIMPLE  
GO  
ALTER DATABASE [TrialsDB] SET READ_COMMITTED_SNAPSHOT OFF  
GO  
ALTER DATABASE [TrialsDB] SET HONOR_BROKER_PRIORITY OFF  
GO  
ALTER DATABASE [TrialsDB] SET RECOVERY FULL  
GO  
ALTER DATABASE [TrialsDB] SET MULTI_USER  
GO  
ALTER DATABASE [TrialsDB] SET PAGE_VERIFY CHECKSUM  
GO  
ALTER DATABASE [TrialsDB] SET DB_CHAINING OFF  
GO  
ALTER DATABASE [TrialsDB] SET FILESTREAM( NON_TRANSACTED_ACCESS = OFF )  
GO  
ALTER DATABASE [TrialsDB] SET TARGET_RECOVERY_TIME = 0 SECONDS  
GO  
ALTER DATABASE [TrialsDB] SET READ_WRITE  
GO 

جدولی با نام  Products را در آن با استفاده از کدهای زیر ایجاد میکنیم.

USE [TrialsDB]  
GO  
/****** Object: Table [dbo].[Product]  
SET ANSI_NULLS ON  
GO  
SET QUOTED_IDENTIFIER ON  
GO  
CREATE TABLE [dbo].[Product](  
[ProductID] [int] NOT NULL,  
[Name] [nvarchar](max) NOT NULL,  
[ProductNumber] [nvarchar](25) NOT NULL,  
[MakeFlag] [bit] NOT NULL,  
[FinishedGoodsFlag] [bit] NOT NULL,  
[Color] [nvarchar](15) NULL,  
[SafetyStockLevel] [smallint] NOT NULL,  
[ReorderPoint] [smallint] NOT NULL,  
[StandardCost] [money] NOT NULL,  
[ListPrice] [money] NOT NULL,  
[Size] [nvarchar](5) NULL,  
[SizeUnitMeasureCode] [nchar](3) NULL,  
[WeightUnitMeasureCode] [nchar](3) NULL,  
[Weight] [decimal](8, 2) NULL,  
[DaysToManufacture] [int] NOT NULL,  
[ProductLine] [nchar](2) NULL,  
[Class] [nchar](2) NULL,  
[Style] [nchar](2) NULL,  
[ProductSubcategoryID] [int] NULL,  
[ProductModelID] [int] NULL,  
[SellStartDate] [datetime] NOT NULL,  
[SellEndDate] [datetime] NULL,  
[DiscontinuedDate] [datetime] NULL,  
[rowguid] [uniqueidentifier] ROWGUIDCOL NOT NULL,  
[ModifiedDate] [datetime] NOT NULL  
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]  
GO 

و برای وارد کردن داده به آن از کدهای زیر استفاده میکنیم.

    USE [TrialsDB]  
    GO  
    INSERT INTO [dbo].[Product]  
    ([ProductID]  
    ,[Name]  
    ,[ProductNumber]  
    ,[MakeFlag]  
    ,[FinishedGoodsFlag]  
    ,[Color]  
    ,[SafetyStockLevel]  
    ,[ReorderPoint]  
    ,[StandardCost]  
    ,[ListPrice]  
    ,[Size]  
    ,[SizeUnitMeasureCode]  
    ,[WeightUnitMeasureCode]  
    ,[Weight]  
    ,[DaysToManufacture]  
    ,[ProductLine]  
    ,[Class]  
    ,[Style]  
    ,[ProductSubcategoryID]  
    ,[ProductModelID]  
    ,[SellStartDate]  
    ,[SellEndDate]  
    ,[DiscontinuedDate]  
    ,[rowguid]  
    ,[ModifiedDate])  
    VALUES  
    (<ProductID, int,>  
    ,<Name, nvarchar(max),>  
    ,<ProductNumber, nvarchar(25),>  
    ,<MakeFlag, bit,>  
    ,<FinishedGoodsFlag, bit,>  
    ,<Color, nvarchar(15),>  
    ,<SafetyStockLevel, smallint,>  
    ,<ReorderPoint, smallint,>  
    ,<StandardCost, money,>  
    ,<ListPrice, money,>  
    ,<Size, nvarchar(5),>  
    ,<SizeUnitMeasureCode, nchar(3),>  
    ,<WeightUnitMeasureCode, nchar(3),>  
    ,<Weight, decimal(8,2),>  
    ,<DaysToManufacture, int,>  
    ,<ProductLine, nchar(2),>  
    ,<Class, nchar(2),>  
    ,<Style, nchar(2),>  
    ,<ProductSubcategoryID, int,>  
    ,<ProductModelID, int,>  
    ,<SellStartDate, datetime,>  
    ,<SellEndDate, datetime,>  
    ,<DiscontinuedDate, datetime,>  
    ,<rowguid, uniqueidentifier,>  
    ,<ModifiedDate, datetime,>)  
    GO   

مرحله بعدی ایجاد یک  ADO.NET Entity Data Model  است.  پس از طی این روند یک فایل edmx را می توانید در پوشه  model  مشاهده کنید. و هنگامی که این فایل را باز کنید جدول  Product  را به صورت زیر مشاهده میکنید.

برای ایجاد کنترلر  Web API بر روی پوشه کنترلر کلیک راست کرده و با انتخاب گزینه  Add در پنجره باز شده Web API 2 controller with actions,using Entity Framework را انتخاب میکنیم.

برای کلاس مدل  Product(AngularJSAutocompleteInMVCWithWebAPI.Models) را انتخاب میکنیم.

و برای کلاس  data Context گزینه  TrialsDBEntities(AngularJSAutocompleteInMVCWithWebAPI.Models) را انتخاب میکینم.

همانطور که میبینید نام کنترلر نیز  Products قرار میگیرد. کدهای زیر در این کنترلر قرار میگیرند.

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Web.Http.Description;
using WebApplication19.Models;

namespace WebApplication19.Controllers
{
    public class ProductsController : ApiController
    {
        private TrialsDBEntities db = new TrialsDBEntities();

        // GET: api/Products
        public IQueryable<Product> GetProducts()
        {
            return db.Product;
        }

        // GET: api/Products/5
        [ResponseType(typeof(Product))]
        public IHttpActionResult GetProduct(int id)
        {
            Product product = db.Product.Find(id);
            if (product == null)
            {
                return NotFound();
            }

            return Ok(product);
        }

        // PUT: api/Products/5
        [ResponseType(typeof(void))]
        public IHttpActionResult PutProduct(int id, Product product)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            if (id != product.ProductID)
            {
                return BadRequest();
            }

            db.Entry(product).State = EntityState.Modified;

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!ProductExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return StatusCode(HttpStatusCode.NoContent);
        }

        // POST: api/Products
        [ResponseType(typeof(Product))]
        public IHttpActionResult PostProduct(Product product)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            db.Product.Add(product);

            try
            {
                db.SaveChanges();
            }
            catch (DbUpdateException)
            {
                if (ProductExists(product.ProductID))
                {
                    return Conflict();
                }
                else
                {
                    throw;
                }
            }

            return CreatedAtRoute("DefaultApi", new { id = product.ProductID }, product);
        }

        // DELETE: api/Products/5
        [ResponseType(typeof(Product))]
        public IHttpActionResult DeleteProduct(int id)
        {
            Product product = db.Product.Find(id);
            if (product == null)
            {
                return NotFound();
            }

            db.Product.Remove(product);
            db.SaveChanges();

            return Ok(product);
        }

        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                db.Dispose();
            }
            base.Dispose(disposing);
        }

        private bool ProductExists(int id)
        {
            return db.Product.Count(e => e.ProductID == id) > 0;
        }
    }
}

از آنجایی که فقط به عملیات خواندن نیاز است می توانید سایر قابلیت ها را حذف کنید و فقط متدهای  Get  را نگه دارید.

    // GET: api/Products  
    public IQueryable<Product> GetProducts()  
    {  
       return db.Products;  
    }   

اکنون بخش واکشی داده ها از دیتابیس آماده است. برای تست آن به آدرس http://localhost:8053/api/products می رویم . نتیجه به صورت زیر خواهد بود.

به فایل  AngularJS برگشته و  Web API را در آن استفاده خواهیم کرد. باید کدها در  Home.js به صورت زیر تغییر کنند.

(function () {
    'use strict';
    angular
        .module('MyApp', ['ngMaterial', 'ngMessages', 'material.svgAssetsCache'])
        .controller('AutoCompleteCtrl', AutoCompleteCtrl);
    function AutoCompleteCtrl($http, $timeout, $q, $log) {
        var self = this;
        self.simulateQuery = true;
        self.products = loadAllProducts($http);
        self.querySearch = querySearch;
        function querySearch(query) {
            var results = query ? self.products.filter(createFilterFor(query)) : self.products, deferred;
            if (self.simulateQuery) {
                deferred = $q.defer();
                $timeout(function () { deferred.resolve(results); }, Math.random() * 1000, false);
                return deferred.promise;
            } else {
                return results;
            }
        }
        function loadAllProducts($http) {
            var allProducts = [];
            var url = '';
            var result = [];
            url = 'api/products';
            $http({
                method: 'GET',
                url: url,
            }).then(function successCallback(response) {
                allProducts = response.data;
                angular.forEach(allProducts, function (product, key) {
                    result.push(
                        {
                            value: product.Name.toLowerCase(),
                            display: product.Name
                        });
                });
            }, function errorCallback(response) {
                console.log('Oops! Something went wrong while fetching the data. Status Code: ' + response.status + ' Status statusText: ' + response.statusText);
            });
            return result;
        }
        function createFilterFor(query) {
            var lowercaseQuery = angular.lowercase(query);
            return function filterFn(product) {
                return (product.value.indexOf(lowercaseQuery) === 0);
            };

        }
    }
})();

در اینجا  MyApp  نام ماژول و AutoCompleteCtrl نام کنترلر ماست . تابع loadAllProducts برای بارگذاری همه محصولات از دیتابیس با استفاده از http in Angular JS$ است. هنگامی که سرویس ما فراخوانی می شود، داده ها را دریافت خواهیم کرد. داده ها در متغیری ذخیره خواهند شد. حلقه ای را با استفاده از  angular.forEach و فرمت مورد نیاز ایجاد میکنیم.

    angular.forEach(allProducts, function(product, key)  
    {  
        result.push(  
        {  
            value: product.Name.toLowerCase(),  
            display: product.Name  
        });  
    });   

تابع querySearch هر زمان که کاربری به جستجوی محصول خاصی بپردازد فراخوانی خواهد شد. که از طریق  View  به صورت زیر فراخوانی خواهد شد.

md-items="item in ctrl.querySearch(ctrl.searchText)"  

به یک  View  برای نمایش داده ها نیاز داریم . برای این کار ابتدا یک کنترلر با نام  Home ایجاد میکنیم و کدهای زیر را در آن قرار می دهیم .

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace WebApplication19.Controllers
{
    public class HomeController : Controller
    {
        // GET: Home
        public ActionResult Index()
        {
            return View();
        }
    }
}

برای ایجاد View بر روی نام کنترلر  کلیک راست کرده و آن را اضافه میکنیم.

در  View  منابع مورد نیاز را اضافه میکنیم.

    <script src="~/scripts/angular.min.js"></script>  
    <script src="~/scripts/angular-route.min.js"></script>  
    <script src="~/scripts/angular-aria.min.js"></script>  
    <script src="~/scripts/angular-animate.min.js"></script>  
    <script src="~/scripts/angular-messages.min.js"></script>  
    <script src="~/scripts/angular-material.js"></script>  
    <script src="~/scripts/svg-assets-cache.js"></script>  
    <script src="~/scripts/Home.js"></script>   

 بعد از اضافه کردن منابع می توان کنترلر  AngularJS را فراخوانی کرد و کدهای  View  را به صورت زیر تغییر داد.

<div ng-controller="AutoCompleteCtrl" layout="column" ng-cloak="" class="autocompletedemoBasicUsage" ng-app="MyApp" style="width: 34%;">
    <md-content class="md-padding">
        <form ng-submit="$event.preventDefault()">
            <md-autocomplete md-no-cache="false" md-selected-item="ctrl.selectedItem" md-search-text="ctrl.searchText" md-items="item in ctrl.querySearch(ctrl.searchText)" md-item-text="item.display" md-min-length="0" placeholder="برای جستجوی محصول اینجا کلیک کنید.">
                <md-item-template>
                    <span md-highlight-text="ctrl.searchText" md-highlight-flags="^i">{{item.display}}</span>
                </md-item-template>
                <md-not-found>
                    مشابه "{{ctrl.searchText}}" یافت نشد.
                </md-not-found>
            </md-autocomplete>
        </form>

    </md-content>
</div>

در اینجا  md-autocomplete نتیجه دریافت شده از دیتابیس را برای جلوگیری از بازدیدهای ناخواسته به دیتابیس،  cache  خواهد کرد. که آن را می توان با کمک md-no-cache فعال یا غیر فعال کرد. اگر برنامه را اجرا کنید مشاهده میکنید که فراخوانی  Web API به درستی کار میکند و داده ها دریافت می شود. یک Style sheet  را نیز برای نمایش بهتر  View  اضافه میکنیم.

<link href="~/Content/angular-material.css" rel="stylesheet" /> 

خروجی برنامه به صورت زیر خواهد بود.

آموزش angular

فایل های ضمیمه
دانلود نسخه ی PDF این مطلب