Tagged: ASP.Net

A broad look at Web Application Performance

So does your web application perform well? Is it responsive enough so that it increases the web user experience? It is a fact that user can relate only to what they see and what they click, that is the user interface (UI) and the experience with it (UX) largely depends on the performance of the overall
application. But what is the complaint that we usually hear, “this page is slow”, “this grid takes for ever to load”, “we click on that button and then we go for a coffee break”…and UI is always blamed first. We all know that it has to do with the perception a lot, but a services guy ignores it, database developer closes his eyes too, till someone proves where is the actual bottleneck.
Performance Tips
My cose friend and ex-colleague called me the other morning asking me help to figure out why the application was timing out, users are screaming and cursing this particular search page, it started timing out suddenly last week. They did all the usual tricks, iisreset, checking logs, checking db connections, even few server restarts, no avail. So this morning, we started looked thru the logs, stack traces and we could easily spot the culprit, a stored procedure in SQL Server, has gone rogue and slow. So in this case, we need to look closely at that proc, probably data has grown so much in one of the table that now a bad join has created performance issues.

I shared that experience, to point to fact that performance and thus performance tuning has to start at the very tail end (or head depends the way your look at the layered architecture :). So now lets take the layers, there is presentation layer, business layer, database access layer (persistence layer) and then there is the database itself.

Lets start with Database: What are the performance optimizations that you can think of, here is some starters.

  • Good logical design, helps in how db is physically stored, think thru your normalization levels depending on your usage, make sure you dont over-normalize, it will kill your joins and thus leads to bad performing queries.
  • Good key selection, have a good candidate for primary key, have proper foreign keys that are indexed (create non-clusterd index)
  • When writing queries, do not return all columns (i.e., no select * …)
  • Use SET NO COUNT ON, think about providing sql hints
  • There is 2 camps about using temp tables, some hate it, some love it, according to me temp table can be very useful when you are dealing with a large amount of tables to join. Create temp tables to divide and conquer. Always think about “number of rows” in the table when you make these decisions and potential growth of data. Check query plans, avoid table scans.
  • Use sp_executesql when you want to execute a dynamic sql
  • Reduce round trips to the server, try to do bulk operations.
  • This is just few I can think of now, Google database tuning and I am sure you can find a tons of good tuning tips from database gurus.

    Now how about Data Access Layer (DAL)? So what is DAL? DAL is nothing but a mechanism that provides us some CRUD capability along with some transactions, querying and concurrency control. In simpler systems, ADO.Net provides an database independent way to achieve this, So ADO.Net is our DAL layer. Or you might choose to absract the core features provided by ADO.Net into another library where you have tighter control and ability to switch databases at will, a DB Services Factory. If you are following a Domain Driven Design, then your OR/M plays the role of DAL.

    Lets see some ADO.Net related performance tips,

  • For connection pooling to work, always use the same connection string. By default pool size is 100, you can increase this (rarely you will need to)
  • Use db server IP rather than the server name in connection string to avoid a domain name resolution lookup. (This is a performance tip, but not a governance tip, for better governance, always use DNS name)
  • Always close the connections explicitly
  • Connect using a service account. Use windows authentication.
  • Use stored procedures, return only what you need.
  • Use some caching strategy to cache frequently used static data.
  • Again this is tip of the iceberg on what performance considerations you can use, for a more complete ADO.Net performance tuning tips, refer to “Improving .Net Application Performance and Scalability” from Microsoft patterns and practices.

    We will continue to explore performance from a OR/M layer perspective, then a Business Layer and finally UI layer in a future post.

    Until then Cheers and Happy Programming.

    Step by Step – GridView with extras

    This is a small tutorial on how to display your result in a gridview that supports the following:
    1) Sorting
    2) Paging
    3) Single/Multiple selection using a check box, Select All Checkbox in header
    4) An inline details view for each row (master-detail view)

    (1) and (2) comes by default if you use a ObjectDataSource for Gridview binding and provide a DataTable/DataView as datasource for that. If your business layer returns a generic list of objects, read this post for ideas how to convert
    it into DataTable/DataView for easy grid handling.

    Since this is a tutorial lets go step by step:

    Step 1:

    Lets setup datasource for our sample, For quick setup, I used customer and order data from a sample xml file. Also, I added a class called GridViewData, this class will provide data for objectdatasource to bind to the grid.

    Xml Data File:

    Customer 1—->* Orders

    <?xml version="1.0" encoding="utf-8"?>
    <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <xs:element name="customers">
        <xs:complexType>
          <xs:sequence>
            <xs:element maxOccurs="unbounded" name="customers">
              <xs:complexType>
                <xs:attribute name="CustomerID" type="xs:string" use="required" />
                <xs:attribute name="ContactName" type="xs:string" use="required" />
                <xs:attribute name="CompanyName" type="xs:string" use="required" />
              </xs:complexType>
            </xs:element>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:schema>
    
    <?xml version="1.0" encoding="utf-8" ?>
    <customers>
      <customers CustomerID="ALFKI"	ContactName="Maria Anders"	CompanyName="Alfreds Futterkiste" />
      <customers CustomerID="ANATR"	ContactName="Ana Trujillo"	CompanyName="Ana Trujillo Emparedados y helados" />
      <customers CustomerID="ANTON"	ContactName="Antonio Moreno"	CompanyName="Antonio Moreno Taquería"></customers>
      <customers CustomerID="AROUT"	ContactName="Thomas Hardy"	CompanyName="Around the Horn"></customers>
      <customers CustomerID="BERGS"	ContactName="Christina Berglund"	CompanyName="Berglunds snabbköp"></customers>
    
      <customers CustomerID="BLAUS"	ContactName="Hanna Moos"	CompanyName="Blauer See Delikatessen"></customers>
      <customers CustomerID="BLONP"	ContactName="Frédérique Citeaux"	CompanyName="Blondesddsl père et fils"></customers>
      <customers CustomerID="BOLID"	ContactName="Maria Anderss"	CompanyName="Alfreds Futterkiste 333"></customers>
      <customers CustomerID="BONAP"	ContactName="Martín Sommer"	CompanyName="Bólido Comidas preparadas"></customers>
      <customers CustomerID="BOTTM"	ContactName="Laurence Lebihan"	CompanyName="Bon app"></customers>
    </customers>
    
    
    <?xml version="1.0" encoding="utf-8"?>
    <xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
      <xs:element name="orders">
        <xs:complexType>
          <xs:sequence>
            <xs:element maxOccurs="unbounded" name="orders">
              <xs:complexType>
                <xs:attribute name="OrderID" type="xs:integer" use="required" />
                <xs:attribute name="CustomerID" type="xs:string" use="required" />
                <xs:attribute name="OrderDate" type="xs:string" use="required" />
              </xs:complexType>
            </xs:element>
          </xs:sequence>
        </xs:complexType>
      </xs:element>
    </xs:schema>
    
    <?xml version="1.0" encoding="utf-8" ?>
    <orders>
      <orders OrderID="10643"	CustomerID="ALFKI"	OrderDate="25-08-1997"></orders>
      <orders OrderID="10692"	CustomerID="ALFKI"	OrderDate="03-10-1997"></orders>
      <orders OrderID="10715"	CustomerID="BONAP"	OrderDate="23-10-1997"></orders>
      <orders OrderID="10730"	CustomerID="BONAP"	OrderDate="05-11-1997"></orders>
      <orders OrderID="10732"	CustomerID="BONAP"	OrderDate="06-11-1997"></orders>
    </orders>
    

    GridViewData Class:

    [DataObject(true)]
    public class GridViewData
    {
        private DataSet customers;
        private DataSet orders;
        public GridViewData()
    	{
            this.customers = HttpContext.Current.Session["Customers"] as DataSet;
            this.orders = HttpContext.Current.Session["Orders"] as DataSet;
            if (this.customers == null)
            {
                this.customers = new DataSet();
                this.customers.ReadXmlSchema(HttpContext.Current.Server.MapPath(@"App_Data\customer.xsd"));
                this.customers.ReadXml(HttpContext.Current.Server.MapPath(@"App_Data\customer.xml"));
                HttpContext.Current.Session["Customers"] = this.customers;
            }
            if (this.orders == null)
            {
                this.orders = new DataSet();
                this.orders.ReadXmlSchema(HttpContext.Current.Server.MapPath(@"App_Data\order.xsd"));
                this.orders.ReadXml(HttpContext.Current.Server.MapPath(@"App_Data\order.xml"));
                HttpContext.Current.Session["Orders"] = this.orders;
            }
    	}
        [DataObjectMethod(DataObjectMethodType.Select)]
        public DataView GetCustomers()
        {
            this.customers.Tables[0].DefaultView.Sort = "CustomerID";
            return (this.customers.Tables[0].DefaultView);
        }
    }
    

    Step 2:

    Add a new webform, GridView.aspx (or any name) to your project.
    Add a GridView control into the webform from the ToolBox.
    Add a ObjectDataSource control into the webform from the ToolBox
    Select ObjectDataSource and set its properties and configure it to use GridViewData Class. (If you select the control Visual Studio will popup a menu where you can select “Configure DataSource”, select that, in the wizard screen that follows, select your GridViewData as your datasource, click next and select your select method from drop down list and click on finish)
    Now click on grid, name it and select its datasource as your objectdatasource, use check box selections to enable Paging and Sorting..

    Your HTML markup will looks something like this:

    <body>
        <form id="form1" runat="server">
        <div>
        
            <asp:GridView ID="GridView1" runat="server" AllowPaging="True" 
                AllowSorting="True" DataSourceID="odsGrid">
            </asp:GridView>
        
        </div>
        <asp:ObjectDataSource ID="odsGrid" runat="server" 
            OldValuesParameterFormatString="original_{0}" SelectMethod="GetCustomers" 
            TypeName="GridViewData"></asp:ObjectDataSource>
        </form>
    </body>
    

    Run your project, gridview will display with data…

    Step 3:

    Now we can refine this gridview to add your pager, number of rows in a page, selection checkbox for each row, Header selection checkbox, etc..

    If you set the PageSize attribute to your gridview, it will automatically add a pager. Since our datasource is providing a DataView, objectDataSource now automatically give you paging and sorting.

           <asp:GridView ID="GridView1" runat="server" AllowPaging="True" 
                AllowSorting="True" PageSize="5"  DataSourceID="odsGrid">
            </asp:GridView>
    
    

    Now we change the GridView markup again to add Templated Columns, for checkbox,
    and bound columns for all the other. Also we set AutoGenerateColumns property of grid to false.

    Now Markup will look like this,

            <asp:GridView ID="GridView1" runat="server" AllowPaging="True" 
                AllowSorting="True" PageSize="5"  DataSourceID="odsGrid" 
                AutoGenerateColumns="False">
                
                <Columns>
                    <asp:TemplateField>
                        <HeaderTemplate>
                            <asp:CheckBox runat="server" ID="chkSelectHeader"  />
                        </HeaderTemplate>
                        <ItemTemplate>
                            <asp:CheckBox runat="server" ID="chkSelect" />
                        </ItemTemplate>
                    </asp:TemplateField>
                    <asp:BoundField HeaderText="Customer ID" DataField="CustomerID" SortExpression="CustomerID" />                                
                    <asp:BoundField HeaderText="Customer Name" DataField="ContactName" SortExpression="ContactName" />
                    <asp:BoundField HeaderText="Company" DataField="CompanyName" SortExpression="CompanyName" />
                </Columns>
            </asp:GridView>
    

    Run your project now, gridview will show checkbox for each row and also in header…
    Now we need to write some code to manage these selections…

    [image]

    Step 4:

    For handling the selections,etc. we will use these grid events and add handlers for it
    – RowCommand
    – RowCreated
    – RowDataBound

    (Select gridview control in design mode, switch to properties, click events and add handlers for these events)

    Add a new page level variable
    Dictionary checkedItems;
    and initialize it on Page_Load. This will be saved into Session for tracking the selections between postbacks on paging, sorting, etc.

    
        Dictionary<string, bool> checkedItems;
        protected void Page_Load(object sender, EventArgs e)
        {
            if (!IsPostBack)
            {
                checkedItems = new Dictionary<string, bool>();
                Session["CheckedItems"] = checkedItems;
            }
            else
            {
                checkedItems = (Dictionary<string, bool>)Session["CheckedItems"];
            }
        }
    
    

    Now we need to add code for all the events that we are handling,
    RowCommand is used to collect the selections and keep track of it…

        protected void gv_RowCommand(object sender, GridViewCommandEventArgs e)
        {
            GetCheckBoxValues((GridView)sender, "chkSelect", checkedItems);
        }
        protected void GetCheckBoxValues(GridView gv, string checkID, Dictionary<string, bool> chkItems)
        {
            Dictionary<string, bool> checkedItems = chkItems;
            foreach (GridViewRow gvr in gv.Rows)
            {
                CheckBox cb = (CheckBox)gvr.FindControl(checkID);
                if (cb != null)
                {
                    string id = gvr.Cells[1].Text; // uses Customer ID
                    if (cb.Checked)
                        checkedItems[id] = cb.Checked;
                    else
                    {
                        if (checkedItems != null)
                        {
                            if (checkedItems.ContainsKey(id))
                                checkedItems.Remove(id);
                        }
                    }
                }
            }
            //Save to session
            Session["CheckedItems"] = checkedItems;
        
        }
    

    With this code in, Run your project, select couple of items , page to next, you can see (if you debug), that Session[“CheckedItems”] varible holds the selections…

    GridView - Select All

    GridView - Select All

    This collects the selections when Paging happens, but we need to have these selections saved when each row selected too… we will use JavaScript and some AJAX calls to handle this. We will add the required javascript function handlers in gv_RowCreated event for this..

        protected void gv_RowCreated(object sender, GridViewRowEventArgs e)
        {
            GridRowCreated(sender, e, "chkSelectHeader", "chkSelect");
        }
        private void GridRowCreated(object sender, GridViewRowEventArgs e, string gridHeaderCheckName, string gridCheckName)
        {
            if (e.Row.RowType == DataControlRowType.Header)
            {
                CheckBox chkAll = (CheckBox)e.Row.FindControl(gridHeaderCheckName);
                if (chkAll != null)
                {
                    //For Header Checkbox click
                    chkAll.Attributes.Add("onclick", "SelectAllRows('" + gridHeaderCheckName + "')");
                }
            }
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                CheckBox chk = (CheckBox)e.Row.FindControl(gridCheckName);
                if (chk != null)
                {
                    //For Check Box Click
                    chk.Attributes.Add("onclick", "SelectRow('" + gridCheckName + "')");
                }
                //For Row Click
                e.Row.Attributes.Add("onclick", "SelectRow('" + gridCheckName + "')");
                e.Row.Attributes.Add("title", "Click to toggle the selection of this row");
    
    
            }
        }
    
    

    And we will add some Javascript code to handle the Row selection and Header Click for select…

       function SelectRow(gridCheckName)
        {
            var key = "";
            var obj = window.event.srcElement;
            if(obj.tagName=="INPUT") //this is a checkbox
            {
                key = obj.parentNode.parentNode.cells[1].innerText;
                if (obj.checked)
                {
                    obj.parentNode.parentNode.style.backgroundColor = "#ffffcc";
                }
                else
                {
                    obj.parentNode.parentNode.style.backgroundColor = "#ffffff";
                }
            }
            else if (obj.tagName=="TD") //this a table cell
            {
                //get a pointer to the tablerow
                var row = obj.parentNode;
                var chk = row.cells[0].firstChild;
                chk.checked = !chk.checked;
                key = row.cells[1].innerText;
                if (chk.checked)
                {
                   row.style.backgroundColor = "#ffffcc";
                }
                else
                {
                   row.style.backgroundColor = "#ffffff";
                }
            }
        }
        function SelectAllRows(gridHeaderName)
        {
           var chkAll = window.event.srcElement; 
           var tbl = chkAll.parentNode.parentNode.parentNode.parentNode;
           
           if (chkAll)
           {
                for(var i=1;i<=tbl.rows.length-1;i++)
                {
                    if (tbl.rows[i].className == "pager") break;
                    var chk = tbl.rows[i].cells[0].firstChild;
                    chk.checked=chkAll.checked;
                    if (chk.checked)
                        chk.parentNode.parentNode.style.backgroundColor = "#ffffcc";
                    else
                        chk.parentNode.parentNode.style.backgroundColor = "#ffffff";
                }
           }
        
        }
    

    We will use gv_RowDataBound event to handle some styling code and also we make the decision whether to show a [+] icon for details view…

        protected void gv_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            CheckBox cb = (CheckBox)e.Row.FindControl("chkSelect");
    
            if (cb != null)
            {
                string id = e.Row.Cells[1].Text; //key
                if (checkedItems != null)
                {
                    if (checkedItems.ContainsKey(id))
                    {
                        cb.Checked = true;
                        e.Row.BackColor = System.Drawing.Color.FromArgb(255, 255, 204);
                    }
                }
            }
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                //decide whether to show [+] icon for details
            }
    
        }
    
    

    Run project now, and you will see that selections are now highlighted…
    Still we save the selections when we page…

    [image]

    Now let us add a webservice named GridHelper.asmx. This service will enable us to make AJAX calls and
    update selections, get details for master-detail view, etc.

    This web service has two web methods, one to handle single row selection and other to handle select/deselect all using header checkbox.

    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Web.Services;
    using System.Web.Services.Protocols;
    using System.Xml.Linq;
    using System.Data;
    
    [WebService(Namespace = "http://tempuri.org/")]
    [WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
    public class GridViewHelper : System.Web.Services.WebService {
    
        public GridViewHelper () {
        }
        [WebMethod(EnableSession = true)]
        public string HandleSingleSelection(string key,string action) {
            Dictionary<string, bool> checkedItems = null;
            if (Session["CheckedItems"] == null)
                checkedItems = new Dictionary<string, bool>();
            else
                checkedItems = (Dictionary<string, bool>)Session["CheckedItems"];
    
            if (action == "add")
            {
                if (checkedItems.ContainsKey(key) == false)
                    checkedItems[key] = true;
            }
            else
            {
                if (checkedItems.ContainsKey(key) == true)
                    checkedItems.Remove(key);
            }
            Session["CheckedItems"] = DDACheckedItems;
        }
        [WebMethod(EnableSession = true)]
        public string HandleHeaderSelection(bool selectAll)
        {
           Session["CheckedItems"] = SetSelections((DataSet)Session["Customers"],selectAll);
        }
        private Dictionary<string, bool> SetSelections(DataSet ds, bool selectAll)
        {
            Dictionary<string, bool> checkedItems = new Dictionary<string, bool>();
            if (selectAll == false) return (checkedItems);
            foreach (DataRow dr in ds.Tables[0].Rows)
            {
                checkedItems[dr["CustomerID"].ToString()] = selectAll;
            }
            return (checkedItems);
        }
    }
    
    

    Now lets go back to our javascript code and add calls to this web service methods.

        function SelectRow(gridCheckName)
        {
            var key = "";
            var obj = window.event.srcElement;
            if(obj.tagName=="INPUT") //this is a checkbox
            {
                key = obj.parentNode.parentNode.cells[1].innerText;
                if (obj.checked)
                {
                    obj.parentNode.parentNode.style.backgroundColor = "#ffffcc";
                    GridViewHelper.HandleSingleSelection(key,"add",HandleSingleSelectionOnSuccess,AjaxCallOnError);
                }
                else
                {
                    obj.parentNode.parentNode.style.backgroundColor = "#ffffff";
                    GridViewHelper.HandleSingleSelection(key,"remove",HandleSingleSelectionOnSuccess,AjaxCallOnError);
                }
            }
            else if (obj.tagName=="TD") //this a table cell
            {
                //get a pointer to the tablerow
                var row = obj.parentNode;
                var chk = row.cells[0].firstChild;
                chk.checked = !chk.checked;
                key = row.cells[1].innerText;
                if (chk.checked)
                {
                   row.style.backgroundColor = "#ffffcc";
                }
                else
                {
                   row.style.backgroundColor = "#ffffff";
                }
            }
        }
        function HandleSingleSelectionOnSuccess(result)
        {    
        }
        function HandleHeaderSelectionOnSuccess(result)
        {    
        }
        function AjaxCallOnError(result)
        {
         alert ("Oops!! Something went wrong!");
        }
        function SelectAllRows(gridHeaderName)
        {
           var chkAll = window.event.srcElement; 
           var tbl = chkAll.parentNode.parentNode.parentNode.parentNode;
           
           if (chkAll)
           {
                for(var i=1;i<=tbl.rows.length-1;i++)
                {
                    if (tbl.rows[i].className == "pager") break;
                    var chk = tbl.rows[i].cells[0].firstChild;
                    chk.checked=chkAll.checked;
                    if (chk.checked)
                        chk.parentNode.parentNode.style.backgroundColor = "#ffffcc";
                    else
                        chk.parentNode.parentNode.style.backgroundColor = "#ffffff";
                }
                GridViewHelper.HandleHeaderSelection(chk.checked,HandleHeaderSelectionOnSuccess,AjaxCallOnError); 
           }
            
        }
    

    With this change, if you run your project now, we will see that selections are working the way we wanted…
    [image]

    Step 5:

    Now for the last piece, master-detail view, if customer has at least an order, we want to place a [+] icon
    after row selector check box. For this we will first add an empty template column to our aspx markup…

                <Columns>
                    <asp:TemplateField>
                        <HeaderTemplate>
                            <asp:CheckBox runat="server" ID="chkSelectHeader"  />
                        </HeaderTemplate>
                        <ItemTemplate>
                            <asp:CheckBox runat="server" ID="chkSelect" />
                        </ItemTemplate>
                    </asp:TemplateField>
                    <asp:TemplateField>
                        <ItemTemplate>
                        </ItemTemplate> 
                    </asp:TemplateField>                 
                    <asp:BoundField HeaderText="Customer ID" DataField="CustomerID" SortExpression="CustomerID" />                                
                    <asp:BoundField HeaderText="Customer Name" DataField="ContactName" SortExpression="ContactName" />
                    <asp:BoundField HeaderText="Company" DataField="CompanyName" SortExpression="CompanyName" />
                </Columns>
    
    

    We use the DataBound event to check whether there is an order present for each customer and add icons accordingly

        protected void gv_RowDataBound(object sender, GridViewRowEventArgs e)
        {
            CheckBox cb = (CheckBox)e.Row.FindControl("chkSelect");
            string id = "";
            if (cb != null)
            {
                id = e.Row.Cells[2].Text; //key
                if (checkedItems != null)
                {
                    if (checkedItems.ContainsKey(id))
                    {
                        cb.Checked = true;
                        e.Row.BackColor = System.Drawing.Color.FromArgb(255, 255, 204);
                    }
                }
            }
            if (e.Row.RowType == DataControlRowType.DataRow)
            {
                //decide whether to show [+] icon for details
                if (DetailExist(id))
                {
                    e.Row.Cells[1].Text = "<img id=\"imgDetailShow\" src=\"Images/plus.png\" alt=\"View\" onclick=\"ShowDetail(this, '" + id + "');\" />";
                }
            }
        }
        private bool DetailExist(string id)
        {
            if (Session["Orders"] == null) return (false);
            DataSet ds = (DataSet)Session["Orders"];
            DataTable dt = ds.Tables[0];
            var query
                    = from orders in dt.AsEnumerable()
                      where orders.Field<String>("CustomerID").ToLower().Contains(id.ToLower())
                      select orders;
            if (query.ToList().Count > 0 ) return (true);
            return (false);
        }
    
    

    Now Add ShowDetail() Javascript function shell and run the project.
    You can see that only for the records where there is a detail record present, [+] icon is presented.
    Clicking on this [+] icon calls the ShowDetail Javascript function. We will use another AJAX call to get the order details..

    GridView

    GridView

    Step 6:

    This new web method returns a JSON string representing orders for the current customer.

        [WebMethod(EnableSession = true)]
        public string GetOrderDetails(string id)
        {
            try
            {
                if (Session["Orders"] == null) return ("[]");
                DataSet ds = (DataSet)Session["Orders"];
                DataTable dt = ds.Tables[0];
                var query
                        = from orders in dt.AsEnumerable()
                          where orders.Field<String>("CustomerID").ToLower() == id.ToLower()
                          select new Order
                          {
                              OrderId = orders.Field<Int64>("OrderID"),
                              OrderDate = orders.Field<String>("OrderDate")
                          };
    
                List<Order> orderList = query.ToList(); 
    
                DataContractJsonSerializer ser = new DataContractJsonSerializer(orderList.GetType());
                MemoryStream ms = new MemoryStream();
                ser.WriteObject(ms, orderList);
                string strJSON = Encoding.Default.GetString(ms.ToArray());
                return strJSON;
            }
            catch (Exception ex)
            {
                System.Diagnostics.Debug.Write(ex.Message);
                throw;
            }
        }
    

    Once we have the JSON string representing the order detial in client layer, we can inject HTML snippet into the grid table to show the details.

        function ShowDetail(e,id)
        {
            GridViewHelper.GetOrderDetails(id,GetOrderDetailsOnSuccess,AjaxCallOnError,e);
        }
        function GetOrderDetailsOnSuccess(result,e)
        {
            var obj = Sys.Serialization.JavaScriptSerializer.deserialize(result, true);
            if (obj.length == 0) return;
                    //e represents the element that was clicked
            var row, index, table, clickedCell;
            var newRow,newCell,cellHTML;
            if (e.nameProp == "plus.png")
            {
    
                row = e.parentNode.parentNode;
                index = row.rowIndex;
                table = row.parentNode.parentNode;
                newRow = table.insertRow(index+1);
                newCell = newRow.insertCell(0);
                newCell.colSpan = 4;
                newCell.style.paddingLeft = "30px";
                
                newCell.appendChild(getResultTable(obj));
                
                clickedCell = e.parentNode;
                cellHTML = clickedCell.innerHTML;
                cellHTML = cellHTML.replace(/plus/, "minus");
                clickedCell.innerHTML = cellHTML;
    
            }
            else
            {
                row = e.parentNode.parentNode;
                index = row.rowIndex;
                table = row.parentNode.parentNode;
                table.deleteRow(index+1);
                clickedCell = e.parentNode;
                cellHTML = clickedCell.innerHTML;
                cellHTML = cellHTML.replace(/minus/, "plus");
                clickedCell.innerHTML = cellHTML;
            }
    
    
        }
        function getResultTable(obj)
        {
               var tbl = document.createElement("Table");
               tbl.setAttribute("width", "100%");
    
               for(i=0;i<obj.length;i++)
               {
                    var  row = tbl.insertRow(i);
                    var  cell = row.insertCell(0);      
                    if (obj[i].OrderId.toString() == "")
                        cell.innerHTML = "&nbsp;";
                    else
                        cell.innerHTML = obj[i].OrderId.toString();
                    cell = row.insertCell(1);      
                    if (obj[i].OrderDate.toString() == "")
                        cell.innerHTML = "&nbsp;";
                    else
                        cell.innerHTML = obj[i].OrderDate.toString();
                }
                return (tbl);        
        }
    

    With this change, we have now created a gridview with lots of nice features. Now by applying some nice
    CSS classes for styling this grid can look great!

    Run the project, you can now click on [+] to expand and view the details and [-] collapses the view.

    GridView - Final

    GridView - Final

    Hope you guys enjoyed this tutorial. Send me a note using comments if you want sample project files in a zip.
    [Update: Get Source Code from here:]

    Disclaimer: No guarantee that sample code provided will work, use at your own risk!

    Comments welcome!

    Cheers!

    Good collection of Visual Studio/ASP.net/IIS tips

    A Cold Case Solved !

    We have several web user controls in one of our sites. These controls are all placed in a \Controls sub directory. Many controls are created runtime and added to placeholders in the page. When we build this particular site (currently uses ASP.Net 3.5) we used to get this
    error message…
    The type or namespace name ‘Controls_XXXXX’ could not be found
    (are you missing a using directive or an assembly reference?)

    And design time Visual Studio was somehow not aware of these control’s classes!

    Today I stumbled upon this MSDN link, addresses this very issue.

    http://msdn.microsoft.com/en-us/library/c0az2h86.aspx

    Essentialy what it says is to add class name to your controls ascx file’s directive:

    <%@ Control Language="C#" AutoEventWireup="true" CodeFile="PickList.ascx.cs" Inherits="Controls_PickList" ClassName="Controls_PickList" %>
    

    And use Reference directive in your page where you use these control…

    <%@ Register src="Controls/PickList.ascx" tagname="PickList" tagprefix="uc1" %>
    

    Even if you are programmatically using these controls…

    Cold case solved!!
    Phew!!!

    Which JSON Serializer to use?

    This is an easy one, but just want to make a note for easy reference. JSON is more ubiquitous than ever these days in ASP.Net web development. From AJAX enables web sites to MVC AJAX, JSON is “the” data-interchange format we want to use.

    There are 2 possible class libraries that you can use in ASP.net to serialize objects to JSON and back. If you are using WCF and your custom objects are marked as [Data Contratcs], then DataContractJsonSerializer (System.Runtime.Serialization.Json) is the class you want to use. But if you are using an object that you cannot mark as a DataContract (may be part of a third-party component), then you want to use JavaScriptSerializer (System.Web.Script.Serialization).

    Here is some code snippets that shows both approaches:

    /* Using JavaScriptSerializer */
    //Serialize		
    String s;
    JavaScriptSerializer ser = new JavaScriptSerializer();
    ser.MaxJsonLength = Int32.MaxValue;
    s = ser.Serialize(obj);
    
    //De-Serialize
    JavaScriptSerializer ser = new JavaScriptSerializer();
    ser.MaxJsonLength = Int32.MaxValue;
    obj = ser.Deserialize(json);
    return (obj);
    
    
    
    /* Using DataContractJsonSerializer */
    
    //Serialize
    DataContractJsonSerializer ser = new DataContractJsonSerializer(obj.GetType());
    MemoryStream ms = new MemoryStream();
    ser.WriteObject(ms, obj);
    string retVal = Encoding.Default.GetString(ms.ToArray());
    
    //De-Serialize
    T obj = Activator.CreateInstance();
    MemoryStream ms = new MemoryStream(Encoding.Unicode.GetBytes(json));
    DataContractJsonSerializer ser = new DataContractJsonSerializer(obj.GetType());
    obj = (T)ser.ReadObject(ms);
    ms.Close();
    

    Happy programming!

    New Resource Kit from MS: Kobe

    Microsoft released Kobe (not Kobe beef) : Web 2.0 Service development Resource Kit. MSDN site says:

    Project Kobe is a getting started resource kit for planning, architecting, and implementing Web 2.0 applications and services using the Microsoft Platform. This resource kit is targeted toward technology decision makers, hands-on solution architects, development managers, and developers in the aspiring web startups community and in enterprises and businesses looking to invest in new Web 2.0 application/service development projects.

    I am planning to download and play with it soon, will post what I find later.

    Until then….
    Cheers

    A LINQ Thought

    LINQ stands for Language Integrated Query. I want to stress on “Integrated” because I think the strength of LINQ comes from the fact that it is a data query engine that is integrated with .Net languages. This very reason gives us many features that we as developers are comfortable
    using in a language into our data manipulation operations.

    Lets take a simple example,

    var query
    = from customer in CustomerTable.AsEnumerable()
    where customer.Field("Name").ToLower().Contains("John")
    select customer;
    

    In the above example, we are using simple string functions that are part of C# string class to do our comparisons for our data fetch.
    Lets take another example, where we will use a C# function to evaluate a sub-query/expression,

    var query
    = from customer in CustomerTable.AsEnumerable()
    where
    customer.Field("Name").ToLower().Contains("John")
    select new Customer 
    {
    Name = customer.Field("Name"),
    State = customer.Field("State"),
    CustomerType = GetCustomerType(customer.Field("State"))
    };
    

    In the above example, GetCustomerType() function is used inline with a LINQ query to evaluate some business rule.
    In short, with great flexibility like this, LINQ has made life of developers much easier!

    Cheers!

    GridView Sorting Trick when using Object Datasource with custom objects

    When you are using Object DataSource in ASP.Net for GridView, if your select method is returning an object that is of type DataView, DataTable or DataSet, you will be able to get sorting automatically.
    But if you are like me and your architecture has a distinct DTO (Entity) layer, and you pass back and forth custom objects or generic list of these custom objects, then you are in less luck with GridView. If you
    are binding GridView with Object Datasource that returns a List, you will need to implement your own custom code to do sorting.

    If you google this issue, you will find different implementation that will point you to add a OnSorting event handler and more than one way to implement a custom sorting.

    What my thinking is, since DataTable and DataView provides us the automatic sorting that we need, then why don’t we create
    a DataTable from the List we have. We can do this elegantly using generics and reflection. Once we create a DataTable, simply pass back a DataView based on this table as return parameter from your Select Method of Object Datasource.

    Here is the code for converting a List to a DataTable, (CustomObject is my custom object defined in my DTO layer)

    using System;
    using System.Data;
    using System.Collections.Generic;
    using System.Reflection;
    public class Utilities
    {
    
    	public static DataTable ToDataTable(List lst) where T : CustomObject
            {
                PropertyInfo[] propertyInfos;
    
                System.Data.DataTable table = new DataTable("GridViewTable");
                DataColumn column=null;
                DataRow row=null;
    
                foreach (CustomObject v in lst)
                {
                    propertyInfos = v.GetType().GetProperties();
                    foreach (PropertyInfo propertyInfo in propertyInfos)
                    {
                        column = new DataColumn();
                        column.DataType = System.Type.GetType(propertyInfo.PropertyType.FullName);
                        column.ColumnName = propertyInfo.Name;
                        table.Columns.Add(column);
                    }
                    //New table made, break the loop
                    break;
                }
                //Now populate the table with values
                foreach (CustomObject v in lst)
                {
                     propertyInfos = v.GetType().GetProperties();
                     row = table.NewRow();
                     foreach (PropertyInfo propertyInfo in propertyInfos)
                         row[propertyInfo.Name] = propertyInfo.GetValue(v, null);
                         
                     table.Rows.Add(row);
                }
                return (table);
            }
    
    }
    

    Cheers

    Ajax and ASP.Net EventValidation

    By default in an ASP.Net webform, Event validation is turned ON. This is a security feature aimed at preventing injection attacks. But consider this scenario, your asp.net page is rendered with a drop down list with certain values, depending on some user selections, you did an AJAX call and modified the contents of this drop down list, when you do a postback on this page, asp.net will throw this error:

    Invalid postback or callback argument. Event validation is enabled using
    in configuration or in a page. For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them. If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation
    method in order to register the postback or callback data for validation.

    The reason for this is that the data that was postback is now different from the original data that was rendered by ASP.Net. I have noticed that in couple of instances you will receive this error, one is selecting any new value from the drop down list and doing a postback on dropdown list, another scenario is if you do a __doPostBack(”,”) call from your javascript.

    Workaround is get around this message is to turn OFF the event validation. This can be done by either setting the page directive
    enableeventvalidation=”false” (for controlling it in a page level) or change web.config to add to control it for the entire site.

    Cheers!

    Ajax Control Toolkit FAQ

    Other day someone asked me why do we get many folders along with AjaxControlToolkit dll when we do site build. Simple answer is these are resource folders for the toolkit that enables it to support localization. If you do not wish to use them, you could simply delete all these folders and just keep the main dll in the root level. If you want to read a little bit more about this, go here.

    Here is a link to a great Ajax Control Toolkit FAQ.

    Cheers!