WCF PollingDuplexHttp Services, Silverlight, and the task parallel library-lessons learned

The PollingDuplexHttp binding has been in the Silverlight SDK for a while now, and it’s a great way to have a service that can send messages to a client.  While Silverlight does support sockets and net.tcp, the restriction to ports 4502-4534 can be problematic, since firewall ports may need to be opened on both client machines and in the data center.  With duplex HTTP though, everything goes through port 443 (or 80 if you don’t care about encryption), so all your traffic is likely to just make it through, which helps ease deployment headaches.  If you’re not familiar with it, there are some great blog posts out there that you should check out that describe it in detail.

That being said, the binding isn’t without its issues, and having built some services with the binding I’ve figured out a few things that would have been nice to know going into the process:

A simple duplex service:

To start with, consider a service like this one:

    [ServiceContract(CallbackContract=typeof(IDuplexClient))]
    public interface ITestService
    {
        [OperationContract]
        string GetCurrentTimeString();
        [OperationContract]
        DateTime GetCurrentTime();
        [OperationContract]
        void RequestTimeUpdates();
        [OperationContract]
        void StopTimeUpdates();
    }

    [ServiceContract]
    public interface IDuplexClient
    {
        [OperationContract(IsOneWay = true)]
        void PublishTime(DateTime time);
    }

This service obviously has a couple of methods to get the current time, as well as ones to subscribe and stop time updates.  The callback contract has an event that can be fired to notify that the time has changed.  A sample implementation of the RequestTimeUpdates() method might look like this:

List<IDuplexClient> clients = new List<IDuplexClient>();
public void RequestTimeUpdates()
{
    // Grab the client callback channel. 
    //this works-each client generates a unique callback channel...
    IDuplexClient c= OperationContext.Current.GetCallbackChannel<IDuplexClient>();
    lock (clients)
    {
        if (clients.Contains(c) == false)
            clients.Add(c);
    }
}

In this case, the duplex channel is unique per client proxy, and storing it on a list is one possibility (more on this later).  Another method could do something like this to publish to all registered Silverlight clients:

private void Publish()
{
    lock(clients)
    {
        foreach (IDuplexClient c in clients)
            c.PublishTime(DateTime.Now);
    }
}

Now, at this point the service will work, but there are a couple of questions I started to wonder about.  For example, what happens if a client registers, and then exits without calling your obviously named deregistration method?  As it turns out, if you do this, you’ll get a timeout on the method call, which blocks your service.  To handle that case, you can try something like this:

private void Publish()
{
    lock(clients)
    {
        List<IDuplexClient> toRemove = new List<IDuplexClient>();
        foreach (IDuplexClient c in clients)
        {
            try
            {
                c.PublishTime(DateTime.Now);
            }
            catch (Exception ex)
            {
                toRemove.Add(c);
            }
        }
        foreach (IDuplexClient c in toRemove)
            clients.Remove(c);
    }
}

Yes, this is a bit extreme, but now ANY exception when calling the duplex event will result in the client getting removed from the callback subscriptions, so at most you’ll time out once per dead client (and not time out perpetually).  Of course, now the problem is that if you have, say, 10 clients, and 9 of them exit without unregistering, then these timeouts (which default to 60 seconds) happen sequentially.  A third iteration of the publish method might look like this:

private void Publish()
{
    lock (clients)
    {
        List<IDuplexClient> toRemove = new List<IDuplexClient>();
        Action<IDuplexClient> invokeAction = (callback) =>
        {
            try
            {
                callback.PublishTime(DateTime.Now);
            }
            catch (Exception ex)
            {
                string s = ex.ToString();
                toRemove.Add(callback);
            }
        };
        System.Threading.Tasks.Parallel.ForEach<IDuplexClient>(clients, invokeAction);

        foreach (IDuplexClient r in toRemove)
            clients.Remove(r);
    }
}

Now, by invoking the callbacks in parallel, you can have, at most, one 60 second timeout.  This means that in our previous example, instead of the 10th client having to wait 9 minutes for the event, it should receive its event immediately.  The service, however, will still be blocked for up to 60 seconds while the publish method fires, which,  depending on your architecture, might mean yet another level of threading.  On top of that, the code to fire an event is starting to get messy, and avoiding having to duplicate it for a service with a large number of events is a good idea.

The Callback Manager

To combat these problems, I’ve wrapped these ideas into a callback manager for duplex services:

public class CallbackManager<KEYTYPE, VALUETYPE>
{
    internal CallbackManager()
    {

    }
    public bool AddSubscription(KEYTYPE key, VALUETYPE value)
    {
        if (m_dict.ContainsKey(key) == false)
        {
            if (m_dict.TryAdd(key, new ConcurrentDictionary<VALUETYPE, bool>()) == false)
                return false;
        }
        return m_dict[key].TryAdd(value, true);
    }

    public bool RemoveSubscription(KEYTYPE key, VALUETYPE value)
    {
        if (m_dict.ContainsKey(key) == true)
        {
            bool b;
            return m_dict[key].TryRemove(value, out b);
        }
        return false;
    }
    //remove ALL subscribers for a key
    public bool RemoveSubscription(KEYTYPE key)
    {
        if (m_dict.ContainsKey(key) == true)
        {
            ConcurrentDictionary<VALUETYPE, bool> b;
            m_dict.TryRemove(key, out b);
            if (b != null)
                return true;
        }
        return false;
    }
    public ICollection<KEYTYPE> GetKeys()
    {
        return m_dict.Keys;
    }

    //if you fire an event, it will only ever be for one key...
    private ICollection<VALUETYPE> GetSubscribers(KEYTYPE key)
    {
        if (m_dict.ContainsKey(key))
            return m_dict[key].Keys;
        return new List<VALUETYPE>(); //empty
    }
    private ICollection<KeyValuePair<VALUETYPE, bool>> GetSubscribersEx(KEYTYPE key)
    {
        if (m_dict.ContainsKey(key))
        {
            return (ICollection<KeyValuePair<VALUETYPE,bool>>)m_dict[key];
        }
        return new List<KeyValuePair<VALUETYPE, bool>>(); //empty
    }

    public void FireEvent(KEYTYPE key,Action<VALUETYPE,bool> action)
    {
        try
        {
            using (BackgroundWorker bg = new BackgroundWorker())
            {
                bg.DoWork += (sender, ea) =>
                    {
                        Action<KeyValuePair<VALUETYPE, bool>> a = callback =>
                            {
                                try
                                {
                                    action(callback.Key, callback.Value);
                                }
                                catch (Exception ex)
                                {
                                    Logger.LogError(ErrorLevel.Information, "Exception sending to callback channel-removing subscription-" + ex.ToString());
                                    RemoveSubscription(key, callback.Key);
                                }
                            };
                        Parallel.ForEach<KeyValuePair<VALUETYPE, bool>>(GetSubscribersEx(key), a);
                    };
                bg.RunWorkerAsync();
            }
        }
        catch (Exception ex)
        {
            Logger.LogError(ErrorLevel.Information, "Exception invoking callback-" + ex.ToString());
        }
    }

    private ConcurrentDictionary<KEYTYPE, ConcurrentDictionary<VALUETYPE, bool>> m_dict = new ConcurrentDictionary<KEYTYPE, ConcurrentDictionary<VALUETYPE, bool>>();
}
}

This class contains methods to manage a collection of subscribers, as well as the main FireEvent method that wraps some of the earlier findings into one generic method.  It takes a delegate for the callback event that you want to fire, and then calls those delegates in parallel.  It also invokes the parallel loop on a background worker thread, which ensures that the service will not block when firing an event.  One side effect of this is that firing multiple events could result in multiple attempts on the same dead proxy, but the code handles these cases, and it’s better than the alternative.

It’s also interesting to note that the delegate that gets passed into the method must itself get wrapped in a delegate (i.e. I can’t just parallel.foreach the action that I get passed in), since it won’t be able to catch a service exception in that case.

One other thing to note-the ConcurrentDictionary class gets used here with a second (bool) template parameter simply because there isn’t an equivalent non-ordered type (like ConcurrentList) in the library.  I’m not sure why, but for the meantime I just added the second type, because the concurrent classes are incredibly useful.  You can modify the collection while it’s being iterated elsewhere, and you aren’t at the mercy of someone forgetting a lock.

So finally, this means that firing an event in the service code looks something like this:

    m_TimeUpdateChannels = new CallbackManager<string, IDuplexClient>();
    m_TimeUpdateChannels.FireEvent("key", (callback, b) =>
    {
        //do something else if I want
        callback.PublishTime(DateTime.Now);
    });

Which is much cleaner than having to write an individual method to fire each event.

So, with a scheme like this in place, writing a duplex service has gotten a whole lot easier to manage.  If you do need to keep track of subscribers, this at least encapsulates it a little, and it keeps most of the WCF plumbing in one place, meaning that the service can concentrate on business logic.

Advertisements
Posted in Silverlight, WCF | 5 Comments

WCF Data contracts, enums, and why they can cause you pain.

WCF is a great technology,but sometimes you run into a problem with it that’ll be really difficult to solve.  The problem I ran into today was with a self-hosted service, a Silverlight client, and the PollingDuplexHttp binding.  Now, aside from all the problems with getting a self-hosted WCF service to work with Silverlight, for some reason I was getting the invocation message across correctly, but I was getting a timeout on the response.  I eventually nailed it down to one of the members in my data contract that contained an enum that looked something like this:

enum myEnum
{
  value1=1,
  value2=2
}

In the data contract, I had a field of type myEnum that had never been assigned a value (since I hadn’t written that code yet), which normally isn’t a problem.  In this case however, it made the WCF deserialization process fall apart, and caused a timeout exception.  As it turns out, in .net the default value for any enum is 0, which usually maps to the first value in the enum.  Had I done this:

enum myEnum
{
  value1,
  value2
}

I would have been fine, since in this case the integer values are implied, and an unassigned enum would have been set to value1.  Instead, I wanted to set the enum values manually to match a legacy app, so I had no value 0, so WCF didn’t know how to interpret it.  I was able to solve the problem by making sure to actually assign a value to the enum (a good idea anyway), and to modify the declaration a bit to keep things neat:

enum myEnum
{
  undefined=0,
  value1=1,
  value2=2
}

Yes, the undefined value doesn’t mean much, but at least it serializes and makes the WCF call work.  With this change you don’t actually have to assign a value to enum members to make them returnable, which helps to reduce coupling.  Not that tricky when you get down to it, but finding the problem was probably a day’s worth of ruling out all the other issues with different versions of Silverlight, a tricky binding like the duplex HTTP, and settings that weren’t matched on the client and server. 

Almost 10 years with .net, and still finding out basic stuff like this.  It really is impossible to know everything.

Posted in Uncategorized | Leave a comment

Manually creating voicemails in Exchange Unified Messaging

Recently, I had someone ask whether it was possible to manually create a message in Exchange UM from a UCMA application.  My first thought was to simply divert the call to an EUM mailbox, but this involved a tricky SIP transfer that required some diversion headers to be set in the INVITE.  It’s probably not impossible to do (and I’m currently trying to figure it out just to prove it can be done), but it turns out that the simpler solution is just to create the message and send it to the user.  After all, voicemails in EUM look a lot like emails with wma or mp3 (in 2010) attachments, right?  As it turns out, it’s just a matter of setting the right headers in the message.  Doing things this way also has the advantage of keeping control over the call during recording and after the message has been recorded, in case you want to do something else in your call flow. 

Using the EWS Managed API, it’s relatively simple to save a message in a user’s mailbox like this:

ExchangeService service = new ExchangeService(ExchangeVersion.Exchange2007_SP1);
EmailMessage message = new EmailMessage(service);
Random r = new Random();
message.Subject = "Voice mail from " + r.Next(0000000, 9999999);
message.From = new EmailAddress("ANI name", "9058825000");
message.ItemClass = "IPM.Note.Microsoft.Voicemail.UM.CA";
message.Sender = new EmailAddress(voicemailapp@rnddev.computer-talk.com); 
message.Body = new MessageBody(BodyType.HTML, "<HTML><body><h1>This is a voice mail.</h1></BODY></HTML>"); 
message.Attachments.AddFileAttachment(@"d:\TestVM.wma");
message.ToRecipients.Add(new EmailAddress("r2TestUser@rnddev.computer-talk.com")); 
message.Save(WellKnownFolderName.Inbox);

This message will at least get you something that has the voicemail icon, and shows the embedded player, but the player will not work.  Also, while Outlook voice access will detect that there’s a message there, it won’t play the contents.  Now, if you look at a voicemail that EUM has recorded you’ll see a bunch of extra headers in the message, such as:

X-CallID: 8bbed00e-109b-4b96-a81f-c9ccf0a82aa9
X-MS-Exchange-Organization-SCL: -1
X-CallingTelephoneNumber: 9058825000
X-VoiceMessageSenderName: ComputerTalk
Subject: Voice Mail from ComputerTalk (52 seconds)
X-VoiceMessageDuration: 52
X-AttachmentOrder: 9058825000 (52 seconds) Voice Mail.wma
Content-Class: Voice-CA
Message-ID: <75d0a188-8a34-48c1-8f93-85cdc3a639e0@RHCTTEX02.corp.computer-talk.com>
Return-Path: MicrosoftExchange329e71ec88ae4615bbc36ab6ce41109e@icescape.com
Date: Mon, 28 Jun 2010 07:01:27 -0400

Inserting those headers in a message using the EWS managed API turns out to be a little tricky.  The properties you need to set are all documented in MSDN and in the MS-OXPROPS spec.  In the EWS managed API, you need to create an ExtendedPropertyDefinition for an extended property that you want to set, and unfortunately, there’s not an enum for these values.  These were my first four attempts at creating the property, and while all of the properties appeared in the message, none of them showed up in Outlook:

ExtendedPropertyDefinition messageLength = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.UnifiedMessaging, 
 "PidNameXVoiceMessageDuration", MapiPropertyType.Integer);
ExtendedPropertyDefinition messageLength = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.UnifiedMessaging, 
0x6801, MapiPropertyType.Integer);
ExtendedPropertyDefinition messageLength = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders,
0x6801, MapiPropertyType.Integer);
ExtendedPropertyDefinition messageLength = new ExtendedPropertyDefinition(DefaultExtendedPropertySet.InternetHeaders, 
"PidNameXVoiceMessageDuration", MapiPropertyType.Integer);

The properties are part of the unified messaging set, but when I tried creating them based on the name or ID with either the InternetHeaders of UnifiedMessaging sets, nothing got added.  What did eventually work was creating the properties by ID only, and then setting them:

//PidTagVoiceMessageDuration
message.SetExtendedProperty(new ExtendedPropertyDefinition(0x6801, MapiPropertyType.Integer), 3);
//PidTagVoiceMessageSenderName
message.SetExtendedProperty(new ExtendedPropertyDefinition(0x6803, MapiPropertyType.String), "Test Sender Name");
//PidTagSenderTelephoneNumber
message.SetExtendedProperty(new ExtendedPropertyDefinition(0x6802, MapiPropertyType.String), "sip:cbardon@rnddev.computer-talk.com");\
//PidTagVoiceMessageAttachmentOrder
message.SetExtendedProperty(new ExtendedPropertyDefinition(0x6805, MapiPropertyType.String), "TestVM.wma");

Now, when you send the message to the user, all the correct voicemail control s light up as expected.  The embedded player works, play on phone works, and Outlook Voice Access can play the message back.  My next thought was, if that worked, why not just use System.Net.Mail instead of EWS:

System.Net.Mail.MailMessage m = new System.Net.Mail.MailMessage("sender@computer-talk.com", "receiver@computer-talk.com");
m.Sender = new System.Net.Mail.MailAddress("voicemailapp@computer-talk.com");
m.Subject = "Voice Mail Test";
m.IsBodyHtml = true;
m.Body = @"<HTML><body><h1>This is a voice mail.</h1></BODY></HTML>";
m.Headers.Add("Content-Type", @"application/ms-tnef; name=""winmail.dat""");
m.Headers.Add("Content-Transfer-Encoding", "binary");
m.Headers.Add("X-VoiceMessageDuration", "3");
m.Headers.Add("Thread-Topic", "Voice mail Test");
m.Headers.Add("Content-Class", "voice");
m.Headers.Add("X-CallingTelephoneNumber", "sip:sender@computer-talk.com");
m.Headers.Add("X-VoiceMessageSenderName", "Test Sender Name");
m.Headers.Add("X-AttachmentOrder", "TestVM.wma");
m.Attachments.Add(new System.Net.Mail.Attachment(@"d:\TestVM.wma"));
System.Net.Mail.SmtpClient c = new System.Net.Mail.SmtpClient("exchangeServerAddress");
c.Send(m);

This appears to work just as well, and as a bonus the code to set the custom headers is much simpler than it was with the exchange APIs.

Of course, as with any solution like this there’s a catch, and the biggest one so far seems to be that with Exchange 2010, there’s no way to get voicemail preview on these messages.  This is understandable, since I believe this is part of the EUM inbound call flow (and not an offline process on all voicemails), but I’m working on clarifying this.  Otherwise, this method appears to work, and requires no extra SIP peer configuration on the EUM server. 

Posted in Uncategorized | Leave a comment

Provisioning your UCMA applications-choosing the right location

Unfortunately, this post has more questions than answers, but hopefully it can try to explain how to get around a nasty error I’ve run into with UCMA. First, if you’re not familiar with creating app GRUUs with ApplicationProvisioner.exe, check out the MSDN article first-it’s got some good step by step instructions, and should cover you for most scenarios.

The situation I ran into may be a little more esoteric, but the application that was being provisioned needed to sit in the DMZ. When going through which servers had capacity, the first candidate that our IT group identified was the machine that was also running the OCS Edge server. I had a strange feeling at the time that this wouldn’t work, and sure enough, the following error popped up when I tried to create the GRUU:

Checking on the front end server, you won’t see the edge server in the list of trusted hosts, but based on the error message, it might imply that the edge server is automatically treated as authenticated, since the app name and port were definitely unique.  What’s strange is that trying to provision the same application on another server that is actually listed as a trusted host works…or at least the provisioner will create a GRUU for it.  When you try to run an app on that server though, the app will fail when setting up an ApplicationEndpoint.  The same thing happens if the FQDN of an already provisioned application is added as a trusted host-you can add the trusted host entry, but the application will stop working. 

I suppose there are a couple of takeaways from this exercise.  First, even though you don’t get an explicit error from provisioning, don’t try running a UCMA application on a trusted host, at least when “treat as authenticated” is checked.  This should be pretty straightforward, since with Application users in R2, there aren’t a whole lot of cases where you’d need to create trusted host entries anymore.  The second takeaway though is that running a UCMA application on the edge server is apparently not supported, and the error that you get isn’t really an intuitive one. 

Posted in Uncategorized | Leave a comment

Custom Windows Event Log Strangeness (or, why does Exists exist?)

If you’re writing a Windows service, or even a windows application, the idea of writing to a windows event log is appealing.  After all, there’s a decent tool for reading it, it’s easy to read and write from, and it handles things like rollover so you don’t have to roll your own solution.  So, you might have a piece of code that looks something like this:

public static void LogError(string error, EventLogEntryType level)
{           
        if (!EventLog.SourceExists(Name))
        {
            EventLog.CreateEventSource(Name, Name);
        }
        System.Diagnostics.EventLog log = new EventLog(Name);
        log.Source = Name;
        log.MaximumKilobytes = MaxKilobytes;
        log.WriteEntry(error, level);
}

This has the advantage of letting you define your own event log name and register a source for it, so you can create something in the Application Logs section of the event viewer.  The documentation on MSDN is pretty straightforward on this, and says that CreateEventSource throws an ArgumentException if the name is already in use, hence the call to SourceExists. 

The strange part of the equation is the event log name.  I noticed when I tried running a test and Creating “Custom Event Source 1” and “Custom Event Source 2”, that I got an ArgumentException when I tried creating the sources.  The names are different, so you’d think they should work, but the exception details said that the first 8 characters matched an existing log name.  Yes, even in Windows 7, 8.3 will never die…  Here’s what’s strange, even adding EventLog.Exists(Name) doesn’t work, because the method will still return false even if creating the source will throw an exception.  Since there’s no method in the EventLog class that checks for a similar log name (instead of just an identical one), I created my own.  This code checks for a similar log name, and if one is found uses it, but it’s simple enough to modify to do whatever you want. 

private static void CreateSource(string name)
{
    try
    {
        if (!EventLog.SourceExists(Name))
        {
            //look for a similar log name   
            if (LogExistsSimilar(ref name) == false)
                EventLog.CreateEventSource(Name, Name);
            else
            {
                string oldName = Name;
                Name = name;  //use the similar log name
            }
        }
    }
    catch (ArgumentException ex)
    {
        Name = "Application";
    }
}

//need to compare the first 8 characters of a log name
private static bool LogExistsSimilar(ref string name)
{
    foreach (EventLog e in EventLog.GetEventLogs())
    {
        if (e.LogDisplayName.StartsWith(name.Substring(0, 8)))
        {
            name = e.LogDisplayName;
            return true;
        }
    }
    return false;
}

This is just something to keep in mind if you want to create custom event logs (which I think is a good idea), and want to be able to customize what they’re called at runtime (or at least configuration time).  In my case, I need to be able to install multiple instances of a UCMA service on one server, and I want to be able to separate the logging for each of them.  Now this means I can call the logs whatever I want, as long as the first 8 characters of the log name are unique.

Posted in Uncategorized | Leave a comment

TLS exceptions in UCMA services-tracking down the issue

I ran into an issue today that was baffling me for a couple of hours.  I had a UCMA service that worked fine on one machine, but when a colleague set it up on another machine, I got the dreaded “Microsoft.Rtc.Internal.Sip.TLSException: CertificateInfoNative::AcquireCredentialsHandle() failed;”, which basically means that you don’t have access to the certificate.  The strange thing was, the certificate checked out, it was in the local machine store, and the service was installed and running as Local System.  It also worked if I changed the service to run as the logged in user, which meant it had to be a permission issue, but Local System has access to everything, right?

As it turns out, some digging led to a fairly simple root cause.  If you install a certificate using web enrollment, it automatically installs to the current user store.  You can just launch the MMC and copy/paste the certificate from one store to another after that-after all, this works fine for root CA certs, right?  Well, in this case, the certificate will definitely be there in the local machine store, and your app will be able to see it in the local machine store, but the private key will still be in the current user’s application data directory. 

To figure this out, try this piece of code:

private static X509Certificate2 GetLocalCertificate()
{
    //6B8F3E0E000000000040
    X509Store store = new X509Store(StoreLocation.LocalMachine);

    store.Open(OpenFlags.ReadOnly);

    X509Certificate2Collection certificates = store.Certificates;

    foreach (X509Certificate2 certificate in certificates)
    {
        //look up a specific cert from the right CA
        if (certificate.SerialNumber == "6B8F3E0E000000000040")
        {
            RSACryptoServiceProvider rsa = certificate.PrivateKey as RSACryptoServiceProvider;
            string keyFile = rsa.CspKeyContainerInfo.UniqueKeyContainerName;
            Console.WriteLine("Private Key is in file name: " + keyFile);
            return certificate;
        }
    }
    return null;
}

Of course, you’ll probably want to put in the serial number of your certificate.  This will find the matching cert, but will also spit out the filename of the private key, which is stored in one of two places (in Server 2003 at least).  For the current user it’s a subdirectory of %appdata%\Microsoft\Crypto\RSA\, and for the local machine it’s something like C:\Documents and Settings\All Users\Application Data\Microsoft\Crypto\RSA\MachineKeys.  If the cert is in the local machine store, but the key is still in the current user’s appdata, then no other accounts will be able to access that private key (unless you change the ACLs on the current user’s appdata that is). 

The solution is pretty simple if you have a cert like this.  Right click the cert in the MMC, export it (with the private key) to a pfx file, and import it again, this time directly into the local machine store.  Now, if you verify, the private key should be in the MachineKeys directory, and not under the current user’s appdata, and your UCMA service should work again. 

Posted in Uncategorized | Leave a comment

Custom Status and Status Tokens-Publishing presence with UCMA 2.0

Publishing presence for a user in OCS is a reasonably common thing to do with an application endpoint, but most of the samples that I found online were restricted to setting the user’s availability.  This is usually one of the following values:

  • 3000 (Available)
  • 6000 (Busy)
  • 9000 (Do Not Disturb)
  • 12000 (Be Right Back)
  • 15000 (Away)
  • 18000 (Offline)

What if you want to publish something a little more detailed though.  Michael Greenlee wrote a great blog post on setting a custom status in a UCMA that I found really useful, and I’d suggest that anyone reading this go check that out first to get a basic application up and running…  After all, the internet only needs a certain number of posts that detail how to set up a basic application endpoint in UCMA. 

OK, so there’s two things that I’d like to add to the discussion, and both were made much clearer after digging through the MS-Pres spec that details the presence document.  If you look at the XML that you publish for a presence state, it looks something like this:

<state xmlns="http://schemas.microsoft.com/2006/09/sip/state" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userState">
  <availability>6000</availability>
</state>

Now, if you want to add a custom status string to the mix, you can change the XML to be something like this:

<state xmlns="http://schemas.microsoft.com/2006/09/sip/state" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userState">
  <availability>6000</availability>
  <activity>
    <custom LCID="1033">Really, really busy</custom>
  </activity>
</state>

Which will display the custom status text beside your name in Communicator.  What’s really handy though is that if you need to support users in multiple languages, you can add as many custom strings as you want to the messages:

<state xmlns="http://schemas.microsoft.com/2006/09/sip/state" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userState">
  <availability>6000</availability>
  <activity>
    <custom LCID="1033">Really, really busy</custom>
    <custom LCID="3084">Très, très occupé</custom>
  </activity>
</state>

If there are multiple strings in the message, Communicator will decide what to display based on the OS language, which means that your app can be localized without a whole lot of effort. 

The other thing that I wanted to try to do was get the status that Communicator publishes for some states (On Call, In a Meeting etc), where the availability is still set to a standard value.  There’s a post on Chris Mayo’s blog that details this in the Communicator automation API, but I wasn’t able to find anything on doing this in UCMA.  As it turns out, there’s another attribute you can set in the presence message:

<state xmlns="http://schemas.microsoft.com/2006/09/sip/state" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="userState">
  <availability>6000</availability>
  <activity token=”on-the-phone”/>
</state>

The token attribute on the activity node can be used for a bunch of standard tags that force Communicator to render your status based on its own string table.  Sure, you could publish “On the Phone” as your own custom presence state, but this way you don’t have to translate anything for different languages.  The list of tokens is in the MS-Pres spec:

  • on-the-phone
  • in-a-conference
  • in-a-meeting
  • out-of-office
  • urgent-interruptions-only

So, in addition to being able to publish presence with one of these “built in” status strings, you’re also able to look for these values in an application that monitors presence of a user from an ApplicationEndpoint.  This means that by just monitoring the presence of the user, you should be able to discern whether they’re on a call, in a conference, or out of the office (yes, there are other ways to do this, but this just uses the user’s presence).  Here’s a simple handler for a presence change that looks for the activity tokens and custom presence states

private static void handlerPresenceNotificationReceived(object sender, RemotePresenceNotificationEventArgs e)
{
    //Multiple presence notifications may be received in one event
    foreach (RemotePresentityNotificationData notification in e.Notifications)
    {
        foreach (PresenceCategoryWithMetaData category in notification.Categories)
        {
            PresenceState state = PresenceState.Create(category);
            string status=string.Empty;

            if (state.Activity!=null && state.Activity.ActivityToken != string.Empty)
                status = state.Activity.ActivityToken.ToString();  //constant values like on-the-phone
            else if (state.Activity != null && state.Activity.CustomTokens != null)
            {
                status = "custom Status";
                int lcid = System.Threading.Thread.CurrentThread.CurrentCulture.LCID;
                foreach (LocaleString l in state.Activity.CustomTokens)
                {
                    if (l.LocaleId == lcid)
                        status = l.Value;
                }                       
            }
            Console.WriteLine("user: " + notification.Uri + " state: " + state.Availability.ToString() + " status:" + status);
        }
    }
}

Posted in Uncategorized | 2 Comments