Microsoft Lync and Skype for Business have a rich set of .NET APIs which make it easy to extend the platform and integrate it with other applications. This blog helps explain how to use those APIs.

UCMA 3.0 applications without Lync Server

Posted: September 30th, 2011 | Author: | Filed under: UCMA 3.0 | Tags: , , | 24 Comments »

One topic that I wanted to write about in Professional Unified Communications Development that did not make it into the book because of time and space considerations is how to create UCMA applications that don’t depend on Lync Server. Now, at first this may seem like a strange and clueless idea, akin to carrying around a cell phone when you haven’t got any cell phone service. But there are actually some very real reasons why you might like to build a UCMA application without Lync Server.

A simple and common example is interactive voice response (IVR) systems. It’s certainly possible to set these up as trusted endpoints that register with Lync Server. But if you want to route calls directly to an IVR (whether from a SIP trunk, a PBX, an Asterisk server, or something else) where Lync Server is not present, you can do this with a standalone UCMA application. In this post, I’ll show how to set up a simple UCMA application in this way, and how to place test calls to it using CounterPath‘s X-Lite softphone.

Since a standalone UCMA application by definition cannot be a trusted application, there are really no preliminaries here. We don’t need to provision anything using PowerShell, and we can dive straight into the code. If you’ve done some basic UCMA development, you know the drill: create a new C# Console Application or Windows Service project, using the 3.5 version of the .NET Framework, and add a reference to Microsoft.Rtc.Collaboration.dll. (If you are new to UCMA development, check out my book, previous posts on this blog, or MSDN’s Unified Communications Developer Center.)

To begin, let’s look at the code to start up the collaboration platform. It’s not all that different from the code you would use for an ordinary collaboration platform, but there are a couple of notable differences:

ServerPlatformSettings settings = new ServerPlatformSettings(
    "standalone", Dns.GetHostEntry("localhost").HostName,
    5060, string.Empty /* empty string for the GRUU */);

_platform = new CollaborationPlatform(settings);

try
{
    _platform.BeginStartup(ar =>
    {
        try
        {
            _platform.EndStartup(ar);

            Console.WriteLine("Platform started.");

            StartEndpoint();
        }
        catch (RealTimeException ex)
        {
            Console.WriteLine(ex);
        }
    }, null);
}
catch (InvalidOperationException ex)
{
    Console.WriteLine(ex);
}

There are a couple of key differences:

  • No TLS certificate is supplied in the platform settings
  • There is no GRUU; we’re passing in an empty string for that parameter

Since this is not a trusted application, and it will use ordinary TCP without TLS to communicate, neither of these things is a problem. Also, we’re using 5060 as the listening port, which is a standard port number for SIP connections.

The next step, as usual, is to establish an endpoint. Here’s some example code:

private void StartEndpoint()
{
    // Create a placeholder URI for the endpoint.
    ApplicationEndpointSettings endpointSettings =
        new ApplicationEndpointSettings("sip:default@" +
            Dns.GetHostEntry("localhost").HostName);

    // Make this a default routing endpoint, so that
    // all requests sent to the listening port on this IP,
    // regardless of To URI, will come to the endpoint.
    endpointSettings.IsDefaultRoutingEndpoint = true;

    // Create a new endpoint and register for AV calls.
    _endpoint = new ApplicationEndpoint(_platform, endpointSettings);
    _endpoint.RegisterForIncomingCall<AudioVideoCall>(OnCallReceived);

    try
    {
        _endpoint.BeginEstablish(ar =>
        {
            try
            {
                _endpoint.EndEstablish(ar);

                Console.WriteLine("Endpoint started.");
            }
            catch (RealTimeException ex)
            {
                Console.WriteLine(ex);
            }
        }, null);
    }
    catch (InvalidOperationException ex)
    {
        Console.WriteLine(ex);
    }
}

You’ll notice this code simply makes up a URI for the application. Since it’s not registering with a Lync Front End Server, the endpoint uses the hostname of the server it’s on for the host portion of the SIP URI (after the @ sign) and the user portion doesn’t really matter. It uses the overload of the ApplicationEndpointSettings constructor that does not take a proxy server FQDN and port, because it’s not going to communicate with a proxy server at all.

A very important step here is to set the IsDefaultRoutingEndpoint property on ApplicationEndpointSettings to true. This tells UCMA that all incoming requests that arrive on the listening port of the application should be handled by this endpoint, unless the SIP URI in the To header matches the SIP URI of another one of the application’s endpoints. In this case, there’s only one endpoint, so every single incoming request will go to the same endpoint.

The rest of this is fairly normal: it registers for incoming AV calls and establishes the endpoint.

There’s nothing really different about the rest of the code; all you need to do to start up a UCMA application without Lync Server is in the steps above. To make testing easy, though, here’s the test class I used in its entirety:

class Tester
{
    CollaborationPlatform _platform;
    ApplicationEndpoint _endpoint;

    public void Start()
    {
        ServerPlatformSettings settings = new ServerPlatformSettings(
            "standalone", Dns.GetHostEntry("localhost").HostName,
            5060, string.Empty);

        _platform = new CollaborationPlatform(settings);

        try
        {
            _platform.BeginStartup(ar =>
            {
                try
                {
                    _platform.EndStartup(ar);

                    Console.WriteLine("Platform started.");

                    StartEndpoint();
                }
                catch (RealTimeException ex)
                {
                    Console.WriteLine(ex);
                }
            }, null);
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine(ex);
        }
    }

    private void StartEndpoint()
    {
        // Create a placeholder URI for the endpoint.
        ApplicationEndpointSettings endpointSettings =
            new ApplicationEndpointSettings("sip:default@" +
                Dns.GetHostEntry("localhost").HostName);

        // Make this a default routing endpoint, so that
        // all requests sent to the listening port on this IP,
        // regardless of To URI, will come to the endpoint.
        endpointSettings.IsDefaultRoutingEndpoint = true;

        // Create a new endpoint and register for AV calls.
        _endpoint = new ApplicationEndpoint(_platform, endpointSettings);
        _endpoint.RegisterForIncomingCall<AudioVideoCall>(OnCallReceived);

        try
        {
            _endpoint.BeginEstablish(ar =>
            {
                try
                {
                    _endpoint.EndEstablish(ar);

                    Console.WriteLine("Endpoint started.");
                }
                catch (RealTimeException ex)
                {
                    Console.WriteLine(ex);
                }
            }, null);
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine(ex);
        }
    }

    void OnCallReceived(object sender, CallReceivedEventArgs<AudioVideoCall> e)
    {
        CallHandler handler = new CallHandler();
        handler.Handle(e.Call);
    }

    public void Stop()
    {
        try
        {
            _endpoint.BeginTerminate(
                ar =>
                {
                    try
                    {
                        _endpoint.EndTerminate(ar);

                        Console.WriteLine("Endpoint terminated.");

                        ShutDownPlatform();
                    }
                    catch (RealTimeException ex)
                    {
                        Console.WriteLine(ex);
                    }
                },
                null);
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine(ex);
        }
    }

    private void ShutDownPlatform()
    {
        try
        {
            _platform.BeginShutdown(ar =>
            {
                try
                {
                    _platform.EndShutdown(ar);

                    Console.WriteLine("Platform shut down.");
                }
                catch (RealTimeException ex)
                {
                    Console.WriteLine(ex);
                }
            }, null);
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine(ex);
        }
    }
}

public class CallHandler
{
    Player _player;

    public void Handle(AudioVideoCall call)
    {
        call.StateChanged += new EventHandler<CallStateChangedEventArgs>(Call_StateChanged);

        try
        {
            call.BeginAccept(
                ar =>
                {
                    try
                    {
                        call.EndAccept(ar);

                        WmaFileSource source = new WmaFileSource("music.wma");

                        source.EndPrepareSource(source.BeginPrepareSource(MediaSourceOpenMode.Buffered, null, null));

                        _player = new Player();
                        _player.SetSource(source);

                        _player.AttachFlow(call.Flow);
                        _player.Start();
                    }
                    catch (RealTimeException ex)
                    {
                        Console.WriteLine(ex);
                    }
                },
                null);
        }
        catch (InvalidOperationException ex)
        {
            Console.WriteLine(ex);
        }
    }

    void Call_StateChanged(object sender, CallStateChangedEventArgs e)
    {
        Console.WriteLine("State changed from {0} to {1}", e.PreviousState, e.State);

        if (e.State == CallState.Terminated)
        {
            _player.Source.Close();
            _player = null;
        }
    }
}

You’ll also need to put a WMA file named music.wma in your application’s working directory (you can find one in the sample applications for the UCMA SDK), and something like the following in your application startup code to create an instance of the tester and get it rolling:

Tester t = new Tester();
t.Start();
Console.ReadLine();
t.Stop();
Console.ReadLine();

No need to configure anything or ensure that you have connectivity to Lync Server; just go ahead and start your application. The only thing you’ll need to watch out for is that the listening port (in this case 5060) is not being blocked by a firewall or being used by another application. If you get an error message on startup saying “Failed to listen on any address and port supplied,” then try changing your listening port to something else instead of 5060.

Now, there’s not much use in having a UCMA application sitting around unless you can place calls to it, and this is where X-Lite comes in. X-Lite is a free SIP softphone from a company called CounterPath (they also have more full-featured versions for sale). Using X-Lite, which you can download at http://www.counterpath.com/x-lite.html, you can place calls directly to your UCMA application from the same computer.

Once you’ve installed X-Lite, there’s a bit of configuration to do before you can call your application. Note that the steps below are based on X-Lite 4.1, build 63214, so if you have an older or newer version you may need to do something a bit different. Also, I want to give credit here to Marshall Harrison, whose post on doing something similar to this with X-Lite and Speech Server helped get me started with this technique.

First, go to the Softphone menu and select Account Settings, as shown below.

In the Account tab in the window that comes up, enter a name for your account in the Account name box and a user ID (which can be anything) in the User ID box. In the Domain box, enter the IP address of the computer you are running X-Lite and your UCMA application on. Leave Password blank, and uncheck the box that says Register with domain and receive calls. Make sure the Domain radio button is selected under Send outbound via. That’s all for this tab.

Switch to the Transport tab. There’s only one option here, Signaling transport, which you need to change to TCP.

Last but not least, go to the Advanced tab, and select Send outgoing request directly to target.

At this point, you are ready to go. In the dialing box in the X-Lite main window, enter a SIP URI like sip:whatever@192.168.0.1:5060, substituting your own IP address instead of 192.168.0.1 and your own listening port from the UCMA application instead of 5060. The user portion of the URI can be anything you want, since the call will go to your application regardless.

Click the Call button, and X-Lite will call your application. If all goes well, you will shortly hear music, and see something like the following:

Congratulations! You’ve created your first standalone UCMA application.

If you have any trouble getting this to work, feel free to drop me an email and I’ll do my best to help.


24 Comments on “UCMA 3.0 applications without Lync Server”

  1. 1 Nigel Budd said at 4:14 pm on October 6th, 2011:

    hi Michael,

    firstly many thanks for a great job on the Prof UC book, understanding UCMA would have been a hard job based on the MS Docs alone.
    Thanks also for UCMA without Lync article on your website, you are right that was kinda missing from the book, and I am especially interested in UCMAs ability to run IVRs.
    I got your sample working without any problems, the only issue I did have was with X-Lite, I entered the IP address as 127.0.0.1 which although the SIP session established, the voice didn’t come through…I expect you knew that though.
    The reason for writing, (except to offer thanks) is a question…
    Your sample app plays the music file in the async callback from the beginaccept, I guess that can only happen once the voice path has been established, but with CTI apps (I usually deal with CSTA by the way, not much MS) I always rely on event changes to perform actions, it makes more sense to me to play the music in the event handler in response to an Established event.
    Was there any advantage in your way?

    thanks

    Nigel.

  2. 2 Michael said at 7:33 pm on October 7th, 2011:

    Hi Nigel,
    Glad to hear the book and the posts are helpful.
    To answer your question, I don’t think there’s much of an advantage either way. I use this approach in my examples just to simplify things. The callback doesn’t get invoked until the call is established (or an exception has occurred), so the effect is more or less the same either way.
    Michael

  3. 3 Ilayaraja said at 4:09 pm on March 2nd, 2012:

    Hi Michael, This is great article that helped me a lot to develop stand-alone IVR with UCMA.
    1. Can you please let me know what are the advantages/benefits that we get automatically by provisioning with Lycn server? And how can we get such benefits in case of standalone IVR without Lync – especially in terms of scalability, fault-tolorence, load-blancing?
    2. Do you have any material on creating out-bound calls from UCMA application without using Lync?
    Thanks a lot for your help in advance.

  4. 4 Outbound calls from UCMA without Lync Server | Lync Development said at 3:12 pm on June 15th, 2012:

    […] my past blog post on standalone UCMA applications, I described how to build a UCMA application that can answer calls totally independently of Lync […]

  5. 5 Giri said at 1:03 am on June 21st, 2012:

    Thank you for this example. Is there a sample you can provide to use vxml files to create IVR app with UCMA 30

  6. 6 Joshua Barker said at 7:25 pm on August 15th, 2012:

    What benefits do you get from registering with Lync Server 2010? Does it monitor the application end point or allow for load balancing or failover?

    I’m familiar with the Speech Server 2007 model where it hosts the application, so this is all completely different in having to run it as a service. Using Lync Server or not, how would you apply inbound calling limits so that all the phone lines aren’t tied up for outbound calls?

  7. 7 Michael said at 10:14 am on August 16th, 2012:

    Joshua, it allows you to receive calls to your UCMA application through Lync – e.g., from other Lync users, or from a Lync Mediation Server. It also allows you to take advantage of Lync Server features like conferencing, presence, and contact lists. You can’t use these things with a standalone UCMA app because they are managed by Lync Server roles.

    To answer your question about inbound calling limits, if you can’t do this at the gateway level, one option is just to have your UCMA application keep count of the number of active calls it is handling. Then, if a call comes in that would put it over the limit, it can decline the call using the Call.Decline method. Does that sound like it would work for what you need to do?

  8. 8 Nilanka said at 7:59 am on August 28th, 2012:

    Thank you for example code. In this code only used local IP’s. I just want to know that is possible to communicate this via WAN without Lync Server.
    1. If so, how UCMA is creating link with both end points (P2P)
    2. Is UCMA having UPnP, Nat Traversal some other technology to create a P2P connection via WAN

  9. 9 Nagu said at 12:34 am on September 5th, 2012:

    Hi Michael,

    It is very nice article. Sorry earlier I forgot to put my name.I was succesfull with above application. Now I have connected two snom phone with Lync server using user sip uri. I also created the Application Endpoint. Now trying to use Application end point to monitor all connected devices call states with sam user uri. How can I do that??

    Thank you

    Nagu

  10. 10 Michael said at 6:47 pm on September 25th, 2012:

    Hi Nagu,

    If you want to monitor the presence of the users (including whether they are in a call) you can use the RemotePresenceView class.
    Let me know if that answers your question.

    Michael

  11. 11 Jeff said at 9:25 am on September 27th, 2012:

    Hello Michael,

    This was great help for reverse engineering an old analog IVR application and porting it to a standalone UCMA 3.0 endpoint application. However I am having issues getting it successfully connected to a Interactive Intelligent CIC server SIP trunk. Are there any resources that you can point me to in order to guarantee success? At this point I believe the problem is getting compatible codecs agreed on the invite. The UCMA app throws a Invalid SDP no media acceptable and 488 not acceptable here error. It appears to only offer the G729 codec which lync does not support. It also wraps the initiator’s address as Unknown if the address is not on the same ip subnet but I am not sure if that mean anything. Thank You, Jeff

  12. 12 Darren said at 4:26 pm on October 28th, 2012:

    Hello Michael,
    This was a great help. Test app works great with x-lite although I cannot seem to get DTMF to come through. Modifying your posted code to include the following..

    void Call_AudioVideoFlowConfigurationRequested(object sender, AudioVideoFlowConfigurationRequestedEventArgs e)
    {
    e.Flow.StateChanged += Flow_StateChanged;
    }

    void Flow_StateChanged(object sender, MediaFlowStateChangedEventArgs e)
    {
    Console.WriteLine(“OnCallFlowStateChanged: ” + e.State);

    if (e.State == MediaFlowState.Active)
    {
    var toneController = new ToneController();
    toneController.AttachFlow(_call.Flow);
    toneController.ToneReceived += ToneController_ToneReceived;
    }
    }

    void ToneController_ToneReceived(object sender, ToneControllerEventArgs e)
    {
    Console.WriteLine(“OnToneReceived: ” + e.Tone);
    }

    never triggers the tone received event. I’ve verified x-lite is not using inband. Is there anything special for tonecontrollers when using ucma in this manner?

    Thanks,
    Darren

  13. 13 Michael said at 10:58 am on October 31st, 2012:

    Darren, not sure why that would be. There shouldn’t be anything special required. So you have X-Lite using RFC 2833 for DTMF and the ToneReceived event still never triggers at all?

    One thing you can do is run some logs on the app server – if I remember right, the RFC 2833 DTMF tones show up in one of the RTP logs.

  14. 14 ShawnG said at 6:36 pm on November 6th, 2012:

    Michael, do you know if I could use this setup to perform a B2BU type of call. This way I can call into the app and have the app conference/connect me with an outbound call?

  15. 15 Michael said at 6:40 pm on November 6th, 2012:

    You can do a B2BUA with a standalone UCMA app using the BackToBackCall class. You won’t have access to Lync Server specific features such as conferencing.

  16. 16 ShawnG said at 4:36 pm on November 8th, 2012:

    Hi Michael, quick question. If I wanted to have this app register with my SIP Server as a SIP User, which class do you suggest I use for this?

  17. 17 Michael said at 8:34 am on November 9th, 2012:

    You want to have your application register with a non-Lync SIP registrar?

  18. 18 ShawnG said at 5:27 pm on November 10th, 2012:

    Yes that is correct, a non Lync SIP Registrar.

  19. 19 UCMA Development: Choosing between Application and User Endpoints. | thoughtstuff | Tom Morgan said at 1:07 pm on January 9th, 2014:

    […] messages, such as re-routing incoming calls for an IVR for instance. See Michael Greenlee’s excellent blog post on the subject for […]

  20. 20 Sairam said at 1:52 am on November 13th, 2014:

    Hi Michael, thanks for the post. It worked for me.

  21. 21 Armen said at 8:30 pm on April 8th, 2015:

    Hi Michael
    great post and code sample, which worked fantastic. But the issue is it is working fine only with XLite client, I tried with other SIP clients and got strange error always: 480 – Temporarily Unavailable.

    Some apps could ignore that error and start call, but music was not playing, some apps just blocked and and showing that they are Registering.

    Sounds like they need a service or something like that.

    Another not related question, did you try to run that on Azure VM???

  22. 22 Dave said at 4:55 am on July 22nd, 2015:

    First of all, really appreciate the articles you have posted regarding development on UCMA. They are really simple to follow and easy to understand.

    I am new to UCMA development and learning the trade on Lync server. I am working on a requirement where the customer wants to use UCMA with VoiceXML. Was just looking for some directions from you. I know we need to use the Browser to invoke the VXML document which is a built-in feature of UCMA 4.0 SDK. Do you have any pointers which i can use to start this off from.

    Awaiting your response. Have a nice day!!!

  23. 23 Chris said at 10:34 am on August 4th, 2015:

    Hi, Michael. I hope you’re doing well. I own and have read your book. As someone else has already shared, it would have been very difficult developing my UCMA application without it.

    I have a question about my application’s behavior, if I may. It’s a client endpoint, connects to Lync, runs some test calls, etc. When I deploy this application, sometimes it fails (at “new CollaborationPlatform(myClientPlatformSettings)”) if the hosts file does not contain “127.0.0.1 ‘device name'”, and other times it does not. I have done some fairly thorough investigation, but cannot satisfy myself as to the root cause of this.

    I do need to figure this out before I deploy the tool more widely. I’m hopeful that perhaps this is an easy one for you. Can you comment? Do you know why this host file entry sometimes seems necessary and sometimes not? Is there a workaround / approach whereby it is never necessary?

    Thank you.

  24. 24 Jerod said at 11:29 am on May 26th, 2016:

    Hi Michael,

    Fantastic blog! Thank you very much for posting this. I was using UCMA 3.0 and everything was working fine however I have needed to upgrade to UCMA 4.0 and have run into a couple strange issues that I was hoping that you may be familiar with.

    When I run the example works fine but I do receive the following:

    Ln 12 – _platform = new CollaborationPlatform(settings);

    A NullReferenceException occurs in:
    SIPEPS
    at Microsoft.Rtc.Internal.Sip.ConnectionControlModule..cctor()

    Continuing, the platform appears to start successfully.

    Then I receive 2 errors back to back for:
    “Unable to load DLL ‘UcmaPerf.dll’: The specified module could not be found. (Exception from HRESULT: 0x8007007E)”

    Which is particularly strange because I couldn’t find any information on UcmaPerf.dll.

    The endpoint starts.

    At this point everything seems to work (as I can connect via SoftPhone). State changes to establishing, then to established but then the CollaborationPlatform fails to play audio. I get 1 of 2 errors at this point:

    Exception: OperationFailureException
    Reason: OperationFailureReason.Unknown
    Message: Resource can’t be reserved because some other device has already reserved

    Any suggestions would be much appreciated.


Leave a Reply

  • Note: Comment moderation is in use because of excessive spam. Your comment may not appear immediately.

  •