1
votes

I am evaluating DocumentDb and attempting to submit a query via the DocumentDb Rest api.

When I attempt to post the query, I'm receiving the following error messages:

"The input content is invalid because the required properties - 'id; ' - are missing"
"The request payload is invalid. Ensure to provide a valid request payload."

this seems to indicate that the documents in the collection do not have an id property, but here's my current test data that I retrieved from the azure portal which you can see have an id property:

[
  {
    "id": "c1058415-8e03-49e2-8c41-97bc902ebfb0",
    "name": "Add a thing",
    "description": "Ad a thing to the list"
  },
  {
    "id": "0f88f4af-7afc-4928-a60f-c3546b28e243",
    "name": "find another thing",
    "description": "find another thing"
  },
  {
    "id": "b669dbc6-6056-4392-a898-4d846e6c0126",
    "name": "stuff goes there",
    "description": "stuff goes there"
  }
]

Here is the code I'm currently attempting to use (note that the actual values have been replaced with dummy data):

private static readonly string MasterKey = "my master key";
private static readonly Uri BaseUri = new Uri("https://mydocdb.documents.azure.com");

private static bool _useNames = true;
private static readonly string DatabaseId = _useNames ? "MyDatabase" : "prdnAA==";
private static readonly string CollectionId = _useNames ? "MyCollection" : "prdnAKFrZAA=";

private static readonly string UtcDate = DateTime.UtcNow.ToString("r");
private static void Main(string[] args)
{
    var client = new HttpClient();
    client.DefaultRequestHeaders.Add("x-ms-date", UtcDate);
    client.DefaultRequestHeaders.Add("x-ms-documentdb-isquery", "True");
    client.DefaultRequestHeaders.Add("x-ms-version", "2015-08-06");

    string verb = "POST";
    string resourceType = "docs";
    string resourceLink = $"dbs/{DatabaseId}/colls/{CollectionId}/docs";
    string resourceId = _useNames ? $"dbs/{DatabaseId}/colls/{CollectionId}" : $"{CollectionId}";

    string authHeader = GenerateAuthToken(verb, resourceId, resourceType, MasterKey, "master", "1.0");
    client.DefaultRequestHeaders.Remove("authorization");
    client.DefaultRequestHeaders.Add("authorization", authHeader);

    var response = client.PostAsync(
        new Uri(BaseUri, resourceLink),
        new StringContent("{\"query\":\"SELECT * FROM root \"}",
            Encoding.UTF8,
            "application/query+json")
        ).Result;
    string result = response.Content.ReadAsStringAsync().Result;
    Console.WriteLine(result);
}

private static string GenerateAuthToken(string verb, string resourceId, string resourceType, string key,
    string keyType, string tokenVersion)
{
    var verbInput = verb ?? "";
    var resourceIdInput = resourceId ?? "";
    var resourceTypeInput = resourceType ?? "";

    var payLoad = string.Format(CultureInfo.InvariantCulture, 
        "{0}\n{1}\n{2}\n{3}\n{4}\n",
        verbInput.ToLowerInvariant(),
        resourceTypeInput.ToLowerInvariant(),
        resourceIdInput,
        UtcDate.ToLowerInvariant(),
        ""
        );

    var hmacSha256 = new HMACSHA256 { Key = Convert.FromBase64String(key) };
    var hashPayLoad = hmacSha256.ComputeHash(Encoding.UTF8.GetBytes(payLoad));
    var signature = Convert.ToBase64String(hashPayLoad);

    return HttpUtility.UrlEncode(
        string.Format(
            CultureInfo.InvariantCulture,
            "type={0}&ver={1}&sig={2}",
            keyType,
            tokenVersion,
            signature)
        );
}

This code is a variation of the example code from the documentation of Access Control on DocumentDB Resources. The example code given on the page is rather flawed in that it uses a function GenerateMasterKeyAuthorizationSignature but the function is named GenerateAuthToken like this example. It also only displays GET requests against the api, and doesn't indicate how the authorization token payload is created for a POST request. Which is why the main thing I'm most concerned with is the authorization header. I'm not convinced I'm setting the resourceId or resourceType correctly. If I try an empty resourceId which is what I would expect to use for a query, I then get an unauthorized response that indicates the expected resourceId of the payload is a reference to the collection (the collectionId in lower case if I'm using _rid's of the database and collection, or the named path if I use names).

note the referenced documentation page has since been updated and the sample code has been removed. I also found other references to generate the auth header and found I am creating it correctly.

Am I using the correct ResourceType and ResourceId values when creating the authorization payload? If I am, why am I getting the error about the required id property?

If I’m not using the correct values, what should they be?

Resolution Update

As Ryan has indicated, the issue is with the CharSet property of the ContentType header. His linked code is probably a better way to do it, but I also found that I could create a variable of the StringContent and modify it before posting to get the expected results.

var stringQuery = new StringContent("{\"query\":\"SELECT * FROM root \"}",
    Encoding.UTF8,
    "application/query+json");
stringQuery.Headers.ContentType.CharSet = null;
HttpResponseMessage response = client.PostAsync(new Uri(BaseUri, resourceLink)
    ,stringQuery).Result;
1

1 Answers

1
votes

Please refer to the REST API docs for querying resources - https://msdn.microsoft.com/en-us/library/azure/dn783363.aspx

Looks like you are missing the ContentType header. This must be set to "application/query+json" for queries.

Even if you added that, I suspect the problem you will have next is that .NET HttpClient always adds a charset on to the end of this header when doing the POST. As can be seen in your code where you use Encoding.UTF8.

Unfortunately there is no easy way in .NET (that i know of) to do a POST without specifying a charset.

I created a sample with a custom extension to .NET HttpClient that does this.

Take a look at the "REST from .NET" sample posted on github - https://github.com/Azure/azure-documentdb-dotnet/tree/master/samples/rest-from-.net

This sample will show you exactly what values should be used and when. It does a GET on all major resources as well as a POST for executing a query.