Tracking Lync Conversations in Code
In response to this question on StackOverflow, here’s a walk-through of how you can track different calls in Lync, using the Lync Client SDK.
Introduction
The first concept to grasp is that calls of any type (such as Instance Message calls, Audio/Video calls, Desktop Sharing calls) exist within a container called a Conversation. When you start up an IM session with someone, you’re actually creating a Conversation first, then connecting a call of type Instant Message within that Conversation. (this is explained in a little more detail in an earlier post)
This means that to track calls, you need to track Conversations.
This sample project, which is also available here on GitHub, will write out the duration of conversations as they finish. A change halfway through the sample will ensure that it only logs AV calls, rather than all types such as IM.
Step 1 – Set up the Project
I’ve created a console application to keep things simple. I’ve also added a reference to Microsoft.Lync.Model from the Client SDK.
Step 2 – Create a Container
In order to keep track of the duration of conversations, we need a container where we can store the Conversation and when it started. The Lync Client SDK doesn’t have a handy ConversationLength property we can analyse as the call finishes, so we’ll capture the time when the conversation starts and compare it to when the conversation ends:
using Microsoft.Lync.Model; using Microsoft.Lync.Model.Conversation; using System; using System.Collections.Generic; namespace ConversationTracker { class ConversationContainer { public Microsoft.Lync.Model.Conversation.Conversation Conversation { get; set; } public DateTime ConversationCreated { get; set; } } class Program { static Dictionary<String, ConversationContainer> ActiveConversations = new Dictionary<String, ConversationContainer>(); } }
(Yes, I know this isn’t the most accurate approach in the world, but hey, it’s sample code! Besides, for the purposes of measuring the length of a conversation it’s probably good enough.)
I’ve created a small container class to hold the Conversation object and a DateTime, and I’ve created a Dictionary so that we can track many different conversations at the same time. The Key of the Dictionary is going to be the ConversationID – a unique value we can use to identify the conversation when it finishes. I’ve also added a couple of using statements to keep the code cleaner.
Step 3 – Subscribe to Conversation Events
Next, get a handle to the Lync client. Then subscribe to two Conversation events, ConversationAdded and ConversationRemoved:
static void Main(string[] args) { var client = LyncClient.GetClient(); client.ConversationManager.ConversationAdded += ConversationManager_ConversationAdded; client.ConversationManager.ConversationRemoved += ConversationManager_ConversationRemoved; Console.ReadLine(); }
In the handler code for ConversationAdded we want to get the ConversationID and add it to the container of Active Conversations. We set the ConversationCreated field to the current date & time:
static void ConversationManager_ConversationAdded(object sender, Microsoft.Lync.Model.Conversation.ConversationManagerEventArgs e) { string ConversationID = e.Conversation.Properties[ConversationProperty.Id].ToString(); ActiveConversations.Add(ConversationID, new ConversationContainer() { Conversation = conversation, ConversationCreated = DateTime.Now }); }
In the handler code for ConversationRemoved we get the ConversationID, then check to see if this Conversation is in our list before we attempt to remove it. We also write out to the Console as we now know how long the conversation lasted:
static void ConversationManager_ConversationRemoved(object sender, Microsoft.Lync.Model.Conversation.ConversationManagerEventArgs e) { string ConversationID = e.Conversation.Properties[ConversationProperty.Id].ToString(); if (ActiveConversations.ContainsKey(ConversationID)) { var container = ActiveConversations[ConversationID]; TimeSpan conversationLength = DateTime.Now.Subtract(container.ConversationCreated); Console.WriteLine("Conversation {0} lasted {1} seconds", ConversationID, conversationLength); ActiveConversations.Remove(ConversationID); } }
Step 4 – Specific Modalities
Let’s say that we only want to track Audio/Video calls, rather than all types of calls.  In the ConversationAdded handler we can test the state of the AudioVideo modality within the Conversation. If the state is Disconnected then we know the Conversation isn’t trying to do any AV. If it’s in any of the other states, such as Connecting or Connected then we know we’ve got some AV going on. I’ve also extracted the storing of the conversation in the container into its own method:
static void ConversationManager_ConversationAdded(object sender, Microsoft.Lync.Model.Conversation.ConversationManagerEventArgs e) { string ConversationID = e.Conversation.Properties[ConversationProperty.Id].ToString(); if (e.Conversation.Modalities[ModalityTypes.AudioVideo].State != ModalityState.Disconnected) { StoreConversation(e.Conversation, ConversationID); } }
Step 5 – Upscaled Conversations
Our example works well for incoming AV calls. However, what about a Conversation which starts out as just an Instant Message call but then gets upscaled as the AudioVideo modality gets added? If you trace through the code you’ll realise that this won’t get logged, as when the Conversation was added, the AV modality state would have been Disconnected. We’re not keeping track of any changes that happen once the Conversation has been created.
What we need is another event handler, this time on the state of the AudioVideo modality. We only want to add it if we’ve decided that the Conversation isn’t already an AV call, but might become one later:
static void ConversationManager_ConversationAdded(object sender, Microsoft.Lync.Model.Conversation.ConversationManagerEventArgs e) { string ConversationID = e.Conversation.Properties[ConversationProperty.Id].ToString(); if (e.Conversation.Modalities[ModalityTypes.AudioVideo].State != ModalityState.Disconnected) { StoreConversation(e.Conversation, ConversationID); } else { e.Conversation.Modalities[ModalityTypes.AudioVideo].ModalityStateChanged += Program_ModalityStateChanged; } }
The event handler for ModalityStateChanged looks like this:
static void Program_ModalityStateChanged(object sender, ModalityStateChangedEventArgs e) { //in this case, any state change will be from Disconnected and will therefore indicate some A/V activity var modality = sender as Microsoft.Lync.Model.Conversation.AudioVideo.AVModality; string ConversationID = modality.Conversation.Properties[ConversationProperty.Id].ToString(); if (!ActiveConversations.ContainsKey(ConversationID)) { StoreConversation(modality.Conversation, ConversationID); modality.ModalityStateChanged -= Program_ModalityStateChanged; } }
We’re quite lucky here, because any changes at all to the state indicate some AudioVideo activity, as we know we’re starting out in the Disconnected state. Once we’ve made a decision to store the Conversation we will unwire the event handler, as there’s no point in maintaining it.
There’s an edge case here, which is that we only calculate and log out the duration when the entire Conversation finishes. As we’ve now allowed AV calls to start after the start of the Conversation, we should also be mindful that AV calls might terminate but the parent Conversation may live on if there is another active modality, such as Instant Message. Building out the ModalityStateChanged event handler to cope with this by reacting differently to the Disconnected state is something I haven’t done, but hopefully by now you’re happy enough with the concept that it’s trivial to add, should you wish.
Download The Code
This project is available for you to download and try out for yourself, or use a base for something else, on GitHub
Hi,
The MSDN site for Microsoft Lync says that “Id” or the conversation identifier is not constant and can change throughout the life of the conversation.
“[ConversationProperty.Id]”
So can we consider it as as unique identifier or the key while storing the conversation.
Moreover i have observed that in Conversation_Removed, we get ConversationProperty.Id as null. It happened with me in Lync 2013.
Please comment.
Here is the MSDN Link which says that
http://msdn.microsoft.com/en-us/library/lync/microsoft.lync.model.conversation.conversationproperty_di_3_uc_ocs14mreflyncwpf.aspx
Hi, I tried with the code, everything seems right, but I’m not getting the conversation ID. It appears that it is null unless I sent an IM first before I launch video call.
Hello,
This is great post. Can this work with Office 365 Lync Online?
Please help me to know this.
Hi ,
I read the following post in stackoverflow —
http://stackoverflow.com/questions/29236289/is-it-possible-to-have-lync-communicate-with-a-rest-api
The solutions mentioned in the stackoverflow post are more C# specific .
I am trying to track the conversations and monitor the incoming messages using the Lync REST API using Java .
Can you please suggest the flow of REST API calls to monitor the incoming the messags ?
–Chandra
Hi, just wanted to say I found this hugely helpful and very well written. Thanks!
I am having the Problem that at ConversationAdded and at ConversationRemoved i am trying to do the Stuff. But at ConversationRemoved i dont get a ConversationId from e.Conversation.Properties[ConversationProperty.Id].ToString().
I need an Identifier for the Conversation… i hope someone has an idea!
I’ve try your code, but unfortunately when i run the code, there is noting happen. Can you help me create code for read lync incoming message? I’ve tried many time with some shared code from MSDN or other website, but what i find is deadlocked. Pleas help..