Building Lync IM Conversation Windows: Helper Classes (Part 5 of 5)

Summary:   This article is the fifth in a series of five articles that describe how to build a Microsoft Lync 2010 IM conversation window that features a spelling checker, and then add the spelling checker to the conversation window. This article describes how to sign in and sign out of Lync 2010, and add Microsoft Bing spelling checker web service calls to an application.

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

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

Contents

  • Introduction

  • Bing Spelling Checker Methods

  • ClientModel Helper Class

  • CredentialForm Window

  • Conclusion

  • Additional Resources

This article is the fifth in a five-part series of articles about how to build a Lync 2010 IM conversation window.

Introduction

Building Lync IM Conversation Windows: Lync 2010 API Event Handlers (Part 4 of 5) describes the methods that handle the events that are raised by Microsoft Lync 2010 API class instances for IM conversations. Part 5 describes the code that is used to add Microsoft Bing spelling checker web service calls, add Lync 2010 sign in method calls, and add a Microsoft Windows Presentation Foundation (WPF) conversation window that accepts user network credentials.

Each article in this series uses code from a WPF application that includes a conversation window, a network credential window, and the ClientModel and BingSpellChecker helper class. These classes are part of the MSDNArticleIM namespace.

Figure 1 shows the WPF conversation window that is built to work with the Microsoft Word Repository Client application. The WPF conversation window is implemented by the MSDNArticleIM.ConversationWindow class. For more information about the Word Repository Client application, see Building a Lync IM Conversation Window: Introduction (Part 1 of 5).

Figure 1. WPF conversation window

Lync_IM_Article_ConversationWindow

Bing Spelling Checker Methods

You must create a service reference to the Bing spelling checker web service. Figure 2 shows MSDNArticleIM.BingSpellChecker service reference settings that are used in the sample application. For information about how to create the service reference, see Getting Started with Bing API, Version 2.

Figure 2. Service reference settings

LyncIMArticle_SpellCheck

The following example shows a simple class that builds a spelling checker request, gets the corrected string from the web service, and then returns the corrected string to the conversation window UI.

Important

The AppId string appearing in the following example must be replaced with a genuine application ID that is obtained from Microsoft Bing Developer Center. For more information, see Getting Started with Bing API, Version 2.

using System;
using System.Net;
using System.Xml;
using System.Text;

namespace MSDNArticleIM
{
    // Bing API 2.0 code sample demonstrating the use of the
    // Spell SourceType over the XML Protocol.
    static class BingSpellChecker
    {
        // Replace the following string with the AppId you received from the
        // Bing Developer Center.
        const string AppId = "xxx";
        static void Main()
        {
        }

        public static HttpWebRequest BuildSpellRequest(string textToCheck)
        {
            string requestString = "http://api.bing.net/xml.aspx?"

                // Common request fields (required).
                + "AppId=" + AppId
                + "&Query=" + textToCheck
                + "&Sources=Spell"

                // Common request fields (optional).
                + "&Version=2.0"
                + "&Market=en-us"
                + "&Options=EnableHighlighting";

            // Create and initialize the request.
            HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(
                requestString);
            

            return request;
        }

        public static string GetSpellResponse(HttpWebResponse response)
        {
            string returnValue = string.Empty;
            // Load the response into an XmlDocument.
            XmlDocument document = new XmlDocument();
            document.Load(response.GetResponseStream());

            // Add the default namespace to the namespace manager.
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(
                document.NameTable);
            nsmgr.AddNamespace(
                "api",
                "https://schemas.microsoft.com/LiveSearch/2008/04/XML/element");

            XmlNodeList errors = document.DocumentElement.SelectNodes(
                "./api:Errors/api:Error",
                nsmgr);

            if (errors.Count > 0)
            {
                // There are errors in the response. Display error details.
                DisplayErrors(errors);
            }
            else
            {
                // There were no errors in the response. Display the
                // spell results.
               returnValue = DisplaySpellResults(document.DocumentElement, nsmgr);
            }
            return returnValue;
        }

        public static string DisplaySpellResults(XmlNode root, XmlNamespaceManager nsmgr)
        {
            string version = root.SelectSingleNode("./@Version", nsmgr).InnerText;
            string searchTerms = root.SelectSingleNode(
                "./api:Query/api:SearchTerms",
                nsmgr).InnerText;

            // Display the results header.
            StringBuilder sb = new StringBuilder();

            // Add the spell SourceType namespace to the namespace manager.
            nsmgr.AddNamespace(
                "spl",
                "https://schemas.microsoft.com/LiveSearch/2008/04/XML/spell");

            XmlNodeList results = root.SelectNodes(
                "./spl:Spell/spl:Results/spl:SpellResult",
                nsmgr);

            // Display the spell results.
            foreach (XmlNode result in results)
            {
                DisplayTextWithHighlighting(
                    result.SelectSingleNode("./spl:Value", nsmgr).InnerText, ref sb);
                sb.Append(System.Environment.NewLine);
            }
            return sb.ToString();
        }

        public static void DisplayTextWithHighlighting(string text, ref StringBuilder sb)
        {
            // Write text to the standard output stream, changing the console
            // foreground color as highlighted characters are encountered.
            foreach (char c in text.ToCharArray())
            {
                if (c == '\uE000')
                {
                    // If the current character is the initial highlighted
                    // character (U+E000), change the console foreground color
                    // to green.
                    Console.ForegroundColor = ConsoleColor.Green;
                   // _ControlToUpdate.fo
                }
                else if (c == '\uE001')
                {
                    // If the current character is the last highlighted
                    // character (U+E001), revert the console foreground color
                    // to gray.
                    Console.ForegroundColor = ConsoleColor.Gray;
                }
                else
                {
                    //Console.Write(c);
                    sb.Append(c);
                }
            }
        }

        public static void DisplayErrors(XmlNodeList errors)
        {
            // Iterate over the list of errors and display error details.
            Console.WriteLine("Errors:");
            Console.WriteLine();
            foreach (XmlNode error in errors)
            {
                foreach (XmlNode detail in error.ChildNodes)
                {
                    Console.WriteLine(detail.Name + ": " + detail.InnerText);
                }

                Console.WriteLine();
            }
        }
    }
}

ClientModel Helper Class

The following example signs the local user in to Lync 2010. If the Lync 2010 UI is suppressed, this example initializes Lync 2010, signals the conversation window that Lync 2010 is initialized, and then signs the user in if the public helper method MSDNArticleIM.ClientModel.SignIn is called. Three delegates and matching events are declared in this example. These events allow the conversation window to react to changes in the state of the Lync 2010 client.

Important

If multiple instances of your Lync 2010 API-based application can run at the same time, only one of the instances can initialize Lync 2010. That instance must also shut down the Lync 2010 client before the application exits.

The MSDNArticleIM.ClientModel.Dispose method that appears in the following example signs the user out, and then shuts down Lync 2010 if Lync 2010 is initialized by this instance.

using System;
using Microsoft.Lync.Model;


namespace MSDNArticleIM
{
    #region public delegates

    public delegate void ClientStateChanged(Boolean signedIn);
    public delegate void ClientShutdown();
        
    /// <summary>
    /// Communicates that LyncClient is initialized to any interested classes (such as MainForm) 
    /// </summary>
    public delegate void ClientInitializedDelegate();

    #endregion

    public class ClientModel: IDisposable
    {

        #region public events
        public event ClientStateChanged ClientStateChangedEvent;
        public event ClientShutdown ClientShutdownEvent;


        /// <summary>
        /// LyncClient has been initialized
        /// </summary>
        public event ClientInitializedDelegate ClientInitializedEvent;
        #endregion

        #region private fields
        private LyncClient _lyncClient;
        private Boolean disposed = false;
        private Boolean _thisInitializedLync = false;
        #endregion

        #region public properties

        public LyncClient _LyncClient
        {
            get
            {
                LyncClient returnValue = null;
                if (_lyncClient != null)
                {
                    returnValue = _lyncClient;
                }
                return returnValue;
            }
        }


        #endregion
  

        #region callback methods


        /// <summary>
        /// Callback method called by LyncClient.SignIn()
        /// </summary>
        /// <param name="source">ILyncClient</param>
        /// <param name="_asyncOperation">IAsynchronousOperation callback</param>
        /// 
        private void SigninCallback( IAsyncResult ar)
        {
            if (ar.IsCompleted == true)
            {
                try
                {
                    _LyncClient.EndSignIn(ar);
                }
                catch (LyncClientException lce)
                {
                    System.Windows.MessageBox.Show("sign in exception: " + lce.Message);
                }
            }
            
        }

        /// <summary>
        /// Handles the asynchronous initialize callback invoked by a client instance upon initialization
        /// </summary>
        /// <param name="initializedClient">LyncClient. The initialized client.</param>
        /// <param name="AsyncOp">IAsynchronousOperation. The async interface exposing the results of the operation.</param>
        private void InitializeCallback(IAsyncResult ar)
        {
            if (ar.IsCompleted == true)
            {
                object[] asyncState = (object[])ar.AsyncState;
                ((LyncClient)asyncState[0]).EndInitialize(ar);
                _thisInitializedLync = true;
                if (ClientInitializedEvent != null)
                {
                    ClientInitializedEvent();
                }
            }
        }


        #endregion

        #region public methods

        public void InitializeClient()
        {


            //***********************************************************
            //If Lync is configured to hide its UI then
            //this method initializes Lync before sign in.
            //***********************************************************

            if (_LyncClient.InSuppressedMode == true)
            {
                if (_LyncClient.State == ClientState.Uninitialized)
                {
                    Object[] _asyncState = { _LyncClient };
                    _LyncClient.BeginInitialize(InitializeCallback, _asyncState);
                }
            }
            else
            {

                //***********************************************************
                //If there is a registering class instance for this ClientModel event,
                //raise the event and the listener will complete sign in as though
                //an initialization was necessary and is completed.
                //***********************************************************
                if (ClientInitializedEvent != null)
                {
                    ClientInitializedEvent();
                }
            }

        }


       /// <summary>
       /// Signs a user into Lync 2010 with the supplied user name, domain and password
       /// </summary>
       /// <param name="SIP">string. Network credential user Id.</param>
       /// <param name="Domain">string. SIP address of user to be signed in.</param>
       /// <param name="Password">string. Network credential password.</param>
        public void SignIn(string SIP, string Domain, string Password)
        {
            if (_LyncClient != null)
            {
                if (_LyncClient.State != ClientState.SignedIn)
                {
                    try
                    {

                        //************************************************
                        //Block UI thread until sign-in process is complete.
                        //************************************************
                        _LyncClient.EndSignIn(_LyncClient.BeginSignIn(
                             SIP,
                            Domain,
                            Password,
                            null,
                            null));


                    }
                    catch (NotInitializedException)
                    {
                        throw new Exception("Lync is not initialized");

                    }
                    catch (NotStartedByUserException)
                    {
                        throw new Exception("Sign in to Lync failed");
                    }
                }
            }
        }

        #endregion
    

        #region client event handlers
        /// <summary>
        /// Handles LyncClient state change events 
        /// </summary>
        /// <param name="source">Client.  Instance of LyncClient as source of events.</param>
        /// <param name="data">ClientStateChangedEventArgs. State change data.</param>
        void _LyncClient_ClientStateChanged(Object source, ClientStateChangedEventArgs data)
        {
            if (this.ClientStateChangedEvent != null)
            {
                if (data.NewState == ClientState.SignedIn)
                {
                    this.ClientStateChangedEvent(true);
                }
                if (data.NewState == ClientState.SignedOut)
                {
                    this.ClientStateChangedEvent(false);
                }
            }
            if (data.NewState == ClientState.ShuttingDown 
                || data.NewState == ClientState.Invalid)
            {
                if (this.ClientShutdownEvent != null)
                {
                    this.ClientShutdownEvent();
                }
            }
        }
        #endregion

        #region constructors
        public ClientModel()
        {
            
            
            try
            {
                _lyncClient = Microsoft.Lync.Model.LyncClient.GetClient();


                if (_LyncClient == null)
                {
                    throw new Exception("Unable to obtain client interface");
                }

                _lyncClient.StateChanged += _LyncClient_ClientStateChanged;
                _lyncClient.CredentialRequested += _LyncClient_CredentialRequested;

            }

            catch (NotStartedByUserException h)
            {
                throw new Exception("Lync is not running");
            }
        }

        void _LyncClient_CredentialRequested(object sender, CredentialRequestedEventArgs e)
        {
            if (e.Type == CredentialRequestedType.SignIn)
            {
                using (CredentialForm _CredentialForm = new CredentialForm())
                {
                    _CredentialForm.ShowDialog();
                    if (_CredentialForm.DialogResult == true)
                    {
                        e.Submit(
                            _CredentialForm.userDomain,
                            _CredentialForm.userPassword,
                            false);
                    }
                }
            }
        }

        #endregion

       

        #region IDisposable Members

        public void Dispose()
        {
            this.Dispose(true);
            GC.SuppressFinalize(this);
        }
        private void Dispose(Boolean disposing)
        {
            if (!this.disposed)
            {
                if (disposing)
                {
                    _LyncClient.StateChanged -= _LyncClient_ClientStateChanged;

                    if (_thisInitializedLync == true)
                    {
                        if (_LyncClient.State == ClientState.SignedIn)
                        {
                            _LyncClient.EndSignOut(_lyncClient.BeginSignOut(null, null));
                            _LyncClient.EndShutdown(_LyncClient.BeginShutdown(null, null));

                        }
                    }
                    _lyncClient = null;
                }
                this.disposed = true;
            }
        }

        #endregion

    }
}

CredentialForm Window

The following example declares a WPF window that accepts Microsoft network credentials that are used to sign a user in to Lync 2010. This window is called from the conversation window when Lync 2010 is configured for UI Suppression mode and the user’s credentials are required to sign in.



<Window x:Class="MSDNArticleIM.CredentialForm"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        Title="Sign In Credentials" Height="269" Width="300">
    <Grid Height="223">
        <Button Content="Sign In" Height="23" HorizontalAlignment="Left" Margin="107,175,0,0" Name="SignIn_Button" VerticalAlignment="Top" Width="75" Click="SignIn_Button_Click" />
        <Label Content="User Uri" Height="28" HorizontalAlignment="Left" Margin="12,28,0,0" Name="User_Uri_Label" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="146,30,0,0" Name="txt_UserUri" VerticalAlignment="Top" Width="120" />
        <Label Content="Domain" Height="28" HorizontalAlignment="Left" Margin="12,71,0,0" Name="Domain_Label" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="146,73,0,0" Name="txt_Domain" VerticalAlignment="Top" Width="120" />
        <Label Content="Password" Height="28" HorizontalAlignment="Left" Margin="12,122,0,0" Name="Password_Label" VerticalAlignment="Top" />
        <TextBox Height="23" HorizontalAlignment="Left" Margin="146,124,0,0" Name="txt_Password" VerticalAlignment="Top" Width="120" />
    </Grid>
</Window>

The following example shows the code that runs behind the credential form window.

using System;
using System.Windows;

namespace MSDNArticleIM
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class CredentialForm : Window, IDisposable
    {
        public string userSIP
        {
            get
            {
                return this.txt_UserUri.Text;
            }
        }
        public string userDomain
        {
            get
            {
                return this.txt_Domain.Text;
            }
        }
        public string userPassword
        {
            get
            {
                return this.txt_Password.Text;
            }
        }

        public CredentialForm()
        {
            InitializeComponent();
        }

        private void SignIn_Button_Click(object sender, RoutedEventArgs e)
        {
            this.Close();
        }
        #region IDisposable Members

        void IDisposable.Dispose()
        {
            
        }
        #endregion
    }
}

Conclusion

Part 1 through part 5 describes how to build an IM conversation window with WPF controls. Building a Lync IM Conversation Window: Introduction (Part 1 of 5) introduces the sample conversation window and lists the Lync 2010 API types that are used to create IM conversations. Building a Lync IM Conversation Window: Window Control Event Handlers (Part 2 of 5) describes the window control event handlers that are used to start actions on the conversation window. Building Lync IM Conversation Windows: Helper Methods (Part 3 of 5) describes the application helper methods that start the Lync 2010 API operations by calling Lync 2010 API methods. Building Lync IM Conversation Windows: Lync 2010 API Event Handlers (Part 4 of 5) describes the methods that handle the events that are raised by Lync 2010 API events as each operation is completed.

The Microsoft Windows Form controls that are used to develop the IM conversation window that appears in Figure 1 can also be used to develop a WPF or Microsoft Silverlight application.

Additional Resources

For more information, see the following resources:

About the Author

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