ASP.NET Dynamic User Controls - asp.net

I'm sure this question has been asked a million times, however I haven't been able to find an answer that solves my problem.
I am programmatically adding some custom user controls to a PlaceHolder which I have on a simple aspx page. All of the user controls Postback's work correctly except for one which has a Gridview on it.
For some reason any postback that gets fired from within this control, does not call the specified event on the first click, however all future clicks it will work fine. I have no idea why this is the case, but many solutions I have found, suggest adding an ID to the ascx User Control, however this doesn't work in my case.
I've taken a look at the source file for the page that gets generated before and after the first click, javascript used for calling the postback changes, i.e
Before first click: onclick="javascript:__doPostBack('tmpControlID$sgvPrimaryEmploymentHistory','Select$0')"
After first click: onclick="javascript:__doPostBack('OFFHome1$tmpControlID$sgvPrimaryEmploymentHistory','Select$0')"
OFFHome1 is the parent user control which exists on the aspx page. All other controls are added to a placeholder in this control, i.e.
<%# Control Language="vb" AutoEventWireup="false" CodeBehind="OFFHome.ascx.vb" Inherits="UmbracoUserControls.OFFHome" %>
<asp:PlaceHolder ID="phOFFSection" runat="server"></asp:PlaceHolder>
Nothing to complicated. Then in the code behind the controls are loaded into the placeholder using the following:
Private Sub LoadNextOFFStep()
Dim ControlName As String = "TestControl.ascx"
phOFFSection.Controls.Clear()
If ControlName IsNot Nothing AndAlso ControlName <> String.Empty Then
Dim NewControl As Object = LoadControl(ControlName)
With NewControl
.ID = USERCONTROLNAME
Dim StepCompleted As Boolean = .FillControl()
If StepCompleted Then
Exit Sub
End If
Dim AllowSkip As Boolean = .AllowSkip()
btnSkip.Visible = AllowSkip
End With
phOFFSection.Controls.Add(NewControl)
End If
End Sub
Again, nothing overly complicated. The USERCONTROLNAME is just a const with the value "tmpControlID" in it.
The control that is giving me trouble is a little complicated, I was originally using a custom GridView control that we have created, but have removed it and replaced it with the standard asp one to see if the problem still occurs, and it does.
Any button, on control which fires off a postback will fail the first time, and all future click will work correctly. On the first click the Page_Load event will get called, but that is it.
What am I doing wrong??

After far too much time spent on this, I have finally worked it out.
It was to do with the order of events, however just not where I had thought. The FillControl function was getting called before User Control had been added to the PlaceHolder. I changed this so that it gets called after the User Control was added to the PlaceHolder and now it works first time.
Basically the code looks like this now:
Private Sub LoadNextOFFStep()
Dim ControlName As String = "TestControl.ascx"
phOFFSection.Controls.Clear()
If ControlName IsNot Nothing AndAlso ControlName <> String.Empty Then
Dim NewControl As Object = LoadControl(ControlName)
With NewControl
.ID = USERCONTROLNAME
Dim AllowSkip As Boolean = .AllowSkip()
btnSkip.Visible = AllowSkip
End With
phOFFSection.Controls.Add(NewControl)
Dim StepCompleted As Boolean = CType(phOFFSection.Controls(0), Object).FillControl()
If StepCompleted Then
LoadNextOFFStep()
Exit Sub
End If
End If
End Sub
Thanks for everyone's help.

Well... the answer to the first question is simple: It fails the first time because the ID of the control (or rather, in the postback script) is different on subsequent page loads. It works on subsequent clicks because the control ID stays the same.
Now as to WHY that is... much tougher! But probably something to do with the order of operations here.
Try explicitly setting the NamingContainer value for NewControl:
With NewControl
.NamingContainer = OffHomeOne; // whatever
.ID = USERCONTROLNAME

Related

value of variables not changing in asp.net

currently I am working on a project named online exam.
All the controls are dynamically created.
I have a webpage where I want to display the student details.
I displayed those details correctly in a table.
Now here comes the time to edit those details.
To edit a record I use the linked button named edit.
When a user clicks on that Linked button the data in that row is replaced with new textboxes.
Upto here I am OK.
Now when I click on the save changes button after making changes to the textboxes.
The old values are not replaced by the new values and the old values remains.
The code for creating textboxes in the table is as follows :
Public Sub Edit_Click(ByVal sender As Object, ByVal e As System.EventArgs)
For x As Integer = 0 To EditList.Count - 1
If sender.id.substring(4) = EditList(x).ID.Substring(4) Then
Session("PreviousRollNo") = RollNoList(x).Text
Dim txtName As New TextBox
txtName.Text = NameList(x).Text
NameList(x).Text = ""
NameList(x).Parent.Controls.Add(txtName)
txtList.Add(txtName)
Dim txtCourse As New TextBox
txtCourse.Text = CourseList(x).Text
CourseList(x).Text = ""
CourseList(x).Parent.Controls.Add(txtCourse)
txtList.Add(txtCourse)
Dim txtAdmissionDate As New TextBox
txtAdmissionDate.Text = AdmissionList(x).Text
AdmissionList(x).Text = ""
AdmissionList(x).Parent.Controls.Add(txtAdmissionDate)
txtList.Add(txtAdmissionDate)
Dim btnSaveChanges As New Button
btnSaveChanges.Text = "Save Changes"
EditList(x).Text = ""
EditList(x).Parent.Controls.Add(btnSaveChanges)
AddHandler btnSaveChanges.Click, AddressOf btnSaveChanges_Click
Session("EditButtonClicked") = True
Dim btnCancel As New Button
btnCancel.Text = "Cancel"
DeleteList(x).Text = ""
DeleteList(x).Parent.Controls.Add(btnCancel)
AddHandler btnCancel.Click, AddressOf btnCancel_Click
Session("CancelButtonClicked") = True
txtName.Focus()
Exit For
End If
Next
End Sub
The code for Save Changes button is as follows :
Public Sub btnSaveChanges_Click(ByVal sender As Object, ByVal e As System.EventArgs)
If txtList(0).Text = "" Then
Dim trError As TableRow = New TableRow
Dim tdError As TableCell = New TableCell
tdError.ColumnSpan = 7
Dim lblError As New Label
lblError.Text = "Please enter name of the student."
lblError.ForeColor = Drawing.Color.Red
tdError.Controls.Add(lblError)
trError.Controls.Add(tdError)
tbl.Controls.Add(trError)
ElseIf txtList(1).Text = "" Then
Dim trError As TableRow = New TableRow
Dim tdError As TableCell = New TableCell
tdError.ColumnSpan = 7
Dim lblError As New Label
lblError.Text = "Please enter the course."
lblError.ForeColor = Drawing.Color.Red
tdError.Controls.Add(lblError)
trError.Controls.Add(tdError)
tbl.Controls.Add(trError)
ElseIf txtList(2).Text = "" Then
Dim trError As TableRow = New TableRow
Dim tdError As TableCell = New TableCell
tdError.ColumnSpan = 7
Dim lblError As New Label
lblError.Text = "Please enter the Admission Date"
lblError.ForeColor = Drawing.Color.Red
tdError.Controls.Add(lblError)
trError.Controls.Add(tdError)
tbl.Controls.Add(trError)
Else
Dim cb As New OleDbCommandBuilder(da)
Dim editRow() As DataRow
editRow = ds.Tables("Student_Detail").Select("Roll_No = '" & Session("PreviousRollNo") & "'")
editRow(0)("Name") = txtList(0).Text
editRow(0)("Course") = txtList(1).Text
editRow(0)("Admission_Date") = txtList(2).Text
da.Update(ds, "Student_Detail")
Page.Response.Redirect("ChangeUserDetails.aspx")
End If
End Sub
I get the error sying that array is out of the bounds. on the first line of the btnSaveChanges_Click.
It means txtlist is always cleared when I click on Save Changes Button.
So I stored txtList in a Session like Session("txtList") = txtList.
and retrieved the data from that. But now I get the old values of the textbox instead of the newer ones.
Here txtList is a list (of Textbox)
Firstly, welcome to the ASP.NET WebForms Page Life Cycle. Remember its pattern with the simple mnemonic: SILVER = Start, Init, Load, Validate, Events, Render.
Secondly, HTTP is stateless. WebForms does an amazing job of hiding this fact from you using ViewState until you do something a little out of the ordinary (as you're now attempting), and it all appears to fall apart. What's really happening is that you're starting to see side-effects of how WebForms is managed, and how it's not as much like WinForms (or another stateful system) as you might think.
When you're responding to an event server-side in WebForms, it's easy to get the impression that nothing has changed. That the entire page is as you left it "last time". All the controls are there, the values you may have set programatically are still set. Magic. Not magic. What's actually happened is the entire page has been re-constructed to respond to that event. How was it re-constructed? By a combination of your page definition (markup), actions taken in control event handlers, and the form data posted back by the client.
Confusing? OK, let's consider an example. Say you've got a page with two controls on it. A textbox named txtInput and a button named btnSubmit with event handler btnSubmit_Click. When the user first requests the page, the HTML for these controls is derived from your markup (aspx page) and returned to the client. Next, the user sets a value in txtInput and clicks the submit button. The server then re-creates the page from scratch based on your markup. At this early stage of the life-cycle, the controls still have their default values. We then hit the Load stage of the life-cycle, and "if the current request is a postback, control properties are loaded with information recovered from view state and control state." In other words, by the time the life-cycle gets to Init, the control has been created from markup, but still has its default value. The Load stage then sets the value according to Postback data.
Left wondering how this applies to your scenario? You're adding your dynamic controls in response to a control event. There's two things wrong with that:
It's too late in the page life-cycle for Init to set the values based on data posted back from the client (recall SILVER, Event is after Init).
Your button click event handler is only run once, in response to the postback where the user clicked the button. But remember on each postback the page is entirely re-created. So the dynamic controls no longer exist as far as the server is concerned! You'll notice that not only are the controls not present server side when responding to the submit event, but after the page has handled it, they're no longer present client-side either.
So what's the answer? Well the "Life-Cycle Events" section of the page I linked offers a clue. It states that the PreInit event be used to (among other things) "Create or re-create dynamic controls". Why would we do it in PreInit? So it's early enough in the page life-cycle for the later events to properly handle it (like setting the values posted back from the client).
Now, I know, you want to add the controls based on the user clicking on the button. How does that fit? The trick is that you've got to manage the "state" yourself. Huh? the state? By this I mean MyDynamicControlsShouldBeShown = true / false. When the button is clicked, creating the controls in response to the button-click event handler is the right action (there's not really any choice there). But you need to store that state somehow so you know on subsequent requests to the page, whether those controls should be re-created in PreInit. One neat option would be to check for the ID of your dynamic control in Request.Form.Keys. If the control ID is present in the Keys collection, then the user is posting back a value for the control, so you should re-create it.
A side-note on the use of Session
Hopefully based on the above you've realised why putting the controls into Session didn't work. But to be clear, the controls you put into the Session object were no longer part of a page that existed (remember, the page gets completely re-created for each request. Those controls were no longer hooked up to the Page events, so didn't get their values populated between Page Init and Load. If somehow it did work, it still wouldn't be a particularly good idea, as Session is not per-request. So what would happen if a user had the same page open in multiple tabs? Strange things, that's what.

Can't clear credentials textboxes on ASP.NET CreateUserWizard Control

I have a CreateUserWizard control using forms authentication on my login/create user page. I customized the CreateUserWizardStep1 so that I could add some additional validators.
After successfully creating a user with the control and it displays "Complete
Your account has been successfully created." I have added an additional button that will allow the person to create another user by setting the ActiveStepIndex = 0. The problem is, while it sets the ActiveStepIndex correctly, it retains the old user account credentials. I try to clear them manually using the following code, but they still stick...Anyone have any ideas?
Protected Sub btnCreateAnotherUser_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Me.cuwMain.ActiveStepIndex = 0
CleanCreateNewUserInput()
End Sub
Private Sub CleanCreateNewUserInput()
Dim txtUserName As TextBox
txtUserName = FindControlIterative(Me.cuwMain, "UserName")
txtUserName.Text = String.Empty
Dim txtPassword As TextBox
txtPassword = FindControlIterative(Me.cuwMain, "Password")
txtPassword.Text = String.Empty
Dim txtConfirmPassword As TextBox
txtConfirmPassword = FindControlIterative(Me.cuwMain, "ConfirmPassword")
txtConfirmPassword.Text = String.Empty
Dim txtEmail As TextBox
txtEmail = FindControlIterative(Me.cuwMain, "Email")
txtEmail.Text = String.Empty
Dim txtQuestion As TextBox
txtQuestion = FindControlIterative(Me.cuwMain, "Question")
txtQuestion.Text = String.Empty
Dim txtAnswer As TextBox
txtAnswer = FindControlIterative(Me.cuwMain, "Answer")
txtAnswer.Text = String.Empty
End Sub
It finds the textboxes correctly, but it does not actually reset their values, even though in the debugger it says it did.
Thoughts ?
What happens if you call Response.Redirect(Request.Url.ToString(), true)? That should clear everything for you.
Also, the recursive nature of the FindControlIterative call would make your code quite expensive to run as it has to drill down into the control heirarchy for every control that you are looking for.
The problem with your code is that:
In a Wizard control, ViewState is not responsible for storing the modified values for controls such as TextBoxes. These controls implement the IPostBackDataHandler interface. The LoadPostBackData event fires in the page lifecycle, in which the VALUES of the controls load from the form HTTP POST headers... which are resubmitted by the client...
So how to destroy the HTTP POST Headers to clear the control values?
A new request results in new HTTP POST Headers... simply do this in the Button click event handler:
Response.Redirect(Page.Request.Url.ToString());
This has the added benefit that it goes to Step 1 of the wizard so you also dont have to do... wiz.MoveTo(wiz.WizardSteps[0]).
Credit to Konrad - ASP.Net Wizard - How to clear contents of web controls
I feel silly posting this..., but I just turned viewstate off on the CreateUserWizard control and that did it.
Thanks for the help Daniel, I now have a better understanding on how ASP.NET stores information.

How do I Prevent FormView from clearing user's entered Values after Insert Method has fired?

I have been struggling with getting FormViews to work the way Microsoft expects me to for about a day and have figure a bunch of great stuff out.
I can catch e.Exception and e.ReturnValue in the ObjectDataSource.Inserting Event Handler and I can even cheat and check other properties of the Object in the ObjectDataSource.ObjectDisposing by checking the e.ObjectInstance ... and I even learned that FormView's Inserting Handler Runs AFTER the ObjectDisposing Handler so If there is a problem found I still have time to react to it and st the e.KeepInInsertMode to true on the FormView.
My problem is, it seems that the values entered by the user into the Insert form are cleared regardless.
So, How do I Prevent a FormView from clearing after it's Insert Method has fired?
(Using ASP.NET + VB)
I don't think posting my code here will really do much good and i would have to modify it to trim out confidential business logic stuff... so I'll skip it for now.
edit:
I have found a temporary and admittedly terribly cludgy solution (in case no one ever finds a REAL solution to the problem).
I have a page variable defined as:
Dim eInsertArgs As FormViewInsertedEventArgs
And then I do the following in my ItemInserted handler
If boolInsertErrorOccurred = False Then
e.KeepInInsertMode = True
eInsertArgs = e
Else
eInsertArgs = Nothing
End If
Then on each of the controls I have something like this in that controls databinding event:
If IsNothing(eInsertArgs) = False Then
Dim _sender As TextBox = sender
_sender.Text = eInsertArgs.Values("_FieldName")
End If
The effect of this is that I am setting the values BACK to the submitted values AFTER ASP.NET binds the FormView to the default (blank) Template.
Please help me find a less terrible solution. :)
You need to create your own server control which inherits from the FormView control.
Public Class MyFormView
Inherits FormView
Protected Overrides Sub OnDataSourceViewChanged(ByVal sender As Object,
ByVal e As EventArgs)
If (MyBase.CurrentMode = FormViewMode.Insert) Then
MyBase.RequiresDataBinding = False
Else
MyBase.OnDataSourceViewChanged(sender, e)
End If
End Sub
End Class
Please take a look at this page: http://www.dotnetmonster.com/Uwe/Forum.aspx/asp-net/76885/FormView-Insert

dynamically set control ID

i have few texboxt and dropdownlist, which their id would be something like "txtName1, txtName2, txtName3..." and "ddlAction1, ddlAction2, ddlAction3...."! I would to to dynamically set the textboxt and dropdownlist id into something like this:
for i as integer = 0 to 6
a = txtName+i.text
b = ddlAction+i.SelectedValue
next i
Need help from you guys to do this! thanks....
The key is FindControl, which looks up a control by its expected ID:
For i As Integer = 0 To 5
Dim txt As TextBox = TryCast(Me.Page.FindControl("txtName" & i.ToString()), TextBox)
Dim ddl As DropDownList = TryCast(Me.Page.FindControl("ddlAction" & i.ToString()), DropDownList)
If txt IsNot Nothing AndAlso ddl IsNot Nothing Then
Dim a As String = txt.Text
Dim b As String = ddl.SelectedValue
End If
Next
It will return null/nothing if a control with that ID isn't found.
Note that FindControl will only search the given control's (or Page's) immediate children, not the entire control tree. To search recursively, you need to use your own FindControl method.
Private Function FindControlRecursive(ByVal control As Control, ByVal id As String) As Control
Dim returnControl As Control = control.FindControl(id)
If returnControl Is Nothing Then
For Each child As Control In control.Controls
returnControl = child.FindControlRecursive(id)
If returnControl IsNot Nothing AndAlso returnControl.ID = id Then
Return returnControl
End If
Next
End If
Return returnControl
End Function
.Net requires you to resolve your variable names at compile time, rather than runtime like your code is trying to do. This is a good thing, as it prevents you from making certain kinds of errors. But it does mean you'll need to look at an alternative approach for this particular problem.
One option is a FindControl -based method. But odds are the controls you care about are grouped together on the page. If they aren't already, put them in a common container control, like a panel. Then you can do something like this (requires System.Linq):
For Each t As TextBox In MyContainer.Controls.OfType(Of TextBox)()
a = t.Text
Next t
For Each d As DropDownList In MyContainer.Controls.OfType(Of DropDownList)()
b = d.SelectedValue
Next d
Also, I hope you're really doing something other than assignment inside your loop. Otherwise, most of the work is for nothing as you will exit the loop having simply assigned the value from the last iteration to your variable.
Finally, it seems like these controls might work in pairs. To me, that's a situation that calls out for you to implement a user control.
I'm not a webforms expert, but I believe the page stores a reference to each control present in the page.
You can think of webforms like an n-ary tree... each control has a parent and can have 0 to many children. So, if these are static controls you should just be able to grab a reference to their parent and iterate over that... with no need for the children's ids.
Also, you can query for children based on their id... something like myControl["myID1"] so you can concat the number with the string and get the control that way.
Lastly, if these are purely dynamic controls, i.e. you don't know how many there are, just store references to them in an ordered collection and iterate over them that way.
EDIT:
Here we go:
WebControl myControl;
myControl.Controls.Add(someControlReference);
Then, to grab a control by ID:
WebControl someControl = myControl.FindControl("someControlID1");
From there you can do like:
string a = someControl.Text

Problem while Creating Dynamic ModalPopup

i want to create a modalpopup dynamically but i come across a problem.I pasted my sub here and i dont know what to do for that problem.When i want to show modalpopup,it says
"Control 'mdldelete2' of type 'ModalPopupExtender' must be placed inside a form tag with runat=server." How can i solve this?
Public Sub Raise_Alarm(ByRef p_Page As Page,
ByRef p_AssignedButton As System.Web.UI.WebControls.Button,
ByVal p_Message As String)
Dim mdldelete2 As Global.AjaxControlToolkit.ModalPopupExtender =
p_Page.Page.FindControl("mdldelete2")
If mdldelete2 Is Nothing Then
mdldelete2 = New Global.AjaxControlToolkit.ModalPopupExtender
End If
With mdldelete2
.TargetControlID = p_AssignedButton.ID
.PopupControlID = "pnlDelete"
.ID = "mdldelete2"
.BackgroundCssClass = "modalBackground"
.OkControlID = "btnDeleteOk"
.CancelControlID = "btnDeleteCancel"
End With
p_Page.Controls.Add(mdldelete2)
Dim mylabel As Label
mylabel = p_Page.FindControl("lblStatus")
mylabel.Text = p_Message
mdldelete2.Show()
End Sub
Really, you should be adding the mdldelete2 control to the Controls collection of the Form control, rather than the Page directly - that might help.
I often find it's easier to add a PlaceHolder control to the page for this sort of thing - it doesn't add anything directly to the page, but gives you a named container to find and add controls to.
Also, just a point - if you did find an instance of the control with the Page.FindControl method, then you don't need to add it to a form collection again, as it's already in there.
Looks like you need to add a ScriptManager control to the aspx
<asp:ScriptManager ID="ScriptManager1" runat="server">
</asp:ScriptManager>

Resources