Getting Started with Lync 2010 Model API

Summary:   This article introduces advanced Microsoft Lync 2010 API features and describes application scenarios where you use Microsoft Lync Model API.

Applies to:   Microsoft Lync 2010 | Microsoft Lync Server 2010 | Microsoft Lync 2010 API

Published:   January 2011 | Provided by:   John Austin, Microsoft | About the Author

Contents

In Adding Lync 2010 Features to Your Application (Part 2 of 2), you learned how to automate Microsoft Lync 2010 to start a new conversation using the BeginStartConversation method and Microsoft Lync 2010 SDK UI Automation. Lync SDK UI Automation is a useful feature when your application has the SIP address of another person and starts a Lync conversation window. Lync SDK UI Automation is used to specify the conversation modalities and application context that you plan to use in the conversation window. After the new Lync conversation window appears, you can dock it within your application. However, that is the limit of the programmatic control that you have on the conversation using Lync SDK UI Automation.

What if you want to do more than create automated Lync conversation window events? What if you could access the state and the events of the conversation that the conversation window represents? Actually, you can do more. You can use the Microsoft.Lync.Model.Conversation namespace to gain access to the state and events. What can you do with all of this? You can programmatically add and remove people from the conversation or connect additional modalities such as audio or video to the conversation. If the conversation includes the instant message modality, you can then capture and insert instant message text into the conversation as if your user was typing in the conversation window text entry control.

Customizing the conversation experience of your user is important. However, it is equally important that you can use Microsoft Lync 2010 API to develop ways to find potential conversation participants. The Lync 2010 API Microsoft.Lync.Model.ContactManager class exposes functionality to search for people or groups, and discover their availability and capability to join conversations. If your application provides a partial person or distribution group name, then you can provide your user with a list of people or groups in the user’s organization that match the partial name. Your search is not limited to matching names or SIP addresses. You can also run the same expert search that is available within the Lync 2010 UI. If your application captures a telephone number associated with a member of the user’s organization, you can perform a reverse-lookup to find the person at that telephone number.

With the ContactManager class, you can traverse the user’s contact list to find people and groups. You can also programmatically manage the contact list for the user. You can add users and groups, or remove them. You can move contacts into and out of custom groups and rename the custom group. This flexibility lets you integrate Lync 2010 into the user’s work environment.

In a future article, I will examine how conversations and the contact lists are managed with Lync 2010 API. For more information about Lync 2010 API, see Lync 2010 General Reference.

The balance of this article describes two UI suppression-related scenarios that use Lync 2010 API. Each scenario presents its own challenges and this article will give you a head start in meeting the challenges.

In the first scenario, the Lync UI is completely hidden so all communication features appear in your own application. In the second scenario, you still use the Lync UI. However, you extend the conversation window by presenting your own Microsoft Silverlight 4 application in the context of a conversation.

UI Suppression

The first scenario where you can use Lync 2010 API is where Lync is configured to suppress its UI. The Contoso SuperStore example in the next section describes this scenario. I have extended the features of the Contoso application to allow the customer to include both chat and audio/video. The following illustration shows the conversation window that the Contoso salesperson uses to talk to the customer. The Contoso salesperson can transfer the customer’s call to another salesperson and hold or forward customer calls.

Tip Tip

You do not have to create a custom application in order to communicate with your kiosk application. Your custom Lync 2010 API-based application is designed to interoperate with Lync 2010.

In this mode, Lync is not visible to the user and is basically running as a service for your application. This scenario requires you to design your own UI for the contact list and conversation features. In addition, you have to use a different code pattern to start Lync, sign a user in to Lync, and then exit Lync by calling the BeginShutdown method. There are two advantages to creating an Lync 2010 API-based application in this manner.

  • First, integrate Lync features into your application in such a way that a user is not even aware that Lync is installed. You can make these features seem like an organic part of your own application.

  • Second, ensure that users can only access the Lync features that you code into your application. You can restrict a user to communication in a specific modality with a specific set of users whom you define. When the Lync UI is suppressed, you remove communication features that you do not want used.

The following illustration shows a sample Microsoft Windows Forms application that contains the visual elements necessary to conduct an audio/video conversation with another Lync user. The application uses many of the classes in the Microsoft.Lync.Model.Conversation and Microsoft.Lync.Model namespaces to sign in to Lync, find contacts, and conduct conversations.

Forms application with visual elements that are used in conversation

LyncAVUISuppressedScreenCap

Before you configure Lync for UI suppression, review the following rules. First, the Lync Controls cannot be used by any process on a computer configured for UI suppression. This includes Silverlight 4 applications that are running in an Internet browser. Second, the classes in the Microsoft.Lync.Model.Extensibility namespace cannot be instantiated or used. Last, the code that you write to sign a user in to Lync must comply with three rules:

  • The Communicator.exe process representing Lync must be initialized by an instance of your own process before a user can sign in to Lync.

  • The process that initializes Lync must also shut down the Lync. If Lync is not shut down before your process ends, Lync cannot be initialized again.

    You can start multiple instances of your process but only the first process instance you start can initialize and shut down Lync.

  • All instances of your process share a common Lync sign-in identifier. This means that when one of the processes signs a user in, all other processes are signed in to Lync for that same user. If one process signs the user out, the user is signed out for all processes.

Important note Important

If the Lync UI is not suppressed then you must not attempt to initialize or shut down Lync. You can only sign a user in or out.

Configure Lync for UI Suppression

There are two ways to configure Lync for UI suppression. You can directly update the local system registry of the computer that runs Lync suppressed or you can create an installation script that starts the Lync setup application by using a switch that configures suppression at installation time. For more information about either method, see Understanding UI Suppression in Lync 2010 SDK.

If you edit the registry to suppress the Lync UI, the configuration change is not permanent until you right-click the Lync icon in the notification area to exit Lync, and then click Exit. After testing your code with suppression enabled, you should enable the Lync UI again. To do this, stop the Communicator.exe process, configure the registry key, set the value of the key to zero, and then save the change. After you update your registry, you can start Lync from the Start menu.

Initialize, Sign In, and Shut Down

This example declares a class field to store a Boolean flag that is initialized as false but is set to true if the running process succeeds in initializing Lync. The flag is examined before you call ModelBeginShutdown(AsyncCallback, object) to determine whether this running application is the process instance that initialized Lync.

The coding issues mentioned previously also apply to the following code examples.

Public Class Form1
    Delegate Sub FormCloseDelegate()
    Public myDelegate As FormCloseDelegate
    Private _Client As LyncClient
    Private _LyncContacts As ContactSubscription
    Private _InitializeFlag As Boolean = False


The following example uses the current state of the Lync client to determine an action. The code is enclosed in a try/catch block that will catch the Microsoft.Lync.Model.AlreadyInitializedException exception. Why catch AlreadyInitializedException when you should check for the uninitialized state of the client before LyncClient.BeginInitialize is called?. It is possible to create a situation using a scripting language where this application is started multiple times in succession. In this case, a condition can exist where two or more processes are trying to initialize Lync simultaneously.

    Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

        Try
            'Communicator.exe appears in Task Manager
            _Client = LyncClient.GetClient()
            Select Case _Client.State
                Case ClientState.Uninitialized
                    _Client.BeginInitialize(AddressOf InitializeCallback, Nothing)
                Case ClientState.SignedIn
                    MessageBox.Show("Another process has already signed you into Lync")
                Case ClientState.SignedOut
                    _Client.EndSignIn(_Client.BeginSignIn(Nothing, Nothing, Nothing, Nothing, Nothing))
            End Select
        Catch ex As AlreadyInitializedException
            MessageBox.Show("Another process has initialized Lync")
        Catch ex As Exception

        End Try

        DisplayContactsPanel()

    End Sub
    Private Sub InitializeCallback(ByVal ar As IAsyncResult)
        _Client.EndInitialize(ar)
        _InitializeFlag = True
        _Client.EndSignIn(_Client.BeginSignIn(Nothing, Nothing, Nothing, Nothing, Nothing))
    End Sub


When your user starts to shut down your application, your code must verify that the current running instance of your process initialized Lync. If it did initialize, then it is responsible for signing the user out and then shutting down Lync. This process is tricky unless you are aware of the trap you can code yourself into. The actions must run in order, and you then code successive actions so they are called in the event handler that handles the event raised by the previous action.

Caution noteCaution

You must allow the Lync shutdown operation to complete before your process ends.

The following example checks the current state of the client before it tries to sign the user out. If the client is signed in then the example signs the user out and passes a callback method in the first argument of BeginSignOut. The BeginShutdown object is coded inside the sign out callback so the code cannot shut down Lync until the user is signed out. I set the _InitializeFlag value to True if this process initializes Lync.

    Private Sub SignOutLync(ByVal ShutdownLync As Boolean)
        If _Client.State = ClientState.SignedIn Then
            _Client.BeginSignOut(AddressOf SignoutCallback, ShutdownLync)
        Else
            If _Client.State <> ClientState.Uninitialized And _InitializeFlag = True Then
                _Client.BeginShutdown(AddressOf EndBeginShutdown, Nothing)
            End If
        End If
    End Sub

    Private Sub SignoutCallback(ByVal result As IAsyncResult)
        ′Complete the sign out operation
        _Client.EndSignOut(result)
        Dim ShutDown As Boolean = CBool(result.AsyncState)

        If ShutDown And _InitializeFlag = True Then
            _Client.BeginShutdown(AddressOf ShutdownCallback, Nothing)  
        End If
    End Sub


    Private Sub ShutdownCallback(ByVal result As IAsyncResult)
        _Client.EndShutdown(result)
        If Me.InvokeRequired = True Then
            myDelegate = New FormCloseDelegate(AddressOf CloseTheForm)
            Me.Invoke(myDelegate)
        End If
    End Sub

    Private Sub CloseTheForm()
        TextBox1.Clear()
        TextBox1.Text = "Lync is shut down. Form is closing"
        TextBox1.Show()
        Me.Close()
    End Sub


The correct order of sign out and shut down can fail if the user presses the system close button. The following example shows how you should respond to the form closing event.

    Private Sub Form1_FormClosing(ByVal sender As Object, ByVal e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing

        If _Client.InSuppressedMode Then
            If _Client.State = ClientState.SignedIn Then
                e.Cancel = True
                SignOutLync(True)
            ElseIf _Client.State = ClientState.Uninitialized Then
                MessageBox.Show("Communicator.exe is uninitialized and will disappear from Task Manager list when this form closes")
            End If
        End If

    End Sub


The Microsoft Lync 2010 SDK documentation includes walkthrough topics that discuss initialization, sign in, sign out from Lync, and Lync shutdown. For information about how to sign in to Lync, see Walkthrough: Sign In to Lync with UI Suppressed (Lync 2010 SDK). For information about how to sign out of Lync and shut down Lync, see Walkthrough: Sign Out of Lync with UI Suppressed (Lync 2010 SDK).

Conversation Window Extension

The second scenario where you use Lync 2010 API is when the Lync UI is not suppressed and is visible to the user. In this scenario, you want to extend the functionality of Lync by creating a conversation window extension. For example, you want to extend the Lync conversation window by creating and registering a Silverlight 4 application that provides IM text translation for conversation participants that do not speak your user’s language. An example of such an extension is included in the Lync SDK sample library.

To build a conversation window extension, download the Microsoft Lync 2010 SDK. The installation program installs a set of example applications on your computer. The Conversation Translator sample is a Silverlight 4 application that discusses how to use the Lync 2010 API to interact with an ongoing conversation, and how to use a Microsoft Bing service in your application.

The Conversation Translator sample uses the Microsoft.Lync.Model.Conversation.Conversation class extensively to monitor incoming IM text for content to be translated and captures the local user’s text to be translated and then sent to the remote user who is using the InstantMessageModality.BeginSendMessage method.

The following illustration shows an extension to the Lync conversation window that translates IM text from one language to another by using the Bing translation web service. The translator extension that is added to the existing conversation window appears in the right side of the window. The extension uses the methods and events that are exposed by the Microsoft.Lync.Model.Conversation.Conversation class.

Lync conversation window extension

LyncConversationWindowExtensionTranslator

The Microsoft.Lync.Model.ContactManager class is an entry point for Lync 2010 API. ContactManager gives you access to the user’s Lync contact list. That access includes the ability to add, rename, or remove custom groups and contacts. ContactManager exposes all groups in a contact list as the ContactManager.Groups property. You read the property to return a collection of all kinds of groups encapsulated by the Microsoft.Lync.Model.Group.GroupCollection class. The class implements several interfaces that you can use to iterate on the collection to find any individual group in the collection. As you might expect, GroupCollection implements the IList, ICollection, and IEnumerable interface.

The contacts in a contact list cannot be accessed directly through ContactManager. Instead, contacts are accessed through the Microsoft.Lync.Model.Group.Group instances from the ContactManager.Groups property. If you want to get a single contact from the user’s contact list or your enterprise global address list (GAL), the most direct way is to call the ContactManagerGetContactByUri(string) method. You use this method if you have the SIP URI of the contact. Consider that a SIP address differs from an email address. For example, the SIP address johnc@contoso.com and the email address johnc@exchange.contoso.com both return contact instances. However, the contact returned using the email address cannot join a conversation and may not be in the GAL. Such a contact instance can be thought of as a “fake contact” and is not usable for conversations.

If you do not have the SIP address of the contact that you are looking for but you do have a partial or full name, you can then use ContactManager to search for the contact. This feature returns the first contact that matches the name that you supply in the lookup call. ContactManager does not limit the search results to the contacts that already appear in a user’s contact list. Instead, it searches for the name in the GAL. The first GAL entry found is returned. You can also search for distribution groups from the GAL using this search feature. For more information about searching for contacts and groups, see Walkthrough: Search For a Contact (Lync 2010 SDK).

Perhaps your application is designed to give someone limited access to your enterprise GAL. For example, you are creating an application to run on a public computer in a location such as the foyer of a retail store. You create a special set of sign-in credentials for the public computer that are used to sign your application in to Microsoft Lync Server 2010. When these credentials are used to sign in, the public computer is provisioned with an associated set of custom groups that you created.

The proposed application puts a customer in contact with the salesperson who can best help the customer find what they seek. This public application gives a customer access to a filtered list of store personnel so that they can get help finding products in the store. This kind of application collects information about a customer’s needs. Depending on the customer’s needs, the application builds a contact list of people who can help the customer.

In this case, you configure Lync on the computer to suppress its UI. Your application presents the list of possible store employee contacts and you do not present a generic contact search feature. After you define a set of custom contact groups for each department in the store and then acknowledge that a customer is looking for snow tires, you get the contacts from the predefined tire department group and load a list with the contacts that are available for Lync chat sessions. Now the customer can start a conversation with any available tire department salesperson.

You may ask why a customer would not merely walk to the tire department and grab the first salesperson they find. Well, this is a really big store and the tire department is at the far end on the third floor. This is a time saver, correct?

The following illustration shows an application that is running on a kiosk in the Contoso Super Store.

Contoso Super Store application

LyncModelAPIIntroArticleSampleScreencap

When a customer selects an item from the list, the radioButton1_CheckedChanged method runs:

using System;
using System.Windows.Forms;
using Microsoft.Lync.Model;
using Microsoft.Lync.Model.Group;


namespace GroupBuilder
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void radioButton1_CheckedChanged(object sender, EventArgs e)
        {
            SalespPerson_ListBox.Items.Clear();
            RadioButton checkedButton = sender as RadioButton;
            if (Tires_RadioButton.Checked == true)
            { 
                Group tireGroup;
                if (LyncClient.GetClient().ContactManager.Groups.TryGetGroup("Tire Department", out tireGroup))
                {
                    foreach (Contact salesPerson in tireGroup)
                    {
                        if (((ContactAvailability)salesPerson.GetContactInformation(ContactInformationType.Availability)) == ContactAvailability.Free)
                        {
                            ContactModel tireSaleperson = new ContactModel(salesPerson);
                            SalespPerson_ListBox.Items.Add(tireSaleperson);
                        }
 
                    }
                }
            }
        }

        private void AskForHelp_Button_Click(object sender, EventArgs e)
        {
            ContactModel chosenContact = SalespPerson_ListBox.SelectedItem as ContactModel;

            // Start a new IM conversation with the selected tire salesperson.
            StartAConversation(chosenContact.LyncContact);
            
        }
        private void StartAConversation(Contact chosenContact)
        {
            

            // In Additional Resources section appearing later in this article, see Walkthrough: Start an IM Conversation.
        }
    }
    internal class ContactModel 
    {
        private Contact thisContact;
        public Contact LyncContact
        {
            get
            {
                return thisContact;
            }
        }
        public override string ToString()
        {
            return thisContact.GetContactInformation(ContactInformationType.DisplayName).ToString();
        }
        public ContactModel(Contact aContact)
        {
            thisContact = aContact;
        }
    }

}


The previous example tries to get a predefined custom group that is named Tire Department and then iterates on all of the contact members of the custom group. The availability of each member is verified. If a contact is free, the contact instance is passed into the constructor of the ContactModel class that overrides ToString with a method that returns the display name of the contact. Finally, the new instance of the ContactModel class is added to SalespPerson_ListBox.

Important noteImportant

Custom groups are defined on a user-by-user basis. For this sample kiosk application to work correctly, the application must sign in a predefined user whose contact list includes custom groups for tires, sporting goods, and clothing. Since the kiosk application does not allow a customer to either sign in or sign out of Lync, the custom groups that you define for the predefined user are always available to this code.

The following example adds several Contoso Superstore tire department users to the kiosk user contact list. The custom group is created once and persists until it is removed.

        /// <summary>
        /// Run this method once to create a persisted custom group for kiosk user. 

        /// The group and contacts are available for all subsequent sign-in sessions on any computer where 

        /// kiosk user signs in.
        /// </summary>
        private void CreateCustomGroups()
        {
            ContactManager _ContactManager = LyncClient.GetClient().ContactManager;
           _ContactManager.EndAddGroup(_ContactManager.BeginAddGroup("Tire Department", null, null));
           Group tireGroup;
           if (_ContactManager.Groups.TryGetGroup("Tire Department", out tireGroup))
           {
               tireGroup.EndAddContact(tireGroup.BeginAddContact(_ContactManager.GetContactByUri("johnc@contoso.com"),null,null));
               tireGroup.EndAddContact(tireGroup.BeginAddContact(_ContactManager.GetContactByUri("JeffP@contoso.com"), null, null));
               tireGroup.EndAddContact(tireGroup.BeginAddContact(_ContactManager.GetContactByUri("TanjaP@contoso.com"), null, null));
           }
        }


The details for building this kind of contact list dynamically will be discussed in a future article. To review a walkthrough of the basic coding pattern, see Walkthrough: Fill a Contact List (Lync 2010 SDK).

The next step is to start a conversation with the salesperson selected in the previous example.

The Microsoft.Lync.Model.Conversation.ConversationManager class is the entry point to conversation programmability. The conversation manager includes two methods and two events. The ConversationManager.AddConversation method is called when you want to start a new conversation. It returns an instance of the Microsoft.Lync.Model.Conversation.Conversation class that you will use to conduct a conversation. The returned conversation instance must be provisioned with the contacts that you want to invite and the conversation modalities that are used during the conversation. The Conversation instance is also the source of all conversation-related events that you register for and handle. Each participant and conversation modality cached within the Conversation instance exposes its own set of methods, properties, and events. For an overview of the conversation object model, see Conversation Class (Lync 2010 SDK).

Important note Important

Updating your application UI using logic in any Lync event handler requires you to assemble the appropriate event data from the Lync thread for use in your own UI thread. This process is usually implemented by declaring a delegate and instantiating it with a method that complies with the delegate. The delegate is invoked by using the this.Invoke(Delegate method, params object[] args); syntax.

The next set of examples creates a new conversation, invites a contact, sends IM text, and then receives IM text in return. Events are handled for the ConversationManager, Conversation, and the conversation Microsoft.Lync.Model.Conversation.InstantMessageModality instance. The following actions are performed:

  • ConversationForm_Load: A new conversation is created.

  • ConversationsManager_ConversationAdded: Register for events on the conversation and add a salesperson to the conversation.

  • Conversation_ParticipantAdded: Register for events on the salesperson’s IM modality and then send the first text message.

  • myInstantMessageModality_MessageReceived: Catch messages from the salesperson and display the messages.

  • myInstantMessageModality_ComposingChanged: Notify the local user that the salesperson is typing a response to a question.

        #region private fields
        private Conversation _Conversation;
        private InstantMessageModality _RemoteIMModality;
        private InstantMessageModality _LocalIMModality;
        private LyncClient _LyncClient;
        private ClientWrap _ClientModel;
        private string textMessage;
        #endregion

        /// <summary>
        /// Opens a conversation host window, registers for conversation manager events, and creates a new conversation
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void ConversationForm_Load(object sender, EventArgs e)
        {
            _LyncClient.ConversationManager.ConversationAdded += ConversationsManager_ConversationAdded;
            _LyncClient.ConversationManager.AddConversation()
        }

        /// <summary>
        /// Handles ConversationAdded state change event raised on ConversationsManager
        /// </summary>
        /// <param name="source">ConversationsManager—The source of the event.</param>
        /// <param name="data">ConversationsManagerEventArgs—The event data. The incoming Conversation is obtained here.</param>
        void ConversationsManager_ConversationAdded(Object source, ConversationManagerEventArgs data)
        {
            // Register for Conversation state changed events.
            data.Conversation.ParticipantAdded += Conversation_ParticipantAdded;
            data.Conversation.StateChanged += Conversation_ConversationChangedEvent;

            // Add a remote participant. The conversation invitation is not sent to the
            // new participants until the first call to BeginSendMessage is made locally.
            if (AddContactToConversation(data.Conversation, _LyncClient.ContactManager.GetContactByUri("johnc@contoso.com")) == true)
            {
               ((InstantMessageModality)data.Conversation.Modalities[ModalityTypes.InstantMessage]).ModalityStateChanged += myInstantMessageModality_ModalityStateChanged;

            }
        }

        /// Adds a participant to a conversation
        /// </summary>
        /// <param name="pConversation">Conversation Conversation to add contact to</param>
        /// <param name="pGroupName">string Name of group to get Contact from.</param>
        /// <param name="pContactUri">string Contact Uri.  Example: SIP:davidp@contoso.com</param>
        public Boolean AddContactToConversation(Conversation pConversation, Contact pContact)
        {
            Boolean returnValue = false;


            // Find out if selected contact can join an IM conversation.
            if (0 == ((ContactCapabilities) pContact.GetContactInformation(ContactInformationType.Capabilities) & ContactCapabilities.RenderInstantMessage))
            {
                // Contact is not available for conversation for any of several possible reasons including:
                //  * contact is offline or in d-n-d mode.
                //  * contact is signed into Lync from a device that cannot render IM text.
                return false;
            }

            // Only add the contact to the conversation if they are enabled for unified communications.
            // If enabled for unified communications, contact can participate in 3+ person conversations and
            // audio/video modalities in this conversation.
            if (pContact.UnifiedCommunicationType == UnifiedCommunicationType.Enabled)
            {
                pContact.ContactInformationChanged += selectedContact_ContactInformationChanged;
                try
                {
                    pConversation.AddParticipant(pContact);
                    returnValue = true;
                }
                catch (ItemAlreadyExistException) { };
            }
            return returnValue;
        }

        /// <summary>
        /// ParticipantAdded callback handles ParticipantAdded event raised by Conversation
        /// </summary>
        /// <param name="source">Conversation Source conversation.</param>
        /// <param name="data">ParticpantCollectionEventArgs Event data</param>
        void Conversation_ParticipantAdded(Object source, ParticipantCollectionChangedEventArgs data)
        {

            // The ParticipantAdded event is raised for every conversation participant,
            // including the local user. If this is an IM conversation then you only want
            // to register for the remote user’s IM modality events. 
            if (data.Participant.IsSelf == false)
            {
                if (((Conversation)source).Modalities.ContainsKey(ModalityTypes.InstantMessage))
                {
                    _RemoteIMModality = data.Participant.Modalities[ModalityTypes.InstantMessage] as InstantMessageModality;
                    _RemoteIMModality.InstantMessageReceived += myInstantMessageModality_MessageReceived;
                    _RemoteIMModality.IsTypingChanged += myInstantMessageModality_ComposingChanged;

                    // Create message text object including text formatting information.
                    IDictionary<InstantMessageContentType, string> textMessage = new Dictionary<InstantMessageContentType, string>();
                    textMessage.Add(InstantMessageContentType.PlainText, "I’m looking for good snow tires, can you help me?");

                    Object[] _asyncState = { textMessage, _Conversation.Participants, ((InstantMessageModality)_Conversation.Modalities[ModalityTypes.InstantMessage]) };

                    try
                    {
                         ((InstantMessageModality)_Conversation.Modalities[ModalityTypes.InstantMessage]).BeginSendMessage(
                        textMessage
                        , SendMessageCallback
                        , _asyncState);
                    }
                    catch (UnauthorizedAccessException)
                    {
                        MessageBox.Show("Contact cannot accept instant messages");
                    }
                }
            }
            else
            {
                _LocalIMModality = data.Participant.Modalities[ModalityTypes.InstantMessage] as InstantMessageModality;
                _LocalIMModality.InstantMessageReceived += myInstantMessageModality_MessageReceived;
            }
        }
        /// <summary>
        /// Async callback method invoked by InstantMessageModality instance when SendMessage is complete
        /// </summary>
        /// <param name="_asyncOperation">IAsyncResult The operation result</param>
        /// 
        private void SendMessageCallback( IAsyncResult ar)
        {
            if (ar.IsCompleted == true)
            {
                Object[] _asyncState = (Object[])ar.AsyncState;
                IDictionary<InstantMessageContentType, string> _messageText = (IDictionary<InstantMessageContentType, string>)_asyncState[0];
                IList<Participant> _participantList = (IList<Participant>)_asyncState[1];
                try
                {
                    ((InstantMessageModality)_asyncState[2]).EndSendMessage(ar);
                    string sentText = string.Empty;
                    if (_messageText.TryGetValue(InstantMessageContentType.PlainText, out sentText))
                    {
                        MessageBox.Show(sentText + " was sent");
                    }
                }
                catch (LyncClientException ex)
                {
                    MessageBox.Show("Contact cannot receive instant messages. Lync Client Exception: " + ex.Message);
                }

            }
        }


        /// <summary>
        /// Callback is invoked when IM Modality state changes upon receipt of message
        /// </summary>
        /// <param name="source">InstantMessageModality Modality </param>
        /// <param name="data">SendMessageEventArgs The new message.</param>
        void myInstantMessageModality_MessageReceived(Object source, MessageSentEventArgs data)
        {
            IDictionary<InstantMessageContentType,string> messageFormatProperty = data.Contents;
            if (messageFormatProperty.ContainsKey(InstantMessageContentType.PlainText))
            {
                string outVal = string.Empty;
                string Sender = (string)((InstantMessageModality)source).Participant.Contact.GetContactInformation(ContactInformationType.DisplayName);
                if (messageFormatProperty.TryGetValue(InstantMessageContentType.PlainText, out outVal))
                {
                    MessageBox.Show(Sender + " sends: " + outVal.ToString());
                }
            }
        }


        /// <summary>
        /// Handles event that is raised when conversation participant is entering a message to be sent
        /// </summary>
        /// <param name="source">InstantMessageModality. The IM modality owned by the composing participant.</param>
        /// <param name="data">ComposingChangeEventArgs. Event argument data.</param>
        void myInstantMessageModality_ComposingChanged(Object source, IsTypingChangedEventArgs data)
        {
            if (data.IsTyping == true)
            {
                MessageBox.Show( ((InstantMessageModality)source).Participant.Contact.GetContactInformation(ContactInformationType.DisplayName) + " is typing");
            }
        }




The prior example is a simplified example that shows how to program IM conversations. For more information, see Walkthrough: Start an IM Conversation (Lync 2010 SDK).

In a previous example, a series of three null user-credential values are passed as arguments to BeginSignIn. Passing this kind of value is not always necessary. In certain situations, you must pass a set of user sign-in credentials in these arguments.

The following table represents the scenarios where arguments should be passed. The Microsoft Windows credentials that appear in the first column are the credentials a local user uses to sign in to Windows. They can be either the network credentials that were used to give the user access to network resources or local credentials assigned in the local computer account. LyncClient attempts to sign in to Lync with argument credentials and reverts to the Windows credentials if the argument credentials are null or invalid.

Windows credentials

BeginSignIn arguments 1, 2, and 3

LyncClient action

Lync sign-in result

Network

null.

Use Windows credentials + saved password.

User signed in.

Network

null.

Use Windows credentials. No saved password.

Sign-in fails. CredentialRequested event is raised.

Network

Valid network domain/user name, and password.

Use argument credentials.

User signed in.

Network

Valid domain/user name. Invalid password.

Use argument domain/user name + saved password.

User signed in.

Network

Valid domain/user name. Invalid password.

Use argument domain/user name. No previous saved password.

Sign-in fails. CredentialRequested event is raised.

Network

Invalid domain/user name. Invalid password.

Use Windows credentials. No saved password.

Sign-in fails. CredentialRequested event is raised.

Local

Valid network domain/user name, and password.

Use argument domain/user name and password.

User signed in with network credentials.

Local

Valid network domain/user name. Invalid or null password.

Use argument domain/user name + saved password.

User signed in with network credentials.

Local

Valid network domain/user name. Invalid or null password.

Use argument domain/user name. No saved password.

Sign-in fails. CredentialRequested event is raised.

Local

Null.

Use Windows credentials.

Sign-in fails. CredentialRequested event is raised.

Local

Invalid domain/user name. Invalid password.

Use Windows credentials. No saved password.

Sign-in fails. CredentialRequested event is raised.

In this article, you learned how to implement Lync conversation and contact list features. By using programmatic access to these Lync features, you can customize your business application and increase the value of your application.

Most of this article provides general guidance and simple examples that can be used to implement Lync 2010 API. The discussion and examples demonstrate the correct way to start and shut down Lync when the Lync UI is suppressed. UI suppression restrictions:

  • The Lync Controls and Lync UI Automation classes cannot be used when the Lync UI is suppressed.

  • An application process must initialize Lync before a user signs in to Lync.

  • Additional application processes that run on one computer share the initialized Lync process and user sign-in credentials.

This article also discusses development options for the Lync 2010 API developer where the Lync Model API is used to add Lync conversation and contact list features to a line of business application. For more information about adding features to an application, go to the Unified Communications Forums.

John Austin is a programmer/writer in the Lync client SDK documentation team. He has been writing technical documentation for four years. Prior to working for Microsoft, John spent two decades as a software developer.

Show: