Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF - Part Two

Introduction

This article series is aimed at teaching you how to use Cosmos DB trigger and HttpTrigger in an Azure function to create a serverless app that can broadcast the data to clients using SignalR. The client can be a desktop/web/mobile client. For this article, I will be using a simple desktop client created using WPF with Material Design. To follow along please make sure you have an Azure account. If you don't have it already you can create one for free by visiting Cloud Computing Services | Microsoft Azure.

This article is part 2 of the series and is aimed at teaching the readers how to create a new Azure function (using VS 2019) with Azure Cosmos DB Trigger and HttpTrigger to extract data from Azure Cosmos DB. To follow along please make sure to refer to Part 1 of this article series to learn how to create a Cosmos DB that we will be connecting to as a part of this tutorial.

Step 1 - Create a new Azure Functions project

Open Visual Studio and Create a new project. Search for Azure Functions in the search box, select it from the result list and click the Next button

	 Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Step 2 - Configure your new project

Choose a project name, location, and solution name for the project and click on Create button.

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Step 3 - Create a new Azure functions application

In this step, we will choose what kind of trigger we want and create a connection string we will be using to connect to Azure Cosmos DB. To configure this step correctly we will need the name of our database and collection we are creating the trigger for. To get the name of the database and collection login to Azure Portal and navigate to your Cosmos DB and click on Data Explorer (Please check part 1 of this article for step-by-step instruction on how to do this)

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Once you have the database name and collection name go back to Visual studio and fill up the data in the corresponding boxes as shown in the screenshot below. Choose the trigger as Cosmos DB Trigger and click Create

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Step 4 - Edit the functions class

The first thing to do here is to rename the function from Function1 (default visual studio name) to some meaningful name. We will rename it OrderTriggerFunction.

Visual Studio creates a bare bone function for us with pre-configured CosmosDBTrigger

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Let us edit the function to set the property CreateLeaseCollectionIfNotExists=true. This will create the lease collection when you run your function for the first time. Leases are just used to keep track of the consumers for this change feed. They are like any other document in the Cosmos DB.

public static class OrderTriggerFunction
{
    [FunctionName("OrderTriggerFunction")]
    public static void Run([CosmosDBTrigger(
        databaseName: "OrderDB",
        collectionName: "Order",
        ConnectionStringSetting = "CosmosDbConnectionString",
        LeaseCollectionName = "leases", CreateLeaseCollectionIfNotExists =true)]IReadOnlyList<Document> input, ILogger log)
    {
        if (input != null && input.Count > 0)
        {
            log.LogInformation("Documents modified " + input.Count);
            log.LogInformation("First document Id " + input[0].Id);
        }
    }
}

Step 5 - Edit the local.settings.json file

We need to add the connection string in the JSON file and for that, we need to visit our Cosmos DB again on Azure Portal and open the Keys window from the left explorer menu and make a note of Primary Connection String, URI and, Primary Key (Please check part 1 of this article for step-by-step instruction on how to do this)

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Once you have the details edit local.settings.json file. Replace the values in the angle bracket (< >) with values from your Cosmos DB account. Key name on line 6 should match the ConnectionStringSetting on the Function.

{
    "IsEncrypted": false,
  "Values": {
    "AzureWebJobsStorage": "UseDevelopmentStorage=true",
    "FUNCTIONS_WORKER_RUNTIME": "dotnet",
    "CosmosDbConnectionString": "<PRIMARY CONNECTION STRING>"
    "DatabaseEndpoint": "<URI>",
    "DatabaseAccountKey": "<PRIMARY KEY>"
  }
}

Step 6 - Create Models

Add a new folder called Models and a new class called Order.

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Copy-paste the following code into the Order class.

public class Order
{
    public string Id { get; set; }
    public string OrderStatus { get; set; }
    public string OrderingProvince { get; set; }
}

Lets create another class inside the Models folder called DashboardViewModel. Copy-paste the following code into DashboardViewModel class.

public class DashboardViewModel
{
    [JsonProperty("CompletedOrdersByProvince")]
    public Dictionary<string,string> CompletedOrdersByProvince { get; set; }
    [JsonProperty("DraftOrdersByProvince")]
    public Dictionary<string, string> DraftOrdersByProvince { get; set; }
    [JsonProperty("CancelledOrdersByProvince")]
    public Dictionary<string, string> CancelledOrdersByProvince { get; set; }
}

Step 7 - Create Service

Add a new folder called Services and add a new class inside it called DashboardService.

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Copy-paste the following code into DashboardService class.

using Microsoft.Azure.Documents.Client;
using OrderFunction.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace OrderFunction.Services {
    public class DashboardService {
        //Name of the Cosmos Db databasae
        private static readonly string DatabaseID = "OrderDB";
        // Name of the container
        private static readonly string CollectionId = "Order";
        private static readonly Uri serviceEndPoint = new Uri(Environment.GetEnvironmentVariable("DatabaseEndpoint"));
        private static readonly DocumentClient client = new DocumentClient(serviceEndPoint, Environment.GetEnvironmentVariable("DatabaseAccountKey"));
        private static readonly Uri docuementCollectionLink = UriFactory.CreateDocumentCollectionUri(DatabaseID, CollectionId);
        public DashboardViewModel GetDashboardViewModel() {
            DashboardViewModel dashboardViewModel = new DashboardViewModel();
            try {
                var query = string.Format("SELECT * FROM c");
                var queryResult = client.CreateDocumentQuery < Order > (docuementCollectionLink, query, new FeedOptions {
                    EnableCrossPartitionQuery = true
                }).ToList();
                var completedOrders = queryResult.Where(a => a.OrderStatus == "Completed").GroupBy(a => a.OrderingProvince).Select(g => new {
                    g.Key, Count = g.Count()
                });
                var draftOrders = queryResult.Where(a => a.OrderStatus == "Draft").GroupBy(a => a.OrderingProvince).Select(g => new {
                    g.Key, Count = g.Count()
                });
                var cancelledOrders = queryResult.Where(a => a.OrderStatus == "Cancelled").GroupBy(a => a.OrderingProvince).Select(g => new {
                    g.Key, Count = g.Count()
                });
                dashboardViewModel.CompletedOrdersByProvince = completedOrders.ToDictionary(x => x.Key, x => x.Count.ToString());
                dashboardViewModel.DraftOrdersByProvince = draftOrders.ToDictionary(x => x.Key, x => x.Count.ToString());
                dashboardViewModel.CancelledOrdersByProvince = cancelledOrders.ToDictionary(x => x.Key, x => x.Count.ToString());
            } catch (Exception) {
                dashboardViewModel = null;
            }
            return dashboardViewModel;
        }
    }
}

Step 8 - Add HttpTrigger function

Right-click on the project -> Add -> New Azure Function. Give a meaningful name for the function and click Add.

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

On the next screen choose HTTP trigger and select Authorization level as Anonymous. Click Add to add the function.

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Copy-paste the following code into the newly created function. This function is used to fetch the initial data for the client. The purpose of this function will be more clear when we build the client to consume it. For testing purposes, we can use Postman to test this function.

using Microsoft.AspNetCore.Http;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.Extensions.Logging;
using OrderFunction.Models;
using OrderFunction.Services;

namespace OrderFunction
{
    public static class DashboardLoadFunction
    {
        [FunctionName("DashboardLoadFunction")]
        public static DashboardViewModel Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            var vm=  new DashboardService().GetDashboardViewModel();
            return vm;
        }
    }
}

Step 9 - Test the HTTP trigger function

For testing, the function we need can use the Postman app. Run the project by pressing F5 on visual studio. You should get a screen similar to this.

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Notice the URL for DashboardLoadFunction. Copy the URL and paste it on Postman and click on Send. If everything works fine you should get the result on the Result pane.

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Step 10 - Check if the Cosmos DB Trigger function is getting hit when data is inserted/updated in Cosmos DB

To test the OrderFunctionTrigger we will run the Function in debug mode by pressing F5 on Visual Studio and inserting a breakpoint inside the function.

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Insert a new record in Cosmos DB. The easiest way to do this is using Data Explorer on Azure Portal. Open Data Explorer and click on New Item on the top menu. Copy-paste the below JSON in the right pane and click the Save button (next to the New Item menu item).

{
    "id": "5",
    "orderstatus": "Cancelled",
    "orderingprovince": "British Colombia",
    "_rid": "MlcjAJfPirYEAAAAAAAAAA==",
    "_self": "dbs/MlcjAA==/colls/MlcjAJfPirY=/docs/MlcjAJfPirYEAAAAAAAAAA==/",
    "_etag": "\"1a00f2ab-0000-0a00-0000-619f07320000\"",
    "_attachments": "attachments/",
    "_ts": 1637812018
}

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Once you click Save if everything works fine, your control should hit the breakpoint in Visual Studio, and the newly added record should there in the input variable. This indicates that the insert operation in Cosmos DB triggered your Azure function correctly.

Live Charts Using Azure Cosmos DB, Azure Functions, SignalR And WPF

Summary and Next Steps

At the end of this tutorial, you should be comfortable with creating an Azure function using Visual Studio with Cosmos DB trigger and HTTP Trigger. You should be able to connect to Azure Cosmos DB from the azure function and should also be able to write queries to fetch data from the DB. You should be able to test both HTTP trigger and Cosmos DB trigger functions. In the next tutorial, we will learn how to publish this function to Azure Cloud and create a SignalR so that clients can get live updates whenever there is a change in the DB.

Thanks for reading and stay tuned for the next article.