Developing UCMA client in .NET 4.0

If you try to run a UCMA client application in .NET 4.0, you might get this exception when you try to create the CollaborationPlatform:

System.IO.FileLoadException was unhandled
    Message=Mixed mode assembly is built against version 'v2.0.50727' of the runtime and cannot be loaded in the 4.0 runtime without additional configuration information.
    Source=Microsoft.Rtc.Collaboration

Add the following lines to your configuration file under the root configuration element to make the error go away:

<startup useLegacyV2RuntimeActivationPolicy="true">
    <supportedRuntime version="v4.0"/>
</startup>

Starting a conversation in UCMA

In a previous article, we learned how to get presence using UCMA. In this article, we'll learn how to start a conversation and send messages using UCMA.

The code for the entire program is located near the end of the article.

The process for starting a conversation is incredibly easy:

  1. Have the UserEndpoint create a Conversation
  2. Create a new InstantMessagingCall
  3. Establish the new IM call

The code:

public class ConnectionService 
{
    public ConversationCreateConversation()
    {
        return new Conversation(_endpoint);
    }
}

public class ConversationService
{
    private ConnectionService _connectionService;
    private Conversation _currentConversation;
    private InstantMessagingCall _currentIMCall;

    public ConversationService(ConnectionService connectionService)
    {
        _connectionService = connectionService;
    }

    public IAsyncResult StartConversationWith(string contactSipUri)
    {
        _currentConversation = _connectionService.CreateConversation();
        _currentIMCall = new InstantMessagingCall( _currentConversation);

        return _currentIMCall.BeginEstablish(
            contactSipUri,
            new ToastMessage("New conversation"), 
            new CallEstablishOptions(),
            EstablishCompleted, 
            null);
    }

    private void EstablishCompleted(IAsyncResult result)
    {
        _currentIMCall.EndEstablish(result);
    }
}

Ending a conversation is similarly simple:

public class ConversationService
{
    public IAsyncResult EndConversation()
    {
        return _currentConversation.BeginTerminate(TerminateCompleted, null);
    }

    private void TerminateCompleted(IAsyncResult result)
    {
        _currentConversation.EndTerminate(result);
    }
}

The remaining task is to send messages:

public class ConversationService
{
    public IAsyncResult SendMessage(string message)
    {
        return _currentIMCall.Flow.BeginSendMessage(message, SendMessageCompleted, null);
    }

    private void SendMessageCompleted(IAsyncResult result)
    {
        _currentIMCall.Flow.EndSendMessage(result);
    }
}

Here is the whole console application in one place:

using System;
using System.Net;
using Microsoft.Rtc.Signaling;
using Microsoft.Rtc.Collaboration;

namespace OCS_IM
{
    public static class Program
    {
        public static void Main(string[] args)
        {
            var connection = new ConnectionService();

            connection.Start().AsyncWaitHandle.WaitOne();

            connection.InitEndpoint(
                "sip:you@domain", 
                "ocs-server-name")
                .AsyncWaitHandle.WaitOne();

            var conversation = new ConversationService(connection);

            conversation.StartConversationWith("sip:contact@domain")
                .AsyncWaitHandle.WaitOne();

            conversation.SendMessage("A message");

            Console.WriteLine("Press any key to end program.");
            Console.ReadLine();

            conversation.EndConversation().AsyncWaitHandle.WaitOne();
            connection.Stop().AsyncWaitHandle.WaitOne();

            Console.ReadLine();
        }
    }

    public class ConversationService
    {
        private ConnectionService _connectionService;
        private Conversation _currentConversation;
        private InstantMessagingCall _currentIMCall;

        public ConversationService(ConnectionService connectionService)
        {
            _connectionService = connectionService;
        }

        public IAsyncResult StartConversationWith(string contactSipUri)
        {
            _currentConversation = _connectionService.CreateConversation();
            _currentIMCall = new InstantMessagingCall(_currentConversation);

            return _currentIMCall.BeginEstablish(
                contactSipUri,
                new ToastMessage("New conversation"),
                new CallEstablishOptions(),
                EstablishCompleted,
                null);
        }

        private void EstablishCompleted(IAsyncResult result)
        {
            _currentIMCall.EndEstablish(result);
        }

        public IAsyncResult SendMessage(string message)
        {
            return _currentIMCall.Flow.BeginSendMessage(
                message, SendMessageCompleted, null);
        }

        private void SendMessageCompleted(IAsyncResult result)
        {
            _currentIMCall.Flow.EndSendMessage(result);
        }

        public IAsyncResult EndConversation()
        {
            return _currentConversation.BeginTerminate(
                TerminateCompleted, null);
        }

        private void TerminateCompleted(IAsyncResult result)
        {
            _currentConversation.EndTerminate(result);
        }
    }

    public class ConnectionService
    {
        private CollaborationPlatform _platform;
        private UserEndpoint _endpoint;

        public IAsyncResult Start()
        {
            var settings = new ClientPlatformSettings("OCS_IM", SipTransportType.Tls);

            _platform = new CollaborationPlatform(settings);

            return _platform.BeginStartup(StartupCompleted, null);
        }

        private void StartupCompleted(IAsyncResult result)
        {
            _platform.EndStartup(result);
        }

        public IAsyncResult InitEndpoint(string currentUserSipUri, string ocsServerName)
        {
            var settings = new UserEndpointSettings(currentUserSipUri, ocsServerName);

            _endpoint = new UserEndpoint(_platform, settings);
            _endpoint.Credential = CredentialCache.DefaultNetworkCredentials;

            return _endpoint.BeginEstablish(EstablishCompleted, null);
        }

        private void EstablishCompleted(IAsyncResult result)
        {
            _endpoint.EndEstablish(result);
        }

        public Conversation CreateConversation()
        {
            return new Conversation(_endpoint);
        }

        public IAsyncResult Stop()
        {
            return _endpoint.BeginTerminate(TerminateCompleted, null);
        }

        private void TerminateCompleted(IAsyncResult result)
        {
            _endpoint.EndTerminate(result);
            _platform.BeginShutdown(ShutdownCompleted, null);
        }

        private void ShutdownCompleted(IAsyncResult result)
        {
            _platform.EndShutdown(result);
        }
    }
}

How to get presence via UCMA

UCMA is a client-side API that allows you to integrate OCS functionality into your application. You can retrieve presence, publish presence, manage your subscriptions, and start conversations and conferences.

In this article, we'll take a look at retrieving presence information from UCMA by subscribing to the contact.

SIP URIs

OCS identifies people through the SIP protocol. You can get a user's SIP address from Active Directory; each valid user will have a single, unique SIP address. SIP addresses generally are of the format:

sip:username@domain

Subscriptions vs. queries

Subscribing lets you get real-time presence updates per contact. So, if you are subscribed to John Smith, you will be notified any time his presence changes. Jane Smith’s presence, on the other hand, will become stale in your application because you’re not subscribed to her and you don’t get any notifications. In her case, you would have to ask OCS for her presence information every minute or ten minutes or whatever to get latest information.

Step 1: Initialize platform

Using UCMA starts with initializing the collaboration platform.

public class PresenceRetriever
{
    private CollaborationPlatform _platform;

    public IAsyncResult Start()
    {
        var settings = new ClientPlatformSettings("OCS_GetPresence", SipTransportType.Tls);

        _platform = new CollaborationPlatform(settings);

        return _platform.BeginStartup(InitCompleted, null);
    }

    private void InitCompleted(IAsyncResult result)
    {
        _platform.EndStartup(result);
    }
}

Step 2: Initialize endpoint

The next step is to initialize your application endpoint:

public class PresenceRetriever
{
    // ... 

    private UserEndpoint _endpoint;

    public IAsyncResult InitEndpoint(string currentUserSipUri, string ocsServerPath)
    {
        var settings = new UserEndpointSettings(currentUserSipUri, ocsServerPath);

        _endpoint = new UserEndpoint(_platform, settings);

        _endpoint.Credential = CredentialCache.DefaultNetworkCredentials;

        return _endpoint.BeginEstablish(EstablishCompleted, null);
    }

    private void EstablishCompleted(IAsyncResult result)
    {
        _endpoint.EndEstablish(result);
    }
}

Step 3: Subscribe to presence

You can subscribe to one or more presences at a time:

public class PresenceRetriever
{
    // ... 

    public void Subscribe(params string[] sipUris)
    {
        _endpoint.RemotePresence.PresenceSubscriptionCategories = 
            new string[] { "state" };

        _endpoint.RemotePresence.PresenceNotificationReceived += 
            PresenceNotificationReceived;

        var targets = new List<RemotePresentitySubscriptionTarget>();

        foreach(var sipUri in sipUris)
        {
            targets.Add(new RemotePresentitySubscriptionTarget(sipUri, null));
        }

        _endpoint.RemotePresence.BeginAddTargets(targets, AddTargetsCompleted, null);
    }

    private void PresenceNotificationReceived(object sender, RemotePresenceNotificationEventArgs e)
    {
        foreach(var notice in e.Notifications)
        {
            foreach(var category in notice.Categories)
            {
                var state = PresenceState.Create(category);

                Console.WriteLine(state.Availability);
            }
        }
    }

    private void AddTargetsCompleted(IAsyncResult result)
    {
        _endpoint.RemotePresence.EndAddTargets(result);
    }
}

If you run the application now, you should get notified every time the contacts you are subscribed to change their presences.

Final step: teardown

When your application is done with OCS, you need to teardown your endpoint and the platform. The code is simple:

public class PresenceRetriever
{
    // ... 

    public IAsyncResult Stop()
    {
        return _endpoint.BeginTerminate(TerminateCompleted, null);
    }

    private void TerminateCompleted(IAsyncResult result) 
    {
        _endpoint.EndTerminate(result);
        _platform.BeginShutdown(ShutdownCompleted, null);
    }

    private void ShutdownCompleted(IAsyncResult result)
    {
        _platform.EndShutdown(result);
    }
}

Asynchronous Calls

Notice how the startup and shutdown methods return IAsyncResults. The reason for this is that since the calls to OCS are performed asynchronously, you may get into situations where you try to establish the endpoint before the platform has been setup or you try to query for presence before the endpoint is established. The way to make sure that all the necessary steps are done is to use the result to figure out whether the operation has completed or force your calling method to wait until the operation has completed via AsyncWaitHandle.WaitOne method on your result.

How to publish presence in UCMA without using XML

Michael Green published a great article about how to publish presence using UCMA, but it involves using XML. If you hate XML as much as I do, this article is for you.

Note that the code presented here is a snippet and therefore won’t compile/work by itself.

Publishing states

The process for publishing your state is the same for any of the four states (user state, machine state, phone state, calendar state). Let’s look at how to publish the user state:

var userState = new UserState(availability, activity);

_endpoint.LocalOwnerPresence.BeginPublishPresence(
    new PresenceCategory[] { userState },
    PublishPresenceCompleted,
    null);

Wow, that was tough! The other three states have similar classes you can use: MachineState, PhoneState, and CalendarState, all of which are located in the Microsoft.Rtc.Internal.Collaboration namespace. PublishPresenceCompleted is as about as dumb as you might expect:

_endpoint.LocalOwnerPresence.EndPublishPresence(result);

What if you don’t have activity (anything you want it to be)? Don’t pass anything in, and Communicator will show the default activity for your availability.

Publishing device capabilities

Publishing the device capabilities is slightly more involved:

var capability = new DeviceCapability(true, true, true);

var capabilities = new DeviceCapabilities(_endpoint.InnerEndpoint.Uri);

// Set capabilities; i.e., capabilities.Text = capability

var device = new Device(
    _endpoint.InnerEndpoint.Id, _
    new DeviceCapabilities[] { capabilities });

Like the *State classes, Device derives from PresenceCategory. You can view the XML that will be generated by calling PresenceCategory.GetCategoryDataXml() method.

1 of 1 pages

On the Side