Custom PersistentProperty Module (Azure)
We recommend that you read the previous sections about Persistent Properties
before reading this section.
Writing a custom module for the PersistentProperty
functionality is pretty simple. In this sample we will create a module that stores the persistent properties to a Azure Storage Tables
Install Azure packages
To be able to use Azure Table Storage we will need to install an extra package.
Install-Package WindowsAzure.Storage
Implement the PersistentPropertyStorage
There is a template for CustomPersistentPropertyStorage
. Choose Add->NewItem and go to XSockets.NET 5
This will give us something like
using System;
using System.Threading.Tasks;
using XSockets.Core.XSocket;
public class AzurePersistentPropertyStorage : PersistentPropertyStorage
{
public override async Task ReadFromPropertyStorage<T>(T controller)
{
throw new NotImplementedException("Implement custom storage for property values");
}
public override async Task WriteToPropertyStorage<T>(T controller)
{
throw new NotImplementedException("Implement custom storage for property values");
}
}
Reading/Writing to Azure Table Storage
The complete sample module looks like this.
using System;
using System.Threading.Tasks;
using XSockets.Core.XSocket;
public class AzurePersistentPropertyStorage : PersistentPropertyStorage
{
public override async Task ReadFromPropertyStorage<T>(T controller)
{
foreach (var pi in this.GetPersistentProperties(controller.GetType()))
{
var e = new StorageObject(string.Format("{0}.{1}", controller.Alias, pi.Name), controller.PersistentId.ToString());
var so = await e.GetEntity();
if (so == null) continue;
pi.SetValue(controller, so.Deserialize(), null);
}
}
public override async Task WriteToPropertyStorage<T>(T controller)
{
foreach (var pi in this.GetPersistentProperties(controller.GetType()))
{
var v = pi.GetValue(controller, null);
if (v == GetDefault(pi.PropertyType)) continue;
//User controller.property as partition key, otherwise you cant use teh same property name on several controllers
await new StorageObject(string.Format("{0}.{1}", controller.Alias, pi.Name), controller.PersistentId.ToString(), v).SaveEntity();
}
}
public static object GetDefault(Type type)
{
if (type.IsValueType)
{
return Activator.CreateInstance(type);
}
return null;
}
}
Helpers
As you can see above there is a StorageObject
as well as methods being called for saving and retrieving entities.
The classed used is pasted in below with a brief description.
StorageObject
We need to inherit the TableEntity
to be able to save data into Azure Table Storage
. This class makes sure that the objects are serialized and saved as JSON
in Azure. It also saves the type so that we can deserialize the objects when reading them from Azure.
using Microsoft.WindowsAzure.Storage.Table;
using XSockets.Core.Common.Utility.Serialization;
using System.Reflection;
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());
}
}
StorageTableHelpers
A static class that read/write to Azure Table Storage. This can surely be written in a better way, look at this as a proof of concept.
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;
}
public static void DeleteTable(this string tableName)
{
// Create the CloudTable object that represents the "people" table.
CloudTable table = tableClient.GetTableReference(tableName);
table.Delete();
}
}
Configuration
As you might have seen above the hepler class expects you to have a configuration-setting for the Azure connectionstring named StorageConnectionString
.
<add key="StorageConnectionString" value="conn-string-here" />
The connectionstring can be found in the Azure portal
The picture below shows where you will find the connections string. You can choose primary or seconday connectionstring. It does not matter.
Test
So, if we repeat the Putty stuff from the Persistent Property Sample
section you will see that values are written into the Azure Storage Table
when the connection is closed. You will also get back the value from the Azure Table Storage
when you reconnect