Using Azure Cosmos DB with ASP.NET Core and Angular

Posted by: Mahesh Sabnis , on 10/10/2017, in Category Microsoft Azure
Views: 44551
Abstract: This article provides an idea to design an application around Azure Cosmos DB. The application uses DocumentDB APIs consumed in ASP.NET Core Web APIs and an Angular 4 client.

During one of my training programs on Azure, a bunch of students asked me how to use the Azure Cosmos DB service in modern web applications. They wanted a Web API in ASP.NET Core to communicate with Cosmos DB and then consume the Web API in an Angular client.

The architecture discussed is shown in the following image:

cosmosdb-aspnet-core-architecture

This article provides a concept to design such an application. The application uses DocumentDB APIs consumed in ASP.NET Core Web API and Angular 4 client.

Cosmos DB, an overview

Azure Cosmos DB is a globally distributed database service which is designed for elastically and independently scaling storage and throughput, across the any number of geographical regions. Cosmos DB provides the following features:

1. Multi-model API, it natively supports the following APIs for creating a globally distributed database

  • Table API
  • DocumentDB API
  • MongoDB API
  • Graph API

2. Easy distribution of the data at any number of Azure regions.

3. Per second granularity for an easy scaling of database throughput.

4. Used for high-responsive and mission critical applications because of low latency for data reading.

5. Always available.

6. Independent from database schema and index.

7. Low cost.

More information of Cosmos DB can be obtained from this link.

Creating Cosmos DB app using the Azure Portal

To create an application using Cosmos DB, you need a Microsoft Azure subscription. Visit https://portal.azure.com and login with the your Microsoft Live credentials (hotmail, outlook a/c).

This will open the Azure portal which provides feature for managing Web Applications, Sql Server Database, APIs, etc. The portal will contain a dashboard similar to the following:

azure-portal

On this page click on the New link, to bring up the Azure Marketplace panel. Search Azure Cosmos DB from the search-box or scroll down to search for Azure Cosmos DB.

Selecting Azure Cosmos DB will bring up another panel where information for Cosmos DB creation can be entered. The following image shows the required details:

create-azure-cosmos-db

In the New account panel, the  following information can be entered

· ID : This will be the ID of the database with a database link as xxxx.documents.azure.com, where xxxx will be the database ID.

· API: This is a drop-down that will allow us to select the model API to be used to create the Cosmos DB. The following image shows the API options:

cosmos-db-apis

Select API as per the requirement. The current application used SQL (DocumentDB).

· Now select Resource Group and Location.

· Click on the Create button. This will create Cosmos DB as shown in the following image

cosmos-db-info

The URI is the database id which will be used in the application to connect to it for creating Database, collection, etc.

The Collection sections shows a list of collections in the database. Database can be managed using links for Add Collection, Refresh, Move, Delete Account, Data Explorer, etc. The Data Explorer shows the panel with a list of collections in the current database.

To connect to the database from the application, Authentication Keys are needed. Click on Keys link. This will show a panel as shown in the following image:

cosmos-db-keys

Document collection can be managed for Add and Delete operations using the Browse link as shown in the following image:

document-collection

To Replicate the data globally across various regions, click on the Replicate data globally link under the Settings section. The Data center location map will be displayed showing the selected region while creating Cosmos DB, as shown in the following image:

replicate-data-globally

The new region can be selected by clicking on the region and saving the selection.

 

Our Demo App and Creating Web API

The application in this article is developed to store and manage personal Information. The information is stored in Document DB using Document API of the Cosmos DB service. The following diagram provides an idea of the application to be implemented:

application-archirecture

The Web APIs are created using ASP.NET Core. The Web API connects with the Document DB API using the Repository class. These APIs are accessed by the Angular client application. The following steps discuss the implementation of the application.

The application needs Visual Studio 2017 to create ASP.NET Core WEB APIs. The Angular client application is created using Visual Studio Code.

Step 1: Open Visual Studio and create a new ASP.NET Core application as shown in the following image:

new-aspnet-core-project

Name this project as ASPNET_Core_COSMOS_DB and click on the OK button. A new window for selecting a web project will be displayed as shown in the following image:

new-project-mvc-app

Note that the .NET Core framework is selected and the ASP.NET Core 2.0 version is selected for generating the project. Select Web Application (Model-View-Controller) project.

This will create an ASP.NET Core application with Models, Views and Controllers folder.

Step 2: Since the application consumes Document DB API for performing CRUD Operations, the Document DB NuGet Package must be installed in the project.

Right-Click on the Dependencies section and click on Manage NuGet Packages. In the NuGet package window search for Microsoft.AzureDocumentDB.Core package.

This package provides following namespaces

  • Microsoft.Azure.Documents
  • Microsoft.Azure.Documents.Client
  • Microsoft.Azure.Documents.Linq

The application uses the DocumentClient class. This class acts as a proxy for accessing Document DB API in the application. This class provides methods to create Database and Document Collection and methods for managing the collection. The DocumentCollection class represents the document collection in the Database.

Step 3: In the Models folder, add a new class file of the name PersonInformationModel.cs and add the following class in it:

using Newtonsoft.Json;
using System;

namespace ASPNET_Core_Cosmos_DB.Models
{
    public class PersonalInformationModel
    {
        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; }
        [JsonProperty(PropertyName = "firstName")]
        public string firstName { get; set; }
        [JsonProperty(PropertyName = "middleName")]
        public string middleName { get; set; }
        [JsonProperty(PropertyName = "lastName")]
        public string lastName { get; set; }
        [JsonProperty(PropertyName = "gender")]
        public string gender { get; set; }
        [JsonProperty(PropertyName = "birthDate")]
        public DateTime birthDate { get; set; }
        [JsonProperty(PropertyName = "address")]
        public string address { get; set; }
        [JsonProperty(PropertyName = "city")]
        public string city { get; set; }
        [JsonProperty(PropertyName = "state")]
        public string state { get; set; }
        [JsonProperty(PropertyName = "pinCode")]
        public string pinCode { get; set; }
        [JsonProperty(PropertyName = "qualification")]
        public string qualification { get; set; }
        [JsonProperty(PropertyName = "occupation")]
        public string occupation { get; set; }
        [JsonProperty(PropertyName = "jobType")]
        public string jobType { get; set; }
        [JsonProperty(PropertyName = "income")]
        public decimal income { get; set; }
        [JsonProperty(PropertyName = "email")]
        public string email { get; set; }
        [JsonProperty(PropertyName = "hobbies")]
        public string hobbies { get; set; }
        [JsonProperty(PropertyName = "maritalStatus")]
        public string maritalStatus { get; set; }
        [JsonProperty(PropertyName = "residentType")]
        public string residentType { get; set; }
        [JsonProperty(PropertyName = "contactNo")]
        public string contactNo { get; set; }

    }
}

This model class defines the PersonalInformationModel class with properties. This will be used to define schema for the document in the collection.

Step 4: In the project, add a new folder of the name Repositories. In this folder, add a new class file of name OperationsRepositories.js. In this file, add the following code

using ASPNET_Core_Cosmos_DB.Models;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Linq;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ASPNET_Core_Cosmos_DB.Repositories
{
    /// <summary>
    /// The interface contains methods for Operations on Collections
    /// </summary>
    /// <typeparam name="TModel"-></typeparam>
    public interface IDbCollectionOperationsRepository<TModel, in TPk>
    {
        Task<IEnumerable<TModel>> GetItemsFromCollectionAsync();
        Task<TModel> GetItemFromCollectionAsync(TPk id);
        Task<TModel> AddDocumentIntoCollectionAsync(TModel item);
        Task<TModel> UpdateDocumentFromCollection(TPk id, TModel item);
        Task DeleteDocumentFromCollectionAsync(TPk id);
    }

    /// <summary>
    /// The following class is used to create DocumentDB, Collections if not exist
    /// </summary>
    public class DbCollectionOperationsRepository : IDbCollectionOperationsRepository<PersonalInformationModel, string>
    {
        #region The DocumentDB Endpoint, Key, DatabaseId and CollectionId declaration
        private static readonly string Endpoint = "https://xxxx.documents.azure.com:443/";
        private static readonly string Key = "0NvoeaheKa61tq7ZEW0YCL8CwIimuzAh2yv1rgPMqAvvixxxdn82ignPXalMbC117Kx4oA60MK4WopWYgYa4DAOaQ==";
        private static readonly string DatabaseId = "PersonalInformationDB";
        private static readonly string CollectionId = "PersonalInfoCollection";
        private static DocumentClient docClient;
        #endregion

        public  DbCollectionOperationsRepository()
        {
            docClient = new DocumentClient(new Uri(Endpoint), Key);
            CreateDatabaseIfNotExistsAsync().Wait();
            CreateCollectionIfNotExistsAsync().Wait();
        }

        #region Private methods to create Database and Collection if not Exist
        /// <summary>
        /// The following function has following steps
        /// 1. Try to read database based on the DatabaseId passed as URI link, if it is not found the exception will be thrown
        /// 2. In the exception, the database will be created of which Id will be set as DatabaseId 
        /// </summary>
        /// <returns></returns>
        private static async Task CreateDatabaseIfNotExistsAsync()
        {
            try
            {
                //1.
                await docClient.ReadDatabaseAsync(UriFactory.CreateDatabaseUri(DatabaseId));
            }
            catch (DocumentClientException e)
            {
                if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
                {
                    //2.
                    await docClient.CreateDatabaseAsync(new Database { Id = DatabaseId });
                }
                else
                {
                    throw;
                }
            }
        }
        /// <summary>
        /// The following function has following steps
        /// 1.Read the collection based on the DatabaseId and Collectionid passed as URI, if not found then throw exception
        /// //2.In exception create a collection.
        /// </summary>
        /// <returns></returns>
        private static async Task CreateCollectionIfNotExistsAsync()
        {
            try
            {
                //1.
                await docClient.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId));
            }
            catch (DocumentClientException e)
            {
                if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
                {
                    //2.
                    await docClient.CreateDocumentCollectionAsync(
                        UriFactory.CreateDatabaseUri(DatabaseId),
                        new DocumentCollection { Id = CollectionId },
                        new RequestOptions { OfferThroughput = 1000 });
                }
                else
                {
                    throw;
                }
            }
        }
        #endregion

        /// <summary>
        /// The method to create a new Document in the collection 
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public async Task<PersonalInformationModel> AddDocumentIntoCollectionAsync(PersonalInformationModel item)
        {
            try
            {
                var document = await docClient.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), item);
                var res = document.Resource;
                var person = JsonConvert.DeserializeObject<PersonalInformationModel>(res.ToString());
                return person;
            }
            catch (Exception ex)
            {
                throw ex;
            }
        }

        /// <summary>
        /// Method to Delete document
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task DeleteDocumentFromCollectionAsync(string id)
        {
           await docClient.DeleteDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id));
        }

        /// <summary>
        /// Method to read Item from the document based on id
        /// </summary>
        /// <param name="id"></param>
        /// <returns></returns>
        public async Task<PersonalInformationModel> GetItemFromCollectionAsync(string id)
        {
            try
            {
                Document doc = await docClient.ReadDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id));
                return JsonConvert.DeserializeObject<PersonalInformationModel>(doc.ToString()); 
            }
            catch (DocumentClientException e)
            {
                if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
                {
                    return null;
                }
                else
                {
                    throw;
                }
            }
        }


        /// <summary>
        /// Method to Read all Documents from the collection
        /// </summary>
        /// <returns></returns>
        public async Task<IEnumerable<PersonalInformationModel>> GetItemsFromCollectionAsync()
        {
            var documents = docClient.CreateDocumentQuery<PersonalInformationModel>(
                  UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
                  new FeedOptions { MaxItemCount = -1 })
                  .AsDocumentQuery();
            List<PersonalInformationModel> persons = new List<PersonalInformationModel>();
            while (documents.HasMoreResults)
            {
                persons.AddRange(await documents.ExecuteNextAsync<PersonalInformationModel>());
            }
            return persons;
        }   
        /// <summary>
        /// Method to Update Document
        /// </summary>
        /// <param name="id"></param>
        /// <param name="item"></param>
        /// <returns></returns>
        public async Task<PersonalInformationModel> UpdateDocumentFromCollection(string id, PersonalInformationModel item)
        {
            try
            {
                var document = await docClient.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id), item);
                var data = document.Resource.ToString();
                var person = JsonConvert.DeserializeObject<PersonalInformationModel>(data);
                return person;
            }
            catch (Exception ex)
            {

                throw ex;
            }
        }
    }
}

The above code has the following specifications:

The IDbCollectionOperationsRepository interface - TModel type represents the model class which will be used for all methods of the interface. The TPk type represents the input parameter to methods of the interface. The interface defines methods for performing CRUD operations.

The DbCollectionOperationsRepository class implements the IDbCollectionOperationsRepository interface where TModel is typed as PersonalInformationModel model class and TPk is typed as string. This class has following features

This class defines private variables which are explained as follows

  • EndPoint – represent the database id, this is created in the section named Creation of the Cosmos DB using Microsoft Azure Portal.
  • Key - The authentication key to access the database.
  • DatabaseId - The database to be created.
  • CollectionId - The collection name to be created.
  • docClient - The Document DB API client.

The CreateDatabaseIfNotExistAsync() method tries to read the database asynchronously using ReadDatabaseAsync() method of the DocumentClient class based on the database URI passed to it. If the database exists, it will be returned. If not, then a new database will be created using CreateDatabaseAsync() method of the DocumentClient class based on the Database object passed to it containing id.

The CreateCollectionIfNotExist() method reads the collection using ReadDocumentCollectionAsync() method of the DocumentClient class based on the DatabaseId and CollectionId parameters passed to it. If the Document collection exists, then it will be returned; else the collection is created using CreateDocumentCCollectionAsync() method of the DocumentClient class.

The above two methods are called in the constructor of the DbCollectionOperationsRepository class.

The AddDocumentIntoCollectionAsync() method accepts PersonalInfromationModel object. This is used to create a new document in the document collection using CreateDocumentAsync() method of the DocumentClient class.

The CreateDocumentAsync() method returns a ResoureResponse object which contains newly created Document as a resource using the Resource property. The AddDocumentIntoCollectionAsync() method returns the newly added document with its id property generated by the Document DB API by deserializing it using JsonConvert object.

The DeleteDocumentFromCollectionAsync() method accepts id as an input parameter. The DeleteDocumentAsync() method of the DocumentClient class is used to delete the document from the collection based on the DocumentId, CollectionId and id parameters.

The GetItemFromCollectionAsync() method accepts id parameter based on which a document will be read from the collection using ReadDocumentAsync() method of the DocumentClient class. The retrieved document is then deserialized in the PersonalnformationModel object and returned to WEB API.

The GetItemsFromCollectionAsync() method is used to read all documents from the DocumentCollection using CreateDocumentQuery() method of the DocumentClient class. The CreateDocumentQuery() method accepts DatabaseId and CollectionId based on which documents are returned.

The UpdateDocumentFromCollection() method accepts id and PersonalInformationModel object to replace an existing document with the new information. To perform the update operation, the ReplaceDocumentAsync() method of the DocumentClient class is used. The ReplaceDocumentAsync() method accepts DatabaseId, CollectionId, DocumentId and Model object to search the document based on id and replace existing values of the searched Document by the values from the Model object.

Step 5: To enable the ASP.NET Core Web API for CORS, install the following package in the project using NuGet Package Manager:

aspnet-core-cors

Step 6: To configure CORS and the dependency injection of the Repositories created in Step 4, modify the ConfigureServices() method in the Startup.cs as shown in the following code:

public void ConfigureServices(IServiceCollection services)
{
    services.AddSingleton(typeof(IDbCollectionOperationsRepository), typeof(DbCollectionOperationsRepository));
    services.AddCors(options =>
    {
        options.AddPolicy("AllowAll",
            builder => builder.WithOrigins("*").AllowAnyHeader().AllowAnyMethod());
    });
    services.AddMvc();
}

Using AddSingleton() method, the Repositories will be registered in dependency injection container and using AddCors() method, the CORS policy is defined for the Web API.

The AddCors() method defines CORS policy by accepting the string parameter representing name of the policy and the second parameter as CorsPolicyBuilder. The CorsPolicyBuilder object is used to define allowed origins, headers and methods.

Once the CORS policy is added, this policy must be added in the Application Builder object. To do so, modify the Configure() function of the Startup.cs by adding the following line

// Shows UseCors with named policy.
app.UseCors("AllowAll");

Step 7: Add the new Empty API Controller in the Controllers folder of name PersonalInformationAPIController with the following code in it:

using ASPNET_Core_Cosmos_DB.Models;
using ASPNET_Core_Cosmos_DB.Repositories;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;

namespace ASPNET_Core_Cosmos_DB.Controllers
{
    [Produces("application/json")]
    [Route("api/PersonalInformationAPI")]
    [EnableCors("AllowAll")]
    public class PersonalInformationAPIController : Controller
    {
        IDbCollectionOperationsRepository _repo;
        public PersonalInformationAPIController(IDbCollectionOperationsRepository r)
        {
            _repo = r;
        }
        [Route("Person/All")]
        public IActionResult Get()
        {
            var persons = _repo.GetItemsFromCollectionAsync().Result;
            return Ok(persons);
        }
        [Route("Person/{id}")]
        public IActionResult Get(string id)
        {
            var person = _repo.GetItemFromCollectionAsync(id).Result;
            return Ok(person);
        }
        [Route("Person/Create")]
        public IActionResult Post([FromBody]PersonalInformationModel per)
        {
            var person = _repo.AddDocumentIntoCollectionAsync(per).Result;
            return Ok(person);
        }
        [Route("Person/Update/{id}")]
        public IActionResult Put(string id, [FromBody]PersonalInformationModel per)
        {
            var person = _repo.UpdateDocumentFromCollection(id,per);
            return Ok(person.Result);
        }
        [Route("Person/Delete/{id}")]
        public IActionResult Delete(string id)
        {
            var res = _repo.DeleteDocumentFromCollectionAsync(id);
            return Ok(res.Status);
        }

    }
}

The above controller is injected with the IDbCollectionOperationsRepository interface.

The controller class is applied with the EnableCors attribute which accepts the CORS policy name defined in Step 6. Each action method from the controller is applied with the custom route expression. These action methods call methods from the Repository class and return the result.

Save and Build the application. The Web API can be tested using tools like Fiddler or Postman .

Creating an Angular Client Application to call Web API

This section explains the mechanism of consuming a Web API in an Angular client. The Angular client application is created using Visual Studio Code (VSCode).

To create an Angular application with basic configuration, read the Implementing an Angular Front-End Application section from this link. Follow the steps in the article for creating package.json, systemjs.config.js, server.js, index.html, etc. so that the Angular application will contain all dependencies in it.

Step 1: In the Angular client application, create a folder of the name app. In this folder, add a new file of name app.model.ts. This file is used to contain the code for the Person model class as shown in the following code:

export class Person {
    public id: string;
    public firstName: string;
    public middleName: string;
    public lastName: string;
    public gender: string;
    public birthDate: Date;
    public address: string;
    public city: string;
    public pinCode: string;
    public state: string;
    public qualification: string;
    public occupation: string;
    public jobType: string;
    public income: string;
    public email: string;
    public hobbies: string;
    public maritalStatus: string;
    public residentType: string;
    public contactNo: string

}

The above class contains public members for storing personal information. This call will be used for databinding with the UI which will be created in the forthcoming steps.

Step 2: In the app folder, add a new file of name constData.ts. This file will be used to declare constant arrays for various type of data values to be displayed on the UI. Add the following code in the file:

export const Occupations = [
    'Service',
    'Self-Employeed',
    'UnEmployeed'
];
export const Qualification = [
    'Bachelor-Science',
    'Bachelor-Arts',
    'Bachelor-Commerce',
    'Bachelor-Engineering',
    'Masters-Science',
    'Masters-Arts',
    'Masters-Commerce',
    'Masters-Engineering',
    'Doctor-MBBS',
    'Doctor-BHMS',
    'Doctor-BAMS',
    'Doctor-MBBS-MD',
    'Doctor-BHMS-MD',
    'Doctor-BAMS-MD',
    'Middle-School',
    'High-School',
    'No-Eduction'
];

export const JobTypes = [
    'IT',
    'Manufactiring',
    'Driver',
    'Doctor',
    'Marketing',
    'Teacher',
    'Gardner',
    'Housekeeper',
    'Banker'
];
export const Genders = [
    'Male', 'Female', 'Transsexual'
];
export const Hobbies = [
    'Music', 'Movies', 'Theater', 'Travel', 'Painting', 'Photography'
];
export const MaritalStatuses = [
    'Married',
    'Single-Unmarried',
    'Single-Widower',
    'Single-Widow',
    'Single-Divorsed'
];
export const ResidentTypes = [
    'Self-Owned',
    'Rental'
];

The above file contains arrays with values for the following

1. Occupations

2. Qualifications

3. JobTypes

4. Genders

5. Hobbies

6. MaritalStatuses

7. ResidentTypes

Step 3: In the app folder add a new file of name app.service.ts. In this file, add the following code

import { Injectable } from '@angular/core';
import { Http, Request, RequestOptions, Headers, Response } from "@angular/http";
import { Observable } from "rxjs";
import { Person } from './app.model';
@Injectable()
export class PersonalInfoService {
    servUrl: string;
    constructor(private http: Http) {
        this.servUrl = "http://localhost:61758/api/PersonalInformationAPI";
    }
    getAll(): Observable {
        var res = this.http.get(`${this.servUrl}/Person/All`);
        return res;
    }
    getSingle(id: string): Observable {
        var res = this.http.get(`${this.servUrl}/Person/${id}`);
        return res;
    }

    post(per: Person): Observable {
        var headers = new Headers({ 'Content-Type': 'application/json' });
        var options = new RequestOptions();
        options.headers = headers;
        var res = this.http.post(`${this.servUrl}/Person/Create`, per, options);
        return res;
    }
    put(id: string, per: Person): Observable {
        var headers = new Headers({ 'Content-Type': 'application/json' });
        var options = new RequestOptions();
        options.headers = headers;
        var res = this.http.put(`${this.servUrl}/Person/Update/${id}`, JSON.stringify(per), options);
        return res;
    }
    delete(id: string): Observable {
        var res = this.http.delete(`${this.servUrl}/Person/Delete/${id}`);
        return res;
    }
}

The above code contains the Angular service which makes a call to the Web API created in the Application Description and Creating WEB API section.

Step 4: In the app folder, add a new file of name app.component.ts. Add the following code in this file:

//1. Import standard Angular modules
import {Component, OnInit} from '@angular/core';
//2. Import Person class
import {Person} from './app.model';
//3. Import all constants
import {
    Occupations,
    JobTypes,
    Genders,
    Hobbies,
    MaritalStatuses,
    ResidentTypes,
    Qualifications
} from './constData';
//4.Import the Service
import {PersonalInfoService} from './app.service';
//5. Import Response object
import {Response} from "@angular/http";
//6. Import Observable object
import {Observable} from "rxjs";
//7. Define the component
@Component({
    selector: 'personal-data',
    templateUrl: '../app/app.view.html'
})
export class PersonalInfomationComponent implements OnInit {
    //7a. Define properties used for databinding
    person: Person;
    persons: Array;
    arrGender: Array;
    arrOccupation: Array;
    arrJobType: Array;
    arrQualification: Array;
    arrHobbies: Array;
    arrMaritalStatus: Array;
    arrResidentType: Array;
    canChange: boolean;
    canEdit: boolean;
    //7b.The property which will be used to generate table headers
    tableHeaders: Array;
    //7c. Constructor to initialize all properties
    constructor(private pServ: PersonalInfoService) {
        this.canChange = false;
        this.canEdit = false;
        this.tableHeaders = new Array();
        this.person = new Person();
        this.persons = new Array();
        this.arrOccupation = new Array();
        this.arrOccupation = Occupations;
        this.arrQualification = new Array();
        this.arrQualification = Qualifications;
        this.arrGender = new Array();
        this.arrGender = Genders;
        this.arrHobbies = new Array();
        this.arrHobbies = Hobbies;
        this.arrJobType = new Array();
        this.arrJobType = JobTypes;
        this.arrMaritalStatus = new Array();
        this.arrMaritalStatus = MaritalStatuses;
        this.arrResidentType = new Array();
        this.arrResidentType = ResidentTypes;
    }
    //7d. Function to read the person object
    //and read all its properties and push them in
    //the tableHeaders property. This will be used
    //for generating table headers to show person
    //information
    private createTableHeaders() {
        for (let p in this.person) {
            this
                .tableHeaders
                .push(p);
        }
    }
    //7e. Function to make call to Angular service
    //and receive response
    private loadData() {
        this
            .pServ
            .getAll()
            .subscribe((resp: Response) => {
                this.persons = resp.json();
            });
    }
    //7f.Function to reinitialize all properties
    clearInputs() {
        this.person.id = '';
        this.person.firstName = '';
        this.person.lastName = '';
        this.person.middleName = '';
        this.person.address = '';
        this.person.city = '';
        this.person.state = '';
        this.person.pinCode = '';
        this.person.email = '';
        this.person.income = '';
        this.person.contactNo = '';
        this.person.qualification = '';
        this.person.maritalStatus = '';
        this.person.gender = '';
        this.person.residentType = '';
        this.person.occupation = '';
        this.person.jobType = '';
        this.person.hobbies = '';
    }
    //7g. Call to clearInputs, loadData and  createTableHeaders at init
    ngOnInit() {
        this.clearInputs();
        this.loadData();
        this.createTableHeaders();
    }
    //7h. Function to re-declare the person object
    //This function sets the canChange flag to true
    //so that the table showing Person information will be
    //hidden and the table containing textboxes will be shown    
    add() {
        this.person = new Person();
        this.canChange = true;
    }
    //7i. Function to mark the Person object
    //as editable. The canEdit flag is set to true
    //to make person object editable    
    edit(p: Person) {
        this.person = p;
        this.canEdit = true;
        this.canChange = true;
    }
    //7j: Function to add and edit Person object
    //If the canEdit is false the new record will be
    //created by calling post() function from service
    //If the canEdit is true then the selected person
    //record will be updated by calling put() function
    //from the Angular service. Once add/edit operation
    //is over the canChange flag will be reset to false.  
    save() {
        if (!this.canEdit) {
            this
                .pServ
                .post(this.person)
                .subscribe((resp: Response) => {
                    this
                        .persons
                        .push(resp.json());
                    this.canChange = false;
                });
        } else {
            this
                .pServ
                .put(this.person.id, this.person)
                .subscribe((resp: Response) => {
                    this
                        .persons
                        .push(resp.json());
                    this.canChange = false;
                    this.canEdit = false;
                });

        }
    }
    //7k. Function to delete the selected person record.
    //This access delete() function from the Angular service
    delete(p: Person) {
        this.pServ.delete(p.id).subscribe((resp: Response) => {
            this.loadData();
        });
    }
}

The above code contains an Angular component which contains the following features (Note: Following numbering matches with the comments applied in the above code)

1. Importing standard modules for creating component.

2. Importing Person class defined in the app.model.ts file

3. Imports all constants declared in the constData.ts file.

4. Import the Angular Service defined in the app.service.ts file.

5. Import Response object for collecting Response from the Angular Service.

6. Import Observable object

7. Define Angular Component. This component contains following

  • a. Define properties for DataBinding with the UI elements.
  • b. Define the tableHeaders property, this will be used to store properties of the Person class for generating table headers to show person information.
  • c. The constructor is injected with the PersonalInfoService object. This is an Angular service object which is used to access functions defined in the Angular service. The constructor initializes all properties to their initial values.
  • d. The createTableHeaders() function is used to read the person object and read all its properties. These properties are pushed in the tableHeaders array property. This will be used for generating table headers to show person information.
  • e. The loadData() function is used to make call to Angular service and receive response.
  • f. The cleatInputs() function is used to reinitialize all properties.
  • g. The ngOnInit() function is used to call to clearInputs(), loadData() and createTableHeaders() function at initionalization.
  • h. The add() function is used to re-declare the person object. This function sets the canChange flag to true so that the table showing Person information will be hidden and the table containing textboxes will be shown.
  • i. The edit() function is used to mark the Person object as editable. The canEdit flag is set to true to make person object editable.
  • j. The save() function is used to add and edit Person object. If the canEdit flag is false then the new record will be created by calling post() function from service. If the canEdit flag is true then the selected person record will be updated by calling put() function from the Angular service. Once add/edit operation is over the canChange flag will be reset to false.
  • k. The delete() function is used to delete the selected person record. This access delete() function from the Angular service

Step 5: In the app folder, add a new file of name app.view.html. This file contains UI of the application. The following HTML markup shows the UI

<div [hidden]="!canChange">
    <table class="table table-bordered table-strpied">
        <tr>
            <td>
                <div>
                    <div class="form-group">
                        <label for="firstname">First Name</label>
                        <input type="text" name="firstname" class="form-control" [(ngModel)]="person.firstName" />
                    </div>
                    <div class="form-group">
                        <label for="middlename">Middle Name</label>
                        <input type="text" name="middlename" class="form-control" [(ngModel)]="person.middleName" />
                    </div>
                    <div class="form-group">
                        <label for="lastname">Last Name</label>
                        <input type="text" name="lastname" class="form-control" [(ngModel)]="person.lastName" />
                    </div>
                    <div class="form-group">
                        <label for="gender">Gender</label>
                        <select name="gender" class="form-control" [(ngModel)]="person.gender">
                        <option *ngFor="let g of arrGender" value="{{g}}">{{g}}</option>
                    </select>

                    </div>
                    <div class="form-group">
                        <label for="dob">Birth Date</label>
                        <input type="text" name="dob" class="form-control" [(ngModel)]="person.birthDate" />
                    </div>
                    <div class="form-group">
                        <label for="address">Address</label>
                        <input type="text" name="address" class="form-control" [(ngModel)]="person.address" />
                    </div>
                    <div class="form-group">
                        <label for="city">City</label>
                        <input type="text" name="city" class="form-control" [(ngModel)]="person.city" />
                    </div>
                    <div class="form-group">
                        <label for="pincode">Pincode</label>
                        <input type="text" name="pincode" class="form-control" [(ngModel)]="person.pincode" />
                    </div>
                    <div class="form-group">
                        <label for="state">State</label>
                        <input type="text" name="state" class="form-control" [(ngModel)]="person.state" />
                    </div>

                </div>
            </td>
            <td>
                <div>
                    <div class="form-group">
                        <label for="qualification">Qualification</label>
                        <select name="qualification" class="form-control" [(ngModel)]="person.qualification">
                                 <option *ngFor="let e of arrQualification" value="{{e}}">{{e}}</option>   
                            </select>
                    </div>
                    <div class="form-group">
                        <label for="occupation">Occupation</label>
                        <select name="occupation" class="form-control" [(ngModel)]="person.occupation">
                         <option *ngFor="let o of arrOccupation" value="{{o}}">{{o}}</option>   
                    </select>
                    </div>
                    <div class="form-group">
                        <label for="jobtype">Job Type</label>
                        <select [(ngModel)]="person.jobType" name="jobtype" class="form-control">
                        <option *ngFor="let j of arrJobType" value="{{j}}">{{j}}</option>
                    </select>
                    </div>
                    <div class="form-group">
                        <label for="income">Income</label>
                        <input type="text" name="income" [(ngModel)]="person.income" class="form-control" />
                    </div>
                    <div class="form-group">
                        <label for="email">Email</label>
                        <input type="text" name="email" [(ngModel)]="person.email" class="form-control" />
                    </div>
                    <div class="form-group">
                        <label for="hobbies">Hobbies</label>
                        <select name="hobbies" [(ngModel)]="person.hobbies" class="form-control">
                        <option *ngFor="let h of arrHobbies" value="{{h}}">{{h}}</option>   
                    </select>
                    </div>
                    <div class="form-group">
                        <label for="maritalstatus">Marital Status</label>
                        <select [(ngModel)]="person.maritalStatus" name="maritalstatus" class="form-control">
                        <option *ngFor="let m of arrMaritalStatus" value="{{m}}">{{m}}</option>
                    </select>
                    </div>
                    <div class="form-group">
                        <label for="residenttype">Resident Type</label>
                        <select [(ngModel)]="person.residentType" name="residenttype" class="form-control">
                        <option *ngFor="let r of arrResidentType" value="{{r}}">{{r}}</option>
                    </select>
                    </div>
                    <div class="form-group">
                        <label for="contactnumber">Contact Number</label>
                        <input type="text" [(ngModel)]="person.contactNo" name="contactnumber" class="form-control" />
                    </div>
                </div>
            </td>
        </tr>
        <tr>
            <td>
                <input type="button" value="New" (click)="clearInputs()" class="btn btn-default" />
            </td>
            <td>
                <input type="button" value="Save" (click)="save()" class="btn btn-success" />
            </td>
        </tr>
    </table>
</div>
<div [hidden]="canChange">
    <table class="table table-bordered table-strpied">
        <tr>
            <td>
                <input type="button" value="Add New" class="btn btn-success" (click)="add()" />
            </td>
        </tr>
        <tr>
            <td>
                <table class="table table-bordered table-strpied">
                    <thead>
                        <tr>
                            <td *ngFor="let c of tableHeaders">{{c|uppercase}}</td>
                            <td></td>
                            <td></td>
                        </tr>
                    </thead>
                    <tbody>
                        <tr *ngFor="let p of persons">
                            <td *ngFor="let c of tableHeaders">
                                {{p[c]}}
                            </td>
                            <td>
                                <input type="button" (click)="edit(p)" value="Edit" class="btn btn-warning" />
                            </td>
                            <td>
                                <input type="button" (click)="delete(p)" value="Delete" class="btn btn-danger" />
                            </td>
                        </tr>
                    </tbody>
                </table>
            </td>
        </tr>
    </table>
</div>

The above HTML has two <div> elements the first <div> element contains text input elements which are bind with the properties from the person class. This <div> also contains buttons defining click binding with functions defined in the component class created in previous step. The second <div> contains <table> which is used to generate column headers and rows based on the data in the persons array.

Step 6: In the app folder, add a new file of name main.ts. This file will contain the bootstrap code for PersonalInformationComponent and register the PersonalInService Angular service, as shown in the following code:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { platformBrowserDynamic } from "@angular/platform-browser-dynamic";
import { FormsModule } from "@angular/forms";
import { PersonalInfomationComponent } from './app.component';
import { HttpModule } from "@angular/http";
import { PersonalInfoService } from "./app.service";

@NgModule({
    declarations: [PersonalInfomationComponent],
    imports: [BrowserModule, FormsModule, HttpModule],
    providers: [PersonalInfoService],
    bootstrap: [PersonalInfomationComponent]
})
export class AppModule { }
platformBrowserDynamic().bootstrapModule(AppModule)

The above code defines PersonalInformationComponent as bootstrap component. In the index.html add the following selector which is defined in PersonalInformationComponent:

<personal-data></personal-data>
 

Executing the application

Run the Web API Project and run the Angular client using following command:

npm run start

Open the browser and enter the following URL in the address bar

http://localhost:9001

The View will be displayed as shown in the following image

angular-client-cosmosdb

Click on the Add New button, the following view will be displayed:

angular-client-cosmosdb-form

Add Person Details as shown in the following image:

image

Click on the button, the entered data will be displayed as shown in the following image

cosmosdb-edit-angular

Navigate to the Azure portal using https://portal.azure.com. Login with the credentials and from the Dashboard, click on the Cosmos DB account created in the beginning of this article. The Database and collection created will be displayed as shown in the following image:

personal-info

Click on the collection name i.e. PersonalInfoCollection, the entered data will be displayed as shown in the following image:

data-explorer

Click on the Edit button to bring up the table showing the Personal Information in the Angular client view. The selected Person record will be displayed in the TextBoxes. The data can be modified and after clicking on the Save button, the data will be saved. Like-wise the Delete functionality can be tested.

Conclusion: The Cosmos DB multi-model APIs can be used for creating Document Collection and provide CRUD operations. This can be used for storing and managing data in the form of JSON documents.

Download the entire source code of this article (Github)

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this Book aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on the latest .NET Core 3.0, .NET Standard and C# 8.0 (final release) too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Mahesh Sabnis is a DotNetCurry author and a Microsoft MVP having over two decades of experience in IT education and development. He is a Microsoft Certified Trainer (MCT) since 2005 and has conducted various Corporate Training programs for .NET Technologies (all versions), and Front-end technologies like Angular and React. Follow him on twitter @maheshdotnet or connect with him on LinkedIn


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!