June 2012

Volume 27 Number 06

The Working Programmer - Talk to Me, Part 4: Feliza Gets Her Voice

By Ted Neward | June 2012

Ted NewardWe’re sprinting to the finish line now. The first column in this series introduced Tropo (see msdn.microsoft.com/magazine/hh781028). The second part introduced Feliza, an F# assembly that practices some simple string parsing legerdemain to sound reasonably like a bad psychiatrist (see msdn.microsoft.com/magazine/hh852597). The third part wired Tropo up against the Feliza Web site (so Tropo was receiving its commands from the Web site rather than a script hosted on its own servers) so we could do the final wiring to get Feliza (see msdn.microsoft.com/magazine/hh975378). And now, we complete the wiring job and turn Feliza loose into the world.

Recall, from the last piece, that our dream of Feliza being able to take phone calls is, at the moment, limited by the Tropo API, but Feliza has no problems with SMS—at 425-247-3096—or with IM (using the registered Jabber handle of feliza@tropo.im), so we’ll continue with that. Remember, Tropo can easily handle voice inputs if there’s a bound set of options to choose from, so if we wanted to limit Feliza to fixed input, we could easily go back to voice. However, for Feliza’s purposes, that doesn’t strike me as a great user experience: “Hello! If you’re depressed, say ‘depressed’; if you’re just feeling bad, say ‘feeling bad’; if you’re ready to kill an automated therapy system, say ‘die, die, die’ …”

Despite the limitation, Feliza can still help people (well, sort of), so let’s continue. Recall that we need Feliza to stand behind a Web site that knows how to respond to the Tropo REST/JSON requests Tropo fires at us, so we need to be able to interpret the incoming JSON, hand it to Feliza and then generate the reply. This means we need an ASP.NET MVC structure to represent the incoming JSON, and one to represent the outgoing response.

Creating a Microsoft .NET Framework class that matches up against this (so ASP.NET MVC can do the heavy lifting of parsing the JSON) is pretty straightforward, as shown in Figure 1.

Figure 1 The .NET Class to Match Tropo Structures Against JSON Objects

public class Session
{
  public string accountId { get; set; }
  public string callId { get; set; }
  public class From
  {
    public string id { get; set; }
    public string name { get; set; }
    public string channel { get; set; }
    public string network { get; set; }
  }
  public From from { get; set; }
  public class To
  {
    public string id { get; set; }
    public string name { get; set; }
    public string channel { get; set; }
    public string network { get; set; }
  }
  public To to { get; set; }
  public string id { get; set; }
  public string initialText { get; set; }
  public string timestamp { get; set; }
  public string userType { get; set; }
};

Not all of the properties shown in Figure 1 will always be populated, but it provides a working exemplar of how to map the Tropo structures against a JSON object.

Thus, it would seem to be a pretty straightforward case to parse the incoming JSON (a “session” object), extract the text out of the initialText field, generate a response object and hand it back, as shown in Figure 2.

Figure 2 Parsing the JSON

[AcceptVerbs("GET", "POST")]
public ActionResult Index(Tropo.JSON.Session session)
{
  string incoming = session.initialText;
  object felizaResponse = new
  {
    tropo = new object[]
    {
      new
      {
        ask = new Tropo.JSON.Ask()
        {
          say = new Tropo.JSON.Say() {
          value = "I'm Feliza. What about '" + incoming +
            "' would you like to talk about?"
          },
          choices = new Tropo.JSON.Ask.Choices() {
            value = "[ANY]"
          }
        }
      }
    }
  };
  return Json(felizaResponse);
}

Unfortunately, we run into a limitation of the ASP.NET MVC JSON serializer because any null values it sees in the returned object will be turned into “null” values in the JSON-serialized response, and Tropo has some issues with null-valued JSON fields. Fortunately, we can fix this by using a custom JSON serializer (in this case, James Newton-King’s excellent Json.NET serializer at bit.ly/a27Ou), which can be configured to not serialize null values. This changes the code slightly to return a custom ActionResult (gratefully obtained from bit.ly/1DVucR) instead of the standard one (see Figure 3).

Figure 3 A Customized ActionResult

public class JsonNetResult : ActionResult
{
  public Encoding ContentEncoding { get; set; }
  public string ContentType { get; set; }
  public object Data { get; set; }
  public JsonSerializerSettings SerializerSettings { get; set; }
  public Formatting Formatting { get; set; }
  public JsonNetResult() { SerializerSettings =
    new JsonSerializerSettings(); }
  public override void ExecuteResult(ControllerContext context)
  {     
    if (context == null)
      throw new ArgumentNullException("context");
    HttpResponseBase response = context.HttpContext.Response;
    response.ContentType = !string.IsNullOrEmpty(ContentType) ?
      ContentType : "application/json";
    if (ContentEncoding != null)
      response.ContentEncoding = ContentEncoding;
    if (Data != null)
    {
      JsonTextWriter writer =
        new JsonTextWriter(response.Output) 
        { Formatting = Formatting };
      JsonSerializer serializer =
        JsonSerializer.Create(SerializerSettings);
      serializer.Serialize(writer, Data);
      writer.Flush();
    }
  }
}

Then, in the Controller, we use this ActionResult instead of the JSON-based one, making sure to configure it to not send back null values:

[AcceptVerbs("GET", "POST")]
public ActionResult Index(Tropo.JSON.Session session)
{
  string incoming = session.initialText;
  object felizaResponse = // ...;
  JsonNetResult jsonNetResult = new JsonNetResult();
  jsonNetResult.SerializerSettings.NullValueHandling =
    NullValueHandling.Ignore;
  jsonNetResult.Data = felizaResponse;
  return jsonNetResult;
}

So at this point, for those readers playing with the code at home, the server is able to take incoming SMS messages, pick out the incoming text and generate a response back. You can probably guess what comes next.

Pleased to Meet You, Feliza

It’s time to complete the circuit by pulling in the Feliza F# binaries from the second column in this series. Either copy the compiled binaries over from the old code, or, if you prefer, create a (second) F# Library project in the ASP.NET MVC Tropo solution, mark the TropoApp project as depending on the Feliza project and copy over the code. (Make sure the ASP.NET MVC project also knows about the F# dependencies, particularly FSharp.Core.dll.)

Now, generating the response is as trivial as taking the incoming text, passing it in to Feliza1’s respondmethod and capturing the results into the returned JSON, as shown in Figure 4.

Figure 4 Generating a Response

[AcceptVerbs("GET", "POST")]
public ActionResult Index(Tropo.JSON.Session session)
{
  object felizaResponse = new
  {
    tropo = new object[]
    {
      new
      {
        ask = new Tropo.JSON.Ask()
        {
          say = new Tropo.JSON.Say() {
            value = Feliza1.respond(session.initialText)
          },
          choices = new Tropo.JSON.Choices() {
            value = "[ANY]"
          }
        }
      }
    }
  };
  JsonNetResult jsonNetResult = new JsonNetResult();
  jsonNetResult.SerializerSettings.NullValueHandling =
    NullValueHandling.Ignore;
  jsonNetResult.Data = felizaResponse;
  return jsonNetResult;
}

It really is that easy—and if a phone is handy, pull it out and you’ll see that texting Feliza results in a kind of conversation. She’s not the world’s smartest chat-bot, but with the F# core behind her, there’s a lot we can do to improve her responses.

Incorporating Voice or SMS into Applications

Feliza’s done, and now the world can rest easy knowing that if anyone’s staying up late, just wishing somebody would text them, Feliza’s ready and waiting for them. Of course, it’s not going to be a very satisfying conversation, because most of the time she won’t have a clue about what she’s reading.

The Tropo API has some interesting quirks, but clearly there’s some significant power there. The twin models—the hosted scripts as well as the “Web API”—offer some interesting flexibility when thinking about how to incorporate voice, SMS or instant messaging into applications.

Feliza’s Web site, too, could be enhanced in a number of ways—for example, for those who are more mobile-Web-minded, it would seem appropriate to create a set of static HTML5 pages using jQuery to hit Feliza with the typed-in text and append the responses to the page. In essence, Tropo offers a new set of “channels” through which to receive input, and ideally those channels would normalize their input into a single set of JSON structures to simplify the ASP.NET MVC endpoint code. And clearly the Tropo story would fit in well alongside other means of communication with customers or users. Other obvious channels that would be interesting to add are Twitter or Facebook, for examples. (Tropo has the ability to hook into Twitter, by the way, which would simplify that particular channel, but a Facebook channel would be something we’d have to build by hand.)

For now, other topics are pressing, so it’s time to wave farewell to Feliza and move on. Don’t worry, she’ll still be there when you’re feeling lonely. In the meantime …

Happy coding!


Ted Neward is an architectural consultant with Neudesic LLC. He’s written more than 100 articles, is a C# MVP and INETA speaker and has authored and coauthored a dozen books, including the recently released “Professional F# 2.0” (Wrox, 2010). He consults and mentors regularly. Reach him at ted@tedneward.com if you’re interested in having him come work with your team, or read his blog at blogs.tedneward.com.