/myVoices

comment(s) 

.NET My Services (Hailstorm) and VoiceXML (VXML)

http://www.brains-N-brawn.com/myVoices 1/18/02 casey chesnut

Introduction

Can you say bleeding edge. myVoices is an ASP.NET VoiceXml interface that integrates to .NET My Services.

Came up with the idea because I have been specializing in Web Services and Wireless Development ... throw in 'idle handedness' because I am not currently on contract (hint hint -> resume) and I decided to combine the 2. Had previously taught myself how to do VoiceXml in .NET back in November, and had been kicking around the .NET My Services SDK since xmas. The results of those previous works can be found here:

Used these wrox 'early adopter' books, the MS press .NET My Services specification, and this Sams VoiceXml book to teach myself

   

Usage

This is a typical scenario of a user interacting with myVoices

    1. USER: gets out their cell phone and dials a phone # to access the app:
      1. BeVocal Phone # 1.877.33VOCAL (86225)
    2. APP: Please enter your pin.
    3. USER: 1 2 3 4 (I typically key this in)
    4. APP: What is your Account ID?
    5. USER: 6 6 6 1 2 3 4 (I typically speak this out loud ... easy to remember after a couple drinks!)
    6. APP: What is your Passport User ID?
    7. USER: 3 8 1 9 (this is the only id i have provisioned for, any other # will fail)
    8. APP: Prompts with a menu of applications
      1. Say Time or Press 1 (this just returns the time on my server in TX)
      2. Say Jokes or Press 2 (this goes to my 1st VoiceXml app which tells knock knock jokes)
      3. Say myVoices or Press 3 (this is the integration with .NET My Services)
    9. USER: myVoices
    10. APP: Prompts with the myVoices menu
      1. Say Favorites or Press 1 (myFavoriteWebSites)
      2. Say Lists or Press 2 (myLists)
      3. Say Contacts or Press 3 (myContacts)
      4. Say Calendar or Press 4 (myCalendar)
      5. Say Inbox or Press 5 (myInbox) - NOTE: not implemented in this drop of SDK
    11. USER: Lists
    12. APP: Choose your list (These are your .NET myLists)
      1. ToDo
      2. Groceries
    13. USER: To Do
    14. APP: Your items are workout, code, find work.
    15. Repeat Step 8 and choose a different myService

NOTE: There is little-to-no exception handling in this, happy-day scenario only, so it is brittle and easy to break

NOTE: You can follow the above steps and call it using your own phone

Recording

Radio Shack has a slick little device for $20 that will let you record cell phone conversations. That along with a hands-free headset and a digital voice recorder I have ... this is a recording of me interacting with the myVoices.

recording.wma - 32 kbps - 700K - 2 min 50 sec

recording.mp3 - 32 kbps - 700K - 2 min 50 sec

OR read by Microsoft agents here (pure silliness, a script reader I wrote over a year ago, in old school ASP)

NOTE: I like how it says 'point NET' instead of 'dot NET', as well as the way it reads my phone # (that can be fixed)

NOTE: the pauses are because my devbox is not that speedy, and the BeVocal account im using is a FREE developer account. Also, although my server and devbox are setup on a fractional T1 (in my apartment!!! ... its why I live there), my bandwidth is getting demolished because my winamp plugin (4.5 out of 5 rating at winamp.com) has gotten so popular, that it gets about 250k hits a day! I am going to shut it down this weekend and release the source for somebody else to pickup; maybe I'll rewrite it in C# when winamp 3 comes out, and build it with a p2p model to distribute the bandwidth ... that would be so cool, because then people would be viewing skins that other people have online, not just the ones I was hosting

Prerequisites

To do this, the following were used/needed:

Components

This layout should help you understand the different pieces of code we will be talking about later on, and what all is happening

Physically, my cell phone could be anywhere. Have no clue where the BeVocal Gateway is, nor do I care. The web pages from the /vxml app are on my server about 15 feet away, while the myVoices application is on my devbox 15 inches away (if I get bored, I will move this piece over to my server and update the Web References). The .NET My Services are also on my devbox, because they are running locally for the technical preview; but ultimately they will be somewhere in MS land

Pages

Different pages the application is composed of, which we will talk about in more detail

The 1st 3 pages (top-left), with the /vxml in them, are all from my previous VoiceXml misadventures (see the link above). The only change is that the MENU page now has a 3rd option which redirects to the PUID page of myVoices. PUID asks the user for their Passport User ID (which is always 3819 in this case). Then it redirects to the SERVICES page which has a menu for selecting which .NET My Service you want to interact with. These 5 (FAVORITES, LISTS, CONTACTS, CALENDAR, and INBOX) were the logical ones I could think of. INBOX is not implemented, because it is not implemented in the .NET My Services SDK, which is odd, because it seems like it would be much simpler than the myCalendar service

Process

We are almost to the good stuff, I think this is the last section for you to suffer through. Regardless, this is how I worked on it. It is currently 11 PM 1/18/02, and believe it or not, I came up with this whole idea yesterday around 4 PM. So if you have any problems with my code, or the projects incompleteness, take into consideration that the whole software lifecycle (minus documentation, which is being done now) took less than 24 hours; including minimal sleep and a couple trips to the gym. Development ended up being relatively easy, much easier than expected. The only tricky part being that ASP.NET does not natively support VoiceXml, as well as the .NET MyServices SDK being a technical preview.

First, I have a test.vxml page in my project, which is nothing but an XML page in VoiceXml format. I would make a test page for each of the pages above, and then test to see that it interacted properly as a VoiceXml doc. To test a VXML, there are 3 steps. 1st, make sure it is well-formed XML, by opening in IE. 2nd, make sure it is valid VXML ... you can use Motorolas Mobile ADK for Voice (see my previous /vxml) which lets you test the page with a keyboard or microphone. 3rd, you can call in with your phone, to make sure the Voice Gateway can handle it; although I did not do this until the end ... and felt the Mobile ADK was good enough of a test until final testing. So with this test.vxml page I now had the presentation mocked up, which I would transfer to a corresponding ASP.NET page. You have to rip all the auto-generated HTML out of the aspx pages, the code-behind page declaration can stay, since it will not get rendered, and the page might need to interact with the codeBehind

Second, I had to insert some data into the .NET My Services to eventually pull out. NOTE: myVoices only reads from .NET myServices; programatically it is cake to insert data, but doing this over the phone would be challenging and quite frustrating. Of course, it took me a couple steps to get data into .NET My Services as well. 1st, I would create a my*Insert.xml file and use their command line hsPost.exe to try and insert the data. This utility was great because it gives good error info as to what is wrong with the format of your request; e.g. how it does not match the schema, elements being in the wrong order, or namespaces being incorrect. So between the schema, hsPost error info, and brute force trial-and-error I would get some data inserted. Which brings up how hsPost sucks, in that it is command line. So once I had a valid insert, I would take that xml, and shove it in my hsPostWeb ASP.NET app. Its an app I created a couple weeks ago, which is basically a web version of hsPost ... duh. So I would modify the xml, and was able to work with it more easily in this environment. After the final insert statement was created, I would do the same process for querying the data, and then for deleting. When I could do these steps, I would clean out the data, and do one final insert of clean data

Third, now that there is data to go against, I could pull it out. Since all of the work in step 2 was XML, I felt like going back to old-school object oriented development (have you heard the term schema-based development yet ... i like it). This was my 1st foray into interacting with the .NET My Services and the lightweight objects that VS.NET creates through the WSDL. I must say it is way too easy. Keep kicking ass MS. Since, I already knew what the queries needed to look like in XML, it was trivial to recreate that as an object which would get serialized to be the same ... especially with intellisense

Fourth, now that the data was getting pulled out of the .NET My Services, all I had to do was serialize it into the same XML format that was needed for the VXML pages I had already mocked up. You'll see this code later on, in which I create an XML doc, add the appropriate elements, attributes, and values; and then just grab that XML and render directly into the middle of the aspx pages, which render as VXML, instead of HTML

Finally, I tested. 1st, with IE to make sure I had valid XML. 2nd, with the Mobile ADK, which I cannot say how invaluable it is for developing in VoiceXml. The only complaint is that it depends on a Java (spit) runtime, which I begrudgingly installed with disgust. I made an ugly face when I installed it, trust me. I am making an ugly face now ... 3rd, I busted out my cell phone, called up BeVocal, and started talking to .NET My Services :)

Development

Now I will go step-by-step and page-by-page through what is happening in the myVoice application. Developed the pages in order of perceived difficulty. Favorites being the easiest, then Lists, then Contacts, and finally Calendar as the hardest. Inbox being unimplementable (you are allowed to make up words as a software engineer).

GlobalPage.cs

Made all .aspx pages inherit from GlobalPage.cs. GlobalPage inherits from System.Web.UI.Page as expected. I use this page to avoid some code duplication that I would have across all the pages. It has no presentation associated with it. Have used this technique since JSP (spit)

puid.aspx | puid.vxml 

Has no codebehind associated with it, it is pure VXML. All it does is prompt the user for their Passport User ID, and then posts this data to services.aspx

<?xml version="1.0" ?>
<vxml version="1.0">
	<form id="form">
		<field name="puid" type="digits"> 
		   <prompt> 
		      Welcome to my Voice. What is your Passport User I D? 
		   </prompt> 
		   <filled> 
		      <submit next= "services.aspx" method="post" namelist= "puid" />
			</filled>
			<noinput>
			   No input. 
			</noinput> 
			<nomatch> 
			   No match.
			</nomatch>
			<help> 
			   Help. What is your Passport User I D?
			</help>
		</field>
	</form>
</vxml>
services.aspx | services.aspx.cs | services.vxml

On pageLoad, its codeBehind sets a session variable "puid" using the value passed from puid.aspx. This PUID is needed later on

if(Request["puid"] != null)
{
	Session["puid"] = Request["puid"];
}

After that, the page is just a menu to direct the user to one of the following my*Service pages

<vxml version="1.0">
	<var name="puid" type="number"/>
	<menu>
		<prompt>
			Choose.
			<enumerate />
		</prompt>
		<choice dtmf="1" next="favorites.aspx">
			Favorites</choice>
		<choice dtmf="2" next="lists.aspx">
			Lists</choice>
		<choice dtmf="3" next="contacts.aspx">
			Contacts</choice>
		<choice dtmf="4" next="calendar.aspx">
			Calendar</choice>
		<choice dtmf="5" next="inbox.aspx">
			Inbox</choice>
		<noinput>
			No input, Choose.
			<enumerate />
		</noinput>
		<nomatch>
			No match, Choose.
			<enumerate />
		</nomatch>
		<help>
			Help, Choose.
			<enumerate />
		</help>
	</menu>
</vxml>

Favorites

  1. USER: Favorites
  2. APP: Choose a web site: brains-N-brawn, D N U G
  3. USER: brains-N-brawn
  4. APP: The url is http://www.brains-N-brawn.com

The requests to insert, query, and delete data look like this:

<insertRequest select="/" xmlns="http://schemas.microsoft.com/hs/2001/10/core">
   <favoriteWebSite xmlns="http://schemas.microsoft.com/hs/2001/10/myFavoriteWebSites">
      <cat ref="system" />
      <title xml:lang="en">brains-N-brawn.com</title>
      <url>http://www.brains-N-brawn.com</url>
   </favoriteWebSite>
</insertRequest>

<queryRequest xmlns="http://schemas.microsoft.com/hs/2001/10/core">
   <xpQuery select="/m:myFavoriteWebSites/m:favoriteWebSite" />
</queryRequest>

<deleteRequest select="/m:myFavorites/m:favoriteWebSite" xmlns="http://schemas.microsoft.com/hs/2001/10/core" />
favorites.aspx | favorites.aspx.cs | favorites.vxml

Here is what happens in the codeBehind on pageLoad:

hsProxy.hsProxy hsp = new hsProxy.hsProxy();
int puid = GetPuid(); 
myFavoriteWebSites myFav = (myFavoriteWebSites) hsp.GetMyService(typeof(myFavoriteWebSites), puid);

These lines create an instance of my hsProxy class library. It also gets the PUID from the session which we saved earlier. GetPuid() is a method of the GlobalPage class. A PUID and the my*Service type are needed by the ServiceLocator object to get a proxy object to make SOAP requests against a my*Service. GetMyService() is just a small method in my hsProxy class that keeps the myVoice application from having to know about the ServiceLocator project. In the end, we have an instance of a proxy object to make calls against the .NET myFavorites service

queryRequestType queryRequest = new queryRequestType();
xpQueryType xpQuery = new xpQueryType();
xpQuery.select="/m:myFavoriteWebSites/m:favoriteWebSite";
queryRequest.xpQuery = new xpQueryType[]{xpQuery};
queryResponseType queryResponse = myFav.query(queryRequest);
queryResponseTypeXpQueryResponse xqr = queryResponse.xpQueryResponse[0];

This block creates an instance of queryRequest type. It then creates an xpQueryType with an xPath expression and associates it with the queryRequest. Finally the myFav proxy object, has its query method called with the query instance to get the response. If you serialized the queryRequest object, it would look very similar to the XML queryRequests used against hsPost.exe above. Note the xPath expression '/m:myFavoriteWebSites/m:favoriteWebSite' will return every favoriteWebSite the user has in the myFavorites service

XmlDocument xd = new XmlDocument();
XmlElement root = xd.CreateElement("root");
xd.AppendChild(root);
int dtmf = 1;
foreach(favoriteWebSiteType fwst in xqr.Items)
{
	XmlElement xe = xd.CreateElement("option");
	XmlAttribute xa1 = xd.CreateAttribute("dtmf");
	xa1.Value = dtmf.ToString();
	dtmf++; //iter for next element
	XmlAttribute xa2 = xd.CreateAttribute("value");
	xa2.Value = fwst.url;
	xe.Attributes.Append(xa1);
	xe.Attributes.Append(xa2);
	xe.InnerText = fwst.title[0].Value;
	root.AppendChild(xe);
}
result = root.InnerXml;

This block is used to iterate through the results, and create an Xml Fragment that can be serialized into the appropriate VoiceXml format. The above will serialize into the 2 <option> elements that appear below within the middle of the document. The 'result' string, is bound to the page through this syntax <%=GetResult()%>. The GetResult() method is part of the GlobalPage class

<vxml version="1.0">
	<form id="form">
		<field name="url">
			<prompt>
				Choose a web site <enumerate />
			</prompt>
			<option dtmf="1" value="http://www.brains-n-brawn.com">brains-n-brawn</option>
			<option dtmf="2" value="http://www.dnug.net">D N U G</option>
			<filled>
				<prompt>
					the url is <value expr="url" />
				</prompt>
				<goto next="services.aspx" />
			</filled>
		</field>
	</form>
</vxml>

Lists

  1. USER: Lists
  2. APP: Choose a list: To Do, Groceries
  3. USER: To Do
  4. APP: The items in the list are: workout, find work, code

The requests to insert, query, and delete data look like this:

<insertRequest select="/" xmlns="http://schemas.microsoft.com/hs/2001/10/core">
   <list idName="to do" xmlns="http://schemas.microsoft.com/hs/2001/10/myLists">
      <title xml:lang="en">to do</title>
   </list>
   <list idName="grocery" xmlns="http://schemas.microsoft.com/hs/2001/10/myLists">
      <title xml:lang="en">grocery</title>
   </list>
</insertRequest>

NOTE: Did multiple inserts with one insertRequest above. Also, NOTE that list and item elements live at the same level in the schema, so that you have to associate them with an ID, instead of with heirarchy, like the other services demonstrated here. When I inserted the above, the following IDs were returned for each list respectively. So I used those same IDs when inserting items into the lists, at least, that was my best guess on what should be done

<m:list idName="to do" id="D0FF71AF-74F4-4D60-A1E5-718A5B5B04A8">
<m:list idName="grocery" id="9CA876CA-0B8C-454B-B6F7-EDA428FDC441">

<insertRequest select="/" xmlns="http://schemas.microsoft.com/hs/2001/10/core">
   <item xmlns="http://schemas.microsoft.com/hs/2001/10/myLists">
      <title xml:lang="en">workout</title>
      <listRef order="1" ref="D0FF71AF-74F4-4D60-A1E5-718A5B5B04A8"/>
   </item>
   <item xmlns="http://schemas.microsoft.com/hs/2001/10/myLists">
      <title xml:lang="en">get a job</title>
      <listRef order="2" ref="D0FF71AF-74F4-4D60-A1E5-718A5B5B04A8"/>
   </item>
   <item xmlns="http://schemas.microsoft.com/hs/2001/10/myLists">
      <title xml:lang="en">code</title>
      <listRef order="3" ref="D0FF71AF-74F4-4D60-A1E5-718A5B5B04A8"/>
   </item>
   <item xmlns="http://schemas.microsoft.com/hs/2001/10/myLists">
      <title xml:lang="en">milk</title>
      <listRef order="1" ref="9CA876CA-0B8C-454B-B6F7-EDA428FDC441"/>
   </item>
   <item xmlns="http://schemas.microsoft.com/hs/2001/10/myLists">
      <title xml:lang="en">cereal</title>
      <listRef order="2" ref="9CA876CA-0B8C-454B-B6F7-EDA428FDC441"/>
   </item>
   <item xmlns="http://schemas.microsoft.com/hs/2001/10/myLists">
      <title xml:lang="en">v8 juice</title>
      <listRef order="3" ref="9CA876CA-0B8C-454B-B6F7-EDA428FDC441"/>
   </item>
</insertRequest>

<queryRequest xmlns="http://schemas.microsoft.com/hs/2001/10/core">
   <xpQuery select="/m:myLists/m:list" />
   <xpQuery select="/m:myLists/m:item[./m:listRef/@ref='9CA876CA-0B8C-454B-B6F7-EDA428FDC441']" />
</queryRequest>

<deleteRequest select="/m:myLists/m:list" xmlns="http://schemas.microsoft.com/hs/2001/10/core" />
<deleteRequest select="/m:myLists/m:item" xmlns="http://schemas.microsoft.com/hs/2001/10/core" />
lists.aspx | lists.aspx.cs | lists.vxml

This page is generated the same as favorites.aspx. The only difference being that we use a myLists proxy object, the xPath expression changes, and the resulting lightweight objects we iterate through relate to the myLists schema. The value attrib's 'value' is the ID that is used to find which items belong to which list. That value is posted to the items.aspx page

<vxml version="1.0">
	<form id="form">
		<field name="list">
			<prompt>
				Choose a list <enumerate />
			</prompt>
			<option dtmf="1" value="36958F80-6A39-4A1C-8110-029CFEA84A43">to do</option>
			<option dtmf="2" value="DECDFEA0-8B9A-4402-9C90-0D7AD8B3AB20">grocery</option>
			<filled>
				<submit next="items.aspx" method="post" namelist="list" />
			</filled>
		</field>
	</form>
</vxml>
items.aspx | items.aspx.cs | items.vxml

This page receives the ID for the list, and uses the myLists proxy object to query with this xPath expression: '/m:myLists/m:item[./m:listRef/@ref='" + listId + "']'. This will only return the items for the specified list. Also, instead of serializing to <option> elements, they are serialized to <prompt> elements, which simply read them out loud, instead of being an option for a user response

<vxml version="1.0">
	<form id="form">
		<block>
			<prompt>
				The items in the list are
			</prompt>
			<prompt>workout</prompt>
			<prompt>get a job</prompt>
			<prompt>code</prompt>
			<goto next="services.aspx" />
		</block>
	</form>
</vxml>

Contacts

  1. USER: Contacts
  2. APP: What is the 1st letter of their first or last name?
  3. USER: 'c'
  4. APP: Choose a contact: Casey Chesnut
  5. USER: Casey Chesnut
  6. APP:  Their info is: email: fake@fake.com phone: 2142821556

NOTE: it reads my phone # as 2 billion 142 million 821 thousand and 556. There is a VoiceXml <sayas> element that could be used to make it read it as a phone number

The requests to insert, query, and delete data look like this. This one ended up being tricky because the namespaces on the elements kept varying from being in the myContacts schema, to the myProfile schema. Programatically, it is easy

<insertRequest select="/" xmlns="http://schemas.microsoft.com/hs/2001/10/core">
   <contact synchronize="no" xmlns="http://schemas.microsoft.com/hs/2001/10/myContacts">
      <name>
         <givenName xml:lang="en" xmlns="http://schemas.microsoft.com/hs/2001/10/myProfile">casey</givenName>
         <surname xml:lang="en" xmlns="http://schemas.microsoft.com/hs/2001/10/myProfile">chesnut</surname>
      </name>
      <emailAddress>
         <email xmlns="http://schemas.microsoft.com/hs/2001/10/myProfile">fake@fake.com</email>
      </emailAddress>
      <screenName>
         <name xml:lang="en" xmlns="http://schemas.microsoft.com/hs/2001/10/myProfile">kcchesnut@hotmail.com</name>
      </screenName>
      <telephoneNumber>
         <nationalCode xmlns="http://schemas.microsoft.com/hs/2001/10/core">214</nationalCode>
         <number xmlns="http://schemas.microsoft.com/hs/2001/10/core">2821556</number>
      </telephoneNumber>
   </contact>
</insertRequest>

//value
<queryRequest 
  xmlns="http://schemas.microsoft.com/hs/2001/10/core" 
  xmlns:mc="http://schemas.microsoft.com/hs/2001/10/myContacts"
  xmlns:mp="http://schemas.microsoft.com/hs/2001/10/myProfile">
     <xpQuery select="/mc:myContacts/mc:contact" />
     <xpQuery select="/mc:myContacts/mc:contact[./mc:name/mp:surname='chesnut']" />
</queryRequest>

NOTE: the following query is what I should have done in my code, but the .NET My Services SDK does not currently support searches by substring. So in my code, I query for all contacts, and then programatically iterate through them, doing the search myself

//substring
<queryRequest 
  xmlns="http://schemas.microsoft.com/hs/2001/10/core" 
  xmlns:mc="http://schemas.microsoft.com/hs/2001/10/myContacts"
  xmlns:mp="http://schemas.microsoft.com/hs/2001/10/myProfile">
     <xpQuery select="/mc:myContacts/mc:contact[substring(string(./mc:name/mp:surname),1,1) = 'c']" />
</queryRequest>

<deleteRequest select="/m:myContacts/m:contact" xmlns="http://schemas.microsoft.com/hs/2001/10/core" />
contacts.aspx | contacts.vxml

This page has no codeBehind associated with it. It is pure VXML. All it does is prompt the user, and then get a single letter response, and then post that to the next page

<vxml version="1.0">
	<form id="form">
		<field name="letter">
			<prompt>
				What is the 1st letter of their first or last name?
			</prompt>
			<grammar type="application/x-gsl">
				[a b c d e f g h i j k l m n o p q r s t u v w x y z]</grammar>
			<filled>
				<submit next="rolodex.aspx" method="post" namelist="letter" />
			</filled>
		</field>
	</form>
</vxml>
rolodex.aspx | rolodex.aspx.cs | rolodex.vxml

This page receives the letter, and here is where it should do the substring search query noted above. But since that does not currently work, and hacking is fun, I just grab all the contacts, iterate through them, and toss out the ones that do not start with the letter the user specified. Instead of just being a <prompt> element, I use <option> because their could be multiple people that have a first or last name that starts with the same name obviously. Wanted this one to do a search though, unlike the Favorites example above, to make it more complicated, and you certainly would not return an entire person's contact list

<vxml version="1.0">
	<form id="form">
		<field name="info">
			<prompt>
				Choose a contact<enumerate />
			</prompt>
			<option dtmf="1" value="email: fake@fake.com phone: 2142821556">casey chesnut</option>
			<filled>
				<prompt>
					their info is <value expr="info" />
				</prompt>
				<goto next="services.aspx" />
			</filled>
		</field>
	</form>
</vxml>

NOTE: not sure if this is the case ... but I think VoiceXML has a tag that will let you make a phone call. So at this point, when you have the contact's phone #, you should say 'CALL' or something to that effect, and then it will end the VoiceXML session and call that person.

Calendar

  1. USER: Calendar
  2. APP: Your appointments are: dallas .NET user group meeting on 1/17/2002, get oil changed on 1/18/2002, haircut on 1/19/2002, hot date on 1/20/2002

NOTE: the search dates is hardcoded to always return these. It should really allow the user to specify dates or a date-range somehow, but that would just be grunt work repeating what we have already learned, so I dodged it

The requests to insert, query, and delete data look like this:

<insertRequest select="/" xmlns="http://schemas.microsoft.com/hs/2001/10/core">
   <event xmlns="http://schemas.microsoft.com/hs/2001/10/myCalendar">
      <body>
         <title xml:lang="en">get oil changed</title>
         <startTime>2002-01-18T15:23:17.0000000-07:00</startTime>
         <endTime>2002-01-18T16:23:17.0000000-07:00</endTime>
      </body>
   </event>
   <event xmlns="http://schemas.microsoft.com/hs/2001/10/myCalendar">
      <body>
         <title xml:lang="en">dallas .NET user group meeting</title>
         <startTime>2002-01-17T15:23:17.0000000-07:00</startTime>
         <endTime>2002-01-17T16:23:17.0000000-07:00</endTime>
      </body>
   </event>
   <event xmlns="http://schemas.microsoft.com/hs/2001/10/myCalendar">
      <body>
         <title xml:lang="en">haircut</title>
         <startTime>2002-01-19T15:23:17.0000000-07:00</startTime>
         <endTime>2002-01-19T16:23:17.0000000-07:00</endTime>
      </body>
   </event>
   <event xmlns="http://schemas.microsoft.com/hs/2001/10/myCalendar">
      <body>
         <title xml:lang="en">hot date</title>
         <startTime>2002-01-20T15:23:17.0000000-07:00</startTime>
         <endTime>2002-01-20T16:23:17.0000000-07:00</endTime>
      </body>
   </event>
</insertRequest>

//generic
<queryRequest xmlns="http://schemas.microsoft.com/hs/2001/10/core">
   <xpQuery select="/m:myCalendar/m:event" />
</queryRequest>

NOTE: this is a myCalendar domain specific getCalendarDaysRequest, and not the generic queryRequest that we have used for all the other services

//command line
<m:getCalendarDaysRequest 
  xmlns:hs="http://schemas.microsoft.com/hs/2001/10/core" 
  xmlns:m="http://schemas.microsoft.com/hs/2001/10/myCalendar">
     <m:startTime>2002-01-14T15:23:17.0000000-07:00</m:startTime>
     <m:endTime>2002-01-21T16:23:17.0000000-07:00</m:endTime>
</m:getCalendarDaysRequest>

//web
<getCalendarDaysRequest 
  xmlns="http://schemas.microsoft.com/hs/2001/10/myCalendar">
     <startTime>2002-01-14T15:23:17.0000000-07:00</startTime>
     <endTime>2002-01-21T16:23:17.0000000-07:00</endTime>
</getCalendarDaysRequest>

<deleteRequest select="/m:myCalendar/m:event" xmlns="http://schemas.microsoft.com/hs/2001/10/core" />
calendar.aspx | calendar.aspx.cs | calendar.vxml

OK, so I thought this one was going to be the hardest ... I lied. Contacts proved much more challenging, with the whole namespace thing. From looking at the Calendar schema though, it is huge, and powerful. If I was not doing the simplest example, it could become complicated real quick. Regardless, inserting the data proved easy, so I tricked it up by using a domain specific request. The code changed for this as well:

getCalendarDaysRequest daysRequest = new getCalendarDaysRequest();
//hardcoded
daysRequest.startTime = new DateTime(2002, 1, 15, 0,0,0);
daysRequest.endTime = new DateTime(2002, 1, 21, 0, 0, 0);
getCalendarDaysResponse daysResponse = myCal.getCalendarDays(daysRequest);

Once again MS ... kick ass!

<vxml version="1.0" application="services.aspx">
	<form id="form">
		<block>
			<prompt>
				your appointments are:
			</prompt>
			<prompt>dallas .NET user group meeting on 1/17/2002</prompt>
			<prompt>get oil changed on 1/18/2002</prompt>
			<prompt>haircut on 1/19/2002</prompt>
			<prompt>hot date on 1/20/2002</prompt>
			<goto next="services.aspx" />
		</block>
	</form>
</vxml>

Inbox

  1. USER: Inbox
  2. APP: my Inbox did not come with the S D K

Repeat: cannot develop against this one yet

<vxml version="1.0" application="services.aspx">
	<form id="form">
		<block>
			<prompt>
				my Inbox did not come with the S D K
			</prompt>
			<goto next="services.aspx" />
		</block>
	</form>
</vxml>

Source

It has my 2 projects (myVoices, and hsProxy) and the 2 MS projects (ServiceLocator, HsSoapExtension)

C# Source Code

README

Future

Hell, writing about this, took almost as long as coding it. So its all great and cool, now its time to reflect/complain:

Rant

I cannot believe all the negative press MyServices has been getting. Seems like they are picking up any developer monkey they can find off the street to comment, or some executive with stake in the antitrust trial. What are they pushing for now, to let other people host MyServices ... you idiots! You wont trust MS to host your info, but you'll trust multiple people ... and isnt separate data islands part of what MyServices is supposed to solve anyways. Simple DB math, only store it in 1 place. Multiple hosts will only work if all the hosts are setup like the UDDI registries, and synch up with each other. There are 2 sides though: business and technical. Business-wise, the value added is easily recognized. If you dont get it, then you narrow mindedness and lack of vision will allow me to eventually put you out of business. Now as to how that is billed, that does seem up in the air. From the initial numbers I saw, I do think MS needs a lower entry-level cost. All the other #s seemed reasonable to me. But how can you complain at this point, when we are all learning how Web Services are going to play in the market; nobody else is even close, aka SUN and the Liberty Alliance. Technically, MyServices is INSANELY cool. I view it as at least a 2nd gen implementation of Web Services, when most are still mucking up their 1st gens. The whole XML Schema integration and Service Fabric makes it look like they have abstracted it to such a level that they can basically deploy new Services by simply dropping in new XML Schemas! Also, lets you work with it as XML or Objects, depending on what your strength is or what you are trying to do. MS was quiet for a while, they got fed up, and now they are totally kicking ass. And people that realize this, are now siding with MS and against the whiners out there. MS will soon have the same support that the whiners have had. More simple math, people want to be on the winning team. Submit to the evil empire.

Updates

USER: Exit (or just hang up)