Custom Unobtrusive jQuery Validation with Data Annotations in MVC 3

Standard

Heads up!

The blog has moved!
The new URL to bookmark is http://blog.valeriogheri.com/

 

MVC 3 introduced what is called Unobtrusive Client Validation. This feature relies on jQuery and HTML5 custom data attributes. It is very powerful and not so hard to use… with the right tutorial to follow 🙂
It’s also very useful because it leverages the power of Data Annotations,  a clean way to express View Model rules in my opinion.
Let’s start the tutorial with a bit of theory (if you’re not interested, you can skip it and go straight to the paragraph “The tutorial”  and download the source code from https://github.com/vgheri/DateCustomValidationExample)

What is a custom data attribute

“As per HTML 5 draft specification a custom data attribute is an attribute in no namespace whose name starts with the string “data-“, has at least one character after the hyphen, is XML-compatible, and contains no characters in the range U+0041 to U+005A (LATIN CAPITAL LETTER A to LATIN CAPITAL LETTER Z). Custom data attributes are intended to store custom data private to the page or application, for which there are no more appropriate attributes or elements. These attributes are not intended for use by software that is independent of the site that uses the attributes. It would be inappropriate, however, for the user to use generic software not associated with that music site to search for tracks of a certain length by looking at this data. This is because these attributes are intended for use by the site’s own scripts, and are not a generic extension mechanism for publicly-usable metadata. Every HTML element may have any number of custom data attributes specified, with any value. JavaScript libraries may use the custom data attributes, as they are considered to be part of the page on which they are used.”

This means that any attribute whose name starts with “data-” will be treated as a storage area for private data.
This allows you to write valid HTML markup (passing an HTML 5 validator) while, simultaneously, embedding data within your page.

MVC 3 + jQuery

MVC3’s new jQuery Validation mechanism links jQuery Validation and Validation Attributes Metadata. The magic happens in the jquery.validate.unobtrusive file that takes all data- attributes and works with them.
To exploit this mechanism, we need to create our own Custom Validation Attributes as we’ll see in this article.

How MVC generates the data- attributes:

The custom data attributes are generated by helpers methods like Html.TextBoxFor(), as it knows already all the Data Anotations that the field needs, and if Unobstrutive setting is true (by default in MVC 3) it will generate all data- that he needs.
For each client validation rule(we’ll get there), an attribute is added with data-val-rulename=”message”. Then, for each parameter in the client validation rule, an attribute is added with data-val-rulename-paramname=”paramvalue”.

The tutorial

In this tutorial we will create a custom validation that checks if a certain date is greater than another date entered by the user in a form. For the purpose of this tutorial we will create a model consisting of one class, Project, that obviously has a start date and an end date. Our goal is to force the user to enter an end date greater than the start date of the project.
To better clarify how to create a custom validator, this tutorial is divided in two steps: first we will provide server side validation, and on top of that we will add client side validation.

Step 1: Server side validation

First we create the model

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace DateCustomValidationExample.Models
{
    public class Project
    {
        public string Name { get; set; }

        public string ProjectManager { get; set; }

        [DisplayName("Start date")]
        [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
        public DateTime StartDate { get; set; }

        [DisplayName("Estimated end date")]
        [DisplayFormat(DataFormatString = "{0:MM/dd/yyyy}", ApplyFormatInEditMode = true)]
        [DateGreaterThan("StartDate", "Estimated end date must be greater than the start date of the project")]
        public DateTime EndDate { get; set; }
    }
}
Note that we already added our custom data attribute data annotation even if it’s not yet created. In my opinion this helps us to think more about how we want to use it rather than how it should be built.
Ok, we defined how we want to use the attribute, now we must create it:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace DateCustomValidationExample.Models
{
    [AttributeUsage(AttributeTargets.Property, AllowMultiple=true)]
    public class DateGreaterThanAttribute : ValidationAttribute
    {
        string otherPropertyName;

        public DateGreaterThanAttribute(string otherPropertyName, string errorMessage)
            : base(errorMessage)
        {
            this.otherPropertyName = otherPropertyName;
        }

        protected override ValidationResult IsValid(object value, ValidationContext validationContext)
        {
            ValidationResult validationResult = ValidationResult.Success;
            try
            {
                // Using reflection we can get a reference to the other date property, in this example the project start date
                var otherPropertyInfo = validationContext.ObjectType.GetProperty(this.otherPropertyName);
                // Let's check that otherProperty is of type DateTime as we expect it to be
                if (otherPropertyInfo.PropertyType.Equals(new DateTime().GetType()))
                {
                    DateTime toValidate = (DateTime)value;
                    DateTime referenceProperty = (DateTime)otherPropertyInfo.GetValue(validationContext.ObjectInstance, null);
                    // if the end date is lower than the start date, than the validationResult will be set to false and return
                    // a properly formatted error message
                    if (toValidate.CompareTo(referenceProperty) < 1)
                    {
                        validationResult = new ValidationResult(ErrorMessageString);
                    }
                }
                else
                {
                    validationResult = new ValidationResult("An error occurred while validating the property. OtherProperty is not of type DateTime");
                }
            }
            catch (Exception ex)
            {
                // Do stuff, i.e. log the exception
                // Let it go through the upper levels, something bad happened
                throw ex;
            }

            return validationResult;
        }
    }
}

Notice how the attribute extends ValidationAttribute and overrides IsValid method, in which we perform the custom validation.

Now we need aController and aView to test the validation.

Create a HomeController with a simple Index action method, build the project and then add a strongly typed View bound to our model, choosing the “Create” item into the scaffolding  menu.
To simplify user interaction, we will use the standard  jQuery datepicker component to let the user choose the dates.
In order to use it, we must add the following references to our page (or into the master layout page)
<script src="../../Scripts/jquery-1.x.x.js" type="text/javascript"></script>
<script src="../../Scripts/jquery-ui-1.x.x.js" type="text/javascript"></script>
<link href="../../Content/themes/base/jquery.ui.all.css" rel="stylesheet" type="text/css" />
Where x.x is the version you currently have installed.
To bind the datepicker method to our input fields, we use the following jquery code
<script>
    $().ready(function () {
        $("#StartDate").datepicker();
        $("#EndDate").datepicker();
        return false;
    });
</script>

Then, in order to use server side validation, we need to add the following code to our HomeController to handle form submit

// POST: /Home/
[HttpPost]
public ActionResult Index(Project model)
{
   return View(model);
}
If we run the project and inspect the HTML emitted, this is what we see:
HTML5 emitted  with the server side validation

HTML5 emitted with the server side validation

As expected there is no custom data attribute related to our custom rule.

Then we can insert two dates that will trigger the error message and we get the error message as we expected
Validation error message

Validation error message

That’s it for server-side validation.

Client side validation

Now on to the second part of this tutorial, where we will achieve client side validation.
Before we start, a few important notes (taken directly from the blog  of one of the developers of Asp.Net MVC 3)

jQuery Validate requires your input elements to be inside of a <form> element in order to be validated. In addition, MVC 3 requires that you have called Html.BeginForm() to render this form, so that it can find its book-keeping object to help render the HTML attributes.

Writing a client-side validator involves two steps:

  1. adding one more method to our DateGreaterThanAttribute
  2. writing some jQuery code to create a validation rule and what is called an adapter, which takes the parameter values from the HTML attributes and turns it into jQuery Validate metadata.
Note that we need to write a custom adapter since the default collection doesn’t come with what we need! Otherwise, there is an adapter collection available at jQuery.validator.unobtrusive.adapters.
Let’s go back to our DateGreaterThanAttribute class and modify it so that it implements the IClientValidatable, which, as MSDN says, provides a way for the ASP.NET MVC validation framework to discover at run time whether a validator has support for client validation.
Since we are implementing it, we need to write the method GetClientValidationRules like the following:
public IEnumerable<ModelClientValidationRule> GetClientValidationRules(ModelMetadata metadata, ControllerContext context)
        {
            //string errorMessage = this.FormatErrorMessage(metadata.DisplayName);
            string errorMessage = ErrorMessageString;

            // The value we set here are needed by the jQuery adapter
            ModelClientValidationRule dateGreaterThanRule = new ModelClientValidationRule();
            dateGreaterThanRule.ErrorMessage = errorMessage;
            dateGreaterThanRule.ValidationType = "dategreaterthan"; // This is the name the jQuery adapter will use
            //"otherpropertyname" is the name of the jQuery parameter for the adapter, must be LOWERCASE!
            dateGreaterThanRule.ValidationParameters.Add("otherpropertyname", otherPropertyName);

            yield return dateGreaterThanRule;
        }

Important: The metadata expressed for this rule will be used by the runtime to emit the HTML5 data-val custom attributes!

Now we need to create the jQuery validation function and the jQuery adapter.
We can create a separate .js file, thus we can reuse the code.
Let’s create a new Folder into /Scripts and call it “Custom”, then let’s create a new script called CustomValidation.js.
Let’s see the code:
/* File Created: January 16, 2012 */

// Value is the element to be validated, params is the array of name/value pairs of the parameters extracted from the HTML, element is the HTML element that the validator is attached to
$.validator.addMethod("dategreaterthan", function (value, element, params) {
    return Date.parse(value) > Date.parse($(params).val());
});

/* The adapter signature:
adapterName is the name of the adapter, and matches the name of the rule in the HTML element.

params is an array of parameter names that you're expecting in the HTML attributes, and is optional. If it is not provided,
then it is presumed that the validator has no parameters.

fn is a function which is called to adapt the HTML attribute values into jQuery Validate rules and messages.

The function will receive a single parameter which is an options object with the following values in it:
element
The HTML element that the validator is attached to

form
The HTML form element

message
The message string extract from the HTML attribute

params
The array of name/value pairs of the parameters extracted from the HTML attributes

rules
The jQuery rules array for this HTML element. The adapter is expected to add item(s) to this rules array for the specific jQuery Validate validators
that it wants to attach. The name is the name of the jQuery Validate rule, and the value is the parameter values for the jQuery Validate rule.

messages
The jQuery messages array for this HTML element. The adapter is expected to add item(s) to this messages array for the specific jQuery Validate validators that it wants to attach, if it wants a custom error message for this rule. The name is the name of the jQuery Validate rule, and the value is the custom message to be displayed when the rule is violated.
*/
$.validator.unobtrusive.adapters.add("dategreaterthan", ["otherpropertyname"], function (options) {
    options.rules["dategreaterthan"] = "#" + options.params.otherpropertyname;
    options.messages["dategreaterthan"] = options.message;
});

Let’s look at it in more details:
$.validator.addMethod("dategreaterthan", function (value, element, params) {
    return Date.parse(value) > Date.parse($(params).val());
});
The first parameter for addMethod is the name that identifies the rule and can be different from what we used inside GetClientValidationRules => dateGreaterThanRule.ValidationType = “dategreaterthan”;
In this example is the same, but you can change it.
The second parameter is the actual validator, in this case simply a comparison between two dates.

The adapter instead is tightly coupled with GetClientValidationRules implementation, therefore the first parameter must match the name we used in the code of our custom attribute for the validationType.

The second parameter is an array of parameters name.

In our case we only added one parameter

dateGreaterThanRule.ValidationParameters.Add("otherpropertyname", otherPropertyName);
so our array will have only one parameter. Should we have more, it would look like [“param1”, “param2”, ….]
The third parameter is a function which is called to adapt the HTML attribute values into jQuery Validate rules and messages.
options.rules["dategreaterthan"] //"dategreaterthan", the key to identify the rule, must match what we used in the addMethod as first parameter.
options.rules["dategreaterthan"] = "#" + options.params.otherpropertyname;

we use the # so that the value stored will be something like “#StartDate”, that is ready to be consumed by the validation rule previously wrote to get the value out of the StartDate field.

The javascript is done! Now let’s go back to our View because we need to add all the references we need to run the unobtrusive validation:
<script src="../../Scripts/jquery.validate.js" type="text/javascript"></script>
<script src="../../Scripts/jquery.validate.unobtrusive.js" type="text/javascript"></script>
<script src="../../Scripts/MicrosoftMvcAjax.js" type="text/javascript"></script>
<script src="../../Scripts/Custom/CustomValidation.js" type="text/javascript"></script>

Everything’s done and now we can run the app. If we inspect the HTML that has been emitted this is what we see

HTML emitted with client side validation

HTML emitted with client side validation

and if we run the application we will see the same error message as before, with no form submit!

The source code is available on my github at the following URL: https://github.com/vgheri/DateCustomValidationExample

That’s all for the unobtrusive custom validation with MVC 3.

I hope this helps,

Valerio


Advertisements

Enabling jQuery IntelliSense in an MVC project with Visual Studio 2010

Standard

Heads up!

The blog has moved!
The new URL to bookmark is http://blog.valeriogheri.com/

 

Hello,
today I’d like to share with you a quick trick to enable IntelliSense to help writing jQuery code in the View.
If we are using the out of the box convention of MVC 3, our layout code will be placed in the Views/Shared/_layout.cshtml folder, and inside it we can put all of our scripts and stylesheets global references.

Then, when we write jQuery code in our own View, we will notice that we have no IntelliSense support helping us, contrary to our expectations.
The problem lies in the way Visual Studio works: IntelliSense engine will not be able to access the references we made into _layout because the engine doesn’t really know which layout will be used to render the View until the code is running (just imagine a scenario where we may dynamically choose a layout based on user profile to understand why Visual Studio works this way).

The solution

The first solution I came up with was to add again the vsdoc script reference (jquery-X.Y.Z-vsdoc.js) to the View and I had IntelliSense again.

Enabling Intellisense

Habemus IntelliSense!

The downside of this is double references that will produce double requests when browsing the site, and this is bad!

So I googled and I found a pretty nice trick to enable Intellisense and to avoid downsides(link):

@if (false)
{
<script src="../../Scripts/jquery-1.7.1-vsdoc.js" type="text/javascript"></script>
}

Surrounding the script reference by an if (false) statement, the reference will never be posted to the browser because the statement will always be false, but we will still have our Intellisense to help us!

Twitter Real Time Search

Standard

Heads up!

The blog has moved!
The new URL to bookmark is http://blog.valeriogheri.com/

 

Hello,I’ve always developed web applictions using the ASP.Net Web Forms framework, and so far everything has been ok. A few weeks ago, though, I came up with the idea to re-engineer one of the most important web application we use at work. This web platform runs the business, has been developed using the Web Forms framework and is quite old fashioned: it’s starting to feel a little cramped when we have to add new features and new UI components that should handle complex user interactions with the UI. Not to mention that is not very usable on tablets like the iPad.

Looking for a solution, I thought it would have been great to use the latest available technologies to re-engineer the system… and therefore MVC!

I started learning the ASP.Net MVC framework, along with jQuery, reading the MVC Music Store tutorial .

After that I decided to give it a try implementing a web application on my own, an application that lets the user query the Twitter real time feed.

I had no idea where to start from, I just liked the idea of using the Twitter API!

I’ll start saying that in the following tutorial there’s not much of the true MVC potential about model binding and validation, nevertheless it has been a starting point to deal with such technologies like jQuery, Json, and the foundamentals of MVC.Before we start, I want to point out that this article is not a “Get Started“ tutorial, therefore to all of you that start from scratch I strongly suggest to start reading the MVC Music Store tutorial using the link posted above, otherwise you will miss some key concepts.

Furthermore, I will not post a lot of code here because it’s not really that readable, but at the end of the post you will find a link to download the source code of the project so that you will be able to follow and modify the code as you wish.

Objective

The goal is to develop a web page that lets a single user (to make things simple) see Tweets related to a topic he’s interested in, as they are being created in real time.

Given the single user restriction (later on I’ll explain why), this application is obviously just a toy, nevertheless it’s still useful to cover the basics and could be easily converted into something more useful.

Use case

The user enters a keyword in the search bar and press the “Search” button. After a while, if someone is twitting something related to the user’ search key, tweets will appear inside a box, piling up as they are being created.

When the user is satisfied, he halts the search by clicking the “Stop” button.

What I will use

ASP.Net MVC 3 framework, jQuery and the Twitterizer library (http://www.twitterizer.net/)

Realization

The Twitter developer website is full of interesting and useful stuff if you want to go into more details then the one you will find here.

To start off we must create an application on the Twitter Developer website to start using the Twitter API. Follow the instructions on the dev website and then do as shown in the following screenshot.

How to setup creation of the app

How to setup creation of the app

At the end of the process, you will receive the authorisation keys for the 0Auth authentication protocol.

To handle authentication and web request processes, I’m usingTwitterizer, an ad-hoc library that can be found here.

At the time I’m writing this, current version is 2.3.3, but it’s not correctly working as the address for the stream in the API is changed and is now using the HTTPS protocol. So I had to download the source code, change the address (TwitterStream.cs, row numbers 210 and 212) and import the Twitterizer.Streaming project into my own solution (I liked also the idea of looking at the code).It’s now time to decide how to architecture the project!

Even for simple and quick projects, I like to use a layered architecture, so for this project I created:

  • a Domain Model layer
  • a Data access layer that handles access to the storage to add and retrieve tweets
  • an Application Logic layer that will handle main user operations and access to the Twitterizer service
  • a Presentation layer, built using the MVC framework
What do you say, it’s too much architecture  for such a small project?
Let me show you something 😀
The general problem

The general problem

The Domain Model layer

It’s very thin given that the domain can be narrowed down to our representation of a Tweet. The same representation will  also be suitable for the View. Let’s see the class diagram:

Domain Model class diagram

Domain Model class diagram

Following the code for the Tweet class.
namespace Model
{
    public class Tweet
    {
        public decimal Id { get; set; }
        public string Username { get; set; }
        public string ProfileImageURL { get; set; }
        public string Text { get; set; }
        public string Timestamp { get; set; }
        public long? NumberOfFollowers { get; set; }

        /// <summary>
        /// Side-effect free function. Extract URLs, hashtag and mention patterns inside a text and convert them to HTML links
        /// </summary>
        /// <param name="tweetText"></param>
        /// <returns></returns>
        public string FormatTweetText()
        {
            string formattedTweetText = string.Empty;
            formattedTweetText = Regex.Replace(this.Text, @"(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])",
               delegate(Match match)
               {
                   return string.Format("<a href=\"{0}\">{0}</a>", match.ToString());
               }, RegexOptions.IgnoreCase);

            formattedTweetText = Regex.Replace(formattedTweetText, @"#+[a-zA-Z0-9]*",
               delegate(Match match)
               {
                   return string.Format("<a href=\"http://twitter.com/#!/search?q={0}\">{1}</a>", match.ToString().Replace("#", "%23"), match.ToString());
               }, RegexOptions.IgnoreCase);

            formattedTweetText = Regex.Replace(formattedTweetText, @"@+[a-zA-Z0-9]*",
               delegate(Match match)
               {
                   string mention = match.ToString().Substring(1);
                   return string.Format("<a href=\"http://twitter.com/#!/{0}\">{1}</a>", mention, match.ToString());
               }, RegexOptions.IgnoreCase);

            return formattedTweetText;
        }
    }
}

The Data Access Layer

This layer is based on the Repository pattern, albeit in a very simplified form, and the repository is created by a factory, using the Abstract Factory pattern.

Let’s first take a look at the class diagram:

Data Access Layer class diagram

The design is pretty easy and straightforward: the concrete repositories instances must implement the ITwitterRepository interface, therefore allowing for an easy swap amongst concrete repositories and no code change is required in the upper layers.
We can retrieve either a list of tweets either a single tweet at a time, using GetRange or GetStatus methods.
Both take a parameter called lastId: this is because we only want to display new tweets and not duplicates.

The concrete repository is created by the RepositoryFactory object, implementing the IRepositoryFactory interface.

In more complex projects, and more susceptible to changes, it would probably be a good idea to use an Inversion of Control tool.

For the purpose of this project I created a concrete repository that works in memory and mimics a queue, and the queue is a static object itself. Thus the single user limitation.
As you can see I didn’ t put a lot of effort in this because it was not really the point where I wanted to focus on. Using a database would be a better idea, or whatever else comes to your mind.

The Application Logic layer

Responsible for handling user generated operations (i.e. start and stop capturing Twitter real time streaming) and subsequent automatically generated requests (more on that later). As shown in the class diagram, there are two object of particular interest, TwitterAccessService and TwitterStreamingListener.

Application Logic Layer class diagram

Application Logic Layer class diagram

The former, as the name implies, implements the Service pattern and is the interface towards the Presentation Layer, the latter is a wrapper for the Twitterizer API. It’s designed as a Singleton (here lies the second cause for  the single user limitation) and is using dependancy injection on the factory method to know which repository to use when a new Tweet or a new event arrives.
Let’s see the code:
namespace ApplicationLogic
{
    public class TwitterStreamingListener
    {
        private static TwitterStreamingListener _instance = null;
        private ITweetsRepository _repository;
        private List<string> _otherEvents;
        private bool _stopSearch;
        private ModelTranslationService _translationService;
        static OAuthTokens tokens = new OAuthTokens()
        {
            ConsumerKey = "yourConsumerKey",
            ConsumerSecret = "yourConsumerSecret",
            AccessToken = "yourAccessToken",
            AccessTokenSecret = "yourAccessTokenSecret"
        };

        public static TwitterStreamingListener GetInstance(ITweetsRepository repository)
        {
            if (_instance == null)
            {
                _instance = new TwitterStreamingListener(repository);
            }
            return _instance;
        }

        private TwitterStreamingListener(ITweetsRepository injected)
        {
            this._stopSearch = false;
            this._repository = injected;
            this._otherEvents = new List<string>();
            this._translationService = new ModelTranslationService();
        }

        public void StartCaptureStreaming(SearchParameters parameters)
        {
            StreamOptions options = new StreamOptions();
            options.Track.Add(parameters.SearchKey);
            this._stopSearch = false;

            TwitterStream stream = new TwitterStream(tokens, "RTSearch (Dev)", options);

            IAsyncResult result = stream.StartPublicStream(
                StreamStopped,
                NewTweet,
                DeletedTweet,
                OtherEvent
            );

            while (!this._stopSearch)
            {

            }

            stream.EndStream(StopReasons.StoppedByRequest, "Stop by user");
        }

        public void StopCaptureStreaming()
        {
            this._stopSearch = true;
        }

        void StreamStopped(StopReasons reason)
        {
            if (reason == StopReasons.StoppedByRequest)
            {
                this._repository.Clear();
                this._stopSearch = true;
            }
            else
            {
                //Do something...
            }
        }

        void NewTweet(TwitterStatus twitterizerStatus)
        {
            Tweet tweet = this._translationService.ConvertToViewModel(twitterizerStatus);
            this._repository.Add(tweet);
        }

        void DeletedTweet(TwitterStreamDeletedEvent e)
        {
            this._repository.Delete(e.Id);
        }

        void OtherEvent(TwitterStreamEvent e)
        {
            this._otherEvents.Add(e.EventType);
        }
    }
}
The static initialization block of code is where you have to put the 0Auth tokens obtained by registering the application on the Twitter Dev site.
In the StartCaptureStreaming method we setup the search key with the following code
StreamOptions options = new StreamOptions();
options.Track.Add(parameters.SearchKey);
TwitterStream stream = new TwitterStream(tokens, "RTSearch (Dev)", options);
And then we start the stream providing callbacks for main events.
IAsyncResult result = stream.StartPublicStream(
                StreamStopped,
                NewTweet,
                DeletedTweet,
                OtherEvent
            );
Whenever a new Tweet arrives, we simply add it to the repository:
void NewTweet(TwitterStatus twitterizerStatus)
{
   Tweet tweet = this._translationService.ConvertToViewModel(twitterizerStatus);
   this._repository.Add(tweet);
}

The repository is injected by the TwitterAccessService object, getting an instance for the repository object by the Factory.Furthermor, TwitterStreamingListener uses the ModelTranslationService object to translate from Twitterizer domain to our application domain model.

These two last objects mimic the behaviour of a (minimal and incomplete) Anti Corruption layer.

The Presentation Layer

This application has only one page to show to the user, and all of the possible actions happens inside it. Therefore the presentation layer will be really thin and simple: 1 page and 1 controller to handle actions.

As I said at the beginning of this article, there will not be much about model binding and validation, a very strong point of the MVC framework.

This is because I chose to use jQuery to load new tweets at runtime.

The page layout is really simple, with a search bar and two buttons to accept user inputs and a box to contain new tweets.

Here is how it looks like:
Search bar and tweet box

Search bar and tweet box

As you can see the page is really simple and clean, with a search bar, two buttons to star and stop the search, and a tweet box to display tweets. Tweets can pile up allowing vertical scrool bar to appear.

The html code is pretty straightforward, so I won’t cover it.

Let’s see the meaning of some of the variables declared in the javascript block of code:

var urlBase = 'http://twitter.com/';
var serviceStarted = false;
var timeOut = 0;
var singleTweetMode = false;
var last = 0;

“serviceStarted” is used to signal the javascript code that the user started a search.

“singleTweetMode” can be set to true to mean that we will display inside the tweets box one new tweet at a time, viceversa we will display several new tweets at a time, therefore pushing down all the old ones.

“last” is used to store the Id of the last tweet that has been shown, so that tweets will be shown respecting their original timeline.

Now let’s see the code in detail based on the different actions performed by the user.

Search

after pressing the Search button, the control passes to the javascript run() function, which, after validating user input, signal to the page that the service is started via the “serviceStarted = true” line of code.

Then, based on the operational mode that we chose via the singleTweetMode, tells the javascript engine that every x milliseconds it should invoke the poll() function.

After that, the application performs an Ajax postback with jQuery, passing the search key as a parameter for the HomeController hosted StartSearch action method, that will then ask the TwitterAccessService to start searching for tweets based on the keyword

function run() {
        if (!serviceStarted) {
            var value = $('input:text[name=searchKey]').val();
            if (value == null || value == '') {
                alert("Please enter a search keyword!");
                return false;
            }
            $('#SearchButton').val('Searching');
            serviceStarted = true;
            if (singleTweetMode) {
                timeOut = window.setInterval(poll, 2000);
            }
            else {
                timeOut = window.setInterval(poll, 10000);
            }
            //$('#SearchButton').attr('disabled', 'true');
            $.post('/Home/StartSearch', { searchKey: value });
        }
}
The HomeController will then invoke the service and then the TwitterStreamingListener object’ StartCaptureStreaming method.
The javascript poll() function therefore performs an Ajax postback to the GetTweets action method (hosted by the HomeController), using function displayNewTweets as a callback to invoke when data has been returned.
$('#LoadingImage').css("display", "inline");
$.post('/Home/GetTweets', { lastId: last }, displayNewTweets);
Let’s now see the code for the GetTweets action method
//
// Ajax: /Home/GetTweets
[HttpPost]
public ActionResult GetTweets(decimal lastId)
{
     JsonResult serializedTweets;
     if (singleTweetMode)
     {
         if (ViewBag.Count == null)
         {
              ViewBag.Count = 0;
         }
         else
         {
              ViewBag.Count++;
         }
         var tweet = service.GetTweet(lastId);
         serializedTweets = Json(tweet);
      }
      else
      {
         if (ViewBag.Count == null)
         {
             ViewBag.Count = 0;
         }
         else
         {
              ViewBag.Count++;
         }
         var tweets = service.GetTweets(lastId);
         serializedTweets = Json(tweets);
       }
       return serializedTweets;
 }
It’s very simple: it invokes the right service method based on the mode (a variable present also in the HomeController) whether it’s for single tweet or several at once, and then serializes the result in Json notation to give the control back to the callback javascript function displayNewTweets.

I serialized data in Json format in order to return data to javascript in a way that it’s simple for it to manage and natively supported by the framework.

Type JsonResult is of type ActionResult, so the method’ signature is still valid.

Upon returning from Post, the displayNewTweets javascript function is invoked, creating the necessary HTML code to display retrieved tweets:
function displayNewTweets(data) {
        $.each(data, function (count, item) {
            if (last < item.Id) {
                last = item.Id;
                addNew(item);
            }
        });
        $('#LoadingImage').css("display", "none");
}

function addNew(item) {
   $('#TweetsBody').prepend(renderTweet(item, 'hidden'));
}

function renderTweet(item) {
        importanceColor = getImportanceColor(item.NumberOfFollowers);
        return '<div class="tweet" id="' + item.ID + '">' +
               '<strong><a href="' + urlBase + item.Username + '" style="color:' + importanceColor + '">' +
               '<img src="' + item.ProfileImageURL + '" class="ProfileImg" alt="Usr image" />' +
               item.Username + '</a></strong><span class="text">' + ' ' + item.Text +
               '</span><span class="created_at"><br /><a href="' + urlBase + '#!/' + item.Username + '/status/' + item.ID + '">' +
               item.Timestamp.toString() + '</span></div>';
}

function getImportanceColor(number) {
        rgb = 255 - Math.floor(16 * (Math.log(number + 1) + 1));
        return 'rgb(' + rgb + ', 0, 0)';
}
In the addNew function the new tweet is put on top of the list, using the “prepend” built-in function.
Function “renderTweet” is just a matter of building up the HTML code to display the new tweet. The username is colored based on the importance of the user, and the importance is based on the number of followers.

Stop search

When the user presses the Stop button, the javascript stop function is invoked

function stop() {
        if (serviceStarted) {
            $('#SearchButton').val('Search');
            $('#StopButton').val('Stopping');
            window.clearInterval(timeOut);
            $.post('/Home/StopSearch');
            $('#StopButton').val('Stop');
            //$('#SearchButton').attr('disabled', 'false');
        }
}

The if statement prevents the user to perform an actual Stop operation even though no search is currently running.

After the check I need to clear the timer that is causing the poll function to be invoked over and over

window.clearInterval(timeOut);

Then I need to perform a post to notify the Twitterizer lib to close the stream and release the resources. To make this operation easy I chose to use the Singleton pattern so that only one stream can be open at once, and therefore it’s easy to retrieve its reference.

References

To write this tutorial I actually used and got inspired by a several web tutorial, stackoverflow posts and books. Some of them are:

The source code can be downloaded here .

That’s it, I really hope you enjoyed this article!

Valerio