July 27th, 2019

Integrating Cosmos DB with OData (Part 1)

Hassan Habib
Sr. Software Engineering Manager

We talked in previous articles about the pluggability of OData with any storage technology regardless of its schema, whether it’s a SQL-based storage, NoSQL, In-Memory or simply a file on a hard-drive.

This power of OData enables developers to work with powerful, planet-scale storage technologies such as Cosmos DB.

In this article we are going to deep dive into one of three ways you can integrate Cosmos DB with OData but before we start, let’s talk a little bit about Cosmos DB, it’s capabilities and why it’s important to be able to expose that storage powerful through an API with OData.

 

What is Cosmos DB?

Azure Cosmos DB is Microsoft’s globally distributed, multi-model database service. It enables you to elastically and independently scale throughput and storage across any number of Azure regions worldwide. You can elastically scale throughput and storage, and take advantage of fast, single-digit-millisecond data access using your favorite API including SQL, MongoDB, Cassandra, Tables, or Gremlin.

Cosmos DB is a perfect solution for the hot category of storage, specifically data that needs to be accessed frequently and be retrieved as fast as possible.

 

Cosmos DB with OData

When planning to integrate Cosmos DB with OData, there are three different ways you can follow to accomplish that level of integration based on your preference and whichever model fits the application you’re developing.

If you don’t have any specific preference when it comes to integrating Cosmos DB with OData, I highly recommend you try them all, and judge for yourself which approach is easier and best fits your needs.

 

Let’s talk about the first of these approaches in this article.

 

First Approach: Pre-Built ASP.NET Core Solution

Cosmos DB in Azure Portal comes with some amazing options that enable you to download a full solution with full CRUD operations and UI to interact with Cosmos DB.

In order for you to do that, go to your Azure portal and create a new Cosmos DB resource as follows:

 

Setting up Cosmos DB

  1. Click on “Create a resource” button at the top left corner in your Azure Portal:

  1. Search for Azure Cosmos DB, then click “Create” button.

  1. When you click the “Create” button, you will be presented with a bunch of options to select which API you’d like to choose to interact with Cosmos DB – for the first and second approach we are going to select “Core (SQL)” as our option as follows:

You might need to create a new resource group and select an account name – for this demo I have named my account studentsdb.

Make sure your account name is all lowercase.

After that’s done, click “Review + create” button to create your new Cosmos DB instance.

The creation process of a Cosmos DB instance might take between 1 – 5 minutes depending on what type of configuration you’ve chosen to build your instance.

  1. When the creation is completed – go to the quick start option on the left side menu, you will be presented with the following options to work with Cosmos DB.
  2. Select “.NET Core” option then click the “Create ‘Items’ Contains” button – it will create a sample container for you to work with.
  3. Once the items container is created, click the “Download” button to download a full ASP.NET Core MVC solution for full CRUD operations to interact with the sample container you just created.
  4. Now that you have downloaded the solution, build the solution, then try to run the solution to make sure all the configurations are set correctly, all dependencies are downloaded and that you can actually interact with the container in your Cosmos DB instance – in this demo I’m going to add a couple of records in the ToDoList. (Note: you might be prompted by Visual Studio 2019 that you’re opening a solution downloaded from the internet – since Azure portal is a trusted source go ahead and click OK)

To verify that the records you’ve created have been successfully persisted, you can go back to your Azure portal and click “Open Data Explorer” button to check the records you created.

The following screenshots show you the data navigation and retrieval on the data explorer window in Azure:

This completes and verifies the setup portion of this first approach.

Now, let’s go ahead an utilize the CRUD operations code to build a new API and integrate OData.

You will notice that the solution we downloaded from Azure Portal implements the Repository pattern, which offer all CRUD operations needed to run an API.

For this demo, we will focus on the following code in ItemController.cs file:

        private readonly IDocumentDBRepository<todo.Models.Item> Respository;
        public ItemController(IDocumentDBRepository<todo.Models.Item> Respository)
        {
            this.Respository = Respository;
        }

        [ActionName("Index")]
        public async Task<IActionResult> Index()
        {
            var items = await Respository.GetItemsAsync(d => !d.Completed);
            return View(items);
        }

We will create an API controller let’s call it ItemsController.cs and use the exact same code snippet above to build an endpoint that returns all items from Cosmos DB in JSON format – our new controller would look something like this:

using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using todo;
using todo.Models;

namespace quickstartcore.Controllers
{
    [Produces("application/json")]
    [Route("api/Items")]
    public class ItemsController : Controller
    {
        private readonly IDocumentDBRepository<Item> Respository;
        public ItemsController(IDocumentDBRepository<Item> Respository)
        {
            this.Respository = Respository;
        }

        // GET: api/Items
        [HttpGet]
        public async Task<IEnumerable<Item>> Get()
        {
            return await Respository.GetItemsAsync(d => !d.Completed);
        }
    }
}

Let’s verify our endpoint is functional by hitting the endpoint in the browser or using a tool like Postman, your response for hitting an endpoint:

http://localhost:1234/api/items

Your response should look something like this:

[
    {
        "id": "3f0beb77-dd24-4b51-a921-6a34c60e7b4b",
        "name": "Brush your teeth",
        "description": "brush your teeth tonight",
        "isComplete": false
    },
    {
        "id": "739c3cef-58b6-48a6-ad8f-21b485bb326f",
        "name": "Organize Office",
        "description": "Organize your office",
        "isComplete": false
    }
]

 

OData Integration

Now that we have implemented and verified our controller endpoint is functional, let’s do the exact same steps we’ve done in this article to implement OData in our solution. I will summarize these steps here as follows:

  1. Add a Nuget package Microsoft.AspNetCore.OData to your solution.
  2. Add OData service in your startup.cs file, then enable dependency injection along with the functions you want OData to provide through your API, here’s an example of how your file should look:
    using System.Linq;
    using Microsoft.AspNet.OData.Extensions;
    using Microsoft.AspNetCore.Builder;
    using Microsoft.AspNetCore.Hosting;
    using Microsoft.Extensions.Configuration;
    using Microsoft.Extensions.DependencyInjection;
    using Microsoft.Extensions.Logging;
    using todo;
    
    namespace quickstartcore
    {
        public class Startup
        {
            public Startup(IHostingEnvironment env)
            {
                var builder = new ConfigurationBuilder()
                    .SetBasePath(env.ContentRootPath)
                    .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
                    .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                    .AddEnvironmentVariables();
                Configuration = builder.Build();
            }
    
            public IConfigurationRoot Configuration { get; }
            public void ConfigureServices(IServiceCollection services)
            {
                // Add framework services.
                services.AddMvc();
    
                services.AddSingleton<IDocumentDBRepository<todo.Models.Item>>(new DocumentDBRepository<todo.Models.Item>());
                services.AddOData();
            }
    
            public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
            {
                loggerFactory.AddConsole(Configuration.GetSection("Logging"));
                loggerFactory.AddDebug();
    
                if (env.IsDevelopment())
                {
                    app.UseDeveloperExceptionPage();
                    app.UseBrowserLink();
                }
                else
                {
                    app.UseExceptionHandler("/Home/Error");
                }
    
                app.UseStaticFiles();
    
                app.UseMvc(routes =>
                {
                    routes.MapRoute(
                        name: "default",
                        template: "{controller=Item}/{action=Index}/{id?}");
    
                    routes.EnableDependencyInjection();
                    routes.Select().Filter().OrderBy().Expand();
                });
            }
        }
    }
  3. Let’s update our controller (ItemsController.cs) to enable OData query – your controller file should look as follows:
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using Microsoft.AspNet.OData;
    using Microsoft.AspNetCore.Mvc;
    using todo;
    using todo.Models;
    
    namespace quickstartcore.Controllers
    {
        [Produces("application/json")]
        [Route("api/Items")]
        public class ItemsController : Controller
        {
            private readonly IDocumentDBRepository<Item> Respository;
            public ItemsController(IDocumentDBRepository<Item> Respository)
            {
                this.Respository = Respository;
            }
    
            // GET: api/Items
            [HttpGet]
            [EnableQuery()]
            public async Task<IEnumerable<Item>> Get()
            {
                return await Respository.GetItemsAsync(d => !d.Completed);
            }
        }
    }
    
  4. Let’s verify our new endpoint with OData integration by hitting an endpoint like:
    http://localhost:36442/api/items?$select=name

    And your response should look as follows:

    [
        {
            "name": "Brush your teeth"
        },
        {
            "name": "Organize Office"
        }
    ]

     

By seeing this result, we verified OData is functional on your API retrieving and processing data from your Cosmos DB instance container. In the next article, we will discuss working with OData & Cosmos DB using the EntityFramework in ASP.NET Core API.

Final Notes

  1. You can download the solution I used to run this demo from here (Don’t forget to update the endpoint & key values in DocumentDBRepository.cs file.
  2. The current demo uses ASP.NET Core 2.2 example, but there are so many other options including classic ASP.NET MVC applications that I encourage you to try out.
  3. The current demo uses Item as a model, you will need to modify the solution slightly to work with whatever model you want to run your application.
  4. I highly recommend following this link to learn more about Cosmos DB and it’s capabilities.
  5. Huge thanks to Cosmos DB team for providing such a great documentation around such powerful technology.
  6. I also highly recommend staying up-to-date with all the new updates in Cosmos DB by following this link to their blog.

 

Category
ODataWebAPI

Author

Hassan Habib
Sr. Software Engineering Manager

I'm a software engineer at Microsoft with over 21 years of experience building mobile, web and enterprise applications. I mastered technology to make people's lives better, one line of code at a time.

3 comments

Discussion is closed. Login to edit/delete existing comments.

  • Ryan CrawCourMicrosoft employee

    Looking forward to parts II and III of this series. So far it’s great.

  • Matt Johnson

    I notice your action method is returning the type IEnumerable. If the user had requested additional query options with $filter then would those have ran client side instead of on the cosmos db cluster?

    • Hassan HabibMicrosoft employee Author

      Good point Matt, IEnumerable works great with smaller data sets since it's running in memory - but in order to run the query on Cosmos DB cluster IQueryable might be a better option.
      This can be accomplished by either returning an IQueryable or passing OData filter query through the predicate input parameter in the Respository.GetItemsAsync(predicate);
      An even easier way to pass in OData filter queries is what I'm going to discuss in the second part...

      Read more