Sending a SUBSCRIBE message with headers

UCMA makes it quite easy to send a subscribe message and then add, remove, or retrieve contacts and groups.

endpoint.ContactGroupServices.EndSubscribe(endpoint.ContactGroupServices.BeginSubscribe(null, null));

In general the ContactGroupServices object, accessible from the Microsoft.Rtc.Collaboration.ContactsGroups namespace, provides a number of useful methods that make it easy to work with contacts.

Recently, however, I came across the challenge that I needed to add a header to my SUBSCRIBE message. Granted, this is not an extremely common scenario but there are a number of headers that have special meanings to Lync and I needed to add one of them.

Unfortunately, this is where ContactGroupServices loses its usefulness as it does not allow you to change the headers.  You will therefore need to move one level down to the Sip signaling level. Fortunately, there is an object here that will help – SipSubscription.

This is a slightly confusing object to use, so I thought I would provide some clarification here.  The following is how you will send the SUBSCRIBE message.

SipSubscription subscription = new SipSubscription(

    endpoint.InnerEndpoint,

    target,

    MicrosoftRoamingContactEvent,

    contactSubcriber);

try

{

   subscription.EndSubscribe(subscription.BeginSubscribe(null, null));

}

catch (Exception ex)

{

    // Do something slightly more advanced than crying

}

Well, that isn’t so difficult.  But what are these parameters you are passing?  Well, first I am assuming the endpoint object you have is of type UserEndpoint.  Therefore, the first object is of typeRealTimeEndpoint, which is what the lower level Sip signaling layer uses for your endpoint .  This is easily obtainable using the InnerEndpoint property of UserEndpoint.

The second parameter is also not rocket science.  It is a RealTimeEndpoint indicating the target address.  If you are subscribing to yourself, the easiest way to obtain this is the following.

RealTimeAddress target = new RealTimeAddress(endpoint.OwnerUri);

The third parameter is the event package name.  There are different event packages possible, but for simplicity sake I will just tell you that the one ContactGroupServicesuses is called “vnd-microsoft-roaming-contacts”.  So just pass this string for this case.  A Bing search will provide more information on this document as well as other event packages available.

The last parameter is where the fun begins.  This must be an instance of an object that implements the ISipSubscriptionProcessor interface.  This interface has the following methods.

GetExtensionHeaders– If you guessed that this is where you need to add your headers, you would be correct.

GetMessageBody –Typically you do not need to do much here for contacts.

ProcessErrorResponse– If something bad happens, this is how UCMA will notify you.

ProcessNotification– When contacts are returned, UCMA will call you here.  Unfortunately you will need to parse the xml returned to understand what happened – something that ContactGroupServicesnormally handles for you.

SubscriptionStateChanges– Called each time your subscription changes.

This is not a difficult interface to implement, but the entire process is not overly straightforward.  The following is my simplified implementation of it.

public class AddHeaderSubscriber : ISipSubscriptionProcessor

{

    private Exception _exception;

    public Exception Exception

    {

        get { return _exception; }

    }

    public void GetExtensionHeaders(SipSubscription.RequestType requestType, out System.Collections.Generic.IEnumerable<SignalingHeader> extensionHeaders)

    {

        // Add our header

        List<SignalingHeader> headers = new List<SignalingHeader>();

        headers.Add(new SignalingHeader("SUPPORTED", "levitation"));

        extensionHeaders = headers;

    }

    public void GetMessageBody(SipSubscription.RequestType requestType, out System.Net.Mime.ContentType contentType, out byte[] messageBody)

    {

        messageBody = new byte[0];

        contentType = null;

    }

    public void ProcessErrorResponse(SipResponseData message)

    {

        _exception = new Exception(message.ResponseText);

    }

    public void ProcessNotification(SipMessageData message)

    {

        // Process a contact if we dare

    }

    public void SubscriptionStateChanged(SubscriptionStateChangedEventArgs eventArg)

    {

        switch (eventArg.NewState)

        {

            case SubscriptionSignalingState.Subscribed:

                // We subscribed!

                break;

            case SubscriptionSignalingState.Subscribing:

                // We are thinking about it

                break;

            case SubscriptionSignalingState.Terminated:

                // I'll be back!

                break;

            case SubscriptionSignalingState.WaitingForRetry:

                // Doo bee doo bee doo bee doo

                break;

            default:

                // ignore other states

                break;

        }

    }

}

Note that it is entirely possible to use bothContactGroupServices and SipSubscription together, though I recommend unsubscribing from one first before using the other.  When possible it is always best to allowContactGroupServices to handle the bulk of the work.  To unsubscribe with SipSubscription, useBeginTerminate and EndTerminate.