Accessing Azure Table Storage Service from a Unity game

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. 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

Nevertheless, in our library you can either one of both options (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";

 Operations available

There are 4 operations you can do with the library on Azure Table Service. 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<Customer>(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<Customer>(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<Customer>(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

6 thoughts on “Accessing Azure Table Storage Service 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

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s