Azure Table Storage
Implementing a storage module that uses Azure Table Storage is pretty easy. You can ofcourse choose any type of storage. The Azure Table Storage is just a sample.
Pre Req
You need the WindowsAzure.Storage
package.
Install-Package WindowsAzure.Storage
You also need to reference the System.Configuration assembly.
The Module for persisting to Azure Table Storage
This is the code to overwrite the default storage module. As you can see it will be easy to use another storage if you do not want to use Azure.
using System;
using System.Threading.Tasks;
using XSockets.Core.XSocket;
public class AzureObjectStorage : PersistentObjectStorage
{
public override async Task Set<T>(T controller, string key, object value)
{
await new StorageObject(controller.Alias, key, value).SaveEntity();
}
public override async Task<object> Get<T>(T controller, string key)
{
var e = new StorageObject(controller.Alias, key);
var so = await e.GetEntity();
return so.Deserialize();
}
public override async Task Remove<T>(T controller, string key)
{
var e = new StorageObject(controller.Alias, key);
await e.DeleteEntity();
}
public override Task Clear<T>(T controller)
{
//No implementation of clear....
throw new NotImplementedException();
}
}
Helpers
There is a few helpers classes to make the module above to work.
The StorageObject
Since Azure expects us to pass in a entity that inherits TableEntity
we have a the StorageObject
class. The class will make sure that we store the JSON representation and the type that was serialized. This can be solved in many ways, this is just one approach.
using Microsoft.WindowsAzure.Storage.Table;
using System.Reflection;
using XSockets.Core.Common.Utility.Serialization;
using XSockets.Plugin.Framework;
public class StorageObject : TableEntity
{
public string Type { get; set; }
public string JSON { get; set; }
public object Deserialize()
{
return Composable.GetExport<IXSocketJsonSerializer>().DeserializeFromString(this.JSON, System.Type.GetType(Type));
}
public StorageObject() { }
public StorageObject(string partitiokKey, string rowKey)
{
this.PartitionKey = partitiokKey;
this.RowKey = rowKey;
}
public StorageObject(string partitiokKey, string rowKey, object o)
{
this.PartitionKey = partitiokKey;
this.RowKey = rowKey;
var t = o.GetType();
this.Type = string.Format("{0}, {1}", t.FullName, Assembly.GetAssembly(t).GetName().Name);
this.JSON = Composable.GetExport<IXSocketJsonSerializer>().SerializeToString(o, o.GetType());
}
}
The communication with Azure
Some helpers to write, read and delete from Azure Storage Tables. Note that you have to add a connection string to your app.config/web.config.
using Microsoft.WindowsAzure.Storage;
using Microsoft.WindowsAzure.Storage.Table;
using System;
using System.Configuration;
using System.Threading.Tasks;
public static class StorageTableHelpers
{
private static string connString;
private static CloudStorageAccount storageAccount;
private static CloudTableClient tableClient;
static StorageTableHelpers()
{
connString = ConfigurationManager.AppSettings["StorageConnectionString"];
// Retrieve the storage account from the connection string.
storageAccount = CloudStorageAccount.Parse(connString);
// Create the table client.
tableClient = storageAccount.CreateCloudTableClient();
}
/// <summary>
/// Generic helper to save to an entity to a Azure Storage Table.
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="entity">Any object that inherits TableEntity</param>
/// <param name="updateAction">Perform update on properties, if null no updates will be made and an overwrite will be made if the entity exists</param>
/// <param name="tableName">If not passed in the table will be named after the entity T</param>
public static async Task SaveEntity<T>(this T entity, Func<T, T> updateAction = null, string tableName = null) where T : TableEntity
{
if (tableName == null)
{
tableName = typeof(T).Name;
}
var t = tableName.EnsureTable();
//Check for current rows...
TableOperation retrieveOperation = TableOperation.Retrieve<T>(entity.PartitionKey, entity.RowKey);
// Execute the operation.
TableResult retrievedResult = await t.ExecuteAsync(retrieveOperation);
// Assign the result to T.
T updateEntity = (T)retrievedResult.Result;
if (updateEntity != null)
{
if (updateAction != null)
{
//Update memebers from delegate
updateEntity = updateAction(updateEntity);
TableOperation insertOrReplaceOperation = TableOperation.InsertOrReplace(updateEntity);
await t.ExecuteAsync(insertOrReplaceOperation);
}
else
{
//merge
TableOperation replaceOperation = TableOperation.InsertOrMerge(entity);
await t.ExecuteAsync(replaceOperation);
}
}
else
{
//Add new entity
TableOperation insertOperation = TableOperation.Insert(entity);
await t.ExecuteAsync(insertOperation);
}
}
public static async Task<T> GetEntity<T>(this T entity, string tableName = null) where T : TableEntity
{
if (tableName == null)
{
tableName = typeof(T).Name;
}
var t = tableName.EnsureTable();
//Check for current rows...
var partitionKey = entity.PartitionKey;
var rowKey = entity.RowKey;
TableOperation retrieveOperation = TableOperation.Retrieve<T>(partitionKey, rowKey);
// Execute the operation.
TableResult retrievedResult = await t.ExecuteAsync(retrieveOperation);
return (T)retrievedResult.Result;
}
public static async Task<T> DeleteEntity<T>(this T entity, string tableName = null) where T : TableEntity
{
if (tableName == null)
{
tableName = typeof(T).Name;
}
var t = tableName.EnsureTable();
if (string.IsNullOrEmpty(entity.ETag))
entity.ETag = "*";
TableOperation deleteOperation = TableOperation.Delete(entity);
// Execute the operation.
TableResult deleteResult = await t.ExecuteAsync(deleteOperation);
return (T)deleteResult.Result;
}
private static CloudTable EnsureTable(this string tableName)
{
CloudTable table = tableClient.GetTableReference(tableName);
table.CreateIfNotExists();
return table;
}
}
Test
So now we can do the same thing as we did when testing the default storage, but now we will be able to see the data in the Azure Table Storage. You can view it in the Cloud Explorer
in Visual Studio.
In the image you can see that the data is persisted on Azure, and we can also get the value back to Putty by using the s2
topic.
You may alos notice that we set the PartitionKey to the controller name and the RowKey to the key of our key/value object. This means that we now changed the behavior of the module to let all users access the key/value. To have individual storage you would probably change the PartitionKey to be the PersistenId of the XSockets connection. Read more about PartitionKey/RowKey here.