Developing Lync Applications - Pro Office 365 Development, Second Edition (2014)

Pro Office 365 Development, Second Edition (2014)

Chapter 7. Developing Lync Applications

The Lync features are provided to developers to make custom Lync applications or embed Lync features within an existing application. In this chapter I will show you how to build a custom Lync application. You’ll start out with a simple application that includes a presence indicator in a WPF application. You’ll then use more of the standard Lync controls to enhance the application by adding a contact search feature and a custom contact list. Finally, you’ll use Lync automation to start a conversation and dock the conversation window in your custom application.

This was introduced in Chapter 1, but as a brief review, the Lync components that you’ll use in this chapter are:

· Lync controls: You will add controls such as PresenceIndicator and ContactSearch to integrate Lync functionality directly into the WPF application.

· Lync automation: You will call Lync automation to control the running Lync client on the local machine and send conversation information.

These are shown in Figure 7-1.

image

Figure 7-1. Lync client architecture

The Lync 2013 client application has a key role in the overall architecture. It manages the connection to the server component (Lync Online 2013). It also monitors client activity to report on presence such as Available or Idle.

Image Caution Lync is the only Office 365 feature that requires a client installation. In order to use any of the Lync features, you must install the Lync 2013 client. A custom Lync application does not replace the need for the Lync client. Rather, the custom application will expect a running instance of the Lync client on the client machine.

If you have not already done so, you will need to download and install the Lync 2013 SDK. You can download this at www.microsoft.com/en-us/download/details.aspx?id=36824. This will download the LyncSdk.exe application. Run this applications and you’ll see the installation screen shown in Figure 7-2. Follow the instructions using all of the default values to install the SDK on your machine. This will provide the necessary assemblies for Lync development.

image

Figure 7-2. Installing the Lync 2013 SDK

Creating the Lync Application

You’ll start by creating a standard WPF application and then add references to the assemblies included with the SDK. This will allow you to include the Lync controls, such as the presence indicator, in your application.

Creating the Visual Studio Project

Start Visual Studio 2013 and create a new project. Select the WPF Application project template, which you’ll find in the Windows Desktop folder. Enter the project name LyncApp as shown in Figure 7-3 and click the OK button to create the project.

image

Figure 7-3. Creating a WPF application project

This project template creates a standard WPF application; now you’ll need to add the Lync controls. In the Solution Explorer, right-click the References folder and select the Add Reference link. Click the Browse button and navigate to the location where the Lync SDK was installed. This is normally C:\Program Files (x86)\Microsoft Office 2013\LyncSDK. Then go to the Assemblies\Desktop subfolder. Add the following DLLs as shown in Figure 7-4:

· Microsoft.Lync.Controls

· Microsoft.Lync.Controls.Framework

· Microsoft.Lync.Model

· Microsoft.Lync.Utilities

image

Figure 7-4. Adding the Lync control assemblies

By including these SDK assemblies, you can include any of the Lync controls in your application. We will demonstrate several of these controls in this chapter. If you expand the Toolbox, you can see the controls that are available, as shown in Figure 7-5.

image

Figure 7-5. The Lync 2013 SDK Controls available in the Toolbox

Adding a Presence Indicator

You’ll start by using the PresenceIndicator control. In the MainWindow.xaml file, edit the XAML code using the code shown in Listing 7-1. The code that must be added is shown in bold. Also, you’ll need to enter a different sip address, one that is available from your O365 environment.

Listing 7-1. The Initial XAML Implementation

<Window x:Class="LyncApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls
="clr-namespace:Microsoft.Lync.Controls;assembly=Microsoft.Lync.Controls"

Title="MainWindow" Height="Auto" Width="Auto">
<Grid>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<controls:PresenceIndicator
x:Name="Presence"
Source="sip:mark@apress365.onmicrosoft.com"
PhotoDisplayMode="Large"
/>
<TextBlock
Text="{Binding DisplayName, ElementName=Presence}"
Margin="4,0,0,0"
VerticalAlignment="Center"
/>
</StackPanel>
</Grid>
</Window>

The following line makes all of the Lync controls available to the application:

xmlns:controls
="clr-namespace:Microsoft.Lync.Controls;assembly=Microsoft.Lync.Controls"

And this line includes a presence indicator in the window:

<controls:PresenceIndicator
x:Name="Presence"
Source="mark@apress365.onmicrosoft.com "
PhotoDisplayMode="Large"
/>

The TextBlock control is bound to the PresenceIndicator and will show the name of the person associated with the specified sip address.

Testing the Initial Application

This initial application does not require any implementation in the code-behind file, Window1.xaml.cs. Make sure the Lync client is installed and you are signed in to the Lync server.

Press F5 to build and run the application. The window will show the presence status of the specified address, as demonstrated in Figure 7-6.

image

Figure 7-6. Running the initial application

Hover over the presence indicator control to view the contact information and then click the expand button in the bottom-right corner. The expanded view may look similar to Figure 7-7, depending on how the user was set up in Office 365.

image

Figure 7-7. Viewing the contact card

While this application is still running, use the Lync client to change your status. Your WPF application should reflect the status change. Also try entering a note using the Lync client and then hover over the presence indicator in your custom application to see the new note, as shown inFigure 7-8.

image

Figure 7-8. Displaying the status note

Using Lync Controls

The Lync SDK provides several controls that you can drag and drop onto your application. This allows you to easily embed these Lync features into a custom application. You already used one of these, PresenceIndicator, in the initial implementation.

Dynamically Adjusting the Presence Indicator

Admittedly, displaying the presence of a hard-coded Lync address is not very useful. You’ll extend the application to allow the user to select a contact from a drop-down list. The presence of the selected contact will then be displayed. The code-behind class will control the Lync address of the presence indicator based on the contact that was selected. You’ll also provide an option to start a conversation with the selected contact.

Edit the Window1.xaml file and replace the entire contents with the code shown in Listing 7-2.

Listing 7-2. Revised XAML Implementation

<Window x:Class="LyncApp.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls
="clr-namespace:Microsoft.Lync.Controls;assembly=Microsoft.Lync.Controls"
Title="MainWindow" Height="Auto" Width="Auto">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="54" />
<RowDefinition />
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<TextBlock Margin="15" Text="View Presence of Selected Contact:" />
<ComboBox x:Name="cboContacts" Width="200" Height="25" Margin="0,0,20,0"
SelectionChanged="cboContacts_SelectionChanged" />
<!-- Presence Indicator -->
<controls:PresenceIndicator PhotoDisplayMode="Large" x:Name="lyncPresence" />
<!-- Start Instant Message -->
<controls:StartInstantMessagingButton x:Name="lyncStartMessage" Height="25"
Margin="20,10,10,10" />
</StackPanel>
</Grid>
</Window>

The Design tab should look like Figure 7-9.

image

Figure 7-9. The design of the revised window

In addition to the PresenceIndicator control, the window now has a ComboBox that will contain the available contacts and a StartInstantMessagingButton control that will be used to start a conversation with the selected contact.

To populate the ComboBox, you’ll need a class to represent the values of each contact. This simple class has two members, name and sipAddress. From the Solution Explorer, right-click the LyncApp project and select the New and then then Class links. Enter the class nameContact.cs. Then enter the following code for its implementation:

namespace LyncApp
{
public class Contact
{
public string name { get; set; }
public string sipAddress { get; set; }
}
}

The initial application did not require any implementation in the code-behind class. However, now you’ll need to provide code to update the presence indicator based on the selected contact. Enter the code shown in Listing 7-3.

Listing 7-3. Implementation of Window1.xaml.cs Code-Behind Class

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;

namespace LyncApp
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.Loaded += MainWindow_Loaded;
}

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
BuildContactList();
LoadContacts();
}

private List<Contact> _contacts = new List<Contact>();

protected void BuildContactList()
{
// Build collection of valid contacts -- using Contact class
_contacts.Add(new Contact()
{ name = "Mark Collins",
sipAddress = "sip:mark@apress365.onmicrosoft.com" });
_contacts.Add(new Contact()
{ name = "Michael Mayberry",
sipAddress = "sip:michael@apress365.onmicrosoft.com" });
_contacts.Add(new Contact()
{ name = "Sahil Malik",
sipAddress = "sip:sahilmalik@winsmarts.com" });
}

protected void LoadContacts()
{
// Bind collection to combo box
cboContacts.ItemsSource = _contacts;
cboContacts.DisplayMemberPath = "name";
cboContacts.SelectedValuePath = "sipAddress";
}

private void cboContacts_SelectionChanged
(object sender, SelectionChangedEventArgs e)
{
// Set the sipAddress of the selected item as the
// source for the presence indicator
lyncPresence.Source = cboContacts.SelectedValue;

// Set the start instant message button
lyncStartMessage.Source = cboContacts.SelectedValue;
}
}
}

This code creates a collection of Contact classes. The BuildContactList() method populates this collection using hard-coded values. You will need to change this implementation to use contacts that are reachable from your client. The LoadContacts() method binds this collection to the ComboBox.

The cboContacts_SelectionChanged() event handler is where the real work is done. This simply sets the Source property on both of the Lync controls. This will specify the Lync address used for determining the presence of the selected contact and the participant when starting a conversation.

Press F5 to build and run the application. Select a contact and their status will be displayed by the presence indicator, as shown in Figure 7-10.

image

Figure 7-10. Displaying the status of the selected contact

Click the button next to the presence indicator and a new conversation window will appear. A sample conversation is shown in Figure 7-11.

image

Figure 7-11. A conversation window launched by the custom application

Searching for Contacts

The Lync controls include a couple that are useful for searching for a contact:

· ContactSearch: Searches for contacts

· ContactList: Displays the standard Lync client contact lists

This functionality is identical to what you’ll find on the standard Lync 2013 client. To include these controls in your application, add the following code to the Windows1.xaml file, just after the current StackPanel control:

<!-- Contact controls -->
<StackPanel Orientation="Horizontal" Grid.Row="1">
<controls:ContactSearch Width="300" />
<controls:ContactList Width="300" />
</StackPanel>

Image Tip The ContactSearch control is actually a combination of two controls. The ContactSearchInputBox control is used to receive the search criteria and the ContactSearchResultList control displays the contacts that were found. You can enter these as separate controls, which will allow you to configure where each is placed in the application. You would need to bind the two controls together so the results will be automatically displayed when the input is changed. The code would be similar to:

<controls:ContactSearchInputBox x:Name="search"/>
<controls:ContactSearchResultList
ItemsSource="{Binding Results, ElementName=search, Mode=OneWay}"
ResultsState="{Binding SearchState, ElementName=search, Mode=OneWay}" />

There is no need to include any additional implementation to the code-behind class. Press F5 to build and run the application. In the search box, start typing a contact’s name, and the matching contacts will be displayed, as shown in Figure 7-12.

image

Figure 7-12. Searching for a contact

Image Note The search feature looks for people in your contact list. It also searches for all other people in your organization. Generally this is limited to users defined in your Office 365 account. If you have configured federation, the search may be able to include these federated domains as well, depending on domain restrictions.

The second control you added displays your contact list, and this is identical to the way the Lync 2013 client displays these. There are several ways to organize the contacts. I chose to group by relationship, so friends and family are listed first, followed by colleagues. If you hover the mouse over a contact, the contact card is displayed. This provides links for starting a conversation.

Adding a Custom Contact List

In a custom application, you might need to control the contacts that are listed based on other application data and/or business rules. For example, you might want the user’s immediate supervisor to be included, as well as other specific individuals for certain escalation rules. Whatever the reason may be, you’ll want to provide a list of contacts that are application driven and not based on the user’s contact list.

This is easy to do by using the CustomContactList control. You add this control to the application and then set its list of contacts in the code-behind class. In the Window1.xaml file, replace the ContactList control with the CustomContactList control, as shown here:

<StackPanel Orientation="Horizontal" Grid.Row="1">
<controls:ContactSearch Width="300" />
<!--<controls:ContactList Width="300" />-->
<controls:CustomContactList x:Name="lyncCustomList" Width="300" />
</StackPanel>

In the code-behind class, Window1.xaml.cs, add the LoadCustomContacts() method using the following code:

protected void LoadCustomContacts()
{
lyncCustomList.ItemsSource = (from C in _contacts select C.sipAddress);
}

This code simply loads the same contacts that you previously populated in the ComboBox. Then call the LoadCustomContacts() method in the MainWindow_Loaded() event handler, adding the line of code shown in bold.

void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
BuildContactList();
LoadContacts();
LoadCustomContacts();
}

Press F5 to build and run the application. Your custom contact list should include the same people that were included in the ComboBox, as shown in Figure 7-13.

image

Figure 7-13. Using the custom contact list

Using Lync Automation

For the next exercise we’ll show you how to use Lync automation to start a conversation and dock the window inside your custom application. This process requires interoperation between your custom WPF application and the Lync application.

Adding the Docking Host Location

Your application will need to include UI elements to interoperate with Lync. You will need to reference the classes in the XAML code needed for this and add a place for the Lync conversation to dock.

1. From the Solution Explorer, right-click the Assemblies folder and select the Add Reference link. Then select the Assemblies link and add WindowsFormsIntegration, System.Windows.Forms and System.Drawing. Then, in theMainWindow.xaml file, add the following namespaces:

xmlns:interop="clr-namespace:System.Windows.Forms.Integration;assembly=WindowsFormsIntegration"
xmlns:forms="clr-namespace:System.Windows.Forms;assembly=System.Windows.Forms"

2. Now you need to add the UI elements to accept the conversation window during docking. Add an additional row to the main grid by adding the following to the Grid.RowDefinitions section:

<RowDefinition />

3. Just before the closing Grid tag (after the StackPanel controls), add the following XAML code:

<interop:WindowsFormsHost x:Name="formHost" Grid.Row="2">
<forms:Panel x:Name="formPanel"></forms:Panel>
</interop:WindowsFormsHost>

This adds the host and panel elements for the docking process. The window layout should look like Figure 7-14. When the automation code is called to start the application and dock the window within your application, the conversation window will be displayed in this Panel control.

image

Figure 7-14. Adding the placeholder for the conversation window

Calling Lync Automation

Using Lync automation requires the application to respond to an event within the custom programming rather than the Lync controls. For this requirement, you will adjust the user interface to include a button to start the conversation.

1. In the MainWindow.xaml file and add the following code just after the StartInstantMessagingButton control. The Design tab should appear similar to Figure 7-15.

<!-- Automation Button -->
<Button x:Name="btnConversationStart" Content="Start Conversation" Margin="10"
Click="btnConversationStart_Click" />

image

Figure 7-15. Adding a custom button for Lync automation

2. Now you’ll implement the code-behind class. Open the MainWindow.xaml.cs file and add the following namespaces to the top of the file:

using Microsoft.Lync.Model;
using Microsoft.Lync.Model.Conversation;
using Microsoft.Lync.Model.Extensibility;

3. Just inside the MainWindow class definition, add these private members, which will be used to manage the conversation window and the Lync objects that are needed.

private LyncClient _client = null;
private Automation _automation = null;
private string _remoteUri = "";
private ConversationWindow _conversationWindow = null;

4. Then add the following delegates, just after the private members. You’ll need these later when managing the conversation window.

private delegate void FocusWindow();
private delegate void ResizeWindow(Size newSize);

5. The LyncClient and Automation objects should be initialized when the application starts. Add the following code to MainWindow constructor:

_client = LyncClient.GetClient();
_automation = LyncClient.GetAutomation();

6. Add all of the methods shown in Listing 7-4 to the end of the MainWindow class.

Listing 7-4. The automation event handlers

private void btnConversationStart_Click(object sender, RoutedEventArgs e)
{
_remoteUri = cboContacts.SelectedValue.ToString();

ConversationManager conversationManager = _client.ConversationManager;
conversationManager.ConversationAdded
+= new EventHandler<ConversationManagerEventArgs>
(conversationManager_ConversationAdded);
Conversation conversation = conversationManager.AddConversation();
}

private void conversationManager_ConversationAdded(object sender,
ConversationManagerEventArgs e)
{
e.Conversation.ParticipantAdded
+= new EventHandler<ParticipantCollectionChangedEventArgs>
(Conversation_ParticipantAdded);
e.Conversation.AddParticipant
(_client.ContactManager.GetContactByUri(_remoteUri));

_conversationWindow = _automation.GetConversationWindow(e.Conversation);

//wire up events
_conversationWindow.NeedsSizeChange += _conversationWindow_NeedsSizeChange;
_conversationWindow.NeedsAttention += _conversationWindow_NeedsAttention;

//dock conversation window
_conversationWindow.Dock(formHost.Handle);
}

void Conversation_ParticipantAdded(object sender,
ParticipantCollectionChangedEventArgs e)
{
// add event handlers for modalities of participants:
if (e.Participant.IsSelf == false)
{
if (((Conversation)sender)
.Modalities.ContainsKey(ModalityTypes.InstantMessage))
{
((InstantMessageModality)e.Participant
.Modalities[ModalityTypes.InstantMessage]).InstantMessageReceived
+= new EventHandler<MessageSentEventArgs>
(ConversationTest_InstantMessageReceived);

((InstantMessageModality)e.Participant
.Modalities[ModalityTypes.InstantMessage]).IsTypingChanged
+= new EventHandler<IsTypingChangedEventArgs>
(ConversationTest_IsTypingChanged);
}
Conversation conversation = (Conversation)sender;

InstantMessageModality imModality =
(InstantMessageModality)conversation
.Modalities[ModalityTypes.InstantMessage];

IDictionary<InstantMessageContentType, string> textMessage =
new Dictionary<InstantMessageContentType, string>();
textMessage.Add(InstantMessageContentType.PlainText, "Hello, World!");

if (imModality.CanInvoke(ModalityAction.SendInstantMessage))
{
IAsyncResult asyncResult = imModality.BeginSendMessage(
textMessage,
SendMessageCallback,
imModality);

}
}
}

private void SendMessageCallback(IAsyncResult ar)
{
InstantMessageModality imModality = (InstantMessageModality)ar.AsyncState;

try
{
imModality.EndSendMessage(ar);
}
catch (LyncClientException lce)
{
MessageBox.Show("Lync Client Exception on EndSendMessage " + lce.Message);
}

}

private void ConversationTest_IsTypingChanged(object sender,
IsTypingChangedEventArgs e)
{

}

private void ConversationTest_InstantMessageReceived(object sender,
MessageSentEventArgs e)
{

}

private void _conversationWindow_NeedsAttention(object sender,
ConversationWindowNeedsAttentionEventArgs e)
{
FocusWindow focusWindow = new FocusWindow(GetWindowFocus);
Dispatcher.Invoke(focusWindow, new object[] { });
}

private void _conversationWindow_NeedsSizeChange(object sender,
ConversationWindowNeedsSizeChangeEventArgs e)
{
Size windowSize = new Size();
windowSize.Height = e.RecommendedWindowHeight;
windowSize.Width = e.RecommendedWindowWidth;
ResizeWindow resize = new ResizeWindow(SetWindowSize);
Dispatcher.Invoke(resize, new object[] { windowSize });
}

private void SetWindowSize(Size newSize)
{
formPanel.Size = new System.Drawing.Size(
(int)newSize.Width, (int)newSize.Height);
}

private void GetWindowFocus()
{
Focus();
}

When the Start Conversation button is clicked, the btnConversationStart_Click event handler is called. This gets the address of the selected contact and stores it in the _remoteUri class member. It then gets the ConversationManager from the Lync client and wires up an event handler for when a conversation is added. It then proceeds to add a conversation.

This invokes the conversationManager_ConversationAdded event handler. This then wires up an event handler for when a participant is added and adds a participant, using the address of the selected contact. In then gets the ConversationWindow using the Automationobject associated to the Lync client. It then wires up the event handlers for the NeedsSizeChange and NeedsAttention events. Finally, it docks the window in the placeholder defined in the XAML file. This is what actually embeds the conversation window inside your custom application window.

The Conversation_ParticipantAdded event handler is then invoked. It is actually called twice because there are two participants, you, the sender or initiator, and the selected contact. This function checks the IsSelf property and effectively ignores this event if raised on behalf on the initiator. For the other participant, it sends a hard-coded message, “Hello, World!”

Image Note The modality defines the type of message that is being sent. In addition to instant messages (text), Lync ­supports audio and video messaging as well as application and screen sharing. A message can have more than one modality.

The Conversation_ParticipantAdded event handler wires up event handlers for the InstantMessageReceived and IsTypingChanged events. These event handlers allow your application to react to when these event occur. An InstantMessageModality object is obtained from the conversation. This is used to send the message asynchronously by calling its BeginSendMessage method. The SendMessageCallback method is then called when the message has been sent. It calls the EndSendMessage methods on theInstantMessageModality object.

The rest of the event handlers simply respond to events that are raised as messages are send back and forth. The InstantMessageReceived and IsTypingChanged event handlers have no implementation. This solution relies on the conversation window implemented by the Lync client. However, you could implement these events and display the received message in your own application controls.

Testing the Application

Press F5 to build and run the application. Select the desired contact from the drop-down list and click the Start Conversation button. The Lync client will begin the conversation and present the window within your custom application, as shown in Figure 7-16.

image

Figure 7-16. Using Lync automation to dock a conversation window in a WPF application

Summary

This chapter showed how to incorporate the functionality of the Lync client into a custom WPF application.

· You used the controls provided by the Lync SDK to easily provide communication functionality without much coding.

· You created a custom contact list, demonstrating how you can organize and present available contacts based on your own internal personnel data.

· You used Lync automation to provide access to the Lync communication window, presented within your custom application UI space.

Adding real-time communication functionality can greatly extend the usability of your custom applications. The techniques covered in this chapter provide you the ability to deliver a great experience to your users.