In my last post, I'd rattled off how to generate C# files using the XSDs we'd previously shipped. Here, I'll go over actually using them. One might think that after getting the C# files, publishing them would be rather easy.

This is true, but only if you know exactly what you're doing. :p

The Code

The code is based the PublishPresence sample shipped with UCMAv2.0 and has been modified to support the generated xml classes. I won’t go into the workings of the sample itself, but will talk about some of the changes that have been made and the problems that motivated them.

I’ll also paste the full code snippet at the bottom of this post, along with a zip file with csproj and all that. Taking the zip, you should be able to just unzip, build and run with VS 2008(after setting username/passwords of course).

  // Get UserStateXml

            userState uState = new userState();

            uState.availability = (uint)_busyAvail;

            uState.availabilitySpecified = true;

            uState.manual = true;

            _userStateXml = Serialize(uState, typeof(stateType));

            Console.WriteLine("UserState Xml: " + _userStateXml);

            Console.WriteLine("**** Press Enter to begin Publishing Presence...");

            Console.ReadKey();

Taken from the Main() function, this is the code that is used to generate a piece of XML. The example above is of a UserState. (To have a state show up in OC, you need to first declare that your endpoint’s device is active by publishing machinestate=3500, then declare your state by publishing a user state. User State is what actually gets sent when you click the jellybean and choose ‘busy’ for instance. Machine state is the state of the device the endpoint is on.).

A couple notes here:

userState uState = new userState();

_userStateXml = Serialize(uState, typeof(stateType));

 

First thing to note is that the Serialize() function is not accepting a type of userState, but of the parent ‘stateType’ class. The problem is that if you were to pass in ‘userState’ to an XmlSerializer(which Serialize eventually does), the generated xml would look like this:

<UserState ...Availability.../>

Unfortunately, OCS is looking for something that looks like this:

<State ... Type='userstate'...availability.../>

The way to go is to tell the XmlSerializer that the type of object is ‘stateType’ and not ‘userState’. Then the xml is generated properly.

Second thing: in addition to publishing _busyAvail, I also set ‘availabilitySpecified=true’. Without the availabilitySpecified flag, the availability will be ignored.

uState.availability = (uint)_busyAvail;

uState.availabilitySpecified = true;

 

These are the ‘gotchas’ that nailed me when I tried this out. I couldn’t figure out how to get the xml to serialize properly for awhile, and got totally confused when my availability didn’t show up too.

Moving on, here’s the ‘Serialize’ function that’s referenced above.

/// <summary>

        /// This serialize method wraps an XmlSerializer. The purpose of having this code here

        /// is to omit the XmlDeclaration from our genereated xml snippets.

        ///

        /// </summary>

        /// <param name="objectToSerialize">This object should be of type 'type'</param>

        /// <param name="type">The type of object to serialize</param>

        /// <returns></returns>

        public static String Serialize(Object objectToSerialize, Type type)

        {

            if (objectToSerialize == null) throw new ArgumentNullException("objectToSerialize");

            if (type == null) throw new ArgumentNullException("type");

 

            XmlSerializer serializer = new XmlSerializer(type);

            StringWriter sw = new StringWriter(new StringBuilder(128), CultureInfo.InvariantCulture);

            XmlWriterSettings setting = new XmlWriterSettings();

            setting.OmitXmlDeclaration = true;

            XmlWriter messageWriter = XmlWriter.Create(sw, setting);

            serializer.Serialize(messageWriter, objectToSerialize);

            messageWriter.Close();

            sw.Close();

 

            return sw.ToString();

        }

This is a simple helper function that returns the serialized xml string given a type and an instance. The thing to note here is the ‘OmitXmlDeclaration’ call. This call instructs the serializer to skip the <? Xml …?> portion when constructing the xml.

Code Listing 

 

Here’s the entire code listing from tip to tail:

using System;

using System.Xml;

using System.IO;

using Microsoft.Rtc.Collaboration;

using Microsoft.Rtc.Collaboration.Presence;

using Microsoft.Rtc.Signaling;

using UCMASampleCode;

using System.Xml.Serialization;

using PresenceCategoryNote;

using PresenceCategoryState;

using System.Text;

using System.Globalization;

using Microsoft.Rtc.Internal.Collaboration;

using System.Net;

 

namespace UCMASampleCode

{

    // This application requires the credentials of Office Communications Server (OCS) users.

    // Be sure that the users in question can log in to Office Communications Server using Office Communicator with

    // the credentials provided from the machine that is running this code.

 

    // This program allows a user to publish his or her presence as busy

    // We suggest you log in another user, belonging to the same enterprise, using Office Communicator to observe

    // this user's presence state change.

    class PublishPresence

    {

 

        #region private members

 

        // These numbers and xml constructs are explained in the UCMA .chm, and the OCS Redline documentation. it is beyond the scope of this sample to reexplain this here.

        private static String _userStateXml;

        private static String _machineStateXml;

        private static String _noteXml;

        private static int _busyAvail = 6500;

        private static int _onlineAvail = 3500;

 

        //Construct the network credential that the UserEndpoint will use to authenticate to the Office Communications Server instance.

        // User name and password pair of a user enabled for Office Communications Server.

        private static String _userName = "user1";     // User name and password pair of a user enabled for Office Communications Server.

        private static String _userPassword = "user1";

 

        private static String _userDomain = "contoso.com";    // Domain that this user is logging into. Note: This is the AD domain, not the portion of the SIP URI following the at sign.

        private static System.Net.NetworkCredential _credential = new System.Net.NetworkCredential(_userName, _userPassword, _userDomain);

 

        //The URI and connection server of the user used.

        private static String _userURI = "sip:user@contoso.com";  // This should be the URI of the user given above.

        private static String _userServer = "ocs.contoso.com";  // The Office Communications Server that the user listed will log in to..

 

        // Transport type used to communicate with your OCS (Office Communications Server) instance.

        private static SipTransportType _transportType = SipTransportType.Tls;

 

        //The user agent string is put in the outgoing message headers to indicate the Application used.

        private static String _applicationName = "UCMASampleCode";

 

        // Other UCMA objects, for global placeholding.

        CollaborationPlatform _collabPlatform;

        UserEndpoint _userEndpoint;

        LocalOwnerPresence _localOwnerPresence;

        CustomPresenceCategory _userState;

        CustomPresenceCategory _machineState;

        CustomPresenceCategory _noteCategory;

        #endregion

 

        #region Methods Modified for Xml Serialization sample

 

        /// <summary>

        /// This serialize method wraps an XmlSerializer. The purpose of having this code here

        /// is to omit the XmlDeclaration from our genereated xml snippets.

        ///

        /// </summary>

        /// <param name="objectToSerialize">This object should be of type 'type'</param>

        /// <param name="type">The type of object to serialize</param>

        /// <returns></returns>

        public static String Serialize(Object objectToSerialize, Type type)

        {

            if (objectToSerialize == null) throw new ArgumentNullException("objectToSerialize");

            if (type == null) throw new ArgumentNullException("type");

 

            XmlSerializer serializer = new XmlSerializer(type);

            StringWriter sw = new StringWriter(new StringBuilder(128), CultureInfo.InvariantCulture);

            XmlWriterSettings setting = new XmlWriterSettings();

            setting.OmitXmlDeclaration = true;

            XmlWriter messageWriter = XmlWriter.Create(sw, setting);

            serializer.Serialize(messageWriter, objectToSerialize);

            messageWriter.Close();

            sw.Close();

 

            return sw.ToString();

        }

 

        static void Main(string[] args)

        {

            // Get Note xml

            noteType note = new noteType();

            noteTypeBody body = new noteTypeBody();

            body.Value = "My Note Text2!";

            note.body = new noteTypeBody[] { body };

            body.type = "personal";

            body.uri = "";

            _noteXml = Serialize(note, typeof(noteType));

            Console.WriteLine("The Note Xml:" + _noteXml);

 

            // Get MachineState Xml

            machineState mState = new machineState();

            mState.availability = (uint)_onlineAvail;

            mState.availabilitySpecified = true;

            mState.manual = false;

            _machineStateXml = Serialize(mState, typeof(stateType));

            Console.WriteLine("MachineState Xml: " + _machineStateXml);

 

            // Get UserStateXml

            userState uState = new userState();

            uState.availability = (uint)_busyAvail;

            uState.availabilitySpecified = true;

            uState.manual = true;

            _userStateXml = Serialize(uState, typeof(stateType));

            Console.WriteLine("UserState Xml: " + _userStateXml);

            Console.WriteLine("**** Press Enter to begin Publishing Presence...");

            Console.ReadKey();

 

            // Actually do the publish

            PublishPresence PubPres = new PublishPresence();

            PubPres.Run();

 

            Console.WriteLine("**** Wait for any interesting notifications. Press enter to begin cleanup and exit...");

            Console.ReadKey();

            PubPres.DeletePresenceAndCleanup();

            Console.WriteLine("**** Presence has been Deleted and Endpoint cleanup has been started...");

            Console.WriteLine("Push a key exit...");

            Console.ReadKey();

        }

 

        /// <summary>

        /// The event handler for the Category Notification. Notifications come in the form of a list of items.

        /// We are only interested in state publications here, so we will only process those.

        /// </summary>

        /// <param name="sender"></param>

        /// <param name="e"></param>

        void LocalOwnerPresence_CategoryNotificationReceived(object sender, CategoryNotificationEventArgs e)

        {

            Console.WriteLine("Received presence category notifications");

            foreach (PresenceCategoryWithMetaData category in e.CategoryList)

            {

                if (category.Name.Equals("state"))

                {

                    //get the xml data

                    string rawXml = category.CreateInnerDataXml();

                    if (rawXml == null || rawXml.Trim().Length == 0)

                    {

                        break;

                    }

 

                    StringReader reader = new StringReader(rawXml);

                    XmlDocument metadataDocument = new XmlDocument();

                    metadataDocument.Load(reader);

 

                    XmlNodeList stateNodeList = metadataDocument.ChildNodes;

                    if (stateNodeList == null || stateNodeList.Count == 0)

                    {

                        continue;

                    }

 

                    //find the availability value

 

                    XmlNode stateNode = stateNodeList[0];

 

                    if (stateNode.Name != "state")

                    {

                        continue;

                    }

 

                    XmlNodeList availabilityNodeList = stateNode.ChildNodes;

                    if (availabilityNodeList == null || availabilityNodeList.Count == 0)

                    {

                        continue;

                    }

 

                    XmlNode availabilityNode = availabilityNodeList[0];

                    if (availabilityNode.Name != "availability")

                    {

                        continue;

                    }

 

                    if (availabilityNode.InnerText == null)

                    {

                        continue;

                    }

 

                    int availability;

 

                    if (Int32.TryParse(availabilityNode.InnerText, out availability))

                    {

                        Console.WriteLine("Availability received: {0} in container {1}", availability, category.ContainerId);

                        break;

                    }

                }

                else

                    if (category.Name.Equals("note"))

                    {

                        string rawXml = category.CreateInnerDataXml();

                        if (rawXml == null || rawXml.Trim().Length == 0)

                        {

                            break;

                        }

 

                        StringReader reader = new StringReader(rawXml);

                        XmlDocument metadataDocument = new XmlDocument();

                        metadataDocument.Load(reader);

 

                        XmlNodeList stateNodeList = metadataDocument.ChildNodes;

                        if (stateNodeList == null || stateNodeList.Count == 0)

                        {

                            continue;

                        }

 

                        //find the availability value

 

                        XmlNode theNode = stateNodeList[0];

                        if (theNode.FirstChild != null)

                            Console.WriteLine(String.Format("**** Recvd note text: {0} in container {1}", theNode.FirstChild.InnerText, category.ContainerId));

                    }

 

            }

        }

 

        #endregion

 

 

        #region public methods

 

       

 

        public void Run()

        {

            // Prepare and instantiate the platform. This starts a chain of events that

            // eventually ends in presence getting published.

            InitalizePlatform(_applicationName, _transportType);

        }

 

        /// <summary>

        /// Deletes the presence categories we just published.

        /// </summary>

        public void DeletePresenceAndCleanup()

        {

 

            // Delete the presence category just published

            _localOwnerPresence.BeginDeletePresence(new PresenceCategory[] { _userState, _machineState, _noteCategory },

                DeletePresenceCompleted, false);

           

        }

        #endregion

 

        #region private methods

        // Create and initialize a platform

        private void InitalizePlatform(string _applicationName, SipTransportType sipTransportType)

        {

            //Initalize and startup the platform.

            ClientPlatformSettings clientPlatformSettings = new ClientPlatformSettings(_applicationName, sipTransportType);

            clientPlatformSettings.DefaultAudioVideoProviderEnabled = false;

            _collabPlatform = new CollaborationPlatform(clientPlatformSettings);

            _collabPlatform.BeginStartup(PlatformStartupCompleted, null);

            Console.WriteLine("Starting the platform.");

        }

 

        // Shutdown a platform

        private void ShutdownPlatform()

        {

            _collabPlatform.BeginShutdown(PlatformShutdownCompleted, null);

            Console.WriteLine("Shutting down the platform.");

        }

 

        //Given a CollaborationPlatform, initalize and register an User Endpoint.

        private void InitalizeRegisteredUserEndpoint(String userURI, String userServer, System.Net.NetworkCredential credential)

        {

            //Initalize and register the endpoint, using the credentials of the user the application will be acting as.

            UserEndpointSettings userEndpointSettings = new UserEndpointSettings(userURI, userServer);

            userEndpointSettings.Credential = CredentialCache.DefaultNetworkCredentials;

            _userEndpoint = new UserEndpoint(_collabPlatform, userEndpointSettings);

            _userEndpoint.BeginEstablish(EndpointEstablishCompleted, null);

            Console.WriteLine("Establishing down the endpoint.");

        }

 

        // Terminate the registration of a userendpoint object

        private void TerminateEndpoint()

        {

            _userEndpoint.BeginTerminate(TerminateEndpointCompleted, null);

            Console.WriteLine("Terminating the endpoint.");

        }

 

        // AsyncCallback for the platform start operation

        private void PlatformStartupCompleted(IAsyncResult result)

        {

            try

            {

                _collabPlatform.EndStartup(result);

                Console.WriteLine("The platform has now started.");

                // Create a user endpoint using the network credential object defined above.

                // Again, the credentials used must be for a user enabled for Office Communications Server,

                // and capable of logging in from the machine that is running this code.

                InitalizeRegisteredUserEndpoint(_userURI, _userServer, _credential);

 

            }

            catch (ConnectionFailureException connFailEx)

            {

                // ConnectionFailureException will be thrown when the platform cannot connect.

                // ClientPlatforms will not throw this exception on startup.

                // It is left to the developer to write real error handling code.

                Console.WriteLine(connFailEx.ToString());

            }

            catch (InvalidOperationException ioe)

            {

                // InvalidOperationException will be thrown when the platform is already started or terminated.

                // It is left to the developer to write real error handling code.

                Console.WriteLine(ioe.ToString());

            }

        }

 

        // AsyncCallback for the platform shutdown operation

        private void PlatformShutdownCompleted(IAsyncResult result)

        {

            //Shutdown actions will not throw.

            _collabPlatform.EndShutdown(result);

            Console.WriteLine("The platform has now shutdown.");

        }

 

        // AsyncCallback for the endpoint register operation

        private void EndpointEstablishCompleted(IAsyncResult result)

        {

            try

            {

                _userEndpoint.EndEstablish(result);

                Console.WriteLine("The User Endpoint owned by URI: ");

                Console.Write(_userEndpoint.OwnerUri);

                Console.Write(" is now established and registered.");

 

                // LocalOwnerPresence is the main class to manage this user's presence data

                _localOwnerPresence = _userEndpoint.LocalOwnerPresence;

 

                // Subscribe to receive all existing presence notifications

                _localOwnerPresence.CategoryNotificationReceived += new EventHandler<CategoryNotificationEventArgs>(LocalOwnerPresence_CategoryNotificationReceived);

                _localOwnerPresence.BeginSubscribe(PresenceSubscribeCompleted, null);

                Console.WriteLine("Beginning the subscription for this endpoint owner's presence notifications.");

            }

            catch (ConnectionFailureException connFailEx)

            {

                // ConnectionFailureException will be thrown when the endpoint cannot connect to the server, or the credentials are invalid.

                // It is left to the developer to write real error handling code.

                Console.WriteLine(connFailEx.ToString());

            }

            catch (InvalidOperationException iOpEx)

            {

                // InvalidOperationException will be thrown when the endpoint is not in a valid state to connect. To connect, the platform must be started and the Endpoint Idle.

                // It is left to the developer to write real error handling code.

                Console.WriteLine(iOpEx.ToString());

            }

            catch (RegisterException regEx)

            {

                // RegisterException will be thrown when the endpoint cannot be registered (usually due to bad credentials).

                // It is left to the developer to write real error handling code (here, the appropriate action is likely reprompting for user/password/domain strings).

                Console.WriteLine(regEx.ToString());

            }

            catch (AuthenticationException ae)

            {

                // AuthenticationException will be thrown when a general authentication-related problem occurred.

                // It is left to the developer to write real error handling code (here, the appropriate action is likely reprompting for user/password/domain strings).

                Console.WriteLine(ae.ToString());

            }

            catch (OperationTimeoutException ate)

            {

                //OperationTimeoutException will be thrown when server did not respond for Register request.

                // It is left to the developer to write real error handling code (here, the appropriate action may be to try again later).

                Console.WriteLine(ate.ToString());

            }

        }

 

        // AsyncCallback for the endpoint terminate operation

        private void TerminateEndpointCompleted(IAsyncResult result)

        {

            _userEndpoint.EndTerminate(result);

            Console.WriteLine("The User Endpoint owned by URI: ");

            Console.Write(_userEndpoint.OwnerUri);

            Console.Write(" is now terminated.");

 

            ShutdownPlatform();

        }

 

 

        // AsyncCallback to publish presence state

        private void PublishPresenceCompleted(IAsyncResult result)

        {

            try

            {

                _localOwnerPresence.EndPublishPresence(result);

                Console.WriteLine("**** Presence has been Published. Push a key to Delete Presence and Cleanup Endpoint...");

            }

            catch (PublishSubscribeException pse)

            {

                // PublishSubscribeException is thrown when there were exceptions during the publication of this category

                // such as badly formed sip request, duplicate publications in the same request etc

                // Include exception handling code here

                Console.WriteLine(pse.ToString());

            }

            catch (RealTimeException rte)

            {

                // RealTimeException is thrown when SIP Transport, SIP Authentication, and credential-related errors are

                // encountered.

                // Include exception handling code here

                Console.WriteLine(rte.ToString());

            }

        }

 

        // AsyncCallback to subscribe to endpoint owner's presence categories

        private void PresenceSubscribeCompleted(IAsyncResult result)

        {

            try

            {

                _localOwnerPresence.EndSubscribe(result);

 

                // Create a Category to hold the xml to be published, then publish it.

                _userState = new CustomPresenceCategory("state", _userStateXml);

                _machineState = new CustomPresenceCategory("state", _machineStateXml);

                _noteCategory = new CustomPresenceCategory("note", _noteXml);

                // There seems to be a problem when publishing states and notes at the same time.

                // For some reason the note notification is not coming back to UCMA. OC sees this though.

                _localOwnerPresence.BeginPublishPresence(new PresenceCategory[] { _noteCategory }, PublishPresenceCompleted, true);

                _localOwnerPresence.BeginPublishPresence(new PresenceCategory[] { _userState, _machineState, }, PublishPresenceCompleted, true);

            }

            catch (PublishSubscribeException pse)

            {

                // PublishSubscribeException is thrown when there were exceptions during this presence operation

                // such as badly formed sip request, duplicate publications in the same request etc

                // Include exception handling code here

                Console.WriteLine(pse.ToString());

            }

            catch (RealTimeException rte)

            {

                // RealTimeException is thrown when SIP Transport, SIP Authentication, and credential-related errors are

                // encountered.

                // Include exception handling code here

                Console.WriteLine(rte.ToString());

            }

        }

 

        // AsyncCallback to publish presence state

        private void DeletePresenceCompleted(IAsyncResult result)

        {

            _localOwnerPresence.EndDeletePresence(result);

            TerminateEndpoint();

        }

 

 

       

        #endregion

    }

}