I looking for strategies others have taken for handling ASP.NET web forms with huge numbers of fields. For example, we have a single page that can have around 200 fields and 3 data entry grids in user controls, Now were looking to add even more. It seems to me that at some point the viewstate, or something, is going to break down. So I'm interested to hear how others have handled this level of fields.
MORE INFO BASED ON GOOD FEEDBACK BELOW: I'm thinking maybe changing my main form to more of a dashboard, and when the user wants to enter/edit a data section they get redirected to a new page entirely. When they're done it redirects back. We already have user controls for the 3 grids (totally different types of data). But User Controls I'm finding are nightmares as far as when they render, interactions with the "parent" etc.
I have a form with over 1500 form fields, no issues as of yet. You should be fine unless your server is resource anemic or you have extraordinary loads.
You should however take note of this massive gotcha that took me unawares:
http://support.microsoft.com/kb/2661403
By default, 1000 form controls is the max you can submit to your page. And there is no error thrown, the page will only accept the first 1000 items, and ignore the rest. Pretty awesome discovery in a production environment...
Fortunately you can override that default with this in your web.config:
<appSettings>
<add key="aspnet:MaxHttpCollectionKeys" value="10000" />
</appSettings>
I wouldn't recommend forms with that many controls, but it was at the insistence of the client :)
I looking for strategies others have taken for handling ASP.NET web
forms with huge numbers of fields.
I like to share "a trick" that I use on the rare case of a form with hundreds of fields. On post back I eliminate the fields that actually not change, or they have some default input. On post back I know the fields that I have eliminate and usually for the default action I do not need to do anything. Eg a not select check box, or an input field that have not change, etc....
That way, the actually post is significant smaller. For example on jQuery here is a simple code:
function cOnSubmit()
{
jQuery(".MyInputCss").each(function(index, domElem)
{
var me = jQuery(domElem);
// just an example - if the default have selected of no action, I eliminate it.
if(me.find("input[type='radio']:checked").val() == "-1")
{
// removing the name is not take part on the post back
me.find("input").removeAttr("name");
}
});
return true;
}
and on the form I call this function as:
<form .... onsubmit="return cOnSubmit();">
It seems to me that at some point the viewstate, or something, is
going to break down.
1) If you have a lot of control, you will end up with Operation is not valid due to the current state of the object.
It can easily be fixed by using MaxHttpCollectionKeys like Chris Hardie's suggested.
Note: a label server control is also counted as one control in addition to textbox server control.
2) The another problem I can think of will be large ViewState.
In order to solve this, you can either store ViewState in StateServer or SQL Server.
Update 12/20/2013:
Sorry, I forget to mention how to save ViewState to Session.
Since you already store SessionState is SQL Server, all you need is to inherit the aspx pages from this BasePage.
public class BasePage : Page
{
protected PageStatePersister _persister;
protected override PageStatePersister PageStatePersister
{
get { return _persister ?? (_persister=new SessionPageStatePersister(this));}
}
}
Related
I am working on a project which creates controls dynamically for a form in the page_load event, loads in their current values from the database and saves their values (using FindControl) when the user clicks the continue button.
When I added a control statically in the .aspx page and followed their same procedure of loading the value in the page load and saving it on the button press I found that the value would not save correctly. It seems that it wouldn't save because the click event fires after the page_load, so the page_load of the post back reverted the value and the user entered value was not saved.
The strange thing is that by changing the control to be dynamically created just as all the other controls on the page and keeping the loading and saving the same it now works. Even though the page load still creates the control with the old database value.
It seems like a very fundamental asp .net feature here but i'm just unclear as to what is going on. I suspect it is to do with the timing of creation and maybe when the view state kicks in.
Static page controls are created just like dynamic page controls. The difference might be coming in your Page_Load. Whenever you postback all the controls are created afresh which means they are created with their initial values. This happens because after creating the controls asp.net throws away the controls/objects.
So, when the request comes, the first thing that asp.net does it to recreate the controls by looking at their definitions (in the designer files). On each postback they are created and initialized again losing their state in the process.
But after creating the controls Asp.Net loads any viewstate that is sent along with the request which makes people think that the state is always saved at the server.
What might be happening is that either the viewstate is not enabled for your control (in case they are created in designer), in which case you may try using EnableViewState property to true of the control.
Or, when you're doing a Page_Load, you're forcefully re-initializing everything. And in process losing all the control data. If you could post the logic of Page_Load, it might get clarified.
Make sure that:
you are not setting the value again for the static control in Page_Load. The dynamic control are probably getting around it by grabbing the ViewState and form values at a different stage in the lifecycle.
The dynamic controls are added After the static control. Or at least they are added in a different container. Placement in the control's collection can affect the ViewState, although it doesn't look like your scenario / since what you mention seems to be more about the values in the current post.
The save is happening After the Page_Load in response to the corresponding event.
I've run into similar problems in the past (quite a few times actually), but what helped me the most is understanding the ASP.NET Page Lifecycle.
Microsoft has an article on it which describes it pretty well, but this post by Solomon Shaffer really cleared up everything.
I suggest reading them both and coming back with additional questions regarding to a particular state, when to load/save data etc..
Hope this helps.
Marko
Note that you may want to use Page.IsPostBack property to avoid reinitializing values on button clicks and other events.
private void Page_Load()
{
if (!this.IsPostBack)
{
// Assign values to the controls.
}
}
On my page I have n-userControls (same control) I need to communicate between them(to be more specific I need to pass one in value) .
I don't want to involve the hosting page for that.
The controls acts as "pagers" and interact with the paged data on the hostin page via events that hosting page is subscribed to.
So when user click on one of the pager and changes it's state, the other control should know about it and change itself accordingly.
I can not use VieState because viewstate is per control and so is the controlstate.
Can I use Session for that? (session is shared and there is only one value that i need to store)
Or maybe there is something better I can use? (no QueryString)
Personally there isn't an "easy" way to do this without doing it through the controlling page, or an event.
From what you are saying what I would envision would be something like this. Assuming two controls A and B that are your pager controls.
The containing page subscribes to the "PageSelectionChanged" event on both controls, in response to that event it updates the data, which you already have, AND it enumerates through all pager controls setting the "Current Page" value.
You already have event plumbing in place for communication from control -> page, use what you already have built.
Why Not Session?
I was asked in the comments if this would be better than session, and the answer is yes, for a number of reasons.
Session information, unless explicitly cleaned up exists for the duration of a users session (typically 20 minutes)
Becase of number 1, you would need to add items to the page, for if(!ispostback) to "clear" the session variables so that the user didn't start on a different page.
Future application growth, session information has to be moved out of process to SQL Server or otherwise to work in a web farm environment, for this I try to avoid it as well.
Using session stores this information in memory on the webserver, although small (4 bytes if integer) it can add up and is un-necessary
Depending on the nature of your updates, you cannot ensure control order with session alone to ensure that 1 control forces an update to all controls.
There are other solutions, the solution similar to the one posted above that does a recursive look at the page, but you have to be careful with that to ensure that you do not get into a looping/endless recursion situation, in addition, if you have a lot of controls on the page, it can add a lot of overhead to constantly loop through everything.
The container page has a property in viewstate that stores the state. When a user clicks on one of the pager, it raises an event that is handled by the container page. This then loops through the n user controls and calls a public method on those controls.
You can build a quick modified version of the Observer Pattern. I would suggest building a manger control on the pages. But if you don't want to modify the page, here is a quick solution.
You can create a static method that will notify all of the same type of controls. By Calling their Update Method. Feel free to pass what ever data you need.
protected void control_event(object sender, EventArgs e)
{
UpdateAllControls(page);
}
public static void UpdateAllControls(Control parent /* can be Page */)
{
foreach (Control c in parent.Controls)
{
if (c.GetType() == this.GetType())
((MyType)).Update()
if (c.HasControls())
controls = GetAllControls(controls, t, c);
}
}
I have a very big problem. I am making a CRM (Costumer Relationship Management) System in ASP.NET 3.5
I have based my entire project on DevExpress.com controls and the use of UpdatePanels.
Now one of my pages, which is a central page in the whole system, contains a very big amount of possibilities and therefore a big amount of UserControls.
Now my problem is that it's getting really really slow because of the fact that UpdatePanels are reposting the entire page and not only the update panel. We are talking sometime 3-4 seconds before a popup window appears :(
Is there any way I can refactor this entire system away from UpdatePanels without breaking my neck?
Are there anyway I can optimize my use of UpdatePanels?
The ViewState is also absolutely giant.
Any good ideas are welcome...
There's no way to get around posting the entire page using UpdatePanels. In lieu of redesigning the app here are a couple things I'd try:
Disable viewstate for any controls that don't need it
Set the UpdateMode="Conditional" for your user controls. This won't get around posting the entire page but it will cut down on rendering time a little. Only the content for the specific UpdatePanel will be updated in the browser.
Make sure your user controls have short IDs. The way ASP.NET webforms names controls in the html these IDs get repeated quite a bit if you have a lot of server controls. Same goes for naming master page placeholders. I once cut a large page to half the size by renaming user controls and placeholders.
Since you're a DevExpress user, you might consider taking a little time to learn their CallbackPanel which will allow you to do asynchronous processing without the overhead of the UpdatePanel.
Alternatively (someone please correct me if I'm wrong) but if all of the postbacks are asynchronous (i.e. in an UpdatePanel), wouldn't it be theoretically possible to disable ViewState for the entire page (in the Page directive) without negative consequences? You'd have to test it completely off course, but it's worth a shot.
You'll have to replace some of the postbacks contained in your update panels with real AJAX calls, i.e. send only the data that is required for the action to the server and get back only what's required to update the view, getting rid of the postback and the UpdatePanels.
(You'll notice my use of the terms 'action' and 'view' - yes, I am an MVC fan. The situation you are in is typical of the mess that is easily got into using WebForms and the ASP.NET AJAX controls.)
I must be missing something. Why is your updatepanel is reloading the entire page. The point of an updatepanel is to refresh only what is in that panel, isn't it? Thanks for the explanation. I guess we're talking about reposting the page and not redrawing the panel as I thought.
Try turning off ViewState, especially for grids.
What kind of control is most common on your page? Try replacing those with your own lightweight UserControl or Server Control that does not use ViewState or ControlState
For all Interested I want to add a solution on how to get rid of the Viewstate data on clientside. It does give the server an extra load but if you are in the same situation as me and have a lot of server power and need to take the load of the clientside this is nice.
Let all your pages Derive from BasePage.cs looking like this
public class BasePage : System.Web.UI.Page
{
protected override void SavePageStateToPersistenceMedium(object viewState)
{
string vsKey = String.Format("VIEWSTATE_{0}_{1}_{2}", base.Session.SessionID, Request.RawUrl, DateTime.Now);
Session.Add(vsKey, viewState);
ClientScript.RegisterHiddenField("__VIEWSTATE_KEY", vsKey);
}
protected override object LoadPageStateFromPersistenceMedium()
{
string vsKey = Request.Form["__VIEWSTATE_KEY"];
return Session[vsKey];
}
}
Now you have a key to the viewstate data session instead of the viewstate in your code...
Works like a charm for me on a website with 1000-1200 daily visitors as well.
my problem is i want to pass value of a label and hidden control which are resides in a datalist say in page1.aspx and i want to access these value in page2.aspx. How can i get the values to another page while clicking on submit button. Is there any better option we have without using sessions, server.transfer, request.querystrings etc. Pls. help. I am using master pages. Can we use previouspage? if so how. Suggest me a better solutions. Thanks in advance.
Yes, you can use PreviousPage, by way of a cross-page post.
First, assign the PostBackUrl property of an <asp:Button> control on the first page to point to the second one.
Next, set the PreviousPage directive on the second page:
<%# PreviousPageType VirtualPath="~/first.aspx" %>
When the second page receives a postback, get the data you need from the first page:
Page previous = Page.PreviousPage;
if (previous != null)
{
Label label = (Label)previous.FindControl("myLabel");
if (label != null)
{
string text = label.Text;
}
}
If you use a hidden input element, then it's value will be available via Request.Form, e.g.
HTML in Page1.aspx
<input type="hidden" name="country" value="Norway" />
C# in Page2.aspx
var country = Request.Form["country"]; // "Norway"
You've certainly eliminated a lot of the usual methods of persisting data between pages. Other than queryString parameters, there are also hidden form values (Adam's example) and cookies which are also accessible from the Request object. If the data is relatively small and not particularly sensitive, you could write it to a cookie on the user's machine.
Alternately, you'd need some sort of persistence mechanism to hold the data. You could write the information to a file on the web server. The obvious downside is that you need to have more complex logic to ensure that data from each user is kept separate, you'd essentially need to create your own session management logic.
The other option that comes to mind would be persisting the information to a database. Unless you need to store and manage a whole bunch of complex data, this really isn't a viable options in most cases. It also comes with most of the same pitfalls as the file storage mechanism.
Unless performance is a real issue, just go with Session.
Is it possible to save ViewState information, e.g. to session, so that when you navigate away from the page it is persisted somehow? Then when you return to that page you can reload the view state and the choices you've made are preserved.
Background
I have two pages, a report page, where you can pick data, do some filtering and sorting etc. and a chart-page, where the data you picked from the report page can be presented in different ways, with different choices for presentation.
If the user has tested different presentations, simply using the back-button could mean quite a few clicks before the user's back at the report page. I'd like a direct link to the report page.
Using QueryString to save control states is not an option.
I can't customize the ViewState storage for the whole application.
Yes, it's possible to store the Viewstate in something like a database. You just need to implement one of the viewstate providers. See here for an example using the SqlViewStateProvider.
Edit: Just re-read your post, and saw that you said you couldn't customize how the viewstate is stored for the whole application. If that's the case, you might want to look into storing it in a session. Scott Hanselman discusses that here.
Your link could automatically navigate back the required number of pages using JavaScript. Look at window.history, if you can count the number of pages forward you can navigate back that many.
The ViewState is already designed to persist the state of the user controls. If your user has made a selection and that selection is processed server side with a full page postback the new state of the controls will be saved in the ViewState (hidden input __VIEWSTATE).
If your report is using AJAX and partial page postbacks then you won't get the ViewState on the page anyway.
Just to clarify, the SQLViewstateProvider is NOT an application wide implementation. You have to create a class which inherits from the System.Web.UI.Page object and overrides the Save And Load viewstate methods of the Parent Page class. For each page that you want the viewstate to be saved on server side you then have to inherit from your newly created Page Template (Which in turn inherits and overrides the System.WEb.UI.Page class).
So it is applied on a per-page basis, not on an application-wide basis.
HEADS UP: Some controls might contain some client-side javascript code which may reference the viewstate on client-side (duh). If the viewstate is now stored on server-side you will get a null-reference exception (for instance, clicking a commandfield in a gridview). I'm working on a workaround for this problem, but unfortunately I do not have any concrete solution as of yet.
This is a bad idea, just use querystrings. I would be interested to know why they are not an option.