15

I checked the MSDN on DocumentDB for .Net (here) and found 3 valid constructors. However none of them makes use of connection strings, which sounds strange for me.

Is there seriously no way to instantiate client with connection string instead of endpoint+authKey combo or I'm missing something?

For example majority of other Microsoft services uses this concept, i.e. https://learn.microsoft.com/en-us/azure/storage/storage-configure-connection-string#parsing-a-connection-string . In our case it would be super if all Azure related stuff is initialized in same manner. Just cleaner, not something show-stopping.

P.S. Please stop telling me about existing constructors with Uri and authKey parameters, question is (slightly) different. I can follow links I provided myself, no needs to help. Thanks.

Sanctus
  • 273
  • 6
  • 12
  • 2
    The first argument to each constructor is a `Uri`. That's the endpoint. What connection string do you want? –  Jan 16 '17 at 19:08
  • 1
    @Amy, something similar to what I can get from Web interface. https://learn.microsoft.com/en-us/azure/documentdb/media/documentdb-get-started/nosql-tutorial-keys.png from https://learn.microsoft.com/en-us/azure/documentdb/documentdb-get-started – Sanctus Jan 16 '17 at 20:17
  • well, those links you gave show you to use the endpoint Uri and then the primary key as the resource identifer. I fail to see the problem? – ADyson Jan 16 '17 at 20:39
  • 1
    @ADyson, just below of highlighted text (URI, Keys), there are PRIMARY and SECONDARY CONECTION STRINGs. – Sanctus Jan 16 '17 at 20:43
  • 1
    Are you saying this constructor no longer exists? `this.client = new DocumentClient(new Uri(EndpointUri), PrimaryKey);` –  Jan 16 '17 at 21:11
  • the constructor only requires you to submit the primary one, according to the example. are you saying the example doesn't work? – ADyson Jan 16 '17 at 22:20
  • @Will, ADyson, Im saying it is not the one I want due to some reasons. And Im saying that even MS providing connection strings for other than .Net frameworks for DocumentDB – Sanctus Jan 17 '17 at 12:54
  • So you want something you know doesn't exist. Not sure what this is, as that's not a question, and this isn't answerable other than to introduce an intermediary between configuration and initialization that takes a custom connection string you create. –  Jan 17 '17 at 14:14
  • @Will, I want something I know nothing about. I listed things I know to exist, but they are not what I want in the end. It is a valid question, as valid as "Sup stack, what is the cleanest way to write hello world with lambda?". – Sanctus Jan 17 '17 at 14:45
  • Meh. The answer you have below is about the only thing you can get. The other solution would be to write something that stands inbetween, as I mentioned. Try that and come back when you have problems. Good luck. –  Jan 17 '17 at 14:52
  • 4
    @Will I don't know why you're being so aggressively unhelpful. I have the exact same question, and I suspect most people doing Azure development probably wonder at least once. Microsoft _provides_ a combined URI + key "connecting string" for e.g. Azure Storage and Azure DocumentDB, but you can only _use_ it with the Azure Storage API. The Azure DocumentDB API requires you to supply both parts when other APIs are capable of accepting them as a single parameter. I think this is a completely valid question, because the answer that "no, there isn't a way" is surprising and thus interesting. – Michael Edenfield May 15 '18 at 22:43
  • I found this question when switching between another method I had been using to query documents (using a CloudTable) and wanting to instead use the SQL API (using a document client, the easiest of constructors requiring a database uri and the primary authkey). I found that Uri is a property of CloudTable once it is instantiated, so I could easily get that using my old code. Authkey is part of the connection string, which is the only thing I'm being given, so I have the same issue. This is a very valid question. – JakeJ May 16 '18 at 20:03
  • 1
    The 3.0 version of the SKD (currently in preview) has this feature: https://learn.microsoft.com/en-us/dotnet/api/microsoft.azure.cosmos.cosmosclient.-ctor?view=azure-dotnet-preview – sschoof Jun 14 '19 at 07:25
  • It only took 2 years :) – Sanctus Jun 14 '19 at 11:47

4 Answers4

19

You can actually do this in a roundabout way.

internal class CosmosDBConnectionString
{
    public CosmosDBConnectionString(string connectionString)
    {
        // Use this generic builder to parse the connection string
        DbConnectionStringBuilder builder = new DbConnectionStringBuilder
        {
            ConnectionString = connectionString
        };

        if (builder.TryGetValue("AccountKey", out object key))
        {
            AuthKey = key.ToString();
        }

        if (builder.TryGetValue("AccountEndpoint", out object uri))
        {
            ServiceEndpoint = new Uri(uri.ToString());
        }
    }

    public Uri ServiceEndpoint { get; set; }

    public string AuthKey { get; set; }
}

Then

var cosmosDBConnectionString = new CosmosDBConnectionString(connectionString)
var client = new DocumentClient(
            cosmosDBConnectionString.ServiceEndpoint,
            cosmosDBConnectionString.AuthKey)

This is taken from the Azure WebJobs Extensions SDK, which is how Azure Functions V2 is able to work with just a connection string. Saves having to try and parse the string yourself.

deadwards
  • 2,109
  • 1
  • 16
  • 27
8

I created a class for parsing connection string similar to how the CloudStorageAccount.Parse works. I tried to follow their pattern as closely as possible in case they ever decide to open source it, this could hopefully be contribed without much change.

public static class DocumentDbAccount
{
    public static DocumentClient Parse(string connectionString)
    {
        DocumentClient ret;

        if (String.IsNullOrWhiteSpace(connectionString))
        {
            throw new ArgumentException("Connection string cannot be empty.");
        }

        if(ParseImpl(connectionString, out ret, err => { throw new FormatException(err); }))
        {
            return ret;
        }

        throw new ArgumentException($"Connection string was not able to be parsed into a document client.");
    }

    public static bool TryParse(string connectionString, out DocumentClient documentClient)
    {
        if (String.IsNullOrWhiteSpace(connectionString))
        {
            documentClient = null;
            return false;
        }

        try
        {
            return ParseImpl(connectionString, out documentClient, err => { });
        }
        catch (Exception)
        {
            documentClient = null;
            return false;
        }
    }

    private const string AccountEndpointKey = "AccountEndpoint";
    private const string AccountKeyKey = "AccountKey";
    private static readonly HashSet<string> RequireSettings = new HashSet<string>(new [] { AccountEndpointKey, AccountKeyKey }, StringComparer.OrdinalIgnoreCase);

    internal static bool ParseImpl(string connectionString, out DocumentClient documentClient, Action<string> error)
    {
        IDictionary<string, string> settings = ParseStringIntoSettings(connectionString, error);

        if (settings == null)
        {
            documentClient = null;
            return false;
        }

        if (!RequireSettings.IsSubsetOf(settings.Keys))
        {
            documentClient = null;
            return false;
        }

        documentClient = new DocumentClient(new Uri(settings[AccountEndpointKey]), settings[AccountKeyKey]);
        return true;
    }

    /// <summary>
    /// Tokenizes input and stores name value pairs.
    /// </summary>
    /// <param name="connectionString">The string to parse.</param>
    /// <param name="error">Error reporting delegate.</param>
    /// <returns>Tokenized collection.</returns>
    private static IDictionary<string, string> ParseStringIntoSettings(string connectionString, Action<string> error)
    {
        IDictionary<string, string> settings = new Dictionary<string, string>();
        string[] splitted = connectionString.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

        foreach (string nameValue in splitted)
        {
            string[] splittedNameValue = nameValue.Split(new char[] { '=' }, 2);

            if (splittedNameValue.Length != 2)
            {
                error("Settings must be of the form \"name=value\".");
                return null;
            }

            if (settings.ContainsKey(splittedNameValue[0]))
            {
                error(string.Format(CultureInfo.InvariantCulture, "Duplicate setting '{0}' found.", splittedNameValue[0]));
                return null;
            }

            settings.Add(splittedNameValue[0], splittedNameValue[1]);
        }

        return settings;
    }
}
MPavlak
  • 2,133
  • 1
  • 23
  • 38
  • Thanks! This is no use for me anymore, but God, this is the first attempt to solve problem, instead of pointing me to other constructors. – Sanctus Jun 29 '17 at 21:21
  • No problem. Took 30 mins looking into how CloudStorageAccount did it and is something I've wanted too!! Just rolled up my sleeves :) – MPavlak Jun 30 '17 at 15:13
7

The DocumentDB SDKs do not have constructor overloads using connection strings. They support initializing with endpoint + master key, and endpoint + permissions/resource tokens.

If you'd like to see a single connection string argument, please propose/upvote here: https://feedback.azure.com/forums/263030-documentdb

Aravind Krishna R.
  • 7,885
  • 27
  • 37
  • 2
    posted request, hope it will get positive reply, since its not as much work as making aggregations or other game-changing things. – Sanctus Jan 19 '17 at 14:26
  • 3
    I voted for this feature. I would contribute it myself if it were an option and also voted to open source the SDK. How hard could it possibly be to parse the connection string and call the correct constructor via factory create method like storage accounts – MPavlak Jun 28 '17 at 18:58
3

That works for me:

    private static DocumentClient InitializeDocumentClient()
    {

        string connectionString = ConfigurationManager.AppSettings["CosmosTest"];
        string[] connectionStringParts = connectionString.Split(';');
        Uri clientUrl = new Uri(connectionStringParts[0].Split('=')[1]);
        int keyStartPosition = connectionStringParts[1].IndexOf('=') + 1;
        string clientKey = connectionStringParts[1].Substring(keyStartPosition, connectionStringParts[1].Length-keyStartPosition);
        return new DocumentClient(clientUrl, clientKey, connectionPolicy);
    }
Verv
  • 2,385
  • 2
  • 23
  • 37
A.Rowan
  • 1,460
  • 2
  • 16
  • 20