Cisco Unity Connection .NET REST SDK

Contents

Overview. 4

Requirements/Special Notes. 4

Installing and Using the Library in Your Project 5

The Easy Way. 5

The Hard Way. 5

Watching the traffic with Fiddler2. 5

HTTP Communication Changes in 10.x. 6

Using the .NET REST SDK. 7

Getting Started. 7

Logging into Connection. 7

The WebCallResult Class. 8

The UnityConnectionRestException. 9

Death to Magic Numbers! 10

Updating Properties on Objects With Dirty Lists. 10

Handling Clusters and Connectivity. 11

Logging and Debugging with the SDK. 13

Users. 14

Creating and Deleting Users. 14

Finding and Fetching Single Users. 15

Finding and Fetching Lists of Users. 16

Updating Users. 18

Voice Names. 19

PINs and Passwords. 20

Private Lists. 21

Alternate Extensions. 23

Notification Devices. 24

Message Waiting Indicators. 26

Mailbox Information. 27

Class of Service. 28

Greetings. 28

Transfer Options. 29

Menu Entries. 29

Phone System.. 29

Exiting the User Mailbox Conversation. 30

LDAP Users. 30

Messages. 31

Fetching Messages for a User 31

Send a New Message Using Wav File. 33

Send a New Message Using Phone (CUTI) 34

Forwarding a Message With Intro. 35

Replying to a Message. 36

Deleting Messages and the Deleted Items Folder 37

Helper Methods. 37

User Templates. 37

Finding and Fetching User Templates. 37

Creating, Updating and Deleting User Templates. 38

Global Users. 39

System Contacts. 39

Call Handlers. 40

Creating and Deleting Call Handlers. 40

Finding and Fetching Call Handlers. 41

Updating Call Handlers. 42

Voice Names. 42

Greetings. 43

Transfer Options. 45

Menu Entries. 47

Getting and Setting Call Handler Owners. 48

Getting Call Handler Owners in 10.0 and Later 48

Setting and Deleting Call Handler Owners in 10.0 and Later 49

Determining if a User is an Owner of a Handler Versions Prior to 10.0. 49

Call Handler Templates. 49

Finding and Fetching Call Handler Templates. 49

Creating, Updating and Deleting Call Handler Templates. 50

Post Greeting Recordings. 51

Directory Handlers. 52

Creating, Updating and Deleting Directory Handlers. 53

Custom Recorded Greetings. 53

Interview Handlers. 54

Creating, Updating and Deleting Interview Handlers. 54

A Note About Dispatch Delivery. 55

Finding and Fetching Interview Handlers. 56

Updating Questions for Interview Handlers. 56

Public Distribution Lists. 57

Creating and Deleting Distribution Lists. 57

Finding and Fetching Distribution Lists. 58

Updating Distribution List Membership. 59

Voice Names. 61

Locations. 61

Finding All Connection Servers in a Network. 62

Finding the Home Server for a Global User 63

Roles and Policies. 64

Checking Roles of User 64

Checking for Greetings Administrator Access Role. 64

Adding and Removing Roles for a User 65

Setting Actions. 66

Schedules. 68

Languages. 71

Time Zones. 72

Partitions. 72

Finding and Fetching Partitions. 73

Creating, Editing and Deleting Partitions. 73

Search Spaces. 74

Finding and Fetching Search Spaces. 74

Creating, Editing and Deleting Search Spaces. 74

Class of Service. 75

Finding and Fetching Classes of Service. 75

Creating, Updating and Deleting Classes of Service. 76

Phone Systems, Port Groups and Ports. 77

Creating a Phone System Integration from Scratch. 77

Fetching, Updating and Deleting Phone Systems. 79

Fetching, Updating and Deleting Port Groups. 79

Fetching, Updating and Deleting Ports. 80

Phone System References. 81

Customizing Codecs. 82

Routing Rules. 84

Finding and Fetching Rules. 84

Creating New Routing Rules. 85

Ordering Routing Rules. 86

Tenants. 87

What is a Tenant?. 87

What Happens When You Create a New Tenant?. 88

Finding and Fetching Tenants. 88

Creating and Deleting Tenants. 89

Adding and Finding User Templates. 89

Adding and Finding Call Handler Templates. 90

Adding and Finding Class of Services. 91

Adding and Finding Schedules. 91

Finding Phone Systems. 92

Adding and Finding Users. 92

Adding and Finding Call Handlers. 93

Adding and Finding Directory Handlers. 94

Adding and Finding Interviewers. 95

Adding and Finding Distribution Lists. 96

Cross Launching CUCA Admin Web Pages. 97

Dealing with Lists and ComboBoxes. 97

Sorting Lists of Objects. 98

Building Portals. 98

Step 1 – Authenticating Users for Access to Your Site. 99

Step 2 – Define the “Scope” of Users to Choose From.. 99

Step 3 – Pick an Object to Edit 100

Step 4 – Perform Edit. 100

Revision History. 101

Overview

The .NET SDK library for the Unity Connection REST interfaces is a set of library code intended to make development of applications using .NET framework easier, faster and less error prone.  Not every method available in every REST interface provided off the Unity Connection platform is included as a “wrapped” functionality in the SDK, but the majority of the commonly needed ones are.

This SDK project is targeted first at wrapping the most commonly used items in the CUPI For Administrators interfaces.  It also includes CUTI (recording using the telephone) and CUMI (getting/sending messages), however coverage of those interfaces is far less extensive currently. No coverage of CUNI (message event notification framework) is provided at this time.  A separate SDK project for covering the CUPI For Users interfaces is underway.

Requirements/Special Notes

The .NET REST SDK is written and tested against all versions of Unity Connection 8.6 and later.  The REST interface is supported in 8.5 builds but is missing some functionality.  The unit tests included in the project are tested against 9.1 and later – running the Unit tests against earlier versions will result in some tests failing – this is expected as functionality was added in later versions that will not exist in earlier builds.  The SDK does not do version checking for you, if you call a method that’s not supported the server error information that comes back will indicate this.

Use of the SDK is not supported by TAC – in other words if you need assistance with an application you are developing using the SDK, TAC is not going to provide that assistance for you.  Support is through development services and the public support forums.  Check the www.CiscoUnityTools.com site on the “links” page for current links to get to those forums.  “Not supported by TAC” does not mean the SDK is somehow dangerous or risky to use.  To be clear it simply wraps the existing supported REST APIs provided with the platform – it does not go outside the bounds of those protocols and applications developed using the SDK are just as safe and supported as those written directly to the API.

Any .NET framework can use these libraries. This means you can, of course, develop desktop and web applications on Windows using C#, VB.NET, Iron Python etc… but you can also use Mono for development of applications on Mac or Linux platforms as well as mobile applications on iOS using MonoTouch or Android using Mono For Android.  In fact the “Connection CoPilot” iOS application in the iTunes store was developed with the CUPI For Users SDK wrapper library.  This is one of the reasons why the library is provided as a source code project instead of only binaries – you must rebuild the source for other platforms.

The library is build and tested using Visual Studio 2010 and Visual Studio 2012 in Windows, however I do validate it works with Mono Develop and Xamarin Studio on Mac.

 

Installing and Using the Library in Your Project

The Easy Way

The SDK is available via NuGet package manager and by far the easiest way to install it is to manage NuGet packages for your project, select online and search on “CUPI” or “Cisco REST SDK” or any number of other combinations of Cisco Unity Connection REST SDK” and the project should come right up.  When you add it the dependent JSON.NET library is automatically installed and all you need to do is add the “Cisco.UnityConnection.RestSdk” to your using list and you’re off to the races.

If the search is not working properly or you’re in an environment such as on a Mac using MonoDevelop and you only have access to the NuGet package manager console you can install the package by issuing the following command:

Install-Package Cisco.UnityConnection.RestSdk

NuGet is nice since it always grabs the latest and notifies you when an updated version of the library is available and gives you the option of installing it.  Couldn’t be easier.

The Hard Way

You can still download the source code for the project and include it manually.  This can be handy if you prefer to debug into the SDK library code directly and see what’s going on or the like or if you just don’t trust NuGet or doing things the easy way makes you generally suspicious.  Whatever works for you.

Using any SubVersion client you like you’ll need to download the SDK project code off the site’s download page: http://www.ciscounitytools.com/CodeSamples/Connection/CUPI/CUPI.html

To add the project right click on your solution node in the solution explorer in Visual Studio.  Select “Add” and then “Existing Project” and a file explorer window will appear.   Navigate to where you downloaded the library code and select the “ConnectionCUPIFunctions.csproj” file.  This will pull the library into your solution and have it build when you rebuild your project.  This will result in the “ConnectionCUPIFunctions.dll” ending up in the target BIN output (debug or release) for your project.  This is the only file you need to include in your setup for the library.

Once you’ve included the project you then need to add a reference to it in your project – in your project right click the “references” node in the solution explorer and select “add reference” – in the resulting dialog select “projects” and select the ConnectionCUPIFunctions project and add it.  Then you only need to add a “using Cisco.UnityConnection.RestFunctions;” directive in your project and you’re off to the races.  The full project includes a couple different project examples such as a simple CLI application, an ASP project for a web based password reset application and a basic WinForms project demonstrating some of the basic capabilities of the library.

NOTE: Visual Studio has the annoying habit of defaulting to the “.Net Framework 4 Client Profile” as the default for new projects.  This will not work for us as the REST SDK requires the full “.Net Framework 4” setting.  Be sure your project is configured for this or you’ll get build errors.

Watching the traffic with Fiddler2

I highly recommend you download and install Fiddler2 on your development machine so you can watch the traffic going to and coming from Connection while you’re using the library.  This library is not intended to be the end-all-be-all of development against Unity Connection and you may want to extend it or do your own library for specialized functions.  The best way to see how Connection’s REST interface is working is to watch the HTTP traffic going back and forth and one of the best ways to do that is with Fiddler.

For customers that don’t want to use the .NET wrapper library for their projects but want to get a jump start on seeing how commands and requests should be formatted and what they return this can be a very fast way of doing that.  It’s also a fantastic way to see if your application is being as efficient as it can be or if it’s requesting/fetching more data than it needs to for the task at hand – unless you’re watching the traffic it can be easy to get sloppy without realizing it.  I’ll discuss this more later when talking about how and why multiple interfaces are presented for doing essentially the same task (hint: one is more work but more efficient on the wire, one is easier and cleaner but results in more traffic).

HTTP Communication Changes in 10.x

With the release of the 10.x platform (which includes Call Manager and Unity Connection products among others) the base server core logic changed for HTTP based communication.  In early release testing both internally and externally this has naturally tripped a lot of folks up.  The changes are there to make the system less vulnerable to attack and are, on the whole, a good thing. 

The very short version is sending basic authentication in every request header you issue is going to bring you to grief.  In earlier versions this was allowed and, of course, that makes things very easy since you don’t need to fiddle around with cookies, keep track of IDs or timeouts and such, just blindly shove the base 64 encoded authentication string into your header on every request and you’re off to the races.  Well, play time is over I’m afraid.  The good news is if you’re using the SDK, it’s taken care of for you – no worries, you’re welcome.  If you’re using the SDK as a learning tool and doing your own work, you’ll want to pay attention to this and make sure you’re not getting burned.

It’s really not that difficult, you just need to understand what’s going on.  When you issue a REST API request the first time you need to send the login name and password in the standard base 64 string (I’ll let you Google the details of that if you’re doing it by hand for some reason or another).  So looking at some Fiddler2 output here for the sequence that looks like this:

Request:

GET https://192.168.0.186:8443/vmrest/users?pageNumber=1&rowsPerPage=50 HTTP/1.1

Cache-Control: no-cache

Authorization: Basic Q0NNQWRtaW5pc3RyYXRvcjplY3NidWxhYg==

Content-Type: application/json

Accept: application/json, */*

Connection: Keep-Alive

 

Response:

HTTP/1.1 200 OK

Cache-Control: private

Expires: Wed, 31 Dec 1969 16:00:00 PST

Set-Cookie: JSESSIONIDSSO=188767B4B17C1F63B60F9D899C38AD75; Path=/; Secure; HttpOnly

Set-Cookie: JSESSIONID=DFB13585A6F71A70D9CE27A093102938; Path=/vmrest/; Secure; HttpOnly

Set-Cookie: REQUEST_TOKEN_KEY=7208623825002684176; Path=/; Secure; HttpOnly

Content-Type: application/json

Transfer-Encoding: chunked

 

Note the JSESSIONID and JSESSIONIDSSO values included in the response and the REQUEST_TOKE_KEY.  The easiest way to deal with it is to just grab all the cookies and then include them in your requests to the server moving forward.  If you send the basic authentication string in your header 3 times in a second, you’ll get a 503 back from the server (service not available).  Many, many folks claimed to be getting 503s “randomly” who swore they were not doing this.  They were.  Watch what’s happening using Fiddler or WireShark before raising a flag that somehow the server is broken.  Spoiler alert: I routinely runs loads for days that send piles and piles of REST requests to servers to check for this and they don’t fail.  Odds are you’re missing something here rather than the platform is behaving “randomly” on you.  Trust me.

So, your next request given the above sequence would look like this:

Request:

POST https://192.168.0.186:8443/vmrest/distributionlists HTTP/1.1

Cache-Control: no-cache

Cookie: JSESSIONIDSSO=188767B4B17C1F63B60F9D899C38AD75; Path=/; Secure; HttpOnly,JSESSIONID=DFB13585A6F71A70D9CE27A093102938; Path=/vmrest/; Secure; HttpOnly,REQUEST_TOKEN_KEY=7208623825002684176; Path=/; Secure; HttpOnly

Content-Type: application/xml

Accept: application/xml, */*

Response:

HTTP/1.1 201 Created

Set-Cookie: REQUEST_TOKEN_KEY=7208623825002684176; Path=/; Secure; HttpOnly

Location: https://192.168.0.186:8443/vmrest/distributionlists/vmrest/distributionlists/992a3272-4732-4a47-851c-7d733d84eb30

Content-Type: application/xml

Transfer-Encoding: chunked

And the server will be perfectly happy with that in your requests you send.  For a while.  The request tokens will expire after a time (a few minutes) and you’ll start seeing something like this coming back from the server:

Response:

HTTP/1.1 204 No Content

Cache-Control: private

Expires: Wed, 31 Dec 1969 16:00:00 PST

Set-Cookie: REQUEST_TOKEN_KEY=-7208623825002684176; Expires=Thu, 01-Jan-1970 00:00:10 GMT

Set-Cookie: REQUEST_TOKEN_KEY=7972341737335967391; Path=/; Secure; HttpOnly

It’s still taking your requests fine but notice that it’s told you the token you’re using is expiring and it wants you to request another one (by sending a basic authentication with your next request).  You can either spot the “Expires” flag for your token in the response and take care of this on the next request or you can simply always send the basic authentication string with your request after a fixed period of time has passed.  The SDK uses a simple mechanism of automatically expiring tokens 60 seconds after they’ve been issued – so if that amount of time has passed since we got our token from the server the next request issued with include the basic authentication string in it, new IDs and tokens will be issued by the server and you can reset your timer for that server.  Rinse, lather, repeat.  Either way works, the timer mechanism is just easier when dealing with communications to multiple servers out of the same client.

If you ignore the warning that the token is expiring, after a period of time the server with throw up the hand and you’ll get a 401 error like this:

Response:

HTTP/1.1 401 Unauthorized

Set-Cookie: REQUEST_TOKEN_KEY=-7208623825002684176; Expires=Thu, 01-Jan-1970 00:00:10 GMT

Content-Type: text/html;charset=utf-8

Content-Length: 2186

If you see that in your testing you can be pretty sure the logic around requesting a new token/ID set is flawed in some way.

Using the .NET REST SDK

This document uses a “task based” approach to demonstrating the use of the library – each major object class (user, call handler, name lookup handler, schedule etc…) has it’s section and small code snippets are shown demonstrating the items you’d typically want to do with those objects.  This does not attempt to document the entire data schema or get into too much theory.  As a developer I know I learn faster with a simple “show me” approach so that’s what I endeavor to do here.

Getting Started

Logging into Connection

The SDK is designed to support multiply threaded applications that may be attached to more than one Connection server at a time (for instance a network of Connection clusters).  The first thing to note is that ALL HTTP traffic goes out a single static method that has a monitor construct on entry/exit – this means all Connection servers you may be attached to in your application will all wait in line nicely and issue their HTTP request/response pairs in sequence, not in parallel.  This avoid conflicts and “cross talk” issues cleanly, however for very heavy traffic applications wanting to talk to many servers at once, it’s not ideal.  So if you’re looking to build a load test framework for REST targeting many servers at once, you will need to dig into your own HTTP library that will handle parallel traffic patterns.

The other thing the SDK handles for you is authentication and cookie management.  If you are talking to, say 3 different Connection servers it will manage the authentication cookies for those servers for you.  In Connection 10.0 and later this is critical as sending login/pw for each HTTP request will result in failures due to new security features on the platform.  In short the ConnectionServerRest object handles this for you and will “flush” the cookie after 1 minute of inactivity to that server.

So, to log into a remote Connection server from your client, you need to create a ConnectionServerRest object and keep it around – this is your “handle” to the server which you will use when sending/receiving data to and from Connection.  You will want to authenticate with an account that had the administrator role and, optionally, the mailbox delegate role if you want to be working with messages in other user’s mailboxes.  The attachment logic is all in the class constructor, there are no static methods off the class so you simply need to create a new instance providing the server name, login and password like this:

 

//attach to server -
ConnectionServerRest connectionServer;
 
try
{
    //insert your Connection server name/IP address and login information here.
    connectionServer = new ConnectionServer("cuc91.cisco.com""login""pw");
}
catch (Exception ex)
{
    //your error handling code here.
}

 

The constructor shown above is the default minimum (server name, login and password).  By default this will accept self signed certificates (the default for Unity Connection server installs of course unless you’ve installed a signed certificate).  You can pass the pAllowSelfSignedCertificates parameter in the constructor (which defaults to true) as false if you wish to override this behavior and throw failures for self signed certificates instead.

The ConnectrionServerRest instance has a few items off it but the most useful is the version interface that makes it easy to check for which version of Connection you’ve attached to, like this:

 

//the Connection server class ToString() override spits out the server 
        //name and version number in the ToString function.
Console.WriteLine("Attached to Connection server: "+connectionServer);
 
//do a version check –
if (connectionServer.Version.IsVersionAtLeast(8,6,0,0)==false)
{
    Console.WriteLine("You are attached to an 8.5 or earlier server!”");
}

At this point you have the details in place for communicating with that Connection server.  The remaining samples simply assume the “connectionServer” reference is created and valid so I won’t repeat this code chunk over and over again.

If you are attaching to a Connection that is older than version 10.0 you need to force SSL3 attachments.  By default updated Windows platforms (8.1, 10.0, 2012R2) have security updates that will restrict SSL3 HTTPS attachments and require TLS 1.1 and later only.  Since Connection versions prior to 10.0 did not support TLS you have to pass a parameter asking the SDK to force SSL3.  The when running the constructor for the ConnectionServer you can pass the “ForceSslConnection” as true like this:

 

connectionServer = new ConnectionServer("cuc91.cisco.com""login""pw", true, true, true);
 
If your connection request comes back with an error including “Could not create SSL/TLS secure channel” you are very likely experiencing this issue and need to force support for SSL3 connections.

The WebCallResult Class

Throughout the library you will see most methods will return an instance of the WebCallResult instead of a simple integer or a bool.  There is a method to the madness here – the WebCallResult class holds all the information about what was requested and what came back from the server so diagnosing what went wrong and what, exactly the error details were is much easier.  The SDK itself does not have logging built into it, naturally, so it has to return to the calling application as much detailed and useful data as it can so YOU can log it properly in your application.  That’s the idea.

Further, the class will hold the ObjectId of an object you just created for you as well as providing pre parsed XML node details from the response if it’s included.  If you fetched a list of users, for instance, with a paging directive (which we’ll cover) the total number of objects returned by the fetch is included in the class properties as well so you can easily do a “7 of 24” type list navigation interface for your users.  In short this class can make your life one heck of a lot easier for only a little extra overhead on your part.  It’s worth it, I promise.  For instance, this code snippet creates a new user and the results are returned as a WebCallResult instance:

 
WebCallResult res;
 
res=UserBase.AddUser(connectionServer, "template""Alias""8001",null);
if (res.Success==false)
            {
Logger.Log("Error! Failed creating new user:"+res.ToString());
return;
            }
 
        Console.WriteLine("User created, new ObjectId="+ res.ReturnedObjectID);

 

The “ToString()” override for the class includes all details of the request, response, error code etc… in one shot – makes logging out failures a simple process.  Notice also that on a success the ObjectId of the newly created user was included -  you could have called the version of the AddUser method that returned an instance of the UserFull class all filled in with this (and much more) but that involves another fetch to the server and if you’re trying to quickly add users that slows things down – nicer to just do a create call and fetch back the ObjectId of the newly created user and move on.  More on that later.

To give you an idea of what logging with the WebCallResult class looks like the following is a dump of the contents of the class after a failed call to fetch a user by alias (the user did not exist):

 

    WebCallResults contents:

    URL Sent: https://192.168.0.186:8443/vmrest/users?query=(Alias%20is%20missinguser)

    Method Sent: GET

    Body Sent:

    Success returned: False

    Status returned 200:Ok

    Error Text: No user found with alias=missinguser or objectId=

    Raw Response Text: {"@total":"0"}

    Total object count: 0

    Status description: OK

 

You can see the full URI called, the method type and what the server returned. Although the HTTP response is 200 (OK) the call is a failure so the Success flag is false (as it should be).  Normally the contents of this class is all you need to log to get an idea of any failed call that may have been made.  There are other properties the class exposes that are not shown here that we’ll be seeing and using in examples later on.

The UnityConnectionRestException

While most items in the SDK are easiest to use via their static methods that return object via out parameters and a WebCallResult class discussed above, you can also create new instances of objects as you normally would with any class.  A few items must be created this way as they don’t have static methods to leverage (for instance the Cluster class).  The SDK uses a subclass of the Exception class so that it can return an instance of the WebCallResult class from a thrown exception.  You aren’t required to use it – a description of the error will appear in the standard Exception instance but it’s best to always try and catch the UnityConnectionRestException so you can get the handy information in the WebCallResult structure.

Here’s an example showing how to fetch a UserBase class instance (which you can explore more fully in the Users section below) using the “old style” class creation pattern:

 

UserBase oMyUser;

try

{

    oMyUser = new UserBase(connectionServer, """jlindborg");

}

catch (UnityConnectionRestException ex)

{

    Console.WriteLine("Failed fetching user:"+ex.WebCallResult);

}

catch (Exception ex)

{

    Console.WriteLine("Failed fetching user:"+ex);

}

 

As a rule it’s good style to catch your more specific exception types first and fall back to the base Exception class - in this case it could throw an ArgumentException (i.e. if you passed a null ConnectrionServerRest in) or more commonly the UntiyConnecitonRestException class for any kind of failure at the HTTP/REST API layer.  The WebCallResult class will have lots of useful information and can tell you if it was a case of not finding the alias or an HTTP timeout instance or any number of other potential problems with enough detail for you to track down the problem report reasonably easily.  The base Exception class will only have a basic explanatory string; none of the other details will be reflected.

As noted you should be using the static calls off the classes when provided as it’s much less code and a lot cleaner to read, but if you find yourself wanting to kick it old school, you can.

Death to Magic Numbers!

Throughout the SDK an effort has been made to use ENUM values for any integer (and string) property that is tied to a finite number of options.  If you’ve invested many hours of your life memorizing the various property conventions for the Unity Connection object model I apologize, but for the rest of humanity this should make dealing with the object model considerably easier.

It’s left as an exercise for the reader which of these two code chunks is easier to read:

 

oRule.State = RoutingRuleState.Active;

oRule.CallType = RoutingRuleCallType.Both;

oRule.RouteAction = RoutintRuleActionType.Goto;

oRule.RouteTargetConversation = ConversationNames.SubSignIn;

 

Versus:

 

oRule.State = 1;

oRule.CallType = 2;

oRule.RouteAction = 2;

oRule.RouteTargetConversation = "SubSignIn";

 

One exception to this is in language codes.  There is a helper value for this and you’ll see this pattern in the code samples:

(intLanguageCodes.EnglishUnitedStates

Languages are assigned to objects based on the integer value (i.e. 1033 for US English) – to keep your code readable you can use the above type of cast. 

Updating Properties on Objects With Dirty Lists

Throughout the SDK you'll find classes that let you update properties on them directly and then execute an "Update" method to send all the pending changes to the Unity Connection server in a single command.  For many types of application this provides an ideal mechanism for allowing users to change multiple properties, discard those changes or doing batch updates quickly and easily.  Leveraging the strongly typed properties off these classes also allows for a much nicer experience finding what you wish to update and legal values for those properties.  Considerably easier than using the REST API directly, which is sort of the point of providing the SDK in the first place.

That said it's important to understand what's happening under the covers.  When you update a property that property and its new value are placed into a "dirty value" queue on the class itself.  The property value in the class itself is NOT updated and neither is the Unity Connection server.  No changes are actually applied until the Update method is called which writes the data back to the server.  Further, even after you call Update() by default it will not update the local values you're working with unless you pass "true" as an optional parameter to the Update method.  If you pass true then after the update to the server has succeeded it will turn around and re-fetch the data for that object from the server again.  Since this involves another round trip HTTP request to the server it is not done automatically.  If you were doing bulk updates, for instance, you may not want that extra overhead.

So for instance, let's look at the process of updating some CallHandler properties:

 

//fetch a call handler to edit

CallHandler oCallHandler;

var res = CallHandler.GetCallHandler(out oCallHandler, connectionServer, "",

        "Test Handler");

 

if (res.Success == false)

{

    Console.WriteLine("Could not find call handler");

    return;

}

 

//update some properties - call handlers have many

oCallHandler.DisplayName = "My new name";

oCallHandler.EnablePrependDigits = true;

oCallHandler.PrependDigits = "44";

oCallHandler.SendPrivateMsg = ModeYesNoAsk.Ask;

 

//will output "Test Handler" here.

Console.WriteLine(oCallHandler.DisplayName);

 

//apply the changes asking for a re fetch of data, too

res = oCallHandler.Update(true);

if (res.Success == false)

{

    Console.WriteLine("Failed updating call handler");

    return;

}

 

Console.WriteLine("Call handler updated");

           

//will output "My new name" here.

Console.WriteLine(oCallHandler.DisplayName);

 

Note that if you had not passed "true" to the Update method in the example above the call to write out the DisplayName property would still have produced "Test Handler" in the example above.

You can also clear pending changes and display or log any pending changes prior to calling an update, like this:

 

//update some properties

oCallHandler.OneKeyDelay = 1200;

oCallHandler.Language = (int)LanguageCodes.Japanese;

 

//dump all pending changes to the console - prints out as a simple

//name value pair list

Console.WriteLine(oCallHandler.ChangeList.ToString());

 

//flush any pending changes

oCallHandler.ClearPendingChanges();

 

Most of the heavily used classes for users, call handlers, interview handlers, name lookup handlers, distribution lists, contacts, greetings, menu entries etc... implement the above convention.  There are, of course, ways to update properties using more traditional name value pair constructions and simple static methods that are more efficient for bulk update type scenarios which do not involve loading objects and working with properties directly, but for typical applications the above construction is considerably easier to develop against.

 

Handling Clusters and Connectivity

One common problem developers have to wrestle with is when a server they are communicating with suddenly goes off line for whatever reason.  If the system is setup in a cluster configuration (meaning it has a partner that can take over for it when it goes off line) conceptually it’s just a matter of redirecting your calls to the partner server and picking up where you left off.  Given REST is a stateless protocol you can simply check the failures you get back to see if it’s a timeout (meaning the server did not respond).

One of the things the ConnectrionServerRest class gets you when you attach to a server is a list of VmsServer objects exposed as a generic list property you can use to determine if the server is part of a cluster or not. If there’s more than one server in the list then you are part of a cluster – simple.  You can create a 2nd instance of a ConnectrionServerRest object for the 2nd server that you can have on “hot standby” in your application easily enough using something like this:

 

//after logging in the connectionServer instance you created will have a list

//of servers that will be either 1 or 2 members.  If it has 2 that means there's

//a cluster mate involved - you can try and create a 2nd ConnectrionServerRest instance 

//up front you can use in the case of a timeout event.

ConnectrionServerRest connectionServerSecondary;

if (connectionServer.VmsServers.Count > 1)

{

    foreach (var oServer in connectionServer.VmsServers)

    {

        //Find the server that's not the one you're already connected to!

        if (oServer.ObjectId != connectionServer.VmsServerObjectId)

        {

            try

            {

                connectionServerSecondary = new ConnectionServer(oServer.HostName,

                             connectionServer.LoginName, connectionServer.LoginPw);

            }

            catch (Exception ex)

            {

                Console.WriteLine("Failed to create secondary server:"+ex);

            }

        }

    }

}

 

Having that instance around you can then “fall back” to it easily enough.  There are, of course, slicker ways to go about this employing a single ConnecitonServer reference used throughout your code that smartly “swaps out” for the primary or secondary server as necessary but this shows the general idea.  Notably the WebCallResult class is key here in that the error code of -1 is reserved to mean specifically a timeout situation.

 

//you've attached to Connection and have been doing various items when we 

//get to this call that will fail.

UserFull oNewUser;

res = UserBase.AddUser(connectionServer, "voicemailusertemplate""newuseralias",

                        "33192"nullout oNewUser);

if (res.Success)

{

    return true;

}

 

if (res.StatusCode != -1)

{

    //normal error, log and bail

    Console.WriteLine("Error creating user:"+res);

    return false;

}

 

//if we're here the status code was -1 which indiates a timeout.  You can have a 

//second ConnectrionServerRest instance for the secondary server already on standby

Console.WriteLine("Primary not responding, trying secondary server");

 

res = UserBase.AddUser(connectionServerSecondary, "voicemailusertemplate"

                        "newuseralias","33192"nullout oNewUser);

if (res.Success)

{

    return true;

}

 

Console.WriteLine("Failed creating user on secondary:"+res);

 

That’s the general idea anyway.  As noted you can get much slicker with the handling of this in your application which is why automatically handling is not done at the SDK level.  There’s simply too many ways you may want to handle the situation to have it be completely automatic under the covers.

As a side note, if you need to test this in your application, the easiest way to do this is by using the command line in the Unity Connection console to turn the network adapter on and off.  Setup a cluster, attach to one of the servers, after you log in set a breakpoint in your application and then execute this command on the server you are connected to:

set network status eth0 down

The next command you issue via REST will result in a timeout – the WebCallResult class will indicate this with a StatusCode of -1 as noted above.  To turn the network interface back on simply issue the same command as above but use “up” instead of “down” for the command.

Logging and Debugging with the SDK

Since I’ve been asked a few times, let me just state up front here that the SDK is not supposed to log to a file on the hard drive for you.  Most of what you need for your own error handling and logging is passed back as part of the WebCallResult class discussed above. Since the SDK can be (and is) used in a variety of application types such as desktop applications, web servers and mobile application it cannot assume access to the local file system for logging purposes.  It does provide a few event handles you can wire up to provide more “dialog like” logging in your application if you prefer and/or can provide more diagnostic output you can handle as you like at your application level as disused in this next section.

The ConnectrionServerRest object exposes a couple of events you can use if you wish to be notified of any error and, optionally, debug event data that you can “hook” in your application to provide a more “dialog” logging output for instance.  As noted above all calls to static methods return a WebCallResult class instance that has all the error and details of what was sent/received from the server that you’d need.  Similarly class constructors return a UnityConnectionRestException which contains a handle to a WebCallResult instance used for the same purpose.  So the need to hook the error events off the server class is reasonably limited with one exception: JSON serialization errors. 

When data comes in from Connection’s REST methods, the SDK is taking that text in the body and “deserializing” it into an instance of a strongly typed object.  So when a bunch of Json text for a user comes flying across from the server you get a nice handle to a UserBase class object out of it which is much easier to work with.  Part of that process involves taking a property in that mess of Json text and stuffing it into a property on the class.  If there’s no property off the class to fit that data, we have a problem.  This can happen if a new property is added on a Connection version that is not accounted for in the SDK – this piece of data is essentially “dropped on the floor” and the SDK carries on as best it can.  In most cases this is not the end of the world, however knowing about this is handy (especially for me) so all my applications I used for testing and using the SDK all have a couple chunks of code in them that look like this after I’ve created and logged into my server creating a ConnectrionServerRest object (called “_server” here):

 

_server.ErrorEvents += ServerOnErrorEvents;

Then the definition for the method that fires when the error event is raised simply looks like this:

 

private static void ServerOnErrorEvents(object sender, 

            ConnectionServer.LogEventArgs logEventArgs)

        {

            Logger.Log("[ERROR]:"+logEventArgs.Line);

        }

 

Nothing too fancy – Any and all errors that are encountered on the server, including any serialization of JSON/XML data coming from the server (or vice versa) will show up in the log now where you can spot them. 

Similarly you can wire up the debug event that can also be useful, however you should only do this if you’re having a specific problem you’re trying to diagnose – you should NEVER have this enabled in a production application because the debug output is VERY chatty.  Every item being sent to and handled from the Connection server will be dumped out this diagnostic event and you’ll slow down your application and fill up logs very quickly if you’re not careful.  In most cases it’s actually easier to just use Fiddler for monitoring traffic and watching what’s going on since the debug data will be showing nearly identical information but in a less useful format.  However if you need to see what’s going on with a customer’s system or the like and that’s not an option, you can dump the traffic information out by wiring up the event like this:

 

_server.DebugEvents += ServerOnDebugEvents;

_server.DebugMode = true;

Notice that you have to turn debug mode on – if you don’t do that (it’s off by default) nothing will be raised out of the event.  Then a very similar signature is used for the event that is fired on the debug event as the error event:

 

private static void ServerOnDebugEvents(object sender, 

            ConnectionServer.LogEventArgs logEventArgs)

{

    Logger.Log("[DEBUG]:" + logEventArgs.Line);

}

 

Not too tricky.  Again, though, I highly encourage folks to wire up and alert/log on error events but leave the debug events out of the picture unless you have a driving need for them in a particular scenario.

Users                         

Users are, of course, the primary object of interest in most voice message applications.  They are tied to a daunting number of other objects in the directory such as a primary call handler, phone system, class of service, alternate extensions, notification devices, private lists etc…easily the largest and most complex object as far as data schema and relationships in the Unity Connection platform.  With that in mind we’ve designed the SDK to simplify finding and using these relationships and data items as possible.

Notably you’ll find many “lazy fetch” references hanging off the User class.   For instance the “AlternateExtensions()” method will fetch a generic list of all the alternate extensions (both user and system) for a user.  The next time you call that same method it will return that pre constructed list for you again without fetching it from the server unless you pass “true” as a parameter to force it to reread the directory data.  Yes, you could just create your own list using static calls off the AlternateExtension class which I cover here, but there’s generally no need for that extra work.  You’ll see this same design pattern for many of the related objects for users – I recommend you leverage them instead of reinventing the wheel.

NOTE: these “lazy fetch” items are implemented as methods instead of properties so that they do not fire when, say, a generic list of User objects is bound to a grid for instance.  Also, it provides a mechanism for being able to force a refetch of the data from the directory even if it’s already been fetched previously.

Creating and Deleting Users

The first thing to note is that the User class is actually TWO classes.  A UserBase and a UserFull class that inherits the functionality of UserBase and adds more properties.  The reason for this is that the number of properties on the User class is enormous and Connection’s REST interface presents a “short form” version when presenting lists of users and the “full version” when you fetch a single user by ObjectId.  This burns a lot of developers that don’t realize this and miss the fact that numerous properties they’re getting when finding a user by name are not there.  This is why.  So as a rule you use UserBase when fetching/iterating over lists of users and when you want to fully interrogate them, you get their UserFull instance.  The UserBase represents by far the most commonly needed items so having to fetch the full user should not always be necessary.  Sounds a little confusing but really it’s not – we try and hide much of that complexity from you in the SDK.

The next thing to understand is that most of the class libraries provided have multiple ways to go about creating new objects or finding existing objects.  I didn’t do this just for fun, there’s reasons to use each of them which I’ll discuss here while covering creating a new user.

Method 1: Static method with object returned.

 

//The user template name passed here is the default one created by setup and should
//be present on any system.  
UserFull oUser;
res = UserBase.AddUser(connectionServer,"voicemailusertemplate","TestUserAlias",
  "8001",null,out oUser);
 
if (res.Success==false)
{
    Console.WriteLine("Failed creating new user:"+res.ToString());
    return;
}
            
Console.WriteLine("User created="+oUser.ToString());

 

Couple things to notice here:  Yes, the static method is off UserBase even though the user details returned are UserFull.  Since UserFull is derived from UserBase (which defines the AddUser method) Visual Studio will bark that you should be using the base class instead of the derived one.  It’s just a warning and things will work fine but I like my applications to build as warning free as possible so I stick to the base class reference here.

The one thing to realize here is that this will make a POST to create the new user and, if it succeeds, it will then fetch the entire set of user details with a follow on GET call using the newly created ObjectId for the user and fill in the UserFull instance for you and hand it back.  You can then make updates to user properties and sub objects easily using that instance.  Convenient if that’s what you want but you need to be aware that follow-on GET isn’t cheap – watching the transaction in Fiddler2 helps make this clear – there’s a lot of data moving back from the server to fill in the UserFull details so if you don’t need that object (for instance you’re simply making a string of users quickly) you’ll want to use the 2nd method.

Method 2: Static method with no object returned:

 
res = UserBase.AddUser(connectionServer,"voicemailusertemplate","TestAlias","8001",
  null);
 
if (res.Success==false)
{
    Console.WriteLine("Failed creating new user:"+res.ToString());
    return;
}
            
Console.WriteLine("User created="+ res.ReturnedObjectId);

 

Notice that the code samples are nearly identical other than the lack of a UserFull object being created.  This is true, however there’s another element here.  The last “null” parameter there is a mechanism by which you can pass a series of name value pairs for user properties such that you can create the user with more custom data values than are allowed in the static method call alone which accepts only the bare minimum for creating a new user.  If you are developing an application that will be adding bulk users as quickly and efficiently as possible you will want to use this so you can provide things like display names, first/last names, billing IDs etc… up front.  This will be much faster than creating users, getting objects back, updating those properties on the objects and then saving them (which we’ll cover in the update section for users). The following example shows what that would look like:

 

ConnectionPropertyList oProps = new ConnectionPropertyList();
oProps.Add("DisplayName""Jeff Lindborg");
oProps.Add("FirstName""Jeff");
oProps.Add("LastName""Lindborg");
oProps.Add("BillingId","7714");
oProps.Add("ListInDirectory"true);
oProps.Add("PromptSpeed", 200);
 
res = UserBase.AddUser(connectionServer,"voicemailusertemplate","TestUserAlias",
  "80001",oProps);
 
if (res.Success == false)
{
    Console.WriteLine("Failed creating new user:" + res.ToString());
    return;
}
            
Console.WriteLine("User created, ObjectId=" + res.ReturnedObjectId);

 

Couple things to note here: The ConnectionPropertyList class is just a simple name value pair construction that has some nice syntactic sugar for handling different types for you (notice the overloads taking strings, bools and integers – this also works with date values).  The names of the properties ARE case sensitive here.  All property names consistently follow the standard “camel hump” construction – first letter is in capital then the first letter of each word in the identifier is capitalized.  Check the UserFull instance using Visual Studio’s auto complete function for a reference if you’re unsure.

This operation is done in one single HTTP request – there is no subsequent fetch of the user details needed here, so for bulk add operations this method is considerably more efficient.

Finding and Fetching Single Users

Fetching single users in the system is easy provided you know their ObjectId (not likely) or their alias (more likely).  If you don’t know either of those properties you will need to get a list of users back using search criteria (see the next section).  The UserBase class has a static method called “GetUser” that you can use to fill in either a UserBase or a UserFull class with information about the user given their alias.  The reason the alias is the only item supported in this construction is because the alias is unique across all users in the directory.  The primary extension is not (same extension can appear in multiple partitions) which makes a “single fetch” construction tricky – I relegate such searches to a construction that can return multiple matches and let you sort out which one you want.

The code construction for a single fetch is very easy, but filling out the UserBase or UserFull is very important.  The code for doing both kinds of fetches looks like this:

 

UserFull oUserFull;
UserBase oUserBase;
 
res=UserBase.GetUser(out oUserFull, connectionServer, """jlindborg");
if (res.Success)
{
    Console.WriteLine("User found:"+oUserFull.ToString());
}
Console.WriteLine("User not found with that alias.");
 
 
res=UserBase.GetUser(out oUserBase, connectionServer, """jlindborg");
if (res.Success)
{
    Console.WriteLine("User found:" + oUserBase.ToString());
}
Console.WriteLine("User not found with that alias.");

 

Not much to it.  You can use either the oUserBase or oUserFull for updating properties on the user, finding its primary call handler, switch assignment etc… the oUserBase simply has fewer properties (the more common ones) than the full list found in oUserFull.  However, if you can get away with sticking to the common items in the UserBase definition, do so.  What you don’t see in the simple code above is what’s going on in the background to get that data.

In the case of filling out the UserBase class a single HTTP GET request is made: https://cuc91:8443/vmrest/users?query=(Alias%20is%20jlindborg)

And a single response is received with roughly 37 lines of data (I use the term “lines” here liberally as obviously the line breaks are not sent, but you get the idea).  By comparison when passing the UserFull as an out parameter, the same GET request is sent and the same 37 lines are received back, but then another GET request is made: https://cuc91:8443/vmrest/users/b083a973-c2a4-4373-aae9-34678ab08d32

And another response is processed, but this time with roughly 150 lines of data.  In short roughly 200 lines of data get shuffled across the wire spread over two HTTP requests instead of about 40 with one.  Always keep this in mind when developing your applications and decide “Do I really need ALL the user data for this?” and try to “stay skinny” when you can.

Finding and Fetching Lists of Users

For Unity Connection 9.1 and earlier the querying capabilities offered by Connection’s REST interface are rather limited.  Notably compound queries are not supported – so you can’t construct a query that says “all users that are in COS=a, primary extension starts with 123 and have first names that start with J”.  You get one clause to filter by.  For Unity Connection 10.0 and later you can have compound filter queries (up to 5).  Even for 9.x and earlier this is still normally enough to work with given the ease of sorting/filtering lists of objects in .NET’s generic list classes.  For 10.0 and later it’s nice to do more complex filtering on the server side and not drag that extra response text over the wire.

The list fetching methods for all class objects help you manage paging through lists of objects as well – as a rule you generally want to limit how many objects you fetch at one time – Connection will throttle you if you make requests that take too many cycles to fulfill if you are not economical in your requests when Connection is busy.  Clearly it can’t let you impact its call processing capabilities simply because you don’t want to deal with handling paging.  If you fetch 1000 users at a crack and you notice your application “randomly” failing during high traffic times, this is likely your issue. When in doubt, limit yourself to no more than 100 items at a time.

First, how not to do this:

 
//this will get all users on the system.  Don't do this unless you're just testing.
res = UserBase.GetUsers(connectionServer, out oUsers);

 

and now how to use paging like you should:

 

//get just the count of users, the oUsers list is empty and there's very little data
//dragged across the wire – the page number=0 is a special command here telling
//Connection that you don’t want actual user data, but just the count of users.
res = UserBase.GetUsers(connectionServer, out oUsers, 0);
Console.WriteLine("Total users="+res.TotalObjectCount);
 
//fetch (up to) the first 5 users in the list
res = UserBase.GetUsers(connectionServer,out oUsers, 1, 5);
if (res.Success == false)
{
    Console.WriteLine("Error fetching users:"+res);
    return;
}
 
//The WebFetchResults class has another benefit: total objects count is included.
//In my server's case the values here are "26" and "5" respectively.
Console.WriteLine("Total objects on server:"+ res.TotalObjectCount);
Console.WriteLine("Objects returned:"+oUsers.Count);
 
//fetch the 2nd page of results and so on.
res = UserBase.GetUsers(connectionServer, out oUsers, 2, 5);
if (res.Success == false)
{
    Console.WriteLine("Error fetching users:" + res);
    return;
}
            
//fetching an invalid page number does not result in an error - it simply returns 
//an empty list of users.
res = UserBase.GetUsers(connectionServer, out oUsers, 30, 5);
if (res.Success == false)
{
    Console.WriteLine("Error fetching users:" + res);
    return;
}
 
//The output here is "26" and "0"
Console.WriteLine("Total objects on server:" + res.TotalObjectCount);
Console.WriteLine("Objects returned:" + oUsers.Count);

 

So in short you can fetch the total object count from the server either stand alone (using a page number of 0) or simply use the first batch of users you fetch to get the value – do a little math and you can see how many pages you have to iterate over to provide a nice “6 of 26” type list presentation to your users or the like.  There’s a simple example of how to do such a presentation in the WinForms example in the “CUPIFastStart” project if you’re interested.

Filtering and sorting, as noted, is rather limited.  Only a single clause is allowed an only “is” and “startsWith” are supported – so no “Contains”, “Between” or other types of similar operators you may be used to.  Also note that when sorting and filtering you can only sort on the same clause you filter on – so you can’t filter by extension and sort by alias in other words.  You can leverage .NET list sorting for lists of manageable size – a UserSort clause is provided for that use which is shown here:

 

//to sort generic lists of users (either base or full) you can use the UserComparer
//class.  Here is how you can rearrange a list of users to sort ascending by their 
//primary extension
UserComparer oCompareer = new UserComparer(UserComparer.UserSortElements.
  DtmfAccessId.ToString());
 
oUsers.Sort(oCompareer);
 
//...and by first name
oCompareer = new UserComparer(UserComparer.UserSortElements.FirstName.ToString());
oUsers.Sort(oCompareer);

 

You can sort by Alias, FirstName, LastName, DisplayName, DtmfAccessId (primary extension).  Empty strings are sorted later than non empty strings (does not apply to Alias or DtmfAccessId since those cannot be blank).

On with the filtering and server side sorting story.  The query and sort clauses can be added to the end of the GetUsers call along with the paging parameters – the SDK takes care of adding these to the URI parameters including escaping out spaces and such.  Here are a couple examples of filtering and sorting

 

//simple fetch to get all users named "Jeff" - neither the name itself or the 
//query parameters are case sensitive.
res = UserBase.GetUsers(connectionServer, out oUsers, "query=(firstname is jeff)");
 
//This gets all users that have primary extensions starting with 2
//with proper paging options included - the GET clauses can just keep getting
//stacked in.
res = UserBase.GetUsers(connectionServer, out oUsers,1, 5, "query=(DtmfAccessId startswith 2)","sort=(DtmfAccessId asc)";

 

For 10.0 doing a multiple clause search looks like this:

 
//simple fetch to get all users named "Jeff" that have an last name that starts 
//with L - neither the name itself or the query parameters are case sensitive.
res = UserBase.GetUsers(connectionServer, out oUsers, "query=(firstname is jeff 
& lastname startswith l)");
 
//This gets all users that have primary extensions starting with 2
//with proper paging options included - the GET clauses can just keep getting
//stacked in.
res = UserBase.GetUsers(connectionServer, out oUsers,1, 5,      "query=(DtmfAccessId startswith 2)","sort=(DtmfAccessId asc)";

 

If you need to construct a very large list of users on the local client for whatever reason it’s best to create a local object such as a Dictionary (.NET has a nice selection of containers to choose from) and use paging to “fill up” your container with users one page full at a time.  A good rule of thumb is to stick to 100 objects at a time when fetching against production servers.  In your test environments certainly more can be returned (with a somewhat long delay) but it’s not a good idea.  To keep the server from throttling your application and your users from twiddling their thumbs wondering if your application is locked up it’s a better idea to fill up your local container in pages and provide a nice update status to your user (i.e. “Loading user details, %40”) – since you get the total number of users in the first page full you’ll know where you’re at in the process.  On the whole this is the preferred approach to handling large lists of objects in general, but users in particular since they are so large by comparison to other objects in the directory.

Updating Users

As with creating users there are a couple ways to update users in the SDK.  The first way leverages the static method for updating users and passing in a property list similar to the option for creating new users and populating the list of user properties you wish to have applied to the user up front. This requires you know the objectId of the user – for instance if you had a list of userObjectIds fetch into a list or that were referenced (for instance as members of a public distribution list) or the like, this method would make sense in scenarios where you’d be bulk applying some properties to all users in that list.  Something along these lines:

 

ConnectionPropertyList oProps = new ConnectionPropertyList();
oProps.Add("VoiceNameRequired",true);
oProps.Add("ListInDirectory"false);
oProps.Add("IsVmEnrolled"false);
 
foreach (string strObjectId in oListOfUserObjectIds)
{
    res = UserBase.UpdateUser(connectionServer, strObjectId, oProps);    
    if (res.Success == false)
    {
        Console.WriteLine("User update failed:"+res);
        break;
    }
}

 

Notice that the above code only issues a series of POST commands, one for each user – it never has to do a GET to fill any data.  Again, for processing similar changes for large groups of users this will be much more efficient than creating user objects first and then updating them. 

Another method leveraging the User class instances instead is to simply make changes to properties off that instance and call the “Update” method directly on that instance.  This is not as efficient for large lists of users but is considerably easier to use and maintain and can be very handy in building user interfaces where you can bind form elements directly to properties of a class as it can save you a bunch of time having to code up a “dirty list” of changed properties.

 

//First, fetch a user - we'll grab one by alias here
res = UserBase.GetUser(out oUserBase, connectionServer, """jlindborg");
if (res.Success == false)
{
    Console.WriteLine("Error fetching user:"+res);
    return;
}
 
//update as many properties as you like
oUserBase.ListInDirectory = false;
oUserBase.DisplayName = "New User Display Name";
oUserBase.VoiceNameRequired = true;
oUserBase.IsVmEnrolled = false;
 
//Apply the changes to the server.  Only changed properties are sent.
res = oUserBase.Update();
 
if (res.Success == false)
{
    Console.WriteLine("Error updating user:"+res);
    return;
}

The second method is handy for a couple reasons.  First, you can make changes right off the instance of the class which makes finding the property you want as easy as searching the InteliSense list Visual Studio provides.  This is also handy if you have the user presented with many UI elements they can change on an object – use binding to show the values of the User object, for instance, and allow them to make a single change when they’re all done.  If they change their mind the class includes a “ClearPendingChanges()” method that flushes the “dirty” property list for you.

Voice Names

Dealing with voice names and greetings for users and call handlers in the REST interfaces in Connection has easily been the item that’s caused developers the most grief.  With that in mind we’ve made an effort to make the stream file handling in the SDK as painless as possible.  So to set the right tone here, let’s see an example of how to set a voice name of a user from a WAV file on the local hard drive.  This sample assumes you’ve already fetched a user instance (either base or full, doesn’t matter):

 

res=oUser.SetVoiceName(@"c:\myvoicename.wav"true);
if (res.Success == false)
{
    Console.WriteLine("Failed updating voice name:"+res);
    return;
}

That’s all there is to it.  Note the “true” parameter on there – this is a very powerful capability in the SDK that will rip your WAV file into a raw PCM 16/8/1 recording for you before uploading the WAV file to Connection.  If you’ve worked with Connection’s REST interface at all you know it can be pretty fussy about the WAV format and you’ll see a lot of “invalid media format” errors coming back.  This eliminates that bit of pain which can save you a bunch of time.  You’re welcome.

And conversely, how do we fetch a voice name from a user on Connection to a local WAV file on my client?  Looks almost identical:

 

res = oUser.GetVoiceName(@"c:\voicenameout.wav");
if (res.Success == false)
{
    Console.Write("Failed fetching voice name:"+res);
    return;
}

OK, so that covers using WAV files from the local client for voice names – not so scary.  What if you want to use the telephone as a recording device instead?  This is referred to in Connection as “TRAP” (Telephone Record and Playback).  Instead of using your microphone and recording a local file and uploading it, you record a stream file directly on the Connection server via your telephone and then assign that recorded stream file to a user’s voice name.  In the REST API pantheon this is referred to as “CUTI” for the “Telephone Interface” API.  The SDK provides a PhoneRecording class to help you with this interface.

This is pretty easy – you need a phone to use and in our example here we’ll assume it’s 1003.  It’s assumed that the Connection server you’re attached to can dial it directly, of course.  Again, in this example our “oUser” object has already been fetched.

 

//First, establish a call to extension 1003 (your extension presumably)
//This is done in the construction of the class instance.
PhoneRecording oPhone;
try
{
    oPhone = new PhoneRecording(connectionServer, "1003");
}
catch (Exception Ex)
{
    Console.WriteLine("Failed to establish phone call:"+Ex);
    return;
}
            
 
//now start recording - you'll only hear a beep here that indicates
//you can start talking - press # and the recording will terminate
res=oPhone.RecordStreamFile();
if (res.Success == false)
{
    Console.WriteLine("Error recording stream:"+res);
    return;
}
 

//for fun, play the currently recorded stream out

res = oPhone.PlayStreamFile();

 
 
//Now set that stream as the voice name for your user
res= oUserBase.SetVoiceNameToStreamFile(oPhone.RecordingResourceId);
 
if (res.Success == false)
{
    Console.WriteLine("Error setting voice name :" + res);
}
 
//hang up the phone
oPhone.Dispose();
 

Wow.  That was pretty easy, right?  You’ll see this class come up again when we talk about greetings for call handlers later.

PINs and Passwords

Another pain point for folks using the CUPI interface in the field has been managing PIN and Passwords.  For clarity “PIN” is a Personal Identification Number and is a password you enter via the phone (numbers only).  A Password is alphanumeric string used for logging into GUI clients (i.e. with access to a full keyboard interface).  The ResetUserPin and ResetUserPasswords methods are provided as both static and instance methods off the user class (either full or base, it doesn’t matter).

 

//use the static class if you have the objectID and don't need to create
//a user object first. 
//Reset the Pin of a user that you have the ObjectId for.
res = UserBase.ResetUserPin(connectionServer, strObjectId"1324523");
 
//More commonly you're leveraging a user object to update PINs and passwords.
//just reset the password and nothing else
res = oUser.ResetPassword("RainySunday");
 
//clear the hacked count and unlock the user's account
//Passing a blank PIN means it will be skipped - you cannot set a PIN or a 
//Password to blank via the SDK.
//The null values mean leave their corresponding values alone
res = oUser.ResetPin(""false, null ,null ,null , true);
 
//reset the PIN and unlock the account.
res = oUser.ResetPin("019012"false);

 

It’s sometimes also desirable to check the PIN and Password settings for a user.  Are they locked out (hacked), is it set to never expire, when was it changed last?  This is offered through the Credential class – each user has a PIN and a Password credential associated with them.  As usual there is a static method to go about this and a convenient “lazy fetch” option off a user instance.  Both are shown here:

 

//use the static class if you have the objectID and don't need to create
//a user object first. Resetting PINs for large numbers of users would be 
//quicker using this method for instance.
 
//Fetch the credentials for a user's PIN
Credential oCredential;
res = Credential.GetCredential(connectionServer,strObjectId,CredentialType.PIN,
  out oCredential);
Console.WriteLine("PIN hacked="+ oCredential.Hacked);
 
//As usual you can also fetch this same information off the user instance using
//a lazy fetch mechanism
Console.WriteLine("PIN hacked="+ oUser.Pin().Hacked);
 
//or for the password
Console.WriteLine("PW last changed="+ oUser.Password().TimeChanged);

 

Private Lists

Each user can have up to 99 private lists (limit is configurable in the user’s Class of Service).  A private list can contain users, public distribution lists, remote contacts and other private lists.  The SDK allow for creating of new lists, recording a voice name for that list, adding and removing members from lists and, of course, reviewing membership and list details.  Note that the SDK currently only supports adding users and public lists and private list members.  Adding private lists to other private lists is a little strange and isn’t plumbed into the library.

The easiest item is reviewing the list details for a user.  As you’ve probably come to expect, there’s a lazy fetch method for private lists off the User instance that you can leverage to quickly review the list information and its membership.  Here’s an example of showing all the lists and each list’s membership details for a user you’ve already fetched:

 

//Dump all private lists and membership details for all private lists
//associated with a user.
foreach (var oList in oUser.PrivateLists())
{
    Console.WriteLine(oList.ToString());
    Console.WriteLine("Memebers:");
 
    foreach (var oMember in oList.PrivateListMembers())
    {
        Console.WriteLine("    "+oMember.ToString());
    }
}

The “ToString” override on the private list object shows the lists name and its number (lists are addressed using their number, from 1 to 99).  The “ToString” override on the list member object shows its type (local user, distribution list or private list), their alias and display name. 

Of course you can also fetch lists and membership information via static calls as shown here:

 

//fetch all the private lists for a user via static call
List<PrivateList> oLists;
res = PrivateList.GetPrivateLists(connectionServer, strUserObjectId,out oLists);
            
//fetch members of a list via a static call - need the ObjectId of the user that 
//owns the list and the objectId of the list itself for this.
List<PrivateListMember> oMemebers;
res = PrivateListMember.GetPrivateListMembers(connectionServer,strListObjectId,strUserObjectId,out oMemebers);

 

To create new lists you use static methods to create a new instance of a list object which you can then edit in much the same way you can users and other objects in the directory.  In this example we create a new private list for a user that we’ve already created a User object for.  Note that when you create a new list you have to assign a number to it – this is that id between 1 and 99 (depending on your maximum count allowed).  You need to assign the ID to one that’s not in use – it’s not required that you add them sequentially (i.e. you can have list 1, 7 and 19 if you like) but this makes managing private lists via the API difficult since the user may be making private lists via the web interfaces and you have no control over how “orderly” they are about it.  If you add a list that already exists the server will return an error.  Similarly if you add a list beyond the number supported you will also get an error.  Be sure to check the return values for the WebCallResults at each step (which I’m not doing here for brevity).

 

//get the count of lists - we'll add one after that number
PrivateList oPrivateList;
int iListCount = oUser.PrivateLists().Count;
 
res = PrivateList.AddPrivateList(connectionServer,oUser.ObjectId,"My new list",
  iListCount+1,out oPrivateList);
 
if (res.Success == false)
{
    Console.WriteLine("Error adding private list:"+res);
    return;
}
 
//set the voice name of the private list - heard durring the message 
//addressing conversation when the list is selected as a message target.            
res=oPrivateList.SetVoiceName(@"c:\myVoiceName.wav");
 
//Find a public list in the directory and add it to our new list
DistributionList oPublicDl;
 
res=DistributionList.GetDistributionList(out oPublicDl,connectionServer,"",
  "allvoicemailusers");
 
res = oPrivateList.AddMemberPublicList(oPublicDl.ObjectId);
 
//Find a user in the directory and add it to our new list
UserBase oUser;
res = UserBase.GetUser(out oUser, connectionServer, """jlindborg");
 
res = oPrivateList.AddMemberUser(oUser.ObjectId);

 

To remove a member you need to fetch the ObjectId of the PrivateDistributionListMember object first.  This sample shows how to fetch a specific private list by number off a user, remove a member from that list and then delete the list entirely:

 

//fetch list #3 for our user (the one we added above).
res = PrivateList.GetPrivateList(out oPrivateList, connectionServer, oUser.ObjectId, "", 3);
if (res.Success == false)
{
    Console.WriteLine("Failed fetching list:" + res);
    return;
}
 
//get a particular member from the list (the user jlindborg) and remove them.
//There is no individual member select opition, you need to get the list and 
//find the member you want here;
foreach (var oMember in oPrivateList.PrivateListMembers())
{
    if (oMember.MemberType == PrivateListMemberType.LocalUser &
        oMember.Alias.Equals("jlindborg"))
    {
        res =oPrivateList.RemoveMember(oMember.ObjectId);
        break;
    }
}
            
//if you want to iterate over the list of members again be sure to use the 
//"refetchData" flag to make sure the list is refetched and you're not using 
//stale data from an earlier fetch.
oPrivateList.PrivateListMembers(true);
 
//now delete the list
res=oPrivateList.Delete();

 

Alternate Extensions

Each user in the directory has a primary extension which shows up on their user object instance.  This is not optional and every user has one.  There can, however, be alternate extensions (both user added and admin added) totaling up to 20 numbers.  These are normally for home phones, cell phones and such that allow the user to be automatically logged in when they call to check voice mail from one of those lines or for callers that forward into the Connection voice mail system from one of those numbers to be forwarded to the user’s greeting. The SDK has a set of methods for fetching, reviewing and managing these extensions for you.

We’ll start with listing the alternate extensions for a user that we’ve already fetched into an oUser object – as you’ve come to expect by now this leverages a simple lazy fetch method to get the alternate extensions list off the user directly:

List<AlternateExtension> oAlternateExtensions;

//output all alternate extensions - what's returned will depend on the user's COS
//settings - admin added alternate extensions may or may not be included.

foreach (var oTempExt in oUser.AlternateExtensions())
{
    Console.WriteLine(oTempExt.ToString());
}

To add and remove an alternate extension uses similar design patterns we’ve already seen – use the static method to create a new instance as shown here:

//Adding an alternate extension can be restricted by the user's class of service so expect //that this call can fail.
AlternateExtension oAltExt;
res = AlternateExtension.AddAlternateExtension(_connectionServer, oUser.ObjectId, 3, "1234",out oAltExt);

if (res.Success)
{
   Console.WriteLine(oAltExt.DumpAllProps());

    //delete the alternate extension you just added.
    res = oAltExt.Delete();

}

The “DumpAllProps” there is just a handy debug mechanism that drops all the values of any instance of just about all the classes in the SDK – it exports every property in the class and it’s corresponding value in a simple name/value pair table format for easy review.

Notice, again, the index number there (3 in this example) – that’s the position of the alternate extension, it’s up to you to pick the right one to add (one that does not exist) – it may be necessary to iterate over them all to find an open one since the user can certainly create/remove them via the web interface and you cannot assume a contiguous progression of IDs in the list.

Notification Devices

In Connection 9.0 and later there are 6 default notification devices that are associated with all users and that cannot be deleted.  Four of these 6 are available via the phone interface to edit (the phone based devices as opposed to the SMTP and HTTP notification devices).  Users and administrator can add additional notification devices of any type – there is technically no limit to these.  This makes dealing with the administration of these devices a little tricky at times – you can count on those 6 devices (5 in versions prior to 9.0 since HTTP did not exist) but beyond that you have no idea how many may be there and there are no defined “slots” for how many additional devices can be created.  So you’ll be doing a lot of walking of lists in other words – and fortunately generic lists in .NET are easy to work with in this regard.

As usual we’ll start with listing the notification devices out – since devices are always tied to users it’s easiest to do this off the User object – you can use the static NotifcationDevice class and fetch them that way, of course, but you’ll need to pass in the objectId of a user to get them (i.e. you cannot iterate over all notification devices in a system, it must be done by user).

 

//list all devices for a user - this includes their name, type and if they're active

foreach (var oDevice in oUser.NotificationDevices())

{

    Console.WriteLine(oDevice.ToString());

}

 

Not too tricky.  You can also fetch a specific notification device by name like this

 

//fetch a specific device for a user by name

res = oUser.GetNotificationDevice("Home Phone"out oNotificationDevice);

if (res.Success == false)

{

    Console.WriteLine("Failed to find home phone device:" + res);

    return;

}

 

You can also create different types of devices – the SDK supports creating a phone, pager, SMS, SMTP or HTML based devices.  Here’s a bit of code that shows how to create a new phone based device, fetch it, update it and then delete it.

 

//create a new phone device for a user

NotificationDevice oNotificationDevice;

res = NotificationDevice.AddPhoneDevice(connectionServer, oUser.ObjectId, "New Phone

    Device",oUser.MediaSwitchObjectId, "5551234","NewUrgentVoiceMail",true,

    out oNotificationDevice);

 

if (res.Success == false)

{

    Console.WriteLine("Failed creating phone device:"+res);

    return;

}

 

//Update some of the notification device details.

oNotificationDevice.DisplayName = "New Display Name";

oNotificationDevice.EventList = "AllMessage";

oNotificationDevice.Active = false;

            

//apply changes

res = oNotificationDevice.Update();

            

if (res.Success == false)

{

    Console.WriteLine("Failed to update new device:"+res);

}

 

//delete the device we just added

res = oNotificationDevice.Delete();

if (res.Success == false)

{

    Console.WriteLine("Failed to delete device:" + res);

}

 

Couple of things to notice about this code sample.  First, when creating a new notification device you must always provide a user’s ObjectId, of course, but also a phone system association (media switch objectID) – this is true for all phone and pager based devices since they require a dial out which needs to know which switch definition to go out on.  Also notice the event trigger string – this is a comma separated string that you can pass multiple types of messages that will cause the device to trigger.  These include and of: AllMessage,NewFax,NewUrgentFax,NewVoiceMail,NewUrgentVoiceMail,DispatchMessage,UrgentDispatchMessage.  Although strange, it won’t hurt anything to include, say “AllMessage” and “NewVoiceMail” in the same list even though they are redundant.

 

Creating an HTML based device (added in Connection 9.0) requires you pass in a Notification Template ID as part of the creation.  To this end there is a NotificationTemplate class provided that makes fetching and enumerating the list of templates defined on the system easy for you.  The following chunk of code shows how to fetch the list of templates and then create a new HTML notification device for a user:

 

//First, fetch the HTML templates on the server - there are two by default.

//Here we only check if at least one is returned and we blindly use it for brevity.

List<NotificationTemplate> oTemplates;

res = NotificationTemplate.GetNotificationTemplates(connectionServer, 

  out oTemplates);

 

if (res.Success == false || oTemplates.Count<1)

{

    Console.WriteLine("Failed fetching templates:"+ res);

    return;

}

 

//Now add the device for our user - the display name needs to be unique here

res = NotificationDevice.AddHtmlDevice(connectionServer, oUserTestDude.ObjectId, 

  oTemplates[0].NotificationTemplateID, "New HTTP","testguy@test.com"

  "NewVoiceMail"true);

 

if (res.Success == false)

{

    Console.WriteLine("Failed adding device:"+ res);

    return;

}

 

//you can turn around and fetch the device by name if you like.  Clearly it's easier

//to just use an out parameter on the AddHtmlDevice above but if you need to fetch

//by name you can use this technique

NotificationDevice oTest;

res = NotificationDevice.GetNotificationDeivce(connectionServer, 

  oUserTestDude.ObjectId, """New HTTP"out oTest);

 

if (res.Success == false)

{

    Console.WriteLine("Failed fetching device"+res);

}

 

 

A special note to consider when it comes to notification devices about scheduling.  By default any new device created will be associated with the default system schedule (all hours).  You can assign any notification device to any schedule in the system by assigning a schedule set’s ObjectId to the notification devices “ScheduleSetObjectId” property and updating it (see the Schedules section for more).  If, however, you wish to assign a custom schedule to a device, the SDK greatly simplifies this rather daunting task for typical scenarios.  If you want to create a simple schedule that’s active for selected days from the same time to the same time each of those days, you can do this in one line of code (a long line admittedly).  If you wish to create more complex schedules with different start/stop times or with “breaks” in the middle of days etc… you’ll need to look at the Schedules section for details on how to do that.

In this example we’ll update a notification device that’s already been loaded to have a new, custom schedule that’s active Monday, Wednesday and Friday from 10am to 9pm:

 

 

 

//Create a new custom schedule in one call using AddQuickSchedule.

res = ScheduleSet.AddQuickSchedule(connectionServer, "Phone Device Schedule"""

  oUser.ObjectId, Schedule.GetMinutesFromTimeParts(10, 0), 

  Schedule.GetMinutesFromTimeParts(21, 0),

  truefalsetruefalsetruefalsefalse);

 

if (res.Success == false)

{

    Console.WriteLine("Failed creating schedule:"+res);

    return;

}

 

//Assign the newly created schedule to the notification device and update

oDevice.ScheduleSetObjectId = res.ReturnedObjectId;

 

res = oDevice.Update();

 

if (res.Success == false)

{

    Console.WriteLine("Failed assigning schedule:"+res);

}

 

At this point the notification device that was created earlier is now associated with a custom schedule.  To be clear this schedule is stored in the same location as all system schedules but because it’s assigned to a user instead of a location (see the Schedules section for more details here) it will not show up in the CUCA web admin interface in the schedules section.  As a side note, the “Schedule.GetMinutesFromTimeParts” call is just a simple helper to convert an hour/minute in 24 hour format into minutes-from-midnight which is what the schedule interface needs for start and stop times – you could replace that with just an integer if you wanted to do your own math there.

Message Waiting Indicators

Users can have up to 10 MWI devices defined – although it’s very rare to ever see more than 2 other than for some edge case scenarios.  With that in mind the SDK does not support finding MWIs by name or the like – you can add, delete and fetch MWIs for users but to find the one you want you’ll need to iterate through them.  As usual we’ll start with a simple example of listing the MWIs for a user that’s been fetched already

 

//dump out all MWIs defined for a user - includes the name, extension if it's 

//Enabled and if the lamp is on or not.

foreach (var oMwi in oUser.Mwis())

{

    Console.WriteLine(oMwi.ToString());

}

 

And now an example of creating a new MWI device, fetching it, updating it and finally deleting it:

 

//Create a new MWI device and set it active

res = Mwi.AddMwi(connectionServer, oUser.ObjectId, "New MWI", oUser.MediaSwitchObjectId, "1234"true);

if (res.Success == false)

{

    Console.WriteLine("Failed adding MWI:"+res);

    return;

}

 

//fetch the new MWI using the objectId returned on the 

//WebCallResults class instance.

Mwi oMwiDevice;

res = Mwi.GetMwiDevice(connectionServer, oUser.ObjectId, res.ReturnedObjectId, 

  out oMwiDevice);

 

if (res.Success == false)

{

    Console.WriteLine("Failed to fetch new MWI:"+res);

    return;

}

 

//update some properties on the Mwi device we just added and save it

oMwiDevice.DisplayName = "New Display Name";

oMwiDevice.IncludeTextMessages = true;

oMwiDevice.MwiExtension = "4321";

oMwiDevice.Active = false;

 

res = oMwiDevice.Update();

 

if (res.Success == false)

{

    Console.WriteLine("Failed to update MWI:"+res);

    return;

}

 

//finally, delete the device we just added

res=oMwiDevice.Delete();

 

if (res.Success == false)

{

    Console.WriteLine("Failed to delete MWI:"+res);

}

 

Mailbox Information

The MailboxInfo class provides information about the size, mounted state, quota limits and if the deleted items folder is enabled for the user.  There’s only one way to get this data and that’s to create a new instance of the MailboxInfo class using the ObjectId of a user – there are not static methods or “lazy fetch” options off the user for this:

 

//Fetch the mailbox information for a user

MailboxInfo oMailboxInfo;

try

{

    oMailboxInfo = new MailboxInfo(connectionServer, oUser.ObjectId);

}

catch (Exception ex)

{

    Console.WriteLine("Failed fetching mailbox info:"+ex);

    return;

}

 

Console.WriteLine("Over send limit="+oMailboxInfo.IsSendQuotaExceeded);

Console.WriteLine("Mailbox size={0}, send limit={1}, recieve limit={2}",

    oMailboxInfo.CurrentSizeInBytes, oMailboxInfo.SendQuota,

    oMailboxInfo.ReceiveQuota);

 

The class also contains a helper function to fetch the message counts (in addition to the full inbox size in byes provided in the attributes). 

 

int iInbox, iSent, iDeleted;

res = oMailboxInfo.GetFolderMessageCounts(out iInbox, out iDeleted, out iSent);

 

if (res.Success == false)

{

    Console.WriteLine("Failed to fetch folder counts:"+res);

    return;

}

 

Console.WriteLine("Inbox message count="+iInbox);

 

This is a read only informational class, there’s no updating capabilities included.

Class of Service

Every user in the system is associated with one and only one Class of Service that dictates system access in the admin, which phone numbers the user is allowed to enter and limits access to licensed features in Connection.  The SDK allows for finding, fetching, reviewing, creating and deleting classes of service.  For more on working with Class of Service objects, see the Class of Service section.

As usual let’s start with simple fetches of lists of class of services:

 

//get and list all COSes

List<ClassOfService> oCoses;

res =ClassOfService.GetClassesOfService(connectionServer, out oCoses);

 

foreach (var oCos in oCoses)

{

    Console.WriteLine(oCos.ToString());

}

 

 

//fetch all COSes that have a display name that starts with "voice"

res = ClassOfService.GetClassesOfService(connectionServer, out oCoses, 

  "query=(DisplayName startswith voice)");

 

foreach (var oCos in oCoses)

{

    Console.WriteLine(oCos.ToString());

}

The query clause construction is the same as was presented in the user searching section and can include a sort clause and paging.  Normally with class of service objects it’s not likely there will be so many that paging is a concern – if you have thousands of class of service objects in your design, you’re doing it wrong.  That said you can certainly construct your application to use such features, they work in the same way here.

There’s also a lazy fetch handle for class of service references for users that can be used off an instance of the UserBase or UserFull class:

 

Console.WriteLine("Can send to DL-" + oUser.Cos().CanSendToPublicDl);

And you can also find a class of service using its name if you need to.  The display names for classes of service must be unique system wide so you can use them for this purpose:

 

ClassOfService oCos;

res = ClassOfService.GetClassOfService(out oCos,connectionServer,"",

  "Voice Mail User COS");

Greetings

Greetings for users are managed through the users primary call handler.  See the Greetings section for Call Handlers for details on how to use this in the SDK.  As with many references on the user you can leverage a simple “lazy fetch” path to get the call handler for a user in one operation:

 

// show the greeting name, play what setting and active/inactive setting
// for all 7 greetings associated with a user.
foreach (var oGreeting in oUser.PrimaryCallHandler().GetGreetings())
{
    Console.WriteLine(oGreeting.ToString());
}

 

Transfer Options

Similar to greetings, the 3 transfer rules for a user are stored on the primary call handler associated with the user.  See the Transfer Options section for Call Handlers for details on how to use this in the SDK.  Similar to other references, there is a “lazy fetch” option for transfer rules off the primary call handler fetch on the user which can be used like this:

 

//Dumps the transfer details (including active/inactive and action setting)
//for all 3 transfer options associated with a user.
foreach (var oTransfer in oUser.PrimaryCallHandler().GetTransferOptions())
{
    Console.WriteLine(oTransfer.ToString());
}

 

Menu Entries

Similar to greetings and transfer options, the menu entries (mappings for actions taken when callers press 0-9, * or # during the user’s greeting) are associated with the user’s primary call handler.  See the Menu Entries section for Call Handlers for details on how to use this in the SDK.  This can be accessed off a user object (either full or base) using a lazy fetch reference to the user’s primary call handler like this:

 

//Dumps the basic configuration for each of the 12 keys in the user's menu
//entries.
foreach (var oMenu in oUserFull.PrimaryCallHandler().GetMenuEntries())
{
    Console.WriteLine(oMenu.ToString());
}

 

Phone System

All objects that can handle calls (users, call handlers, interviewers, name lookup handlers) and many sub objects such as notification devices are assigned to phone systems.  Connection can have numerous different phone systems defined at the same time and assigning users and phone notification devices and such to different phone system is how administrators can segment, say, tenants that are sharing a single Connection installation and/or dictate which ports notification dial outs for particular users will be used.

Currently there is no option to create new phone systems or port groups (this is coming in a later version of Connection) so the Phone System is informational (read only).  As usual you can list all phone systems via a static method call or leverage simple “lazy fetch” calls off objects like the user to review the details of the phone system they are associated with.

 
//get all phone systems
List<PhoneSystem> oPhoneSystems;
res = PhoneSystem.GetPhoneSystems(connectionServer, out oPhoneSystems);
 
foreach (var oPhoneSystem in oPhoneSystems)
{
    Console.WriteLine(oPhoneSystem.ToString());
    Console.WriteLine("Details:");
    Console.WriteLine(oPhoneSystem.DumpAllProps("--->"));
}
 
 
//Get the phone system details for a particular user
Console.WriteLine(oUser.PhoneSystem().ToString());

 

Exiting the User Mailbox Conversation

So, where does the user go when they hit * to exit out of their inbox conversation?  Certainly you can customize the conversation to simply not allow that, and some sites do, but normally the users can exit out and by default they end up going to the Opening Greeting call handler created by setup – mostly because they have to go somewhere and that’s as good a place as any.  Like many other settings in Connection, the user’s exit destination is controlled by the “Action trinity” that lets you set an action, conversation and destination so that you can define the exit action to be anything from hang up immediately (very rude but efficient), launch a special conversation or send the call to another user, call handler, interview handler or name lookup handler.  Lots of options and you control it all by editing three properties on the FullUser object:

·          ExitAction

·          ExitTargetConversation

·          ExitTargetHandlerObjectId

This is the same 3 property construction used in many places in Connection to do the same thing – route the call somewhere based on an action.  Anything from where to go when a menu entry is pressed to where to go after a greeting finishes playing to how to handle a caller not entering anything in a name lookup handler.  Rather than repeat the details of how to use these three properties here and everywhere else I’ve included a general discussion on how to use the “Action” properties as these are referred to in a consolidated Setting Actions section.

LDAP Users

The UserLdap class is provided to allow you to find and import users from an LDAP integration.  So for instance if you have Active Directory LDAP synchronization configured for Unity Connection, the users available for import are provided as part of the static calls to “GetLdapUsers” off this class.  Only those users available for import from LDAP are presented via this interface and once you import a user they will no longer be returned in that search.  If you then turn around and delete that user from Unity Connection’s directory they will once again show up as available for import (or should – there have been a few bugs in this area in some versions of Unity Connection).

So what’s going on here?  Behind the scenes there’s another database you cannot access directly via REST or ODBC that is responsible for holding all the user’s found in your LDAP configuration(s) for a server.  This table maintains a mapping of users from the LDAP integration to Unity Connection users in the directory.  When you pull a list of users to import they are pulled from this database behind the scenes, not from the LDAP directory itself – the synching of that database is done separately in the background.  When a user is imported the ObjectId of that user in Connection is added to the table in that background database which indicates this user is “mapped” and should not be offered for import again.  When you delete that user the mapping should be removed, thus making it available again.

All that really isn’t important for getting your job done, however, so lets just look at a typical example of something you’d want to do in an application.  Find a user to import and import them. The UserLdap class contains only a few methods off of it since you can’t create/delete/edit LDAP users, of course, only find and import them.  LDAP users have only a few properties on them: first and last name, alias and pkid (unique ID from that mapping table mentioned above).  Notably they do not have extensions populated on them so you have to provide that when importing them along with the alias of a template, similar to creating a new user in the previous section.

Here’s a code chunk showing finding a user to import with the alias “jlindborg” and then importing them:

 

//Find the user

List<UserLdap> oLdapUsers;

res = UserLdap.GetLdapUsers(connectionServer, out oLdapUsers, 1, 20,"query=(alias is

             jlindborg)");

 

if (res.Success == false)

{

    Console.WriteLine("Failed fetching LDAP user:"+res);

    return;

}

 

if (oLdapUsers.Count != 1)

{

    Console.WriteLine("Failed finding jlindborg");

    return;

}

 

//Import the user

UserFull oImportedUser;

res = oLdapUsers[0].Import("3388192345""voicemailusertemplate",out oImportedUser);

 

if (res.Success == false)

{

    Console.WriteLine("Failed importing user:"+res);

    return;

}

 

Console.WriteLine("User imported:"+oImportedUser.ToString());

 

You can, of course, also use the static method to import a new imported user if you happen to know the pkid and alias of the user to import – this is pretty unusual though, it’s much easier to find the user as we did above and them import them off the instance method which has the alias, first/last name and pkid values already populated for you and you only have to provide the extension and the alias template.

Note that the option to pass back out the FullUser object on the import used in the above sample is optional there.  If you’re just importing users en mass and don’t need to get a user object back out, you can do that and save that round trip HTTP fetch needed to fill in the user properties.

As a side node – users created via import from an LDAP source will behave like any other user you create directly.  Specifically you can edit their first/last names and such and the API (and by extension the SDK) will happily allow for that.  However when the LDAP sync takes place next time any values “owned” by the LDAP integration will be overwritten.  Just be aware that the API does not prevent you from editing these fields on local users.  You can tell a user is LDAP synched by checking the LdapCcmPkid property on the UserFull instance – it will not be empty in the case of an LDAP integrated user.

One last note, be careful about alias conflicts.  The alias from LDAP may very well conflict with another user in Connection’s database and the import will fail.  Be sure to check your error reasons during import, particularly if you are working with a mix of locally created users and users imported from an LDAP provider.

Messages

To access messages for either review or sending on behalf of other users the account you log into Unity Connection with must have the “Mailbox Access Delegate Account” role assigned to it.  If you attempt to send a message from a user or review messages for a user with an account that does not have this role, Connection will return “authentication required” errors back.

Fetching Messages for a User

First, let’s look at fetching messages for a user – this is similar to fetching users from the directory but has a bit more sugar added to help with filtering, sorting and paging. 

 

//Fetch the unread and urgent messages sorted by the oldest first.  

//Gets the first 20 messages in the stack that match this criteria

res = UserMessage.GetMessages(connectionServer, oUser.ObjectId, out oMessages,1,20,

  MessageSortOrder.OLDEST_FIRST, MessageFilter.Read_False | 

  MessageFilter.Priority_Urgent);

 

The first thing to notice is the paging parameters are passed as simple integers – 1 and 20 in this case.  So the page number (0 gets count, 1 is the first page) is first, followed by the number to fetch per page.  Just like with items in the directory to total count of messages returned is stored in the WebCallResult class passed back so you can setup “x of y” type paging structures in your UIs easily.  If you do not pass these parameters the SDK defaults to 1 and 10 respectively.

Also note that you can set the sort order to sort by oldest first, newest first or urgent first (regardless of read state).  The default sort order if nothing is passed in here is newest messages first.  The filter options can be compounded by separating with me “or” operators – in the example above it’s fetching urgent unread messages.  There are a dozen different filters you can apply alone or together.  By default it uses the “none” filter to get all messages of all states and types.

There’s also the ability to get information about individual messages and fetch attachments (usually WAV files of course) off of them.  There’s static and instance versions of these but usually you’re creating a UserMessage class instance and working with that.  This example shows fetching the first 10 messages from a user’s inbox, outputting the top level details of each message along with the attachment count and then fetching the first attachment off each message that has at least one and dumping it to a local hard drive location:

 

//Fetch the messages for a user - the default for this method is to fetch

//the first page of results 10 at a time if you pass no additional parameters. 

List<UserMessage> oMessages;

res=UserMessage.GetMessages(connectionServer, oUser.ObjectId, out oMessages);

 

if (res.Success == false)

{

    Console.WriteLine("Failed fetching messages:"+res);

    return;

}

 

//iterate over all messages fetched returning top level information, attachment 

//count and then save the first attachment (if present) into c:\temp\ using

//the GUID of the message. 

foreach (var oMessage in oMessages)

{

    Console.WriteLine(oMessage.ToString());

                

    //get how many attachments are present in the message - usually 1

    int iCount;

    oMessage.GetMessageAttachmentCount(out iCount);

 

    Console.WriteLine("    Attachment count="+iCount);

 

    //Extract the first message attachment to a local file

    if (iCount > 0)

    {

//note that the MsgId includes a "0:xxx" tacked onto the beginning which

//is used for forwarding scenarios - Windows does not like colons in paths 

//so if you want to use this construction you must remove it

        string sFile = string.Format(@"c:\{0}.wav",oMessage.MsgId.Replace(“:”,””)); 

        res = oMessage.GetMessageAttachment(sFile , 0);

 

        if (res.Success == false)

        {

            Console.WriteLine("Failed saving attachment:"+res);

        }

    }

}

 

Most of the time you will be dealing with the inbox messages which is why the SDK defaults to that folder - however you can fetch messages from the deleted items folder and/or the sent items folder as well.  The following example shows fetching the first 10 deleted items messages and restoring the first one to the inbox as an unread message - this scenario might be encountered if a user accidentally deleted a message they didn't mean to for instance.  Remember that the user's class of service may be configured to not stored deleted messages in the deleted items folder - in which case the folder will always be empty.

 

//get deleted messages for user

List<UserMessage> oMessages;

res = UserMessage.GetMessages(oServer, oUser.ObjectId, out oMessages, 1, 10,

                                MessageSortOrder.NEWEST_FIRST, MessageFilter.None,

                                MailboxFolder.DeletedItems);

           

if (res.Success == false)

{

    Console.WriteLine("Failed to fetch deleted messages:"+res);

    return;

}

           

if (oMessages.Count < 1)

{

    Console.WriteLine("No deleted messages found for user");

    return;

}

 

UserMessage oMessage = oMessages.First();

 

//mark the message unread.

oMessage.Read = false;

res=oMessage.Update();

 

if (res.Success == false)

{

    Console.WriteLine("Failed to update read status of message:"+res);

    return;

}

 

//restore it to the inbox folder

res = oMessage.Restore();

if (res.Success == false)

{

    Console.WriteLine("Failed to restore deleted message:"+res);

    return;

}

Send a New Message Using Wav File

This example shows how to send a new voice message from the mailbox of a user to an SMTP address.  The SDK only uses SMTP as destination addresses since this is all that’s needed.  The API does support other address constructions such as target ObjectId strings accompanied by object type designations, but these are unnecessary in my opinion.  All addressable objects in Connection contain a usable SMTPAddress that can be used for addressing and this simplifies the interface quite a bit.  The CreateMessage methods both allow for multiple addressing targets that can be either “TO”, “CC” or “BCC” address types; however the address itself is always just a plain SMTP string.

All the properties of a message can be included as flags in the call – these include urgent, secure, private, dispatch, read and delivery receipts as well as an optional callerId instance which can be passed as NULL if you don’t want to include the caller ID details with the message.

This example flags a message for urgent and private as well as having the “c:\test.wav” file converted into raw PCM before being uploaded as a message attachment.

 

//fetch user with alias of "jlindborg" - we will be sending the message from his

//mailbox.

UserFull oUser;

res = UserBase.GetUser(out oUser, connectionServer, "", "jlindborg");

 

if (res.Success == false)

{

    Console.WriteLine("Could not find user in database by alias=jlindborg");

    return;

}

 

//create a recipient - you must include at least one and can include many.

MessageAddress oRecipient = new MessageAddress();

oRecipient.AddressType = MessageAddressType.TO;

oRecipient.SmtpAddress = "jsmith@lindborglabs.com";

 

 

//set the message for urgent and private.  Note that the callerId instance can be

//passed as null here if none is desired.

res = UserMessage.CreateMessageLocalWav(connectionServer, oUser.ObjectId,

    "Test Subject""c:\\test.wav"trueSensitivityType.Private , false

    falsefalsefalsenulltrue, oRecipient);

 

if (res.Success == false)

{

    Console.WriteLine("Error uploading voice message:" + res);

    return;

}

 

Console.WriteLine("Message sent");

 

Send a New Message Using Phone (CUTI)

As with greetings and voice names you can use CUTI for recording voice messages as well.  An alternative version of the CreateMessage method is provided to handle this use of the CUTI interface.  The advantage here is that you can record a voice message using the phone as a media device and the media never leaves the Unity Connection server itself.  It’s created there and “turned into” a voice message attachment right where it stands: no need to produce recorded media locally to the client or upload binary files to the server.  Particularly for mobile clients or for applications where media security is a concern this can be a real advantage.

In this example we’ll record the message using the phone with CUTI and address it to multiple targets – including both a remote user and CC’ing the message to the sending subscriber. 

 

//fetch user with alias of "jlindborg" - we will be sending the message from his

//mailbox.

UserFull oUser;

res = UserBase.GetUser(out oUser, connectionServer, "", "jlindborg");

 

if (res.Success == false)

{

    Console.WriteLine("Could not find user in database by alias=jlindborg");

    return;

}

 

//create a recipient

MessageAddress oRecipient = new MessageAddress();

oRecipient.AddressType = MessageAddressType.TO;

oRecipient.SmtpAddress = "jsmith@lindborglabs.com";

 

//CC the message to the sender

MessageAddress oCc = new MessageAddress();

oRecipient.AddressType = MessageAddressType.CC;

oRecipient.SmtpAddress = oUser.SmtpAddress;

 

//use the CUTI interface to leave a message using extension 1001.

PhoneRecording oPhone;

 

try

{

    oPhone = new PhoneRecording(connectionServer, "1001");

}

 

catch (Exception ex)

{

    Console.WriteLine("Failed to connect to phone extension:" + ex);

    return;

}

 

//record the message itself - ends when the user hits #

res = oPhone.RecordStreamFile();

if (res.Success == false)

{

    Console.WriteLine("Failed recording message:" + res);

    return;

}

 

//include caller ID details in the message

CallerId oCallerId = new CallerId();

oCallerId.CallerName = "Jeff Lindborg";

oCallerId.CallerNumber = "2065551234";

 

//convert the recorded stream into a voice message.

res = UserMessage.CreateMessageResourceId(connectionServer, oUser.ObjectId,

    "my subject", oPhone.RecordingResourceId,falseSensitivityType.Normal, 

    falsefalsefalsefalse, oCallerId, oRecipient, oCc);

 

 

if (res.Success == false)

{

    Console.WriteLine("Failed uploading message:"+res);

    return;

}

 

//hangup

oPhone.Dispose();

 

Console.WriteLine("Message sent");

 

Forwarding a Message With Intro

The SDK provide methods off the message object for easily forwarding a message with an introduction.  You can provide the recording for the introduction either via a WAV file on the local hard drive or using the CUTI interface to record it on the Connection server and reference it there.  You can also forward without an introduction by using the wav file call and passing a blank path instead.

This example grabs the first message from a user's inbox and forwards it to another user with an intro using a wav file recorded on the local hard drive.

 

//get inbox messages for user

List<UserMessage> oMessages;

res = UserMessage.GetMessages(oServer, oUser.ObjectId, out oMessages);

           

if (res.Success == false)

{

    Console.WriteLine("Failed to fetch deleted messages:"+res);

    return;

}

           

if (oMessages.Count < 1)

{

    Console.WriteLine("No deleted messages found for user");

    return;

}

 

//construct an address for the recipent to forward to

MessageAddress oAddress= new MessageAddress();

oAddress.AddressType = MessageAddressType.CC;

oAddress.SmtpAddress = "jsmith@testdomain.com";

 

//notice the special construction for handling urgent (priority) and private

//(sensitivity).  The SDK uses simple Boolean flags for these however the API uses

//more complex string values - use the enums provided for handling that.

UserMessage oMessage = oMessages.First();

 

res = oMessage.ForwardMessageLocalWav("FW:" + oMessage.Subject,

    oMessage.Priority == PriorityType.Urgent,oMessage.Sensitivity,

    oMessage.Secure, falsefalse"c:\\intro.wav"true, oAddress);

       

if (res.Success == false)

{

    Console.WriteLine("Failed to forward message:"+res);

}

Replying to a Message

The SDK provides a way to reply or to reply all to any message.  It will allow you to attempt the reply no matter what the senders information, it's up to you to make sure that makes sense.  The list of recipients is provided for review off the UserMessage class instance as is the FromSub boolean flag which indicates if the message was sent from a subscriber vs. the outside caller mailbox (which cannot be replied to).  The SDK will not stop you from replying to invalid addresses or the like - the mailbox you send the message on behalf of will simply get a NDR message for the failed attempt.  

The reply can be constructed using a wav file from the local file on the hard drive or via a stream recorded on the Connection server via CUTI, just as with forwarding or creating a new message.  The following chunk of code just does a simply reply to a message - all that's needed for a reply all is to pass the flag as true instead of defaulting to false.  In this case we will use the CUTI interface to record a reply using the phone.

 

//get inbox messages for user

List<UserMessage> oMessages;

res = UserMessage.GetMessages(oServer, oUser.ObjectId, out oMessages);

            

if (res.Success == false)

{

    Console.WriteLine("Failed to fetch deleted messages:"+res);

    return;

}

           

if (oMessages.Count < 1)

{

    Console.WriteLine("No deleted messages found for user");

    return;

}

 

//just use the first message in the list

UserMessage oMessage = oMessages.First();

 

//Establish a phone connection

PhoneRecording oPhone;

try

{

    oPhone = new PhoneRecording(oServer, "1002");

}

catch (Exception ex)

{

    Console.WriteLine("Failed to establish phone connection:"+ex);

    return;

}

 

//now record a reply over the phone

Console.WriteLine("Record your reply, press # to end.");

res = oPhone.RecordStreamFile();

 

if (res.Success == false)

{

    Console.WriteLine("Failed to record reply:"+res);

    return;

}

 

//reply to all recipients

res = oMessage.ReplyWithResourceId("RE:" + oMessage.Subject,

    oPhone.RecordingResourceId, oMessage.Priority == PriorityType.Urgent,

    oMessage.Sensitivity,oMessage.Secure, falsefalsetrue);

       

if (res.Success == false)

{

    Console.WriteLine("Failed to forward message:"+res);

    return;

}

Console.WriteLine("Reply sent!");

           

//hang up

oPhone.Dispose();

 

Deleting Messages and the Deleted Items Folder

The UserMessage class has a delete method off it just as many other class definitions do, however the UserMessage version includes a boolean flag that lets you "hard" vs. "soft" delete a message.  "Soft" deletes will leave a copy of the message in the deleted items folder provided the user's class of service is configured to do that.  A "hard" delete will never put a copy in the deleted items folder regardless of what the class of service is configured to do.

There's also a static method to clear the deleted items folder entirely.  The following code chunk shows a message being "hard" deleted and then the deleted items folder being cleared out.

 

//hard delete the message

res = oMessage.Delete(true);

if (res.Success == false)

{

    Console.WriteLine("Failed to delete message:"+res);

}

 

//clear all messages from the deleted items folder

res =UserMessage.ClearDeletedItemsFolder(oServer, oUser.ObjectId);

 

if (res.Success == false)

{

    Console.WriteLine("Failed to clear the deleted items folder");

}

 

Helper Methods

Finally there are some helper functions off the UserMessage class that can be useful when dealing with messages – notably the static methods for converting milliseconds from 1970 into local time and vice versa.  The details on a message object returned from Connection have the ArrivalTime and ModificationTime stored as longs – this is milliseconds from midnight on 1/1/1970 – it’s a common format for storing date/times, particularly in Linux land.  You can use the “ConvertFromMillisecondsToTimeDate” method on the static UserMessage class to take that long and convert it either into GMT or into local time on the box you’re running on.  Similarly you can use the “ConvertFromTimeDateToMilliseconds() to go in the other direction.

User Templates

[Editing/adding requires Unity Connection 10.0 or later]

When creating a new user you are required to provide a user template’s alias to do it.  Users and their related sub objects constitute hundreds of individual properties that span many sub collections and templates allow administrators to setup default behavior and settings for many, many properties up front.  The idea that you could manage all of them on your own with each create is, of course, highly unrealistic. 

A user template is very much like a user but has somewhat fewer properties.  Anything that is only filled in for a specific user instance such as their extension or their SMTP address for instance are not present on the user template.  Outside of that they behave very similar to a user including their sub objects such as their primary call handler with associated transfer and greeting rules etc.  As such I’m not going to cover all that again for templates; just the basics and you can review the User section for more details on how the object model hangs together for users as a whole.

Finding and Fetching User Templates

You can always count on at least one user template to be in the system since the installer creates one and marks it undeletable.  This is the “voiceMailUserTemplate” alias and should always be available to you.  In fact in many of my examples I simply use this rather than fetching the list of available templates and selecting one.  Nothing wrong with that but typically you’ll want to provide users with a list of templates to choose from when creating a new user.  The SDK provides the usual list fetching methods accompanied by easy paging capabilities if there are very large numbers of templates on your system.  This example simply selects the first 20 (default count limit) and dumps the top level information for each out to the console:

 

List<UserTemplate> oTemplates;

res = UserTemplate.GetUserTemplates(_server, out oTemplates);

if (res.Success == false)

{

    Console.WriteLine("Failed to fetch templates:"+res);

    return;

}

 

foreach (var oTemplate in oTemplates)

{

    Console.WriteLine(oTemplates.ToString());

}

 

You can, of course, include filter/sort clauses as optional parameters to the GetUserTemplates method as you can with most other GetXXX calls in the SDK.  Typically for templates such filtering is not necessary; however it’s provided for completeness and consistency.

This example shows how to fetch a specific template by alias instead of fetching them all as a list:

 

UserTemplate oTemplate;

res = UserTemplate.GetUserTemplate(_server, """myTemplate"out oTemplate);

 

if (res.Success == false)

{

    Console.WriteLine("Failed to find template:"+res);

    return;

}

 

Console.WriteLine("Template found:"+oTemplate.ToString());

 

Creating, Updating and Deleting User Templates

This example shows how to create a new user template, edit a few properties in it and then turn around and delete it.  Not a terribly practical example but you get the idea:

 

UserTemplate oTemplate;

 

res = UserTemplate.AddUserTemplate(_server, "voicemailusertemplate",

    "myNewTemplate","New Template Name",nullout oTemplate);

 

if (res.Success == false)

{

    Console.WriteLine("Failed to create template:"+res);

    return;

}

 

Console.WriteLine("Template created:"+oTemplate.ToString());

 

oTemplate.DisplayName = "Updated Display Name";

oTemplate.Department = "Engineering";

oTemplate.PromptSpeed = 150;

res = oTemplate.Update();

if (res.Success == false)

{

    Console.WriteLine("Failed to update template:"+res);

    return;

}

 

Console.WriteLine("Updated template");

 

res = oTemplate.Delete();

if (res.Success == false)

{

    Console.WriteLine("Failed to delete template:"+res);

    return;

}

            

Console.WriteLine("Template deleted");

One thing to notice here: Yes, when you create a new user template you must provide the alias of an existing user template!  In our example above we used the system created “voicemailusertemplate” template since we know it’s always there.  That may seem a little circular but again, there is a LOT of user properties to be carting around, and starting with a template of defaults is very handy.

Global Users

The GlobalUser class behaves very much like the UserBase class with respect to finding, fetching and iterating users.  The primary difference is you don’t create, edit or delete global users, you can only find them.  The global user collection represents a small amount of data for all users across all Connection servers in your network and is used for addressing purposes for the most part.  All users created on all Connection servers replicate around to all other Connection servers and show up in the global users collection eventually. 

This can be used when addressing messages to other users on the network for validation or for more complex scenarios such as cross server login or the like.  For an example on how to use global users and locations to “hop” to different Connection servers in a digital network to get to the home server for remote servers all over your network, see the Locations topic.

System Contacts

System contacts are user objects that do not have mailboxes on Unity Connection and can be used to route calls to destinations and such without using a seat license (they are free).  At the time of this writing the REST API does not support deleting these objects, however you can create and edit them.  This example just runs through creating a contact, adding a voice name and updating some properties on it:

 

//create a new system contact using the default system contact template

//created by install

Contact oContact;

res=Contact.AddContact(oServer, "systemcontacttemplate", "Test Contact",

                    "Test","Contact", "testcontact", null, out oContact);

 

 

if (res.Success == false)

{

    Console.WriteLine("Failed to create new contact:"+res);

    return;

}

 

//add a voice name to the contact from a wav file

res = oContact.SetVoiceName("c:\\voiceName.wav", true);

if (res.Success == false)

{

    Console.WriteLine("Failed to set contact voice name:"+res);

    return;

}

 

//update some contact properties

oContact.DisplayName = "New display name";

oContact.ListInDirectory = true;

           

res = oContact.Update();

if (res.Success == false)

{

    Console.WriteLine("Failed to update contact:"+res);

    return;

}

Call Handlers

Call handlers come in two flavors: a “system call handler” which are the ones you see in the CUCA web interface in the system call handlers section such as the “opening greeting” and “say goodbye” handlers.  Then there are “primary call handlers” which are tied to a user with a mailbox.  These have the same functionality but the primary handlers are exposed in the user pages in the CUCA web interface and not on their own.  Much of the call processing functionality is in the call handler which is why many of the tasks you do on a user you are actually doing on their associated primary call handler.  Understanding this makes the model much easier to grasp.

The key piece to understand is that fetching call handlers will, by default, include all call handlers - both system and primary - which may not always be what you want.  When fetching call handlers it's a good idea to filter by name (or at least by non primary flag - which we'll see here in a minute).

Creating and Deleting Call Handlers

First, let's just create a simple system call handler here.  You never "create" a primary call handler, they are created automatically when you create a user with a mailbox (admin users do not have call handlers or mailboxes).  One wrinkle with system call handlers is when you create one you must pass in the ObjectId (GUID) for a call handler template as part of the creation.  With User creation you can use the alias of the template which gets passed on the URI but that's not allowed for display name fields (call handlers do not have an alias, but display name must be unique).  So before creating a new handler you must first fetch the template you wish to use.  For more details on managing call handler templates, see the Call Handler Template section.

 

//First, we need to fetch a call handler template to work with.

//This is the default template that should be present on any system.

CallHandlerTemplate oTemplate;

res = CallHandlerTemplate.GetCallHandlerTemplate(out oTemplate, connectionServer,

"","System Call Handler Template");

 

if (res.Success == false)

{

    Console.WriteLine("Failed fetching handler template:"+res);

    return;

}

 

//Create a system call handler with no extension - the name must be

//unique among all handlers on the system or this will fail

CallHandler oHandler;

res = CallHandler.AddCallHandler(connectionServer, oTemplate.ObjectId,

"My New Handler", "", null,out oHandler);

 

if (res.Success == false)

{

    Console.WriteLine("Failed creating handler:"+res);