Team XSockets.NET

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

custom persistent property storage

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.

Azure ConnString

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

Azure Table Storage - Demo

results matching ""

    No results matching ""