Accessing Azure Table Storage Service and Azure CosmosDB with Table API from a Unity game

Update (2/1/2018): Blog post and GitHub code has been updated with instructions on how to connect to CosmosDB using Table Storage API.

Azure Table Storage Service is an inexpensive, highly available NoSQL key-value store. It can store petabytes of structured data, supports a flexible data schema and Azure team provides a REST API. Furthermore, Azure CosmosDB has a Table Storage API, so the same code can also be used here. So, I thought, why not extending my Azure Services for Unity library with support for access to Azure Table Storage Service from any Unity game? Well, here we are, our latest commit to the repository contains access methods for the service.

Authentication for the Azure Storage service

Authentication for the entire Azure Storage service can, generally, be handled in two ways. Either via setting the storage account key in the Authorization HTTP header or by using a specially crafted URL that contains a SAS (Shared Access Signature). The SAS option is highly recommended because, well, you don’t want your users to have access to your storage account key, since they can use it for malicious purposes. The SAS Token looks like this

?sv=2015-12-11&ss=bfqt&srt=sco&sp=rwdlacup&se=2017-03-20T19%3A11%3A43Z&st=2017-02-18T11%3A11%3A43Z&spr=https&sig=dohu1Yr7xxPZxx1cte1pZyzt8LfLCwd1TtRRyoVts44%3D&api-version=2015-12-11

SAS Token is appended to the query string and can contain various parameters, such as API version, service version, start and expiry time, permissions, IP, protocol and an encrypted signature. It can be used to protect all Azure Storage related services, such as Queues, Tables, Files and Blob and (most importantly) can grant specific permissions (such as only table insert permissions). SAS Tokens can be generated (more sample code). So, a good practice for your game is to have a web service generate SAS Tokens, fetch these to your game clients which can use to connect to Azure Table Service. For development purposes, you can generate SAS Tokens from within the Azure Portal or from the Azure Storage Explorer tool. Same tool can be used to monitor your storage account as a whole.

Update (March 7th, 2017): Check here for a cool sample to generate SAS tokens from an Azure Function!

Check here for a list of options when you generate a SAS Token from Azure Storage Explorer: sas

The library allows you to select the storage ‘type’ you want to access. Set the EndpointStorageType to TableStorage if you want to access Azure Table Storage.

TableStorageClient.Instance.EndpointStorageType = EndpointStorageType.TableStorage;

Or set ot to CosmosDBTableAPI if you want to access CosmosDB with Table API.

TableStorageClient.Instance.EndpointStorageType = EndpointStorageType.CosmosDBTableAPI;

When accessing Azure Table Storage, you can either one of both options to authenticate (bunch of characters omitted from the keys, of course)

TableStorage.Instance.AuthenticationToken = "CxKQjfUNxWxsd9HaZ6a5ZnDs4UQngqFRsGH0qEIwBve1w==";
TableStorage.Instance.SASToken = "?sv=2015-12-11&ss=bfqt&srt=sco&sp=rwdlacup&se=2017-03-20T19%3A11%3A43Z&st=2017-02-18T11%3A11%3A43Z&spr=https&sig=dohu1YyoVts44%3D&api-version=2015-12-11";

However, when you want to access CosmosDB via Table Storage API, the only authentication method allowed is via Key. Check here on how to get it (use either the Primary or the Secondary key).

Operations available

There are 4 operations you can do with the library on Azure Table Service (or CosmosDB with Table API). You can create a table, delete, insert or update an entity and query for entities.

CreateTableIfNotExists
TableStorage.Instance.CreateTableIfNotExists("tableName", createTableResponse =>
        {
            if (createTableResponse.Status == CallBackResult.Success)
            {
                string result = "CreateTable completed";
                Debug.Log(result);
                StatusText.text = result;
            }
            //you could also check if CallBackResult.ResourceExists
            else
            {
                ShowError(createTableResponse.Exception.Message);
            }
        });

With the CreateTableIfNotExists, you just specify the table name. The callback will report about success/failure.

DeleteTable
 TableStorage.Instance.DeleteTable("tableName", deleteTableResponse =>
        {
            if (deleteTableResponse.Status == CallBackResult.Success)
            {
                string result = "DeleteTable completed";
                Debug.Log(result);
                StatusText.text = result;
            }
            else
            {
                ShowError(deleteTableResponse.Exception.Message);
            }
        });

The DeleteTable method will delete the specified table.

InsertEntity
 [Serializable()]
    public class Customer : TableEntity
    {
        public Customer(string partitionKey, string rowKey)
            : base(partitionKey, rowKey) { }

        public Customer() : base() { }

        public int Age;
        public string City;
    }
        Customer cust = new Customer();
        cust.PartitionKey = "Gkanatsios23";
        cust.RowKey = "Dimitris23";
        cust.Age = 33;
        cust.City = "Athens2";

        TableStorage.Instance.InsertEntity(cust, "tableName", insertEntityResponse =>
        {
            if (insertEntityResponse.Status == CallBackResult.Success)
            {
                string result = "InsertEntity completed";
                Debug.Log(result);
                StatusText.text = result;
            }
            else
            {
                ShowError(insertEntityResponse.Exception.Message);
            }
        });

All entities that you want to be saved on Azure Table Service must inherit from a custom class called TableEntity, which contains a PartitionKey and RowKey property. The selection of the fields that will correspond to these properties must be done with careful consideration. I don’t want to repeat the documentation, so please check here for some great guidance (but DO check it, this is really important!).

So, when you make a class that inherits from TableEntity, you can then use the generic InsertEntity method to insert an instance of it onto Table Service. Easy, right?

QueryTable
        //Age >= 30 && Age <= 33
        string query = "$filter=(Age%20ge%2030)%20and%20(Age%20le%2033)&$select=PartitionKey,RowKey,Age,City";
        TableStorage.Instance.QueryTable(query,"tableName", queryTableResponse =>
        {
            if (queryTableResponse.Status == CallBackResult.Success)
            {
                string result = "QueryTable completed";
                Debug.Log(result);
                StatusText.text = result;
            }
            else
            {
                ShowError(queryTableResponse.Exception.Message);
            }
        });

If you want to query entities within a table you should use the generic QueryTable method along with a specially crafted query string like the one mentioned in the code above (in the query variable). For a reference on how to create this query string, check documentation here. For the curious reader that wonders why we did not use LINQ query to create this just like in the AppService part of the library, the answer is that this still doesn’t work with UWP, hence this GitHub issue is still alive and well, unfortunately.

UpdateEntity
        Customer cust = new Customer();
        cust.PartitionKey = "Gkanatsios23";
        cust.RowKey = "Dimitris23";
        cust.Age = 33;
        cust.City = "Athens25";

        TableStorage.Instance.UpdateEntity(cust, "tableName", insertEntityResponse =>
        {
            if (insertEntityResponse.Status == CallBackResult.Success)
            {
                string result = "InsertEntity completed";
                Debug.Log(result);
                StatusText.text = result;
            }
            else
            {
                ShowError(insertEntityResponse.Exception.Message);
            }
        });

UpdateEntity method is pretty straightforward, again. We’ve also implemented a InsertOrMergeEntity method, relative to this API method.

That’s it! Don’t forget to also check my other two blog posts for instructions on how to use the library

as well as the GitHub repository: https://github.com/dgkanatsios/AzureServicesForUnity

7 thoughts on “Accessing Azure Table Storage Service and Azure CosmosDB with Table API from a Unity game

  1. I couldn’t get any CallbackResponse result data with QueryTable. I added data to response.Result in TableStorageClient.cs (row 245) and now it is working. Here is my code:

    private IEnumerator QueryTableInternal(TableQuery query, string tableName, Action<CallbackResponse> onQueryTableCompleted)
    where T : TableEntity
    {
    string url = string.Format(“{0}{1}()?{2}”, Url, tableName, query.ToString());

    using (UnityWebRequest www =
    StorageUtilities.BuildStorageWebRequest(url, HttpMethod.Get.ToString(), accountName, string.Empty))
    {
    yield return http://www.Send();
    if (Globals.DebugFlag) Debug.Log(www.responseCode);

    CallbackResponse response = new CallbackResponse();

    if (Utilities.IsWWWError(www))
    {
    if (Globals.DebugFlag) Debug.Log(www.error ?? “Error ” + http://www.responseCode);
    Utilities.BuildResponseObjectOnFailure(response, www);
    }
    else if (www.downloadHandler != null) //all OK
    {
    //let’s get the header for the new object that was created
    T[] data = JsonHelper.GetJsonArrayFromTableStorage(www.downloadHandler.text);
    if (Globals.DebugFlag) Debug.Log(“Received ” + data.Length + ” objects”);

    foreach (var item in data)
    {
    Debug.Log(string.Format(“Item with PartitionKey {0} and RowKey {1}”, item.PartitionKey, item.RowKey));
    }
    response.Status = CallBackResult.Success;
    response.Result = data; // <-added this
    }
    onQueryTableCompleted(response);
    }
    }

    Like

  2. Thanks a lot for this wonderful API !
    Tutorials to read/write data online from Unity are very uncommon, particularly those which still work in 2021. Your project have really helping me.

    Like

Leave a comment