Tutorial: Develop an ASP.NET web application with Azure Cosmos DB for NoSQL

APPLIES TO: NoSQL

The Azure SDK for .NET allows you to query data in an API for NoSQL container using LINQ in C# or a SQL query string. This tutorial will walk through the process of updating an existing ASP.NET web application that uses placeholder data to instead query from the API.

In this tutorial, you learn how to:

  • Create and populate a database and container using API for NoSQL
  • Create an ASP.NET web application from a template
  • Query data from the API for NoSQL container using the Azure SDK for .NET

Prerequisites

Create API for NoSQL resources

First, you'll create a database and container in the existing API for NoSQL account. You'll then populate this account with data using the cosmicworks dotnet tool.

  1. Navigate to your existing API for NoSQL account in the Azure portal.

  2. In the resource menu, select Keys.

    Screenshot of an API for NoSQL account page. The Keys option is highlighted in the resource menu.

  3. On the Keys page, observe and record the value of the URI, PRIMARY KEY, and PRIMARY CONNECTION STRING* fields. These values will be used throughout the tutorial.

    Screenshot of the Keys page with the URI, Primary Key, and Primary Connection String fields highlighted.

  4. In the resource menu, select Data Explorer.

    Screenshot of the Data Explorer option highlighted in the resource menu.

  5. On the Data Explorer page, select the New Container option in the command bar.

    Screenshot of the New Container option in the Data Explorer command bar.

  6. In the New Container dialog, create a new container with the following settings:

    Setting Value
    Database id cosmicworks
    Database throughput type Manual
    Database throughput amount 4000
    Container id products
    Partition key /categoryId

    Screenshot of the New Container dialog in the Data Explorer with various values in each field.

    Important

    In this tutorial, we will first scale the database up to 4,000 RU/s in shared throughput to maximize performance for the data migration. Once the data migration is complete, we will scale down to 400 RU/s of provisioned throughput.

  7. Select OK to create the database and container.

  8. Open a terminal to run commands to populate the container with data.

    Tip

    You can optionally use the Azure Cloud Shell here.

  9. Install a pre-releaseversion of the cosmicworks dotnet tool from NuGet.

    dotnet tool install --global cosmicworks  --prerelease
    
  10. Use the cosmicworks tool to populate your API for NoSQL account with sample product data using the URI and PRIMARY KEY values you recorded earlier in this lab. Those recorded values will be used for the endpoint and key parameters respectively.

    cosmicworks \
        --datasets product \
        --endpoint <uri> \
        --key <primary-key>
    
  11. Observe the output from the command line tool. It should add more than 200 items to the container. The example output included is truncated for brevity.

    ...
    Revision:       v4
    Datasets:
            product
    
    Database:       [cosmicworks]   Status: Created
    Container:      [products]      Status: Ready
    
    product Items Count:    295
    Entity: [9363838B-2D13-48E8-986D-C9625BE5AB26]  Container:products      Status: RanToCompletion
    ...
    Container:      [product]       Status: Populated
    
  12. Return to the Data Explorer page for your account.

  13. In the Data section, expand the cosmicworks database node and then select Scale.

    Screenshot of the Scale option within the database node.

  14. Reduce the throughput from 4,000 down to 400.

    Screenshot of the throughput settings for the database reduced down to 400 RU/s.

  15. In the command bar, select Save.

    Screenshot of the Save option in the Data Explorer command bar.

  16. In the Data section, expand and select the products container node.

    Screenshot of the expanded container node within the database node.

  17. In the command bar, select New SQL query.

    Screenshot of the New SQL Query option in the Data Explorer command bar.

  18. In the query editor, add this SQL query string.

    SELECT
      p.sku,
      p.price
    FROM products p
    WHERE p.price < 2000
    ORDER BY p.price DESC
    
  19. Select Execute Query to run the query and observe the results.

    Screenshot of the Execute Query option in the Data Explorer command bar.

  20. The results should be a paginated array of all items in the container with a price value that is less than 2,000 sorted from highest price to lowest. For brevity, a subset of the output is included here.

    [
      {
        "sku": "BK-R79Y-48",
        "price": 1700.99
      },
      ...
      {
        "sku": "FR-M94B-46",
        "price": 1349.6
      },
    ...
    
  21. Replace the content of the query editor with this query and then select Execute Query again to observe the results.

    SELECT
      p.name,
      p.categoryName,
      p.tags
    FROM products p
    JOIN t IN p.tags
    WHERE t.name = "Tag-32"
    
  22. The results should be a smaller array of items filtered to only contain items that include at least one tag with a name value of Tag-32. Again, a subset of the output is included here for brevity.

    ...
    {
    "name": "ML Mountain Frame - Black, 44",
    "categoryName": "Components, Mountain Frames",
    "tags": [
        {
        "id": "18AC309F-F81C-4234-A752-5DDD2BEAEE83",
        "name": "Tag-32"
        }
    ]
    },
    ...
    

Create ASP.NET web application

Now, you'll create a new ASP.NET web application using a sample project template. You'll then explore the source code and run the sample to get acquainted with the application before adding Azure Cosmos DB connectivity using the Azure SDK for .NET.

Important

This tutorial transparently pulls packages from NuGet. You can use dotnet nuget list source to verify your package sources. If you do not have NuGet as a package source, use dotnet nuget add source to install the site as a source.

  1. Open a terminal in an empty directory.

  2. Install the cosmicworks.template.web project template package from NuGet.

    dotnet new install cosmicworks.template.web
    
  3. Create a new web application project using the newly installed dotnet new cosmosdbnosql-webapp template.

    dotnet new cosmosdbnosql-webapp
    
  4. Build and run the web application project.

    dotnet run
    
  5. Observe the output of the run command. The output should include a list of ports and URLs where the application is running.

    ...
    info: Microsoft.Hosting.Lifetime[14]
          Now listening on: http://localhost:5000
    info: Microsoft.Hosting.Lifetime[14]
          Now listening on: https://localhost:5001
    info: Microsoft.Hosting.Lifetime[0]
          Application started. Press Ctrl+C to shut down.
    info: Microsoft.Hosting.Lifetime[0]
          Hosting environment: Production
    ...
    
  6. Open a new browser and navigate to the running web application. Observe all three pages of the running application.

    Screenshot of the sample web application running with placeholder data.

  7. Stop the running application by terminating the running process.

    Tip

    Use the Ctrl+C command to stop a running process.Alternatively, you can close and re-open the terminal.

  8. Open Visual Studio Code using the current project folder as the workspace.

    Tip

    You can run code . in the terminal to open Visual Studio Code and automatically open the working directory as the current workspace.

  9. Navigate to and open the Services/ICosmosService.cs file. Observe the RetrieveActiveProductsAsync and RetrieveAllProductsAsync default method implementations. These methods create a static list of products to use when running the project for the first time. A truncated example of one of the methods is provided here.

    public async Task<IEnumerable<Product>> RetrieveActiveProductsAsync()
    {
        await Task.Delay(1);
    
        return new List<Product>()
        {
            new Product(id: "baaa4d2d-5ebe-45fb-9a5c-d06876f408e0", categoryId: "3E4CEACD-D007-46EB-82D7-31F6141752B2", categoryName: "Components, Road Frames", sku: "FR-R72R-60", name: """ML Road Frame - Red, 60""", description: """The product called "ML Road Frame - Red, 60".""", price: 594.83000000000004m),
           ...
            new Product(id: "d5928182-0307-4bf9-8624-316b9720c58c", categoryId: "AA5A82D4-914C-4132-8C08-E7B75DCE3428", categoryName: "Components, Cranksets", sku: "CS-6583", name: """ML Crankset""", description: """The product called "ML Crankset".""", price: 256.49000000000001m)
        };
    }
    
  10. Navigate to and open the Services/CosmosService.cs file. Observe the current implementation of the CosmosService class. This class implements the ICosmosService interface but doesn't override any methods. In this context, the class will use the default interface implementation until an override of the implementation is provided in the interface.

    public class CosmosService : ICosmosService
    { }
    
  11. Finally, navigate to and open the Models/Product.cs file. Observe the record type defined in this file. This type will be used in queries throughout this tutorial.

    public record Product(
        string id,
        string categoryId,
        string categoryName,
        string sku,
        string name,
        string description,
        decimal price
    );
    

Query data using the .NET SDK

Next, you'll add the Azure SDK for .NET to this sample project and use the library to query data from the API for NoSQL container.

  1. Back in the terminal, add the Microsoft.Azure.Cosmos package from NuGet.

    dotnet add package Microsoft.Azure.Cosmos
    
  2. Build the project.

    dotnet build
    
  3. Back in Visual Studio Code, navigate again to the Services/CosmosService.cs file.

  4. Add a new using directive for the Microsoft.Azure.Cosmos and Microsoft.Azure.Cosmos.Linq namespaces.

    using Microsoft.Azure.Cosmos;
    using Microsoft.Azure.Cosmos.Linq;
    
  5. Within the CosmosService class, add a new private readonly member of type CosmosClient named _client.

    private readonly CosmosClient _client;
    
  6. Create a new empty constructor for the CosmosService class.

    public CosmosService()
    { }
    
  7. Within the constructor, create a new instance of the CosmosClient class passing in a string parameter with the PRIMARY CONNECTION STRING value you previously recorded in the lab. Store this new instance in the _client member.

    public CosmosService()
    { 
        _client = new CosmosClient(
            connectionString: "<primary-connection-string>"
        );
    }
    
  8. Back within the CosmosService class, create a new private property of type Container named container. Set the get accessor to return the cosmicworks database and products container.

    private Container container
    {
        get => _client.GetDatabase("cosmicworks").GetContainer("products");
    }
    
  9. Create a new asynchronous method named RetrieveAllProductsAsync that returns an IEnumerable<Product>.

    public async Task<IEnumerable<Product>> RetrieveAllProductsAsync()
    { }
    
  10. For the next steps, add this code within the RetrieveAllProductsAsync method.

    1. Use the GetItemLinqQueryable<> generic method to get an object of type IQueryable<> that you can use to construct a Language-integrated query (LINQ). Store that object in a variable named queryable.

      var queryable = container.GetItemLinqQueryable<Product>();
      
    2. Construct a LINQ query using the Where and OrderByDescending extension methods. Use the ToFeedIterator extension method to create an iterator to get data from Azure Cosmos DB and store the iterator in a variable named feed. Wrap this entire expression in a using statement to dispose the iterator later.

      using FeedIterator<Product> feed = queryable
          .Where(p => p.price < 2000m)
          .OrderByDescending(p => p.price)
          .ToFeedIterator();
      
    3. Create a new variable named results using the generic List<> type.

      List<Product> results = new();
      
    4. Create a while loop that will iterate until the HasMoreResults property of the feed variable returns false. This loop will ensure that you loop through all pages of server-side results.

      while (feed.HasMoreResults)
      { }
      
    5. Within the while loop, asynchronously call the ReadNextAsync method of the feed variable and store the result in a variable named response.

      while (feed.HasMoreResults)
      {
          var response = await feed.ReadNextAsync();
      }
      
    6. Still within the while loop, use a foreach loop to go through each item in the response and add them to the results list.

      while (feed.HasMoreResults)
      {
          var response = await feed.ReadNextAsync();
          foreach (Product item in response)
          {
              results.Add(item);
          }
      }
      
    7. Return the results list as the output of the RetrieveAllProductsAsync method.

      return results;
      
  11. Create a new asynchronous method named RetrieveActiveProductsAsync that returns an IEnumerable<Product>.

    public async Task<IEnumerable<Product>> RetrieveActiveProductsAsync()
    { }
    
  12. For the next steps, add this code within the RetrieveActiveProductsAsync method.

    1. Create a new string named sql with a SQL query to retrieve multiple fields where a filter (@tagFilter) is applied to tags array of each item.

      string sql = """
      SELECT
          p.id,
          p.categoryId,
          p.categoryName,
          p.sku,
          p.name,
          p.description,
          p.price,
          p.tags
      FROM products p
      JOIN t IN p.tags
      WHERE t.name = @tagFilter
      """;
      
    2. Create a new QueryDefinition variable named query passing in the sql string as the only query parameter. Also, use the WithParameter fluid method to apply the value Tag-75 to the @tagFilter parameter.

      var query = new QueryDefinition(
          query: sql
      )
          .WithParameter("@tagFilter", "Tag-75");
      
    3. Use the GetItemQueryIterator<> generic method and the query variable to create an iterator that gets data from Azure Cosmos DB. Store the iterator in a variable named feed. Wrap this entire expression in a using statement to dispose the iterator later.

      using FeedIterator<Product> feed = container.GetItemQueryIterator<Product>(
          queryDefinition: query
      );
      
    4. Use a while loop to iterate through multiple pages of results and store the value in a generic List<> named results. Return the results as the output of the RetrieveActiveProductsAsync method.

      List<Product> results = new();
      
      while (feed.HasMoreResults)
      {
          FeedResponse<Product> response = await feed.ReadNextAsync();
          foreach (Product item in response)
          {
              results.Add(item);
          }
      }
      
      return results;
      
  13. Save the Services/CosmosClient.cs file.

    Tip

    If you are unsure that your code is correct, you can check your source code against the sample code on GitHub.

Validate the final application

Finally, you'll run the application with hot reloads enabled. Running the application will validate that your code can access data from the API for NoSQL.

  1. Back in the terminal, run the application.

    dotnet run
    
  2. The output of the run command should include a list of ports and URLs where the application is running. Open a new browser and navigate to the running web application. Observe all three pages of the running application. Each page should now include live data from Azure Cosmos DB.

Clean up resources

When no longer needed, delete the database used in this tutorial. To do so, navigate to the account page, select Data Explorer, select the cosmicworks database, and then select Delete.

Next steps

Now that you've created your first .NET web application using Azure Cosmos DB, you can now dive deeper into the SDK to import more data, perform complex queries, and manage your Azure Cosmos DB for NoSQL resources.