I have a Report.aspx page that loads different UserControls to view specific object data alternatively. The UserControls inherit the same base type class where override the specific operations methods. I use a specific Service class for each UserControl to get data which implements a specific interface with operations signatures.
I now use a factory pattern to return the actual UserControl type to the Report.aspx and load it into a PanelControl, then call the control method to fetch some data based on some arguments.
This UserControl should be loaded in every postback due to the dynamic nature of it, any other solution is accepted. On every postback though I don't need to load data from the BL which calls the DL. I try to find a solution to show to the BL that I don't need you to call for the data again because I'm just posting back for other issues (e.g. download a report file, print etc.). And I would like this to happen on the BL level, not the ASPX front end. So far I think that I should let BL somehow know this (PostBack or !PostBack). I guess I could just create a parameter true, false and pass the PostBack property.
Any ideas, architecture and best practices are so welcome.
why not wrap the logic to call the BL inside the if(!Page.IsPostback){....} ?
Can you elaborate your statement "On every postback though I don't need to load data from the BL which calls the DL."?? During each postback, user control needs data to show (even if it is same data as last postback) because usercontrol goes through same lifecycle as ASPNET webpage. How can you prevent that?
I have decided that a very nice solution is System.Runtime.Caching in .NET 4.0.
Works very nice for every layer you need to use it.
http://msdn.microsoft.com/en-us/library/dd985642
Related
I have a ScriptControl (requires ScriptManager) with JavaScript to handle client-side interactions and ICallbackEventHandler to communicate back and forth. Everything works perfectly with one or multiple instances of the control on a page. I placed the control inside a GridView with sorting and it still works. However, I place the GridView in an UpdatePanel and now whenever I sort I get the following error for each instance:
Sys.InvalidOperationException: Two components with the same id 'GridView_ctl02_MyControl' can't be added to the application.
Can someone point me in the right direction on how to solve this? I am assuming ScriptManager is not disposing of the old Sys.UI.Control objects before trying to $create() the new ones with the same ID. I thought the UpdatePanel/ScriptManager combination would automatically take care of disposing objects that would be replaced?
Edit: This page appears to support what I thought: http://msdn.microsoft.com/en-us/library/system.web.ui.scriptmanager.registerdispose.aspx
Use the RegisterDispose method to
register dispose scripts for controls
that are inside an UpdatePanel
control. During asynchronous
postbacks, UpdatePanel controls can be
updated, deleted, or created. When a
panel is updated or deleted, any
dispose scripts that are registered
for controls that are inside the
UpdatePanel are called. In typical
page development scenarios, you do not
have to call the RegisterDispose
method.
Just to double check I placed an alert("dispose " + this.element.id) inside my JavaScript dispose() function. Every single instance alerts dispose GridView_ctl02_MyControl, but afterwards I get the error that two components can't have the same name GridView_ctl02_MyControl. I'm at a loss...
When the page unloads, my component's dispose() method is called and Sys.Application.removeComponent() is also called. When the UpdatePanel reloads, only dispose() method is called. For now I have solved this by putting Sys.Application.removeComponent(this); inside the dispose(). I didn't find a shortcut such as $remove (similar to $create), implying you aren't expected to need this often.
This seems logical in that you can keep a component loaded even after its related DOM elements (if any) have been replaced by the UpdatePanel. This way you have more control over the component's life. I can't imagine a use case, but I'm sure you could come up with one.
If I am way off and there is a better approach, please let me know!
I haven't seen this implemented before in ASP.NET, but am thinking about a UI that would look and act like this:
Conceptual Overview
A TabControl is loaded and the first tab contains a grid
When a row is double-clicked, a new tab is created with the record detail
The content of the tab/record detail is created by a usercontrol
Many tabs could be created, and therefore many instances of the usercontrol will be created
I know ASP.NET will rename my (runat="server") ID's for me, and that I can use jQuery or ASP.NET server-side code to work with the ID's... My concerns are:
How can I ask ASP.NET to generate a unique ID for each Nth instance of my usercontrol (to be rendered in a placeholder)
How do I actually create that extra instance of the control?
What else do I need to keep in mind?
Since I don't want postbacks I'm considering basing my implementation off of ComponentArt's Callback Control, and using ASP.net usercontrols to achieve this effect. This will allow me to do most things that require a postback, but won't refresh all the elements on a page... just the section that contains the user control. That being said, I'm not tied to a particular implementation.
You should look into the Page.LoadControl method. It works nicely and as far as I remember you put placeholders on your page and load the controls into the PlaceHolders, that's how you control the ids.
One thing that doesn't work out so well with this approach is when your control raises events that your Page object has to handle. If your control is selfcontained however you shouldn't have a problem.
This might help you get started:
http://www.codeproject.com/KB/aspnet/LoadingUSerControl.aspx
Previously Called: How to deal with dynamically created controls under load in aspx
in response to a question below: the information required to determine which controls to restore is contained in a dedicated viewstate object.
I am dynamically creating controls in the codebehind page - these controls are all hooked up to click handlers so when a postback occurs I must re-create the previous set of controls, then clear the controls down and generate the new set of controls based on the previous click.
This is coded and working correctly under normal circumstances esentially as follows:
in Page_Load
if not postback generate default buttons
else if postback re-generate buttons that were shown on last page
in click_handler
Clear the dynamically generated buttons created in the Page_Load
generate new buttons based on the specific click being handled
however when the server comes under load we start getting problems:
With 5 users per second we start getting the exception:
Multiple controls with the same ID 'add0' were found. FindControl requires that controls have unique IDs.
With 100 users per second we start getting the exception:
The control collection cannot be modified during DataBind, Init, Load, PreRender or Unload phases.
Once this occurs all subsequent requests get the same error and IIS has to be re-started.
What could be causeing this and how can I avoid it? Do html requests possibly overwrite and interfere with each other when under load? do objects somehow hand around after a page unload in a manner that would allow the next page load to trip over them?
How are you storing information about the controls you need to restore? If you are using ViewState or ControlState, then I don't see how load could affect things. That's how any of the composite controls do things.
I will say that I saw your second error while using the Infragistics UltraWebGrid, and never was able to track it down. From the call stack, it appeared that EnsureChildControls was being called during the Load phase (or maybe LoadViewState).
A private static variable was being used to store a dictionary of names and table cells so that table cells would not get re-created during the page lifecycle.
The key point is that it was marked static - it should have been an instance variable - the end result being that under load when requests started backing up then multpile requests were sharing this static dictionary.
exactly what happened i'm not 100% sure - but under medium loads FindControl would find multiple controls of the same name, under very high loads it seems one request would try to modify a control (probably add to it) while it was in an invalid state from the other request.
End result - if you dont really know what your doing - prefer instance variable sto static variables.
Everything you have written seems to be correct and doable. Most likely this is an issue with your control generation code. Perhaps if you post some of that we can better find a solution.
I'm trying to wrap my head around asp.net. I have a background as a long time php developer, but I'm now facing the task of learning asp.net and I'm having some trouble with it. It might very well be because I'm trying to force the framework into something it is not intended for - so I'd like to learn how to do it "the right way". :-)
My problem is how to add controls to a page programmatically at runtime. As far as I can figure out you need to create the controls at page_init as they otherwise disappears at the next PostBack. But many times I'm facing the problem that I don't know which controls to add in page_init as it is dependent on values from at previous PostBack.
A simple scenario could be a form with a dropdown control added in the designer. The dropdown is set to AutoPostBack. When the PostBack occur I need to render one or more controls denepending on the selected value from the dropdown control and preferably have those controls act as if they had been added by the design (as in "when posted back, behave "properly").
Am I going down the wrong path here?
I agree with the other points made here "If you can get out of creating controls dynamically, then do so..." (by #Jesper Blad Jenson aka) but here is a trick I worked out with dynamically created controls in the past.
The problem becomes chicken and the egg. You need your ViewState to create the control tree and you need your control tree created to get at your ViewState. Well, that's almost correct. There is a way to get at your ViewState values just before the rest of the tree is populated. That is by overriding LoadViewState(...) and SaveViewState(...).
In SaveViewState store the control you wish to create:
protected override object SaveViewState()
{
object[] myState = new object[2];
myState[0] = base.SaveViewState();
myState[1] = controlPickerDropDown.SelectedValue;
return myState
}
When the framework calls your "LoadViewState" override you'll get back the exact object you returned from "SaveViewState":
protected override void LoadViewState(object savedState)
{
object[] myState = (object[])savedState;
// Here is the trick, use the value you saved here to create your control tree.
CreateControlBasedOnDropDownValue(myState[1]);
// Call the base method to ensure everything works correctly.
base.LoadViewState(myState[0]);
}
I've used this successfully to create ASP.Net pages where a DataSet was serialised to the ViewState to store changes to an entire grid of data allowing the user to make multiple edits with PostBacks and finally commit all their changes in a single "Save" operation.
You must add your control inside OnInit event and viewstate will be preserved. Don't use if(ispostback), because controls must be added every time, event in postback!
(De)Serialization of viewstate happens after OnInit and before OnLoad, so your viewstate persistence provider will see dynamically added controls if they are added in OnInit.
But in scenario you're describing, probably multiview or simple hide/show (visible property) will be better solution.
It's because in OnInit event, when you must read dropdown and add new controls, viewstate isn't read (deserialized) yet and you don't know what did user choose! (you can do request.form(), but that feels kinda wrong)
After having wrestled with this problem for at while I have come up with these groundrules which seems to work, but YMMV.
Use declarative controls whenever possible
Use databinding where possible
Understand how ViewState works
The Visibilty property can go a long way
If you must use add controls in an event handler use Aydsman's tip and recreate the controls in an overridden LoadViewState.
TRULY Understanding ViewState is a must-read.
Understanding Dynamic Controls By Example shows some techniques on how to use databinding instead of dynamic controls.
TRULY Understanding Dynamic Controls also clarifies techniques which can be used to avoid dynamic controls.
Hope this helps others with same problems.
If you truly need to use dynamic controls, the following should work:
In OnInit, recreate the exact same control hierarchy that was on the page when the previous request was fulfilled. (If this isn't the initial request, of course)
After OnInit, the framework will load the viewstate from the previous request and all your controls should be in a stable state now.
In OnLoad, remove the controls that are not required and add the necessary ones. You will also have to somehow save the current control tree at this point, to be used in the first step during the following request. You could use a session variable that dictates how the dynamic control tree was created. I even stored the whole Controls collection in the session once (put aside your pitchforks, it was just for a demo).
Re-adding the "stale" controls that you will not need and will be removed at OnLoad anyway seems a bit quirky, but Asp.Net was not really designed with dynamic control creation in mind. If the exact same control hierarchy is not preserved during viewstate loading, all kinds of hard-to find bugs begin lurking in the page, because states of older controls are loaded into newly added ones.
Read up on Asp.Net page life cycle and especially on how the viewstate works and it will become clear.
Edit: This is a very good article about how viewstate behaves and what you should consider while dealing with dynamic controls: <Link>
Well. If you can get out of creating controls dynamicly, then do so - otherwise, what i whould do is to use Page_Load instead of Page_Init, but instead of placing stuff inside the If Not IsPostBack, then set i just directly in the method.
Ah, that's the problem with the leaky abstraction of ASP.NET web forms.
Maybe you'll be interested to look at ASP.NET MVC, which was used for the creation of this stackoverflow.com web site? That should be an easier fit for you, coming from a PHP (thus, pedal-to-the-metal when it comes to HTML and Javascript) background.
I think the answer here is in the MultiView control, so that for example the dropdown switches between different views in the multi-view.
You can probably even data-bind the current view property of the multiview to the value of the dropdown!
The only correct answer was given by Aydsman. LoadViewState is the only place to add dynamic controls where their viewstate values will be restored when recreated and you can access the viewstate in order to determine which controls to add.
I ran across this in the book "Pro ASP.NET 3.5 in C# 2008" under the section Dynamic Control Creation:
If you need to re-create a control multiple times, you should perform the control creation in the Page.Load event handler. This has the additional benefit of allowing you to use view state with your dynamic control. Even though view state is normally restored before the Page.Load event, if you create a control in the handler for the Page.Load event, ASP.NET will apply any view state information that it has after the Page.Load event handler ends. This process is automatic.
I have not tested this, but you might look into it.
Im adding textboxes (not a fixed number of textboxes) dynamically to a form on ASP.NET page, how do i read back data from these textboxes?
Assuming you're wanting to access the controls on the postback you'd probably re-create the dynamic controls exactly as they were created on the initial load, then use the page's FindControls method to find the controls. It would probably help to create the textboxes with IDs like Textbox1, Textbox2, etc.
Look at Request.Params and extract them from there. You will, of course, have to give them ids to be able to tell them apart.
From all the ASP.NET apps I've worked with, .NET likes to use the following algorithm when generating the Id for server controls:
ctl00$cphBody$[ControlID]
Try using this algorithm when accessing your data from the dynamically generated textboxes.
When you add them you should be giving them names/ids, and you can use those to reference them.
If not, walk your DOM in javascript to find them inside the form you made - they'll be in the same order you inserted them.
Lastly, they're all available as post/get inputs to your page, so you should be able to look at them all as long as you assigned them different names.
-Adam
When creating textboxes dynamically (presumably using JavaScript, but same goes for ASP.NET controls) give them names in a specific pattern. The one you will be able to recognize later.
On server-side, in any event handler occurring after Page_Init you can iterate through Request.Form collection.
Do not be tempted to use Request.Param because it can be used to apply cross-site request forgery on your application (an attacker could lure user into issuing a GET request which your application would interpret the same as it would interpret a POST one, which is usually not a good thing).
If you are adding dynamic ASP.NET controls (in Page_Render for example) you can also reconstruct controls and use their properties.
You can use FindControl and pass the textbox ID to get an instance of the textbox when post back. The Text property contains the data, given that we are at page load stage or later in the cycle.
When adding dynamic controls, override the CreateChildControls method and add the dynamic controls to control hierarchy at this stage of the cycle.
Remember that in ASP.Net, every postback is a new instance of your class. If you created these controls during a previous postback or on the first view then they were garbage collected with the rest of that previous instance. So to use the controls in this new instance, you need to create them again. If you need the state information loaded for those controls (including any value entered by the user), you also need to create before the viewstate is loaded, meaning you do it during the Init event, rather than the load event.
To create dynamic controls, I would usually use a ASP.NET PlaceHolder Control and add the dynamic controls to this container.
I would give each dynamic control an ID.
You can then subsequently use FindControl on the PlaceHolder to access the dynamic controls.
I say "dynamic controls" to mean controls you add at run-time