How To Bind KendoUI Grid In Angular With .NET Core API With Multilayer Architecture And Angular Routing

Introduction

KendoUI is a UI framework that has 100+ angular UI widgets and features. Using KendoUI it is easy to create an application by using KendoUI widgets.

We already create API in the previous article that we need in this article. Below is the link for the API.

Preconditions

  • Basic knowledge of Angular CLI
  • Basic knowledge of Dot Net core
  • Basic knowledge of SQL server
  • Bootstrap
  • Node.js
  • V.S. Code,Visual Studio

We cover the below things,

  • Create Angular application
  • Angular Routing
  • Kendo UI setup
  • Bind KendoUI grid

Step 1

Run the below command in cmd for creating new angular project.

ng new KendoGridProject

Step 2

Now create the following file according to the below image

Step 3

Add the following code in package.json,

{
  "name": "project1",
  "version": "0.0.0",
  "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build",
    "watch": "ng build --watch --configuration development",
    "test": "ng test"
  },
  "private": true,
  "dependencies": {
    "@angular/animations": "~12.1.1",
    "@angular/common": "~12.1.1",
    "@angular/compiler": "~12.1.1",
    "@angular/core": "~12.1.1",
    "@angular/localize": "^13.2.5",
    "@angular/forms": "~12.1.1",
    "@angular/platform-browser": "~12.1.1",
    "@angular/platform-browser-dynamic": "~12.1.1",
    "@angular/router": "~12.1.1",
    "@progress/kendo-angular-buttons": "^7.0.3",
    "@progress/kendo-angular-charts": "^6.0.1",
    "@progress/kendo-angular-common": "^2.0.3",
    "@progress/kendo-angular-dateinputs": "^6.0.2",
    "@progress/kendo-angular-dialog": "^6.0.2",
    "@progress/kendo-angular-dropdowns": "^6.0.2",
    "@progress/kendo-angular-excel-export": "^4.0.4",
    "@progress/kendo-angular-grid": "^6.1.0",
    "@progress/kendo-angular-icons": "^1.0.1",
    "@progress/kendo-angular-indicators": "^1.1.3",
    "@progress/kendo-angular-inputs": "^8.0.7",
    "@progress/kendo-angular-intl": "^3.1.3",
    "@progress/kendo-angular-l10n": "^3.0.4",
    "@progress/kendo-angular-label": "^3.1.3",
    "@progress/kendo-angular-layout": "^6.5.1",
    "@progress/kendo-angular-navigation": "^1.1.5",
    "@progress/kendo-angular-notification": "^3.0.5",
    "@progress/kendo-angular-pdf-export": "^3.0.4",
    "@progress/kendo-angular-popup": "^4.0.5",
    "@progress/kendo-angular-progressbar": "^2.0.4",
    "@progress/kendo-angular-treeview": "^6.0.2",
    "@progress/kendo-angular-upload": "^8.0.2",
    "@progress/kendo-data-query": "^1.5.6",
    "@progress/kendo-drawing": "^1.16.3",
    "@progress/kendo-licensing": "^1.2.2",
    "@progress/kendo-svg-icons": "^0.1.2",
    "@progress/kendo-theme-default": "^5.0.0",
    "hammerjs": "^2.0.8",
    "rxjs": "~6.6.0",
    "tslib": "^2.2.0",
    "zone.js": "~0.11.4"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "~12.1.1",
    "@angular/cli": "~12.1.1",
    "@angular/compiler-cli": "~12.1.1",
    "@types/jasmine": "~3.6.0",
    "@types/node": "^12.11.1",
    "jasmine-core": "~3.7.0",
    "karma": "~6.3.0",
    "karma-chrome-launcher": "~3.1.0",
    "karma-coverage": "~2.0.3",
    "karma-jasmine": "~4.0.0",
    "karma-jasmine-html-reporter": "^1.5.0",
    "typescript": "~4.3.2"
  }
}

Now run the following command in cmd,

npm i

Step 4

Add the following code in layout.component.html,

<!--The content below is only a placeholder and can be replaced.-->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="description" content="">
  <meta name="author" content="">
  <title>Kendo</title>   
</head>
<body> 
<br />
  <div id="wrapper"> 
    <!-- Navigation -->
    <nav class="navbar navbar-default navbar-static-top" role="navigation" style="margin-bottom: 0">
      <kendo-appbar position="top">    
        <kendo-appbar-spacer width="32px"></kendo-appbar-spacer>
        <kendo-appbar-section>
            <ul>
               <li><a class="DashFont" [routerLink]="['/Admin/UserList']">Users</a></li>
            </ul>
        </kendo-appbar-section>
        <kendo-appbar-spacer></kendo-appbar-spacer>
    </kendo-appbar>
    </nav>
    <div id="page-wrapper">
      <div class="row">
        <div class="col-lg-12">   
          <router-outlet></router-outlet>
        </div>
        <!-- /.col-lg-12 -->
      </div>
      <!-- /.row -->
    </div>
  </div>
</body>
</html>

Step 5

Add the following code in layout.component.ts,

import {
    Component,
    OnInit
} from '@angular/core';
@Component({
    selector: 'app-layout',
    templateUrl: './layout.component.html',
    styleUrls: ['./layout.component.css']
})
export class LayoutComponent implements OnInit {
    private data: any;
    public kendokaAvatar = '/assets/img/0.jpg';
    public logout = '/assets/img/logout.png';
    constructor() {}
    ngOnInit(): void {}
    public bottomNavigationItems: Array < any > = [{
        text: 'Home',
        icon: 'home',
        selected: true
    }, {
        text: 'Calendar',
        icon: 'calendar'
    }, {
        text: 'Notifications',
        icon: 'bell'
    }];
}

Step 6

Add the following code in app.UserModel.ts,

export class  UserModel 
{
    public UserId: number =0;
    public UserName: string =""; 
    public FullName: string ="";
    public EmailId: string ="";
    public Contactno: string =""; 
    public Password: string ="";
    public imagename: string = "";
    public Status!: boolean;
    public file!: File;
}

Step 7

Add the following code in app.UserModelss.ts,

export class UserModelss
{
    public UserId: number =0 ;
    public UserName: string ="";
    public FullName: string ="";
    public EmailId: string ="";
    public Contactno: string ="";
    public Password: string ="";
    public Status!: boolean;
}

Step 8

Add the following code in model.ts,

export class Product {
    public ProductID: number =0;
    public ProductName = '';
    public Discontinued = false;
    public UnitsInStock: number =0;
    public UnitPrice = 0;
}

Step 9

Add the following code in edit.service.ts,

import { Injectable } from '@angular/core';
import { Observable, BehaviorSubject, of, throwError  } from 'rxjs';
import { HttpClient, HttpErrorResponse, HttpHeaders, HttpParams, HttpResponse } from '@angular/common/http';
import { environment } from 'src/environments/environment';
import { catchError, tap,map } from 'rxjs/operators';
import { UserModel } from '../models/app.UserModel';
import { filtermodel } from '../models/filtermodel';
import { NotificationService } from "@progress/kendo-angular-notification";
import { Component, ViewChild, TemplateRef, Input, Output, EventEmitter } from "@angular/core";

const CREATE_ACTION = 'create';
const UPDATE_ACTION = 'update';
const REMOVE_ACTION = 'destroy';

@Injectable()
export class EditService extends BehaviorSubject < any[] > {
    @ViewChild("template", {
        read: TemplateRef
    })
    public notificationTemplate: TemplateRef < any > | undefined;
    token: any;
    public apiUrl = environment.apiEndpoint + "/api/User/";
    private data: any;
    constructor(private http: HttpClient, private notificationService: NotificationService) {
        super([]);
        this.data = JSON.parse(localStorage.getItem('AdminUser') || '{}');
        this.token = this.data.token;
    }
    public read() {
        if (this.data.length) {
            return super.next(this.data);
        }
        this.GetAllUsers().pipe(tap(data => {
            this.data = data;
        })).subscribe(data => {
            super.next(data);
        });
    }
    public GetUserId(Id: any) {
        this.apiUrl = environment.apiEndpoint + "/api/User/";
        var editUrl = this.apiUrl + Id;
        let headers = new HttpHeaders({
            'Content-Type': 'application/json'
        });
        headers = headers.append('Authorization', 'Bearer ' + `${this.token}`);
        return this.http.get < UserModel > (editUrl, {
            headers: headers
        }).pipe(tap(data => data), catchError(this.handleError));
    }
    public GetUserName(UserName: any) {
        this.apiUrl = environment.apiEndpoint + "/api/User/";
        var editUrl = this.apiUrl + "GetByUserName?username=" + UserName;
        let headers = new HttpHeaders({
            'Content-Type': 'application/json'
        });
        headers = headers.append('Authorization', 'Bearer ' + `${this.token}`);
        return this.http.get < UserModel > (editUrl, {
            headers: headers
        }).pipe(tap(data => data), catchError(this.handleError));
    }
    // Save User
    public SaveUser(usermodel: UserModel) {
        let formdata = new FormData();
        const file = usermodel.file;
        formdata.append("UserName", usermodel.UserName);
        formdata.append("Contactno", usermodel.Contactno);
        formdata.append("EmailId", usermodel.EmailId);
        formdata.append("FullName", usermodel.FullName);
        formdata.append("Password", usermodel.Password);
        formdata.append("imagename", usermodel.imagename);
        formdata.append("Status", "true");
        formdata.append('file', usermodel.file);
        this.apiUrl = environment.apiEndpoint + "/api/User";
        var putUrl = this.apiUrl + "/" + usermodel.UserId;
        let headers = new HttpHeaders({
            'Authorization': 'Bearer ' + `${this.token}`
        });
        if (usermodel.UserId > 0) {
            //headers = headers.append('Authorization', 'Bearer ' + `${this.token}`);
            return this.http.put < any > (putUrl, formdata, {
                headers: headers
            }).pipe(catchError(this.handleError));
        } else {
            return this.http.post < any > (this.apiUrl, formdata, {
                headers: headers
            }).pipe(catchError(this.handleError));
        }
    }
    public save(data: any, isNew ? : boolean) {
        const action = isNew ? CREATE_ACTION : UPDATE_ACTION;
        this.reset();
        this.GetAllUsers().subscribe(() => this.read(), () => this.read());
    }
    public remove(data: any) {
        this.reset();
        var deleteUrl = this.apiUrl + data.UserId;
        let headers = new HttpHeaders({
            'Content-Type': 'application/json'
        });
        headers = headers.append('Authorization', 'Bearer ' + `${this.token}`);
        return this.http.delete < any > (deleteUrl, {
            headers: headers
        }).pipe(catchError(this.handleError));
    }
    public resetItem(dataItem: any) {
        if (!dataItem) {
            return;
        }
        // find orignal data item
        const originalDataItem = this.data.find((item: {
            ProductID: any;
        }) => item.ProductID === dataItem.ProductID);
        // revert changes
        Object.assign(originalDataItem, dataItem);
        super.next(this.data);
    }
    private reset() {
        this.data = [];
    }
    public GetAllUsers() {
        var apiUrl = environment.apiEndpoint + "/api/User/";
        let headers = new HttpHeaders({
            'Content-Type': 'application/json'
        });
        headers = headers.append('Authorization', 'Bearer ' + `${this.token}`);
        return this.http.get < UserModel[] > (this.apiUrl, {
            headers: headers
        }).pipe(tap(data => data), catchError(this.handleError));
    }
    public GetAllserverUsers(pageSize: number, skip: number, model: filtermodel) {
        let formdata = new FormData();
        formdata.append("field", "");
        formdata.append("operatortype", "");
        formdata.append("value", "");
        formdata.append("pageSize", "" + pageSize + "");
        formdata.append("skip", "" + skip + "");
        // var getUrl = this.apiUrl+"GetAllServerList?pageSize=" + pageSize+"&skip="+ skip;
        //var getUrl = this.apiUrl+"GetAllServerList?pageSize=" + pageSize+"&skip="+ skip;
        var putUrl = this.apiUrl + "GetAllServerList";
        let headers = new HttpHeaders({
            'Authorization': 'Bearer ' + `${this.token}`
        });
        //  let headers = new HttpHeaders({ 'Content-Type': 'application/json' });
        //  headers = headers.append('Authorization', 'Bearer ' + `${this.token}`);
        return this.http.post < any > (putUrl, formdata, {
                headers: headers
            })
            //  return this.http.get<UserModel[]>(getUrl, { headers: headers })
            .pipe(tap(data => data), catchError(this.handleError));
    }
    public GetFilteredUsers(pageSize: number, skip: number, model: string) {
        debugger
        let formdata = new FormData();
        formdata.append("model", model);
        // formdata.append("operatortype",model.operator);
        // formdata.append("value",model.value);
        // formdata.append("pageSize",""+pageSize +"");
        // formdata.append("skip", ""+ skip +"");
        //  var putUrl = this.apiUrl+"GetFilteredList?pageSize=" + pageSize+"&skip="+ skip;
        var putUrl = this.apiUrl + "GetFilteredList";
        //   let headers = new HttpHeaders({ 'Content-Type': 'application/json' });
        // headers = headers.append('Content-Type', 'application/x-www-form-urlencoded');
        //headers = headers.append('Content-Type', 'application/json; charset=utf-8');
        //  headers = headers.append('Authorization', 'Bearer ' + `${this.token}`);
        let headers = new HttpHeaders({
            'Authorization': 'Bearer ' + `${this.token}`
        });
        //  return this.http.put<any>(putUrl, formdata, { headers: headers })
        return this.http.post < any > (putUrl, formdata, {
            headers: headers
        }).pipe(tap(data => data), catchError(this.handleError)
            //  return this.http.get<UserModel[]>(getUrl, { headers: headers }).pipe(tap(data => data),
        );
    }
    private fetch(action: string = '', data ? : any): Observable < any[] > {
        return this.http.jsonp(`https://demos.telerik.com/kendo-ui/service/Products/${action}?${this.serializeModels(data)}`, 'callback').pipe(map(res => < any[] > res));
    }
    private serializeModels(data ? : any): string {
        return data ? `&models=${JSON.stringify([data])}` : '';
    }
    private handleError(error: HttpErrorResponse) {
        if (error.error instanceof ErrorEvent) {
            // A client-side or network error occurred. Handle it accordingly.
            console.error('An error occurred:', error.error.message);
        } else {
            // The backend returned an unsuccessful response code.
            // The response body may contain clues as to what went wrong,
            console.error(`Backend returned code ${error.status}, ` + `body was: ${error.error}`);
        }
        // return an observable with a user-facing error message
        return throwError('Something bad happened; please try again later.');
    };
    public showSuccess(): void {
        this.notificationService.show({
            content: "User saved successfully",
            hideAfter: 600,
            position: {
                horizontal: "center",
                vertical: "top"
            },
            animation: {
                type: "fade",
                duration: 400
            },
            type: {
                style: "success",
                icon: true
            },
        });
    }
    public showError(result: string): void {
        let contentmessage = "";
        contentmessage = result;
        this.notificationService.show({
            content: contentmessage,
            hideAfter: 600,
            position: {
                horizontal: "center",
                vertical: "top"
            },
            animation: {
                type: "fade",
                duration: 400
            },
            type: {
                style: "error",
                icon: true
            },
        });
    }
}

Step 10

Add the following code in users-grid.component.css,

.k-grid-div-root{
    margin-left: 120px;
    margin-right:  120px;
    /* position: fixed;
    top: 50%;
    left: 20%; */
}
button{
    color: rgb(42, 39, 42);
    background-color: rgb(243, 183, 213);
}

Step 11

Add the following code in app-routing.module.ts,

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { DashboardComponent } from './dashboard/dashboard.component';
import { LayoutComponent } from './layout/layout.component';
import { LoginComponent } from './login/login.component';
// import { UserlistComponent } from './userlist/userlist.component';
import { UsersGridComponent } from './users-grid/users-grid.component';

const routes: Routes = [
  {
    path: 'Admin',
    component: LayoutComponent,
    children: [
      { path: 'Dashboard', component: DashboardComponent }

    ]
  },
  {
    path: 'Admin',
    component: LayoutComponent,
    children: [
      { path: 'UserList', component : UsersGridComponent }
    ]
  },
  {
    path: '',
    component: LayoutComponent,
    children: [
      { path: '', component : UsersGridComponent }
    ]
  },

  // {
  //   path: '',
  //   component: LayoutComponent,
  //   children: [
  //     { path: '', component : UserlistComponent }
  //   ]
  // },
  { path: 'login', component : LoginComponent },
  { path: '**', redirectTo: "login", pathMatch: 'full' },
//  { path: '', redirectTo: "login", pathMatch: 'full' }
];
@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

Step 12

Add the following code in app.component.html,

<router-outlet></router-outlet>

Step 13

Add the following code in app.module.ts,

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import 'hammerjs';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';
import { HttpClientModule, HttpClientJsonpModule } from '@angular/common/http';
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { LoginComponent } from './login/login.component';
import { DashboardComponent } from './dashboard/dashboard.component';
import { LayoutComponent } from './layout/layout.component';
import { UsersGridComponent } from './users-grid/users-grid.component';
import { UsersGridEditComponent } from './users-grid-edit/users-grid-edit.component';
import { EditService } from './users-grid/services/edit.service';

// kendo_ libraries
import { InputsModule } from '@progress/kendo-angular-inputs';
import { LabelModule } from '@progress/kendo-angular-label';
import { ChartsModule } from '@progress/kendo-angular-charts';
import { ButtonsModule } from '@progress/kendo-angular-buttons';
import { DialogModule } from '@progress/kendo-angular-dialog';
import { UploadModule } from '@progress/kendo-angular-upload';
import { FileSelectModule } from '@progress/kendo-angular-upload';
import { PDFExportModule } from '@progress/kendo-angular-pdf-export';
import { NotificationModule } from '@progress/kendo-angular-notification';
import { NavigationModule } from '@progress/kendo-angular-navigation';
import { IconsModule } from '@progress/kendo-angular-icons';
import { IndicatorsModule } from '@progress/kendo-angular-indicators';
import { LayoutModule } from '@progress/kendo-angular-layout';
import { SharedModule } from '@progress/kendo-angular-grid';
import { GridModule } from '@progress/kendo-angular-grid';

@NgModule({
  declarations: [
    AppComponent,
    LayoutComponent,
    DashboardComponent,
    LoginComponent,
    UsersGridComponent,
    UsersGridEditComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    BrowserModule,
    AppRoutingModule,
    FormsModule,
    ReactiveFormsModule,
    RouterModule,
    BrowserAnimationsModule,
    CommonModule,
    HttpClientJsonpModule,
    HttpClientModule,
    GridModule,
    InputsModule,
    LabelModule,
    ChartsModule,
    ButtonsModule,
    DialogModule,
    UploadModule,
    FileSelectModule,
    PDFExportModule,
    NotificationModule,
    NavigationModule,
    IconsModule,
    IndicatorsModule,
    LayoutModule,
    GridModule,
    SharedModule
  ],
  providers: [
    {
        deps: [HttpClient],
        provide: EditService
    }
      ],
  bootstrap: [AppComponent]
})
export class AppModule { }

Step 14

Add the following code in index.html,

<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Project1</title>
    <base href="/">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/x-icon" href="favicon.ico">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css" integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
    <link rel="stylesheet" href="https://unpkg.com/@progress/kendo-theme-default@latest/dist/all.css" />
  </head>
<body>
  <app-root></app-root>
</body>
</html>

Step 15

Add the following code in styles.css,

/* You can add global styles to this file, and also import other style files */
body { font-family: "RobotoRegular",Helvetica,Arial,sans-serif; font-size: 14px; margin: 0; }
      my-app { display: block; box-sizing: border-box; padding: 30px; }
      my-app > .k-icon.k-i-loading { font-size: 64px; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); }
      .example-wrapper { min-height: 280px; align-content: flex-start; }
      .example-wrapper p, .example-col p { margin: 0 0 10px; }
      .example-wrapper p:first-child, .example-col p:first-child { margin-top: 0; }
      .example-col { display: inline-block; vertical-align: top; padding-right: 20px; padding-bottom: 20px; }
      .example-config { margin: 0 0 20px; padding: 20px; background-color: rgba(0,0,0,.03); border: 1px solid rgba(0,0,0,.08); }
      .event-log { margin: 0; padding: 0; max-height: 100px; overflow-y: auto; list-style-type: none; border: 1px solid rgba(0,0,0,.08); background-color: #fff; }
      .event-log li {margin: 0; padding: .3em; line-height: 1.2em; border-bottom: 1px solid rgba(0,0,0,.08); }
      .event-log li:last-child { margin-bottom: -1px;}

      .k-grid .k-grid-header, .k-grid .k-grid-content {
        background: #fff;
        color: rgb(65, 48, 13);
      }
      .k-grid-div-root[_ngcontent-apc-c709] {
        position: inherit !important;
      }
      .k-appbar-light{
        background-color: #d6f0e8;
      }
      .k-grid-header .k-filterable{
          width: 155px !important;
      }
      .k-touch-action-auto{
        width: 155px !important;
      }
      .navbar {
    background-color:  #549ae5 !important;
}

Step 16

Add the following code in layout.component.css,

:host {
    padding: 0;
}
kendo-appbar .title {
    font-size: 18px;
    margin: 10px;
}
kendo-badge-container {
    margin-right: 8px;
}
kendo-appbar ul {
    font-size: 14px;
    list-style-type: none;
    padding: 0;
    margin: 0;
    display: flex;
}
kendo-appbar li {
    margin: 0 9px;
}
kendo-appbar li:hover {
    cursor: pointer;
    color: #d6002f;
}
kendo-appbar .actions .k-button {
    padding: 0;
}
kendo-breadcrumb {
    margin-left: 30px;
}

.DashFont{
    font-size: 16px;
    font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
}

Step 17

Add the following code in users-grid.component.html,

<div class="k-grid-div-root">
  <kendo-grid [kendoGridBinding]="gridData" [pageSize]="10" [pageable]="true" [height]="510"
    (dataStateChange)="onStateChange($event)" (edit)="editHandler($event)" (remove)="removeHandler($event)"
    (add)="addHandler()" [columnMenu]="true" [filterable]="true" [sortable]="true">
    >
    <ng-template kendoGridToolbarTemplate>
      <button kendoGridAddCommand>Add new</button>
    </ng-template>
    <kendo-grid-column field="FullName" [width]="140"></kendo-grid-column>
    <kendo-grid-column field="UserName" [width]="120"></kendo-grid-column>
    <kendo-grid-column field="EmailId" [width]="100"></kendo-grid-column>
    <kendo-grid-column field="Contactno" [width]="130"></kendo-grid-column>
    <kendo-grid-column title="command">
      <ng-template kendoGridCellTemplate>
        <button kendoGridEditCommand [primary]="true">Edit</button>
        <button kendoGridRemoveCommand>Delete</button>
      </ng-template>
    </kendo-grid-column>
  </kendo-grid>
</div>

<app-users-grid-edit [model]="editDataItem" [isNew]="isNew" (save)="saveHandler($event)" (cancel)="cancelHandler()">
</app-users-grid-edit>

Now run the following command

ng serve open

How to bind KendoUI grid in angular

Summary

In this article, we learn how to create angular project, set routing, and bind kendoUI grid with API.


Similar Articles