Wednesday, March 25, 2020

Implementing CI/CD DevOps pipelines using a simple ASP.NET Core 3.1 MVC application using GitHub Actions and Azure App Services


In this post I will show you how easy it is to implement a CI/CD DevOps pipeline using GitHb Actions. The sample web application I will use is an ASP.NET Core 3.1 MVC application and I will deploy it to Azure App Services. 

Before proceeding, It is assumed that you have the following pre-requisites:
- You already have an Azure Account
- You already have a GitHub account
- You have .NET Core 3.1 (or later) installed on your computer
Creating a simple ASP.NET Core 3.1 application
In a working directory, execute the following command from within a terminal window:

dotnet new mvc -o GithubActions2Azure

Change directory to the newly created app:

cd GithubActions2Azure

Add a .gitignore file to the application:

dotnet new gitignore

Let us make the application our own by, perhaps, changing the title of the app and the background color.

1) Add the following CSS to wwwroot/css/site.css
2) In Views/Home/Index.cshtml, change the main heading from:

<h1 class="display-4">Welcome</h1>

           TO

<h1 class="display-4">Welcome to our MVC Core 3.1 App</h1>

Let us now run the application and see what it looks like. In the terminal window run the following command:

dotnet run

Point your browser to https://localhost:5001. You will see a strangely colored web page that looks like this:


Stop the server by hitting CTRL + C in the terminal window.

Push code to GitHub

We are now ready to push this application to GitGub. Go to GitHub and create a repository. I created a repository named GithubActions2Azure. Copy the commands that are shown on the GitHub page that looks like this:

Back in your application’s terminal window, type the following commands to initialize a git repo, add files to the repo and commit the changes:

git init
git add .
git commit -m "1st commit"

Next, we will push the code to GitHub. Paste the git commands that you copied from GitHub. This is what it looked like for me:

git remote add origin https://github.com/medhatelmasry/GithubActions2Azure.git
git push -u origin master

If you refresh the GitHub page, you will see that the code is in GitHub. It looks like this:

























Azure

The next step is to create a web app in Azure. Login to the Azure portal at https://portal.azure.com/. Click on Create a resource:

Enter “web" in the filter then choose "Web App”:


On the next page, click on the blue Create button:

 

Choose a subscription then create a new Resource Group:


In named my new resource group GithubActions2Azure. Here are all the choices I made:


Click on Create on the next page:


Creation of a new Azure web service takes less than three minutes. When it is completed, a message appears declaring that “Your deployment is complete”. Click on the blue “Go to resource” button:


We need the publish profile. Click on “Get publish profile” to download your publish profile XML file:


Build & Deploy using GitHub Actions
 Back it GitHub, click on the Settings tab of your application’s repo:


Click on Secrets on the left-side:


Click on “Add a new secret”:


Give the secret the name AZURE_WEBAPP_PUBLISH_PROFILE and paste the contents of the publish profile file that you earlier downloaded from the Azure portal for your web app:

 
Click on Actions on the top navigation bar in GitHub:


You will see a multitude of templates for a multitude of technologies and programming languages. We will paste our own template, therefore, click on the “Set up a workflow yourself” link on the right-side:


This will create a .github/Workflows folder in your application. Name the file deploy_to_azure.yml then click on the green “Start commit” button:


Enter a comment and description for the commit then click on the “Commit new file” button:


Go to this site to get an appropriate template for our web app: https://github.com/Azure/webapps-deploy. Scroll down until you find this section then click on dotnet.yml:


Copy the contents of the asp.net-core-webapp-on-azure.yml. Return to the GitHub repo of our application, edit the deploy_to_azure.yml file, and replace the contents by pasting the clipboard. This is what it looks like for me:


On line 18, set the value of AZURE_WEBAPP_NAME to the name of your app. In my case it is GithubActions2Azure.

On line 20, set the value of DOTNET_VERSION with the exact version of .NET Core with which you created your app. You can find out by running the following command in a terminal window:

dotnet --version

In my case, the .NET Core version is 3.1.200.

Click on the green “Start commit” button, give it a title then click on “Commit changes”.

Click on Actions on the top navigation bar:


Click on “Deploy ASP.NET Are all to ..”. If all goes well, there should be a green check mark beside the workflow, which was responsible for building and deploying the app to Azure. Click on “Updated deploy_to_azure.yml”.


Click on “build-and-deploy” on the left-side:


You will be comforted by the fact that all tasks completed successfully:


To make sure all is well, go to the Azure portal and click on the URL of your website:


Our ugly looking tomato-colored website appears:


Note that if you make any changes to your source code and push it to GitHub, it will automatically get deployed to Azure. This is what CI/CD is all about. Let's try that.

CI/CD
In a terminal window at your project folder, do a pull in order to get all the changes we made in GitHub when we added the .yml file:

git pull

In wwwroot/css/site.css, change the background-color to wheat. Let us push our changes to Github with these commands:

git add .
git commit -m "changed background color to wheat"
git push origin master

Back in GitHub, you will find that the deployment is running:


Upon completion it will display a green check mark:

Refresh the website in your browser and you will find that it has changed background color to something nicer:


Thanks for coming this far in my tutorial and I hope you found it useful.

Friday, March 6, 2020

Build an ASP.NET Core 3.1 tag helper that consumes an API RESTful service

Tag Helpers in ASP.NET Core allow you to create your own tags that fulfill a server-side purpose. In this tutorial, I will show you how to create a tag helper <toon> that accesses a Web API service and displays contents in any razor view page.

The Web API service we will consume in this exercise is located at https://api4u.azurewebsites.net/api/people. It delivers the names of cartoon characters and their respective images.

1) To start with, create an ASP.NET Core Web application named TagHelperDemo in a terminal window using this command:

dotnet new mvc -o TagHelperDemo

2) Next, let's create a class that closely matches the nature of the Web API JSON object. Therefore, add the following CartoonCharacter class to a Models folder in your project:
public class Toon {
  public int Id { get; set; }
  public string LastName { get; set; }  
  public string FirstName { get; set; }
  public string Occupation { get; set; }
  public string Gender { get; set; }
  public string PictureUrl { get; set; }
  public int Votes { get; set; }
}
 3) We will need to install the API Client libraries. To that end, execute the following commands from a terminal window
dotnet add package Microsoft.AspNet.WebApi.Client
dotnet add package System.Runtime.Serialization.Xml
This should add the following dependencies to your project.json file:
<PackageReference Include="System.Runtime.Serialization.Xml" Version="4.3.0" />
<PackageReference Include="Microsoft.AspNet.WebApi.Client" Version="5.2.7" />
 4) Add the following tag to the bottom of Views/Home/Home.cshtml:


<toon></toon>

5) Create a folder named TagHelpers and add to it a class file named ToonTag.cs. Have the class inherit from TagHelper and implement the ProcessAsync() method as follows:

public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
}


We could have implemented a method Process() instead. However, in our case, it is appropriate to implement ProcessAsync() instead because we are about to make an async call to a remote service.

6) Add the following instance variable to the ToonTag class:

 private string baseUrl = "https://api4u.azurewebsites.net/";

7) Annotate the ToonTag class with the following:

[HtmlTargetElement("toon")]
[HtmlTargetElement(Attributes = "toonie")]


The first annotation defines the tag <toon> and the second defines the “toonie” attribute. This means that we have two different ways to produce the same output on a razor .cshtml view.

8) Add the following method to the ToonTag class:

async Task<List<Toon>> GetToonsAsync() {
    HttpClient client = new HttpClient();
    client.BaseAddress = new Uri(baseUrl);
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    List<Toon> toons = null;
    try {
        // Get all cartoon characters
        HttpResponseMessage response = await client.GetAsync("/api/people");
        if (response.IsSuccessStatusCode) {
            string json = await response.Content.ReadAsStringAsync();
            toons = JsonConvert.DeserializeObject<List<Toon>>(json);
        }
    } catch (Exception e) {
        System.Diagnostics.Debug.WriteLine(e.ToString());
    }
    return toons;
}

The above code makes a request to the Web API service and returns an List<Toon> collection with the results.

9) Add the following code inside the ProcessAsync() method:

List<Toon> toons = await GetToonsAsync();
string html = string.Empty;
html += "<table><tr><th>Name</th><th>Picture</th></tr>";
foreach (var item in toons) {
    html += "<tr>";
    html += "<td>" + item.FirstName + " " + item.LastName + "</td>";
    html += "<td><img src='" + item.PictureUrl + "' style='width: 50px' /></td>";
    html += "</tr>";
}
html += "</table>";
output.Content.SetHtmlContent(html);

The above code creates a table with the collection of toon characters so that it can be displayed wherever the tag helper is used.

11) Register the tag name in the Views/_ViewImports.cshtml file by adding the following to the list of tags that are already there:

@addTagHelper "TagHelperDemo.TagHelpers.ToonTag, TagHelperDemo"

You may need to adjust the above names depending on what you called your app and/or your tag helper class.

11) Compile and run your application. You should see the following output:



If you inspect the table in your browser, you will see the following:




The above is using the tag and not the attribute.

Edit Home.cshtml and comment out “<toon></toon>” and put the following <div> tag with the toonie attribute underneath it:
<div toonie></div>

When you run your application. you should see the same output as before. If you inspect the table you will see the following HTML source:



This proves to us that you can either use tags or attributes with TagHelpers in ASP.NET Core.




Sunday, February 16, 2020

Build a simple Sketchpad app with SignalR in ASP.NET Core 3.1

Companion Video: https://youtu.be/ktPTkISof4k
Source Code: https://github.com/medhatelmasry/SignalrSketchpad

What is SignalR

ASP.NET SignalR is a library to add real-time web functionality to applications. Real-time web functionality is the ability to have server-side code push content to the connected clients as it happens, in real-time. It essentially allows your server-side C# code to invoke client-side JavaScript functions and vice-versa.

SignalR takes advantage of several transports, automatically selecting the best available transport given the client's and server's capabilities. SignalR takes advantage of WebSockets, an HTML5 API that enables bi-directional communication between the browser and server. SignalR will use WebSockets under the covers when it's available, and gracefully fall back to other techniques and technologies when it is not, while the application code remains the same.

SignalR also provides a simple, high-level API for doing server-to-client RPC (call JavaScript functions in a client's browser from server-side .NET code) in an ASP.NET application, as well as adding useful hooks for connection management, such as connect/disconnect events, grouping connections, authorization, etc.

Assumptions

It is assumed that you have .NET Core 3.1 and VS Code installed on your computer. The following walkthrough works well on Linux, Mac and Windows 10.

The Mission

We will build an ASP.NET Code 3.1 application that allows multiple users, using multiple browsers, to share and scribble on the same canvas.

Getting Started

Let's get started. Go into a terminal windows at suitable workspace folder on your computer and create an ASP.NET Core 3.1 web app as follows:
mkdir SignalrSketchpad
cd SignalrSketchpad
dotnet new webapp --no-https

We will use Library Manager (LibMan) to get the client library from unpkg. unpkg is a content delivery network (CDN)) that can deliver anything found in npm, the Node.js package manager.
Run the following command if you do not already have LibMan installed on your computer:
dotnet tool install -g Microsoft.Web.LibraryManager.Cli
Run the following command in the root folder of the SignalrSketchpad project to get the SignalR JavaScript client library by using LibMan. You might have to wait a few seconds before seeing output.
libman install @aspnet/signalr -p unpkg -d wwwroot/lib/signalr --files dist/browser/signalr.js --files dist/browser/signalr.min.js
The above parameters specify the following options:
- Use the unpkg provider.
- Copy files to the wwwroot/lib/signalr destination.
- Copy only the specified files.

In the SignalrSketchpad project folder, create a Hubs folder. In the Hubs folder, create a DrawDotHub.cs file with the following code:
public class DrawDotHub: Hub {
   public async Task UpdateCanvas(int x, int y) {
      await Clients.All.SendAsync("updateDot",x, y);
   }

   public async Task ClearCanvas() {
      await Clients.All.SendAsync("clearCanvas");
   }
}

The DrawDotHub class inherits from the SignalR Hub class. The Hub class manages connections, groups, and messaging.

The UpdateCanvas and ClearCanvas methods can be called by a connected JavaScript client to draw and clear drawings respectively on all clients.

Configure SignalR

Append this code to the ConfigureServices() method in Startup.cs:
services.AddSignalR();
Add this code to the Configure() method in Startup.cs. Put it inside the app.UseEndpoints() block:
endpoints.MapHub<DrawDotHub>("/drawDotHub");

Add SignalR client code

Replace contents of Pages\Index.cshtml with the following code:
@page
<style>
        /* Some CSS styling */
        .rightside {
            float: left;
            margin-left: 10px;
        }

        #sketchpad {
            float: left;
            height: 300px;
            width: 600px;
            border: 2px solid #888;
            border-radius: 4px;
            position: relative; /* Necessary for correct mouse co-ords in Firefox */
        }

        #clear_button, #save_button {
            float: left;
            font-size: 15px;
            padding: 10px;
            -webkit-appearance: none;
            background: #feee;
            border: 1px solid #888;
            margin-bottom: 5px;
        }
</style
<h1>SignalR Sketchpad</h1
<div id="sketchpadapp">
        <div class="rightside">
            <button id="clear_button" onclick="tellServerToClear()">Clear Canvas</button>
            <br />
            <canvas id="sketchpad" width="600" height="300"></canvas>
        </div
</div>

<script src="~/lib/signalr/dist/browser/signalr.js"></script>
<script src="~/js/draw.js"></script>

The preceding code:
- Creates a drawing canvas with id = sketchpad.
- Creates a “Clear Canvas” button right above the canvas that calls a function named tellServerToClear().
- Includes script references to SignalR and the draw.js application code that will be created in the next step.
- JavaScript files signalr.js and draw.js are loaded

We will not need Pages/Index.cshtml.cs so you can go ahead and delete it. 

In the wwwroot/js folder, create a file named draw.js with the following code: 

"use strict";

var connection = new signalR.HubConnectionBuilder().withUrl("/drawDotHub").build();

connection.on("updateDot", function (x, y) {
    drawDot(x, y, 8);
});

connection.on("clearCanvas", function () {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
});

connection.start().then(function () {
    // nothing here
}).catch(function (err) {
    return console.error(err.toString());
});

function tellServerToClear() {
    connection.invoke("ClearCanvas").catch(function (err) {
        return console.error(err.toString());
    });
}
//////////////////////////////////////////////////////
// Variables for referencing the canvas and 2dcanvas context
var canvas, ctx;
// Variables to keep track of the mouse position and left-button status
var mouseX, mouseY, mouseDown = 0;
// Draws a dot at a specific position on the supplied canvas name
// Parameters are: A canvas context, the x position, the y position, the size of the dot
function drawDot(x, y, size) {
    // Let's use black by setting RGB values to 0, and 255 alpha (completely opaque)
    var r = 0;
    var g = 0;
    var b = 0;
    var a = 255;
    // Select a fill style
    ctx.fillStyle = "rgba(" + r + "," + g + "," + b + "," + (a / 255) + ")";
    // Draw a filled circle
    ctx.beginPath();
    ctx.arc(x, y, size, 0, Math.PI * 2, true);
    ctx.closePath();
    ctx.fill();
}

// Keep track of the mouse button being pressed and draw a dot at current location
function sketchpad_mouseDown() {
    mouseDown = 1;
    drawDot(mouseX, mouseY, 8);

    connection.invoke("UpdateCanvas", mouseX, mouseY).catch(function (err) {
        return console.error(err.toString());
    });
}

// Keep track of the mouse button being released
function sketchpad_mouseUp() {
    mouseDown = 0;
}

// Keep track of the mouse position and draw a dot if mouse button is currently pressed
function sketchpad_mouseMove(e) {
    // Update the mouse co-ordinates when moved
    getMousePos(e);
    // Draw a dot if the mouse button is currently being pressed
    if (mouseDown == 1) {
        drawDot(mouseX, mouseY, 8);
        connection.invoke("UpdateCanvas", mouseX, mouseY).catch(function (err) {
            return console.error(err.toString());
        });
    }
}

// Get the current mouse position relative to the top-left of the canvas
function getMousePos(e) {
    if (!e)
        var e = event;
    if (e.offsetX) {
        mouseX = e.offsetX;
        mouseY = e.offsetY;
    }
    else if (e.layerX) {
        mouseX = e.layerX;
        mouseY = e.layerY;
    }
}

// Set-up the canvas and add our event handlers after the page has loaded
// Get the specific canvas element from the HTML document
canvas = document.getElementById('sketchpad');
// If the browser supports the canvas tag, get the 2d drawing context for this canvas
if (canvas.getContext)
    ctx = canvas.getContext('2d');

// Check that we have a valid context to draw on/with before adding event handlers
if (ctx) {
    // React to mouse events on the canvas, and mouseup on the entire document
    canvas.addEventListener('mousedown', sketchpad_mouseDown, false);
    canvas.addEventListener('mousemove', sketchpad_mouseMove, false);
    window.addEventListener('mouseup', sketchpad_mouseUp, false);
} else {
    document.write("Browser not supported!!");
}

The preceding code:
- gets a 2d handle to the canvas element in the page
- sets event listeners for mousedown, mousemove and mouseup events
- a connection is established with the server-side hub at endpoint /drawDotHub
- whenever the server invokes a function named updateDot() on the client, then the drawDot() JavaScript function is called
- whenever the server invokes a function named clearCanvas() on the client, then the clearCanvas() JavaScript statement is executed
- connection.start() establishes a live connection with the server
- JavaScript function tellServerToClear() invokes method ClearCanvas() on the server
- JavaScript function sketchpad_mouseDown() and sketchpad_mouseMove() invoke the UpdateCanvas() methods on the server.

Run the app by typing “dotnet run” in a terminal window inside the project root folder. Point your browser to http://localhost:5000. 
Open the same page in a different browser then put both browsers side-by-side. When you scribble on one canvas you will see the same sketch on the other browser.
Use the concept to build much more sophisticated SignalR apps.


Friday, January 3, 2020

Using EF 3.0 Core database provider for Azure CosmosDB with ASP.NET MVC

Azure Cosmos DB is Microsoft's globally distributed, multi-model database service. You can interact with the database using four favorite API including SQL, MongoDB, Cassandra, Tables, or Gremlin. In this tutorial we will be accessing Azure Cosmos DB using the SQL API.

A native Entity Framework Core provider for Azure Cosmos DB was released with .NET Core 3.0. This makes it much easier for developers who are familiar with EF Core to work with Azure Cosmos DB.

Source Code: https://github.com/medhatelmasry/CosmosEfWeb

Video: https://youtu.be/VklO8olYTL8 

Prerequisites


Before you can start this tutorial, it is assumed that you have the following:


1) At the time of writing, the latest version of .NET Core is 3.1. As a result, I am using .NET Core 3.1. You can, however, use and version of .NET Core as long as it is 3.0 or later.


2) You need to have an Azure account in order to work with Azure Cosmos DB.


3) In order to make this tutorial work on any operating system (Linux, Mac and Windows), I will be using the VS Code editor.


Prior knowledge of ASP.NET MVC is a big bonus because I will not explain the what the roles and responsibilities of Models, Views and Controllers in this article.



Creating a database in Azure Cosmos DB


The first step is to create a database in on Azure Cosmosdb. Login into your Azure account by going to https://portal.azure.com. Click on the "Create a resource" button.



In the filter field, enter the word "cosmos" the select "Azure Cosmos DB".



On the next page, click on the blue Create button.



On the "Create Azure Cosmos DB Account" page:

1) Create a new resource group. I named my resource group CosmosEfWeb-RG.

2) Give your Cosmos DB an account name. I named mine cosmos-ef-web. Not that the name needs to be all in lower case letters.


3) Do not change the API. To use Entity Framework Core we must use "Core (SQL)".


4) Finally, choose the location closest you. Since I reside on the west coast of Canada, I chose "(US) West US 2".




Click on the "Review + create" button. The next page will look like this:



Note the message that indicates that it will take about six minutes to create the account. Click on the blue Create button.

Once the provisioning is completed, you will see a page that looks like this.



Click on the blue "Go to resource" button. Click on "Data Explorer" on the left-side navigation.



Click on the "New Container" dropdown-list then select "New Database".



Give the database a name. I named my database "CollegeDB" because I will soon create an MVC app that does CRUD operations with student data.



Click on the blue OK button. After the database is created, click on the three dots on the right-side of the database then select "New Container". A container is equivalent to a collection or table in a database.



Note that this is where you can delete the entire database.


Enter a container-id. I named my container students because I will be adding a list of students. I added a partition key of /id.

Note: The Partition Key is used to automatically partition data among multiple servers for scale-ability. Choose a JSON property name that has a wide range of values and is likely to have evenly distributed access patterns.

Click on the blue OK button.

At this point our database is ready and we can start populating it with data with an application that we are about to build. We need, however, some credentials. Click on Keys on the left-side menu.



On the next page, you will need the URI and the "PRIMARY KEY". Copy both values and park them in a text editor.



We can now start developing our application.

Building an ASP.NET Core MVC app.


Open a terminal window in a working directory on your computer.

Type the following command to determine which version of .NET Core SDK you have.

dotnet --list-sdks

The following versions were listed on my computer:

2.2.104 [C:\Program Files\dotnet\sdk]
3.0.100 [C:\Program Files\dotnet\sdk]
3.1.100 [C:\Program Files\dotnet\sdk]

Create a folder named CosmosEfWeb with the following terminal command:

mkdir CosmosEfWeb

Change to the new folder with this command:

cd CosmosEfWeb

Now, if you want to use the exact version of .NET Core that I created my application with, then you can create a global.json file for version 3.1.100 with next command. Note that is optional.

dotnet new globaljson --sdk-version 3.1.100

Next, let us create a new MVC app with this command:

dotnet new mvc

In order to use the new Entity Framework .NET Core 3.0 provider, you must add the Nuget package Microsoft.EntityFrameworkCore.Cosmos. Add this package with the following terminal command:

dotnet add package Microsoft.EntityFrameworkCore.Cosmos

Now let's open our application in the VS Code editor. I was able to do so by simply entering the following command in a terminal window:

code .

We are going to model students. Therefore, add the following very simple Student.cs class inside the Models folder:

public class Student {
  [JsonProperty(PropertyName = "id")]
  public string Id { get; set; }

  [Required]
  [JsonProperty(PropertyName = "firstname")]
  public string FirstName { get; set; }

  [Required]
  [JsonProperty(PropertyName = "lastname")]
  public string LastName { get; set; }

  [Required]
  [JsonProperty(PropertyName = "school")]
  public string School { get; set; }
}


Let us add the database credentials. Add the following section to the appsettings.json file:

"CosmosDb": {
  "Endpoint": "https://cosmos-ef-web.documents.azure.com:443/",
  "Key": "ABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFGHIJKLMNOPQRSTUVWXYZ1234451234567890ABCDEFGHIJKLMN==",
  "DatabaseName": "CollegeDB",
  "ContainerName": "Students"
}


Endpoint comes from URI and Key comes from PRIMARY KEY.

Service class


We will next create a service that is the middle tier between our controller and the database. Create a folder named Services.

In the Services folder, add the following interface file:

public interface ICosmosDbService {
  Task<IEnumerable<Student>> GetStudentsAsync(string query);
  Task<Student> GetStudentAsync(string id);
  Task AddStudentAsync(Student student);
  Task UpdateStudentAsync(string id, Student student);
  Task DeleteStudentAsync(string id);
}

Also, in the same Services folder, add a class file named CosmosDbService, that implements ICosmosDbService. The CosmosDbService.cs file contains the following code:

public class CosmosDbService : ICosmosDbService {
  private Microsoft.Azure.Cosmos.Container _container;

  public CosmosDbService(
    CosmosClient dbClient,
    string databaseName,
    string containerName) {
    this._container = dbClient.GetContainer(databaseName, containerName);
  }

  public async Task AddStudentAsync(Student student) {
    await this._container.CreateItemAsync<Student>(student, new PartitionKey(student.Id));
  }

  public async Task DeleteStudentAsync(string id) {
    await this._container.DeleteItemAsync<Student>(id, new PartitionKey(id));
  }

  public async Task<Student> GetStudentAsync(string id) {
    try {
        ItemResponse<Student> response = await this._container.ReadItemAsync<Student>(id, new PartitionKey(id));
        return response.Resource;
    }
    catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) {
        return null;
    }
  }

  public async Task<IEnumerable<Student>> GetStudentsAsync(string queryString) {
    var query = this._container.GetItemQueryIterator<Student>(new QueryDefinition(queryString));
    List<Student> results = new List<Student>();
    while (query.HasMoreResults) {
        var response = await query.ReadNextAsync();

        results.AddRange(response.ToList());
    }

    return results;
  }

  public async Task UpdateStudentAsync(string id, Student student) {
      await this._container.UpsertItemAsync<Student>(student, new PartitionKey(id));
  }
}


To Startup.cs, add a method named InitializeCosmosClientInstanceAsync that is responsible for reading the database credentials from appsettings.json and returning the service. Add the following method to Startup.cs:

/// <summary>
/// Creates a Cosmos DB database and a container with the specified partition key.
/// </summary>
/// <returns></returns>
private static async Task<CosmosDbService> InitializeCosmosClientInstanceAsync(IConfigurationSection configurationSection)
{
  string databaseName = configurationSection.GetSection("DatabaseName").Value;
  string containerName = configurationSection.GetSection("ContainerName").Value;
  string account = configurationSection.GetSection("Endpoint").Value;
  string key = configurationSection.GetSection("Key").Value;

  CosmosClientBuilder clientBuilder = new CosmosClientBuilder(account, key);
  CosmosClient client = clientBuilder
                      .WithConnectionModeDirect()
                      .Build();
  CosmosDbService cosmosDbService = new CosmosDbService(client, databaseName, containerName);
  DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
  await database.Database.CreateContainerIfNotExistsAsync(containerName, "/id");

  return cosmosDbService;
}


Also in Startup.cs, add the following code to the ConfigureServices method in order to create a singleton object representing the service:

services.AddSingleton<ICosmosDbService>(InitializeCosmosClientInstanceAsync(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());


Students controller


Let us now create a controller with methods that enable listing, add, edit, and display of student data. In the Controllers folder add a file named StudentsController.cs with the following code:

public class StudentsController : Controller {
  private readonly ICosmosDbService _cosmosDbService;

  public StudentsController(ICosmosDbService cosmosDbService) {
    _cosmosDbService = cosmosDbService;
  }

  [ActionName("Index")]
  public async Task<IActionResult> Index() {
    return View(await _cosmosDbService.GetStudentsAsync("SELECT * FROM c"));
  }

  [ActionName("Create")]
  public IActionResult Create() {
    return View();
  }

  [HttpPost]
  [ActionName("Create")]
  [ValidateAntiForgeryToken]
  public async Task<ActionResult> CreateAsync(Student student) {
    if (ModelState.IsValid) {
      student.Id = Guid.NewGuid().ToString();
      await _cosmosDbService.AddStudentAsync(student);
      return RedirectToAction("Index");
    }

    return View(student);
  }

  [ActionName("Edit")]
  public async Task<ActionResult> EditAsync(string id) {
    if (id == null) return BadRequest();

    Student student = await _cosmosDbService.GetStudentAsync(id);
    if (student == null) return NotFound();

    return View(student);
  }

  [HttpPost]
  [ActionName("Edit")]
  [ValidateAntiForgeryToken]
  public async Task<ActionResult> EditAsync(Student student) {
    if (ModelState.IsValid) {
      await _cosmosDbService.UpdateStudentAsync(student.Id, student);
      return RedirectToAction("Index");
    }

    return View(student);
  }

  [ActionName("Delete")]
  public async Task<ActionResult> DeleteAsync(string id) {
    if (id == null) return BadRequest();

    Student student = await _cosmosDbService.GetStudentAsync(id);
    if (student == null) return NotFound();

    return View(student);
  }

  [HttpPost]
  [ActionName("Delete")]
  [ValidateAntiForgeryToken]
  public async Task<ActionResult> DeleteConfirmedAsync(string id) {
    await _cosmosDbService.DeleteStudentAsync(id);
    return RedirectToAction("Index");
  }

  [ActionName("Details")]
  public async Task<ActionResult> DetailsAsync(string id) {
    return View(await _cosmosDbService.GetStudentAsync(id));
  }
}

Views

Let us add a menu item on the layout that leads to the Students controller. Add the following line item to the ordered list in _Layout.cshtml:

<li class="nav-item">
  <a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a>
</li>


The next step is to add all the views necessary for listing, adding, editing, displaying and deleting data. 


Create folder Students under Views. In the Views/Students folder add files Index.cshtml, Create.cshtml, Edit.cshtml, Details.cshtml and Delete.cshtml. Below is the code for each of these files.


Index.cshtml


@model IEnumerable<CosmosEfWeb.Models.Student>
@{
  ViewData["Title"] = "List of students";
}

<h1>@ViewData["Title"]</h1>
<p>
  <a asp-action="Create" class="btn btn-success btn-sm">Create New Student</a>
</p>
<table class="table">
  <thead>
    <tr>
      <th>
        @Html.DisplayNameFor(model => model.FirstName)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.LastName)
      </th>
      <th>
        @Html.DisplayNameFor(model => model.School)
      </th>
      <th></th>
    </tr>
  </thead>
  <tbody>
@foreach (var item in Model) {
      <tr>
        <td>
          @Html.DisplayFor(modelItem => item.FirstName)
        </td>
        <td>
          @Html.DisplayFor(modelItem => item.LastName)
        </td>
        <td>
          @Html.DisplayFor(modelItem => item.School)
        </td>
        <td>
          @Html.ActionLink("Edit", "Edit", new { id=item.Id }, new { @class = "btn btn-primary btn-sm" }) |
          @Html.ActionLink("Details", "Details", new { id=item.Id }, new { @class = "btn btn-primary btn-sm" }) |
          @Html.ActionLink("Delete", "Delete", new { id=item.Id }, new { @class = "btn btn-primary btn-sm" })
        </td>
      </tr>
}
  </tbody>
</table>


Create.cshtml


@model CosmosEfWeb.Models.Student

@{
  ViewData["Title"] = "Create New Student";
}

<h1>@ViewData["Title"]</h1>
<hr />
<div class="row">
  <div class="col-md-4">
    <form asp-action="Create">
      <div asp-validation-summary="ModelOnly" class="text-danger"></div>

      <div class="form-group">
        <label asp-for="FirstName" class="control-label"></label>
        <input asp-for="FirstName" class="form-control" />
        <span asp-validation-for="FirstName" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="LastName" class="control-label"></label>
        <input asp-for="LastName" class="form-control" />
        <span asp-validation-for="LastName" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="School" class="control-label"></label>
        <input asp-for="School" class="form-control" />
        <span asp-validation-for="School" class="text-danger"></span>
      </div>
      <div class="form-group">
        <input type="submit" value="Create" class="btn btn-primary" />
      </div>
    </form>
  </div>
</div>

<div>
  <a asp-action="Index">Back to List</a>
</div>

@section Scripts {
    @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}


Edit.cshtml


@model CosmosEfWeb.Models.Student
@{
  ViewData["Title"] = "Edit Student";
}

<h1>@ViewData["Title"]</h1>
<hr />
<div class="row">
  <div class="col-md-4">
    <form asp-action="Edit">
      <div asp-validation-summary="ModelOnly" class="text-danger"></div>
      <input type="hidden" asp-for="Id" />

      <div class="form-group">
        <label asp-for="FirstName" class="control-label"></label>
        <input asp-for="FirstName" class="form-control" />
        <span asp-validation-for="FirstName" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="LastName" class="control-label"></label>
        <input asp-for="LastName" class="form-control" />
        <span asp-validation-for="LastName" class="text-danger"></span>
      </div>
      <div class="form-group">
        <label asp-for="School" class="control-label"></label>
        <input asp-for="School" class="form-control" />
        <span asp-validation-for="School" class="text-danger"></span>
      </div>
      <div class="form-group">
        <input type="submit" value="Save" class="btn btn-primary btn-sm" /> |
        <a asp-action="Index" class="btn btn-primary btn-sm">Back to List</a>
      </div>
    </form>
  </div>
</div>

@section Scripts {
  @{await Html.RenderPartialAsync("_ValidationScriptsPartial");}
}


Details.cshtml


@model CosmosEfWeb.Models.Student

@{
  ViewData["Title"] = "Student Details";
}

<h1>@ViewData["Title"]</h1>
<div>
  <hr />
  <dl class="row">
    <dt class = "col-sm-2">
      @Html.DisplayNameFor(model => model.FirstName)
    </dt>
    <dd class = "col-sm-10">
      @Html.DisplayFor(model => model.FirstName)
    </dd>
    <dt class = "col-sm-2">
      @Html.DisplayNameFor(model => model.LastName)
    </dt>
    <dd class = "col-sm-10">
      @Html.DisplayFor(model => model.LastName)
    </dd>
    <dt class = "col-sm-2">
      @Html.DisplayNameFor(model => model.School)
    </dt>
    <dd class = "col-sm-10">
          @Html.DisplayFor(model => model.School)
    </dd>
  </dl>
</div>
<div>
  @Html.ActionLink("Edit", "Edit", new { id = Model.Id }, new { @class = "btn btn-primary btn-sm" }) |
  <a asp-action="Index" class="btn btn-primary btn-sm">Back to List</a>
</div>


Delete.cshtml


@model CosmosEfWeb.Models.Student
@{
  ViewData["Title"] = "Delete Student";
}

<h1>@ViewData["Title"]</h1>
<h3>Are you sure you want to delete this student?</h3>
<div>
  <hr />
  <dl class="row">
    <dt class = "col-sm-2">
      @Html.DisplayNameFor(model => model.FirstName)
    </dt>
    <dd class = "col-sm-10">
      @Html.DisplayFor(model => model.FirstName)
    </dd>
    <dt class = "col-sm-2">
      @Html.DisplayNameFor(model => model.LastName)
    </dt>
    <dd class = "col-sm-10">
      @Html.DisplayFor(model => model.LastName)
    </dd>
    <dt class = "col-sm-2">
      @Html.DisplayNameFor(model => model.School)
    </dt>
    <dd class = "col-sm-10">
          @Html.DisplayFor(model => model.School)
      </dd>
  </dl>
 
  <form asp-action="Delete">
    <input type="submit" value="Delete" class="btn btn-danger btn-sm" /> |
    <a asp-action="Index" class="btn btn-primary btn-sm">Back to List</a>
  </form>
</div>


Run application


We have not yet run our application. I am sure you are very curious about the fruits of your very hard work. 

From a terminal window, run the following command:

dotnet run

To view the application, point your browser to http://localhost:5000 or https://localhost:5001. You will see a home page that looks like this:




Click on Students on the top menu bar. Click on green "Create New Student" button on the next page:


Next add some data pertaining to a student in the "Create New Student" form:




When I added Bob Fox in the school of Wildlife, I was redirected to the Index.cshtml page with the following list:


Below are the Edit, Details and Delete pages that should work as expected.







I hope you use this tutorial as a launching pad for much more useful applications that take advantage of the many good features that Azure Cosmos DB has to offer.