C#C# V4 Storage & Playback Tutorial for Realtime Apps

 

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.

Requires that the Storage and Playback add-on is enabled for your key. How do I enable add-on features for my keys? - see http://www.pubnub.com/knowledge-base/discussion/644/how-do-i-enable-add-on-features-for-my-keys
PubNub's Storage and Playback feature enables developers to store messages as they are published, and retrieve them at a later time. Before using this feature it must be enabled in the PubNub Admin Console.
Being able to pull an archive of messages from storage has many applications:
  • Populate chat, collaboration and machine configuration on app load.
  • Store message streams with real-time data management.
  • Retrieve historical data streams for reporting, auditing and compliance (HIPAA, SOX, Data Protection Directive, and more).
  • Replay live events with delivery of real-time data during rebroadcasting.
As needed, specific messages can be marked "do not archive" when published to prevent archiving on a per-message basis, and storage retention time can range from 1 day to forever.
These code samples build off code defined in the Pub & Sub tutorial, so before proceeding, be sure you have completed the Pub & Sub tutorial first.
To begin, lets populate a new channel with some test publishes that we'll pull from storage using the History() method.
for (int i=0; i < 500; i++) {
    PNPublishResult publish = pubnub.Publish()
        .Message( new List<string> { 
            // the payload (Required)
		    "message#" + i.ToString() 
        })
        .Channel("history_channel") // destination of the message (Required)
        .ShouldStore(true) // store in history (defaults to account default)
        .Meta("<json data as dictionary object>") // optional meta data object which can be used with the filtering ability.
        .UsePOST(true) // use POST to publish, defaults to false
        .Sync(); // block the thread, exception thrown if something goes wrong.
}
In the above example, we subscribe to history_channel, and onConnect, we'll publish a barrage of test messages to it. You should see these messages as they are published on the console.
Now that we've populated storage, we can pull from storage using the History() method call:
pubnub.History()
    .Channel("history_channel") // where to fetch history from
    .Count(100) // how many items to fetch
    .Async(new PNHistoryResultExt(
        (result, status) => {
        }
    ));
The response format is
[
	["message1", "message2", "message3",... ],
	"Start Time Token",
	"End Time Token"
]
By default, History() returns the last 100 messages, first-in-first-out (FIFO). Although we specified 100 for the Count, that is the default, and if we hadn't specified a Count, we would have still received 100. Try this example again, but specify 5 for Count to see what is returned.
Setting the reverse attribute to true will return the first 100 messages, first-in-first-out (FIFO). Try this example again, but specify true for Reverse to see what is returned.
The timetoken response value is a string, representing 17-digit precision unix time (UTC). To convert PubNub's timetoken to Unix timestamp (seconds), divide the timetoken number by 10,000,000 (10^7).
Given this example, you will get the last two messages published to the channel:
pubnub.History()
    .Channel("history_channel") // where to fetch history from
    .Count(2) // how many items to fetch
    .Async(new DemoHistoryResult());

public class DemoHistoryResult : PNCallback<PNHistoryResult> {
    public override void OnResponse(PNHistoryResult result, PNStatus status) {
    }
};
[[498,499],14272454823679518,14272454845442942]
To page for the next 2, use the set the start attribute to start timetoken value, and request again with all other settings the same:
[[496,497],14272454791882580,14272454823679518]
As illustrated, paging with the default Reverse as true pages 2 at-a-time starting from newest, in FIFO order. If you repeat these Paging example steps with Reverse as false, you will page 2 at-a-time as well, starting instead from the oldest messages, but still in FIFO order. You will know you are at the end of history when the returned start timetoken is 0.

 
Usage!
You can call the method by passing 0 or a valid time token as the argument.
public class PubnubRecursiveHistoryFetcher {
    private static Pubnub pubnub;

    public abstract class CallbackSkeleton {
        public abstract void HandleResponse(PNHistoryResult result);
    }

    public PubnubRecursiveHistoryFetcher() {
        // NOTICE: for demo/demo pub/sub keys Storage & Playback is disabled,
        // so use your pub/sub keys instead
        PNConfiguration pnConfiguration = new PNConfiguration();
        pnConfiguration.SubscribeKey = "demo";
        pubnub = new Pubnub(pnConfiguration);
    }

    static public void Main() {
        PubnubRecursiveHistoryFetcher fetcher = new PubnubRecursiveHistoryFetcher();
        GetAllMessages(new DemoCallbackSkeleton());
    }

    public static void GetAllMessages(CallbackSkeleton callback) {
        GetAllMessages(-1L, callback);
    }

    public static void GetAllMessages(long startTimestamp, CallbackSkeleton callback) {
        CountdownEvent latch = new CountdownEvent(1);

        pubnub.History()
            .Channel("history_channel") // where to fetch history from
            .Count(100) // how many items to fetch
            .Start(startTimestamp) // first timestamp
            .Async(new DemoHistoryResult(callback));
    }

    public class DemoHistoryResult : PNCallback<PNHistoryResult> {
        CallbackSkeleton internalCallback;
        public DemoHistoryResult(CallbackSkeleton callback) {
            this.internalCallback = callback;
        }
        public override void OnResponse(PNHistoryResult result, PNStatus status) {
            if (!status.Error && result != null && result.Messages != null && result.Messages.Count > 0) {
                Console.WriteLine(result.Messages.Count);
                Console.WriteLine("start:" + result.StartTimeToken.ToString());
                Console.WriteLine("end:" + result.EndTimeToken.ToString());

                internalCallback.HandleResponse(result);
                GetAllMessages(result.EndTimeToken, this.internalCallback);
            }
        }
    };

    public class DemoCallbackSkeleton : CallbackSkeleton {
        public override void HandleResponse(PNHistoryResult result) {
            //Handle the result
        }
    }
}
To pull from a slice of time, just include both Start and End attributes:
pubnub.History()
    .Channel("history_channel") // where to fetch history from
    .Count(100) // how many items to fetch
    .Start(13847168620721752L) // first timestamp
    .End(13847168819178600L) // last timestamp
    .Async(new DemoHistoryResult());

public class DemoHistoryResult : PNCallback<PNHistoryResult> {
    public override void OnResponse(PNHistoryResult result, PNStatus status) {
    }
};
pubnub.History()
    .Channel("history_channel") // where to fetch history from
    .Count(100) // how many items to fetch
    .IncludeTimetoken(true) // include timetoken with each entry
    .Async(new DemoHistoryResult());

public class DemoHistoryResult : PNCallback<PNHistoryResult> {
    public override void OnResponse(PNHistoryResult result, PNStatus status) {
    }
};

Retrieves up to 100 messages starting with the message stored before the start timetoken parameter value and older. In other words, the retrieval is from newest to oldest or in descending time direction.

first msg ------------ start tt ------------ last msg
               <------]

Retrieves up to 100 messages starting with the message stored at the end timetoken parameter (if one exists with that timetoken) value and newer. In other words, the retrieval is from oldest to newest or in ascending time direction.

first msg ------------ end tt ------------ last msg
                      [------------>

Retrieves up to 100 messages starting with the message stored after the start timetoken parameter value and newer. In other words, the retrieval is from oldest to newest or in ascending time direction.

first msg ------------ start tt ------------ last msg
                               [-------->

Retrieves up to 100 messages starting with the message stored at the end timetoken (if one exists with that timetoken) parameter value and older. In other words, the retrieval is from newest to oldest or in descending time direction.

first msg ------------ end tt ------------ last msg
                <------------]

Retrieves up to 100 messages starting with the message stored at the end timetoken (if one exists with that timetoken) parameter value and older but not older than the message stored after the start timetoken. In other words, the retrieval is from newest to oldest or in descending time direction ending with the message that comes after (is older) than the start timetoken value.

first msg ----- start tt -------- end tt ---- last msg
                        [   <-----------]