Amit's Blog

Sharing Thoughts and Learning

  Home  |   Contact  |   Syndication    |   Login
  43 Posts | 1 Stories | 236 Comments | 14 Trackbacks

News

About Me?
Read it in
Blog Statistics
Proud Member of

Archives

Post Categories

Articles

Book Review

I Visit.

OpenSource Project(s)

Tuesday, September 18, 2007 #

Thanks to Joe Stagner for setting up a blog for me. I will be posting over there from here on. I will also incrementally import the older posts. Please update your links. I am expecting the feed readers will not bothered as the feedburner account will be the same. Sorry for the inconvenience, I promise you will find me for a long time blogging in that space.

Blog Moved
http://weblogs.asp.net/rashid/

Edit: The reason behind I am moving this Blog, I think it is a better place for Asp.net developers where the posts will be evaluated by all the leaders. But my blessing will always be with GWB team for makging such an wonderful place for the blogging..


Monday, September 17, 2007 #

My new article has been just published for September Ajax Column in DotNetSlackers.com. In this article, I have answered few common issues which I found often discussed in Asp.net Ajax Web Service Forum. In this column, I have covered:

  • Web Service Method, succeededCallback and failedCallback methods signature.
  • Complex data type interchange which includes Array, Dictionary, Custom Class.
    • GenerateScriptType Attribute
    • ScriptIgnore Attribute
    • Custom JavaScriptConverter class
  • Progress Indicator for long running task.
  • Soap Header/Custom Http Header

Check it out.

kick it on DotNetKicks.com


Saturday, September 15, 2007 #

In this post, I will show you how to compress the Asp.net Ajax Web Service response, To understand the benefits of compression let us start with a simple example, Consider you have an web service which returns a large data like the following:

[WebMethod()]
public string GetLargeData()
{
    using (StreamReader sr = File.OpenText(Server.MapPath("~/DataFile.txt")))
    {
        return sr.ReadToEnd();
    }
}

The web method reads an large text file (around 100KB) and returns it contents. Once we call this method from a page the network activity in the firebug shows like the following:

Plain

Now, lets examine the HttpModule which compress the Ajax Web Service response. The following shows the complete code of this module:

using System;
using System.IO;
using System.IO.Compression;
using System.Globalization;
using System.Web;


public class JsonCompressionModule : IHttpModule
{
    public JsonCompressionModule()
    {
    }

    public void Dispose()
    {
    }

    public void Init(HttpApplication app)
    {
        app.PreRequestHandlerExecute += new EventHandler(Compress);
    }

    private void Compress(object sender, EventArgs e)
    {
        HttpApplication app = (HttpApplication)sender;
        HttpRequest request = app.Request;
        HttpResponse response = app.Response;

        //Ajax Web Service request is always starts with application/json
        if (request.ContentType.ToLower(CultureInfo.InvariantCulture).StartsWith("application/json"))
        {
            //User may be using an older version of IE which does not support compression, so skip those
            if (!((request.Browser.IsBrowser("IE")) && (request.Browser.MajorVersion <= 6)))
            {
                string acceptEncoding = request.Headers["Accept-Encoding"];

                if (!string.IsNullOrEmpty(acceptEncoding))
                {
                    acceptEncoding = acceptEncoding.ToLower(CultureInfo.InvariantCulture);

                    if (acceptEncoding.Contains("gzip"))
                    {
                        response.Filter = new GZipStream(response.Filter, CompressionMode.Compress);
                        response.AddHeader("Content-encoding", "gzip");
                    }
                    else if (acceptEncoding.Contains("deflate"))
                    {
                        response.Filter = new DeflateStream(response.Filter, CompressionMode.Compress);
                        response.AddHeader("Content-encoding", "deflate");
                    }
                }
            }
        }
    }
}

Next, register this module in the web.config like the following:

<httpModules>
    <add name="ScriptModule" type="System.Web.Handlers.ScriptModule, System.Web.Extensions, Version=1.0.61025.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
    <add name="JsonCompressionModule" type="JsonCompressionModule"/>
</httpModules>

This time the network activity shows like the following:

Compressed Json

So by adding this little module, we have saved 74KB. Now consider the impact of this in an highly traffic ajax web application :-). You will find the complete source code in the bottom of this post. If you want to learn more optimization tips check out my previous post Implement Yahoo's YSlow in your Asp.net pages and Combine Multiple JavaScript and CSS Files and Remove Overheads.

Download: Full Source

kick it on DotNetKicks.com


Thursday, September 13, 2007 #

In my previous post, I have shown the data binding capabilities of ListView control. In this post, I will show the Data Editing power of ListView. Like the previous DataGrid, DataList and GridView controls the ListView control has the EditItemTemplate and it also comes up with the long awaited InsertItemTemplate, in the past there was a lot of attempt to allow the users to insert data through Header or Footer Template which is no longer required. In this post I will show you the full data editing capabilities of ListView which includes Insert, Edit and Delete with both LinqDataSource and code. We will follow the previous post example for the data editing.

The first thing you have to do to add the data editing capabilities is defining the Edit item and Insert item templates. Instead of row kind of style editing like the DataGrid and GridView we will follow the form style editing, this is the another powerful features of ListView that you have the full control on how the html is generated. The following shows the screen-shots of both EditItemTemplate and InsertItemTemplate view:

EditItemTemplate

InsertItemTemplate

The following shows the EditItem and InsertItem Template:

<EditItemTemplate>
    <tr>
        <td colspan="9">
            <table>
                <tbody>
                    <tr>
                        <th>
                            Company:
                        </th>
                        <td>
                            <asp:TextBox ID="CompanyNameTextBox" runat="server" Text='<% #Bind("CompanyName")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="CompanyValidator" runat="server" ControlToValidate="CompanyNameTextBox" ErrorMessage="<div>Company cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Contact:
                        </th>
                        <td>
                            <asp:TextBox ID="ContactNameTextBox" runat="server" Text='<% #Bind("ContactName")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="ContactValidator" runat="server" ControlToValidate="ContactNameTextBox" ErrorMessage="<div>Contact cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Title:
                        </th>
                        <td>
                            <asp:TextBox ID="ContactTitleTextBox" runat="server" Text='<% #Bind("ContactTitle")%>'></asp:TextBox>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Address:
                        </th>
                        <td>
                            <asp:TextBox ID="AddressTextBox" runat="server" TextMode="MultiLine" Columns="20" Rows="4" Text='<% #Bind("Address")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="AddressValidator" runat="server" ControlToValidate="AddressTextBox" ErrorMessage="<div>Address cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            City:
                        </th>
                        <td>
                            <asp:TextBox ID="CityTextBox" runat="server" Text='<% #Bind("City")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="CityValidator" runat="server" ControlToValidate="CityTextBox" ErrorMessage="<div>City cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Postal Code:
                        </th>
                        <td>
                            <asp:TextBox ID="PostalCodeTextBox" runat="server" Text='<% #Bind("PostalCode")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="PostalCodeValidator" runat="server" ControlToValidate="PostalCodeTextBox" ErrorMessage="<div>Postal code cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Country:
                        </th>
                        <td>
                            <asp:TextBox ID="CountryTextBox" runat="server" Text='<% #Bind("Country")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="CountryValidator" runat="server" ControlToValidate="CountryTextBox" ErrorMessage="<div>Country cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Phone:
                        </th>
                        <td>
                            <asp:TextBox ID="PhoneTextBox" runat="server" Text='<% #Bind("Phone")%>' ValidationGroup="edit"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="PhoneValidator" runat="server" ControlToValidate="PhoneTextBox" ErrorMessage="<div>Phone cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="edit"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th></th>
                        <td>
                            <asp:LinkButton ID="UpdateButton" CommandName="Save" runat="server" Text="Update" ValidationGroup="edit"></asp:LinkButton>
                            <asp:LinkButton ID="CancelButton" CommandName="Cancel" runat="server" Text="Cancel" CausesValidation="false"></asp:LinkButton>
                        </td>
                    </tr>
                </tbody>
            </table>
        </td>
    </tr>
</EditItemTemplate>
<InsertItemTemplate>
    <tr>
        <td colspan="9">
            <table>
                <tbody>
                    <tr>
                        <th>
                            ID:
                        </th>
                        <td>
                            <asp:TextBox ID="CustomerIDTextBox" runat="server" Text='<% #Bind("CustomerID")%>' ValidationGroup="add"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="IDValidator" runat="server" ControlToValidate="CustomerIDTextBox" ErrorMessage="<div>ID cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="add"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Company:
                        </th>
                        <td>
                            <asp:TextBox ID="CompanyNameTextBox" runat="server" Text='<% #Bind("CompanyName")%>' ValidationGroup="add"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="CompanyValidator" runat="server" ControlToValidate="CompanyNameTextBox" ErrorMessage="<div>Company cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="add"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Contact:
                        </th>
                        <td>
                            <asp:TextBox ID="ContactNameTextBox" runat="server" Text='<% #Bind("ContactName")%>' ValidationGroup="add"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="ContactValidator" runat="server" ControlToValidate="ContactNameTextBox" ErrorMessage="<div>Contact cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="add"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Title:
                        </th>
                        <td>
                            <asp:TextBox ID="ContactTitleTextBox" runat="server" Text='<% #Bind("ContactTitle")%>'></asp:TextBox>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Address:
                        </th>
                        <td>
                            <asp:TextBox ID="AddressTextBox" runat="server" Text='<% #Bind("Address")%>' TextMode="MultiLine" Columns="20" Rows="4" ValidationGroup="add"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="AddressValidator" runat="server" ControlToValidate="AddressTextBox" ErrorMessage="<div>Address cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="add"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            City:
                        </th>
                        <td>
                            <asp:TextBox ID="CityTextBox" runat="server" Text='<% #Bind("City")%>' ValidationGroup="add"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="CityValidator" runat="server" ControlToValidate="CityTextBox" ErrorMessage="<div>City cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="add"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Postal Code:
                        </th>
                        <td>
                            <asp:TextBox ID="PostalCodeTextBox" runat="server" Text='<% #Bind("PostalCode")%>' ValidationGroup="add"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="PostalCodeValidator" runat="server" ControlToValidate="PostalCodeTextBox" ErrorMessage="<div>Postal code cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="add"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Country:
                        </th>
                        <td>
                            <asp:TextBox ID="CountryTextBox" runat="server" Text='<% #Bind("Country")%>' ValidationGroup="add"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="CountryValidator" runat="server" ControlToValidate="CountryTextBox" ErrorMessage="<div>Country cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="add"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th>
                            Phone:
                        </th>
                        <td>
                            <asp:TextBox ID="PhoneTextBox" runat="server" Text='<% #Bind("Phone")%>' ValidationGroup="add"></asp:TextBox>
                            <asp:RequiredFieldValidator ID="PhoneValidator" runat="server" ControlToValidate="PhoneTextBox" ErrorMessage="<div>Phone cannot be blank.</div>" SetFocusOnError="true" Display="Dynamic" ValidationGroup="add"></asp:RequiredFieldValidator>
                        </td>
                    </tr>
                    <tr>
                        <th></th>
                        <td>
                            <asp:LinkButton ID="InsertButton" CommandName="Insert" runat="server" Text="Save" ValidationGroup="add"></asp:LinkButton>
                            <asp:LinkButton ID="CancelButton" CommandName="Cancel" runat="server" Text="Cancel" CausesValidation="false"></asp:LinkButton>
                        </td>
                    </tr>
                </tbody>
            </table>
        </td>
    </tr>
</InsertItemTemplate>

To edit existing record, you have to put a Button in the ItemTemplate/AlternatingItemTemplate which commandName is set to Edit, you can also use ImageButton/LinkButton instead of regular button as in this example I am using LinkButton. The same holds true for deleting record, in this case the commandName has to be Delete. Next, in the EditItemTemplate you have to add buttons which commandName sets to Update and Cancel.

To add record, you must have to set the InsertItemTemplate position, the supported values are FirstItem, LastItem and None. Since we are using form view for adding record it does not look appealing showing it all the time, instead we will put a button in the LayoutTemplate, upon click the form view will appear at the end. Next, in the InsertItemTemplate two new buttons are required which commandName sets to Insert and Cancel. At last, as we are showing this template on the new button click, we need to put few lines of code to show/hide the template as well as the new button. The following shows the code:

private void CloseInsert()
{
    lvwCustomers.InsertItemPosition = InsertItemPosition.None;
    ((LinkButton) lvwCustomers.FindControl("NewButton")).Visible = true;
}

protected void NewButton_Click(object sender, EventArgs e)
{
    lvwCustomers.EditIndex = -1;
    lvwCustomers.InsertItemPosition = InsertItemPosition.LastItem;
    ((LinkButton) sender).Visible = false;
}

protected void lvwCustomers_ItemEditing(object sender, ListViewEditEventArgs e)
{
    CloseInsert();
}

protected void lvwCustomers_ItemCanceling(object sender, ListViewCancelEventArgs e)
{
    if (e.CancelMode == ListViewCancelMode.CancelingInsert)
    {
        CloseInsert();
    }
}

protected void lvwCustomers_ItemInserted(object sender, ListViewInsertedEventArgs e)
{
    CloseInsert();
}

That's it we are all set to Add/Edit/Delete the records with LinqDataSource control.

Now we will see how to do the same with code. It is almost same as the LinqDataSource except few things, we need to hook the ItemCommand event and add few empty event handlers, the following shows the code:

protected void lvwCustomers_ItemCommand(object sender, ListViewCommandEventArgs e)
{
    switch(e.CommandName)
    {
        case "Insert":
        {
            InsertCustomer(e.Item);
            break;
        }
        case "Update":
        {
            UpdateCustomer(e.CommandArgument as string, e.Item);
            break;
        }
        case "Delete":
        {
            DeleteCustomer(e.CommandArgument as string);
            break;
        }
    }
}

protected void lvwCustomers_ItemInserting(object sender, ListViewInsertEventArgs e)
{
}

protected void lvwCustomers_ItemEditing(object sender, ListViewEditEventArgs e)
{
    CloseInsert();
    lvwCustomers.EditIndex = e.NewEditIndex;
    BindList();
}

protected void lvwCustomers_ItemUpdating(object sender, ListViewUpdateEventArgs e)
{
}

protected void lvwCustomers_ItemCanceling(object sender, ListViewCancelEventArgs e)
{
    if (e.CancelMode == ListViewCancelMode.CancelingInsert)
    {
        CloseInsert();
    }
    else
    {
        lvwCustomers.EditIndex = -1;
    }

    BindList();
}

protected void lvwCustomers_ItemDeleting(object sender, ListViewDeleteEventArgs e)
{
}

As you can see that in ItemCommand event we are checking the commandName and based upon that we are calling the helper function which does the actual db operation. The reason behind having those empty event handlers like ItemInserting, ItemUpdating and ItemDeleting is, if those left unhandled you will get a nasty exception that the event was not handled. While playing with the ListView data editing my first try was these events to do the db operation instead of the ItemCommand, but found that the event argument always left empty(except the itemIndex) as well as the EditItem and InsertItem property of ListView if the ListView is not bind with any DataSource control.

Now lets see what those db operation functions does, the following shows the code:

private void InsertCustomer(ListViewItem insertItem)
{
    if (IsValid)
    {
        using (NorthwindDataContext db = new NorthwindDataContext())
        {
            Customer customer = new Customer();

            customer.CustomerID = ((TextBox)insertItem.FindControl("CustomerIDTextBox")).Text;
            customer.CompanyName = ((TextBox)insertItem.FindControl("CompanyNameTextBox")).Text;
            customer.ContactName = ((TextBox)insertItem.FindControl("ContactNameTextBox")).Text;
            customer.ContactTitle = ((TextBox)insertItem.FindControl("ContactTitleTextBox")).Text;
            customer.Address = ((TextBox)insertItem.FindControl("AddressTextBox")).Text;
            customer.City = ((TextBox)insertItem.FindControl("CityTextBox")).Text;
            customer.PostalCode = ((TextBox)insertItem.FindControl("PostalCodeTextBox")).Text;
            customer.Country = ((TextBox)insertItem.FindControl("CountryTextBox")).Text;
            customer.Phone = ((TextBox)insertItem.FindControl("PhoneTextBox")).Text;

            db.Customers.Add(customer);

            db.SubmitChanges();
            CloseInsert();
            BindList();
        }
    }
}

private void UpdateCustomer(string customerID, ListViewItem editItem)
{
    if (IsValid)
    {
        using (NorthwindDataContext db = new NorthwindDataContext())
        {
            Customer customer = db.Customers.Where(c => c.CustomerID == customerID).Single();

            customer.CompanyName = ((TextBox) editItem.FindControl("CompanyNameTextBox")).Text;
            customer.ContactName = ((TextBox)editItem.FindControl("ContactNameTextBox")).Text;
            customer.ContactTitle = ((TextBox)editItem.FindControl("ContactTitleTextBox")).Text;
            customer.Address = ((TextBox)editItem.FindControl("AddressTextBox")).Text;
            customer.City = ((TextBox)editItem.FindControl("CityTextBox")).Text;
            customer.PostalCode = ((TextBox)editItem.FindControl("PostalCodeTextBox")).Text;
            customer.Country = ((TextBox)editItem.FindControl("CountryTextBox")).Text;
            customer.Phone = ((TextBox)editItem.FindControl("PhoneTextBox")).Text;

            db.SubmitChanges();
            lvwCustomers.EditIndex = -1;
            BindList();
        }
    }
}

private void DeleteCustomer(string customerID)
{
    using(NorthwindDataContext db = new NorthwindDataContext())
    {
        Customer customer = db.Customers.Where(c => c.CustomerID == customerID).Single();
        db.Customers.Remove(customer);
        db.SubmitChanges();
    }

    Pager dpCustomers = (Pager)lvwCustomers.FindControl("dpCustomers");
    dpCustomers.CurrentPageNo = 1;

    BindList();
}

That's it. You will find the complete source code of both of this example in the bottom of this post. In my next post, I will show the new GroupTemplate of ListView.

Download: Full Source

kick it on DotNetKicks.com


Sunday, September 09, 2007 #

In my previous post, I have presented few cool css examples of the new Asp.net ListView control. In this post, I will show how you can setup the data binding of the ListView control with the new LinqDataSource control as well as through code, which outputs the previous post examples.

Like its ancestor, the ListView comes up with few templates and some of them are really new. In order to bind data we first have to define the LayoutTemplate. The LayoutTemplate is a new template that acts as a container for either group or item templates. Since our previous example presents the data in tabular format, we will exclude the GroupTemplate from discussing in this post. One of the required thing you have to keep in mind while defining the LayoutTemplate is, you must have to declare an itemContainer/groupContainer where the item/group will be repeated. For example, I have declared the tbody as the itemContainer like the following:

<LayoutTemplate>
    <table>
        <thead>
            <tr>
                <th>Company</th>
                <th>Contact</th>
                <th>Title</th>
                <th>Address</th>
                <th>City</th>
                <th>Postal Code</th>
                <th>Country</th>
                <th>Phone</th>
            </tr>
        </thead>
        <tbody id="itemContainer" runat="server"></tbody>
        <tfoot>
            <tr>
                <th style="text-align:right" colspan="8">
                    <asp:DataPager runat="server" ID="dpCustomers" PageSize="10">
                        <Fields>
                            <asp:NumericPagerField ButtonCount="5"/>
                        </Fields>
                    </asp:DataPager>
                </th>
            </tr>
        </tfoot>
    </table>
</LayoutTemplate>

As you can see, the LayoutTemplate also contains another new control DataPager that I will discuss later. Another important thing is that unlike the Repeater control where usually the HeaderTemplate contains the starting tag and the FooterTemplate contains the ending tag of the container , the LayoutTemplate contains the both except the repeating part.

Next, we have to define the ItemTemplate and the AlteringItemTemplate like the following:

<ItemTemplate>
    <tr>
        <td>
            <asp:Label ID="lblCompany" runat="server" Text='<% #Eval("CompanyName")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblContact" runat="server" Text='<% #Eval("ContactName")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblTitle" runat="server" Text='<% #Eval("ContactTitle")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblAddress" runat="server" Text='<% #Eval("Address")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblCity" runat="server" Text='<% #Eval("City")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblPostalCode" runat="server" Text='<% #Eval("PostalCode")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblCountry" runat="server" Text='<% #Eval("Country")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblPhone" runat="server" Text='<% #Eval("Phone")%>'></asp:Label>
        </td>
    </tr>
</ItemTemplate>
<AlternatingItemTemplate>
    <tr class="odd">
        <td>
            <asp:Label ID="lblCompany" runat="server" Text='<% #Eval("CompanyName")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblContact" runat="server" Text='<% #Eval("ContactName")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblTitle" runat="server" Text='<% #Eval("ContactTitle")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblAddress" runat="server" Text='<% #Eval("Address")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblCity" runat="server" Text='<% #Eval("City")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblPostalCode" runat="server" Text='<% #Eval("PostalCode")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblCountry" runat="server" Text='<% #Eval("Country")%>'></asp:Label>
        </td>
        <td>
            <asp:Label ID="lblPhone" runat="server" Text='<% #Eval("Phone")%>'></asp:Label>
        </td>
    </tr>
</AlternatingItemTemplate>

At last, we will add a LinqDataSource control which will refer the Customers table of Northwind database. Certainly you have to add a LinqToSQLClasses previously and define the data model. The following shows the LinqDataSource:

<asp:LinqDataSource ID="linqCustomers" runat="server" ContextTypeName="NorthwindDataContext" TableName="Customers"/>

One of the benefits of using LinqDataSource over the other DataSource controls is it produces very optimized SQL without writing a single line of code. For example, the following SQL is generated when I moved to page two by clicking the DataPager.

exec sp_executesql N'SELECT TOP 10 [t1].[CustomerID], [t1].[CompanyName], [t1].[ContactName], [t1].[ContactTitle], [t1].[Address], [t1].[City], [t1].[Region], [t1].[PostalCode], [t1].[Country], [t1].[Phone], [t1].[Fax]
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]) AS [ROW_NUMBER], [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
    FROM [dbo].[Customers] AS [t0]
    ) AS [t1]
WHERE [t1].[ROW_NUMBER] > @p0',N'@p0 int',@p0=10

Like the other data controls, both the ListView and DataPager are tightly coupled with the DataSource controls.

So far we have seen how to bind data with the DataSource Control, now lets see how to bind data with code. The first issue you will face while binding pageable data that like the GridView built-in paging, there is no way you can utilize the DataPager control unless you use the DataSource controls. So you have to roll your own pager, in this example I have substitute the DataPager control with the server side version of my AjaxDataControls pager. Thus the LayoutTemplate becomes:

<LayoutTemplate>
    <table>
        <thead>
            <tr>
                <th>Company</th>
                <th>Contact</th>
                <th>Title</th>
                <th>Address</th>
                <th>City</th>
                <th>Postal Code</th>
                <th>Country</th>
                <th>Phone</th>
            </tr>
        </thead>
        <tbody id="itemContainer" runat="server"></tbody>
        <tfoot>
            <tr>
                <th colspan="8" style="text-align:right">
                    <cc1:Pager ID="pager" runat="server" OnPageChange="pager_PageChange" SliderSize="5" ShowFirstAndLast="false" ShowPreviousAndNext="true"/>
                </th>
            </tr>
        </tfoot>
    </table>
</LayoutTemplate>

The next issue you will face once you put the pager in the LayoutTemplate is, you will not be able to get the reference of the control by FindControl() until you bind the data of the ListView. So we have to first bind with a dummy data to get the reference, once done we will bind with the original data like the following:

private void BindList()
{
    using (NorthwindDataContext db = new NorthwindDataContext())
    {
        //Unless we bind the data we will not be able to refer the pager control
        //so bind it with a dummy data
        lvwCustomers.DataSource = new Customer[] { new Customer() };
        lvwCustomers.DataBind();

        Pager pager = (Pager)lvwCustomers.FindControl("pager");
        //Calculate the startIndex based upon the Current and RowPerPage
        int startIndex = (pager.CurrentPageNo - 1) * pager.RowPerPage;

        //Now bind it with the actual data.
        lvwCustomers.DataSource = db.Customers.Skip(startIndex).Take(pager.RowPerPage);
        lvwCustomers.DataBind();

        //We also need to know the total row in order to properly render the pager
        pager.TotalRow = db.Customers.Count();
    }
}

Next we have to handle the PageChange event of the pager to render the new page records. The following shows the code of this event:

protected void pager_PageChange(object sender, PageChangeEventArgs e)
{
    ((Pager)sender).CurrentPageNo = e.PageNo;
    BindList();
}

That's it. You will find the full source code of both these example in the bottom of this post. In my next post, I will show how we can leverage the data editing capabilities of ListView control.

Download: Full Source

kick it on DotNetKicks.com


Wednesday, September 05, 2007 #

My First experiment with Asp.net ListView control was awesome. I downloaded few templates from CSS Table Gallery and able to apply it only in few minutes. Checkout the result of Northwind Customer table:

Green

Grey

Groove

Groove

Simply Rocking!

I will be covering the other features of this cool control in my future posts. Stay tuned.

Download: Full Source

kick it on DotNetKicks.com


Friday, August 24, 2007 #

Modal Progress Dialog

If you ever like to show a Modal Progress dialog like the above for any ajax operation no matter which Update Panel or Web Service call is responsible for it, the following code will do the same.

<%@ Page Language="C#" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
    [System.Web.Services.WebMethod()]
    public static string GetDate()
    {
        //Doing a fake delay for 3 seconds
        System.Threading.Thread.Sleep(3000);

        return DateTime.Now.ToString();
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        if (!IsPostBack)
        {
            lblDate1.Text = lblDate2.Text = DateTime.Now.ToString();
        }
    }

    protected void btnDate1_Click(object sender, EventArgs e)
    {
        //Doing a fake delay for 3 seconds
        System.Threading.Thread.Sleep(3000);
        lblDate1.Text = DateTime.Now.ToString();
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Modal Dialog Progress</title>
    <style type="text/css">
        .modalBackground
        {
            background-color:#e6e6e6;
            filter:alpha(opacity=60);
            opacity:0.60;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <asp:ScriptManager ID="ScriptManager1" runat="server" EnablePageMethods="true">
        </asp:ScriptManager>
        <fieldset>
            <legend>UpdatePanel</legend>
            <div>
                <asp:UpdatePanel ID="UpdatePanel1" runat="server">
                    <ContentTemplate>
                        <asp:Label ID="lblDate1" runat="server"></asp:Label>
                        <asp:Button ID="btnDate1" runat="server" Text="Get Date" OnClick="btnDate1_Click" />
                    </ContentTemplate>
                </asp:UpdatePanel>
            </div>
        </fieldset>
        <fieldset>
            <legend>PageMethods</legend>
            <div>
                <asp:Label ID="lblDate2" runat="server"></asp:Label>
                <asp:Button ID="btnDate2" runat="server" Text="Get Date" OnClientClick="return btnDate2_Click()" />
            </div>
        </fieldset>
        <asp:Panel ID="pnlProgress" runat="server" style="background-color:#ffffff;display:none;width:400px">
            <div style="padding:8px">
                <table border="0" cellpadding="2" cellspacing="0" style="width:100%">
                    <tbody>
                        <tr>
                            <td style="width:50%"></td>
                            <td style="text-align:right">
                                <img alt="" src="indicator-big.gif" />
                            </td>
                            <td style="text-align:left;white-space:nowrap">
                                <span style="font-size:larger">Loading, Please wait ...</span>
                            </td>
                            <td style="width:50%"></td>
                        </tr>
                    </tbody>
                </table>
            </div>
        </asp:Panel>
        <ajaxToolKit:ModalPopupExtender ID="mpeProgress" runat="server" TargetControlID="pnlProgress" PopupControlID="pnlProgress" BackgroundCssClass="modalBackground" DropShadow="true" />
    </form>
    <script type="text/javascript">

        Sys.Net.WebRequestManager.add_invokingRequest(onInvoke);
        Sys.Net.WebRequestManager.add_completedRequest(onComplete);

        function onInvoke(sender, args)
        {
            $find('<%= mpeProgress.ClientID %>').show();
        }

        function onComplete(sender, args)
        {
            $find('<%= mpeProgress.ClientID %>').hide();
        }

        function btnDate2_Click()
        {
            PageMethods.GetDate (
                                    function(result)
                                    {
                                        var lbl = $get('<%= lblDate2.ClientID %>');

                                        if (document.all)
                                        {
                                            lbl.innerText = result;
                                        }
                                        else
                                        {
                                            lbl.textContent = result;
                                        }
                                    }
                                );

            return false;
        }

        function pageUnload()
        {
            Sys.Net.WebRequestManager.remove_invokingRequest(onInvoke);
            Sys.Net.WebRequestManager.remove_completedRequest(onComplete);
        }

    </script>
</body>
</html>

The main trick is hooking the two special events of Sys.Net.WebRequestManager class. This class is responsible for managing all kinds of communication with the server, even a manual invoke of a Sys.Net.WebRequest class will bring this class into action. It fires the invokingRequest before making an Ajax call and fires the completeRequest once the method completes. We are simply showing/hiding the modal popup extender in these events.

Download: Full Source

kick it on DotNetKicks.com


Friday, August 17, 2007 #

I recently did a review on Asp.net Ajax Update Panel of O'Reilly which is now published in DotNetSlackers.com. Check it out.


Monday, August 13, 2007 #

My latest article Asp.net Ajax Error Logging has been just published in DotNetSlackers.com. In this article, I have demonstrate how to extend Asp.net Ajax Framework to create an effective error logging system which support multiple trace listeners like Enterprise Library. A must read if you are developing or planning to develop an Asp.net Ajax Web Application. You will also find my name in the Ajax Columns in DotNetSlackers.com as I have planned to write at least one, preferably two articles each month. If you have any comment on my new article or you have any specific topic which you think I should cover, please do send me your feedback.

kick it on DotNetKicks.com


Saturday, August 11, 2007 #

In this post, I will show you how to load different user control in UpdatePanel from different menu item click. I have found a lot of request in Asp.net Ajax Forum and some of them are having misconception about this. Once you complete reading this post you will be able to load controls dynamically and learn how to employ different helper controls like UpdateProgress, ModalPopupExtender while the UserControl is loading. First let us create plain page without the ajax support:

Markup:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="PlainSampleMenu.aspx.cs" Inherits="PlainSampleMenuPage" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Plain Sample Menu</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:Menu ID="Menu1" runat="server" OnMenuItemClick="Menu1_MenuItemClick">
            <Items>
                <asp:MenuItem Text="File">
                    <asp:MenuItem Text="Load Control1"></asp:MenuItem>
                    <asp:MenuItem Text="Load Control2"></asp:MenuItem>
                    <asp:MenuItem Text="Load Control3"></asp:MenuItem>
                </asp:MenuItem>
            </Items>
        </asp:Menu>
        <br />
        <br />
        <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
    </form>
</body>
</html>

Code Behind:

using System;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class PlainSampleMenuPage : System.Web.UI.Page
{
    private const string BASE_PATH = "~/DynamicControlLoading/";

    private string LastLoadedControl
    {
        get
        {
            return ViewState["LastLoaded"] as string;
        }
        set
        {
            ViewState["LastLoaded"] = value;
        }
    }

    private void LoadUserControl()
    {
        string controlPath = LastLoadedControl;

        if (!string.IsNullOrEmpty(controlPath))
        {
            PlaceHolder1.Controls.Clear();
            UserControl uc = (UserControl)LoadControl(controlPath);
            PlaceHolder1.Controls.Add(uc);
        }
    }

    protected void Page_Load(object sender, EventArgs e)
    {
        LoadUserControl();
    }

    protected void Menu1_MenuItemClick(object sender, MenuEventArgs e)
    {
        MenuItem menu = e.Item;

        string controlPath = string.Empty;

        switch (menu.Text)
        {
            case "Load Control2":
                controlPath = BASE_PATH + "SampleControl2.ascx";
                break;
            case "Load Control3":
                controlPath = BASE_PATH + "SampleControl3.ascx";
                break;
            default:
                controlPath = BASE_PATH + "SampleControl1.ascx";
                break;
        }

        LastLoadedControl = controlPath;
        LoadUserControl();
    }
}

As you can see we are loading the UserControls in the PlaceHolder Control on different menu item click. If you are wondering why I am also loading the controls in Page Load event based upon the ViewState, the reason is otherwise those dynamically loaded controls will not be visible in the consequent postbacks. There are few good articles written by Scott Mitchell on dynamically loading Controls which you will find in the following links:

Now let us ajaxify this page, lets wrap the PlaceHolder with an UpdatePanel control. The following shows the markup:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SampleMenu1.aspx.cs" Inherits="SampleMenuPage1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Sample Menu</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:Menu ID="Menu1" runat="server" OnMenuItemClick="Menu1_MenuItemClick">
            <Items>
                <asp:MenuItem Text="File">
                    <asp:MenuItem Text="Load Control1"></asp:MenuItem>
                    <asp:MenuItem Text="Load Control2"></asp:MenuItem>
                    <asp:MenuItem Text="Load Control3"></asp:MenuItem>
                </asp:MenuItem>
            </Items>
        </asp:Menu>
        <br />
        <br />
        <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
        <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
            <ContentTemplate>
                <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="Menu1" />
            </Triggers>
        </asp:UpdatePanel>
    </form>
</body>
</html>

Since the Menu control resides outside the UpdatePanel we will also need a AsyncPostBackTrigger for the Menu Control. Now run this page, you will find the controls are loaded without a full postback.

Now let us further enhance this page with UpdateProgress and AjaxControlToolKit's ModalPopupExtender. First with the UpdateProgress Control.

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SampleMenu1.aspx.cs" Inherits="SampleMenuPage1" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Sample Menu - UpdateProgress</title>
</head>
<body>
    <form id="form1" runat="server">
        <asp:Menu ID="Menu1" runat="server" OnMenuItemClick="Menu1_MenuItemClick">
            <Items>
                <asp:MenuItem Text="File">
                    <asp:MenuItem Text="Load Control1"></asp:MenuItem>
                    <asp:MenuItem Text="Load Control2"></asp:MenuItem>
                    <asp:MenuItem Text="Load Control3"></asp:MenuItem>
                </asp:MenuItem>
            </Items>
        </asp:Menu>
        <br />
        <br />
        <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
        <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
            <ContentTemplate>
                <asp:UpdateProgress ID="UpdateProgress1" runat="server" AssociatedUpdatePanelID="UpdatePanel1">
                    <ProgressTemplate>
                        Loading....
                    </ProgressTemplate>
                </asp:UpdateProgress>
                <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="Menu1" />
            </Triggers>
        </asp:UpdatePanel>
    </form>
</body>
</html>

As you can see we have added a UpdateProgress Control in the UpdatePanel ContentTemplate. But the UpdateProgress does not get visible when you click any of the menu item. This is the true nature of UpdatePanel, it does not shows the UpdateProgress if the control is out side the UpdatePanel which caused the postback. We have to manually show the UpdateProgress by the JavaScript code like the following:

<script type="text/javascript">

    var prm = Sys.WebForms.PageRequestManager.getInstance();

    prm.add_initializeRequest(initializeRequest);
    prm.add_endRequest(endRequest);

    var _postBackElement;

    function initializeRequest(sender, e)
    {
        if (prm.get_isInAsyncPostBack())
        {
            e.set_cancel(true);
        }

        _postBackElement = e.get_postBackElement();

        if (_postBackElement.id.indexOf('Menu1') > -1)
        {
            $get('UpdateProgress1').style.display = 'block';
        }
    }

    function endRequest(sender, e)
    {
        if (_postBackElement.id.indexOf('Menu1') > -1)
        {
            $get('UpdateProgress1').style.display = 'none';
        }
    }
</script>

Now run the page, you will find the UpdateProgress Control is showing the loading message no matter which control caused the postback.

In this final section we will implement the AjaxControlToolKit's ModalPopupExtender to show the loading message, this will give you a similar look of CodePlex Site. The following shows the source:

<%@ Page Language="C#" AutoEventWireup="true" CodeFile="SampleMenu2.aspx.cs" Inherits="SampleMenuPage2" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
    <title>Sample Menu - ModalPopupExtender</title>
    <style type="text/css">
        .modalBackground
        {
            background-color:#dcdcdc;
            filter:alpha(opacity=60);
            opacity:0.60;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
        <asp:Menu ID="Menu1" runat="server" OnMenuItemClick="Menu1_MenuItemClick">
            <Items>
                <asp:MenuItem Text="File">
                    <asp:MenuItem Text="Load Control1"></asp:MenuItem>
                    <asp:MenuItem Text="Load Control2"></asp:MenuItem>
                    <asp:MenuItem Text="Load Control3"></asp:MenuItem>
                </asp:MenuItem>
            </Items>
        </asp:Menu>
        <br />
        <br />
        <asp:ScriptManager ID="ScriptManager1" runat="server"></asp:ScriptManager>
        <asp:UpdatePanel ID="UpdatePanel1" runat="server" UpdateMode="Conditional">
            <ContentTemplate>
                <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
            </ContentTemplate>
            <Triggers>
                <asp:AsyncPostBackTrigger ControlID="Menu1" />
            </Triggers>
        </asp:UpdatePanel>
        <asp:Panel ID="Panel1" runat="server" style="background-color:#ffffff;display:none;width:400px">
            <div style="padding:8px">
                <h2>Loading...</h2>
            </div>
        </asp:Panel>
        <ajaxToolKit:ModalPopupExtender ID="ModalPopupExtender1" runat="server" TargetControlID="Panel1" PopupControlID="Panel1" BackgroundCssClass="modalBackground" DropShadow="true" />
        <script type="text/javascript">

            var prm = Sys.WebForms.PageRequestManager.getInstance();

            prm.add_initializeRequest(initializeRequest);
            prm.add_endRequest(endRequest);

            var _postBackElement;

            function initializeRequest(sender, e)
            {
                if (prm.get_isInAsyncPostBack())
                {
                    e.set_cancel(true);
                }

                _postBackElement = e.get_postBackElement();

                if (_postBackElement.id.indexOf('Menu1') > -1)
                {
                    $find('ModalPopupExtender1').show();
                }
            }

            function endRequest(sender, e)
            {
                if (_postBackElement.id.indexOf('Menu1') > -1)
                {
                    $find('ModalPopupExtender1').hide();
                }
            }
        </script>
    </form>
</body>
</html>

As you can see we first created a custom style which will be shown as the background style when the modal popup is visible, next we have added a regular Asp.net Panel control which will be shown as the modal popup. We have initially set the Panel control display mode to none, so there will be no flicker when the modal popup is shown. At last, we have added a ModalPopupExtender and sets its required property. The previous rule also applies here, we have to manually show/hide the ModalPopup which we are doing in the above JavaScript code. You will also found few cool techniques of ModalPopupExtender in this link.

To run the sample first create a AjaxControlToolKit enable site website then create a folder DynamicControlLoading folder and copy the files in the following attachment.

Download: Complete Source

kick it on DotNetKicks.com