Thursday, 27 March 2008

Unit Testing ASP.NET Pages Using WatiN

Introduction:

Unit testing is an integral part of the application design. Unit testing is applied at different levels of the application. In this article we will focus on the User Interface level unit testing. We will use WatiN to test our ASP.NET application.

What is WatiN?

WatiN is a tool inspired from Watir (Don't worry I will write an article on Watir  very soon) to test web pages. WatiN stands for Web Application Testing in .NET.

What are we testing?

In this article we will be testing a simple ASP.NET page. The page will demonstrate an agreement acceptance scenario. The user will type his name in the TextBox, click the "I agree" checkbox and then press the submit button. This is a very simple page to test but after you are familiar with the workings of the WatiN framework you can apply the same concepts for testing large pages.

Here is the screen shot of our page:

 Testing the Agreement Page:

Add a class library project to your solution and make references to the testing tool (I am using MbUnit but you can use NUnit or VS Team Suite Test Project) and the WatiN library. You can download the WatiN library from the here.  

Here is the simple test which makes sure that the user has agreed the agreement.

[TestFixture(ApartmentState = ApartmentState.STA)]
    public class TestAgreementPage
    {
        [Test]
        public void test_can_accept_the_user_agreement()
        {             
            IE ie = new IE(ConfigurationManager.AppSettings["DefaultPageUrl"] as String);

            ie.TextField("txtName").TypeText("Mohammad Azam");

            ie.CheckBox("chkAgree").Checked = true;
           
            ie.Button("Btn_Agree").Click();

            Assert.AreEqual("Valid",ie.Span("lblMessage").Text);           
        }       
       
    }

The class is decorated with the TestFixture attribute which also makes sure that the tests are run in a Single Threaded Apartment state. This is because the test will launch the Internet Explorer.

The IE class contained in the WatiN library does the main work. IE class opens the Internet Explorer and refers to the HTML controls using their name or ID. The line ie.TextField("txtName").TypeText("Mohammad Azam") refers to the TextBox with the ID "txtName". When the browser is launched WatiN will write the text "Mohammad Azam" inside the TextBox named "txtName". This will be done right before our eyes and you would be able to see WatiN typing text into the TextBox. Then the CheckBox with the ID "chkAgree" will be checked. Finally, WatiN will press the submit button and the form is submitted.

If you run this test it will fail. This is because the Label named "lblMessage" is never set to "Valid". Let's do that in the page code behind.

 protected void Btn_Agree_Click(object sender, EventArgs e)
        {
                lblMessage.Text = "Valid";
        }

Now, if you run the test it will pass. But, something does not seem right. Let's remove the following line from our test.

ie.CheckBox("chkAgree").Checked = true;

This means we are not going to mark the CheckBox as checked. If you run the test again it will pass. This is not right! The test should only pass when the CheckBox is checked. Let's make a change to the code behind of the page.

protected void Btn_Agree_Click(object sender, EventArgs e)
        {
            if (chkAgree.Checked)
            {
                lblMessage.Text = "Valid";
            }
        }

Now, the test will only pass when the CheckBox is checked.

Programmatically Running the Web Server:

In the above example you will need to start your WebServer by either manually running the command line tool or by running the Web Application Project. But, sometimes you will want the unit test project to dynamically start a web server. Let's check it out how this can be done.

First, if you want to start the ASP.NET internal server (WebDev.WebServer) then you can use command line to start it. The syntax is shown below:

WebDev.WebServer.exe /port:1950 /path: "C:\Projects\MyWebApplication"

You will need to be in the same directory where the WebDev.WebServer exists. By default it is located at:

C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\WebDev.WebServer.exe

Now, let's use this information to start the server using unit tests. First, here is the required configuration saved in the configuration file (App.config).

<configuration>

  <appSettings>
    <add key="WebServerExePath" value="C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\WebDev.WebServer.exe"/>
    <add key="Port" value="1950"/>
    <add key="WebApplicationPath" value="c:\projects\demowatiN\demowatiN"/>
    <add key="DefaultPageUrl" value="
http://localhost:1950/Default.aspx"/>  
  </appSettings>
 
</configuration>

The BaseTestPage class will use this information to start the server and all the test classes will derive from the BaseTestPage class to use this functionality. 

Here is the complete code for the BaseTestPage class:

public class BaseTestPage
    {
        static Process server = null;

        static BaseTestPage()
        {
            if (Process.GetProcessesByName("WebDev.WebServer").Count() == 0)
            {
                string webServerExePath = (string)ConfigurationManager.AppSettings["WebServerExePath"];
                server = new Process();
                Process.Start(webServerExePath, GetWebServerArguments());
            }
        }

        public static string GetWebServerArguments()
        {
            string args = String.Format("/port:{0} /path:\"{1}\"",GetPort(),GetWebApplicationPath());
            if (String.IsNullOrEmpty(args)) throw new ArgumentNullException("Arguments is not defined");
            return args;
        }

        public static string GetPort()
        {
            string port = ConfigurationManager.AppSettings["Port"] as String;
            if (String.IsNullOrEmpty(port)) throw new ArgumentNullException("Port is null or empty");

            return port;
        }

        public static string GetWebApplicationPath()
        {
            string webApplicationPath = ConfigurationManager.AppSettings["WebApplicationPath"] as String;
            if (String.IsNullOrEmpty(webApplicationPath)) throw new ArgumentNullException("WebApplicationPath is null or empty");

            return webApplicationPath;
        }

      
    }

We used a static constructor to make sure that the process is not running. If it is not running we make a new process and start it else we use the old process. The GetWebServerArguments(), GetPort() and GetWebApplicationPath() are just helper methods to improve the readability.

Finally, you will derive all your unit test classes from the BaseTestPage class as shown below:

public class TestAgreementPage : BaseTestPage

Now, if you run your unit test project will start the WebServer and then run all the tests.

Conclusion:

In this article we learned how to unit test our user interface layer. Unit testing the user interface helps us to understand the requirements of the interface and quickly see the expected result based on the user input. If this testing is done manually then it might take a lot of time.

 
 

Wednesday, 26 March 2008

Supporting Complex Types in Property Window

ntroduction

Whenever you set any property of a control in the property window, the property window needs to save this property value in the .aspx file. This process is known as code serialization. For properties that are of simple types (such as integer and string) this code serialization happens automatically. However, when property data types are user defined complex types then you need to do that work yourself. This is done via what is called as Type Converters. This article is going to examine what type converters are and how to create one for your custom control.

Type Converters

A type converter is a class that converts values entered in the property window to the actual data type of the property and vice a versa. The type converter class is inherited from TypeConverter or ExpandableObjectConverter base class. If you inherit from TypeConverter base class then you need to supply a delimited string in the property window where each part of the string corresponds to a property of the underlying complex type. If you inherit from ExpandableObjectConverter base class then Visual Studio makes your job easy by providing an expandable tree to enter the individual property values. The following figure shows how this expandable region looks like:

Creating an Expandable Type Converter

As an example of creating a type converter let's assume that you have a custom control that displays full name in the web form. The Name property of the control allows you to specify the full name to be displayed. The Name property is of type FullName. The FullName class consists of two public properties namely FirstName and LastName.

Creating FullName class

To begin developing this example first of all create a new Web Control project in Visual Studio. Add a new class to the project and name it as FullName. Code the FullName class as shown below:

[Serializable] public class FullName { private string strFName; private string strLName;  public string FirstName  { get { return strFName; } set { strFName = value; } }  public string LastName { get { return strLName; } set { strLName = value; } }  public FullName() { }  public FullName(string fname, string lname) { strFName = fname; strLName = lname; } }

The FullName class simply consists of two public properties namely FirstName and LastName. Notice that the FullName class is marked as [Serializable]

Creating FullNameConverter class

Now add another class to the project and name it as FullNameConvertor. Inherit the FullNameConvertor class from ExpandableObjectConverter base class. As a convention the type converter class names should have name of the class they convert attached with "Converter" at the end. Once created you need to override certain methods of the base class. These methods are explained next.

  • bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
    The CanConvertFrom method tells the property window whether the source type can be converted to the property data type. Most of the cases you will ensure that if the source type is string then the method returns true; false otherwise.
  • bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
    The CanConvertTo method tells the property window whether a property value can be converted to the destination data type. Most of the cases you will ensure that if the destination type is string then the method returns true; false otherwise.
  • object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
    The actual task of converting a source value (string) into the destination type (FullName) is done inside ConvertFrom method
  • object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destinationType)
    The actual task of converting a property value (FullName) to the destination type (string) is done inside ConvertTo method.

The following code shows all these methods for FullNameConverter class.

public override bool CanConvertFrom (ITypeDescriptorContext context, Type sourceType) { if (sourceType == typeof(string)) { return true; } else { return base.CanConvertFrom(context, sourceType); } }  public override bool CanConvertTo (ITypeDescriptorContext context,  Type destinationType) { if (destinationType == typeof(string)) { return true; } else { return base.CanConvertTo(context, destinationType); } }  public override object ConvertFrom (ITypeDescriptorContext context,  System.Globalization.CultureInfo culture,  object value) { if(value is string) { string[] names = ((string)value).Split(' '); if (names.Length == 2) { return new FullName(names[0],names[1]); } else { return new FullName(); } } else { return base.ConvertFrom(context,culture,value); } }  public override object ConvertTo (ITypeDescriptorContext context,  System.Globalization.CultureInfo culture,  object value, Type destinationType) { if (value is string) { FullName name = value as FullName; return name.FirstName + " " + name.LastName; } else { return base.ConvertTo(context, culture, value,  destinationType); } }

The CanConvertFrom() method checks if the data type of proposed value is string. If so it returns true otherwise base class version of CanConvertFrom() method is called. Similar job is done inside CanConvertTo() method. Remember that these two methods though sound similar are called at different times. The CanConvertFrom() method is called when you enter a value in the property window whereas CanConvertTo() method is called when property window reads previously serialized property value.

The ConvertFrom() method converts a supplied string value into an instance of type FullName. It does so by splitting the source string (e.g. Nancy Davalio) at the occurrence of a white space. An instance of FullName class is returned based on the supplied FirstName and LastName values.

The ConvertTo() method does reverse of ConvertFrom() method. It converts a FullName instance into its string representation. This is done by simply concatenating FirstName property, a white space and the LastName property. The resultant string is returned from the ConvertTo() method.

Note that in the above example we inherited FullNameConverter class from ExpandableObjectConverter base class. Even if you inherit from TypeConverter base class the process of overriding the methods remains the same.

Attaching type converter to FullName class

Now that you have completed the FullNameConverter class it's time to attach it to the FullName class. This is done as follows:

[TypeConverter(typeof(FullNameConvertor))] [Serializable] public class FullName ...

You need to decorate the FullName class with [TypeConverter] attribute. The TypeConverter attribute accepts the type information of a class that is acting as a type converter for this class.

Synchronizing markup and property window

Whenever you make any change in the property window immediately the new values should be saved to the .aspx file. To enable this behavior you need to mark the FirstName and LastName properties with the following additional attributes.

[RefreshProperties(RefreshProperties.All)] [NotifyParentProperty(true)]

The RefreshProperties() attribute should be familiar to you because we discussed it in the previous article of this series. It simply refreshes the property window by re-querying all the property values. More important is the NotifyParentProperty() attribute. This attribute governs whether the parent property (Name) is to be notified when any of the child properties (FirstName and LastName) are changed. This way the parent property can reflect the newly assigned values.

Coding the custom control

Now it's time to develop our custom control. We will call it as FullNameLabel and it resembles as shown below:

public class FullNameLabel : WebControl { [DesignerSerializationVisibility (DesignerSerializationVisibility.Visible)] [PersistenceMode(PersistenceMode.InnerProperty)] public FullName Name { get { return ViewState["_fullname"] as FullName; } set { ViewState["_fullname"] = value; } }  protected override void Render(HtmlTextWriter writer) { if (Name != null) { writer.WriteFullBeginTag("span"); writer.Write(Name.FirstName); writer.Write("&nbsp;"); writer.Write(Name.LastName); writer.WriteEndTag("span"); } } }

The code inside the FullNameLabel control is not a rocket science. It simply declares a public property called Name that is of type FullName. It then emits the first name and last name separated by a white space in the overridden Render() method.

Carefully notice the declaration of FullNameLabel class. It is marked with two special attributes viz. [DesignerSerializationVisibility] and [PersistenceMode]. The [DesignerSerializationVisibility] attribute governs whether a property will be serialized. The DesignerSerializationVisibility enumeration has four possible values viz. Default, Visible, Content, Hidden. The value of Content indicates that the contents of the property will be serialized.

The [PersistenceMode] attribute governs how a property will be serialized. The PersistenceMode enumeration has four values namely Attribute, InnerProperty, InnerDefaultProperty and EncodedInnerDefaultProperty. The value of InnerProperty indicates that the Name property will be serialized as a nested tag of the custom control tag.

You can see details of other enumerated values of DesignerSerializationVisibility and PersistenceMode enumeration in MSDN help.

If you use the FullNameLabel control on a web form you should see its Name property in the property window as shown below:

Notice how the FirstName and LastName properties appear as sub-properties of Name property. The serialized markup of the control after the Name property is set looks like this:

<cc1:FullNameLabel ID="FullNameLabel1"  runat="server" EnableTheming="True"> <Name FirstName="Nancy" LastName="Davalio" /> </cc1:FullNameLabel>

Notice how the Name tag appears as a nested tag of <FullNameLabel> tag.

In the next article of this series I will show how to create custom editors to set a property value. Stay tuned.
 

Tuesday, 25 March 2008

Adding Multiple Rows in the GridView Control

Introduction:

A while back an article was published on www.gridviewguy.com which explained how to add a single row at the bottom of the GridView control. You can read the article using this link. Many readers were interested in the idea of adding multiple rows to the GridView. This article explains how to add multiple rows to the GridView control.

Populating the GridView Control:

The first task is to populate the GridView control. We will be using the LINQ to SQL Classes to populate the GridView but you can use any data container that you like.

private void BindData()
        {
            NorthwindDataContext northwind = new NorthwindDataContext();
            gvReport.DataSource = GetProducts();
            gvReport.DataBind();
        }

        // since the product list is long I am only selecting three products
        private List<Product> GetProducts()
        {
            NorthwindDataContext northwind = new NorthwindDataContext();
            return (from p in northwind.Products
                    select p).Take(3).ToList<Product>();
        }

The database is the Northwind database and we are using the Products table of the database. The GetProducts method returns the top three products from the Products table (You can return all the rows it does not really matter).

Here is the ASPX part of the code:

<asp:GridView ID="gvReport" runat="server" AutoGenerateColumns="false">
   
    <Columns>
   
    <asp:TemplateField HeaderText="ProductName">
    <ItemTemplate>
    <%# Eval("ProductName") %>
   
    <asp:TextBox ID="txtProductName" runat="server" Visible='<%# DoesProductExists( (string) Eval("ProductName"))  %>' />
   
    </ItemTemplate>
    </asp:TemplateField>
   
     <asp:TemplateField HeaderText="CategoryID">
    <ItemTemplate>
   <asp:DropDownList ID="ddlCategories" DataSource=<%# GetCategories() %> DataTextField="CategoryName" DataValueField="id" runat="server" />
    </ItemTemplate>
    </asp:TemplateField>
   
    </Columns>
   
    </asp:GridView>

The first column displays the "ProductName". If the ProductName is not available then a TextBox is created which is used to enter a new ProductName.

The GetCategories method is used to populate the DropDownList in the second column of the GridView control. Here is the implementation of the GetCategories method.

  protected List<Category> GetCategories()
        {
            NorthwindDataContext northwind = new NorthwindDataContext();
            return northwind.Categories.ToList<Category>();
        }


Adding New Rows to the GridView Control:

Now, let's see how to add new rows to the GridView control. The rows are added using the "Add" Button control. Here is the implementation of the add button click.

// adds the new row
        protected void Button1_Click(object sender, EventArgs e)
        {
            Count += 1;

            var list = GetProducts();
            // add empty elements at the end of the list
            list.AddRange(new Product[Count]);
           
            gvReport.DataSource = list;
            gvReport.DataBind();
        }

Let's first talk about how we are going to add empty rows to the GridView control. Each time a button is clicked the postback is triggered. So, we need a way to know how many empty rows have to be created. We will use ViewState to store the number of rows that have to be created and then add the rows in the product list as empty products.

The Count property in the button click code is used to store the number of empty rows to be created. Here is the implementation of the Count property.

public int Count
        {
            get
            {
                if (ViewState["Count"] == null)
                    return 0;

                return (int) ViewState["Count"];
            }
            set
            {
               
                ViewState["Count"] = value;
            }
        }

The list.AddRange(new Product[Count]); line is used to append the rows to the product list.

The effect is shown in the GIF Animation below:


I have also used UpdatePanel to eliminate the server postback.

I hope you liked the article, happy coding!
 
Author: AzamSharp
 

Saturday, 22 March 2008

Building a Volta Control : A Flickr Widget

By: Tanzim Saqib
Download Sample Code

This article illustrates how to create a Volta control around Flickr, the popular image hosting service.

What is Volta?

Microsoft Live Labs Volta is Microsoft's emerging toolset that enables developers to build multi-tier web applications by applying familiar .NET techniques and patterns. The intended use of this toolset is for Developers to build web application as .NET client application, and then specify the portions of the codes that will run on the client and server side. The compiler then creates cross browser JavaScript, Web Services, communication, serialization, security etc. to tie the tiers together. This article is based on the first CTP of Volta considering its current limitations. We will see how we can create a Volta control that the compiler can convert into an AJAX Widget without requiring us writing a single line of JavaScript code. We will write code in our very familiar C# language. In this article, you will end up creating a Flickr widget like the one shown in figure 1.

Figure 1: The Flickr widget built using Volta

Currently Volta is at its first CTP release, which was made publicly available on December 5 last year. Volta requirements and the installer can be found at http://labs.live.com/volta/download/ Since it's just the first CTP, there are still a lot of limitations. I hope they will get over those in the next CTP or so.

The Specification of the Widget

The widget will have the following features:

  • The photos for a particular Flickr user will be shown in a 3x3 grid.
  • The tooltip of each of the photos will display the title of the image.
  • There will be "prev" and "next" buttons for navigation.
  • On click of each of the photos, a new window will open with the original Flickr source.
  • There will not be a single line of JavaScript code.

The Flickr API

Flickr is a very popular photo hosting and sharing service. It is considered to be one of the leading examples of Web 2.0 applications. Users of Flickr can host hundreds of photos and share them with others. Flickr allows users to tag their photos, which make them easy to find. It has some of the best photo management features, which let users control access to photos pretty efficiently. A few of the interesting features of Flickr are the possibility of marking photos as favourites, grouping photo pool and Flickr Interestingness. Flickr also offers users the ability to release images under certain common usage licenses.

Flickr has an open API besides RSS and Atom feeds. The Flickr API offers numerous methods. It supports different popular request and response formats that should be familiar to the third party developers:

  • REST
  • XML-RPC
  • SOAP

We choose REST because it is the simplest among others, and it is performed by simple HTTP GET or POST actions.

The Flickr Methods

The Flickr API exposes 115 methods so far. The methods are categorized in Authentication, Photos, User Activity, Photo Pool, Photo Upload and many more. Few of them are named as follows:

  • flickr.photos.getInfo
  • flickr.photos.search
  • flickr.interestingness.getList
  • flickr.photos.transform.rotate
  • flickr.test.echo

A full listing can be found at: http://www.flickr.com/services/api/.

The Flickr Request, Response format

The general format of a Flickr request is:

  1. http://api.flickr.com/services/rest/?api_key=__API_KEY__&method=__METHOD_NAME__&param1=value1&param2=value2  

__API_KEY__ is replaced by the API key

  1. http://api.flickr.com/services/rest/?api_key=9163070a740049b1bfe553864a4f1519&method=flickr.interestingness.getList  

The response of a REST query can be rendered by the browser. Simply copy the URL above and paste it into the browser's address bar; then hit enter. You will see a response text somewhat like the following:

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <rsp stat="ok">  
  3. <photos page="1" pages="5" perpage="100" total="500">  
  4.  <photo id="621909774" owner="30959251@N00" secret="70cdeec6f7" server="1411"  
  5.    farm="2" title="Yeah, holidays!" ispublic="1" isfriend="0" isfamily="0" />  
  6.  <photo id="620373432" owner="80176513@N00" secret="7c9d5769ab" server="1151"   
  7.    farm="2" title="In the Mood" ispublic="1" isfriend="0" isfamily="0" />  
  8.  <photo id="619761326" owner="50008412@N00" secret="5aec4bf2ec" server="1037"  
  9.    farm="2" title="Hear me roar!!!!" ispublic="1" isfriend="0" isfamily="0" />  
  10.  <photo id="619727916" owner="46355903@N00" secret="76680dafa6" server="1146" farm="2"   
  11.    title="Rain, rain, go away, I want to do some Sunrise photos" ispublic="1"   
  12.    isfriend="0" isfamily="0" />  
  13. <photo id="620355782" owner="42009080@N00" secret="148cbdb6b0" server="1145" farm="2"   
  14.    title="Beneath A Black Sky" ispublic="1" isfriend="0" isfamily="0" />  
  15. <photo id="621726130" owner="35373726@N00" secret="6b3d8fa0ca" server="1114" farm="2"  
  16.    title="" ispublic="1" isfriend="0" isfamily="0" />  
  17.   
  18. ... ...  
  19.   
  20. </photos>  
  21. </rsp>  

As you see the response is pretty straightforward. It simply has a bunch of photo tags and each of the tags has information about that particular photo. A photo tag has the unique identifier of the photo, owner identifier, server information and also a title. In our widget, we need to parse such XML and display the photos.

Note the rsp tag with a stat attribute that has ok as value. It means the API call was completely errorless and successful. Let us make some mistakes intentionally to see how Flickr reacts. Hit the browser using the previous URL with a wrong API Key. You will receive an XML as response like the following:

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2.   
  3. <rsp stat="fail">  
  4.   <err code="100" msg="Invalid API Key (Key not found)" />  
  5. </rsp>  

Here the stat attribute has fail as value, which means some kind of problem has occurred during the API call and the Flickr API notifies it so that the code can decide whether it should crawl any further or not.

The User NSID

User NSID is an alphanumeric string which uniquely identifies a user. It is often useful, especially in our case where our widget will display the photos of a particular user. In order to retrieve the NSID for a user, Flickr offers a method named flickr.people.findByUsername which takes the Flickr screen name of the user and returns an XML like the following:

  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <rsp stat="ok">  
  3.   <user id="9198206@N07" nsid="9198206@N07">  
  4.     <username>your_screen_name</username>  
  5.   </user>  
  6. </rsp>  

The Solution Structure

First of all, create a new Volta Application and name it VoltaFlickrWidgetDemo. Then, add a new Volta Control application named VoltaFlickrWidget to the solution. Add a class to the Volta control project named FlickrHelper.cs. This class contains very useful methods that fetch Flickr photo data and make them easily accessible by the control. Now include the VoltaFlickrWidget project in the References of the VoltaFlickrWidgetDemo project. The resulting solution structure will be like the one shown in the following figure.

Figure 2: Solution Structure

The Flickr Volta Control

The Flickr Volta control we are going to develop should be easy to add to the Volta page considering that there is a div element with an id of divWidgetContainer present in the Volta page:

  1. var divWidgetConatiner = Document.GetById<Div>("divWidgetContainer");  
  2.   
  3. VoltaControl1 flickrWidget = new VoltaControl1("screen_name""9149b1bfe387273833553864a4f1519""Flickr Widget", 300, 270);  
  4.   
  5. flickrWidget.AddToDiv(divWidgetConatiner);  
  6. flickrWidget.DownloadPhotos();  

Here VoltaControl1 is the Flickr control we are going to build. Sorry for the naming, but Volta is still immature and can't rename the underlying HTML file of the Volta Control which we will discuss later. Thus, instead of making it confusing, we are keeping it called VoltaControl1. In the above code snippet, VoltaControl1 is initialized with the screen name, API key, control's title, height and width properties. The AddToDiv method of the control adds itself to the div passed as a parameter and then the DownloadPhotos method starts downloading the photos. As soon as AddToDiv is called, the control's HTML file content is injected into the DOM, and HTML elements become accessible like so:

  1. Document.GetById<Div>("divSomeDiv");  

The FlickrHelper

The FlickrHelper class basically performs two operations. One of them is retrieving the NSID of the user against the screen name; and the other is fetching the photo collection of the specified user. The following GetNSID method performs an Asynchronous REST call to the Flickr API and receives the NSID back:

  1. public void GetNSID()  
  2. {  
  3.     IHttpRequest request = HttpRequestFactory.Create();  
  4.     request.AsyncSend("POST", URL_USER_NSID + ScreenName, string.Empty,  
  5.         delegate(string response)  
  6.         {  
  7.             OnNsidLoaded(new NsidLoadedEventArgs(response));  
  8.         });  
  9. }  

Both the IHttpRequest interface and the HttpRequestFactory class can be found in the Microsoft.LiveLabs.Volta.MultiTier namespace. The AsyncSend method performs the asynchronous call and then calls back the delegate, inside of which the OnNsidLoaded event is fired to notify the user of the FlickrHelper class that the NSID data has just arrived. An instance of the NsidLoadedEventArgs is passed to the subscriber of that event to make the NSID available after parsing the necessary XML:

  1. public class NsidLoadedEventArgs : EventArgs  
  2. {  
  3.     public string Nsid { getset; }  
  4.   
  5.     public NsidLoadedEventArgs(string response)  
  6.     {  
  7.         var xdocument = new XmlDocument();  
  8.         xdocument.LoadXml(response);  
  9.         var xnUser = xdocument.GetElementsByTagName("user")[0];  
  10.   
  11.         if (xnUser == null)  
  12.             throw new Exception("Incorrect screenName");  
  13.         else  
  14.         {  
  15.             var xelUser = xnUser as XmlElement;  
  16.             Nsid = xelUser.GetAttribute("nsid");  
  17.         }  
  18.     }  
  19. }  

Similarly, an asynchronous call is made for fetching photo feed:

  1. public void DownloadPhotos(string nsid)  
  2. {  
  3.     IHttpRequest request = HttpRequestFactory.Create();  
  4.     request.AsyncSend("POST", URL_PHOTOS + nsid, string.Empty,  
  5.         delegate(string response)  
  6.         {  
  7.             OnPhotosLoaded(new PhotosLoadedEventArgs(response, nsid));  
  8.         });  
  9. }  

In the above code snippet, an instance of PhotoLoadedEventArgs is passed to the subscriber. In this case the photo URL collection is sent back using a Dictionary object:

  1. public class PhotosLoadedEventArgs : EventArgs  
  2. {  
  3.     public Dictionary<intstring> PhotoPool { getset; }  
  4.   
  5.     public PhotosLoadedEventArgs(string response, string nsid)  
  6.     {  
  7.         ...  
  8.         // code edited to save space  
  9.     }  
  10. }  

The Control Logic

The contents of VoltaControl1.cs file is basically HTML manipulation, so most of it is self-explanatory and doesn't need further discussion. Yet there are few points to ponder. The most important part is the DownloadPhotos method:

  1. public void DownloadPhotos()  
  2. {  
  3.     _WidgetContent.InnerHtml = "<br /><center><i>Downloading photos...</i></center>";  
  4.     flickrHelper.NsidLoaded += new NsidLoadedEventHandler(flickrHelper_NsidLoaded);  
  5.     flickrHelper.GetNSID();  
  6. }  

It registers the NsidLoaded event of FlickrHelper, which is handled by flickrHelper_NsidLoaded. Then, the GetNSID method is called. Therefore, as soon as the NSID data is available, it will call the flickrHelper_NsidLoaded method to handle the data:

  1. void flickrHelper_NsidLoaded(object sender, NsidLoadedEventArgs e)  
  2. {  
  3.     if (e.Nsid == null)  
  4.         throw new Exception("Invalid NSID supplied.");  
  5.     else  
  6.     {  
  7.         _Nsid = e.Nsid;  
  8.         flickrHelper.PhotosLoaded += new PhotosLoadedEventHandler(flickrHelper_PhotosLoaded);  
  9.         flickrHelper.DownloadPhotos(_Nsid);  
  10.     }  
  11.   
  12.     flickrHelper.NsidLoaded -= flickrHelper_NsidLoaded;  
  13. }  

Unlike the NSID, the DownloadPhotos method of FlickrHelper returns a Dictionary of photos URLs and the LoadPhotos method iterates through the photos and displays them in a 3x3 grid using a typical logic for pagination.

Construction of the Volta Page

The construction of the Volta page is simple. The default page would do, but for our project we need an extra div with an id of divWidgetContainer. Moreover, we will add some other HTML code to beautify it. As for the Volta control, we are keeping the Volta page called VoltaPage1. We will use some CSS classes, and add a CSS file named Style.css in the root of the Volta application. In the current release of Volta, CSS files are not automatically discovered even if you add it in the page's HTML as links. To make the Style.css file usable by the Volta page, perform the following steps:

  1. Right click on the Style.css from the Solution Explorer and choose Properties
  2. Set the Build Action as Embedded Resource
  3. Open the VoltaPage1.Designer.cs file and add the following line before namespace VoltaFlickrWidgetDemo:
  1. [assembly: VoltaFile("Style.css")]  

As stated before, the control is very convenient to use:

  1. VoltaControl1 flickrWidget = new VoltaControl1("ajaxwidget",  
  2.     "9163070a740049b1bfe553864a4f1519""Flickr Widget", 300, 270);  
  3.   
  4. flickrWidget.AddToDiv(divWidgetConatiner);  
  5. flickrWidget.DownloadPhotos();  

This is pretty much it. When you download and run this project, you will see Flickr REST calls and photo navigation all performed within an AJAX application created without writing any JavaScript code. Volta generated the necessary client code for us to run the application in multiple browsers.

Summary

You have learned how to create a Volta control around the Flickr API. The code was purely written in .NET, yet we achieved complete AJAX functionality without writing a single line of JavaScript code introducing typical ContentProxy kind of things.

Source: http://dotnetslackers.com/articles/aspnet/BuildingAVoltaControlAFlickrWidget.aspx

Friday, 21 March 2008

Extending the GridView to Include Sort Arrows

Introduction
Before ASP.NET version 2.0 was released, I wrote a book and dozens of articles on the DataGrid control, which was the most functional data Web control in the ASP.NET 1.x toolbox. While the DataGrid still exists in ASP.NET 2.0, the GridView control serves as a much more functional and feature-rich choice. The GridView can be bound to data source controls like the SqlDataSource and ObjectDataSource, and paging, sorting, editing, and deleting can be implemented without having to write a single line of code.

While the DataGrid (and GridView) offer built-in sorting support, there is no visual feedback as to what column the data is sorted by. In Part 18 of the An Extensive Examination of the DataGrid Web Control article series, I showed how to dynamically update a sortable DataGrid's header columns so that an up or down arrow image would appear next to the sorted column name depending on whether the column was sorted ascendingly or descendingly (view a live demo). This was accomplished by programmatically looping through the DataGrid's Columns collection and adding the up or down arrow image to column whose SortExpression value matched the DataGrid's SortExpression.

A down arrow image is shown in the Price column because the data is sorted in descending order, by price.
I recently needed to implement this functionality in a GridView control. Rather than adding this code to an ASP.NET page, like I did with the DataGrid demo, I decided to instead create a custom Web server control that extended the GridView control, adding the necessary functionality. In this article we will look at the steps for building such a custom control as well as how to use the control in an ASP.NET page. The custom control's complete source code and a demo application are available for download at the end of this article. Read on to learn more!

A Quick Primer on GridView Sorting
The GridView control makes sorting a breeze. Simply set the grid's AllowSorting property to True. Doing so renders each column's header as a LinkButton. When this LinkButton is clicked, a postback ensues and the GridView's Sorting event is fired. If the GridView is bound to a data source control that supports sorting, the GridView will internally re-bind to the data source using the new sort order. If you have programmatically bound data to the GridView, then you will need to create an event handler for the Sorting event, and manually sort and re-bind the data. After the Sorting event has fired and the data has been re-sorted, the GridView fires its Sorted event, thereby completing the sorting workflow.

Each column in the GridView has a SortExpression property that indicates the data field by which the data should be sorted if that column's sorting LinkButton is clicked. When the LinkButton in a column header is clicked, the GridView sets its SortExpression property value to the value of the clicked column's SortExpression. If the GridView's SortExpression matches the SortExpression of the sorted column, then the GridView's SortDirection property is toggled. This functionality enables the GridView control to provide built-in, bi-directional sorting.

The following diagram depicts the sorting workflow, including the GridView's assignment of its SortExpression and SortDirection properties. Keep in mind that the entire process is kicked off by the user clicking a LinkButton in one of the sortable column's headers, and completes by displaying the data in the requested sorted order. So, from the end user's perspective, she clicks the text in a particular column header and the data is sorted by that column.

The GridView's sorting workflow.

For more information on sorting with the GridView, along with a downloadable demo, check out the Paging and Sorting Report Data tutorial.

Creating a Custom Server Control that Extends the GridView
When I implemented the up and down arrow images for the DataGrid control in Part 18 of the An Extensive Examination of the DataGrid Web Control article series, I put the code that added (or removed) the arrow images in the ASP.NET page's code-behind class. Specifically, the code enumerated the DataGrid's Columns collection and, for every column, started by removing any <img> elements that were present in the HeaderText. This "cleaned out" the up or down arrow image from all columns. After removing any <img> elements, the code checked to see if the current column's SortExpression matched the DataGrid's SortExpression. If so, then an <img> element was tacked on to the end of the column's HeaderText, displaying the up or down arrow image depending on whether the data was being sorted in ascending or descending order.

Like the DataGrid, the GridView has a Columns collection; each column has a SortExpression; the GridView has SortExpression and SortDirection properties. In short, I could have implemented the up and down arrow images in the GridView using code in the ASP.NET page very similar to that used for the DataGrid. However, I decided that instead of having to write code for each page where I wanted to show up and down arrow images, I decided to instead build a custom server control that extended the GridView control. I could then bake in the necessary code by overriding the necessary GridView methods.

To create a custom server control that extends an existing control, create a public class that derives from the Web control that you want to extend. I created a class named GridView in my skmControls2 Class Library project. (The skmControls2 Class Library project was first created and discussed in Creating a TextBox Word / Character Counter Control.)

public class GridView : System.Web.UI.WebControls.GridView
{
    ... Override necessary GridView methods here ...
}

The GridView has a virtual InitializeRow method that is called each time a row is added to the GridView, including the header row. My first thought was to override this method and, for the header row, enumerate the fields and update each column's HeaderText appropriately (removing any image markup first, and then adding the up or down arrow image for the sorted row). However, if you attempt to update a GridView's field's properties during the databinding stage, the field reports to the GridView that its state has been changed and that the data needs to be re-bound. Consequently, I ended up stuck in an infinite loop:

  1. Databinding would commence
  2. The InitializeRow method would be executed for the header row
  3. I'd update the HeaderText for the cells in the header row
  4. Updating the HeaderText would signal the GridView to re-bind its data, taking us back to Step 1! Eep.
Instead, I needed to modify the HeaderText properties either before or after the databinding process. After some thought, I realized that I really only needed to update the HeaderText properties after the data has been sorted. The GridView's OnSorted method is what raises the Sorted event, which occurs after the data has been sorted. Therefore, I decided to override this method and update the HeaderText properties there.

public class GridView : System.Web.UI.WebControls.GridView
{
   protected override void OnSorted(EventArgs e)
   {
      string imgArrowUp = ...;
      string imgArrowDown = ...;
      
      foreach (DataControlField field in this.Columns)
      {
         // strip off the old ascending/descending icon
         int iconPosition = field.HeaderText.IndexOf(@" <img border=""0"" src=""");
         if (iconPosition > 0)
            field.HeaderText = field.HeaderText.Substring(0, iconPosition);

         // See where to add the sort ascending/descending icon
         if (field.SortExpression == this.SortExpression)
         {
            if (this.SortDirection == SortDirection.Ascending)
               field.HeaderText += imgArrowUp;
            else
               field.HeaderText += imgArrowDown;
         }
      }

      base.OnSorted(e);
   }
}

The overridden OnSorted method starts by defining the URLs for the up and down arrow images. We'll discuss how these image URLs are determined later on in this article. Next, the GridView's Columns collection is enumerated. For each column, if there is an <img> element it is stripped off. Next, a check is performed to see if the current column's SortExpression matches the GridView's. If so, the HTML to display the up or down arrow image is appended to the column's HeaderText depending on the value of the GridView's SortDirection property.

An HTML Encoding Gotcha

This Control Has Been Enhanced!
The "HTML Encoding Gotcha" outlined here is no longer an issue with the enhancements made to this control on February 6th, 2008. For more information on these enhancements, see: Improving the Sort Arrows GridView Control.

One subtlety that I quickly stumbled upon when testing, is that the BoundField HTML encodes its HeaderText's content, by default. That is, when the BoundField was being rendered it was taking the <img> tag added in the OnSorted method and HTML encoding it, replacing < and > with &lt; and &gt;, respectively. In other words, it was transforming a HeaderText value of:

Price <img border="0" src="up.gif" />

To:

Price &lt;img border="0" src="up.gif" /&gt;

Consequently, the browser would display the sorted column's header text as Price <img border="0" src="up.gif" /> rather than showing an image.

This BoundField has an HtmlEncode property that, if True (the default), HTML encodes the entire content of the BoundField. One way to fix the header text being HTML encoded, then, would be to set the BoundField's HtmlEncode property to False. But what if you wanted the data rows to be HTML encoded, but not the header text?

To allow for this level of flexibility, I created another custom class in the skmControls2 project. This one extended the BoundField class and overrode the InitializeCell method, which is where the HTML encoding takes place. The BoundField HTML encodes its content only if both its HtmlEncode and SupportsHtmlEncode properties are True. SupportsHtmlEncode is a read-only property that, for the BoundField, always returns True. I overrode this property and instead had it return a value based on a private member variable. Then, in the InitializeCell method I set this member variable to false when initializing a header cell.

public class BoundField : System.Web.UI.WebControls.BoundField
{
   bool allowHtmlEncode = true;

   protected override bool SupportsHtmlEncode
   {
      get
      {
         return allowHtmlEncode;
      }
   }

   public override void InitializeCell(DataControlFieldCell cell, DataControlCellType cellType, DataControlRowState rowState, int rowIndex)
   {
      if (this.HtmlEncode && cellType == DataControlCellType.Header)
      {
         allowHtmlEncode = false;
         base.InitializeCell(cell, cellType, rowState, rowIndex);
         allowHtmlEncode = true;
      }
      else
         base.InitializeCell(cell, cellType, rowState, rowIndex);
   }
}

Since the BoundField is the only GridView field that HTML encodes its data, this hack is only needed for the BoundField. All other fields - the TemplateField, CheckBoxField, ButtonField, and so on - will correctly display the up and down arrow images without any extra hacks. We'll see how to use this custom BoundField class in the "Using the Custom GridView Control in an ASP.NET Web Page" section.

Specifying the Image URLs for the Up and Down Arrows
At this point you may be wondering how the custom GridView control determines the URLs for the up and down image arrows. The simplest option would be to require the page developer to specify the URLs for these two images. I created two such properties: ArrowUpImageUrl and ArrowDownImageUrl. But I also wanted to allow the page developer to have a working GridView even if she didn't want to spend the time to find up and down arrow images. Therefore, I embedded an up and down arrow image into the skmControls2 assembly and use these in the case where the developer does not specify a value for the ArrowUpImageUrl and ArrowDownImageUrl properties.

For more information on embedding resources into an assembly, and retrieving these resources through an ASP.NET page, be sure to read Accessing Embedded Resources through a URL Using WebResource.axd.

Using the Custom GridView Control in an ASP.NET Web Page
The download available at the end of this article includes the complete source code for the custom GridView and BoundField controls, as well as a demo ASP.NET website. To use the skmControls2 controls in an ASP.NET website, copy the DLL to the website's /Bin directory and then add the following @Register directive to the tops of the .aspx pages where you want to use the controls:

<%@ Register Assembly="skmControls2" Namespace="skmControls2" TagPrefix="skm" %>

(Alternatively, you can add this @Register directive in the Web.config file so that you do not need to add it to every ASP.NET page that uses the controls. See Tip/Trick: How to Register User Controls and Custom Controls in Web.config.)

Then just use the same GridView declarative markup you normally would, but replace the "asp" in the <asp:GridView> tags with "skm", as in <skm:GridView>. That's it! Likewise, if you're using any BoundFields that have their HtmlEncode properties set to True (the default), you will want to replace the "asp" in <asp:BoundField with "skm".

The demo included in the download has an ASP.NET page that shows the ProductID, ProductName, CategoryName, UnitPrice, and Discontinued fields from the Northwind's Products database table in a sortable GridView using BoundFields (namely, skmControls2 BoundFields), a TemplateField, and a CheckBoxField.

<skm:GridView ID="ProductsGrid" runat="server" AutoGenerateColumns="False" DataSourceID="NorthwindDataSource" AllowSorting="True">
   <Columns>
      <skm:BoundField DataField="ProductID" HeaderText="ID" InsertVisible="False"
         SortExpression="ProductID" />
      <asp:TemplateField HeaderText="Name" SortExpression="ProductName">
         <ItemTemplate>
            <asp:Label runat="server" Text='<%# Bind("ProductName") %>' id="Label1" Font-Bold="True"></asp:Label>
         </ItemTemplate>
      </asp:TemplateField>
      <skm:BoundField DataField="CategoryName" HeaderText="Category Name" SortExpression="CategoryName" />
      <skm:BoundField HtmlEncode="False" DataFormatString="{0:c}" DataField="UnitPrice" HeaderText="Price" SortExpression="UnitPrice" />
      <asp:CheckBoxField DataField="Discontinued" HeaderText="Discontinued" SortExpression="Discontinued" />
   </Columns>
</skm:GridView>

Note that the grid's declarative markup does not set the ArrowUpImageUrl or ArrowDownImageUrl properties. Consequently, the grid displays the standard up and down arrow images (see the first screen shot below). If you want to use custom arrow images, specify the URL either declaratively or programmatically. The properties can be set to absolute URLs (like http://www.example.com/images/UpArrow.gif) or relative paths using the ~ character, like ~/Images/UpArrow.png. The demo page includes a checkbox that allows you to toggle between using the default arrow images and custom ones.

The following two screen shots show the demo page in action. The first one shows the grid sorted by price in descending order. The built-in up and down arrow images are used here.

The grid sorted by price in descending order

The second image shows the grid sorted by product name in ascending order. Here, a custom up arrow image is used.

The grid sorted by product name in ascending order

Custom BoundField Markup is Lost When Using the Fields Dialog Box
If you use the custom BoundField controls from the skmControls2 library - e.g., <skm:BoundField ... /> - it is important to know that if you use the Fields dialog box to modify the GridView's columns, Visual Studio will replace the custom BoundField markup with the default markup (namely, <asp:BoundField ... />). The Fields dialog box is the dialog box displayed when you click the "Edit Columns" link from the GridView's Smart Tag.

Conclusion
In this article we looked at building a custom GridView server control (along with a custom BoundField control) in order to automatically display up and down arrow images for the sorted column. This arrow provides the user with visual feedback as to what column the data is sorted by, as well as whether the data is sorted in ascending or descending order. Best of all, this functionality is wrapped up in a custom server control, so you can utilize this behavior without having to write any code in your ASP.NET page's code-behind class. Be sure to download the source code and the demo application available at the end of this article.

Happy Programming!

  • By Scott Mitchell
  • Source: http://aspnet.4guysfromrolla.com/articles/012308-1.aspx