Azure Cosmos DB – Angular with WebAPI

This article is about using Azure Cosmos DB with Angular and WebAPI. Why Azure Cosmos DB, Any web, mobile, gaming, and IoT application that needs to manage extensive amounts of data, reads, and writes, are great of use cases.

Few differences between a document DB and a relational database:-

Document DB Relational Database
De-normalize data (Think about JSON format, key value pairs) Normalized data (Plain SQL queries)
Referential integrity NOT enforced Referential integrity FORCED through normalization and relationship
Mixed data in a collection Uniform data in tables
Flexible schema The schema is not so flexible
SQL like language as well as Javascript Pure T-SQL

More info here – https://azure.microsoft.com/en-ca/services/cosmos-db/

We will create a project – beer tracker and it will use angular, webapi and cosmosdb. We use azure cosmos db emulator to not get into configuring Azure CosmosDB which will make it post a really long one.

Download Azure CosmosDB Emulator here – https://docs.microsoft.com/en-us/azure/cosmos-db/local-emulator

This is how the final demo would look:-

Please note that in this application we are using Cosmos DB local emulator instead of real Azure Cosmos DB service. Once installed properly, it should show like below in your browser. I have installed and it is working in my chrome browser.

We will start with WebAPI, let’s create a new WebAPI project from visual studio 2017. And post that we will hook up Azure cosmosDB into webAPI and last part will be froentend UI by Angular.

In the below step, we are going to install Microsoft.Azure.DocumentDB NuGet package. DocumentDB is a true schema-free NoSQL document database service designed for modern mobile and web applications.

Install-Package Microsoft.Azure.DocumentDB -Version 2.2.3

We need to enable CORS in our Web API to allow requests from front end Angular application. For that, install Microsoft.AspNet.WebApi.Cors using NuGet

Install-Package Microsoft.AspNet.WebApi.Cors -Version 5.2.7

Lets go the the AppStart and update the WebApiConfig.cs

public static class WebApiConfig
     {
         public static void Register(HttpConfiguration config)
         {
             // Web API configuration and services       
             // Web API routes
        config.MapHttpAttributeRoutes();

        EnableCorsAttribute cors = new EnableCorsAttribute("*", "*", "*");
        config.EnableCors(cors);

        config.Routes.MapHttpRoute(
            name: "DefaultApi",
            routeTemplate: "api/{controller}/{id}",
            defaults: new { id = RouteParameter.Optional }
        );
    }
}

We will also update the web.config of our webapi project to use cosmosDB emulator. Later on it can be changed for Azure CosmosDB keys. It will have no impact on other part of this demo.

<!--config keys for cosmos DB--&gt;
<add key="endpoint" value="https://localhost:8081" /&gt;  
<add key="authKey" value="C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" /&gt;  
<add key="database" value="AngularBeerMeetup" /&gt;  
<add key="collection" value="BeerMeetupCollection" /&gt;
<!--config keys for cosmos DB--&gt;

Now lets add the models and API controllers.

namespace BeerMeetupSolution.Models
{
using Newtonsoft.Json;
public class BeerMeetup
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "uid")]
public string UId { get; set; }
[JsonProperty(PropertyName = "location")]
public string Location { get; set; }
[JsonProperty(PropertyName = "brand")]
public string Brand { get; set; }
[JsonProperty(PropertyName = "cheers")]
public string Cheers { get; set; }
}
}

We also create a DocumentDBRepository class, just to keep here CosmosDB CRUD operations. These CRUD methods will be called by API controllers.

public static class DocumentDBRepository where T : class
     {
         private static readonly string DatabaseId = ConfigurationManager.AppSettings["database"];
         private static readonly string CollectionId = ConfigurationManager.AppSettings["collection"];
         private static DocumentClient client;
    
public static async Task<T&gt; GetItemAsync(string id)
    {
        try
        {
            Document document = await client.ReadDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id));
            return (T)(dynamic)document;
        }
        catch (DocumentClientException e)
        {
            if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                return null;
            }
            else
            {
                throw;
            }
        }
    }

    public static async Task<IEnumerable<T&gt;&gt; GetItemsAsync()
    {
        IDocumentQuery<T&gt; query = client.CreateDocumentQuery<T&gt;(
            UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
            new FeedOptions { MaxItemCount = -1 })
            .AsDocumentQuery();

        List<T&gt; results = new List<T&gt;();
        while (query.HasMoreResults)
        {
            results.AddRange(await query.ExecuteNextAsync<T&gt;());
        }

        return results;
    }

    public static async Task<IEnumerable<T&gt;&gt; GetItemsAsync(Expression<Func<T, bool&gt;&gt; predicate)
    {
        IDocumentQuery<T&gt; query = client.CreateDocumentQuery<T&gt;(
            UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
            new FeedOptions { MaxItemCount = -1 })
            .Where(predicate)
            .AsDocumentQuery();

        List<T&gt; results = new List<T&gt;();
        while (query.HasMoreResults)
        {
            results.AddRange(await query.ExecuteNextAsync<T&gt;());
        }

        return results;
    }

    public static async Task<T&gt; GetSingleItemAsync(Expression<Func<T, bool&gt;&gt; predicate)
    {
        IDocumentQuery<T&gt; query = client.CreateDocumentQuery<T&gt;(
            UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
            new FeedOptions { MaxItemCount = -1 })
            .Where(predicate)
            .AsDocumentQuery();
        List<T&gt; results = new List<T&gt;();
        results.AddRange(await query.ExecuteNextAsync<T&gt;());
        return results.SingleOrDefault();
    }

    public static async Task<Document&gt; CreateItemAsync(T item)
    {
        return await client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), item);
    }

    public static async Task<Document&gt; UpdateItemAsync(string id, T item)
    {
        return await client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id), item);
    }

    public static async Task DeleteItemAsync(string id)
    {
        await client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, id));
    }

    public static void Initialize()
    {
        client = new DocumentClient(new Uri(ConfigurationManager.AppSettings["endpoint"]), ConfigurationManager.AppSettings["authKey"]);
        CreateDatabaseIfNotExistsAsync().Wait();
        CreateCollectionIfNotExistsAsync().Wait();
    }

    private static async Task CreateDatabaseIfNotExistsAsync()
    {
        try
        {
            await client.ReadDatabaseAsync(UriFactory.CreateDatabaseUri(DatabaseId));
        }
        catch (DocumentClientException e)
        {
            if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                await client.CreateDatabaseAsync(new Database { Id = DatabaseId });
            }
            else
            {
                throw;
            }
        }
    }

    private static async Task CreateCollectionIfNotExistsAsync()
    {
        try
        {
            await client.ReadDocumentCollectionAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId));
        }
        catch (DocumentClientException e)
        {
            if (e.StatusCode == System.Net.HttpStatusCode.NotFound)
            {
                await client.CreateDocumentCollectionAsync(
                    UriFactory.CreateDatabaseUri(DatabaseId),
                    new DocumentCollection { Id = CollectionId },
                    new RequestOptions { OfferThroughput = 1000 });
            }
            else
            {
                throw;
            }
        }
    }
}

Here is the APIs which are accessible and can be tested by Fiddlers or PostMan tool. All these methods are exposed via end points and accessible by HTTP protocols. We have also used WebAPI route prefix, and this will help angular code to resolve the url and access them.

[RoutePrefix("api/beermeetup")]
namespace BeerMeetupSolution.Controllers
 {
     [RoutePrefix("api/beermeetup")]
     public class BeerMeetupController : ApiController
     {    
    [HttpGet]
    public async Task<IEnumerable<Models.BeerMeetup&gt;&gt; GetAsync()
    {

        IEnumerable<Models.BeerMeetup&gt; value = await DocumentDBRepository<Models.BeerMeetup&gt;.GetItemsAsync();
        return value;
    }

    [HttpPost]
    public async Task<Models.BeerMeetup&gt; CreateAsync([FromBody] Models.BeerMeetup objbm)
    {
        if (ModelState.IsValid)
        {
            await DocumentDBRepository<Models.BeerMeetup&gt;.CreateItemAsync(objbm);
            return objbm;
        }
        return null;
    }
    public async Task<string&gt; Delete(string uid)
    {
        try
        {
            Models.BeerMeetup item = await DocumentDBRepository<Models.BeerMeetup&gt;.GetSingleItemAsync(d =&gt; d.UId == uid);
            if (item == null)
            {
                return "Failed";
            }
            await DocumentDBRepository<Models.BeerMeetup&gt;.DeleteItemAsync(item.Id);
            return "Success";
        }
        catch (Exception ex)
        {
            return ex.ToString();
        }
    }
    public async Task<Models.BeerMeetup&gt; Put(string uid, [FromBody] Models.BeerMeetup o)
    {
        try
        {
            if (ModelState.IsValid)
            {
                Models.BeerMeetup item = await DocumentDBRepository<Models.BeerMeetup&gt;.GetSingleItemAsync(d =&gt; d.UId == uid);
                if (item == null)
                {
                    return null;
                }
                o.Id = item.Id;
                await DocumentDBRepository<Models.BeerMeetup&gt;.UpdateItemAsync(item.Id, o);
                return o;
            }
            return null; ;
        }
        catch (Exception ex)
        {
            return null;
        }

    }
}
}

Now we will switch to our front end. The Angular part. Ensure you have install NPM and its configured in your system.

Type into the black command prompt

ng new AngularUI 

It will take some time for Angular CLI to create a new project and once it completes we will switch to Visual Studio Code.

The boiler plate generated by AngularCLI would be ready, and we will start to build our frontend code.

Adding model for BeerMeetup,

export class BeerMeetup {  
uid: string;
location: string;
brand: string;
cheers: string;
}

After model, we add service which will have crud methods.

  
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';

import { BeerMeetup } from './beermeetup';

const api = 'http://localhost:53090//api';

@Injectable()
export class BeerMeetupService {
constructor(private http: HttpClient) { }

getBM() {
return this.http.get<Array<BeerMeetup>>(`${api}/beermeetup`);
}

deleteBM(beermeetup: BeerMeetup) {
return this.http.delete(`${api}/beermeetup?uid=${beermeetup.uid}`);
}

addBM(beermeetup: BeerMeetup) {
return this.http.post<BeerMeetup>(`${api}/beermeetup/`, beermeetup);
}

updateBM(beermeetup: BeerMeetup) {
return this.http.put<BeerMeetup>(`${api}/beermeetup?uid=${beermeetup.uid}`, beermeetup);
}
}

Lastly, we will add the component

  
import { Component, OnInit } from '@angular/core';

import { BeerMeetup } from './beermeetup';
import { BeerMeetupService } from './beermeetup.service';

@Component({
selector: 'app-ohs',
templateUrl: './beermeetup.component.html'
})
export class BeerMeetupComponent implements OnInit {
addingBM = false;
deleteButtonSelected = false;
heroes: any = [];
selectedBM: BeerMeetup;

constructor(private beermeetupService: BeerMeetupService) { }

ngOnInit() {
this.getBM();
}

cancel() {
this.addingBM = false;
this.selectedBM = null;
}

deleteBM(hero: BeerMeetup) {
this.deleteButtonSelected = true;
let value: boolean;
value = confirm("Are you sure want to delete this meetup?");
if (value != true) {
return;
}
this.beermeetupService.deleteBM(oh).subscribe(res => {
this.ohs= this.heroes.filter(h => h !== oh);
if (this.selectedBM === oh) {
this.selectedBM = null;
}
});
}

getBM() {
return this.beermeetupService.getBM().subscribe(ohs=> {
this.ohs= ohs;
});
}

enableAddMode() {
this.addingBM = true;
this.selectedBM = new BeerMeetup();
}

onSelect(hero: BeerMeetup) {
if (this.deleteButtonSelected == false) {
this.addingBM = false;
this.selectedBM = oh;
}
this.deleteButtonSelected = false;
}

save() {
if (this.addingBM) {
this.beermeetupService.addBM(this.selectedBM).subscribe(
obj => {
this.addingBM = false;
this.selectedBM = null;
this.ohs.push(oh);
});
} else {
this.beermeetupService.updateBM(this.selectedBM).subscribe(obj=> {
this.addingBM = false;
this.selectedBM = null;
});
}
}
}

Once this is done, lets switch over to app.module.ts, we will ensure our modules are registered here.

  
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';

import { AppComponent } from './app.component';
import { BeerMeetupService } from './beermeetup.service';
import { BeerMeetupComponent } from './beermeetup.component';
@NgModule({
declarations: [
AppComponent,
BeerMeetupComponent
],
imports: [
BrowserModule,
FormsModule,
HttpClientModule
],
providers: [BeerMeetupService],
bootstrap: [AppComponent]
})
export class AppModule { }



Now we will move to Visual Studio Code, Terminal and fire

ng serve -o
Advertisements