ASP.NET data binding on postback - asp.net

Working on an ASP.NET 4.0 project, which uses user controls to dynamically generate a form based on definitions stored in the database. One form field would look like this:
<usc:RefControl ID="ctrlUser1"
ReferenceFieldId='product.user1'
ValidationFormat="^\d+\.?\d{0,2}$"
runat="server"/>
Behind the scenes the control emits a RegularExpressionValidator based on the RefControl.ValidationFormat property.
This works fine, however the problem is that this architecture only allows us to validate with regexes. Now I need to add date format validation, based on the user's preferences (which aren't dependent on their UICulture).
Reluctant to refactor the entire user control setup, I need to be able to pass a on-the-fly regex pattern to the ValidationFormat property. Currently I'm using a data binding expression to bind the property to a page instance method:
<usc:RefControl ID="ctrlUser2"
ReferenceFieldId='product.user2'
ValidationFormat="<%# GetUserDateValidationFormat()%>"
runat="server"/>
Code behind:
/// <summary>
/// Returns a regular expression that validates the current user's date format
/// </summary>
public string GetUserDateValidationFormat()
{
//...
}
Works okay on first page load, but on subsequent postbacks the validation doesn't work. I think the issue is that the data binding expression doesn't evaluate at all, but I'm not sure I understand why. I'm calling Page.DataBind() in Page_Init whether Page.IsPostBack or not, so shouldn't this work?
If you see I'm barking up the wrong tree, any alternative solutions to the same problem are also welcome.
EDIT
Managed to solve this problem. The issue was with the way ASP.NET page life cycle invokes the user control's events before the page's own events. The control values were being initialized before the data binding on the page could happen.
Because I still need to do the control initialization before Page_Load to subscribe the controls to viewstate, I simply moved the initialization logic to the Page.InitComplete event so the call to Page.DataBind() could get called first.
protected void Page_Init(object sender, EventArgs e)
{
Page.InitComplete += new EventHandler(Page_InitComplete);
}
So the event tree becomes
User Control Page_Init => Hook InitComplete handler
Page (aspx) Page_Init => Bind data
User Control Page_InitComplete => Initialize the control

Couldn't you just set the property in Page_Load()?
public void Page_Load(...)
{
ctrlUser1.ValidationFormat = GetUserDateValidationFormat();
// do whatever stuff you do
}

Related

Read custom user control state

I add a custom user control to my page, with textboxes and radioboxes, but after each postback, the contents are removed and I don't know how to read the values that were entered in it.
From what I've learned you have to add the user controls on each refresh, but that does not restore their state. I want the values to be stored in the ViewState but that does not happen for some reason.
Note: I need to add multiple user controls on one page, so I need to identify each user control, either trough an ID or a linq expression that selects that user control from my page.
Im programming with C# 4.0
How I add my user control:
private void AddInstrumentDetailToPage()
{
RMAItem lItem = (RMAItem)Page.LoadControl("/Controls/RMAItem.ascx");
InstrumentDetail.Controls.Add(lItem);
}
I save the amount of controls I have in the ViewState, and call AddInstrumentDetail() that amount of times, but they appear to be empty. I've read somewhere that I have to add it in Page_Init because view state is not initialised yet, but that doesn't help either.
You need to recreate dynamically added (User-)Controls at latest in Page_Load to maintain ViewState. So you should store the number of already added controls in a ViewState-Property and according to that reload them in Page_Load. You have to sum up this variable with 1 in AddInstrumentDetailToPage.
MSDN: Dynamically Adding User Controls
If you store the number of added controls in a Viewstate variable, you cannot recreate controls in Page's Init-Event because the ViewState-Variable would yet not been reloaded there.
You won't have any ControlState unless you make a PostBack. Refreshing your browser/requesting the same link will just be a fresh start.
By default, any control added to your page are data stored in ViewState and ControlState for your UserControl.
If you are willing to save your contents, I would suggest you save it on Request.Session.Add("yourKey", "yourValue").
You can access your session with var yourValue = Request.Session["yourKey"]; on your protected void Page_Load(object sender, EventArgs e) method.
Specify an ID for your RMAItem instance, otherwise storing data on ControlState might not work!
private void AddInstrumentDetailToPage()
{
RMAItem lItem = (RMAItem)Page.LoadControl("/Controls/RMAItem.ascx");
lItem.ID = "rmaItem1"; //<-- This is important
InstrumentDetail.Controls.Add(lItem);
}
Hope it helps!

Why don't events of dynamically added user control fire on postback?

There are plenty of similar questions on SO (1, 2, 3, 4 etc) and on the web (1, 2 etc) but none of the corresponding answers clarifies my situation.
I have a simple custom user control consisting of two drop-down lists. Selection of a value in one drop-down list should lead to the populating of the other one. As long as I declare the user control in the .aspx code everything works as expected.
Now I want to add the user control on the page programmatically (on button click). Although the control is being added, the selection in one drop-down list causes only a postback but no action of the other drop-down list.
While debugging I've found out that not only OnSelectedIndexChanged does not fire, but also OnLoad and all the other events.
Common reasons for such a behaviour regarding all the discussions I've looked through are the following:
AutoPostBack of the DropDownList is not set to true or the databound DropDownList is being rebound on each postback which causes the losing of events. //not a case here, more likely refers to dynamically added drop-down lists
ID is assigned to the dynamically added control automatically on each postback (and every time a different one so that ViewState is not persisted correctly and events do not know that they should fire). // ok I've checked it and now assign the ID manually
The control is added only once (as opposed to the addition on every postback which is necessary because in the ViewState only the state (values) of server controls are stored, but not the controls themselves) and/or
the control is added on each postback but too late in the Page Life Cycle. //ok I'm adding my control in OnInit event handler now
In order to let the page know that the control was added (and how many of them) I use Session. Below a bit of code and then finally the question :)
.aspx:
<asp:Content ID="Content3" ContentPlaceHolderID="MainContent" runat="server">
<asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>
<asp:Button ID="Button1" runat="server" Text="Button" onclick="Button1_Click" />
</asp:Content>
and the code behind:
protected void Page_Load(object sender, EventArgs e)
{
if(!this.IsPostBack)
{
Session.Remove("Childcontrols");
}
}
private void AddTransitControl()
{
List<Control> controls = (List<Control>)Session["Childcontrols"];
AddTransitPoint atp = (AddTransitPoint)LoadControl("~/UserControls/AddTransitPoint.ascx");
string id = this.ID + "_eb" + (controls.Count).ToString();
atp.ID = id;
controls.Add(atp);
PlaceHolder1.Controls.Add(atp);
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (Page.IsPostBack)
{
if (Session["Childcontrols"] == null)
{
Session["Childcontrols"] = new List<Control>();
}
List<Control> controls = (List<Control>)Session["Childcontrols"];
int count = 0;
foreach (Control c in controls)
{
// AddTransitPoint atp = (AddTransitPoint) c; //mystically not working (fires no events)
AddTransitPoint atp = (AddTransitPoint)LoadControl("~/UserControls/AddTransitPoint.ascx"); //it is working!
string id = this.ID + "_eb" + count;
count++;
atp.ID = id;
PlaceHolder1.Controls.Add(atp);
}
}
}
protected void Button1_Click(object sender, EventArgs e)
{
AddTransitControl();
}
(I'm pretty sure that the code of the user control itself is not really relevant for the case but on request I can add it later).
So now the question: Through trial and error I've found out that if I store the newly added control in the collection in Session and OnInit just take the control out of this collection and add again to the control collection of my placeholder, no events of this control fire on the next postback (independent of the way the postback is called). Other way if I create OnInit a new control for each stored in Session and add to the placeholder control collection this newly created control - everything works! So what is wrong with stored in the Session controls and why do they lose their events?
And one more small question here. What is the best practice for creating IDs for such controls? I use a string of particular format along with a counter, but I doubt it is the best way to do it. For example if I added different types of controls I would face trouble with this method.
Thank you everybody for reading such a long question and for your valuable input!
My understanding is this: events must be re-established on every postback. You get this for free if the event is defined in the the ASPX file, because those attributes (OnClick, OnSelectionChanged, etc.) are re-processed when the page object is instantiated.
You do not get this for free with dynamically-built controls, or controls which are defined in the ASPX file but have their events wired-up in the codebehind. For those controls, the events are not known at instantiation time, thus the page can not automatically re-establish them for you. For those controls, you must re-establish your events on every postback.
My understanding of session-stored data is: when you put an object in the session, that object itself is not kept alive in memory. Instead, it is serialized, and the serialized data is stored in the session. When you get the object back out of the session, it is deserialized.
Objects' events do not survive the serialization/deserialization round-trip because they are logically function pointers - so when the object is removed from memory and later re-built, the entire memory "landscape" has changed, so those events/pointers would no longer be valid.
I haven't done a lot of work with dynamically-generated controls, so I can't tell you the most effective pattern to managing them and their events. But I know that storing them in the session isn't really buying you any benefit over re-creating them on each postback; and it is bloating up the session memory unnecessarily. Hopefully some other SO'ers on this thread can point you to the best-practices way of managing them.
The controls events don't work when stored in the session because they are now referencing an instance of the page which no longer exists.
Let me rephrase what I think you are saying (after a brief look at the code):
When I add a user control (ASCX) to a page dynamically, the controls on the user control do not fire.
If so, the code for the user control is relevant, because it should be handling the events fired from its controls.
If not, then you wire up the server controls (Microsoft created or you created) with delegates to handle the event, as you are missing the "I'll do the magic for you" step when dragging and dropping a control on a page.
Am I at least somewhere close to the target?
You will have to recreate the viewstate within the control to solve the problem

se the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation

guys, I have a usercontrol in my asp.net 3.5 application and I am passing some plain text on button click event. button is situated in the usercontrol. but when I fire the event, I am getting the following error;
Invalid postback or callback argument.
Event validation is enabled using
in configuration or <%# Page
EnableEventValidation="true" %> 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.
when I set the EnableEventValidation="false" to web form page like blow. it fires the event;
<%# Page EnableEventValidation="false" %>
but I am thinking that it shouldn't be a good idea to set that false. So, what is the alternative here? there error saying that 'use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.' but where am I gonna register that thing? thanks !
Also, I am using some AjaxControlToolkit controls inside my usercontrol and some jquery stuff.
try
if (!Page.IsPostBack)
before load and bind data in datagrid
The problem with ASP.NET 2.0 event validation is that it is all or nothing and a bit heavy handed. I read an article that explains what you should do here.
Basically you need to register the child controls of the user control with the event validation engine.
C#
protected override void Render(HtmlTextWriter writer)
{
// Register controls for event validation
foreach (Control c in this.Controls)
{
this.Page.ClientScript.RegisterForEventValidation(
c.UniqueID.ToString()
);
}
base.Render(writer);
}
VB
Protected Overrides Sub Render(ByVal writer As System.Web.UI.HtmlTextWriter)
For Each aControl As Control In Me.Controls
Me.Page.ClientScript.RegisterForEventValidation(aControl.UniqueID.ToString)
Next aControl
MyBase.Render(writer)
End Sub
Now this may not solve your problem because I am not sure how you are returning the string. Another article explains that the event validation is a combination of the control's unique ID and all the values it can pass back. If you are using the command value of a button to pass the text you may have trouble with this task.
If the event that is causing the issue is the click, then registering the controls of the user control should do the trick.
In my situation I was trying to update a GridViews datasource on page_load and I forgot to check if the request was a postback, thus my source was trying to change and it threw this error. Once I did the check for post back it worked fine. Hope this helps someone in the future.
if (Page.IsPostBack == false)
{
this.sqlObj = new SqlServer(ConfigurationManager.ConnectionStrings["PdfReceiverConnectionString"].ToString());
this.populateDataGrid();
}
You can leave it enabled then register your control for event validation. Add the following call in the PreRender or Render page life cycle then your control should work without having to turn off eventValidation:
Page.ClientScript.RegisterForEventValidation(this.UniqueID);
Check your HTML, nested from Tags will also cause that

Refresh Content In a .ascx File

Because .ascx files are naturally rendered early in the page life cycle, if I want to update information on a form that is outside the .ascx part, how do I refresh the .ascx file to reflect the changes, say on button click (the same one that saves info)?
For instance (pseudocode):
Sub Page_load
'user control is naturally rendered here'
End Sub
Sub Button_Click
SaveStuff()
ReRenderUserControl()
End Sub
If you're creating a user control that's being built based on data saved. What you can do is create a method that does that building and then call it within the page and user control (pseudocode):
UserControl:
protected Page_Load(object sender, EventArgs e)
{
BuildControlBasedOnData();
}
public BuildControlBasedOnData()
{
// Build the user control based on saved data
}
Calling Page:
Button_Click(object sender, EventArgs e)
{
UserControl1.BuildControlBasedOnData();
}
User Controls (.ascx files) are rendered during the containing page life cycle, just as you mention. When you do a button click postback on the containing page, upon the postback load the User Control will be completely rerendered. If you require the User Control to contain different information upon this load there are many things you can do:
In your User Control, go to a data store and return the information required during its load event or any other proper event (databind of a control, etc.)
In the Page Load of your containing
page, assign values to properties of
your UserControl that you use within
different aspects of the User
Control. These values could come
from a data store or querystring or
whatever.
There are plenty of other options as well. If you're talking about partial postbacks or AJAXy type stuff, then you'll probably have to use JavaScript to update the different parts of your User Control (unless you're using UpdatePanels, which you shouldn't because they are the devil).
I came up with what I believe to be an imperfect, yet useable solution. All I did was make the Page_Load function in my .ascx file Public and then called it after my info was saved. This took care of my problem.
If anyone has a more elegant solution, please let me know!

Creating custom server control to accept user input

I am trying to build a server control that, depending on a "QuestionTypeId" display either a text box, date picker or Yes-No radio buttons.
I have my control displaying how I want it to, but when the submit button is pressed on the form, the text box, date picker or radio buttons that were generated in the RenderContents method are all null.
I have attempted to store the generated controls in view state, that stopped them being null, but the user inputs were not being stored.
I will post code if it is needed. Just ask.
I think you need to create (and add) the controls in CreateChildControls. This will mean you'll need to store the value of the QuestionTypeId in either Viewstate or ControlState (I'd argue that ControlState is applicable in this case, as your control can't work without this value).
When you add controls dynamically, you need to make sure they are recreated before the viewstate is restored.
I haven't done this in a while, but from memory I think you should recreate your controls in the OnInit method. This happens before postback data has been loaded and before the controls have their values set from viewstate.
It may be worth doing some reading on the asp.net page lifecycle:
http://msdn.microsoft.com/en-us/library/ms178472.aspx
You can create a user control and use server controls for textbox, datepicker, radiobuttons.
If you create a cusom server control then you have to add the posted data to your control properties. You can do this at your control OnInit event:
MyProperty = Request.Form("myControl");
A simpler method would be to create all the controls at design time and make the controls invisible based on you requirements.
Example code:
protected void Page_Load(object sender, EventArgs e)
{
txtBox.Visible = QuestionTypeID == 1;
chkBox.Visible = QuestionTypeID == 2;
}
If you do use dynamic controls you should do as David pointed out, save the value QuestionTypeID in ViewState or ControlState and then create the control you want based on that value.
(the control needs to be created every time the page loads even on a post back and they cannot be created later in the page life cycle then the Page_Load method if you want their ViewState persisted and recovered)
Example code:
protected void Page_Load(object sender, EventArgs e)
{
var questionId = ViewState["QuestionTypeID"];
if(questionId == /* Value to create TextBox */) {
var txt = new TextBox { ID = "txt" };
placeHolder.Controls.Add(txt);
} else if(questionId == /* Value to create Calender */) {
var cal = new Calender { ID = "cal" };
placeHolder.Controls.Add(cal);
}
/* Once the controls are added they will be populated with the posted values */
}
P.S.
It's always a good idea with dynamic controls to specify the ID.
You can save the added controls to member variables and use them elsewhere (after they are assigned)
You can subscribe to their events and if the user posted a new value your method will be called
I have followed your advice and done the following:
1) Question Type is stored in view state in my server control.
2) on CreateChildControls now creates a new instance of my control and adds it to a place holder on the page.
My problem now is that things seem to fire in bit of an odd order:
1)On initial load of page, create child controls is fired and the RenderContents method of my server control fires.
2)A button is clicked to load a new contact, this triggers create child controls and RenderContents is fired.
3)details are entered and save is pressed, this triggers Create Child Controls but RenderContents is not triggered and a NullReferenceException is generated by trying to access my control to get the value out. (If i skip the code that access my controls, RenderContents is called and renders.
Seconly, another issue is that when I try an set a value (onDataBind) I try to access the Text box that has been generated in my server control and get another NullReferanceExeption
Thoughts?

Resources