How to call the Azure Cosmos DB REST API with C#
azure api
Authentication, request parameters and a few catches to not forget when calling the Cosmos DB API from C#.
June 23, 2022
A short one for me to remember. Cosmos DB, as many other Azure services, provides a REST API which allows developers to access data directly, without relying on the SDK or special tools. I wouldn't recommend using it regularly in a production application, but it can be handy for quick experiments and PoC validations.
In my case I wanted to generate a set of documents (10 000) to validate that my application performs as expected.
Authentication
As usual, authentication is the most complex part of the process. For the Cosmos API you need to build a signature, which is a compilation of several pieces of information. Fortunately, the format is well described, along with a helper method to generate it:
string GenerateMasterKeyAuthorizationSignature(HttpMethod verb, string resourceType, string resourceLink, string date, string key)
{
var keyType = "master";
var tokenVersion = "1.0";
var payload = $"{verb.ToString().ToLowerInvariant()}\n{resourceType.ToLowerInvariant()}\n{resourceLink}\n{date.ToLowerInvariant()}\n\n";
var hmacSha256 = new System.Security.Cryptography.HMACSHA256 { Key = Convert.FromBase64String(key) };
var hashPayload = hmacSha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(payload));
var signature = Convert.ToBase64String(hashPayload);
var authSet = WebUtility.UrlEncode($"type={keyType}&ver={tokenVersion}&sig={signature}");
return authSet;
}
date
needs to be in a specific format that can be obtained with:var date = DateTime.UtcNow.ToString("R");
.resourceType
is the type of resource we target (most oftendocs
orcolls
).resourceLink
represents the target resource, which depends on what we're trying to do and whatresourceType
is.- When creating a new document,
resourceType
is "docs" andresourceLink
ends at collection level like this: "dbs/mydatabase/colls/mycollection". - When patching an existing document,
resourceType
is "docs" andresourceLink
ends at a specific document like this: "dbs/mydatabase/colls/mycollection/docs/mydocumentid". - etc.
- When creating a new document,
Once the signature is generated, it is used in HTTP headers as Authorization
:
// hc is HttpClient
hc.DefaultRequestHeaders.Add("Authorization", sig);
Calling the API
Actual API calls require a bunch of headers to be present:
// The same date must be used for the signature and request header.
var date = DateTime.UtcNow.ToString("R");
// Signature for document creation (POST).
var sig = GenerateMasterKeyAuthorizationSignature(HttpMethod.Post, "docs", $"dbs/mydatabase/colls/mycollection", date, "mycosmoskey");
var hc = new HttpClient();
hc.BaseAddress = new Uri("https://mycosmosaccount.documents.azure.com");
hc.DefaultRequestHeaders.Add("x-ms-documentdb-partitionkey", "[mypartitionkeyvalue]");
hc.DefaultRequestHeaders.Add("x-ms-version", "2018-12-31");
hc.DefaultRequestHeaders.Add("x-ms-date", date);
hc.DefaultRequestHeaders.Add("Authorization", sig);
- The same
date
is used for both the signature and the request header. x-ms-documentdb-partitionkey
is required and must be an array.x-ms-version
is not random. There's a list of supported versions.
And then the request itself, aimed at the /docs
resource:
// 'myObject' is a POCO created earlier
var content = new StringContent(JsonSerializer.Serialize(myObject), Encoding.UTF8, "application/json");
var resp = await hc.PostAsync($"/dbs/mydatabase/colls/mycollection/docs", content);
One thing I realized when running a long batch of requests is that the signature expires after 15 minutes. Quick and easy solution is to check for the HTTP response code and recreate the signature if needed:
if (!resp.IsSuccessStatusCode)
{
if (resp.StatusCode == HttpStatusCode.Forbidden)
{
Console.WriteLine("Token expired, generating new one...");
date = DateTime.UtcNow.ToString("R");
sig = GenerateMasterKeyAuthorizationSignature(HttpMethod.Post, "docs", $"dbs/mydatabase/colls/mycollection", date, "mycosmoskey");
hc.DefaultRequestHeaders.Remove("x-ms-date");
hc.DefaultRequestHeaders.Remove("Authorization");
hc.DefaultRequestHeaders.Add("x-ms-date", date);
hc.DefaultRequestHeaders.Add("Authorization", sig);
}
}
Reference
Feedback
Found something inaccurate or plain wrong? Was this content helpful to you? Let me know!
š§ codez@deedx.cz