C#Message Updates and Deletes in C# V4

 

These docs are for PubNub 4.0 for C# which is our latest and greatest! For the docs of the 3.x versions of the SDK, please check the links: C#, Windows 8, Windows 8.1, ASP.Net, Windows Phone 8, Windows Phone 8.1, Xamarin.iOS, Xamarin.Android, Xamarin.Mac and C# PCL.

If you have questions about the PubNub for C# SDK, please contact us at support@pubnub.com.

PubNub stores all messages per channel with the Storage & Playback feature add-on. Since it stores all messages in a time series, and doesn't expose Updating and Deleting, you need a simple pattern to implement this functionality. It involves simply publishing the updated versions of messages within the stream.

There are two ways to do this:

Let's use these two JSON messages as the messages we want to update and delete. The first message has a spelling error in the status, the second one was posted prematurely and needs to be deleted.

{
	message_id: 10001,
	channel: "jasdeep-status",
	user: "jasdeep",
	status: "Writng up design patterns",
	usecase: "update",
	deleted: false
}
{
	message_id: 20001,
	channel: "jasdeep-status",
	user: "jasdeep",
	status: "The Patriots lost the Super Bowl!",
	usecase: "delete",
	deleted: false
}

Using the Interleaving pattern, you publish new versions of the same message to the same channel just like normal publishes. The subsequent messages must use the same message_id as the original message, and to simplify rendering, the messages for updates should contain the full content so that the original is not needed.

Interleaving in Time Series

Assuming we already published message 1 and 2 above. We would then publish the updated version (and deleted) to the same channel.

DateTime date = new DateTime();

Dictionary<string, object> data = new Dictionary<string, object>();
data.Add("message_id", "10001");
data.Add("channel", "jasdeep-status");
data.Add("original_timetoken", date.Millisecond / 1000);
data.Add("user", "jasdeep");
data.Add("status", "Writing up design patterns...");
data.Add("usecase", "update");
data.Add("deleted", false);
data.Add("is_update", true);

pubnub.Publish()
    .Message(data)
    .Channel("jasdeep-status")
    .Execute(new DemoPublishResult());

public class DemoPublishResult : PNCallback<PNPublishResult> {
    public override void OnResponse(PNPublishResult result, PNStatus status) {
        if (status.Error) {
            Console.WriteLine("error happened while publishing: " +  pubnub.JsonPluggableLibrary.SerializeToJsonString(status));
        } else {
            Console.WriteLine("message Published w/ timetoken " + result.Timetoken.ToString());
        }
    }
};

In this case we have updated part of the text as an additional publish to the channel, using the same message_id as the original message. In our client side logic, for displaying these messages we need to account for messages having the same ID and be able to update the content rather than display it as a new message.

While the code for updating display content varies considerably with each platform/UI framework, a simple example for a single channel follows:

static Dictionary<string, string> message_ids = new Dictionary<string, string>();
static Dictionary<string, string> deleted_ids = new Dictionary<string, string>();
static Dictionary<string, string> message_list = new Dictionary<string, string>();
static string my_channel = "jasdeep-status";

public static void display_messages(Dictionary<string, string> message, string channel) {
    if (message == null) return;

    if (!message_list.ContainsKey(channel)) {
        message_list.Add(channel, "");
    }
    if (!message_ids.ContainsKey(channel)) {
        message_ids.Add(channel, "");
    }
    if (!deleted_ids.ContainsKey(channel)) {
        deleted_ids.Add(channel, "");
    }

    Dictionary<string, Dictionary<string, string>> messageObject = new Dictionary<string, Dictionary<string, string>>();

    // if new message, add to list
    if (!message_ids[channel].Contains(message["message_id"].ToString())) {
        message_ids[channel] = message["message_id"].ToString();

        messageObject.Add(message["message_id"], message);
        message_list[channel] = pubnub.JsonPluggableLibrary.SerializeToJsonString(messageObject);
    } else {
        if (message.ContainsKey("deleted") && message["deleted"].ToString() == "1") {
            deleted_ids[channel] = message["message_id"];

            // delete from message list
            message_list.Remove(channel);

            // update UI, remove message from display
            // ...
        } else {
            // replace content (same as adding new one, because of identical message_id)
            messageObject.Add(message["message_id"], message);
            message_list[channel] = pubnub.JsonPluggableLibrary.SerializeToJsonString(messageObject);
            // update UI, update display content
            // ...
        }
    }
}


pubnub.AddListener(new DemoSubscribeCallback());

pubnub.Subscribe<string>()
    .Channels(new string[] {
        my_channel
    })
    .Execute();

public class DemoSubscribeCallback : SubscribeCallback {
    public override void Status(Pubnub pubnub, PNStatus status) {
        Console.WriteLine(pubnub.JsonPluggableLibrary.SerializeToJsonString(status));
    }

    public override void Message<T>(Pubnub pubnub, PNMessageResult<T> message) {
        Dictionary<string, string> msg = message.Message as Dictionary<string, string>;
        display_messages(msg, message.Subscription);
    }

    public override void Presence(Pubnub pubnub, PNPresenceEventResult presence) {
    }
}

Of course if you are using a javascript frontend framework like Backbone or Angular, you can go directly to the Collection and search for the message_id to handle updates and deletes.

With the Interleave pattern, it's not required to go further back in Storage History to see the original published message since it is simply replaced or deleted by subsequent publishes with the same message_id. The original message isn't needed to create the UI.

If the original message is also retrieved through history, you process all the messages in order just like the subscribe handler above, updating content or removing content if the message_id is used again. Therefore interleaving update/delete messages makes it simpler for managing the UI code.

If you are retrieving and displaying hundreds or thousands of messages from history it is possible that the rendering might be slow. In a scenario such as this where messages are being displayed as they are being processed it's possible to display a message that is later updated or deleted! Of course that will be corrected when the update/delete message is processed.

To avoid this race condition, you can delay render until all messages have been processed, or you can use a Side Channel pattern described below.

In the Side Channel pattern, you are publishing any updates and deletes to a separate channel that you also subscribe to. This means that you have a primary content channel and a side channel for updates/deletes.

In the Storage & Playback feature add-on, messages are stored on a per channel basis, this means that the side-channel has only update/delete messages in it, rather than both the orginal messages and the changes.

Side channel in Time Series

When retrieving from Storage through history, before retrieving the main channel content you first retrieve all the updates/deletes from the side channel. Then you can process updates/deletes can be processed at the same time as processing the associated original message.

This pattern requires a little bit more code (albeit not much more) and alleviates the possibility in the Interleave pattern of displaying any original/older versions of messages before the updates/deletes are processed.

This pattern might be a better chocie than Interleave if you are rendering as you go, have a slow rendering process, and potentially could have a race condition where the original message and the update/delete messages have a long time gap or a lot of other messages between them in the series causing delay before UI is updated to reflect updates/deletes.

Assuming we already published message 1 and 2 above. We would then publish the updated version (and deleted) to the same channel.

DateTime date = new DateTime();

Dictionary<string, object> data = new Dictionary<string, object>();
data.Add("message_id", "10001");
data.Add("channel", "jasdeep-status-updates");
data.Add("original_timetoken", date.Millisecond / 1000);
data.Add("user", "jasdeep");
data.Add("status", "Writing up design patterns...");
data.Add("usecase", "update");
data.Add("deleted", false);
data.Add("is_update", true);

pubnub.Publish()
    .Message(data)
    .Channel("jasdeep-status")
    .Execute(new DemoPublishResult());

public class DemoPublishResult : PNCallback<PNPublishResult> {
    public override void OnResponse(PNPublishResult result, PNStatus status) {
        if (status.Error){
            Console.WriteLine("error happened while publishing: " +  pubnub.JsonPluggableLibrary.SerializeToJsonString(status));
        } else {
            Console.WriteLine("message Published w/ timetoken " + result.Timetoken.ToString());
        }
    }
};

While the code for updating display content varies considerably with each platform/UI framework, a simple example for a single channel follows:

static Dictionary<string, string> message_ids = new Dictionary<string, string>();
static Dictionary<string, string> deleted_ids = new Dictionary<string, string>();
static Dictionary<string, string> message_list = new Dictionary<string, string>();
static string my_channel = "jasdeep-status";

public static void display_messages(Dictionary<string, string> message, string channel) {
    if (message == null) return;

    if (!message_list.ContainsKey(channel)) {
        message_list.Add(channel, "");
    }
    if (!message_ids.ContainsKey(channel)) {
        message_ids.Add(channel, "");
    }
    if (!deleted_ids.ContainsKey(channel)) {
        deleted_ids.Add(channel, "");
    }

    Dictionary<string, Dictionary<string, string>> messageObject = new Dictionary<string, Dictionary<string, string>>();

    if (message.ContainsKey("-UPDATES") && channel.Contains(message["-UPDATES"])) {
        if (!deleted_ids[channel].Contains(message["message_id"])) {
            message_ids[channel] = message["message_id"];
            messageObject.Add(message["message_id"], message);
            message_list[channel] = pubnub.JsonPluggableLibrary.SerializeToJsonString(messageObject);
        } else {
            // else it came from side-channel
            if (message.ContainsKey("deleted") && message["deleted"] == "1"){
                deleted_ids[channel] = message["message_id"];

                // delete from message list
                message_list.Remove(channel);

                // update UI, remove message from display
                // ...
            } else {
                // replace content (same as adding new one, because of identical message_id)
                messageObject.Add(message["message_id"], message);
                message_list[channel] = pubnub.JsonPluggableLibrary.SerializeToJsonString(messageObject);
                // update UI, update display content
                // ...
            }
        }
    }
}

// Subscribe to both the main content channel as well as the updates channel
pubnub.AddListener(new DemoSubscribeCallback());

pubnub.Subscribe<string>()
    .Channels(new string[] {
        my_channel,
        string.Format("{0}-UPDATES", my_channel)
    })
    .Execute();

With the Side Channel pattern, you want to retrieve history on the side channel first before retrieving the main history so that update/delete messages are processed at the same time as the original messages.

If you have any questions, please don't hesitate to make contact!