Ganesh Ranganathan

Jan 242012
 

Reusability is one of the most underrated aspects of software development. The focus on meeting functional requirements in a product as quickly as possible often overshadows the reusability and maintainability part. Code duplication, poor adherence to design are often symptoms of such development which increase costs in the long term in favour of short term benefits.

WPF encourages creation of reusable controls, primarily by its support for creation of “lookless controls” where the client of the control is not only able to reuse the control, but also completely alter its appearance. The control in essence is just a bundle of logic with a default template. Its upto the client whether to accept this default skin or override it with one of his own. Though this concept looks similar to themes and skins in other technologies, its extremely powerful that you can alter the visual appearance of the control at a granular level. If the default template defines a button and a textbox, that can easily be changed to an filled rectangle with a Linear Gradient and a TextBlock to display the data.

Difference between User Controls and Custom Controls.

Custom controls arent the only type of control library that WPF offers. There is also the User Control library. The difference between the two lies in our end purpose. If we are simply looking to bundle a few controls together and provide basic customization to the end user, then User Controls are the way to go. They are also much more easier to use than Custom Controls. However, if your aim is to provide full customization capability to the developer who consumes your control, then Custom Controls are much better.

Dependency Properties

Dependency Properties are quite different from the conventional properties which we use in C#, and are exclusive to WPF. They do not belong to any particular class and their value can be set sources other than just the class itself. The values could come from their default values, styles, themes, callbacks etc. They also support Data binding so your UI elements can directly bind to them and update whenever the value changes, like how properties in classes implementing INotifyPropertyChanged events behave.

Custom Control Example

The custom control example I build is a filled rectangle with a slider. As you increase the slider the fill percentage of the rectangle increases as well. Here is an illustration

The control is quite simple. There are two main properties here which are exposed to the outer world – The FillColor and the EmptyColor denoting the colors of the rectangle. The third property is the Value of slider which is used within the control, but that too could be exposed out of the control. Lets see the code for the control. ( Notice the absence of any UI stuff)

public class FilledBarControl : Control
    {
        public FilledBarControl()
        {
            DataContext = this;
        }
        
        static FilledBarControl()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(FilledBarControl), new FrameworkPropertyMetadata(typeof(FilledBarControl)));
          
        }

        public static readonly DependencyProperty EmptyColorProperty = DependencyProperty.Register("EmptyColor", typeof(Color), typeof(FilledBarControl), new UIPropertyMetadata((Color)Colors.Transparent));

        public Color EmptyColor
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (Color)GetValue(EmptyColorProperty);
            }
            set
            {
                SetValue(EmptyColorProperty, value);
            }
        }
        

        public static readonly DependencyProperty FillColorProperty = DependencyProperty.Register("FillColor", typeof(Color), typeof(FilledBarControl), new UIPropertyMetadata((Color)Colors.Red));

        public Color FillColor
        {
            // IMPORTANT: To maintain parity between setting a property in XAML and procedural code, do not touch the getter and setter inside this dependency property!
            get
            {
                return (Color)GetValue(FillColorProperty);
            }
            set
            {
                SetValue(FillColorProperty, value);
            }
        }
    }

As you can see, there are two steps to declaring a dependency property – First is the registering of the property using the DependencyProperty.Register method and the second is the definition of the getter and setter methods. The naming convention for the DependencyObject is to append “Property” after the name of the Dependecy Property. Hence here the object becomes FillColorProperty. You can also define the default values for the Property in the Register method. It needs to be passed inside the constructor of the PropertyMetadata object. I used Red Color here, so if the developer doesnt pass any value for the FillColor, the control automatically chooses Red.

Like written before, a custom control is simply defined in code, it doesn’t necessarily need a UI to exist – the UI can be supplied by the developer using the control. The default look and feel for the control is defined in a separate file – Generic.xaml inside the themes folder. Here is the xaml for the filled Bar control.

<Style TargetType="{x:Type local:FilledBarControl}">
        <Setter Property="Template">
            <Setter.Value>
                <ControlTemplate TargetType="{x:Type local:FilledBarControl}">
                    <UniformGrid Columns="1">
                        <Border BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}" >                           
                            <Rectangle Height="{TemplateBinding Height}"
                            Width="{TemplateBinding Width}">
                            <Rectangle.Fill>
                                <LinearGradientBrush StartPoint="0,0" EndPoint="1,0">
                                    <GradientStop Color="{Binding Path=FillColor}"
                        Offset="0"/>
                                    <GradientStop Color="{Binding Path=FillColor}"
                        Offset="{Binding ElementName=slider, Path=Value}"/>
                                    <GradientStop Color="{Binding Path=EmptyColor}" 
                        Offset="{Binding ElementName=slider, Path=Value}"/>
                                </LinearGradientBrush>
                            </Rectangle.Fill>
                        </Rectangle>
                        </Border>

                        <Slider x:Name="slider" Width="200" Height="50" 
            Minimum="0" Maximum="1" Value="0.2"/>
                    </UniformGrid>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>

The XAML is just a control template containing a Uniform Grid. One of the rows is the Rectangle and the other a Slider for the value. The rectangle’s fill tag is a LinearGradientBrush with two phases. One from the Start Point (0,0) to the Value (bound to the Slider’s value) and another which starts from the Value to the end point. This gradient gives the impression of a filled rectangle whose fill percentage changes as the slider is dragged.

Now, how can a host application change the appearance of the control? There are two ways – one using the Dependency Properties and other completely overriding the Control Template itself. As you can recall, there were two dependency Properties defined in the FilledBar.cs – The FillColor and the EmptyColor. Both these properties appear in the Intellisense while defining the control in the XAML. An example of such customization would be

         <FilledBar:FilledBarControl HorizontalAlignment="Center" 
                                    VerticalAlignment="Center"  
                                    Height="100" Width="200" 
                                    BorderThickness="2" BorderBrush="#003300" 
                                    FillColor="Maroon" EmptyColor="LightGreen" />

This is how the control now looks. Note that both colours have changed as per our definition

The second form of customization is what makes Custom Controls so much more powerful than ordinary User Controls. Lets assume that a rectangular fill bar doesnt suit my requirement and my application would look better with a FilledCircle rather than a FilledBar. Rather than writing the entire control again just for one change, I could just swap out the Rectangle and substitute it with an Ellipse whose Fill is done by a RadialGradientBrush. These changes do not require any changes to the original control itself – a style can be created in the Resource Dictionary and referred to by the code.

            <Style TargetType="{x:Type FilledBar:FilledBarControl}" x:Key="FilledCircle">
                <Setter Property="Template">
                    <Setter.Value>
                        <ControlTemplate TargetType="{x:Type FilledBar:FilledBarControl}">
                            <UniformGrid Columns="1" Width="{TemplateBinding Width}" Height="{TemplateBinding Height}" >

                                <Ellipse Height="100" Width="100" Stroke="{TemplateBinding BorderBrush}" StrokeThickness="{TemplateBinding BorderThickness}"
                            >
                                        <Ellipse.Fill>
                                            <RadialGradientBrush >
                                                <GradientStop Color="{Binding Path=FillColor}"
                        Offset="0"/>
                                                <GradientStop Color="{Binding Path=FillColor}"
                        Offset="{Binding ElementName=slider, Path=Value}"/>
                                                <GradientStop Color="{Binding Path=EmptyColor}" 
                        Offset="{Binding ElementName=slider, Path=Value}"/>
                                            </RadialGradientBrush>
                                        </Ellipse.Fill>
                                    </Ellipse>
                              

                            <Slider x:Name="slider" Width="200" Height="50" 
            Minimum="0" Maximum="1" Value="0.2"/>
                        </UniformGrid>
                        </ControlTemplate>

                    </Setter.Value>
                </Setter>
                
            </Style>

The only changes in the above style tag are the changing to ellipse and RadialGradientbrush. Note that instead of using the Border, we just bind the BorderBrush and BorderThickness properties to the Stroke and StrokeThickness properties of the Ellipse. This allows setting of the properties just as we did with the unchanged controls and is much more easier to read. The control is declared in xaml with the style tag referring to our user dictionary which overrides the default style written in the Generic.xaml

        <FilledBar:FilledBarControl HorizontalAlignment="Center" VerticalAlignment="Center" 
                                    Grid.Row="3" Grid.Column="1"  
                                    Style="{StaticResource ResourceKey=FilledCircle}"  
                                    Height="200" Width="200" 
                                    BorderThickness="2" BorderBrush="Brown" 
                                    FillColor="Blue" EmptyColor="Transparent">

Here is how the control looks now.

We can have multiple instance of the same control using different styles and values for Dependency Property. An example of the Host Application

Jan 202012
 

I wrote a small application to extract post and user data for groups in the popular enterprise microblogging portal, Socialcast. It lets users access data instantly about the activity going on in their groups and the number of likes/comments being posted by group members.

The details about the API can be found here and their side demo.socialcast.com can be used for testing. There are three main calls in the application

  • Message Data for the group (Using the parameters Group_Id and since to filter by the data)
  • To find the group Id using the group name using the Groups.xml API call.
  • Then finding the group members using groupname/members.xml.

The method to retrieve group_id from the groupname needed improvement. Most organizations contain upwards of a 1000 groups. Making two heavy API calls for finding out the group id from the name is a bit of a drag on the performance of the application. To avoid this, I just cached all the group Ids and names in a Dictionary<string, int> object and used that to lookup the id. For groups that were created after the tool was distributed, the API call was a fallback.

The UI of the tool is fairly simple. Just a textbox to enter the group URL and two datagridviews to display the data along with two listboxes to display the stats. Since data retrieval is a very slow process, a backgroundworker with a progress bar make sure the application doesnt freeze and the user is shown some progress.  There is also a dropdown to select the time for which the user wants to retrieve messages and whether the member information is required or not. The quickest options are selected by default.


The API access code is in a library which hides the webservice calls from the client application. Each method contains the standard page and number of records per page argument. Apart from them, there are other parameter which are needed to make the call e.g GroupName, member id etc. To simply the URL construction for the web service call a class contains the skeleton urls for each call like given below

public static class ObjectType
 {
 static string defaultFormat = ".xml";
 public static string Users = "users" + defaultFormat;
 public static string Streams = "streams" + defaultFormat;
 public static string Messages = "messages" + defaultFormat;
 public static string StreamMessages = "streams/{0}/messages" + defaultFormat;
 public static string MessagesById = "messages/{0}" + defaultFormat;
 public static string SearchUsers = "users/search" + defaultFormat;
 public static string Groups = "groups" + defaultFormat;
 public static string GroupMembers = "groups/{0}/members" + defaultFormat;
 }

public class SocialCastData
 {
 ///
<summary> /// These are the private variables which are configured
 /// as per your socialcast site
 /// </summary>
 string skeletonURL = "https://{0}.socialcast.com/api/{1}";
//Rest of code here
}

The method to construct the service url takes in the Object Type member, and appends it to the skeleton url along with filling in the domain of the socialcast site and any additional parameter (e.g. group id). This approach allows me to add new service calls easily with just one more variable in the ObjectType class. Any additional query string parameters are supplied using the serviceParams which is a list of keyvaluepairs. The list is iterated through and appended after the url. The SocialcastAuthDetails object is a must in every calls since it contains the domain, the username and password all of which are required to be supplied for getting the response.

Here is the method to get the Group ID from the groupName. The GroupQuery.QueryForGroupId accesses the cache and returns immediately if found. If not then another API call is made to get all the groups, iterate through them and identify the id. If the group id is still not found, an exception is thrown.

        private int GetGroupIdByGroupName(string groupName,SocialCastAuthDetails auth)
        {

            int _groupId = 0;
            int _pageNumber =1;
            bool moreItems = true;

            _groupId = GroupsQuery.QueryForGroupID(groupName);
            if (_groupId != 0)
                return _groupId;
            else
            {

                while (moreItems)
                {
                    XmlDocument group = new XmlDocument();
                    var serviceParams = new List>();
                    serviceParams.Add(new KeyValuePair("page", _pageNumber.ToString()));
                    serviceParams.Add(new KeyValuePair("per_page", "500"));
                    group.LoadXml(base.MakeServiceCalls(helper.GetSocialcastURL(ObjectType.Groups, auth.DomainName, null, serviceParams),GetCredentials(auth.Username,auth.Password)));
                    if (group.SelectNodes("//groups/group") == null || group.SelectNodes("//groups/group").Count == 0)
                        moreItems = false;
                    else
                    {
                        foreach (XmlNode groupNode in group.SelectNodes("//groups/group"))
                        {
                            if (String.Compare(GetNodeInnerText(groupNode, "groupname"), groupName, true) == 0)
                            {
                                string id = GetNodeInnerText(groupNode, "id");
                                _groupId = Convert.ToInt32(id);
                                GroupsQuery.AddToDictionary(groupName, _groupId);
                                moreItems = false;
                                break;
                            }
                        }
                    }
                    _pageNumber++;
                }
                return _groupId;
            }
        }

In the front end, a background worker is used to perform the time consuming operation asynchronously. Here is the code (Rough, not refactored)

    class GetStatistics
    {
        APIAccessor _api = new APIAccessor();

        public AppData GetData(string urlEnteredbyUser, string sinceWhatPeriod, bool loadUserData)
        {
            var serviceUrl = UrlReplace(urlEnteredbyUser.Trim());
            List<SocialcastMessage> messages = null;
            List<UserProfile> users = null;
            if (serviceUrl == string.Empty)
            {
                throw new GroupNotFoundException(null, "The group Url you have entered is not valid");
            }
            else
            {
                messages = GetMessageStats(serviceUrl, sinceWhatPeriod);
                if(loadUserData)
                        users = GetAllGroupUsers(serviceUrl);
                return new AppData(messages, users);
            }
        }

        private List<UserProfile> GetAllGroupUsers(string groupName)
        {
            List<UserProfile> Users = new List<UserProfile>();
            int userCount = 500;
            int pageNumber = 1;
            bool moreItems = true;
            while (moreItems)
            {
                var xdoc = _api.GetGroupMembers(groupName, pageNumber.ToString(), userCount.ToString(), GetCredentials());
                if (xdoc.SelectNodes("//users/user") == null || xdoc.SelectNodes("//users/user").Count == 0)
                    moreItems = false;
                else
                {
                    foreach (XmlNode userNode in xdoc.SelectNodes("//users/user"))
                    {
                        string name = GetNodeInnerText(userNode, "name");
                        string userName = GetNodeInnerText(userNode, "username");
                        string email = GetNodeInnerText(userNode, "contact-info/email");
                        int followingCount = Convert.ToInt32(GetNodeInnerText(userNode, "following-count"));
                        int followersCount = Convert.ToInt32(GetNodeInnerText(userNode, "followers-count"));

                        Users.Add(new UserProfile()
                        {
                            Name = name,
                            userName = userName,
                            Email = email,
                            FollowerCount = followersCount,
                            FollowingCount = followingCount
                        });
                       
                    }
                    pageNumber++;
                }
            }

            return Users;
        }

        private List<SocialcastMessage> GetMessageStats(string serviceUrl, string sinceWhatPeriod)
        {
            List<SocialcastMessage> Messages = new List<SocialcastMessage>();
            int messageCount = 500;
            int pageNumber = 1;
            bool moreItems = true;
            long sinceWhen = GetSinceWhenValue(sinceWhatPeriod);
            while (moreItems)
            {
                var xdoc = GetMessagesForTheGroup(serviceUrl, messageCount, pageNumber, sinceWhen);
                if (xdoc.SelectNodes("//messages/message") == null || xdoc.SelectNodes("//messages/message").Count == 0)
                    moreItems = false;
                else
                {
                    foreach (XmlNode node in xdoc.SelectNodes("//messages/message"))
                    {
                        DateTime createdDate;
                        DateTime.TryParse(GetNodeInnerText(node, "created-at"), out createdDate);
                        string ticksLastUpdatedDate = GetNodeInnerText(node, "last-interacted-at");
                        var epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
                        epoch = epoch.AddSeconds(Int64.Parse(ticksLastUpdatedDate));
                        string permaUrl = GetNodeInnerText(node, "permalink-url");
                        string commentsCount = GetNodeInnerText(node, "comments-count");
                        string likesCount = GetNodeInnerText(node, "likes-count");
                        string title = GetNodeInnerText(node, "title");
                        string user = GetNodeInnerText(node, "user/name");

                        Messages.Add(new SocialcastMessage()
                        {
                            Title = title,
                            Url = permaUrl,
                            CreatedDate = createdDate,
                            UpdatedDate = epoch,
                            Comments = int.Parse(commentsCount),
                            Likes = int.Parse(likesCount),
                            UserName = user
                        });

                    }
                    pageNumber++;
                }
            }


            return Messages;

        }


        private long GetSinceWhenValue(string sinceWhatPeriod)
        {
            switch (sinceWhatPeriod)
            {
                case "All Messages":
                    return 0;
                case "Since Last week":
                    return GetTicksSince1970(7);
                case "Since Last Month":
                    return GetTicksSince1970(30);
                default:
                    return 0;
            }
        }

        private static long GetTicksSince1970(int days)
        {
            DateTime sinceLastWeek = DateTime.Now.Subtract(new TimeSpan(days, 0, 0, 0, 0));
            DateTime epoch = new DateTime(1970, 1, 1);
            return Convert.ToInt64(sinceLastWeek.Subtract(epoch).TotalSeconds);
        }


        private XmlDocument GetMessagesForTheGroup(string streamName, int numberOfPosts, int page, long sinceWhen)
        {
            string sinceWhenString = null;
            if (sinceWhen != 0)
                sinceWhenString = sinceWhen.ToString();
            var xdoc = _api.GetStreamMessages(streamName, numberOfPosts.ToString(), page.ToString(), sinceWhenString, GetCredentials());
           return xdoc;
        }


        private string UrlReplace(string enteredByUser)
        {
        
            string regexPattern = @"https://demo.socialcast.com/groups/([A-Za-z0-9\-]+)";
            Match match = Regex.Match(enteredByUser, regexPattern,
                RegexOptions.IgnoreCase);

            // Here we check the Match instance.
            if (match.Success)
            {
                // Finally, we get the Group value and return it
                string key = match.Groups[1].Value;
                return key;
            }
            return string.Empty;
        }

        private SocialCastAuthDetails GetCredentials()
        {
            return new SocialCastAuthDetails()
            {
                Password = "demo",
                Username = "emily@socialcast.com",
                DomainName = "demo"
            };

        }

        private string GetNodeInnerText(XmlNode node, string xpath)
        {
            try
            {
                if (node.SelectSingleNode(xpath) != null)
                {
                    return node.SelectSingleNode(xpath).InnerText.Trim();
                }
                return String.Empty;
            }
            catch
            {
                return string.Empty;
            }
        }
    }
Dec 242011
 

I have been spending some of my free time trying to build a complete cricket statistics database by parsing records from Cricinfo. However scraping HTML pages is an ardous task. There is simply no standard way of achieving it and often becomes a struggle with regular expressions. A good solution to this problem is the Html Agility Pack. Its a library which standardizes parsing of HTML pages and converts them into a XML style DOM object that you can extract data from. There are a good number of options for error checking (for HTML which is not XHTML compliant)

The API is very similar to the XmlDocument class in System.Xml namespace and hence there is hardly any learning curve. You can search for nodes based on the Xpath expression of the element you want to search. Now getting the xpath can be a bit tricky, so an easier way would be to use a chrome extension called XPath Helper. Once this extension is installed and activated, press Ctrl+Shift+X to activate and then shift to give the xpath of any particular element on which the mouse is hovering. The given XPath can be easily tailored to get the whole set of data which we need to extract.

Now, its time to start scraping. Download Html Agility pack from Codeplex and add a reference to the dll. Its a pretty simple code to get the webpage as a string , then load it in the HTML Agility pack and let it create the DOM structure. Then the XPath is used to get the list of rows in the table and each row is translated into an innings object and added to a collection. At the end its written to a csv file that can be converted to an excel spreadsheet. The code is pretty rough and I did it more for a trial. When the complete database will be built it will become much more difficult since it would involve parsing of different kind of pages and ensure integrity of data.

class Program
    {
        static void Main(string[] args)
        {
            new ReadText().StartParsing();
        }

        
    }

    class ReadText
    {
        public void StartParsing()
        { 
            string TestUrl = "http://stats.espncricinfo.com/ci/engine/stats/index.html?class=1;filter=advanced;page={0};orderby=start;size=200;template=results;type=batting;view=innings;wrappertype=print";
            Console.WriteLine("Extracting Tests\n\n");
            ExtractInningsView(TestUrl, "..\\..\\AllTestInnings.csv",404);
            Console.WriteLine("Extracting ODIs\n\n");
            string ODIUrl = "http://stats.espncricinfo.com/ci/engine/stats/index.html?class=2;filter=advanced;page={0};orderby=start;size=200;template=results;type=batting;view=innings;wrappertype=print";
            ExtractInningsView(ODIUrl,"..\\..\\AllODIInnings.csv",356);

        }

        private void ExtractInningsView(string statUrl,string fileName,int pageCount)
        {
            List<InningsPlayed> AllInnings = new List<InningsPlayed>();
            for (int j = 1; j < pageCount; j++)
            {
                Console.WriteLine("Reading Page: " + j.ToString());
                string pageText = ReadWebPage(String.Format(statUrl, j));
                var htmlDoc = new HtmlDocument();
                htmlDoc.LoadHtml(pageText);

                for (int i = 1; i < 200; i++)
                {
                    string inningsXpath = "//tbody/tr[@class='data1'][{0}]/td";
                    var nodeList = htmlDoc.DocumentNode.SelectNodes(String.Format(inningsXpath, i));

                    if (nodeList != null)
                    {
                        AllInnings.Add(new InningsPlayed()
                        {
                            Name = nodeList[0].InnerText,
                            Runs = nodeList[1].InnerText,
                            Minutes = nodeList[2].InnerText,
                            BallsFaced = nodeList[3].InnerText,
                            Fours = nodeList[4].InnerText,
                            Sixes = nodeList[5].InnerText,
                            StrikeRate = nodeList[6].InnerText,
                            Innings = nodeList[7].InnerText,
                            Opposition = nodeList[9].InnerText,
                            Ground = nodeList[10].InnerText,
                            StartDate = nodeList[11].InnerText
                        });
                    }
                }
            }
            DumpToFile(AllInnings,fileName);
        }

        private void DumpToFile(List<InningsPlayed> AllInnings,string fileName)
        {
            StreamWriter writer = new StreamWriter(fileName);
            
            StringBuilder builder = new StringBuilder();
            writer.WriteLine("Name,Runs,Minutes,Balls_Faced,Fours,Sixes,StrikeRate,Innings,Opposition,Ground,StartDate");
            int iterations = 0;
            foreach (var inning in AllInnings)
            {
                writer.WriteLine(string.Format("{0},{1},{2},{3},{4},{5},{6},{7},{8},{9},{10}", inning.Name, inning.Runs, inning.Minutes, inning.BallsFaced, inning.Fours, inning.Sixes, inning.StrikeRate, inning.Innings, inning.Opposition, inning.Ground, inning.StartDate));
                iterations++;
                if (iterations % 10 == 0)
                    writer.Flush();
            }
            
        }

        private string ReadWebPage(string Url)
        {
            // Reading Web page content in c# program
            //Specify the Web page to read
            WebRequest request = WebRequest.Create(Url);
            //Get the response
            WebResponse response = request.GetResponse();
            //Read the stream from the response
            StreamReader reader = new StreamReader(response.GetResponseStream());
            return reader.ReadToEnd();
        }

        
    }

    class InningsPlayed
    {
        public string Name { get; set; }
        public string Runs { get; set; }
        public string BallsFaced { get; set; }
        public string Minutes { get; set; }
        public string Fours { get; set; }
        public string Sixes { get; set; }
        public string StrikeRate { get; set; }
        public string Innings { get; set; }
        public string Opposition { get; set; }
        public string Ground { get; set; }
        public string StartDate { get; set; }
    }
Oct 112011
 

Selectors often are faced with a dilemma after a budding talent plays a few matches only to not justify his talent with his performances. To find out how much rope selectors have given players who have ended up perfoming quite well, I took out the figures of all batsmen who averaged above 40 for India (Qualifier – 20 matches). 14 players made the cut overall. Then the cumulative averages over their career were calculated to see how they fared with each test match.

First the current crop of batsmen who average above. Sachin, Sehwag and Dravid come in this club. As we see from the pic, Sachin consistently lifted his game over his formative years and broke into the 50s in his 29th match and except for a brief period never went into the 40s again till now.

Now the next rung of current batsmen who average above 40. Laxman’s case is the most interesting here. He underperfomed for nearly his first 40 matches but then turned the tide on its head after the 2001 australia series. The repeated chances that Laxman had been given have paid off and he is one of the most dependable players in the side.

Dada’s selection was an open and shut case following his classy performance in the debut series.

Here are the retired batsmen who averaged above 45. No surprises here – Both Azhar and Gavaskar had great performances in their initial matches.

Then the retired batsmen who averaged above 40. Here people who improved their performances over time considerably are Siddhu and Vengsarkar.

So it turns out that with the exception of Laxman and Siddhu, most players who averaged above 40 did do well in their initial years.

Sep 052011
 

Last week I found a bug in Firefox/Chrome while rendering HTML pages. If the script tag is self closed, then the rest of the page is not rendered. The tag needs to be closed with </script> and not />. I spent nearly 2 hours in trying to find why it was not working before finally comparing the page with a working application to find the root cause issue.

What doesnt work:-

<script type="text/javascript" src="../../Scripts/scripts.js">

What works

<script src="../../Scripts/scripts.js" type="text/javascript">
</script>

I posted it on my company’s internal microblogging platform and a colleague replied saying that this is not a bug but rather a HTML 4.01 specification to which FF/Chrome are strictly adhering to. Click Here for the page

Though it turned out not to be a bug, I would still appreciate if a more meaningful error message is displayed to the user so it can be corrected. Self closing HTML tags are used quite commonly and it would be great if the browser itself tells you when the closing tag is mandatory

Sep 012011
 

A few days ago, there was a discussion on a cricket site regarding who is the better bowler amongst Waqar/Pollock/Donald. Though very good bowlers in their era, they failed to break into the all time great list of Marshall, Akram, Ambrose, Mc Grath ,Warne amongst a few others.

Donald had sheer pace and meancing swing. Waqar was feared for his toe-crushing yorkers and Pollock though not a express pace – still had amazing discipline with which he picked up wickets. So I pulled up their statistics and this is the nationwide distribution of their average and strike rate.

Waqar’s average is great in Pakistan, Bangladesh, Zimbabwe and Sri Lanka. In Pakistan bowling on flat wickets and maintaining that kind of average is quite laudable. In India the sample size is too small to draw any meaningful conclusions out of it. His main bugbear seems to be bowling in South Africa, England and Oz. In England and Saffers under bowling friendly conditions, Waqar has underperformned by quite a margin. In Australia he averages 40 which is even worse. Donald’s worst bowling performance is in Pakistan where he averages 32 followed by 28 in Oz. Everywhere else, he averages a commendable sub-25 level. Pollock has a similar problem in performing in Australia – averaging 34 there. 28 and 27 in Windies and India are another place where he has not performed at his best.

Lets see their year wise performance in their career.

Waqar’s career: Waqar’s first few years were the cream of his career. From 1990 to 1994, his bowling performances were superlative. It was during his prime that he tormented the English batsman with the lethal toe-crushers and banana swing. However he suffered an injury in 1995 after which his performance became wayward. His place in the team was in and out in the next few years. Though he gave some inspiring performances in the coming years, the waqar of yore was not seen again.

Donald’s career: Donald’s prime was from 96 – 2000. This period was a relatively easier period to bat than the early 90s which still favored bowlers to an extent. Still Donald’s stats dont give that away. His consistency during the prime years was remarkable, going above 20 in just one year. I still remember his delivery to an UAE batsman who had the gall to bat to Donald without a helmet on. The ball was a lethal bouncer which the batsman on the head, who remarkably escaped without any injuries

Pollock’s career: Unlike the other two bowlers being discussed, Shaun Pollock was an allrounder – He didnt have the same express pace that Waqar or Donald had, but he was ever the workhorse who was disciplined and bowled a consistent line and length with great swing. He took 421 wickets at an average of 23 odd, which in itself is quite remarkable. Add to that his batting ability and you have a match winner. Pollock’s prime was from 98 to 2003 barring slightly poor performance in the one year of 2002.

It is very difficult to pick and choose among these three bowlers as who is the best of them. However my personal choice would be to go with Donald, followed by Waqar and Pollock.

Aug 142011
 

Testing the user interface has to be the most boring part of testing software. Not only is it not fun, it can be horribly repetitive as well with you having to check the same functionality over and over again. The time spent on it could be put to use to far better purposes. Automated Testing tools like NUnit and MS Test were developed in order to counter this. Along came processes like Test Driven development that emphasized the need to write test cases and write testable software. Though all this helped a lot, its benefits were confined mostly to the developer himself, it wasn’t of any use to the testers who again put in redundant effort in testing the UI.

It was then that the idea of automating UI testing starting getting thrown about and many such tools and frameworks appeared in the market. These frameworks essentially provide an API to programmatically access the user interface of Windows applications and gives the developer to simulate the end user’s way of accessing our software. One such framework which I found extremely useful was the UI Automation library from Microsoft. It works pretty well with any UI technology including relatively recent ones such as WPF.

At the core of the UI Automation library is the type AutomationElement – All UI elements in the application get wrapped into this AutomationElement and provide a common way of interacting with the UI elements. For e.g. The application window will be an AutomationElement just like a small button inside the window. Both support different set of Actions but share the commmon types, this makes writing code for UI automation extremely simple.

I wrote a simple calculator application and a console application which tests the calculator giving random values and verifying the output. The XAML Code for the WPF application is:-

    <grid x:Name="LayoutRoot">
        </grid><grid .RowDefinitions>
            <rowdefinition Height="1*" />
            <rowdefinition Height="1*" />
            <rowdefinition Height="1*" />
            <rowdefinition Height="4*" />
        </grid>
        <grid .ColumnDefinitions>
            <columndefinition Width="2*" />
            <columndefinition Width="4*" />
        </grid>
        <label Grid.Row="0" Grid.Column="0" Content="First Number" />
        <textbox Grid.Row="0" Grid.Column="1" AutomationProperties.AutomationId="txtFirstNumber" Text="{Binding FirstNumber,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
        <label Grid.Row="1" Grid.Column="0" Content="Second Number" />
        <textbox Grid.Row="1" Grid.Column="1" AutomationProperties.AutomationId="txtSecondNumber" Text="{Binding SecondNumber,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
        <label Grid.Row="2" Grid.Column="0" Content="Result" />
        <textbox Grid.Row="2" Grid.Column="1" AutomationProperties.AutomationId="txtResult" Text="{Binding Result,Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}" />
        
        <grid Grid.Row="3" Grid.Column="0" Grid.ColumnSpan="2">
            </grid><grid .ColumnDefinitions>
                <columndefinition Width="1*" />
                <columndefinition Width="1*" />
                <columndefinition Width="1*" />
                <columndefinition Width="1*" />
            </grid>
            <button Grid.Column="0" AutomationProperties.AutomationId="btnAdd" Content="Add" Command="{Binding Add}" />
            <button Grid.Column="1" AutomationProperties.AutomationId="btnSubtract" Content="Subtract" Command="{Binding Subtract}" />
            <button Grid.Column="2" AutomationProperties.AutomationId="btnMultiply" Content="Multiply" Command="{Binding Multiply}" />
            <button Grid.Column="3" AutomationProperties.AutomationId="btnDivide" Content="Divide" Command="{Binding Divide}" />
   

This creates a simple calculator app with four functions Add, Subtract, Multiply and Divide. Here is a screenshot of the app

The viewmodel is pretty simple as well

    public class MainViewModel : ViewModelBase
    {
       
        private double _firstNumber;
        private double _secondNumber;
        private double _result;
        private RelayCommand _add;
        private RelayCommand _multiply;
        private RelayCommand _subtract;
        private RelayCommand _divide;

        public double FirstNumber
        {
            get { return _firstNumber; }
            set
            {
                _firstNumber = value;
                RaisePropertyChanged("FirstNumber");
            }
        }

        public double SecondNumber
        {
            get { return _secondNumber; }
            set
            {
                _secondNumber = value;
                RaisePropertyChanged("SecondNumber");
            }
        }

        public double Result
        {
            get { return _result; }
            set
            {
                _result = value;
                RaisePropertyChanged("Result");
            }
        }

        public ICommand Add
        {
            get { _add = _add ?? new RelayCommand(() => Result = FirstNumber + SecondNumber); return _add; }
        }

        public ICommand Subtract
        {
            get { _subtract = _subtract ?? new RelayCommand(() => Result = FirstNumber - SecondNumber); return _subtract; }
        }

        public ICommand Multiply
        {
            get { _multiply = _multiply ?? new RelayCommand(() => Result = FirstNumber * SecondNumber); return _multiply; }
        }

        public ICommand Divide
        {
            get { _divide = _divide ?? new RelayCommand(() => Result = FirstNumber / SecondNumber); return _divide; }
        }
  }

The project also contains a simple Console application which launches along with the project and automates the testing of the application by entering values in the textboxes and pressing the four buttons and verifying the result values.

namespace CalculatorTest
{
    class TestCalc
    {
        AutomationLibrary library = new AutomationLibrary();

        AutomationElement desktopElement;
        AutomationElement mainWindow;

        public TestCalc()
        {
            desktopElement = GetDesktop();
        mainWindow = library.GetElement(desktopElement, AutomationElement.NameProperty, "Simple Calculator");
        }
        internal void StartTest()
        {
            for (int i = 0; i < 25; i++)
            {
                Random r = new Random();
                double firstNumber = r.Next(1300);
                Thread.Sleep(30);
                double secondNumber = r.Next(1000);
                if (VerifyResult(firstNumber, secondNumber))
                    Console.WriteLine(" All Tests Have Passed for {0} & {1}",firstNumber,secondNumber);
                else
                    Console.WriteLine("Tests have failed for {0} & {1}", firstNumber, secondNumber);
            }

        }

        public bool VerifyResult(double firstNumber,double secondNumber)
        {
            bool allTestPass = true; 
            library.SetValueOnTextBox(mainWindow, AutomationElement.AutomationIdProperty, "txtFirstNumber", TreeScope.Descendants, firstNumber.ToString());
            library.SetValueOnTextBox(mainWindow, AutomationElement.AutomationIdProperty, "txtSecondNumber", TreeScope.Descendants, secondNumber.ToString());
            double addResult = PressOpButtonandGetValue("btnAdd");
            double subResult = PressOpButtonandGetValue("btnSubtract");
            double mulResult = PressOpButtonandGetValue("btnMultiply");
            double divResult = PressOpButtonandGetValue("btnDivide");

            if (addResult != (firstNumber + secondNumber))
                allTestPass = false;
            if (subResult != (firstNumber - secondNumber))
                allTestPass = false;
            if (mulResult != (firstNumber * secondNumber))
                allTestPass = false;
            if (Math.Round(divResult,5) != Math.Round((firstNumber / secondNumber),5))
                allTestPass = false;
            return allTestPass;
        }

        public double PressOpButtonandGetValue(string buttonID)
        {
            library.PressButtonOnWindow(mainWindow, AutomationElement.AutomationIdProperty, buttonID, TreeScope.Descendants);
            Thread.Sleep(200);
            string result = library.GetTextFromTextElement(mainWindow, AutomationElement.AutomationIdProperty, "txtResult", TreeScope.Descendants);
            double actualResult;
            if (double.TryParse(result, out actualResult))
                return actualResult;
            throw new ArgumentException("Wrong Value detected");
        }

        private AutomationElement GetDesktop()
        {
            Console.WriteLine("Getting Desktop");
            var desktopElement = library.GetRootElement();
            if (desktopElement == null)
            {
                Console.WriteLine("Unable to get the desktop Element, Exiting the application");
                throw new ElementNotAvailableException("Unable to get the desktop Element");
            }
            return desktopElement;
        }
    }
}

Directly interacting with the Microsoft UI Automation API can lead to extremely repetitive code since most of the functions are used again and again. So I wrote a small library to simply this. Here is the code. It is still largely uncommented and not refactored yet, will improve it in future blog posts.

class AutomationLibrary
    {
        public AutomationElement GetRootElement()
        {
            return AutomationElement.RootElement;
        }

        public AutomationElement GetElement(AutomationElement rootElement, AutomationProperty property, object value)
        {
            return GetElement(rootElement, property, value, TreeScope.Children);
        }

        public AutomationElement GetElement(AutomationElement rootElement, AutomationProperty property, object value, TreeScope searchScope)
        {

            AutomationElement aeMainWindow = null;

            int numWaits = 0;
            do
            {
                aeMainWindow = rootElement.FindFirst(searchScope, new PropertyCondition(property, value));
                ++numWaits;
                Thread.Sleep(200);
            } while (aeMainWindow == null && numWaits < 50);
            return aeMainWindow;
        }

        public AutomationElement GetElementWithoutWait(AutomationElement rootElement, AutomationProperty property, object value)
        {
            return GetElementWithoutWait(rootElement, property, value, TreeScope.Children);
        }

        public AutomationElement GetElementWithoutWait(AutomationElement rootElement, AutomationProperty property, object value, TreeScope searchScope)
        {
            AutomationElement aeMainWindow = rootElement.FindFirst(searchScope, new PropertyCondition(property, value));
            return aeMainWindow;
        }


        public bool PressButtonOnWindow(AutomationElement element, AutomationProperty property, object value)
        {
            return PressButtonOnWindow(element, property, value, TreeScope.Children);
        }

        public bool PressButtonOnWindow(AutomationElement element, AutomationProperty property, object value, TreeScope treeScope)
        {
            try
            {
                //var window = GetElementWithoutWait(element, property, value,treeScope);
                //if (window == null)
                //    window = GetElement(element,property,value,treeScope);
                //if(window==null)
                //    return false;
                //else
                //{
                var buttonInvoke = GetInvokePattern(element, property, value, TreeScope.Descendants);
                buttonInvoke.Invoke();
                return true;
                // }
            }
            catch
            {
                return false;
            }
        }

        public InvokePattern GetInvokePattern(AutomationElement window, AutomationProperty property, object value, TreeScope treeScope)
        {
            AutomationElement aeButtonElement;
            int numWaits = 0;
            do
            {
                aeButtonElement = window.FindFirst(treeScope, this.GetPropertyCondition(property, value));
                ++numWaits;
                Thread.Sleep(500);
            } while (aeButtonElement == null && numWaits < 75);
            object objPattern;
            InvokePattern invokePatternObj;
            if (aeButtonElement.TryGetCurrentPattern(InvokePattern.Pattern, out objPattern))
            {
                invokePatternObj = objPattern as InvokePattern;
                return invokePatternObj;
            }
            return null;
        }

        public ValuePattern GetValuePatternWithoutWait(AutomationElement window, AutomationProperty property, object value, TreeScope searchScope)
        {
            AutomationElement aeTextBoxElement = window.FindFirst(searchScope, GetPropertyCondition(property, value));
            if (aeTextBoxElement == null)
                throw new ElementNotAvailableException("TextBoxElement Element not Available");
            object objPattern;
            ValuePattern valuePatternObj;
            if (aeTextBoxElement.TryGetCurrentPattern(ValuePattern.Pattern, out objPattern))
            {
                valuePatternObj = objPattern as ValuePattern;
                return valuePatternObj;
            }
            else
                throw new ElementNotEnabledException("The Value Pattern was not retrieved from the element");
        }

        public InvokePattern GetInvokePatternWithoutWait(AutomationElement window, AutomationProperty property, object value, TreeScope searchScope)
        {
            AutomationElement aeButtonElement = window.FindFirst(searchScope, GetPropertyCondition(property, value));
            if (aeButtonElement == null)
                throw new ElementNotAvailableException("Button Element not Available. Try the GetInvokePattern method which has a wait");
            object objPattern;
            InvokePattern invokePatternObj;
            if (aeButtonElement.TryGetCurrentPattern(InvokePattern.Pattern, out objPattern))
            {
                invokePatternObj = objPattern as InvokePattern;
                return invokePatternObj;
            }
            else
                throw new ElementNotEnabledException("The Invoke Pattern was not retrieved from the element");
        }


        public ExpandCollapsePattern GetExpandCollapsePattern(AutomationElement element, AutomationProperty property, object value, TreeScope searchScope)
        {
            AutomationElement aeExpanderElement;
            int numWaits = 0;
            do
            {
                aeExpanderElement = GetFirstChildNode(element, property, value, searchScope);
                ++numWaits;
                Thread.Sleep(300);
            } while (aeExpanderElement == null && numWaits < 75);
            object objPattern;
            ExpandCollapsePattern togPattern;
            if (true == aeExpanderElement.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out objPattern))
            {
                togPattern = objPattern as ExpandCollapsePattern;
                return togPattern;
            }
            else
                return null;

        }

        public void SetValueOnTextBox(AutomationElement element, AutomationProperty property, object value, TreeScope searchScope, string valueToBeSet)
        {
            var valuePattern = GetValuePatternWithoutWait(element, property, value, searchScope);
            if (valuePattern != null)
                valuePattern.SetValue(valueToBeSet);
        }

        public ExpandCollapsePattern GetExpandCollapsePatternWithoutWait(AutomationElement element, AutomationProperty property, object value, TreeScope searchScope)
        {
            try
            {
                AutomationElement aeExpanderElement = GetFirstChildNode(element, property, value, searchScope);
                if (aeExpanderElement == null)
                    throw new ElementNotAvailableException("Expander Element not available. Try the GetExpandCollapsePattern which has a wait");
                object objPattern;
                ExpandCollapsePattern togPattern;
                if (true == element.TryGetCurrentPattern(ExpandCollapsePattern.Pattern, out objPattern))
                {
                    togPattern = objPattern as ExpandCollapsePattern;
                    return togPattern;
                }
                else
                    return null;
            }
            catch
            {
                return null;
            }
        }

        public SelectionItemPattern GetSelectionItemPattern(AutomationElement element, AutomationProperty property, object value, TreeScope searchScope)
        {
            AutomationElement aeSelectionPattern;
            int numWaits = 0;
            do
            {
                aeSelectionPattern = GetFirstChildNode(element, property, value, searchScope);
                ++numWaits;
                Thread.Sleep(300);
            } while (aeSelectionPattern == null && numWaits < 75);
            object objPattern;
            SelectionItemPattern selectionItemPattern;
            if (true == element.TryGetCurrentPattern(SelectionItemPattern.Pattern, out objPattern))
            {
                selectionItemPattern = objPattern as SelectionItemPattern;
                return selectionItemPattern;
            }
            else
                return null;
        }

        public SelectionItemPattern GetSelectionItemWithoutWait(AutomationElement element, AutomationProperty property, object value, TreeScope searchScope)
        {
            try
            {
                AutomationElement aeExpanderElement = GetFirstChildNode(element, property, value, searchScope);
                if (aeExpanderElement == null)
                    throw new ElementNotAvailableException("Expander Element not available. Try the GetSelectionItemPattern which has a wait");
                object objPattern;
                SelectionItemPattern togPattern;
                if (true == element.TryGetCurrentPattern(SelectionItemPattern.Pattern, out objPattern))
                {
                    togPattern = objPattern as SelectionItemPattern;
                    return togPattern;
                }
                else
                    return null;
            }
            catch
            {
                return null;
            }
        }

        public TextPattern GetTextPatternWithoutWait(AutomationElement element, AutomationProperty property, object value, TreeScope searchScope)
        {
            try
            {
                AutomationElement aeTextElement = GetFirstChildNode(element, property, value, searchScope);
                if (aeTextElement == null)
                    throw new ElementNotAvailableException("Text Element not available");
                object objPattern;
                TextPattern txtPattern;
                if (true == aeTextElement.TryGetCurrentPattern(TextPattern.Pattern, out objPattern))
                {
                    txtPattern = objPattern as TextPattern;
                    return txtPattern;
                }
                else
                    return null;
            }
            catch
            {
                return null;
            }
        }

        public string GetTextFromTextElement(AutomationElement element, AutomationProperty property, object value, TreeScope searchScope)
        {
            var textPattern = GetTextPatternWithoutWait(element, property, value, searchScope);
            if (textPattern != null)
                return textPattern.DocumentRange.GetText(-1);
            return null;

        }



        public PropertyCondition GetPropertyCondition(AutomationProperty property, object value)
        {
            return new PropertyCondition(property, value);
        }

        public AutomationElementCollection GetAllChildNodes(AutomationElement element, AutomationProperty automationProperty, object value, TreeScope treeScope)
        {
            var allChildNodes = element.FindAll(treeScope, GetPropertyCondition(automationProperty, value));
            if (allChildNodes == null)
                throw new ElementNotAvailableException("Not able to find the child nodes of the element");
            return allChildNodes;
        }

        public AutomationElement GetFirstChildNode(AutomationElement element, AutomationProperty property, object value, TreeScope searchScope)
        {
            var firstchildNode = element.FindFirst(searchScope, GetPropertyCondition(property, value));
            if (firstchildNode == null)
                throw new ElementNotAvailableException("Not able to find the first child node of the element");
            return firstchildNode;
        }

        public bool FirstChildTextNodeContains(AutomationElement element, string toCheck)
        {
            var firstTextNode = this.GetFirstChildNode(element, AutomationElement.ControlTypeProperty, ControlType.Text, TreeScope.Children);
            if (firstTextNode.Current.Name == toCheck)
                return true;
            else
                return false;
        }

        public void SelectComboBoxItem(AutomationElement element, int indexToBeSelected, AutomationProperty property, object value, TreeScope searchScope)
        {
            var allChildren = this.ExpandComboBoxViewAndReturnChildren(element, property, value, searchScope);
            if (allChildren.Count < indexToBeSelected)
                throw new Exception("The combobox has fewer items than those that need to be selected");
            var itemToBeSelected = allChildren[indexToBeSelected];
            var selectPattern = this.GetSelectionItemPattern(itemToBeSelected, AutomationElement.ControlTypeProperty, ControlType.ListItem, TreeScope.Element);
            if (selectPattern != null)
                selectPattern.Select();
            var togglePattern = this.GetExpandCollapsePattern(element, property, value, TreeScope.Descendants);
            if (togglePattern != null)
                togglePattern.Collapse();
        }

        public AutomationElementCollection ExpandComboBoxViewAndReturnChildren(AutomationElement element, AutomationProperty property, object value, TreeScope searchScope)
        {
            try
            {
                var comboBox = GetFirstChildNode(element, AutomationElement.AutomationIdProperty, value, searchScope);
                var expandPattern = GetExpandCollapsePattern(comboBox, AutomationElement.ControlTypeProperty, ControlType.ComboBox, TreeScope.Element);
                if (expandPattern == null)
                    throw new ElementNotAvailableException("Couldnt Find Expand Pattern in combobox");
                expandPattern.Expand();
                return this.GetAllChildNodes(comboBox, AutomationElement.ControlTypeProperty, ControlType.ListItem, TreeScope.Children);
            }
            catch (Exception e1)
            {
                throw e1;
            }
        }

        public void WorkaroundPopulateControlTree(int x1, int y1)
        {
            uint x = (uint)x1;
            uint y = (uint)y1;
            NativeMethods.SetCursorPos(x1, y1);
            Thread.Sleep(200);
            NativeMethods.mouse_event(NativeMethods.MOUSEEVENTF_LEFTDOWN | NativeMethods.MOUSEEVENTF_LEFTUP, x, y, 0, 0);
        }


        public bool WaitTillElementSelected(AutomationElement aeMainWindow, AutomationProperty automationProperty, object value, TreeScope treeScope)
        {
            var btnStartAnalysis = GetElement(aeMainWindow, automationProperty, value, treeScope);
            int numWait = 0;
            do
            {
                if (btnStartAnalysis.Current.IsEnabled == true)
                    return true;
                Thread.Sleep(2000);
            } while (btnStartAnalysis.Current.IsEnabled == false && numWait < 90);

            return false;
        }
    }

    class NativeMethods
    {

        [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
        public static extern void mouse_event(uint dwFlags, uint dx, uint dy, uint cButtons, uint dwExtraInfo);

        public const int MOUSEEVENTF_LEFTDOWN = 0x02;
        public const int MOUSEEVENTF_LEFTUP = 0x04;
        public const int MOUSEEVENTF_RIGHTDOWN = 0x08;
        public const int MOUSEEVENTF_RIGHTUP = 0x10;

        /// Return Type: BOOL->int  
        ///X: int  
        ///Y: int  
        [System.Runtime.InteropServices.DllImportAttribute("user32.dll", EntryPoint = "SetCursorPos")]
        [return: System.Runtime.InteropServices.MarshalAsAttribute(System.Runtime.InteropServices.UnmanagedType.Bool)]
        public static extern bool SetCursorPos(int X, int Y);

    }
}

Running the application launches the console application which enters random inputs and tests the result for all mathematical operations.

The code can be downloaded here.

May 232011
 

At the recently concluded I/O developer conference, Google made an much awaited announcement – The Google Places API has been opened to everyone (was in beta testing for some time). For the uninitiated, Google Places is a Google application for searching local businesses like hotels, ATMs etc. Places fits in beautifully with Google maps both on the web and the Android.

Now with the API being opened up, Its also possible for any location aware tools and websites to make uses of Places search option to add the functionality to your own sites. Check out the documentation here. I integrated the Places API search in my previous geolocation example explained in a blog post here. The application returns 20 places near the user’s current location and adds markers to the map for each.

Web Workers

Web workers are one of the most interesting concepts of HTML5. They are a standard based on running JS scripts on a background thread rather than the main UI thread. This is extremely important since the more time consuming scripts (like complex mathematical calculations) can be offloaded to a secondary thread rather than freezing up your application, having huge applications in graphics intensive work. I used Web Workers in the current application to work call a server side method which in turn calls the Google places API to search for a list of places near the user’s application.

Here are some limitations of Web Workers in their current implementation:-

  • Not supported on all browsers (Most notably Internet Explorer).
  • We cannot access any DOM object in the Web Worker script. All communication needs to be to the main thread using the postMessage function.
  • Because we cannot access DOM objects, it also doesn’t allow any script to be loaded which refers to DOM, which renders most JS libraries like JQuery and Prototype unusable.

In this example, I following components.

  • An ASP.NET MVC server side in order to call the Google places API. Its a controller method which calls the API url and a method which holds the data. On client side we have a similar method in JSON with same properties. The ASP.NET MVC Model binder converts the JSON object to a CLR object and passes it to the Action method. Client Side Javascript cannot call the Google places API directly because it would be a cross site request and not allowed by Google. Hence the Server’s broker method becomes necessary here
  • Client side main script which uses Geolocation to determine the user’s location. It then passes this location to the MVC Action. Before calling the action, it checks whether the browser supports Web Workers – If so they are offloaded to secondary thread. Else called on the main thread itself.
  • Worker script which makes the AJAX call to the Server using xmlHttpRequest object (Jquery cannot be used here) 🙁

Here is the code.

ASP.NET Server Side

        public ActionResult GoogleSearchAPI(SearchQuery query)
        {
            //Base URL for calling the Google Places API
            string BaseAPIURL = String.Format("https://maps.googleapis.com/maps/api/place/search/xml?location={0},{1}&radius={2}", query.Latitude, query.Longitude, query.Radius);
            if (!string.IsNullOrEmpty(query.Name))
                //Append the name parameter only if data is sent from Client side.
                BaseAPIURL = String.Concat(BaseAPIURL, String.Format("&name={0}", query.Name));
            //Include the API Key which is necessary
            BaseAPIURL = String.Concat(BaseAPIURL, String.Format("&sensor=false&key={0}", GetAPIKey()));
            //Get the XML result data from Google Places using a helper method whichc makes the call.
            string _response = MakeHttpRequestAndGetResponse(BaseAPIURL);
            //Wrap XML in a ContentResult and pass it back the Javascript
            return Content(_response);
        }

        //Helper method to call the URL and send the response back.
        private string MakeHttpRequestAndGetResponse(string BaseAPIURL)
        {
            var request = (HttpWebRequest)WebRequest.Create(BaseAPIURL);
            request.Method = WebRequestMethods.Http.Get;
            request.Accept = "application/json";
            string text;
            var response = (HttpWebResponse)request.GetResponse();

            using (var sr = new StreamReader(response.GetResponseStream()))
            {
                text = sr.ReadToEnd();
            }

            return text;
        }

    ///
    /// Our data object  which sends the object with data from client side to
    /// server. The Model binder takes care of conversion between JSON and
    /// CLR objects.
    ///
    public class SearchQuery
    {
        public string Latitude { get; set; }
        public string Longitude { get; set; }
        public string Radius { get; set; }
        public string Type { get; set; }
        public string Name { get; set; }
    }

Client Side Main script

Most of the Geolocation code is the same as my previous example. This is the additional code written after the geolocation data is found and the coordinates is passed on to another method which uses it to retrieive places data and mark it on map

var spawnWorkerThread = function (position) {
    //This is executed if the getPosition is successfull. Moving the map to the user's location
    map.panTo(new google.maps.LatLng(position.coords.latitude, position.coords.longitude));
    var coordinates = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
    //Create a JSON object with the details of search query.
    var placesQuery = { Latitude: position.coords.latitude, Longitude: position.coords.longitude, Type: "establishment", Radius: "500" };
    //Check if the browser supports WEbWorkers
    if (Modernizr.webworkers) {
        printMsg("Web Workers are supported on your browser. Searching for places nearby your location");
        //Load the Worker Script
        var myWorker = new Worker("/files/webworkersmvc/Scripts/worker.js");
        //Send the JSON object to the Worker thread after serializing it to string
        myWorker.postMessage(JSON.stringify(placesQuery));
        // receive a message from the worker
        myWorker.onmessage = function (event) {
            //Send the returned data to the processPlacesData method
            processPlacesData(event.data);
        };
    }
    else {
    //Make the call in a standard way and not using Web Workers
        printMsg("Web Workers isnt supported on your browser.Calling Places API the conventional way");
        var xhr = new XMLHttpRequest();
        //Calling the controller method.
        xhr.open("POST", "http://www.ganeshran.com/Files/webworkersmvc/Home/GoogleSearchAPI");
        xhr.setRequestHeader("Content-Type", "application/json");
        xhr.onreadystatechange = function () {
            if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 0)) {
                processPlacesData(xhr.responseText);
            }
        }
        xhr.send(JSON.stringify(placesQuery));
    }

};

var processPlacesData = function (data) {
    //Parse the XML result into an xml obect
    var places = $.parseXML($.trim(data));
    var xmldoc = $(places);
    var resultstring = "";
    //Iterate through each result object
    $("result", places).each(function () {
        var typestring = "";
        $("type", this).each(function () {
            typestring += $(this).text() + " , ";
        });
        //Create a MapResult object for each result.
        var resObj = new mapResult($("name", this).text(),
                                      $('vicinity', this).text(),
                                      typestring,
                                      $('lat', this).text(),
                                      $('lng', this).text(),
                                      $('icon', this).text());
        //Create a Google Maps Marker and use the result object's latitude and
        //longitude.
        var marker = new google.maps.Marker({
            position: new google.maps.LatLng(resObj.latitude, resObj.longitude),
            title: resObj.name,
            animation: google.maps.Animation.DROP
        });
        //If the screen is smaller then zoom lesser else zoom more closer.
        //This is to make the markers visible
        if (screen.width < 1000) {
            map.setZoom(12);
        }
        else {
            map.setZoom(15);
        }
        //Set each marker on the map
        marker.setMap(map);
        //this is for the window to show information when the marker is clicked.
        //A single infowindow is reused in order to display only one
        google.maps.event.addListener(marker, 'click', function () {
            infowindow.setContent(resObj.getMarkerHTML());
            infowindow.open(map, marker);
        });
    });

};

//Javascript object to hold the map data and the get the HTML required for the marker.
function mapResult (name, vicinty, types, latitude, longitude, icon) {
    this.name = name;
    this.vicinity= vicinty;
    this.types = types;
    this.latitude = latitude;
    this.longitude = longitude;
    this.iconpath = icon;
}
//Prototype method to avoid creating seperate copies of the method
//for each object
mapResult.prototype.getMarkerHTML = function () {
var htmlstring = "
<div style="color: blue; font-weight: bold;">";
    htmlstring += "Name: " + this.name + "";
    htmlstring += "Types: " + this.types + "";
    htmlstring += "Location: " + this.latitude + "," + this.longitude + "";
    htmlstring += "Vicinity: " + this.vicinity +"</div>";
    return htmlstring;
};

Worker Side Script

The worker side script is pretty straightforward. Just calls the controller and passes on the data to the main thread using the PostMessage function

// receive a message from the main JavaScript thread
onmessage = function (event) {
    // do something in this worker
    var info = event.data;
    var xhr = new XMLHttpRequest();
    xhr.open("POST", "http://www.ganeshran.com/Files/webworkersmvc/Home/GoogleSearchAPI");
    xhr.setRequestHeader("Content-Type", "application/json");
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4 && (xhr.status == 200 || xhr.status == 0)) {
            //Send message back to Main thread
            postMessage(xhr.responseText);
        }
    }
    xhr.send(info);
};

Demo Page

http://www.ganeshran.com/files/webworkersmvc/

Demo Pics

On PC

On Android

May 122011
 

Gone are the days when the web was a one-size-fits-all kind of global information vending machine. In the past few years, continuous efforts are being made towards making the web as local as possible, with websites being able to present content suited to the users browsing it.

One huge component of this new wave are the development of location aware websites. Websites which give relevant information without the user having to explicitly search for it. For e.g. A movie ticket booking website which can be used to book tickets in theatres all over the country. Very few people, if any would travel more than 20 km to watch a movie. Hence it makes business sense to display the theatres in the 20 km radius of the users location. Instead of subjecting the user to an information overload, the website is smart enough to simplify the entire process and cut down the time taken.

Geolocation isnt a new concept. Previously, websites used the IP address and did a lookup to get a rough idea of the user’s location. There were other methods like the locale setting too. So while framing the HTML5 spec, the W3C decided to arrive at a standard for providing the client’s location to the server which abstracts out the actual method used by the browser to determine it (Cell tower triangulation, IP address, GPS etc). Since its a privacy concern to reveal one’s location, the whole geolocation API depends on explicit permission separately provided for each website.

I created an example page which integrates Google Maps API and geolocation and displays a map as per the user’s location. Since not every browser would be able to support geolocation yet, I used the JS library Modernizr which detects whether the feature is supported by the end browser without us having to do the browser sniffing using the user agent. Here is the script code.

/************************************
 * Title: Geolocation API access
 * Author: Ganesh Ranganathan
 ************************************/

var map; //global variable for the map object
$(document).ready(function(){
	//this is the default latitude /longitude to be set on the map
	var latlng = new google.maps.LatLng(-34.397, 150.644);
	var myOptions = {zoom: 8,
			center: latlng,
			mapTypeId: google.maps.MapTypeId.ROADMAP};

	//Map constructor which creates the map
	map = new google.maps.Map($('#map_canvas')[0],myOptions);

	//Modernizr is a JS library which lets us check if
	//the target browser supports these features
	if(Modernizr.geolocation)
	{
		printMsg('Waiting for permission from you',false);
		//The getCurrentPosition takes in two callbacks - one for
		//success and one for error. Both functions are defined anonymously
		//inside the method
		navigator.geolocation.getCurrentPosition(function(position){
			printMsg('Permission Granted: Your Coordinates are '+position.coords.latitude+
					 ' & '+position.coords.longitude);
			//This is executed if the getPosition is successfull. Moving the map to the user's location
			map.panTo(new google.maps.LatLng(position.coords.latitude,position.coords.longitude));
		},function(error){
			//This is executed if the getPosition is unsucesfull.
			//Show the error reason to the user
			switch(error.code)
			{
			case error.TIMEOUT:
				printMsg ("Error: Timeout",false);
				break;
			case error.POSITION_UNAVAILABLE:
				printMsg ("Error: Position unavailable",false);
				break;
			case error.PERMISSION_DENIED:
				printMsg ("Error: Permission denied",false);
				break;
			case error.UNKNOWN_ERROR:
				printMsg ("Error: Unknown error",false);
				break;
			}
		});
	}
	else //Print this message if GeoLocation itself isnt supported by the browser
		printMsg("geolocation is not supported on your browser",false)
});

//Helper function to set the status message to
//the span value
var printMsg = function(txt,append){
	if(!append)
		$('#statusMsg').text(txt);
	else{
		var existingText = $('#statusMsg').text();
		$('#statusMsg').text(existingText+'  '+txt);
	}
};

The getCurrentPosition method of the navigator.geolocation object takes in two functions as the parameters. First is the SuccessCallback and second is the errorCallback. The success callback is passed the position object which contains the location information like latitude and longitude. This is the simple markup for the page. Please note that the Modernizr, Google Maps, Jquery scripts are referenced in the order they are needed.


<script src="Scripts/modernizr-1.7.min.js" type="text/javascript"><!--mce:0--></script>
<script src="Scripts/jquery-1.6.min.js" type="text/javascript"><!--mce:1--></script>
<script src="http://maps.google.com/maps/api/js?sensor=false" type="text/javascript"><!--mce:2--></script>
<script src="Scripts/scripts.js" type="text/javascript"><!--mce:3--></script>
<h1>Geolocation API with HTML5</h1>
<div id="status">
 <span id="statusMsg"> </span></div>

The demo page for this application can be seen at – http://www.ganeshran.com/Files/geolocationtry

Here is the application when its run for the first time in Chrome. As you can see it asks for permission from the user. Meanwhile the map is centered towards the default location.

If we allow the application to access our location, then the map moves to the user’s coordinates – Bangalore in my case. Here is a screenshot – this time in Firefox

Since its a standardized API, the same code works for mobile browsers as well. Here are some screenshots of the page running on the Android stock browser which is also geolocation compliant.

After granting permission

May 112011
 

I came across this amazingly useful jquery feature at Microsoft Tech-ed 2011 at a talk by Fritz Onion. Microsoft for a long time now has been favoring jQuery instead of the in house developed Ajax Control Toolkit. Though both of them coexist for now in some of the ASP.NET Visual studio templates, I believe its only a matter of time before ACT is totally discarded as jQuery has proven to be the defacto web standard for Javascript programming.

Its also heartening to know that not only are Microsoft planning to adopt jQuery as the standard in their frameworks, they are also going to actively contribute code to the jQuery project. (To clarify, Its not a fork from the original project and jQuery will continue to be licensed under the MIT-PL). One such Microsoft initiative is the jQuery plugin – Templates. Its so useful and simple to use that I am amazed no one thought of this before.

jQuery Templates rids us the need for using DOM manipulation code while adding elements on the fly. It acts like a placeholder for certain pieces of data, the values of which are dynamically substituted by the plugin and also acts as a sort of repeater for multiple elements following the same data structure. I would call it a cross between the .NET String.Format() and HTML repeater control. For e.g. below is a sample template tag

There are two things that stand out in the above script tag, one is an unfamiliar type – x-jquery-tmpl and the other are the $ signs. Apart from that, the rest of the stuff is pretty much run-of-the-mill markup. The x-jquery-tmpl type is used to make the browser ignore the content between the script tag and not try to parse it. This is necessary since the parsing would need to be done by the plugin. The $ is used to mark the member name of the binding data structure. For e.g. ${genre} indicates that this value would be replaced by the genre property of the object that is bound to the template tag.

This is the script code which gets data from the server (an Xml file which contains a catalog root node with 12 book nodes and details like name, author, price etc) through an ajax call and then binds it to the template script tag which we described in the previous paragraph. The template needs an array of objects which have to bound to the individual tags. Any markup will be repeated the number of times as there are elements in the array.

/***************************************
* Description: JQuery Templating feature
* Author : Ganesh Ranganathan 
*****************************************/
$(document).ready(function () {
    //Method to be called when all DOM elements are loaded
    makeAjaxCall();
});

function makeAjaxCall() {
    $.ajax({
        url: 'ajax/catalog.xml', //Make an ajax call to the server
        datatype: 'xml',
        success: function (data) {
            // Callback function on successfull call
            var books = [];
            //Iterate through the xml file and apply function on each 'book' node
            $(data).find('book').each(function () {
                //books is an object array where we create an object for each xml node
                //and assign values to the properties. These property names are bound in
                //the templates
                books.push({ bookName: $(this).find('title').text(),
                author: $(this).find('author').text(),
                genre: $(this).find('genre').text(),
                price: $(this).find('price').text(),
                description: $(this).find('description').text()
                 });
            });

        //This is the templating call where the data structure is bound to the id
        //of the script tag. The tmpl() call returns the DOM element containing the HTML
        // which is then appended to the mainDiv which is the holder element
            $('#books').tmpl(books).appendTo('#mainDiv');
        }
    });
}

This is the minimal markup that needs to be used for the page. The important part is the the script files – jquery.1.4.4.min.js and jquery.tmpl.js that can be downloaded from its repo on github or you could add a NuGet Package reference as well which auto adds all the dependencies and saves a lot of time.

<head runat="server">
    <title></title>
    <script src="Scripts/jquery-1.4.4.min.js" type="text/javascript"></script>
    <script src="Scripts/jQuery.tmpl.js" type="text/javascript"></script>
    <script src="Scripts/script.js" type="text/javascript"></script>
    <link href="Styles/styles.css" rel="stylesheet" type="text/css" />
</head>

<body>
    <script id="books" type="text/x-jquery-tmpl">
    <div id="bookHolder">Name: ${bookName} <br />
    Author: ${author} <br />
    Genre: ${genre} <br />
    Price: ${price} 
    </div>
    </script>
    <form id="form1" runat="server">
    <div id="heading">Books Catalog</div>
    <div id="mainDiv">
    </div>
    </form>
</body>
</html>

With some ugly basic styling, here is what the final page looks like. Please note that there was absolutely no DOM manipulation in the Javascript code. Conventional approaches would have required iterating through the xml file, creating DOM elements, assigning their node value, taking care of their hierarchy etc. JQuery’s templating plugin rids us of most of this additional plumbing code and makes the script files more concise and readable.