i have a form that is generated dynamically.
the plan is to generate it, the user to enter data, and then to save all that lot away.
although a slight variation to this is if the form has previous data associated with it, and then it loads in all pre-populated. - the user may then change any previous selections.
and that is the rub really, i know if i call generateform regardless of postback the viewstate should take over and remember what the settings weer.. but as the generateform method as mentioned above populates the form if the form has previously been saved. which will win, the viewstate or the generateform method for the field populations.. ?
thanks
nat
If you dynamically generate any form controls that post data or cause a postback, you need to recreate them again on postback in order for them to be bound to their data, or their events, after the postback. Conceptually, this makes sense. If you don't have a control in your form after the postback, how could you look at its contents?
There are several ways you could approach this problem.
1) Call GenerateForm() no matter what. Since you said it pre-populates some of the data, you would need to change it so it can be called without doing that. ASP.NET will populate the controls with the data posted automatically on postback, which is what you want.
2) Keep a list of all your dynamically generated controls in a ViewState variable, so you can re-generate them upon postback. For most situations involving dynamically-created controls that aren't very simple (e.g., you may not know in advance exactly what controls are generated), this is the best solution. And often you will want to be able to access the data after a postback, but maybe you really don't want to recreate the whole form because you aren't using it any more.
As long as you recreate a control of the same type and ID on or before Page_Load(), it will be bound to the posted data. It does not need to be in exactly the same place on your form. And it does not need to be used or displayed, either - you can destroy it before the form is rendered, e.g., in Page_PreRender()
3) If you have no interest in any of this, you can always use Request.Form to look directly at the posted data, though this can also be tricky because the names will likely not match your form control IDs exactly. ASP.NET generates unique client-side IDs that depend on the container, and this is what you'll find in Request.Form. If you don't regenerate a control, you may not be able to easily determine the ID that you are looking for. Generally, you should not do this, but it's a way to look at the posted data and sometimes you need it.
Related
I'm trying to build a very specific search page for a project and I'm having lot of trouble dealing with multiple postbacks invoked by dynamically-generated controls on a single page.
The page has to work like this:
There is a single checkbox, "Detailed search", that causes a postback on checking/unchecking.
When detailed search is not active, a simple grid with contents and buttons is displayed. Nothing special.
When detailed search is active, N checkboxes must be generated from some dynamic data, that represent the sections where you want the search to happen. Below the checkboxes, an AJAX-enabled tab control will appear, initially with no tab pages.
When checking one of the section checkboxes, a postback will occur. After the postback, data will be searched in the section selected by the user, then a new tab page containing a grid view of results and the name of the section will be added to the tab control. If the checkbox is unchecked, the tab page will disappear from the control, again, after a postback.
Now, the issue is that pretty much everything has to be generated dynamically, and that pretty much everything is connected to something else.
First issue: dealing with the "Detailed search" checkbox. Sounds easy, doesn't it? My initial idea was to set Page.Viewstate["DetailedSearchEnabled"] to true or false during the check/uncheck event handler, then create controls dynamically checking the value of DetailedSearchEnabled during Page_Load.
Nope. The postback event-handling happens between Page_Load and Page_LoadComplete. It would take an additional refresh for things to work as intended.
<< Then I'll just generate the controls on Page_LoadComplete! >>
Nope. Those controls need event handling as well, and if they're generated after Page_Load they will not be wired up correctly.
A possible solution would be generating everything in advance, on Page_Load, and only hiding/showing controls on Page_LoadComplete. But that is inefficient, and one important point of this search page is that only the minimum amount of controls should be generated.
The difficulty of this task seems to come from the way event wiring and the page life cycle work.
Surely there must be a better way of approaching this problem.
First issue: dealing with the "Detailed search" check box.
The correct approach (if you want to use page post-backs) is as follows:
In the CheckChanged event handler, save the value of the Checked property to ViewState["DetailedSearchEnabled"]. If the value is true, add the dynamic check boxes to the page. If the value is false, find and remove them.
Override LoadViewState. After calling base.LoadViewState, re-create the dynamic check boxes and wire up their events if ViewState["DetailedSearchEnabled"] is true. Note that neither Page_Load nor Page_LoadComplete is the appropriate place to do this.
Yes, you should create the dynamic check boxes at two points in the page life cycle. I recommend a helper method.
In general, your event handlers should add or remove just the dynamic controls (if any) affected by those particular events, but LoadViewState should re-create all dynamic controls that existed from the previous page request. You must store enough information in view state for LoadViewState to do so.
My answer to this other question demonstrates how to add and remove dynamic controls. You may want to use it as a reference.
Sounds to me like you should be using a CheckBoxList control to handle your dynamic checkboxes. You can add an remove items to the CheckBoxList during your post back and not have to worry about dynamically adding/removing actual controls/events to the form.
Here is a link to the msdn:
https://msdn.microsoft.com/en-us/library/14atsyf5(v=vs.85).aspx
Here is some sample code:
Protected void Button1_Click (object sender, System.EventArgs e)
{
CheckBoxList.Items.Add(new ListItem("TextValue1", "Value1"));
CheckBoxList.Items.Add(new ListItem("TextValue2", "Value2"));
}
If all else fails, you could still fall back on the quick-and-dirty old-fashioned ASP way.
Use Response.Write or <%...%> to generate your dynamic controls as plain old HTML (simple form fields, e.g. <input type="checkbox" name="foo" value="1" />).
Make sure you have a form field for every piece of information you may need after the postback(s). If necessary, use hidden form fields to 're-post' values across subsequent postbacks.
After postback, retrieve the values of the controls with the Request object.
Use those values to adjust the generation of controls as you see fit.
You should be able to do all of this in Page_Load. The advantage is total freedom. The disadvantage is total freedom to make a big mess of your aspx. So you may want to migrate all this dirty code out of your aspx, and into a custom-made control, which you can then add to your aspx.
When generating your own HTML, be careful not to introduce XSS vulnerabilities. Use HtmlEncode where necessary.
As you suggested yourself, there is a better way to tackle it.
If I was in the same situation, I would create web methods for interacting with the page, and use client side to do the UI. I'm currently working mostly with angular JS, although it does come with a learning curve. You could use ng-hide/ng-show to bind to the checkbox event to display the detailed search. When the n number of checkboxes needs to be displayed, you can then just fill them in with ng-repeat, for each of the items you need to display, after a check/uncheck you can dynamically populate new controls etc. through web method calls if extra data is needed.
Pure ASP postbacks are quite clunky from my experience, and not really suited for building a maintainable dynamic UI.
Instead of making so many postbacks, it would be better to use jquery and ajax calls to load the controls as needed and then attach events to it or you can even use UpdatePnael for that. Help Links:
https://www.simple-talk.com/dotnet/asp.net/ajax-basics-with-jquery-in-asp.net/
http://encosia.com/using-jquery-to-directly-call-aspnet-ajax-page-methods/
I am creating a custom control extending WebControl. This web control allows the consumer to define a collection of columns in markup, something like this:
<Custom:CustomGrid>
<Columns>
<Custom:DataColumn HeaderText="FirstName" />
<Custom:DataColumn HeaderText="LastName" />
</Columns>
and put an IEnumerable in a DataSource property and this is rendered out to a table.
This control also allows paging. The IEnumerable in DataSource is the full list, and I display a page of the list at a time. I am already saving the current page, number of rows per page, etc. to viewstate. Should I also put the full list in viewstate? Maybe session?
This list can become a bit hefty. Maybe save in session with a random key, which is saved in viewstate?
What is the best practice here?
Edit: I don't think it's right to impose that all types in the IEnumerable be serializable. Is that fair? So do I need to copy the data source to some other data structure for serialization?
Edit 2: Even if I do use a base control instead of implementing RenderChildControls I will need to implement CreateChildControls, but I will still need to persist the data somewhere, or did I miss the point of the base class?
Indeed, not all IEnumerable instances will be serializable.
If the query is cheap to run I wouldn't persist the whole data set but just run the query again for a different page or a change in the sort order.
If you put the data in viewstate you'll end up with huge pages. Session state might be acceptable if you don't have many users, but large data sets with lots of users won't scale well. What if I bind a million rows to your control? Or what if your control was used in a repeater and shown 100 times on a page?
Are you sure you need to persist the data? This isn't premature optimisation is it?
Remember that your control is a UI component. The viewstate should hold enough information to maintain the UI state as it is. A change in state (e.g.: switching to a different page of results) is something for which your control should pass responsibility to the data source.
Take a look at good old GridView. It displays what you give it and remembers that. If you're using paging then it raises an event to say "the user has changed page; give me page x of data". For me, that's the best practice for a UI control.
For implementing databound control it is better to use base class which was designed to perform such task. For example in ASP.NET exist CompositeDataboundControl which can be used as a base class to implement custom data bound controls. I can advice to review the following Dino Esposito article:
http://msdn.microsoft.com/en-us/library/aa479016.aspx.
Basically if you create control like ASP.NET gridview then it is store values in viewstate. To be more clear it is create number of the DataRow controls which saved assigned values in viewstate. During postback it recreates the same number of rows and values are restored from viewstate. If you will save only datasource for example in session without using viewstate then you will need to redatabind data to your grid during every postback. So, if you create Server control similar to gridview then approach described in the Dino Esposito post will be very helpful because it shows how to create control similar to ASP.NET Server GridView control.
This may be a basic question... However I've just been picking a solution without giving much though to it...
Consider a page with a grid of forms: /FormList.aspx
where choosing to edit a form redirects the user to a page like: /FormEdit.aspx?Id=2
I usually am okay with passing the values in the query string, because I check in the code-behind of FormEdit that the Id is valid. Which is the best way to pass the value, though: session or query string? Or other?
While the user is editing the form... I usually save the Id temporarily in session (to avoid getting it from the url again). During the user's form edition, what is the best way to store the value? In the session or in a hidden field ? Or other? (When I want to store a temporary DataTable, I believe I can only use the session, but when it's an integer value...)
Thanks in advance for your suggestions :)
I would suggest not to use Session for something that can easily and quickly be stored and checked in a query string or hidden form field; the information you seem to be talking about here is perfect for the query string.
Note that Session could introduce bugs when someone attempts to edit multiple forms at once in different tabs. When they save one, the Session value taken would be from the last one they loaded up to save... likely not what you will want, and it would likely be difficult to figure this out.
What you should do is persist the formID in the query string/form fields, and just double-check it for sanity when they submit the save; Make sure it's an ID that exists and that they are allowed to edit, for example.
Normally QueryString is a good choice if you are having very small data to trasfer between your pages.
2- Session would be a great when you want to store user specific data.
in you case , best would be the querystring as you are sending very small data (ID ) to the next page.
A few things to consider:
If manipulation of the value is a security risk you need to either save it on the server side, or secure the value client side(Which isn't easy to get correct). Or even better: recalulate them.
Saving in the session can easily create problems if the user has the website open in several tabs at the same time. It can also break the back button.
For example if you safe the ID of the item the user is currently editing in the session, and the user edits two entries at the same time the content of the one he started editing first might be written into the entry he started editing later.
My suggestion is to keep the value in a session variable, but when you run the page_load of FormEdit.aspx for first time, save the value in the ViewState of the page, and clear the session var.
Something like that (in page_load of FormEdit.aspx):
if (!IsPostBack)
{
ViewState["MyVar"] = Session["MyVar"];
Session.Remove("MyVar");
}
The problem with ASP.NET and query strings is that they persist on postbacks. That is, the form action for any page defaults to the url including the query string that was used to load it.
If the ID in question is just used to choose a particular form (and is not related to user data) this is not a big deal, in fact it's probably what you want.
On the other hand, if it identifies a record, you may not want this. Assuming you code things properly, this should not present a security risk, but it can give the user the impression that there is one by exposing the internal ID of a record in the query string. It also just looks unfortunate.
There are ways to work around this, and I think in 3.5 you can programatically change the form action. (It used to be you had to use javascript to do that - even though the form action was exposed it could not be changed).
The best way to deal with this is avoid query strings for any data record identifiers. Use POSTs to load data records instead, e.g. instead of using an asp:HyperLink control, use an asp:LinkButton control.
Of course, since POSTS load the same page they are sourced from, this requires that your forms be on the same page as your list. So, instead of having two separate pages, FormList.aspx and FormEdit.aspx, just put the code on the same page so you can post back directly. Presumably each of your forms is in a UserControl anyway. So your main page just has the job of choosing which usercontrol to show, everything else is delegated to the user controls, and all the parameter-passing can be done through posts inside your main page. This is a better architecture and keeps everything nice and clean.
I am writing a survey generating system in asp.net. i asked an ealier question about the best way to create controls that can be passed about as variables. This was problematic with user controls so i was advised to use custom controls and a quick way to do this was to inherit from the panel control and just add a bunch of standard controls the the controls collection by overriding the CreateChildControls method. This way i could create my "survey" controls,which are basically Questions in the survey. THe question controls are then dynamically added to the page. This all works well but know i have come to the point that i want to try and retrieve the values from these controls and i seem to be lost in a nether world of of viewstates and page lifecycles. I can ensure that the dynamically added text boxes have a known ID, however even if i add the parent control in the page init handler the CreateChildControls method does not run until after until after the viewstate is loaded. I cannot work out how to retreive the values from these text boxes.
You can call the EnsureChildControls method on the init handler of your control to ensure CreateChildControls is called before the ViewState is loaded.
You certainly seem to be doing this the hard way. TextBox values are not saved in the Viewstate, they are posted in the request.
Why aren't you using a UserControl here? So you can "pass" it somewhere? what exactly are you trying to do?
I have application full of various controls databound to my classes. I would like to ask user "You are closing application and you made some changes. Do you want to save your changes?". For this I need to recognize that user made any changes.
How to catch user made changes in databound controls? Is textBoxXXX_TextChanged the only way to do this?
Thanks in advance for all your answers.
It depends on the datasource; for example DataTable and DataSet sources contain the GetChanges() methods which allow you to easily see if rows have been added/removed/modified. Other data sources will have their own implementations, if any. If there is no implementation then it's up to you to determine how to check for those changes.
In any event this is something you should do at the data-level, not the UI (by watching for "changed" events). Watching events doesn't scale beyond a couple controls and maintenance can be iffy.
Update: Not sure why I didn't think of it, but a second option is to add a BindingSource to your UI object and use it as a databinding proxy (your UI controls databind to the BindingSource and the BindingSource binds to the real datasource). It provides a better approach than handling all your individual "Control_Changed" events, and requiring rework of your other layers (esp. if they aren't custom data-types).
You need to provide custom logic for that, there's not really an automatic way of doing this. As I see it there are several options:
At the start of the editing, save a copy of the original data object, and when you need to check, compare the current object with the saved one. The comparison can be custom (field by field) or semi-automatic by use of serialization (compare the serialized forms) - if it is serializable.
In each of your data object's property set accessors, test for a change in value and mark the object as 'dirty'.
As been discussed, there are many ways to do this depending on how granular you want to get.
A relatively easy way using client side javascript would be to do something like the following:
Hook into the onchange events of the form elements. You could do this dynamically on page load using javascript/DOM.
When the onchange error handler is called, you could set a page level variable: pageHasChanged = true;
Hook into the page's beforeonunload event (occurs when the user tries to navigate away from the page) and check the pageHasChanged variable to see if any changes were made. If changes were made you could alert the user.
This doesn't give you the detail of what changed, but would be fairly easy to modify to track which form elements changed.