Running UCMA Code in a Windows Workflow Application: Code Listing (Part 4 of 4)

Summary:   Combine the capabilities of Microsoft Unified Communications Managed API (UCMA) and Windows Workflow Foundation. Part 2 and Part 3 discuss how to create the application.

Applies to:   Microsoft Unified Communications Managed API (UCMA) 3.0 Core SDK | Windows Workflow Foundation

Published:   October 2011 | Provided by:   John Clarkson, Microsoft | About the Author

Contents

  • Application Configuration

  • Application Code

  • Helper Class

  • Conclusion

  • Additional Resources

Download code  Download code

Watch video  See video

This is the last in a series of four articles about how to add a UCMA component to a workflow.

Application Configuration

App.Config, the application configuration file, is used to configure settings for the computer that is hosting the application, and the remote endpoint. When the appropriate parameters are entered in the add elements (and the XML comment delimiters are removed), they do not have to be entered from the keyboard when the application is running.

The following example shows the content of the App.Config file.



<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0" />
  </startup>
  <appSettings>
    <!-- Please provide parameters necessary for the sample to run without 
    prompting for input -->

    <!-- Provide the FQDN of the Microsoft Lync Server -->
    <add key="ServerFQDN1" value="sales1.sales.contoso.com"/>

    <!-- The user name of the user that the sample logs in as -->
    <!-- You may leave this value as blank to use credentials of the currently 
    logged on user -->
    <add key="UserName1" value="barbara"/>

    <!-- The user domain of the user that the sample logs in as -->
    <!-- You may leave this value as blank to use credentials of the currently 
    logged on user -->
    <add key="UserDomain1" value="sales.contoso.com "/>

    <!-- The user URI of the user that the sample logs in as, in the format 
    user@host -->
    <add key="UserURI1" value="barbara@sales.contoso.com"/>
  </appSettings>
</configuration>

Application Code

The following examples contain code from the ConsoleAppDotNET35 project.

Program.cs

The following example creates the UCProgram object and defines handlers for events of interest.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Rtc;
using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Collaboration.Sample.Common;
using System.Threading;
using System.Configuration;

namespace ConsoleAppDotNET35
{
    public class UCProgram
    {
        string conversationSubject = "UCMA ramp up";
        string messageText;
        InstantMessagingCall ImCall;
        InstantMessagingFlow ImFlow;
        UserEndpoint userEndpoint;
        UCMASampleHelper helper;
        AutoResetEvent completedEvent = new AutoResetEvent(false);

        //Class entry point.
        static void Main(string[] args)
        {
            UCProgram program = new UCProgram();
        }

        //Start an instant message. Text of the message depends on the parameter value.
        public void UcmaIM(Int32 choice)
        {
            helper = new UCMASampleHelper();
            userEndpoint = helper.CreateEstablishedUserEndpoint("johnclaSampleUser");
            Console.WriteLine("endpoint owned by " + userEndpoint.OwnerUri + " is established/registered");

            ConversationSettings conversationSettings = new ConversationSettings();
            conversationSettings.Subject = conversationSubject;
            Conversation conversation = new Conversation(userEndpoint, conversationSettings);

            ImCall = new InstantMessagingCall(conversation);
            ImCall.InstantMessagingFlowConfigurationRequested += new EventHandler<InstantMessagingFlowConfigurationRequestedEventArgs>(ImCall_InstantMessagingFlowConfigurationRequested);

            Int32 helloChoice = 1;
            Int32 goodbyeChoice = 2;
            if (choice == helloChoice)
                messageText = "hello";
            else if (choice == goodbyeChoice)
                messageText = “goodbye”
            else
                messageText = "press 1 or press 2";

            string targetURI = "sip:barbara@contoso.com";
            ImCall.BeginEstablish(targetURI, null, CallEstablishCompleted, null);
            completedEvent.WaitOne();
        }

         //Handler for the InstantMessagingCall.InstantMessagingFlowConfigurationRequested event.
        void ImCall_InstantMessagingFlowConfigurationRequested(object sender, InstantMessagingFlowConfigurationRequestedEventArgs e)
        {
            ImFlow = e.Flow;
            ImFlow.StateChanged += new EventHandler<MediaFlowStateChangedEventArgs>(ImFlow_StateChanged);
        }


        //Handler for the InstantMessagingFlow.StateChanged event.
        void ImFlow_StateChanged(object sender, MediaFlowStateChangedEventArgs e)
        {
            if (e.State == MediaFlowState.Active)
                ImFlow.BeginSendInstantMessage(messageText, SendMessageCompleted, ImFlow);
        }

        //Callback method for the InstantMessagingCall.BeginEstablish method.
        private void CallEstablishCompleted(IAsyncResult res)
        { 
           ImCall.EndEstablish(res);
        }

        //Callback method for the InstantMessagingFlow.BeginSendInstantMessage method.
        private void SendMessageCompleted(IAsyncResult res)
        {
          ImFlow.EndSendInstantMessage(res);
        }
    }
}

UcmaHello.cs

The following example creates the UcmaHello activity.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using ConsoleAppDotNET35;

namespace ActivityLibrary1
{
    public sealed class UcmaHello : CodeActivity
    {
        private UCProgram ucma;

        // Define an activity input argument of type string
        public InArgument<string> Text { get; set; }

        //Calls the UcMaIM() method in the UCMA project.
        //This activity executes when the user enters 1. The UcMaIM() 
        // method will send an instant message with the text “hello”.
        protected override void Execute(CodeActivityContext context)
        {
            ucma = new UCProgram();
            ucma.UcmaIM(1);
        }
    }
}

UcmaGoodbye.cs

The following example creates the UcmaGoodbye activity.



using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Activities;
using ConsoleAppDotNET35;

namespace ActivityLibrary1
{
    public sealed class UcmaGoodbye : CodeActivity
    {
        private UCProgram ucma;

        // Define an activity input argument of type string
        public InArgument<string> Text { get; set; }

        //Calls the UcMaIM() method in the UCMA project.
        //This activity executes when the user enters 2. The UcMaIM() 
        // method will send an instant message with the text “goodbye”.
        protected override void Execute(CodeActivityContext context)
        {
            ucma = new UCProgram();
            ucma.UcmaIM(2);
        }
    }
}

Helper Class

The code appearing in this section is an abbreviated version of the code that appears in the Microsoft Unified Communications Managed API (UCMA) 3.0 Core SDK UCMASampleHelper.cs file. The following code includes declarations and the following five methods. UCMASampleHelper.cs is included in the UCMA 3.0 Core SDK documentation, which is located in the %ProgramFiles%\SDK\Core\Sample Applications\QuickStarts\Common folder.

  • CreateEstablishedUserEndpoint

  • EstablishUserEndpoint

  • ReadUserSettings

  • CreateUserEndpoint

  • PromptUser



using System;
using System.Configuration;
using System.Globalization;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;

using Microsoft.Rtc.Collaboration;
using Microsoft.Rtc.Signaling;

namespace Microsoft.Rtc.Collaboration.Sample.Common
{
    class UCMASampleHelper
    {
        private static ManualResetEvent _sampleFinished = new ManualResetEvent(false);

        // The name of this application, to be used as the outgoing user agent string.
        // The user agent string is put in outgoing message headers to indicate the Application used.
        private static string _applicationName = "UCMASampleCode";

        const string _sipPrefix = "sip:";

        // These strings will be used as keys into the App.Config file to get information to avoid prompting. For most of these,
        // Suffixes 1-N will be used on each subsequent call. For example, UserName1 will be used for the first user, and UserName2 for the second.
        private static String _serverFQDNPrompt = "ServerFQDN";
        private static String _userNamePrompt = "UserName";
        private static String _userDomainPrompt = "UserDomain";
        private static String _userURIPrompt = "UserURI";

        // Construct the network credential that the UserEndpoint will use to authenticate to the Microsoft Lync Server.
        private string _userName; // User name and password pair of a user enabled for Microsoft Lync Server. 
        private string _userPassword;
        private string _userDomain; // Domain that this user is logging into. Note: This is the AD domain, not the portion of the SIP URI following the @ sign.
        private System.Net.NetworkCredential _credential;

        // The URI and connection server of the user used.
        private string _userURI; // This should be the URI of the user given above.

        // The Server FQDN used.
        private static string _serverFqdn;// The Microsoft Lync Server to log in to.

        // Transport type used to communicate with your Microsoft Lync Server instance.
        private Microsoft.Rtc.Signaling.SipTransportType _transportType = Microsoft.Rtc.Signaling.SipTransportType.Tls;

        private static CollaborationPlatform _collabPlatform;
        private static bool _isPlatformStarted;
        private static CollaborationPlatform _serverCollabPlatform;
        private AutoResetEvent _platformStartupCompleted = new AutoResetEvent(false);
        private AutoResetEvent _endpointInitCompletedEvent = new AutoResetEvent(false);
        private AutoResetEvent _platformShutdownCompletedEvent = new AutoResetEvent(false);
        private UserEndpoint _userEndpoint;
        private ApplicationEndpoint _applicationEndpoint;

        // The FQDN of the computer that the application contact is targeted at.
        private string _applicationHostFQDN;

        // The port that the application contact is configured to receive data on.
        private int _applicationPort = -1;

        // The GRUU assigned to the application contact.
        private string _applicationGruu;

        // The URI of the contact being used.
        private string _applicationContactURI;

        private string _certificateFriendlyName;

        private bool _useSuppliedCredentials;
        private static int _appContactCount;
        private static int _userCount = 1;

        // Method to read user settings from app.config file or from the console prompts
        // This method returns a UserEndpointSettings object. If you do not want to monitor LocalOwnerPresence, you may 
        // want to call the CreateEstablishedUserEndpoint method directly. Otherwise, you may call ReadUserSettings
        // followed by CreateUserEndpoint, followed by EstablishUserEndpoint methods.
        public UserEndpointSettings ReadUserSettings(string userFriendlyName)
        {
            UserEndpointSettings userEndpointSettings = null;
            string prompt = string.Empty;
            if (string.IsNullOrEmpty(userFriendlyName))
            {
                userFriendlyName = "Default User";
            }

            try
            {
                Console.WriteLine(string.Empty);
                Console.WriteLine("Creating User Endpoint for {0}...", userFriendlyName);
                Console.WriteLine();

                if (ConfigurationManager.AppSettings[_serverFQDNPrompt + _userCount] != null)
                {
                    _serverFqdn = ConfigurationManager.AppSettings[_serverFQDNPrompt + _userCount];
                    Console.WriteLine("Using {0} as Microsoft Lync Server", _serverFqdn);
                }
                else
                {
                    // Prompt user for server FQDN. If server FQDN was entered before, then let the user use the saved value.
                    string localServer;
                    StringBuilder promptBuilder = new StringBuilder();
                    if (!string.IsNullOrEmpty(_serverFqdn))
                    {
                        promptBuilder.Append("Current Microsoft Lync Server = ");
                        promptBuilder.Append(_serverFqdn);
                        promptBuilder.AppendLine(". Please hit ENTER to retain this setting - OR - ");
                    }

                    promptBuilder.Append("Please enter the FQDN of the Microsoft Lync Server that the ");
                    promptBuilder.Append(userFriendlyName);
                    promptBuilder.Append(" endpoint is homed on => ");
                    localServer = PromptUser(promptBuilder.ToString(), null);

                    if (!String.IsNullOrEmpty(localServer))
                    {
                        _serverFqdn = localServer;
                    }
                }

                // Prompt user for user name
                prompt = String.Concat("Please enter the User Name for ",
                                        userFriendlyName,
                                        " (or hit the ENTER key to use current credentials)\r\n" +
                                        "Please enter the User Name => ");
                _userName = PromptUser(prompt, _userNamePrompt + _userCount);

                // If user name is empty, use current credentials
                if (string.IsNullOrEmpty(_userName))
                {
                    Console.WriteLine("Username was empty - using current credentials...");
                    _useSuppliedCredentials = true;
                }
                else
                {
                    // Prompt for password
                    prompt = String.Concat("Enter the User Password for ", userFriendlyName, " => ");
                    _userPassword = PromptUser(prompt, null);

                    prompt = String.Concat("Please enter the User Domain for ", userFriendlyName, " => ");
                    _userDomain = PromptUser(prompt, _userDomainPrompt + _userCount);
                }

                // Prompt user for user URI
                prompt = String.Concat("Please enter the User URI for ", userFriendlyName, " in the User@Host format => ");
                _userURI = PromptUser(prompt, _userURIPrompt + _userCount);
                if (!(_userURI.ToLower().StartsWith("sip:") || _userURI.ToLower().StartsWith("tel:")))
                    _userURI = "sip:" + _userURI;

                // Increment the last user number
                _userCount++;

                // Initalize and register the endpoint, using the credentials of the user the application will be acting as.
                // NOTE: the _userURI should always be of the form "sip:user@host"
                userEndpointSettings = new UserEndpointSettings(_userURI, _serverFqdn);

                if (!_useSuppliedCredentials)
                {
                    _credential = new System.Net.NetworkCredential(_userName, _userPassword, _userDomain);
                    userEndpointSettings.Credential = _credential;
                }
                else
                {
                    userEndpointSettings.Credential = System.Net.CredentialCache.DefaultNetworkCredentials;
                }
            }
            catch (InvalidOperationException iOpEx)
            {
                // Invalid Operation Exception should only be thrown on poorly-entered input.
                Console.WriteLine("Invalid Operation Exception: " + iOpEx.ToString());
            }

            return userEndpointSettings;
        }

        // Method to create an endpoint given a UserEndpointSettings object.
        // This method returns a UserEndpoint object so that you can wire up Endpoint-specific event handlers. 
        // If you do not want to get endpoint specific event information at the time the endpoint is established, you may 
        // want to call the CreateEstablishedUserEndpoint method directly. Otherwise, you may call ReadUserSettings
        // followed by CreateUserEndpoint, followed by EstablishUserEndpoint methods.
        public UserEndpoint CreateUserEndpoint(UserEndpointSettings userEndpointSettings)
        {
            // Reuse platform instance so that all endpoints share the same platform.
            if (_collabPlatform == null)
            {
                // Initalize and startup the platform.
                ClientPlatformSettings clientPlatformSettings = new ClientPlatformSettings(_applicationName, _transportType);
                _collabPlatform = new CollaborationPlatform(clientPlatformSettings);
            }

            _userEndpoint = new UserEndpoint(_collabPlatform, userEndpointSettings);
            return _userEndpoint;
        }

        // Method to establish an already created UserEndpoint.
        // This method returns an established UserEndpoint object. If you do not want to monitor LocalOwnerPresence, you may 
        // want to call the CreateEstablishedUserEndpoint method directly. Otherwise, you may call ReadUserSettings
        // followed by CreateUserEndpoint, followed by EstablishUserEndpoint methods.
        public bool EstablishUserEndpoint(UserEndpoint userEndpoint)
        {
            // Startup the platform, if not already
            if (_isPlatformStarted == false)
            {
                userEndpoint.Platform.BeginStartup(EndPlatformStartup, userEndpoint.Platform);

                // Sync; wait for the platform startup to complete.
                _platformStartupCompleted.WaitOne();
                Console.WriteLine("Platform started...");
                _isPlatformStarted = true;
            }
            // Establish the user endpoint
            userEndpoint.BeginEstablish(EndEndpointEstablish, userEndpoint);

            // Sync; wait for the registration to complete.
            _endpointInitCompletedEvent.WaitOne();
            Console.WriteLine("Endpoint established...");
            return true;
        }

        // Method to create an established UserEndpoint.
        // This method returns an established UserEndpoint object. If you do not want to monitor LocalOwnerPresence, you may 
        // want to call this CreateEstablishedUserEndpoint method directly. Otherwise, you may call ReadUserSettings
        // followed by CreateUserEndpoint, followed by EstablishUserEndpoint methods.
        public UserEndpoint CreateEstablishedUserEndpoint(string endpointFriendlyName)
        {
            UserEndpointSettings userEndpointSettings;
            UserEndpoint userEndpoint = null;
            try
            {
                // Read user settings
                userEndpointSettings = ReadUserSettings(endpointFriendlyName);

                // Create User Endpoint
                userEndpoint = CreateUserEndpoint(userEndpointSettings);

                // Establish the user endpoint
                EstablishUserEndpoint(userEndpoint);
            }
            catch (InvalidOperationException iOpEx)
            {
                // Invalid Operation Exception should only be thrown on poorly-entered input.
                Console.WriteLine("Invalid Operation Exception: " + iOpEx.ToString());
            }
            return userEndpoint;
        }

        /// <summary>
        /// If the 'key' is not found in app config, prompts the user using prompt text.
        /// </summary>
        /// <param name="promptText">If key is not found in app.Config, the user will be prompted for input using this parameter.</param>
        /// <param name="key">Searches for this key in app.Config and returns if found. Pass null to always prompt.</param>
        /// <returns>String value either from App.Config or user input.</returns>
        public static string PromptUser(string promptText, string key)
        {
            String value;
            if (String.IsNullOrEmpty(key) || ConfigurationManager.AppSettings[key] == null)
            {
                Console.WriteLine(string.Empty);
                Console.Write(promptText);
                value = Console.ReadLine();
            }
            else
            {
                value = ConfigurationManager.AppSettings[key];
                Console.WriteLine("Using keypair {0} - {1} from AppSettings...", key, value);
            }
            return value;
        }
    }
}

Conclusion

If you plan to develop and deploy a Windows Workflow application that uses UCMA components, the scenario and code examples that appear in this set of articles can help you get started.

Additional Resources

For more information, see the following resources:

About the Author

John Clarkson is a programming writer with the Microsoft Lync product team.