Presenting this Saturday at Atlanta Code Camp

by Administrator 17. May 2012 07:50

I'll be presenting a talk this Saturday at the Atlanta Code Camp about the natural language processing engine I've been blogging about, recently. You can find more details at http://www.atlantacodecamp.org/default.aspx.

Hope to see you there!

Tags: ,

.Net | Natural Language Processing

Writing a Natural Language Parser in C# Part 5 - Questions and Rules

by Administrator 10. April 2012 12:13

This post is part of a series on creating a natural language processor in C#. The other entries in this series are:

Writing a Natural Language Parser in C# Part 1–Why?
Writing a Natural Language Parser in C# Part 2 – Architecture
Writing a Natural Language Parser in C# Part 3–CommandProcessor and ConversationContext
Writing a Natural Language Parser in C# Part 4–Tokens

In this, the last part in this series, I'd like to look at the final step in processing the user's input which is locating a rule that matches the set of tokens that have been generated.  If you'll remember, in the last entry we saw how the user's input was tokenized and how those tokens we then organized into a Dictionary<int, List<TokenResult>> structure where the integer key was the index into the string where the tokenized input was found and the TokenResult itself was added to the list corresponding with that position.  Now that we have that structure, we need to evaluate the rules we have defined to see if there is a match.

What is a Rule

A rule is a method that represents a form of expected input the system can look for.  In a way, the tokens and rules we define make up the system's vocabulary where the tokens represent the words and phrases the system knows about and the rules represent the whole thoughts the system can work out.  For example, look at the following rule definition.

A Rule Definition
  1. public static void GetWeather(ConversationContext cContext, TokenList list, TokenWeather weather)
  2.         {
  3.             cContext.Say(WeatherService.GetTodaysWeather(cContext.ConversationUser), null);
  4.         }

All rules are defined in classes that implement the IRuleClass interface.  This interface has no members defined on it, but is used as a marker interface so that we can find all the types that contain rules using reflection.  Whereas we could use MEF to load all our tokens, we can't do this with rules since there is no commonality between the classes that contain them.  Here, we have to resort to using reflection. 

Each rule will take a ConversationContext as its first parameter which will be followed by a list of token types.  In the above example, we're looking for a TokenList and a TokenWeather.  The objective is that if the user passes in something like "what's the weather" or "get the weather", this rule will be found to be a match for that request and our weather service will be called to get the forecast.  Then, the Say method on the ConversationContext instance will be called to push the forecast back to the user.  The second parameter, if you'll recall (null here) is the tag value that is serialized and saved in the conversation history for future reference.

Finding the matching Rule

So, how do we go about using our dictionary to find a matching rule?

First, locate and load all the rules found in the solution.  See code below that uses reflection to locate and get instances of all the rules.

Load rules
  1. var rules = new List<MethodInfo>();
  2.             var assemblies = new List<Assembly>();
  3.             var currentAssembly = Assembly.GetExecutingAssembly();
  4.             string path = currentAssembly.Location;
  5.             IEnumerable methods = null;
  6.  
  7.             foreach (string dll in Directory.GetFiles(Path.GetDirectoryName(path), "*.dll"))
  8.             {
  9.                 try
  10.                 {
  11.                     assemblies.Add(Assembly.LoadFile(dll));
  12.                 }
  13.                 catch (Exception)
  14.                 {
  15.                 }
  16.             }
  17.  
  18.             foreach (var assembly in assemblies)
  19.             {
  20.                 List<Type> ruleClasses = (from t in assembly.GetTypes()
  21.                                           where !t.IsInterface
  22.                                           where !t.IsAbstract
  23.                                           where t.GetInterface("SmartHome.Core.IRuleClass") != null
  24.                                           select t).ToList();
  25.  
  26.                 foreach (var ruleClass in ruleClasses)
  27.                 {
  28.                     if (ruleClass == null)
  29.                     {
  30.                         continue;
  31.                     }
  32.  
  33.                     methods = from m in ruleClass.GetMethods()
  34.                               where m.IsStatic
  35.                               select m;
  36.  
  37.                     rules.AddRange(methods.Cast<object>().Cast<MethodInfo>());
  38.                 }
  39.             }
  40.  
  41.             return rules.ToList();

Loop through the rules and for each one, get a list of all the rule's parameters.

Get list of parameters
  1. var parameters = (from p in rule.GetParameters()
  2.                                   where p.ParameterType != typeof(ConversationContext)
  3.                                   select p).ToList();

Now, since the order of the parameters should match the order our tokens are found in the input, we loop though the parameters for each of the rules and for each one, see if we have a token in each of the lists in our dictionary; in index order.  For each rule, then, we keep track of how many of our TokenResult classes wrapped a matching Token type, again in the same order that the parameters are specified.  After all the rules have been evaluated, we look at these results we have saved for each.  If there are tokens in the dictionary that are not found in the parameter list, the rule is thrown out.  Next, if there are parameters in a rule that are not in the dictionary, the rule is thrown out.  Of the remaining rules, the top 1 is selected.  The rule is invoked and an instance of the ConversationContext along with the other defined parameters are passed in.

Questions

As we saw before, the ConversationContext class defines a method, AskQuestion().  This functionality was not in my original implementation.  I found, however, that there were some things that were difficult to communicate to the system in a single sentence.  For example, I wanted the system to be able to handle reminding the user of something on a particular date at a particular time and also allow the user to specify which channels they were reminded on.  For example, "remind me to get a haircut on Friday at 4:00 PM and remind me via email and sms".  My thoughts were that it's easier for the user to just say, "remind me to get a haircut" and let the system prompt them for the other details.  To enable this, I created  two additional types, Question and QuestionManager.  The Question type simply contains information about the question.  See its definition, below.

Question
  1. [DataContract]
  2.     public class Question
  3.     {
  4.         [DataMember]
  5.         public string QuestionText { get; set; }
  6.  
  7.         [DataMember]
  8.         public List<Token> ExpectedReplys { get; set; }
  9.         
  10.         [DataMember]
  11.         public Action<ConversationContext, object,
  12.             List<Token>> ExecuteIfAnswered { get; set; }
  13.         
  14.         [DataMember]
  15.         public ConversationMode Mode { get; set; }
  16.         
  17.         [DataMember]
  18.         public string Address { get; set; }
  19.         
  20.         [DataMember]
  21.         public Guid UserId { get; set; }
  22.         
  23.         [DataMember]
  24.         public DateTime PosedDateTime { get; set; }
  25.         
  26.         [DataMember]
  27.         public object State { get; set; }
  28.     }

Instances of this class store the text of the question and  information about the channel and context the question was asked on.  In addition, there is a callback that will be executed when the question is answered and a list of tokens that the input must match in order qualify as an answer to the question.  This match is evaluated in exactly the same way we matched rules, before.  The QuestionManager stores these questions until they are answered.  It is called to evaluate the input from the user to see if it matches any of the questions it is managing and once a question is matched, its callback is called and it is then removed from the QuestionManager.

Tags: ,

.Net | Natural Language Processing

Writing a Natural Language Parser in C# Part 4–Tokens

by Administrator 1. April 2012 06:50

This post is part of a series on creating a natural language processor in C#.  The other entries in this series are:

Writing a Natural Language Parser in C# Part 1–Why?
Writing a Natural Language Parser in C# Part 2 – Architecture 
Writing a Natural Language Parser in C# Part 3–CommandProcessor and ConversationContext
Writing a Natural Language Parser in C# Part 5 - Questions and Rules

This week, I’d like to look deeper into how the speech processor tokenizes the incoming command.

When I first started thinking about creating a speech processor for my smart home software, I did quite a bit of research on the internet looking to see what the state of this technology was and what people were doing with it.  What I found was that there were several pieces of software out there like SharpNLP and Antelope that are capable of taking a sentence and breaking it down into its constituent phrases and words.  They can identify the part of speech each is, the definition of words and their synonyms.  The output from such a tool might look something like this.

Let/VB 's/PRP see/VB how/WRB tokenization/NN works/VBZ in/IN SmartNLP/NNP ./.

I was amazed at all this until I tried to figure out how to apply this technology to my problem of communicating with my machine.  I found that even with all this information about what was said, I still couldn’t understand it enough to act on it.  I discovered a different approach which is the conversion of the input into tokens containing a different set of properties.  Once the input was tokenized, it was no longer a string, but a collection of objects that could be processed to discover what was being communicated.

Let’s look at how the system tokenizes its input.

Anatomy of A Token

A token has three members;  A collection of phrases, a value and one method that takes the input and returns results.  A skeleton would look like this:

Token
  1. [DataContract]
  2.     public class Token
  3.     {
  4.         protected List<string> Words;
  5.  
  6.         [DataMember]
  7.         public object Value { get; set; }
  8.  
  9.         public virtual IEnumerable<TokenResult> Parse(string input, Guid userId)
  10.         {
  11.  
  12.         }
  13.     }

Here, the list of strings, Words, holds a collection of words or phrases that the class will locate and generate a result for.  The Value property will hold the value that has been parsed.  Depending on the token, this could be a string, a DateTime or  a number.  Last, the Parse method is called to do the actual parsing.  All tokens inherit from the base Token class.  The parse method is implemented in this class to provide a basic functionality of locating phrases that are in the Words collection and return a TokenResult for each.

Some of the tokens are very simply implemented and others are quite complicated depending on what is being parsed.  For example, the token that parses the user’s request for information is TokenList, as in “list reminders”.  Its entire implementation looks like this:

TokenList
  1. [DataContract]
  2.     [Export(typeof(IParseToken))]
  3.     public class TokenList : Token, IParseToken
  4.     {
  5.         public TokenList()
  6.         {
  7.             Words = new List<string> { "list", "show", "get", "lists", "what are my", "whats" };   
  8.         }
  9.     }

This class takes advantage of the base class’ Parse implementation.  Also, notice that the Words lists contains synonyms for list.  If the user specifies any of these values, it will be parsed as TokenList.

As an example of a more complicated Token, consider a token that parses a DateTime.  Here, we would override the base class’ implementation of parse and look for portions of the input that could be parsed as a DateTime.  This can get quite complicated when you consider that the user could say something like “remind me to call bob next saturday”  This token would need to be able to recognize that “next saturday” specifies a date and then calculate what that date is.

TokenResult

The Token classes all return an instance of TokenResult for each value they parse out.  The TokenResult class is listed below.

TokenResult
  1. [DataContract]
  2.     [KnownType("GetKnownTypes")]
  3.     public class TokenResult
  4.     {
  5.         [DataMember]
  6.         public object Value { get; set; }
  7.  
  8.         [DataMember]
  9.         public string TokenType { get; set; }
  10.  
  11.         [DataMember]
  12.         public int Start { get; set; }
  13.  
  14.         [DataMember]
  15.         public int Length { get; set; }
  16.  
  17.         [DataMember]
  18.         public Token Token { get; set; }
  19.  
  20.         private static IEnumerable<Type> GetKnownTypes()
  21.         {
  22.             return new List<Type>
  23.                             {
  24.                                 typeof (Token),
  25.                                 typeof (TokenInt),
  26.                                 typeof (TokenLong),
  27.                                 typeof (TokenNumeric),
  28.                                 typeof (TokenPercentage),
  29.                                 typeof (TokenQuotedPhrase),
  30.                                 typeof (TokenResult),
  31.                                 typeof (Tokens.Nouns.TokenNoun),
  32.                                 typeof (Tokens.Nouns.TokenToDo),
  33.                                 typeof (Tokens.Nouns.TokenEmail),
  34.                                 typeof (Tokens.Nouns.TokenSms),
  35.                                 typeof (Tokens.Nouns.TokenWeather),
  36.                                 typeof (Tokens.Nouns.TokenNews),
  37.                                 typeof (Tokens.Nouns.TokenIm),
  38.                                 typeof(Tokens.Nouns.TokenNeither),
  39.                                 typeof(Tokens.Nouns.TokenYesNo),
  40.                                 typeof(TokenReminder),
  41.                                 typeof(TokenDefinedList),
  42.                                 typeof(TokenNamed),
  43.                                 //typeof (Tokens.Nouns.TokenDevice),
  44.                                 //typeof (Tokens.Nouns.TokenRoom),
  45.                                 typeof (Tokens.Nouns.TokenState),
  46.                                 //typeof (Tokens.Nouns.TokenStructure),
  47.                                 //typeof (Tokens.Nouns.TokenZone),
  48.                                 typeof (Tokens.Nouns.TokenDim),
  49.                                 typeof (Tokens.Prepositions.TokenPreposition),
  50.                                 typeof (Tokens.Temporal.TokenDeterminateSeries),
  51.                                 typeof (Tokens.Temporal.TokenExactTime),
  52.                                 typeof (Tokens.Temporal.TokenIndeterminateSeries),
  53.                                 typeof (Tokens.Temporal.TokenTemporal),
  54.                                 typeof(Tokens.Temporal.TemporalParts.TokenDayOfWeek),
  55.                                 typeof(Tokens.Temporal.TemporalParts.TokenApril),
  56.                                 typeof(Tokens.Temporal.TemporalParts.TokenAugust),
  57.                                 typeof(Tokens.Temporal.TemporalParts.TokenDayAfterTomorrow),
  58.                                 typeof(Tokens.Temporal.TemporalParts.TokenDayBeforeYesterday),
  59.                                 typeof(Tokens.Temporal.TemporalParts.TokenDecember),
  60.                                 typeof(Tokens.Temporal.TemporalParts.TokenEach),
  61.                                 typeof(Tokens.Temporal.TemporalParts.TokenEighteenth),
  62.                                 typeof(Tokens.Temporal.TemporalParts.TokenEighth),
  63.                                 typeof(Tokens.Temporal.TemporalParts.TokenEleventh),
  64.                                 typeof(Tokens.Temporal.TemporalParts.TokenFebruary),
  65.                                 typeof(Tokens.Temporal.TemporalParts.TokenFifteenth),
  66.                                 typeof(Tokens.Temporal.TemporalParts.TokenFifth),
  67.                                 typeof(Tokens.Temporal.TemporalParts.TokenFirst),
  68.                                 typeof(Tokens.Temporal.TemporalParts.TokenForteenth),
  69.                                 typeof(Tokens.Temporal.TemporalParts.TokenForth),
  70.                                 typeof(Tokens.Temporal.TemporalParts.TokenFriday),
  71.                                 typeof(Tokens.Temporal.TemporalParts.TokenInt),
  72.                                 typeof(Tokens.Temporal.TemporalParts.TokenJanuary),
  73.                                 typeof(Tokens.Temporal.TemporalParts.TokenJuly),
  74.                                 typeof(Tokens.Temporal.TemporalParts.TokenJune),
  75.                                 typeof(Tokens.Temporal.TemporalParts.TokenLong),
  76.                                 typeof(Tokens.Temporal.TemporalParts.TokenMarch),
  77.                                 typeof(Tokens.Temporal.TemporalParts.TokenMay),
  78.                                 typeof(Tokens.Temporal.TemporalParts.TokenMonday),
  79.                                 typeof(Tokens.Temporal.TemporalParts.TokenMonth),
  80.                                 typeof(Tokens.Temporal.TemporalParts.TokenNinteenth),
  81.                                 typeof(Tokens.Temporal.TemporalParts.TokenNinth),
  82.                                 typeof(Tokens.Temporal.TemporalParts.TokenNovember),
  83.                                 typeof(Tokens.Temporal.TemporalParts.TokenNumeric),
  84.                                 typeof(Tokens.Temporal.TemporalParts.TokenOctober),
  85.                                 typeof(Tokens.Temporal.TemporalParts.TokenOrdinal),
  86.                                 typeof(Tokens.Temporal.TemporalParts.TokenOther),
  87.                                 typeof(Tokens.Temporal.TemporalParts.TokenPercentage),
  88.                                 typeof(Tokens.Temporal.TemporalParts.TokenRelativeTemporalOrdinal),
  89.                                 typeof(Tokens.Temporal.TemporalParts.TokenSaturday),
  90.                                 typeof(Tokens.Temporal.TemporalParts.TokenSecond),
  91.                                 typeof(Tokens.Temporal.TemporalParts.TokenSeptember),
  92.                                 typeof(Tokens.Temporal.TemporalParts.TokenSeventeenth),
  93.                                 typeof(Tokens.Temporal.TemporalParts.TokenSeventh),
  94.                                 typeof(Tokens.Temporal.TemporalParts.TokenSixteenth),
  95.                                 typeof(Tokens.Temporal.TemporalParts.TokenSixth),
  96.                                 typeof(Tokens.Temporal.TemporalParts.TokenSpecifiedDate),
  97.                                 typeof(Tokens.Temporal.TemporalParts.TokenSunday),
  98.                                 typeof(Tokens.Temporal.TemporalParts.TokenTenth),
  99.                                 typeof(Tokens.Temporal.TemporalParts.TokenThird),
  100.                                 typeof(Tokens.Temporal.TemporalParts.TokenThirteenth),
  101.                                 typeof(Tokens.Temporal.TemporalParts.TokenThirtieth),
  102.                                 typeof(Tokens.Temporal.TemporalParts.TokenThirtyFirst),
  103.                                 typeof(Tokens.Temporal.TemporalParts.TokenThursday),
  104.                                 typeof(Tokens.Temporal.TemporalParts.TokenTime),
  105.                                 typeof(Tokens.Temporal.TemporalParts.TokenToday),
  106.                                 typeof(Tokens.Temporal.TemporalParts.TokenTomorrow),
  107.                                 typeof(Tokens.Temporal.TemporalParts.TokenTuesday),
  108.                                 typeof(Tokens.Temporal.TemporalParts.TokenTwelth),
  109.                                 typeof(Tokens.Temporal.TemporalParts.TokenTwentieth),
  110.                                 typeof(Tokens.Temporal.TemporalParts.TokenTwentyEighth),
  111.                                 typeof(Tokens.Temporal.TemporalParts.TokenTwentyFifth),
  112.                                 typeof(Tokens.Temporal.TemporalParts.TokenTwentyFirst),
  113.                                 typeof(Tokens.Temporal.TemporalParts.TokenTwentyFourth),
  114.                                 typeof(Tokens.Temporal.TemporalParts.TokenTwentyNinth),
  115.                                 typeof(Tokens.Temporal.TemporalParts.TokenTwentySecond),
  116.                                 typeof(Tokens.Temporal.TemporalParts.TokenTwentySeventh),
  117.                                 typeof(Tokens.Temporal.TemporalParts.TokenTwentySixth),
  118.                                 typeof(Tokens.Temporal.TemporalParts.TokenTwentyThird),
  119.                                 typeof(Tokens.Temporal.TemporalParts.TokenWednesday),
  120.                                 typeof(Tokens.Temporal.TemporalParts.TokenYesterday),
  121.                                 typeof (Tokens.Verbs.TokenCreate),
  122.                                 typeof (Tokens.Verbs.TokenDelete),
  123.                                 typeof (Tokens.Verbs.TokenList),
  124.                                 typeof (Tokens.Verbs.TokenRemind),
  125.                                 typeof (Tokens.Verbs.TokenReset),
  126.                                 typeof (Tokens.Verbs.TokenWhatIs),
  127.                                 typeof(Tokens.Verbs.TokenWhereIs),
  128.                                 typeof(Tokens.Verbs.TokenWhoIs),
  129.                                 typeof (Tokens.Verbs.TokenWhoSang),
  130.                                 typeof (Tokens.Verbs.TokenWhoWasIn),
  131.                                 typeof (Tokens.Verbs.TokenRemindMeTo),
  132.                                 typeof (Tokens.Verbs.TokenRemindMeAt),
  133.                                 typeof(System.Type),
  134.                                 typeof(Questions.Question)
  135.                                 //typeof(StructuredSpeech2.House.Structure.Device),
  136.                                 //typeof(StructuredSpeech2.House.Structure.House),
  137.                                 //typeof(StructuredSpeech2.House.Structure.Room),
  138.                                 //typeof(StructuredSpeech2.House.Structure.X10Device),
  139.                                 //typeof(StructuredSpeech2.House.Structure.Zone),
  140.                                 //typeof(StructuredSpeech2.House.Devices.X10LampDevice),
  141.                                 //typeof(StructuredSpeech2.Tokens.Verbs.TokenTurn),
  142.                                 //typeof(StructuredSpeech2.Tokens.Nouns.TokenDeviceList)
  143.                             };
  144.         }
  145.     }

This class wraps a token instance and additionally holds the start position and length of the parsed value.  You’ll also notice code here that facilitates the serializing of tokens.  The application often stores tokens and token results in the database and this code allows the types to be serialized and persisted.

Recall that the CommandProcessor class calls into the TokenManager which calls into each token, in turn, and compiles all the results into buckets. 

TokenManager

The TokenManager holds a collection of tokens and manages giving each a shot at parsing the input.  It, then, uses the start position and length properties on the results to organize then into a dictionary that can be used to determine a matching rule to be executed.  The TokenManager class is listed, below.

TokenManager
  1. [Export]
  2.     public class TokenManager
  3.     {
  4.         [ImportMany(typeof(IParseToken))]
  5.         private List<IParseToken> Tokens { get; set;}
  6.  
  7.         public Dictionary<int, List<TokenResult>> TokenizeInput(
  8.             string input, Guid userId)
  9.         {
  10.             var results = new List<TokenResult>();
  11.  
  12.             try
  13.             {
  14.                 foreach (var token in Tokens)
  15.                 {
  16.                     results.AddRange(token.Parse(input, userId));
  17.                 }
  18.             }
  19.             catch (Exception e)
  20.             {
  21.                 Logger.Log(e.Message);
  22.             }
  23.  
  24.  
  25.             CreateQuotedPhraseTokens(results, input);
  26.  
  27.             //arrange all token results by their start positions
  28.             var buckets = new Dictionary<int, List<TokenResult>>();
  29.  
  30.             foreach (var result in results.OrderBy(r => r.Start))
  31.             {
  32.                 if (!buckets.ContainsKey(result.Start))
  33.                 {
  34.                     buckets[result.Start] = new List<TokenResult>();
  35.                 }
  36.  
  37.                 buckets[result.Start].Add(result);
  38.             }
  39.  
  40.             return buckets;
  41.         }
  42.  
  43.         private void CreateQuotedPhraseTokens(
  44.             List<TokenResult> results, string input)
  45.         {
  46.             int index = 0;
  47.             List<WordInfo> words = new List<WordInfo>();
  48.             string accumulator = "";
  49.  
  50.             for (index = 0; index < input.Length - 1; index++)
  51.             {
  52.                 if (input[index] == ' ')
  53.                 {
  54.                     words.Add(new WordInfo
  55.                     {
  56.                         Found = false,
  57.                         Length = accumulator.Length,
  58.                         Start = index - accumulator.Length,
  59.                         Value = accumulator
  60.                     });
  61.  
  62.                     accumulator = "";
  63.                     continue;
  64.                 }
  65.  
  66.                 accumulator += input[index];
  67.             }
  68.  
  69.             accumulator += input[index];
  70.  
  71.             words.Add(new WordInfo
  72.             {
  73.                 Found = false,
  74.                 Length = accumulator.Length,
  75.                 Start = (index + 1) - accumulator.Length,
  76.                 Value = accumulator
  77.             });
  78.  
  79.             accumulator = "";
  80.  
  81.             foreach (var word in words)
  82.             {
  83.                 var match = results.Where(r =>
  84.                     word.Start >= r.Start && (word.Start + word.Length) <=
  85.                     (r.Start + r.Length)).FirstOrDefault();
  86.  
  87.                 if (match != null)
  88.                 {
  89.                     if (accumulator.Length > 0)
  90.                     {
  91.                         results.Add(new TokenResult
  92.                         {
  93.                             Length = accumulator.Trim().Length,
  94.                             Start = word.Start - 1 - accumulator.Trim().Length,
  95.                             Token = new TokenQuotedPhrase { Value = accumulator.Trim() },
  96.                             TokenType = typeof(TokenQuotedPhrase).ToString(),
  97.                             Value = accumulator.Trim()
  98.                         });
  99.                         accumulator = "";
  100.                     }
  101.                 }
  102.                 else
  103.                 {
  104.                     accumulator += word.Value + " ";
  105.                 }
  106.             }
  107.  
  108.             if (accumulator.Length > 0)
  109.             {
  110.                 results.Add(new TokenResult
  111.                 {
  112.                     Length = accumulator.Trim().Length,
  113.                     Start = input.Length - 1 - accumulator.Trim().Length,
  114.                     Token = new TokenQuotedPhrase { Value = accumulator.Trim() },
  115.                     TokenType = typeof(TokenQuotedPhrase).ToString(),
  116.                     Value = accumulator.Trim()
  117.                 });
  118.             }
  119.         }
  120.     }

In lines 4 and 5, you can see that we’re using MEF to load all the Tokens into a collection.  The TokenizeInput method loops through the tokens and passes the input to each.  It then calls the CreateQuotedPhraseTokens method, which I’ll discuss shortly.  Next, the results are iterated through and organized into a dictionary.

It’s quite possible the user will sometimes specify words or phrases that we have no token for.  In fact, there are situations where we expect the user to do this.  For example, when the user asks the system to create a reminder for them they will say something like, “Remind me to cook the golden goose next Friday”.  We can parse out enough of the input to determine the users would like a reminder created and when they would like to be reminded.  We don’t have tokens, however, to represent the “cook the golden goose” portion of the input.  For this reason, after all the token classes have parsed out their results from the input, the TokenManager tokenizes the “left out” portions of the input as a TokenQuotedPhrase type.  This allows us to use these values when locating rules to execute and inside those rules we can use that portion of the input as data.

The tokenization of the input is an important part of understanding what the user is asking for.  It allows us to work with the input as a collection of objects as opposed to dealing with a string.  The last part of the process is matching the tokens to a rule.  We’ll look at how this is done next time.

Tags: ,

.Net | Natural Language Processing

Writing a Natural Language Parser in C# Part 3–CommandProcessor and ConversationContext

by Administrator 25. March 2012 06:51

This post is part of a series on creating a natural language processor in C#. The other entries in this series are:

Writing a Natural Language Parser in C# Part 1–Why?
Writing a Natural Language Parser in C# Part 2 - Architecture
Writing a Natural Language Parser in C# Part 4–Tokens 
Writing a Natural Language Parser in C# Part 5 - Questions and Rules

The command processor is is the class that controls the flow of parsing a request and replying to it.  Below is a full listing of the class.

CommandProcessor class
  1. [Export]
  2.     [PartCreationPolicy(CreationPolicy.NonShared)]
  3.     public class CommandProcessor
  4.     {
  5.         [Import]
  6.         private QuestionManager _questionManager { get; set; }
  7.  
  8.         public void ProcessCommand(string command, Guid userId,
  9.             SmartHome.Global.ConversationMode mode,
  10.             string address,
  11.             Action<string> callback)
  12.         {
  13.             string localCommand = command.ToLower().Replace("'", "");
  14.             var context = ServiceLocator.GetInstance<ConversationContext>();
  15.             context.Init(userId, mode, address, callback);
  16.             var tokenManager = ServiceLocator.GetInstance<TokenManager>();
  17.             var buckets = tokenManager.TokenizeInput(command, userId);
  18.             context.LogRequest(command, buckets);
  19.  
  20.             //before we check the tokens against rules, let's see if we match any questions
  21.             Question question = null;
  22.             List<Token> tokens = new List<Token>();
  23.  
  24.             _questionManager.CheckForMatchingQuestion(buckets, mode, userId,
  25.                 address, ref question, ref tokens);
  26.  
  27.             if (question != null)
  28.             {
  29.                 question.ExecuteIfAnswered.Invoke(context,
  30.                     question.State, tokens);
  31.                 
  32.                 _questionManager.RemoveQuestion(question);
  33.                 
  34.                 return;
  35.             }
  36.  
  37.             RuleMethod ruleMethod = RuleManager.LocateMatchingRule(buckets, context);
  38.  
  39.             if (ruleMethod != null)
  40.             {
  41.                 ruleMethod.Rule.Invoke(null, ruleMethod.PassIns);
  42.             }
  43.             else
  44.             {
  45.                 context.Say("I didn't understand your request", null);
  46.             }
  47.         }
  48.     }

First of all, you can see that MEF is being used as an IOC container.  The only method in this class is the ProcessCommand method that takes the following parameters.

  • A string that is the command the user sent in, “What’s the weather” for example.
  • The user id.  This is a Guid because we are using the Membership provider for authentication.
  • The conversation mode is specified, next, as an enumeration.  The values in the enumeration specify what channel the user specified the command through (email, IM, etc.).
  • An address is specified as a string for the next parameter.  We can tell from the mode what channel the user is on, but if they have multiple IM accounts or email addresses, we also need the address to figure out how to reply to them on the right account.
  • Last is a call back to be called after the command is processed.  This is primarily here for commands sent to the system on a duplex WCF service.  In order to reply to the user on this service, we need to be able to respond on the specified interface defined in the WCF service.  This callback allows that.

To start off, we clean the command up a bit by making it lower case and removing apostrophes.  Now, we create an instance of ConversationContext and initialize it.  We’ll look at this class in just a minute.  Next, we create the TokenManager and pass the user’s command to it.  What is passes us back is a Dictionary<int, List<TokenResult>> where the int specifies a start position where tokens were found and the List contains all the tokens that were found there.  We’ll look at the tokenization of the string in detail in the next part in this series.

Since the system saves all messages sent by the system or the user, we next log the users request.  The buckets are passed in because they will be serialized and saved along with the command.  When future requests come in, this will allow us to pull any past request from the database, de-serialize it and inspect it.

In some cases, the system will ask clarifying questions of the user.  For example, if you as the system to remind you of something, it will need to know when to remind you and how.  So, it will ask you these questions and wait for your reply.  In the next code block we use the QuestionManager to check and see if the current request is actually the answer to a previously asked question.

If the command is not the answer to a question, we need to check and see if it matches one of the rules we have defined.  Rules are how you specify sentences the system should recognize and what it should do when it receives that sentence.  The RuleManager class compares our buckets  with the rules to find a match.  If one is found, it is invoked.  Otherwise, the system replies that it did not understand the command.

Notice that the response is sent via the Say method on the ConversationContext class.  Since this functionality completes the processing cycle, let’s look at that class next.

ConversationContext

As has been said before, the purpose of this class is to manage the conversation’s details such as the user it belongs to, it’s mode, address and history.  Below is a listing of this class.

ConversationContext Class
  1. [Export]
  2.     [PartCreationPolicy(CreationPolicy.NonShared)]
  3.     public class ConversationContext
  4.     {
  5.         public User ConversationUser { get; set; }
  6.         public Conversation Conversation { get; set; }
  7.         public List<ConversationHistory> ConversationHistory { get; set; }
  8.         public SmartHome.Global.ConversationMode Mode { get; set; }
  9.         public Action<string> Callback { get; set; }
  10.         public string Address { get; set; }
  11.  
  12.         [Import]
  13.         private IEventAggregator EventAggregator { get; set; }
  14.  
  15.         [Import]
  16.         public QuestionManager QuestionManagerReference { get; set; }
  17.  
  18.         public void Init(Guid userId, SmartHome.Global.ConversationMode mode,
  19.             string address, Action<string> callback)
  20.         {
  21.             ConversationUser = UserData.GetUserByUserId(userId);
  22.             Conversation = ConversationData.GetConversationByUserAndMode(
  23.                 userId, mode, address);
  24.             ConversationHistory = ConversationData.GetConversationHistory(
  25.                 Conversation.ConversationId);
  26.             Mode = mode;
  27.             Callback = callback;
  28.             Address = address;
  29.         }
  30.  
  31.         public void LogRequest(string request, object tag)
  32.         {
  33.             string tagString = SerializeTag(tag);
  34.             var history = ConversationData.CreateConversationHistory(
  35.                 Conversation.ConversationId, request, tagString, tag, true);
  36.             ConversationHistory.Add(history);
  37.         }
  38.  
  39.         public void Say(string comment, object tag)
  40.         {
  41.             string tagString = string.Empty;
  42.             tagString = SerializeTag(tag);
  43.  
  44.             EventAggregator.GetEvent<ReplyToChannelEvent>().Publish
  45.                 (new ReplyToChannelEventArgs
  46.             {
  47.               Mode = (SmartHome.Global.ConversationMode)Conversation.Mode,
  48.               Reply = comment,
  49.               TagString = tagString,
  50.               Tag = tag,
  51.               ConversationId = Conversation.ConversationId,
  52.               Callback = Callback,
  53.               UserId = ConversationUser.UserId,
  54.               Address = Address
  55.             });
  56.         }
  57.  
  58.         private string SerializeTag(object tag)
  59.         {
  60.             string tagString = string.Empty;
  61.  
  62.             if (tag != null)
  63.             {
  64.                 if (tag is IEntityWithChangeTracker)
  65.                     (tag as IEntityWithChangeTracker).SetChangeTracker(null);
  66.                 var ser = new DataContractSerializer(tag.GetType());
  67.                 var ms = new MemoryStream();
  68.                 ser.WriteObject(ms, tag);
  69.                 tagString = Encoding.Default.GetString(ms.ToArray());
  70.             }
  71.  
  72.             return tagString;
  73.         }
  74.  
  75.         public void AskQuestion(string text, List<Token> expectedReplies, object state,
  76.             Action<ConversationContext, object, List<Token>> executeOnAnswer)
  77.         {
  78.             Question question = new Question
  79.             {
  80.                 Address = Address,
  81.                 ExecuteIfAnswered = executeOnAnswer,
  82.                 ExpectedReplys = expectedReplies,
  83.                 Mode = Mode,
  84.                 PosedDateTime = DateTime.Now,
  85.                 UserId = ConversationUser.UserId,
  86.                 State = state,
  87.                 QuestionText = text
  88.             };
  89.  
  90.             QuestionManagerReference.AddQuestion(question);
  91.  
  92.             Say(text, null);
  93.         }
  94.     }

At the top of this class are instance variables that hold the instance’s state.  The Init method initializes these variables.

Next is the LogRequest method we saw earlier that serializes the tag that is passed in (the buckets) and writes the request to the database.

The next method is Say().  We saw this method being used in the CommandProcessor class to reply to the user.  It’s also used by the various rules for the same purpose.  If you as the system about the weather, it will send the details back to you with the Say method.  As you can see, I’m using the EventAggregator from the PRISM library to make the component of the system more loosely coupled.  The say method serializes the tag parameter just like the LogRequest method does and then it raises an event asking someone to reply to a user.  There are various classes responsible for communicating with the user on different channels.  These classes are listening for this event and when they receive it, they will inspect the event args to see if they should respond.  If so, they will send the response to the user and log the communication to the database.

Context

Looking at the listing, the next method is a helper that serializes objects into xml so they can be written to the database. This is a very important piece of functionality.  When a rule is invoked by the system, it is passed an instance of the ConversationContext class.  You can see the ConversationHistory property on this class which contains the entire history of the conversation.  Most of these entries have a piece of context that helps to either explain why the system responded the way it did or to store a piece of information that may be useful later.  For example, suppose you ask the system to list all the reminders it has for you.  These will be returned to you in a numbered list.  Suppose you’d like to delete the third item in the list.  You could do that by telling the system to “delete 3”.  How does the system know what was listed, much less which in the list was number three?  It does this by walking backward through the conversation history until it finds a list that was stored in the tag.  It then uses that list.

As another example, suppose you ask the system to remind you to meet with Phil.  The system will ask you what time you would like to be reminded.  You reply that you’d like to be reminded at 4:00 pm.  How does the system know what you’re talking about?  You guessed it.  By storing the reminder as a tag.  This is a powerful idea and allows the system to have a stateful conversation via a stateless protocol in a similar way that ASP.Net does with its serialized state.

The last method in the ConversationContext class is AskQuestion().  This is similar to Say() in that it also raised the event to have the reply sent back to the user.  However, it also adds the question to a collection maintained by the QuestionManager.

Next time, we’ll look at the TokenManager and how the commands are tokenized.

Tags: ,

.Net | Natural Language Processing

Writing a Natural Language Parser in C# Part 2 - Architecture

by Administrator 18. March 2012 11:16

This post is part of a series on creating a natural language processor in C#. The other entries in this series are:

Writing a Natural Language Parser in C# Part 1–Why? 
Writing a Natural Language Parser in C# Part 3–CommandProcessor and ConversationContext 
Writing a Natural Language Parser in C# Part 4–Tokens
Writing a Natural Language Parser in C# Part 5 - Questions and Rules

In our last post we discussed why one might be interested in building and using a natural language processor in a business or home project.  This week, I’d like to look at the architecture of the processor I’ve written.  This will be a high-level look at the various pieces of the system, the flow of processing a single sentence and how each piece contributes to the process.

An Activity Diagram

Below is an activity diagram depicting the interaction of the major parts of the system and how they work together to process a sentence sent to the system by the user.

Activity

The steps involved in this process are as follows:

  • The string the user submits, along with a User ID and other bits of information are submitted to the Command Processor.
  • The command processor creates an instance of a conversation context.  This is a very important part of the system as a whole.  It keeps track of what user made the request, what method was used to communicate the request (email, IM, etc.) and has a history of all statements that have occurred in both directions for the duration of the conversation.
  • The system contains a collection of Tokens that know how to inspect the input for certain strings or conditions.  They generate a TokenResult instance for each interesting piece of the statement and records where the interesting part begins in the string and how long it is.  The token result also records a strong type that indicates the kind of thing that was found such as a certain phrase, a date or the name of something it knows about.
  • After all the tokens have had a chance to process the string, the resulting TokenResults all exist in a single collection.  This collection is then organized into buckets where each bucket corresponds to a start position in the string.  For example, all interesting things found to have begun at position zero would be in a bucket together.  How can there be more than one token result for the same phrase?  Well consider that the string contained a numeral “1”.  This could represent an integer, a long, a decimal, and ordinal (think “first” as in the first day of the month or first day of the week), etc,
  • Rules are methods that return void and have any number of parameters which are each a type of token.  These rules are all defined in classes that are marked with a particular interface and are obtained via reflection.  After all the token result instances have been organized into buckets, each of these methods is inspected and its parameters are compared to the contents of each bucket, in order, to see if a match for the parameter is found in the corresponding bucket. 
  • Once a matching rule is found, it is executed by passing in the conversation context and all the matching tokens.  The context can be used from within the method to inspect the history of the conversation and also to send responses back to the user.

An Example

Lets’ look at a simple example.  Even if this example does not make complete sense to you right now, as we look into each piece of the system in more detail, it will become clear to you.

Let’s say the user sends in a request, “What’s the weather”.  Once the tokens have all had a chance to look at this string, we will have a collection of token results.  In fact, we will have two results.  The first will say that “What’s the” can be tokenized into a TokenList beginning at position 0 in our string.  The second will say that the remainder of the string can be tokenized into a TokenWeather beginning at position 10 (I know this seems like an incorrect position, but I will explain in a future post).

Now, these two tokens will be put into a couple of buckets and the system will begin to go through all the defined rules.  Once of these rules has a signature like this:

Weather Rule
  1. public static void GetWeather(ConversationContext cContext, TokenList list, TokenWeather weather)

Since TokenList matches what’s in our first bucket and TokenWeather matches what’s in the second, this will be the matching rule and will be executed.

Threading

It should be noted that the entire process diagrammed above executes on a single thread.  However, much like a web server, each request is handled on a separate thread that is initiated by classes that listen on different protocols.

Tags: ,

.Net | Natural Language Processing

Writing a Natural Language Parser in C# Part 1–Why?

by Administrator 9. March 2012 11:35

Recently, I did a refactor of several key pieces of the natural language processor I created for my smart home.  Spending the time refocusing on how it works convinced me to write a series of posts explaining how it works and how it was built.  In this first installment, I'll look at why an NLP engine is a useful piece of software and other things it could be used for.

Originally Written for Smart House

Obviously, I created this engine for use in my smart house.  As I've said many times, the original idea was inspired by similar work done by Ian Mercer.  By making this a part of my smart house, I can communicate with the system to request information and ask it to carry out work for me using natural language.  What's more, since the interface to my system is nothing more than text, I can communicate through any channel that supports text: email, instant messaging, SMS, etc.  By using text to speech and speech recognition functionality available on multiple platforms, I can even have a conversation with the system through speech.

Is There Room for NLP in Business

Is there a place for this interface in business? I think so.  UX technologies are changing rapidly.  Touch is an expected interface in most devices, now.  Certainly mobile phones are expected to support touch, as well as tablet computers.  The Kinnect introduced motion gestures for gaming and this method of interacting with systems is coming to PCs.  The point is that interfaces not involving a keyboard and mouse are getting more common-place and natural language certainly has a place at the table.

Some examples of business use might include:

  1. An ad-hoc query system that lets users ask for what they want from the data source using sentences. 
  2. Personal Assistant software that could ask the user for direction on what to do with emails or documents and allow the user to specify answers in natural language.
  3. Conference room software that controls displays and / or whiteboards via natural language requests.

I've found working with this technology to be a lot of fun and the results have been satisfying.  I invite all of you to follow this series to see how it all works and to contribute your own ideas.

In part 2, we'll look at the architecture of the system.  From there, we'll take deep dives into the individual parts.

Tags: ,

.Net | Natural Language Processing

How My Windows Phone Simplifies My Life

by Administrator 21. January 2012 08:27

I’ve had my Windows Phone 7 for about a year, now, and I’ve found it to be the useful, flexible device I’ve owned.  Plus, it’s fun to use.  Over the course of the past year, I’ve laid several other devices and programs aside and have, instead, allowed my phone to take over their former roles.  Below, I’d like to talk a bit about the various ways that my Windows Phone has become the central device in my life.

Social Integration

I find that the integration of Facebook, LinkedIn and twitter into my phone has virtually eliminated the need for me to ever have to open any of these sites in a browser.  Granted I’m not a super user of any of these sites, but other than profile updates and the like, the phone offers all I need.  I’m notified of any Tweets directed at me or any mentions of me on Facebook, any person from my contact list that I pin to the start screen allows me to immediately see a change in their status and the People Hub allows me to browse a consolidated feed of all the news from all the sites.  In addition, I can upload photos to my accounts, post a status or check in.

Google Analytics

It’s fun to follow the traffic and popular content on my blog.  Used to be, I had to browse to the Google Analytics site to get this information.  However, I know open an app on my phone, Phonealytics Free, and see a graph of my hits by date as well as by post.  A nice application that allows me to see what I want to with a couple of taps and swipes.

GPS

One device that I used to keep in my Jeep was a Gamin Nuvi GPS.  The maps were getting old and the windshield mount had just broken.  About this same time, I became aware of the Garmin StreetPilot app for windows phone.  I downloaded the trial and gave it a try.  I was amazed at how much better the features were in the phone app compared to the device.  Within a week of obtaining the trial version of the application, I upgraded to the full version and have used it daily since.  The app offers turn-by-turn directions via voice (with street names), traffic information and delays, photos of highway exits and information on what lanes you need to be in for the next turn.  You can save favorite locations, as well.   There is never an issue with the age of the maps or information about places of interest as the maps are downloaded as needed and the app uses Google Search to find locations.

Music and Podcasts

A second device I always kept with me was a 128 GB Zune.  I kept my entire music collection on the device and periodically updated podcast feeds to it.  My Jeep has a plug for auxiliary inputs and I’d just plug the Zune into the Stereo and listen to this media as I drove.  The Windows Phone Marketplace now has a podcast section that allows me to subscribe to podcasts on the phone.  I can specify how many episodes to keep on the phone and the phone automatically updates my subscriptions over wifi when it has access to it.  This has allowed me to always have fresh podcast content without having to worry about plugging a device into my PC for syncing.

It would be impossible for me to keep my entire music collection on my phone, but it would be great to have access to all of it via streaming.  I recently found the tversity application in the marketplace and gave it a spin.  The app requires that you install an application on a PC containing your content and that application exposes the titles to the phone app.  I already had my collection on a Homeserver, so I installed the tversity application and configured it to expose not only my music collection, but also my videos.  Now, I can stream my music to my phone wherever I am, over 3G.  The video is a bit jerky over 3G, but works great over WiFi.  Another device I no longer need.

There are many other apps that I used almost every day that make my life simpler.  Access to cloud storage from any device with the SkyDrive app.  Access to OneNote notes from anywhere make it easy to stay organized.  The Lync app allows me to stay in touch with coworkers via text and voice.

My phone is easily one of the best purchases I’ve ever made and I’m excited to see what Microsoft does with the platform going forward.

Tags: , ,

SmartPhone | Productivity | WP7

A Completely Dynamic View Model

by Administrator 29. December 2011 11:43

Recently, I was working on a project where there was a requirement to print certain documents.  These documents were stored, in pieces, in the database and then loaded and assembled at runtime.  This allowed parts of the documents to be reused as needed.

The Xaml for these documents contained bindings to data that originated from the database.  The usual drill would be to statically define all the required properties on the view model and then write code to populate them from the database.  There's nothing wrong with this approach, but the project was in a mode where new documents were being generated quickly and they required new properties to be defined on the view model making the view model code a hot spot.

The data required in the bindings was largely stored as name/value pairs in the database.  Wouldn't it be much nice if new values added to the database just showed up at runtime without having to create new properties and the code to populate them?

Using dynamic objects would be a great way to go, if you could bind to them.  Unfortunately, they don't support reflection which is required for binding.  A second problem is that adding properties to a dynamic object requires a syntax like this:

Connect to XMPP
  1. dynamic vm = new object();
  2.             vm.PropertyName = 5;

Which, of course, requires you to know all property names to be added, ahead of time.  Since I'd like to be able to add properties that I find in the database, this is unsuitable.

I did some research and found this blog post by Lester Lobo.  The solution accompanying the post contained this class.

Connect to XMPP
  1. using System.Collections.Generic;
  2. using System.Collections.ObjectModel;
  3. using System.ComponentModel;
  4. using System.Dynamic;
  5. using System.Windows.Data;
  6. using System;
  7.  
  8. namespace DynamicVM
  9. {
  10.     public class DynamicObjectClass : DynamicObject, INotifyPropertyChanged
  11.     {
  12.         #region DynamicObject overrides
  13.  
  14.         public DynamicObjectClass()
  15.         {
  16.         }
  17.  
  18.         public override bool TryGetMember(GetMemberBinder binder, out object result)
  19.         {
  20.             return members.TryGetValue(binder.Name, out result);
  21.         }
  22.  
  23.         public override bool TrySetMember(SetMemberBinder binder, object value)
  24.         {
  25.             members[binder.Name] = value;
  26.             OnPropertyChanged(binder.Name);
  27.             return true;
  28.         }
  29.  
  30.         public override IEnumerable<string> GetDynamicMemberNames()
  31.         {
  32.             return members.Keys;
  33.         }
  34.  
  35.         public override bool TryGetIndex(GetIndexBinder binder, object[] indexes, out object result)
  36.         {
  37.             int index = (int)indexes[0];
  38.             try
  39.             {
  40.                 result = itemsCollection[index];
  41.             }
  42.             catch (ArgumentOutOfRangeException)
  43.             {
  44.                 result = null;
  45.                 return false;
  46.             }
  47.             return true;
  48.         }
  49.  
  50.         public override bool TrySetIndex(SetIndexBinder binder, object[] indexes, object value)
  51.         {
  52.             int index = (int)indexes[0];
  53.             itemsCollection[index] = value;
  54.             OnPropertyChanged(System.Windows.Data.Binding.IndexerName);
  55.             return true;
  56.         }
  57.  
  58.         public override bool TryDeleteMember(DeleteMemberBinder binder)
  59.         {
  60.             if (members.ContainsKey(binder.Name))
  61.             {
  62.                 members.Remove(binder.Name);
  63.                 return true;
  64.             }
  65.             return false;
  66.         }
  67.  
  68.         public override bool TryDeleteIndex(DeleteIndexBinder binder, object[] indexes)
  69.         {
  70.             int index = (int)indexes[0];
  71.             itemsCollection.RemoveAt(index);
  72.             return true;
  73.         }
  74.  
  75.         #endregion DynamicObject overrides
  76.  
  77.         public void AddProperty(string propertyName, object value)
  78.         {
  79.             members[propertyName] = value;
  80.         }
  81.  
  82.         #region INotifyPropertyChanged
  83.  
  84.         public event PropertyChangedEventHandler PropertyChanged;
  85.  
  86.         private void OnPropertyChanged(string propertyName)
  87.         {
  88.             if (PropertyChanged != null)
  89.                 PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
  90.         }
  91.  
  92.         #endregion INotifyPropertyChanged
  93.  
  94.         #region Public methods
  95.  
  96.         public object AddItem(object item)
  97.         {
  98.             itemsCollection.Add(item);
  99.             OnPropertyChanged(Binding.IndexerName);
  100.             return null;
  101.         }
  102.  
  103.         #endregion Public methods
  104.  
  105.         #region Private data
  106.  
  107.         Dictionary<string, object> members = new Dictionary<string, object>();
  108.         ObservableCollection<object> itemsCollection = new ObservableCollection<object>();
  109.  
  110.         #endregion Private data
  111.     }
  112.  
  113. }

This class was almost just what I needed.  It solved the problem of not being able to bind to a dynamic, but I still needed a way to add properties on the fly.  For this, I added the AddProperty() method, which you'll see in the above listing.

Using the class is easy.  Below is an example.

Connect to XMPP
  1. AValue = new DynamicObjectClass();
  2.             AValue.Foo = "Hello"; //use the out-of-the-box syntax for adding a property
  3.  
  4.             AValue.AddProperty("Bar", 5); //add a property discovered at runtime.
  5.             AValue.AddProperty(propName, propValue);

So, using the above, I can simply read the name/value pairs out of the database and then add each to my view model at  runtime.  This means that any new value added to the database will automatically be available on the now dynamic view model for binding.

One Offs

Of course, you don't have to use a completely dynamic view model.  It's just as easy to define properties at design time and have one of those properties be a dynamic type.  Also, it's possible to have a dynamic view model that contains a property that is, itself, a dynamic.

Tags: , , ,

.Net | Database | WPF | dynamic

An Introduction to KnockoutJS for XAML Developers

by Administrator 17. December 2011 18:58

I've recently been working with KnockoutJS and have found it to be a very comfortable paradigm to use in constructing a web site.  Having worked with XAML a good bit over the last few years, the benefits of MVVM have been made apparent to me time and again and the approach is familiar.

KnockoutJS lets you use very nearly the same MVVM approach you're used to using in WPF or Silverlight development.  I've attached a sample application, below.  Let's have a look at it.

If you run the sample application, you'll see the following web page open in the browser.

RunningApp

There is an editable table that allows the user to edit existing contacts. Clicking the Add Contact button adds a new blank row to the table.

The ViewModel

The project contains a Javascript file named ViewModel.cs, in the Scripts directory.  The contexts of this file are listed below.

   1:  var viewModel = {
   2:      contacts : ko.observableArray([
   3:          { firstName : "Bob", lastName : "Marley" },
   4:          { firstName : "Larry", lastName : "The Cable Guy" },
   5:          { firstName : "Maxwell", lastName : "Smart" }
   6:      ]),
   7:   
   8:      addContact : function() {
   9:          this.contacts.push( { firstName : "", lastName : "" } );
  10:      }
  11:  }
  12:   
  13:  $(document).ready(function() {
  14:      
  15:      ko.applyBindings(viewModel); 
  16:  })
 
 

In the first code block, lines 1 – 11, we create a view model.  In WPF or Silverlight, out view model would contain properties and commands that we could bind to in our view.  The same is true with KnockoutJS.  Here, we have a property that is a collection of contacts and a function (command).  In lines 13 – 16, we ask Knockout to parse our view and connect the data bindings to our view model.

Notice that our property is an instance of an observableArray.  In XAML data binding, we have the concept of  the view and view model communicating with one another where events are raised when values change so that data and the controls bound to it stay in sync.  This same functionality is encapsulated in the observableArray for arrays and observable for simple properties.    So, modifying a bound property from the view model will cause a control in the view that is bound to the property to change, as well.  The reverse is also true, modifying the contents of the control will update the view model property.

The View

   1:  @{
   2:      Layout = null;
   3:  }
   4:   
   5:  <!DOCTYPE html>
   6:  <html>
   7:  <head>
   8:      <title>Index</title>
   9:      <script src="../../Scripts/jquery-1.5.1.js" type="text/javascript"></script>
  10:      <script src="../../Scripts/jQuery.tmpl.js" type="text/javascript"></script>
  11:      <script src="../../Scripts/knockout-1.2.1.js" type="text/javascript"></script>
  12:      <script src="../../Scripts/ViewModel.js" type="text/javascript"></script>
  13:  </head>
  14:  <body>
  15:      <div>
  16:          <fieldset>
  17:              <legend>Email Addresses</legend>
  18:   
  19:                  <table>
  20:                  <thead>
  21:                      <tr>
  22:                          <th>First Name</th>
  23:                          <th>Last Name</th>
  24:                      </tr>
  25:                  </thead>
  26:                  <tbody 
  27:                    data-bind='template: { name: "contactRowTemplate", foreach: contacts }'>
  28:                  </tbody>
  29:              </table>
  30:   
  31:              <button data-bind="click: addContact">Add Contact</button>
  32:   
  33:              <script type="text/html" id="contactRowTemplate">
  34:                  <tr>
  35:                      <td><input class="required" 
  36:                          data-bind="value: firstName, uniqueName: false"/></td>
  37:                      <td><input class="required" 
  38:                          data-bind="value: lastName, uniqueName: false"/></td>
  39:                  </tr>
  40:              </script>
  41:   
  42:          </fieldset>
  43:      </div>
  44:  </body>
  45:  </html>
 

We start by setting up our html table.  All probably looks familiar up to line 27.  In Xaml, we use Binding expressions to state what we want to bind to and how that binding is to behave.  KnockoutJS uses the "data-bind" attribute to specify these bindings.  In line 27 a binding is expresses that says, "for each item in the collection of contacts on the view model, create and populate an instance of the template named 'contactRowTemplate'".  You can see the template definition in lines 33 – 40.

Compare this binding with a similar Xaml binding and you'll find:

  • It's possible to do an ItemsSource type binding in KnockoutJS where an item is generated for each entry in an array.
  • Adding or removing an item from the array causes row corresponding to that item to appear or disappear respectively.
  • A template can be specified for how each item in the list will be constructed.  Compare this with the ability to specify an ItemTemplate in Xaml (note that you can organize the container of the list as you wish which is very similar to ControlTemplate's.
  • In Xaml, the data context flows in a predictable way through all an item's sub-items.  Note that the binding on the <tbody> element is to an array and the bindings inside the template are to a single item in the array.  This behaves the same as in Xaml.

The button in line 31 uses a click binding to bind the click event to the function we defined on our view model.

Yes, the binding syntax is different and there are many more binding types available than are used in this simple sample, but the library is very well documented here and the familiarity of the MVVM approach to constructing a UI make KnockoutJS a very easy library to get started with.

Download File - KnockoutSampleSolution

Tags: , , , ,

.Net | ASP.Net | JavaScript | Silverlight | WPF

What is a JavaScript Closure, Anyway?

by Administrator 10. December 2011 14:15

Back in the late nineties, during the .com boom, I worked on a huge web application that used XmlHttp (now known as AJAX) and extensive lots of JavaScript.  The language and how it's perceived has changed a lot since then and so has the maturity of the community using it.  Techniques have come about that make JavaScript easier to use in large internet applications.  Most of these techniques have come about because developers are learning to go with the flow of the language instead of fighting it; to use the unique qualities of the language to advantage.

An Example

Consider the following JavaScript:

   1:  function MyClass(x) {
   2:     var Addend = x;
   3:   
   4:      return AddTo = function (y) {
   5:          return y + Addend;
   6:      }
   7:  }
   8:   
   9:  var AddToTen = MyClass(10);
  10:  alert(AddToTen(10));
  11:   
  12:  var AddToFive = MyClass(5);
  13:  alert(AddToFive(10));

Beginning in line 1, a function is being defined.  Inside this function, we define a local variable and set its value equal to the passed in parameter.  Then, an inner function is returned.  This inner function refers to the local variable defined in the outer function.  Once the outer function has completed execution, it cannot be garbage collected because it is still being referred to by the inner function.  It's also very important to understand that whatever value the local variable has at the time the inner function is returned, is the value that will be visible to the inner function when it is executed from an external call.

When lines 9 and 10 run, 10 is passed in to the outer function and the local variable is set to 10.  When the inner function is returned, it will always see Addend as having the value of 10.  Thus, this script will pop the first alert with a value of 20 and then a second with the value of 15. 

In short, whenever an inner function is returned from an outer one, a logical copy of the outer functions state is made available to the inner function and this state can be accessed each time the inner function is invoked.

What About a .Net Example

We can duplicate this behavior in C#.  I created a Console application to illustrate this.

    class Program
    {
        static void Main(string[] args)
        {
            var five = MyMethod(5);
            Console.WriteLine(five(10));

            var ten = MyMethod(10);
            Console.WriteLine(ten(10));
            Console.ReadLine();
        }

        static Func<int, int> MyMethod(int x)
        {
            int addend = x;

            return new Func<int, int>((y) =>
            {
                return addend + y;
            });
        }
    }

Tags: , ,

.Net | ASP.Net | JavaScript