Introduction to the UCMA API - Part 3 - Signing In

OK, so I admit that yesterday’s post was a bit lacking in excitement. Today we are going to take the first step in UCMA coding – getting our client to sign in. If you are just joining this series, attached to this post is a .zip file of the previous day’s project. I will continue to do this in the future. That way you can jump in whenever you want, but you’ll have to make today’s changes yourself until tomorrow.

So open up our client in Visual Studio and add a new file to the project – IDKStudioClientManager.cs. Perhaps this is a poor name, but I must admit that I have no idea what we will wind up doing – so this is a generic enough name.

Our manager class will contain all of the SIP logic for our application. First make sure you add a using statement for Microsoft.Rtc.Signaling at the top of the file. Before we really begin the UCMA coding, we must add a bit more plumbing so we can see status messages in our UI. First, let’s add an event so we can alert our UI to our progress.

        /// <summary>

        /// Occurs when some progress occurs

        /// </summary>

        public event EventHandler<ProgressEventArgs> Progress;

Of course, to get this code to compile we will need to add the ProgressEventArgs class. Create a new code file called EventArgs.cs where we will store all of these event argument classes.

    #region ProgressEventArgs class

    /// <summary>

    /// A message that indicates the current progress

    /// </summary>

    public class ProgressEventArgs : EventArgs

    {

        private string _message;

        /// <summary>

        /// Creates a new ProgressEventArgs instance

        /// </summary>

        /// <param name="message">The message</param>

        public ProgressEventArgs(string message)

        {

            _message = message;

        }

        /// <summary>

        /// The message

        /// </summary>

        public string Message

        {

            get { return _message; }

        }

    }

    #endregion

Let’s also add an event handler to tell our UI when we are finished.

 

        /// <summary>

        /// Occurs when the request has been completed

        /// </summary>

        public event EventHandler<CompletedEventArgs> Completed;

And of course we now need to add the CompletedEventArgs class

    #region CompletedEventArgs class

    /// <summary>

    /// Arguments indicating how the request completed

    /// </summary>

    public class CompletedEventArgs : EventArgs

    {

        private string _message;

        private CompletionStatus _status;

        /// <summary>

        /// Creates a new CompletedEventArgs instance

        /// </summary>

        /// <param name="status">The status of the completion</param>

        /// <param name="message">The message</param>

        public CompletedEventArgs(CompletionStatus status, string message)

        {

            _status = status;

            _message = message;

        }

        /// <summary>

        /// The message

        /// </summary>

        public string Message

        {

            get { return _message; }

        }

        /// <summary>

        /// The status of the completion

        /// </summary>

        public CompletionStatus Status

        {

            get { return _status; }

        }

    }

    #endregion.

In order for this to compile, you will need the CompletionStatus enumeration. I try to add all of my enumerations in the same file, so create a new Enums.cs file and add CompletionStatus.

    /// <summary>

    /// How the attempt completed

    /// </summary>

    public enum CompletionStatus

    {

        /// <summary>

        /// We succeeded

        /// </summary>

        Success,

        /// <summary>

        /// We failed

        /// </summary>

        Failure

    }

Now we can add a few helper methods to get status back to our UI.

        /// <summary>

        /// Records progress

        /// </summary>

        /// <param name="message">The message</param>

        /// <param name="args">Arguments to the message</param>

        private void RecordProgress(string message, params object[] args)

        {

            RecordProgress(String.Format(CultureInfo.CurrentCulture, message, args));

        }

        /// <summary>

        /// Records progress

        /// </summary>

        /// <param name="message">The message</param>

        private void RecordProgress(string message)

        {

            if (null != Progress)

            {

                Progress(this, new ProgressEventArgs(message));

            }

        }

        /// <summary>

        /// Raises an error

        /// </summary>

        /// <param name="message">The message of the error</param>

        private void Error(string message)

        {

            // Indicate an error occurred in the progress

            RecordProgress(message);

            // Indicate we have completed

            if (null != Completed)

            {

                Completed(this, new CompletedEventArgs(CompletionStatus.Failure, message));

            }

        }

        /// <summary>

        /// Raises an error

        /// </summary>

        /// <param name="message">The message of the error</param>

        /// <param name="args">Arguments to pass to the message</param>

        private void Error(string message, params object[] args)

        {

            Error(string.Format(CultureInfo.CurrentCulture, message, args));

        }

You will have to add a using statement for System.Globalization to get this to compile. Let’s add an empty method called Start to our manager that we will fill in shortly.

        /// <summary>

        /// Starts our client

        /// </summary>

        public void Start()

        {

        }

Now let’s return to our main form. We need to take messages from our manager and display them appropriately. We also must take into account the fact that UCMA will call us on separate threads at times. UCMA makes heavy use of asynchronous APIs. Therefore our UI code must be thread safe. First let’s add methods to update the progress and to enable or disable the start button.

        /// <summary>

        /// Enables the button

        /// </summary>

        /// <param name="button"></param>

        /// <param name="label"></param>

        void EnableButton(Button button, string label)

        {

            button.Text = label;

            button.Enabled = true;

        }

        /// <summary>

        /// Updates the progress with the message

        /// </summary>

        /// <param name="message">The message</param>

        private void UpdateProgress(string message)

        {

            lbxProgress.Items.Add(message);

            lbxProgress.SelectedIndex = lbxProgress.Items.Count - 1;

        }

Note that when we update the progress we automatically select the last item. As we will eventually receive numerous progress items, this helps readability. We cannot call these methods directly in our event handlers because in Windows Forms we can only access UI on the UI thread. We’ll get to this code shortly. For now, let’s add an instance variable for our manager to the main form.

private IDKStudioClientManager _manager;

Now let’s add a method to start our manager when we click the Start button.

        /// <summary>

        /// Starts running our manager

        /// </summary>

        private void StartManager(object stateInfo)

        {

            _manager = new IDKStudioClientManager();

            _manager.Completed += new EventHandler<CompletedEventArgs>(_manager_Completed);

            _manager.Progress += new EventHandler<ProgressEventArgs>(_manager_Progress);

            _manager.Start();

        }

In this method we have hooked up the event handlers for the Completed and Progress events in the manager. Keep in mind that it is likely these events will be called on a different thread than the UI thread. Let’s create the event handler for the Progress event. Don’t worry about the stateInfo object for now. This is required because we will call this method through ThreadPool.QueueUserWorkItem which requires the method to conform to a WaitCallback delegate.

        void _manager_Progress(object sender, ProgressEventArgs e)

        {

            if (true == lbxProgress.InvokeRequired)

            {

                lbxProgress.Invoke(new UpdateProgressDelegate(UpdateProgress), e.Message);

            }

            else

            {

                UpdateProgress(e.Message);

            }

        }

This code makes sure we update the UI on the correct thread. Let’s now add the code for the Completed event.

            if (e.Status == CompletionStatus.Success)

            {

                MessageBox.Show("Application finished",

                                "Success!",

                                MessageBoxButtons.OK,

                                MessageBoxIcon.Information);

            }

            else

            {

                MessageBox.Show(String.Format(CultureInfo.CurrentCulture,

                                              "We failed due to the following error:\n{0}",

                                              e.Message),

                                "Failure!",

                                MessageBoxButtons.OK,

        MessageBoxIcon.Error);

            }

            if (true == btnStart.InvokeRequired)

            {

                Invoke(new EnableButtonDelegate(EnableButton), btnStart, "Start");

                Invoke(new EnableButtonDelegate(EnableButton), btnOptions, btnOptions.Text);

            }

            else

            {

                btnStart.Enabled = true;

            }

You will need to add a using statement for System.Globalization in order for this to compile. When we complete we display a message with the appropriate result and then set the text of the Start button to “Start”. Let’s add a handler for the start button click event.

        private void btnStart_Click(object sender, EventArgs e)

        {

            btnOptions.Enabled = false;

            btnStart.Enabled = false;

            btnStart.Text = "Stop";

            ThreadPool.QueueUserWorkItem(new WaitCallback(StartManager), this.Handle);

        }

So when we click the start button we will start our manager. We also change the text of the button to “Stop”. For today, we will not have the ability to stop our manager. You will need to add a using statement for System.Threading for this to compile. Finally, open the main form designer.cs file and change the default state of the Start button to enabled.

OK, so now you have a manager that does absolutely nothing! For today, we will have our application “sign in” to OCS. Remember how I previously mentioned that the UCMA API allows us to create SIP endpoints? Well, we happen to have a SipEndpoint class in UCMA that will help you sign in. This class basically represents our client and represents something that accepts and sends SIP communications.

So, in order to sign in to OCS we must first create an instance of the SipEndpoint class. The SipEndpoint class derives from the abstract RealTimeEndpoint class. There are two classes of interest to us that derive from RealTimeEndpoint. Both of these classes allow you to send out of dialog messages or establish and receive long sessions.

· SipEndpoint – can be constructed off any connection manager that derives from RealTimeConnectionManager (server or client)

· SipPeerToPeerEndpoint –can only be constructed off connection managers that derive from RealTimeServerConnectionManager (RealTimeServerTlsConnectionManager or a RealTimeServerTcpConnectionManager)

 

For our purposes right now, we will use the SipEndpoint class. We will likely use SipPeerToPeerEndpoint in the future. In order to create a SipEndpoint instance, though, we will need to create a RealTimeConnectionManager.

 

RealTimeConnectionManager provides the connection manager features we need for our end points. It contains the logic that allows us to send messages. RealTimeConnectionManager by itself can only send messages. It does not contain logic for listening for them. Since we will likely want to receive messages in the future, we will use the RealTimeServerConnectionManager class. This class allows us to receive messages as well, though we will do that in a future post.

 

RealTimeServerConnectionManager, which derives itself from RealTimeConnectionManager, has two descendents – RealTimeServerTcpConnectionManager and RealTimeServerTlsConnectionManager. I will not go into depth in this section on the differences between Tcp and Tls but in short Tls is more secure than simple Tcp because the data over the session is encrypted. Within our client application, I have already provided access in the settings to switch between Tls and Tcp so you can experiment with them.

 

So once we have a RealTimeConnectionManager instance, we can create our SipEndpoint instance. Once we have this, we need to register our SIP endpoint. Registering in UCMA is basically the same as signing in in most other environments. First, let’s add some variables that we will use shortly.

 

        private RealTimeServerConnectionManager _connectionManager;

        private SipEndpoint _endPoint;

Now let’s add a new method RegisterEndpoint that will contain the code for creating and registering our endpoint. Before we get into the details of creating our SipEndpoint, let’s look at the constructor. There are three available constructors for SipEndpoint but the one we will use here is defined as follows.

 

public SipEndpoint (

        string uri,

        SipAuthenticationProtocols allowedAuthenticationProtocols,

        SipTransportType transportType,

        string serverName,

        int serverPort,

        bool allowNoAuthentication,

        RealTimeConnectionManager connectionManager,

        string endpointId

)

 

To create our SipEndpoint we must supply the information in the following table.

 

Argument

Description

uri

This is the SIP URI of our endpoint. It typically has the form of sip:name@domain.com

allowedAuthenticationProtocols

You can use none, Kerberos, or Ntlm here. You will probably want to play with this some, but I will use Ntlm here.

transportType

This is Tcp or Tls as previously described

serverName

This is the name of the OCS server we will register with

serverPort

This is the port on the server we will use. The default port for Tcp is 5060 and for Tls it is 5061.

allowNoAuthentication

Use this setting if the server does not authenticate communications from our client. This must be true if you use None for allowedAuthenticationProtocols. In almost all cases, you will pass false here.

connectionManager

This is the connection manager that we must create.

endPointId

If you want to use a specific endpoint Id (also called epid) you can specify it here. In this case I will pass null.

 

So at long last we are ready to write some UCMA code! In the RegisterEndpoint method, add the following code.

 

            // Create the connection manager

            RecordProgress("Creating the connection manager");

            if (Settings.TransportType == SipTransportType.Tls)

            {

                try

                {

                    _connectionManager = (RealTimeServerConnectionManager)new RealTimeServerTlsConnectionManager(Settings.CertificateIssuerName, Settings.CertificateSerialNumber);

                    RecordProgress("Created the Tls connection manager");

                }

                catch (TlsFailureException)

                {

                    Error("Cannot read the certificate");

                    return;

                }

                // Create the endpoint

                _endPoint = new SipEndpoint(Settings.Uri,

                                           SipAuthenticationProtocols.Ntlm,

                                           SipTransportType.Tls,

                                           Settings.OCSServerName,

                                           5061,

                                           true,

                                           _connectionManager,

                                           null);

                _endPoint.CredentialCache.Add(SipEndpoint.DefaultRtcRealm, CredentialCache.DefaultNetworkCredentials);

                RecordProgress("Created a Tls endpoint for {0}", Settings.Uri);

            }

            else

            {

                _connectionManager = (RealTimeServerConnectionManager)new RealTimeServerTcpConnectionManager();

                RecordProgress("Created the Tcp connection manager");

                _endPoint = new SipEndpoint(Settings.Uri,

                                           SipAuthenticationProtocols.Ntlm,

                                         SipTransportType.Tcp,

                                           Settings.OCSServerName,

                                           5060,

                                           true,

                                           _connectionManager,

                                           null);

                _endPoint.CredentialCache.Add(SipEndpoint.DefaultRtcRealm, CredentialCache.DefaultNetworkCredentials);

                RecordProgress("Created a Tcp endpoint for {0}", Settings.Uri);

       }

You will need to add a using statement for System.Net for this code to compile. In this code we do the following.

1) Create a connection manager – either RealTimeServerTlsConnectionManager or RrealTimeServerTcpConnectionManager depending on whether the user selected Tls or Tcp

2) Create our endpoint using the arguments mentioned above.

3) Because we are authenticating, we must provide credentials. In our case, we just add the default credentials

 

So we’ve done the first step – creating our endpoint. We now need to register it in order to “sign in”. Before we continue though, I must explain a crucial point about the UCMA API. UCMA contains both synchronous and asynchronous methods. If you want your application to be at all scalable, you must call the asynchronous versions. Personally I find the synchronous versions useful when trying something out for the first time, but I always change the code to call the asynchronous versions. I strongly advise you to use the asynchronous versions of each of the methods.

 

In order to register our endpoint with the OCS server, the method we need is called BeginRegister. This method simply takes the endpoint to register so add the following code at the end of the RegisterEndpoint method.

 

// Register the endpoint

_endPoint.BeginRegister(new AsyncCallback(RegisterCallback), _endPoint);

BeginRegister will register our endpoint with the server and then call RegisterCallback, passing our _endPoint object as state information. In order for this to work, we will have to add our callback method, RegisterCallback.

 

/// <summary>

/// Called when the endpoint has finished registering

/// </summary>

/// <param name="asyncResult"></param>

private void RegisterCallback(IAsyncResult asyncResult)

{

    SipEndpoint endPoint = asyncResult.AsyncState as SipEndpoint;

   SipResponseData response;

    try

    {

        response = endPoint.EndRegister(asyncResult);

        RecordProgress("Registered the endpoint successfully.");

    }

    catch (Exception e)

    {

        Error("An exception occurred when registering the endpoint.\n{0}", e.ToString());

        return;

    }

}

Here we simply call EndRegister on the SipEndpoint we passed previously (we could of course just use _endPoint but we may change that in the future). We catch any exceptions and pass them back to the UI.

 

When you compile and run this you will see the messages stating that you have registered successfully. If you run into any errors make sure you have specified a valid user in Active Directory for the uri and that you have specified the OCS server correctly. You can also try switching between Tcp and Tls to see if that makes a difference.

Part2.zip