RowCommand GridView took 6,7 seconds - asp.net

I placed a GridView inside an update panel.
<asp:UpdatePanel ID="UpdatePanel4" UpdateMode="Conditional" ChildrenAsTriggers="true"
runat="server">
<asp:GridView ID="GridView_Overview" OnRowCommand="GridView_Layout_RowCommand" />
</asp:GridView>
</asp:UpdatePanel>
When the user press a button, the gridView will be filled up with a datatable:
GridView_Overview.DataSource = dataTable;
GridView_Overview.DataBind();
The dataTable contains more than 10000 records. Therefore, the binding process to the gridview took about 3,4 seconds.
When a row is selected in the gridview:
protected void GridView_Layout_RowCommand(object sender, GridViewCommandEventArgs e)
{if (e.CommandName.Equals("Select"))
{
int index = Convert.ToInt32(e.CommandArgument);
GridViewRow row = this.GridView_Overview.Rows[index];
Int64 pID = Int64.Parse(((Label)row.FindControl("ID")).Text); // abc
}
}
It took 5,6 seconds to perform GridView_Layout_RowCommand as above. What is the issue here? How can I improve the performance of selecting a row. If I discard the //abc code line then it is fast but then I can not get the ID value for further process.
Thanks in advance.

This problem is occurring because the asynchronous postback from the UpdatePanel will be triggered only after walking through the entire DOM. If you're adding over 10000 records to the page then your DOM is going to be huge and the delay will be significant.
The key to solving your problem is destroying the GridView DOM elements before the postback is triggered. This way, there will be much less DOM to be traversed.
See this blog post for some tips on handling the situation: Slow performance of a GridView inside an UpdatePanel
At the very least, including even the most basic form of paging will improve client-side performance as it will reduce the number of DOM elements added to the page. For maximum results, you'll want to have a solution that also only selects each page of data so that you don't need to return 10000 records but only display a subset.

Alison's answer mentions this, and I want to reinforce the point: you really should not display 10,000 records at a single time. Even with minimal HTML (which the GridView won't generate, because it's creating long server-side IDs), you've created a huge page for your users to download.
You've also created a lot of work for your server -- work that may be (is?) entirely unnecessary. How many of your users do you expect to go through all 10,000 records at once? That's asking a lot, even for extremely dedicated people.
The best way to increase performance and make your page more usable is to add paging. It's a little complicated, but paging will reduce the size of the data your server has to transfer, and significantly reduce the amount of time spent by the browser on the AJAX call and rendering the HTML.
MSDN has an overview up here. Essentially, when the page changes, you'll want to do something like this (note: not exact code):
byte pageSize;
protected override void OnInit(EventArgs e)
{
pageSize = 50;
}
void GridView_Overview_PageIndexChanging(Object sender, GridViewPageEventArgs e)
{
GridView_Overview.DataSource = dataTable.Skip(pageSize * e.NewPageIndex).Take(pageSize);
GridView_Overview.DataBind();
}
You'll have to include a using System.Data.Linq; directive at the top of your code-behind page. You may want to let the user select the number of items per page, in which case you'd get pageSize from a control on the page, instead of setting it in OnInit as I did.

Check what the client is sending back to the server using the development tools of the browser. What you should check specifically is the size of the post when clicking.
If my suspicions are right (can't imaging they've fixed it since I last tried and scrapped it) it's posting the entire viewstate back to the server and returning it again to keep the control state server-side intact which in your case with 10,000 row will be quite substantial.
The only way to limit the effect of this behaviour is to add paging and get the pages from the data source each time the user is shifting grid page i.e. don't bind the entire data set to the grid at once.

Related

Recommendations for reducing the ViewState size on a legacy system

I've looked up ways to reduce the ViewState:
Viewstate Optimisations
Strategies for reducing ViewState size in asp.net
However, due to the situation I'm in, I need the quickest and most effective ways to reduce the ViewState size. The legacy system I'm working on is bloated and routinely has a ViewState that's 800Kb+ on multiple postbacks.
For example, I'm pretty sure populating drop down lists with 100+ items on multiple post backs is one of the culprits, correct?
Any suggestions/advice would be appreciated.
Edit 1
Disabling the ViewState entirely doesn't appear feasible. It breaks all of the controls, of which there are many, rendering the pages unusable. If this is the best approach, how should I go about handling all the broken controls?
If you have drop-down lists with many items on them, and if the content of these dropdown lists can be retrieved easily on page postbacks, then this content should NOT be put into viewstate. Instead, the dropdown list should be re-populated on the server on each postback. The classic example of this is a dropdown list containing the list of 50 states. It make NO sense to put this content into viewstate. This data can be cached on the server and used to repopulate the dropdown list on each postback, instead of passing this data back and forth to the client on each round trip.
So how do you bind content to a dropdown list without having it added to viewstate, and without turning off viewstate for the control? The answer lies in an understanding of the ASP.Net eventing pipeline. Viewstate tracking beings when the TrackViewState() method is called after the OnInit page event. Any changes to programmatically make to a control after TrackViewState() has executed gets put into viewstate. So if you are databinding your dropdown list in the Page_Load event, the entire content of the dropdown list will get put into viewstate, which you often do not want.
Thus if you do not want your dropdown list content to be serialized into viewstate, you must databind BEFORE the TrackViewState() method is executed. The best place to do this is in the Init event for the dropdown list. So to make a long story short, populate your dropdown list in its Init, and the drop-down list content will NOT be serialized into viewstate. Of course, since the content is not in viewstate, you will need to re-populate it on every postback. However, if the content is cached and is cheap to retrieve (as is the case, for example, with the list of 50 states) then this is not a problem.
Example: Say you have a dropdown list named "dropDownList1", and you can retrieve a List containing the content for the list in a method called GetData(). You can populate this list in the page_load event:
protected void Page_Load(object sender, EventArgs e)
{
//Content of dropdown list will be serialized into viewstate
dropDownList1.DataSource = GetData();
Page.DataBind();
}
but if you do this, the content will be serialized into viewstate. If you populate the list in its Init event:
protected void dropDownList1_Init(object sender, EventArgs e)
{
//Content of dropdown list will NOT be serialized into viewstate
GetData().ForEach(item => this.dropDownList1.Items.Add(item));
}
the content will NOT be serialized into viewstate. As long as it is cheap to retrieve this content, you should do it this way.
For more information on this, see this excellent article on Infinities Loop. This is the best article I have ever seen on viewstate, and by understanding the content of this article we were able to start using viewstate more intelligently on our web pages and dramatically reduce our viewstate size.
You might want to look at compressing the view state as described on this code project page.
Note: I have not tried this but it seems like it should work.
Quickest and most effective way would be to turn off view state entirely:
<system.web>
<pages enableViewState="false" />
You can also turn it off for individual pages:
private void Page_Init(object sender, System.EventArgs e)
{
    this.EnableViewState = false;
}
Depending on how you are storing your sessions(Database stored preferably), the amount of traffic the page gets, and the amount of memory your server has (if you are using InProc sessions), you could store the viewstate in the session. It's quite easy to do, downside is there are pitfalls and it won't work for all sites.
Here is a good article outlining the basics of the process and what some of the downsides/pitfalls can be:
http://www.hanselman.com/blog/MovingViewStateToTheSessionObjectAndMoreWrongheadedness.aspx

GridView rows jumping around while editing data

I've got a simple intranet form (ASP.NET 2.0) with a GridView bound to data from Active Directory. It does this with an SqlDataSource that selects from a view that pulls the data from AD. This works better than doing LDAP queries directly from ASP.NET, since I can do table joins, etc. without much fuss. The GridView has a CommandField column with an edit button, and a few EditItemTemplates with DropDownLists for selecting valid values.
Updates, on the other hand, are done by connecting directly to AD. To allow for this, I have to handle the OnRowUpdating event of the GridView, and set Cancel in the GridViewUpdateEventArgs to true, since the SqlDataSource has no update command defined. Then it just creates a DirectorySearcher, looks up the user based on distinguishedName (the data key of the GridView), calls GetDirectoryEntry() on the result, sets a couple properties, and finally calls CommitChanges() on the DirectoryEntry. No problems there - it's updating the data just fine.
It is, however, behaving a little confusingly. It seems there's a bit of a propagation delay when updating the data. It takes roughly ten seconds after updating before the view used for the SqlDataSource reflects the change. I'm not sure if this is typical AD behavior, or if the view is connecting to a different DC than the one the update is being issued against. This behavior isn't a problem in and of itself, and I can live with it if necessary.
But while that isn't a big problem, it's causing another issue - the GridView is sorted by default on the "company" column, which is one of the properties the user is allowed to edit. If the user changes the company on a row, waits a little while (or continues making multiple edits), then goes to edit another row, they could potentially end up editing a row adjacent to the one they wanted. This is because the GridView rebinds, and suddenly the row that had its company changed is sorted elsewhere in the list. The GridView is evidently triggering the edit based on the index of the row, and suddenly there's a different row at that index because the view used for the data source suddenly just caught up to reality.
What's sort of perplexing is that the GridView is trying to data bind on every post-back. In the past, I've had to make sure to call DataBind() after making data edits to ensure the data is up-to-date, but now it's happily hitting the data source every time. And yet the page ViewState is coming in around 66 KB, so I know the GridView is putting its data there.
So, off the top of my head, these would be a couple of fixes (that I haven't implemented successfully yet).
Stop the GridView from data binding with every page load, and stick with what it's got cached in ViewState. There's no "Cancel" property on the GridView's OnDataBinding event args, and setting Cancel during the data source's OnSelecting event just leaves me with an empty GridView.
Fix the propagation delay. Again, I don't have my heart set on that, but if it's a means of fixing the rows jumping around unexpectedly, then that's fine with me. Would I just have to make sure I'm explicitly connecting to the same DC both in the database view and when updating AD from ASP.NET? Or is there something more going on?
Trigger the row edit based on the primary key of the row rather than the index. I'm guessing I'd have to create a CommandButton in the row, set the CommandArgument to the primary key of the row, then walk through all the rows in the GridView on post-back and manually set EditIndex accordingly.
I'm open to other ideas too. If the ViewState ends up being a couple hundred KB, that's workable. This site is accessed over a pretty speedy LAN, and is strictly for internal use.
Updating Active Directory from ASP.NET tends to be a little slow no matter how you access it.
Not knowing what your code looks like, I would suggest the UpdatePanel and UpdateProgess tools if you are using the default ASP.NET tool set.
This will prevent any additional calls to the server until the update is complete. Something like this
<asp:UpdateProgress ID="updateMyGridViewProgress" runat="server">
<ProgressTemplate>
<img src="images/loading.gif" alt="Updating the information" />
</ProgressTemplate>
</asp:UpdateProgress>
<asp:UpdatePanel ID="updateMyGridView" runat="server">
<ContentTemplate>
<asp:GridView
ID="myGridView"
OnRowUpdating="UpdateAdInfo"
runat="server" >
</asp:GridView>
</ContentTemplate>
</asp:UpdatePanel>
I've had to use something similar to this to help users understand when the update had completed in the past.
However, the default ASP.NET AJAX UpdatePanel tends to be verbose in the information that it posts back to the server for even simple operations.
Personally, I would try to avoid using GridView and use something a little more AJAX friendly (ie jqGrid http://www.trirand.com/blog/)

Nesting gridview/formview in webuser control inside a parent gridview

I'm developing an ASP.net 2 website for our HR department, where the front page has a matrix of all our departments against pay grades, with links in each cell to all the jobs for that department for that grade.
These links take you a page with a gridview populated dynamically, as each department has a different number of teams, e.g. Finance has one team, IT has four. Each cell has a webuser control inserted into it.
The user control has a sql datasource, pulling out all the job titles and the primary key, popuating a formview, with a linkbutton whose text value is bound to the job title.
(I'm using a usercontrol as this page will also be used to show the results of a search of all roles in a range of grades for a department, and will have a varying number of rows).
I've got everything to display nicely, but when I click on the linkbutton, instead of running the code I've put in the Click event, the page posts back without firing any events.
Having looked around, it looks like I have to put an addhandler line in somewhere, but I'm not sure where, could anyone give me some pointers please? (fairly numpty please, I'm not too experience in ASP yet and am winging it. I'm also using VB but C# isn't a problem)
This is how I'm inserting the controls into the parent grid, have I missed anything obvious?
For row As Int16 = 0 To dgvRoleGrid.Rows.Count - 1
tempwuc = New UserControl
tempwuc = LoadControl("wucRoleList.ascx")
tempwuc.ID = "wucRoleList" & col.ToString
tempwuc.EnableViewState = True
dgvRoleGrid.Rows(row).Cells(col).Controls.Add(tempwuc)
CType(dgvRoleGrid.Rows(row).FindControl(tempwuc.ID), wucRoleList).specialtyid = specid
CType(dgvRoleGrid.Rows(row).FindControl(tempwuc.ID), wucRoleList).bandid = dgvRoleGrid.DataKeys(row)(0)
CType(dgvRoleGrid.Rows(row).FindControl(tempwuc.ID), wucRoleList).familyid = Session("familyid")
Next
Hard to say without having a look at more of your code, but the most common reason for not receiving an event from a control in a repeater is that you are re-binding the data instead of relying on ViewState. Does your code look something like this:
public partial class _Default : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
if(!Page.IsPostBack) // <--- YOU REALLY NEED THIS!
{
DoDataBinding();
}
}
}
Are you missing the IsPostBack check? Also... why the heck are you using FindControl if you already have a handle to your control? Cast to the appropriate type when you declare/load the control.
The reason I'm doing it this way is I'm fairly new to all this, and winging it with whatever seems to work; with the deadlines I'm facing, I don't really have the time to find the most elegant solution, as long as it's stable and works, it'll do.
There is an ispostback check which doesn't rebind the datasource on postback.
I've got round the issue by using treeviews, using the databound event to amend the postback URL to include the value as parameter, but I'd still like to work out how to get this working properly.

Gridview Pagination good or bad-Performance

Is Gridview pagination bad?
Reason: 1.If javascript is disabled, it will not work.
2.Search engine will not be able to index(I don't know what exactly the reason behind this).
Can somebody provide some information?
EDIT:
Now I am coding it as :
protected void GridView1_PageIndexChanging(object sender, GridViewPageEventArgs e)
{
int newPagenumber = e.NewPageIndex;
GridView1.PageIndex = newPagenumber;
GridView1.DataSource = Session["myDataSet"];
GridView1.DataBind();
}
the default implementation of gridview pagination is the worst option for performance as well as SEO. because it loads all data rows from your data source even if its only displaying 10 rows. second, search engines do not index postbacks
however there are many workarounds to sort this problem. implement custom data source to get rid of reading all rows from your data source, and then build your custom control to support seo friendly pager controls (you can even use the new asp.net mvc framework for this quite well)
on a seperate note - i see your code above - this is the worst way to code. never save bulky data in sessions, you would quickly run out of memory if you have too many users... instead use a global cache application, save your most frequently read data in there and use it for display purpose
To clarify, are you saying that:
By default the paging controls in a GridView are LinkButtons, which use JavaScript to perform postbacks?
A page with a paginated GridView will only display n records at once (where n is your page size)?
Therefore your concern is that a search engine will only ever see the first n records on your page as it may not follow javascript links? Therefore this could be a concern for SEO if you wish all the records in your datasource to be indexed?
My solution to this would be to use a ListView control, instead, with a DataPager control that uses a QueryString field to pass page number by GET. This would be easily followable by an search engine bot.
Pagination is concept to avoid rendering all the content at a time in a page. This will help in reducing the total size of the page.
If you don't apply pagination to your gridview and your control contains thousands of rows then this will be a horrible experience for the user to wait for the page to load.
You can pull only the number of records that you need for a particular page from database. This will avoid the burden of fetching all the rows and then displaying only the ones that are necessary.

ASP.NET Ajax Show UpdateProgress control for minimum amount of time

Sometimes my ajax request is so fast that the user does not realize a ajax call was made. So I would like to force the UpdateProgress control to display for a minimum about of time, even if the ajax request has finsihed.
For example:
John browses the site and the ajax call takes 2 seconds to complete. I only want the UpdateProgress control to display for those 2 seconds.
Mary also browses the site but the ajax call takes > 0.5 seconds. So I want to display the UpdateProgress control for at least 1 full second.
Does anybody have any ideas on how to do this?
Edit
I have discovered that the AjaxLoadingPanel control offered by Telerik has this ability. It has a property called MinDisplayTime that does this very thing. It would be nice to know how to do this using the standard (Free) asp.net ajax controls.
Thanks
I had the same problem working on an intranet site where the UpdatePanel contents changed so quickly that I couldn't tell if an update had happened without debugging, or checking the database.
The way I tackled this problem was to let the UpdatePanel do its thing as before, but use an UpdatePanelAnimationExtender to briefly flash a change of background colour, for example, before fading back to normal, giving the user the impression that an action has happened. If this happens quickly, say 0.3 of a second, and an appropriate 'action' colour is chosen, this can be very effective.
<ajaxToolkit:UpdatePanelAnimationExtender ID="myUpdatePanelExtender" runat="server" TargetControlID="myUpdatePanel">
<Animations>
<OnUpdating> ... </OnUpdating>
<OnUpdated> ... </OnUpdated>
</Animations>
</ajaxToolkit:UpdatePanelAnimationExtender>
You'll need to get the AJAX Control Toolkit, but if you're doing Asp.Net AJAX work, you'll be better off having it, if you don't already.
For what to place within the <OnUpdating> tag etc., see the Asp.Net Ajax Control Toolkit site page 'Using Animations'.
protected void Button1_Click(object sender, EventArgs e)
{
System.Threading.Thread.Sleep(3000);
Label1.Text = DateTime.Now.ToString();
}
http://www.asp.net/Ajax/Documentation/Live/tutorials/ProgrammingUpdateProgress.aspx
you could work around the latter by keeping track of the time your server side code takes to execute (or perhaps even better by measuring the ajax execution time, but that's more tricky)
the idea is this
long startTime = System.DateTime.Now.Ticks;
//this process can take some time, especially when executed for the first time
//it gets data and binds it to a control
FetchData();
//if this takes not enough time, add an extra second to allow the UpdateProgress to display
if (TimeSpan.FromTicks(DateTime.Now.Ticks - startTime).TotalSeconds < 1)
System.Threading.Thread.Sleep(1000);

Resources