Introduction
In this article, we will create an Angular 6 application with Web API 2.0. We will see all four CRUD operations and save the data to Cosmos DB. For testing purposes, we can use Cosmos DB emulator and save the data to local system. After successfully completed testing, we can create a Cosmos DB service in Azure also.
Background
Those who are new to Cosmos DB, Azure Cosmos DB is Microsoft's globally distributed, multi-model database. With the click of a button, Azure Cosmos DB enables you to elastically and independently scale throughput and storage across any number of Azure's geographic regions. It offers throughput, latency, availability, and consistency guarantees with comprehensive service level agreements (SLAs), something no other database service can offer. Currently, there are five different types of APIs that are supported by Cosmos DB as given below.
- SQL
- MongoDB
- Graph
- Table
- Cassandra
Implementation
Web API Service Creation
Create a new Web API 2.0 project WebAPI4AngularCosmosDB
using Visual Studio.
I am using ASP.NET 4.5 Templates for it.
Next step, we are going to install Microsoft.Azure.DocumentDB
NuGet package.
We can create a DocumentDBRepository
class now. This is the core repository for CosmosDB CRUD operations.
DocumentDBRepository.cs
namespace WebAPI4AngularCosmosDB
{
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.Azure.Documents;
using Microsoft.Azure.Documents.Client;
using Microsoft.Azure.Documents.Linq;
public static class DocumentDBRepository<T> 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> 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>> GetItemsAsync()
{
IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
new FeedOptions { MaxItemCount = -1 })
.AsDocumentQuery();
List<T> results = new List<T>();
while (query.HasMoreResults)
{
results.AddRange(await query.ExecuteNextAsync<T>());
}
return results;
}
public static async Task<IEnumerable<T>> GetItemsAsync(Expression<Func<T, bool>> predicate)
{
IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
new FeedOptions { MaxItemCount = -1 })
.Where(predicate)
.AsDocumentQuery();
List<T> results = new List<T>();
while (query.HasMoreResults)
{
results.AddRange(await query.ExecuteNextAsync<T>());
}
return results;
}
public static async Task<T> GetSingleItemAsync(Expression<Func<T, bool>> predicate)
{
IDocumentQuery<T> query = client.CreateDocumentQuery<T>(
UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId),
new FeedOptions { MaxItemCount = -1 })
.Where(predicate)
.AsDocumentQuery();
List<T> results = new List<T>();
results.AddRange(await query.ExecuteNextAsync<T>());
return results.SingleOrDefault();
}
public static async Task<Document> CreateItemAsync(T item)
{
return await client.CreateDocumentAsync
(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), item);
}
public static async Task<Document> 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;
}
}
}
}
}
This is a static and generic class. Inside this class, we have an Initialize
method and it will be invoked from the Application_Start
method inside Global.asax class.
When the application starts, DocumentDBRepository<Hero>.Initialize()
will be called and it will create one Cosmos DB database and collection if it does not exist. Please note that in the first run, there was no database and collection in our Cosmos DB.
Cosmos DB endpoint, key, database and collection name must be stored in the Web.config file.
<configuration>
<appSettings>
<add key="webpages:Version" value="3.0.0.0" />
<add key="webpages:Enabled" value="false" />
<add key="ClientValidationEnabled" value="true" />
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
<add key="endpoint" value="https://localhost:8081" />
<add key="authKey"
value="C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" />
<add key="database" value="AngularHeroDB" />
<add key="collection" value="MyCollection1" />
</appSettings>......
Please note that in this application, we are using Cosmos DB local emulator instead of real Azure Cosmos DB service. This is for testing purposes. Once our testing is over, we will change this configuration with real Cosmos DB configurations.
You can get the Cosmos DB local emulator from the below URL:
Download the emulator and install it to your local system. After successful installation, you can run the emulator and it will show as service in your system tray.
If you click the Open Data Explorer, local Cosmos DB emulator will be opened in your browser.
In the Explorer button, you can see currently there is no database available in our emulator.
Now we are going to create a model Hero.cs in our Web API project inside the model folder.
Hero.cs
namespace WebAPI4AngularCosmosDB.Models
{
using Newtonsoft.Json;
public class Hero
{
[JsonProperty(PropertyName = "id")]
public string Id { get; set; }
[JsonProperty(PropertyName = "uid")]
public string UId { get; set; }
[JsonProperty(PropertyName = "name")]
public string Name { get; set; }
[JsonProperty(PropertyName = "saying")]
public string Saying { get; set; }
}
}
Please note that we used JsonProperty
attribute in this class so that we can convert our C# property to json property easily.
Now we are going to invoke DocumentDBRepository
class from Application_Start
method inside Global.asaxthrough
dependency injection.
Global.asax
namespace WebAPI4AngularCosmosDB
{
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using WebAPI4AngularCosmosDB.Models;
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
DocumentDBRepository<Hero>.Initialize();
}
}
}
Please build the application and run with your local IIS server.
DocumentDBRepository<Hero>.Initialize()
will execute Initialize
method and the below two asynchronized methods also will be executed.
CreateDatabaseIfNotExistsAsync().Wait();
CreateCollectionIfNotExistsAsync().Wait();
If you check the Cosmos DB emulator data explorer, you can see that our new database and collection is created successfully.
In our Web.config configurations, we give database as AngularHeroDB
and collection as MyCollection1
.
We can create our HeroController.cs API controller and create all four CRUD methods. All these methods are very simple. Through these methods, we can create, edit, update and delete hero which we use in our Angular application.
HeroController.cs
namespace WebAPI4AngularCosmosDB.Controllers
{
using WebAPI4AngularCosmosDB.Models;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Web.Http;
[RoutePrefix("api/Hero")]
public class HeroController : ApiController
{
[HttpGet]
public async Task<IEnumerable<Hero>> GetAsync()
{
IEnumerable<Hero> value = await DocumentDBRepository<Hero>.GetItemsAsync();
return value;
}
[HttpPost]
public async Task<Hero> CreateAsync([FromBody] Hero hero)
{
if (ModelState.IsValid)
{
await DocumentDBRepository<Hero>.CreateItemAsync(hero);
return hero;
}
return null;
}
public async Task<string> Delete(string uid)
{
try
{
Hero item = await DocumentDBRepository<Hero>.GetSingleItemAsync(d => d.UId == uid);
if (item == null)
{
return "Failed";
}
await DocumentDBRepository<Hero>.DeleteItemAsync(item.Id);
return "Success";
}
catch (Exception ex)
{
return ex.ToString();
}
}
public async Task<Hero> Put(string uid, [FromBody] Hero hero)
{
try
{
if (ModelState.IsValid)
{
Hero item = await DocumentDBRepository<Hero>.GetSingleItemAsync(d => d.UId == uid);
if (item == null)
{
return null;
}
hero.Id = item.Id;
await DocumentDBRepository<Hero>.UpdateItemAsync(item.Id, hero);
return hero;
}
return null; ;
}
catch (Exception ex)
{
return null;
}
}
}
}
If needed, you can check our API methods from POSTMAN or any other REST client.
Angular Application Creation
Create new Angular application using Angular CLI.
ng new Angular4WebAPICosmosDB
It will take some time to create a new project and after successful creation, open the application in any code editor. I am using Visual Studio code as IDE.
Please note that in this Angular application, we are using style.scss file instead of default style.css file. You must change the build settings for styles in angular.json file.
Download the entire Angular 6 source code from Github and run npm install
.
Before running Angular application, we need to enable CORS in our Web API to allow requests from Angular application. For that, install Microsoft.AspNet.WebApi.Cors
using NuGet.
Now, add the below code inside the Register
method in WebApiConfig.cs file.
namespace WebAPI4AngularCosmosDB
{
using System.Web.Http;
using System.Web.Http.Cors;
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
config.MapHttpAttributeRoutes();
EnableCorsAttribute cors = new EnableCorsAttribute("*", "*", "*");
config.EnableCors(cors);
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
}
}
}
Run the Web API and Angular application.
Our Angular application will be loaded and shown as below:
You can click the add new hero button and add some details.
If you check the data explorer in Cosmos DB emulator, you can see one document inside our MyCollection1
collection as below:
You can edit and delete heroes using our Angular application.
We have used our local Cosmos DB emulator for testing purposes. You can even reset the existing data in emulator using Reset Data button of emulator.
Please select SQL as API.
Please create the service and after successful creation, you can view the keys in the Keys pane.
Copy the new URI and primary key and paste to our Web.config instead of old keys for local Cosmos DB.
Run the Web API application again and after some time, our new Azure Cosmos database and collection will be created, and you can check the data in Data Explorer pane.
If you refresh our already running Angular application, you can notice that there is no Hero data that will be displayed. Because we have changed the connection from local Cosmos DB to Azure.
You can add some data again and save it. Please re open the Data Explorer in Azure and notice that our new data will be shown in the explorer as given below:
Happy coding with Angular 6, Cosmos DB and local emulator too.