In this guide, learn how to use Azure Cosmos DB with Web Forms to create a powerful grid for your web apps.
Azure Cosmos DB and Grid in Web Forms - is this combination really possible?
Yes, absolutely. Azure Cosmos DB is the new kid in the block while Web Forms is the old veteran, but still the favorite web development technology of many people.
So, let’s start with building this grid.
This is a list of the content sections, so you can navigate easier:
Before we begin, here's a short summary defining Cosmos DB:
“Today’s applications are required to be highly responsive and always online. To achieve low latency and high availability, instances of these applications need to be deployed in datacenters that are close to their users.
...
Azure Cosmos DB is a globally distributed database service that's designed to provide low latency, elastic scalability of throughput, well-defined semantics for data consistency, and high availability. In short, if your application needs guaranteed fast response time anywhere in the world, if it's required to be always online, and needs unlimited and elastic scalability of throughput and storage, consider building applications by using Azure Cosmos DB.”
—MSDN: Global data distribution with Azure Cosmos DB - overview
In this sample I will use a nice option to configure an Azure Cosmos DB – its dedicated Emulator. You can use your existing collection from your Azure account or download the Emulator, which does not require an account.
Next step is to create the Container (Collection) and add some items. One of the fields you need to fill is called Partition Key and this setting is explained in more detail in the next section.
Once this is complete, your database emulator will now look like this:
In the Explorer tab you can now see any records you might have:
Are you familiar with the concept of Grouping data? Partitioning is similar where it uses the same values of a field to distribute the data into divisions.
It is useful for load balancing, performance, scalability, manageability, availability and similar abilities.
Let’s divide this in 2 sections (Reference):
By using partitioning, the items in a container are divided into distinct subsets, called logical partitions.
Example 1: If UserID
serves as the partition key for the items in a container, and there are 1000 unique UserID
values, 1000 logical partitions will be created for the container.
Example 2: If all the items contain a City property, then you can use City as the partition key for the container and specific values for the City such as, "London", "Paris", "NYC" etc. will form a distinct logical partition.
You don’t need to worry about these since Cosmos DB automatically manages the placement of logical partitions onto physical partitions (server infrastructure) depending on the load. Also, you can't control their size, placement, the count, or the mapping. What you can do, however, is to control the number of logical partitions and the distribution of data and throughput by choosing the right partition key using the suggestions from the next step.
Choosing a partition key is an important decision that will affect your application’s performance. You can consider the following best practices and details when choosing a partition key:
Click the image below to expand and see a possible case with hot partition:
Pro Tip: You can choose this key only initially - changing some properties of a collection like the ID or the partition key are not supported. Reference
I highly suggest that you check these videos before choosing the key:
I choose to use a Template Web Application provided by the Progress Telerik UI for ASP.NET AJAX Toolset. It is very similar to a standard ASP.NET Web Application Project so you are perfectly fine if you decide to use that instead. The next step would be to open the NuGet Manager and add the Microsoft Azure Cosmos DB Client Library.
Pro Tip: Make sure that you are using a corresponding version of the Azure Cosmos DB Library and the Emulator. A long-time discrepancy between these can lead to issues. To avoid this, you can use their latest version.
Now we will display our data in a grid control and make it visually appealing to the user’s eye. This can be achieved by using a standard asp:GridView or any other server-side grid control you prefer. I will be using the RadGrid – the most capable player in the Telerik AJAX league:
<telerik:RadGrid ID="RadGrid1" runat="server" AllowPaging="True" Width="800px"
OnNeedDataSource="RadGrid1_NeedDataSource" OnColumnCreated="RadGrid1_ColumnCreated"
AutoGenerateEditColumn="true" OnUpdateCommand="RadGrid1_UpdateCommand"
AutoGenerateDeleteColumn="true" OnDeleteCommand="RadGrid1_DeleteCommand"
OnInsertCommand="RadGrid1_InsertCommand">
<MasterTableView DataKeyNames="Id,Completed" CommandItemDisplay="Top">
</MasterTableView>
</telerik:RadGrid>
And this is the entire definition. The columns are generated automatically based on the DataType of the field. Complex functionalities like paging, filtering, grouping, sorting, aggregates and many more are also provided with a single property and without any additional coding on the developer’s part.
Yes, it is charming.
On the outside, it will look like the image below. If Bootstrap is not your thing, you can also choose from around 20 other built-in skins or create your custom one:
You can find the required assemblies to run this grid in the Download section.
To set up a valid connection to your database, you will need to use some key identifiers. I prefer to have them directly in the web.config:
<appSettings>
...
<add key="endpoint" value="https://localhost:8081/" />
<add key="authKey" value="C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" />
<add key="database" value="ToDoList" />
<add key="collection" value="Items" />
</appSettings>
Now, we can get a reference to the database in the code-behind:
private static readonly string DatabaseId = ConfigurationManager.AppSettings["database"];
private static readonly string CollectionId = ConfigurationManager.AppSettings["collection"];
private static DocumentClient client
{
get
{
return new DocumentClient(
new Uri(ConfigurationManager.AppSettings["endpoint"]),
ConfigurationManager.AppSettings["authKey"]);
}
}
For implementing additional methods like CreateDatabaseIfNotExistsAsync and CreateCollectionIfNotExistsAsync, you can check the DocumentDBRepository.cs file and its GitHub code.
Once you have a valid connection and access to the database, you can now extract the records from the Collection and bind the grid. Usually, grids can be bound using their DataSource property and the DataBind() method. Since we are using RadGrid in this case, we can make avail of the ultimate NeedDataSource event handler of the grid:
protected void RadGrid1_NeedDataSource(object sender, GridNeedDataSourceEventArgs e)
{
IQueryable<Item> source = client.CreateDocumentQuery<Item>(
UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId));
RadGrid1.DataSource = source;
}
The amazing thing about it is that you don’t have to worry about actions like paging, filtering, sorting, grouping, their corresponding events, and when to rebind the grid. The NeedDataSource event does everything automatically under the hood and all that is left for you is to lean back and enjoy the functionality.
Inserting is the most straightforward operation to modify the database. The process simply includes extracting the new values and adding a new record (Document) to the data source:
protected void RadGrid1_InsertCommand(object sender, GridCommandEventArgs e)
{
GridEditableItem item = e.Item as GridEditableItem;
Hashtable newValues = new Hashtable();
item.ExtractValues(newValues);
Item newItem = new Item()
{
Name = (string)newValues["Name"],
Description = (string)newValues["Description"],
Completed = (bool)newValues["Completed"]
};
client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(
DatabaseId, CollectionId), newItem).Wait();
}
For create and update operations on documents, the partition key is optional. When absent, the client library will extract the partition key from the document before sending the request to the server. Reference
Usually deleting a record (Document) requires only its unique data key value to remove it from the database. In this case the partition key part is also required so the Azure Cosmos DB knows in which bucket the item is located:
protected void RadGrid1_DeleteCommand(object sender, GridCommandEventArgs e)
{
GridDataItem item = (GridDataItem)e.Item;
string dataKeyID = item.GetDataKeyValue("Id").ToString();
bool partitionKeyID = (bool)item.GetDataKeyValue("Completed");
RequestOptions requestOptions = new RequestOptions()
{
PartitionKey = new PartitionKey(partitionKeyID)
};
client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(
DatabaseId, CollectionId, dataKeyID), requestOptions).Wait();
}
Now for updating, I will separate the section into 2 parts:
protected void RadGrid1_UpdateCommand(object sender, GridCommandEventArgs e)
{
GridEditableItem item = (GridEditableItem)e.Item;
string dataKeyID = item.GetDataKeyValue("Id").ToString();
bool partitionKeyID = (bool)item.GetDataKeyValue("Completed");
bool newPartitionKeyID = ((CheckBox)item["Completed"].Controls[0]).Checked;
Hashtable newValues = new Hashtable();
item.ExtractValues(newValues);
Item updatedItem = new Item()
{
Name = (string)newValues["Name"],
Description = (string)newValues["Description"],
Completed = (bool)newValues["Completed"]
};
if (partitionKeyID != newPartitionKeyID)
{
RequestOptions requestOptions = new RequestOptions()
{
PartitionKey = new PartitionKey(partitionKeyID)
};
client.DeleteDocumentAsync(UriFactory.CreateDocumentUri(DatabaseId, CollectionId, dataKeyID), requestOptions).Wait();
client.CreateDocumentAsync(UriFactory.CreateDocumentCollectionUri(DatabaseId, CollectionId), updatedItem).Wait();
}
else
{
updatedItem.Id = dataKeyID;
client.ReplaceDocumentAsync(UriFactory.CreateDocumentUri(
DatabaseId, CollectionId, dataKeyID), updatedItem).Wait();
}
}
Now comes the sweet part – you have the sample ready to download and run. All you need to change is the configuration of your own Azure keys as demonstrated in the Access the Database step. If you want to play some more with the Telerik tools, you can use the assemblies in the Bin folder of the project or get the installer from the Telerik Download page.
Eyup Yusein is a Technical Support Officer working on the Progress Telerik web developer tools. His main field of strength is handling various implementations with RadGrid. He likes to discover simple and unconventional explanations for complicated matters and to learn about the newest technological achievements. He also dearly enjoys spending time with his family.