Nesting gridview/formview in webuser control inside a parent gridview - asp.net

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.

Related

Textbox not updating on page load ASP.net

I have a dynamically built table on the page load of my web page containing textboxes with defaults that can be changed by the user. There is also some validation on the user input values (e.g., input can not be less than zero). When the user submits the table, only valid inputs are stored, and the page is refreshed pulling the stored values to populate the table when it is rebuilt. For some reason the textboxes will still display invalid inputs after submitting and reloading the page even though they are not persistent, and this problem does not happen for labels.
Is there something specifically built into the textbox control causing this issue? Is there somewhere storing the state, perhaps (I tried setting the ViewState flag to false)? Is this just some part of the page lifecycle?
The textbox is built in a method called from the Page_Load event
void Page_Load(object sender, EventArgs e)
{
//page initialization...
ValidateData(); //This is where the current data is verified, and stored if valid
BuildTable(); //This takes that stored value for the text
}
void BuildTable
{
tbTable = new Table();
tbRow = new TableRow();
tbCell = new TableCell();
TextBox tb = new TextBox();
tb.text = some stored value;
tbCell.Controls.Add(tb);
tbRow.Cells.Add(tbCell);
tbTable.Rows.Add(tbRow);
divFromPage.Controls.add(tbTable);
}
If more information is necessary to help with this issue, just let me know.
Edit :
After reproducing your problem i came to the following conclusion.
I believe the problem is not the viewstate but asp is just repopulating these values because the form data which you submited has the same name as the input element which you are returning.
Even though you generate them dynamicly since you add them to the same div the result is always the same. This leads me to 2 solutions :) :
My solution nr 3 still stands , i tried it out and on button click redirecting works as intended with no posted data creeping back into my textbox.
This statement takes care of that :
Response.Redirect("~/test.aspx");
Alternativly you could generate a random ID to make sure the inputfields names you are returning are different from the ones that were submited. You wouldn't have to change all the names but if the table ID for example is different the entire table won't get populated with the submitted data anymore.
Be aware that you might need a if(IsPostBack) in your pageload because your data will be lost right after pageload(unless you handle the saving before that)
If you are doing a postback asp.net uses it's viewstate to maintain all the textbox,textfields and all other form elements(including input type hidden).
I currently see 3 solutions to your problem :
You could do what ivowiblo suggested, before you load the data from the database( i assume you do) you could wipe all the textfields. On a first time visit this will be pointless off course since they are blank. But after a postback it will actually wipe the textfields.
You could disable the viewstate on those textboxes so asp will not persist the state. I'm not sure why your test didn't work but it should work. If you choose this approach feel free to edit your question with some actual code
My personal favorite : Redirect the page. In stead of just returning the result and causing a postback to the page do the following : after the user hits the save button you can save the data and then do a Response.Redirect to your current page. The browser will be redirected to the page and no viewstate will be involved.
Just figured this out, kind of a D'oh moment, I just populated the data in the PreRender event and it works just fine, thanks for all the input.

RowCommand GridView took 6,7 seconds

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.

(New to ViewStates) What is the best way(s) to save a dynamically generated control into the viewstate?

I am creating a web application in Asp.net and I'm still fairly new. I'm just starting to wrap my head around the basics of the ViewState. In my application, people are searching through a database and I give them ways to narrow their search. When they have entered a valid search constraint (ex: date past 10/1/11) I dynamically add another set of controls allowing them to add another constraint. I want to save the contents of the previous constraint (a set of Controls) so that I can still have it on the webpage when they enter the next constraint.
If it makes any difference, one constraint set consists of a drop-down list of attributes, a few literal control, and one or two text fields depending on what attribute was chosen from the drop down list.
How would I go about this?
Thanks so much guys.
The easiest way to track viewstate for dynamic controls is to recreate the controls in OnInit and assign the same ID to the controls every time the page is posted back. If the controls are assigned the same ID each time they're created, when the ViewState is loaded, the controls will be repopulated.
protected override void OnInit(EventArgs e)
{
TextBox txt = new TextBox();
txt.ID = "txt1";
this.Controls.Add(txt);
}
EDIT
To make things easier, try using the DynamicControlsPlaceHolder. Just put the control on the page, and it will persist the controls and their values behind the scenes:
http://www.denisbauer.com/ASPNETControls/DynamicControlsPlaceholder.aspx
Check out this link:
https://web.archive.org/web/20210330142645/http://www.4guysfromrolla.com/articles/092904-1.aspx
http://www.codeproject.com/KB/viewstate/retainingstate.aspx
ViewState for dynamic controls is still maintained by the ASP.NET framework. Just make sure you add them during init or preinit, because viewstate is loaded for every control between the init and load stages.

Failed to load viewstate. The control tree into which viewstate is being loaded must match the control tree that was used to save viewstate

I'm currently working on a dynamic core for several webprojects. It has a core that uses a treeview and a menu. And then for each specific projekt it loads several different wuc into a maincontent. Some business projects use business related wucs while others uses different ones. So the span of wuc's is really big.
Now to my problem, whenever a user press a menuitem or a treeitem it loads a wuc to the maincontent linked to that object.
But I'm having some viewstate errors and i've been looking around for 2 days now and none of the solutions explained are working for my projekt.
All my wuc has to have viewstate enabled.
Cycle is ->
Page(Control A) does postback with variable to change control to ControlB in wucPanel(UpdatePanel).
OnLoad LoadRequested Wuc.
Current code is
protected void Load_Page(object sender, EventArgs e)
{
//Code to decide which wuc to load.
UserControl wucc = (UserControl)Page.LoadControl(sFilePath);
ParentControl.ContentTemplateContainer.Controls.Add(wucc);
}
I've tried several fixes like adding diffrent ids to the wuc, but this either disabels the internal functions of control like handlers etc or generates the same viewstate error.
One solution i found was to load ControlA and then just removing it and then load ControlB. But this disabled the scripts for my 3rd party controller (Telerik).
I've also read about having diffrent PlaceHolders for each typof but since i expect havign up to 50 diffrent Controls I don't feel this is gonna help me.
And moving from Page_Load -> Page_Init generated the same error.
Error:
Failed to load viewstate. The control tree into which viewstate is being loaded must match the control tree that was used to save viewstate during the previous request. For example, when adding controls dynamically, the controls added during a post-back must match the type and position of the controls added during the initial request.
In your case Anders, you still need to add the old control to your page in the init method along with the new control that you now want to add. Keep a reference to this old control that you have just added in a class level variable. So something like
Control _oldControl = null;
protected void Init_Page(object sender, EventArgs e)
{
//Code to decide which wuc to load.
UserControl wucc = (UserControl)Page.LoadControl(sFilePath);
ParentControl.ContentTemplateContainer.Controls.Add(wucc);
_oldControl = wucc as Control;
//Now add the new control here.
}
//override the LoadViewState method and remove the control from the control's collection once you page's viewstate has been loaded
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
ParentControl.ContentTemplateContainer.Controls.Remove(_oldControl);
}
Hope this helps. If it did, please check the checkbox next to this answer to accept it and vote it up if you like :)
In order to avoid ViewState related errors please make absolutely sure that in Page_Init you create the same control tree that was created the previous time ViewState was saved i.e. the previous postback. Simple page life cycle:
Page Init - create the control tree
- View State is loaded and applied here
Page Load - already loaded view state, you can do modifications to the control tree here
- Save View State
Page PreRender
For what it’s worth I recently had the same problem.
My scenario was as follows.
A fixed panel of filters (dropdown lists and textboxes) which built a search SQL string. On submission of the search consequent results were displayed in an editable gridview beneath.
On editing the gridview I cold effectively change the state of a database record thus removing it from the gridview under the filters previously chosen. In some cases this resulted in no results being returned thus causing me to hide the gridview.
I then found that if I used the new state of the record in the filter and resubmitted the search that error sometimes occurred.
The problem I eventually found had nothing to do with enabled viewstates etc but simply that the empty gridview, though no longer visible (changed programmatically), had not been rebound to a null datasource.
This appeared to cause the conflict and the error.
So it appears as though in my case the viewstate issue arose from a non-visible gridview that contained non-refreshed data.

Creating a UserControl Programmatically within a repeater?

I have a repeater that is bound to some data.
I bind to the ItemDataBound event, and I am attempting to programmatically create a UserControl:
In a nutshell:
void rptrTaskList_ItemDataBound(object sender, RepeaterItemEventArgs e)
{
CCTask task = (CCTask)e.Item.DataItem;
if (task is ExecTask)
{
ExecTaskControl foo = new ExecTaskControl();
e.Item.Controls.Add(foo);
}
}
The problem is that while the binding works, the user control is not rendered to the main page.
Eh, figured out one way to do it:
ExecTaskControl foo = (ExecTaskControl)LoadControl("tasks\\ExecTaskControl.ascx");
It seems silly to have a file depedancy like that, but maybe thats how UserControls must be done.
You could consider inverting the problem. That is add the control to the repeaters definition and the remove it if it is not needed. Not knowing the details of your app this might be a tremendous waste of time but it might just work out in the end.
If you are going to do it from a place where you don't have an instance of a page then you need to go one step further (e.g. from a webservice to return html or from a task rendering emails)
var myPage = new System.Web.UI.Page();
var myControl = (Controls.MemberRating)myPage.LoadControl("~/Controls/MemberRating.ascx");
I found this technique on Scott Guithrie's site so I assume it's the legit way to do it in .NET
I think that #Craig is on the right track depending on the details of the problem you are solving. Add it to the repeater and remove it or set Visible="false" to hide it where needed. Viewstate gets tricky with dynamically created controls/user controls, so google that or check here if you must add dynamically. The article referenced also shows an alternative way to load dynamically:
Control ctrl=this.LoadControl(Request.ApplicationPath +"/Controls/" +ControlName);

Resources