I have a UserControl A that has to be loaded first and after that completes loading, I need to load a UserControl B.
I prefer to add both these user controls on the page at compile time (would like to avoid dynamic loading if possible).
If I add user controls on the page at compile time and set visible to false for User Control B, does it still execute the B's code behind? I can then set the visibility to true after loading User Control A
Should I be using events/delegates for notifying the completion of loading User Control A?
Don't load everything in the page event in control b, just put a method on control b to be called. Then add an event to control a which the page consumes, when the event is raised, call the load method on control b.
Edit: SampleCode
Ok so for example, create
a ASPX page
2x user controls
Put both user controls into the aspx page.
<cc:control1 runat="server" id="control_one" />
<cc:control2 runat="server" id="control_two" />
Then in control 1, create a delegate and event.
public delegate void MyCustomEvent (EventArgs args);
public event MyCustomEvent MyEvent;
protected void Page_Load(object sender, EventArgs e)
{
MyEvent(e);
}
So I have the event raised on page load. So you would have your logic in there thats required, when your done, calls MyEvent event.
In the page you want to add a handler for that event so when it's called you can do something with it.
protected override void OnInit(EventArgs e)
{
control_one.MyEvent += new WebUserControl1.MyCustomEvent(control_one_MyEvent);
base.OnInit(e);
}
void control_one_MyEvent(EventArgs args)
{
control_two.MyCustomLoad();
}
So when the page is initialized I add the event handler. In the event handler I call a custom method on the second control to load stuff.
Then in the second control I have:
public void MyCustomLoad()
{
//Stuff only loaded when event is raised and calls this method.
}
So this allows control 1 to load something, say it's done, when the page knows it's done, it can tell control 2 to do something.
Edit: After discussing this with a friend I'll explain what I mean by controlling the order.
You cannot control the order of page-life-cycle events. i.e: You can't have Control A, run through all it's page-life-cycle events, then once it's done, have Control B run through all it's page-life-cycle events.
If you do-away with the page life cycle, you can do a degree, as my example above shows, create a way of controlling the order in which the controls are rendered. By raising an event(s) at certain points when Control A is finished, you can tell Control B to do something.
The intermediate between the two controls is the page which handles the events raised by Control A which calls a method on Control B. You (well you can hack around to do it) can't specifically make Control A tell Control B to do something because that creates a direct dependency between the two controls which is bad.
Yes, the code behind will still run
Events could be useful
But if your controls have a specific dependency on each other, maybe they should just be a single control?
This is a fatally-flawed design. You should design your UI so that it doesn't matter in what order the controls load. The order in which controls load is outside of your control.
To address "Phill's" issue with an Order/Orderlines control pair:
I assume that the Order control was developed because it's useful by itself. I assume that OrderLines was developed to be able to show the line items for a given order displayed by the Order control.
I contend that there should be a single, composite control which combines Order and OrderLines. this control will pass to the OrderLines control, a DataSource consisting of the line items it is to display. This makes OrderLines independent of any other control - it simply displays the data it is told to display. It has no idea where that data came from.
Note that this can extend to a typical grid / detail / detail lines scenario, where you pass the grid a set of orders; when selected, a particular grid row will pass the Order control the selected order; when its' time to display the line items, pass the line items collection of the current order to the OrderLines control, etc.
This leaves each control with nothing to do but the Single job it is Responsible for.
"I have a UserControl A that has to be loaded first and after that completes loading, I need to load a UserControl B.
I prefer to add both these user controls on the page at compile time (would like to avoid dynamic loading if possible). "
I would suggest using WebFormsMVP: -
http://webformsmvp.com/
http://wiki.webformsmvp.com/index.php?title=Main_Page
As well as being a good implementation of Model-View-Presenter, one of the most useful features of this framework is its support for Messaging.
In a nutshell, you create Message Types, and your usercontrols (views) can post messages of whichever type you need to a message bus. Other controls can subscribe to messages of a particular type and the WebFormsMVP framework will ensure they are delivered.
This allows you to handle interaction between usercontrols by messaging publish & subscribe, without worrying about which order they load in.
Your only other option is to use dynamic control loading because, as others have pointed out, you can't rely on the order in which ASP.NET loads controls into a container.
Related
I have couple of user controls which are statically referenced in aspx. We are setting some public properties on the user controls in Page_Preinit event.
The page also references a master page
This was working fine so far. Had to do a ui redesign and we implemented nested master pages.
Now, all of a sudden, the user controls are showing up as null. If I change the master page to parent one (instead of child -nested), it works fine.
Appreciate any pointers to this issue.
some sample code: here ucAddress is null
protected void Page_PreInit(object sender, EventArgs e) { ucAddress.City = "Dallas"; }
This blog post describes the problem very well and also offers a solution. I can confirm that the solution works and I'll repeat the relevant parts here:
The problem
We have a user control and initialize its control in the Init event so that it is ready when the ViewState is restored (between Init and Load).
Once you start using this encapsulation technique, it won’t be long until you want to pass in a parameter that affects the data you load. Before we do, we need to be aware that the Init event is fired in reverse order. That is, the child controls have their Init event fired before that event is fired at the parent. As such, the Page.Init event is too late for us to set any properties on the controls.
The natural solution is to try and use the Page.PreInit event, however when you do you’ll often find that your control references are all null. This happens when your page is implemented using a master page, and it relates to how master pages are implemented. The <asp:ContentPlaceHolder /> controls in a master page use the ITemplate interface to build their contents. This content (child controls) is not usually prepared until the Init event is called, which means the control references are not available. For us, this represents a problem.
The Solution
The fix is remarkably simple; all we need to do is touch the Master property on our Page and it will cause the controls to become available. If we are using nested master pages, we need to touch each master page in the chain.
The author of the blog post then offers a nice little snippet that you can execute in the PreInit handler of your Page that uses a MasterPage and contains a user control:
protected override void OnPreInit(EventArgs e)
{
// Walk up the master page chain and tickle the getter on each one
MasterPage master = this.Master;
while( master != null ) master = master.Master;
// Access now initialized user control
ucAddress.City = "Dallas";
}
Found the issue. had to initialize child master page before accessing user control.
I'm currently working on a dynamic core for several webprojects. It has a core that uses a treeview and a menu. And then for each specific projekt it loads several different wuc into a maincontent. Some business projects use business related wucs while others uses different ones. So the span of wuc's is really big.
Now to my problem, whenever a user press a menuitem or a treeitem it loads a wuc to the maincontent linked to that object.
But I'm having some viewstate errors and i've been looking around for 2 days now and none of the solutions explained are working for my projekt.
All my wuc has to have viewstate enabled.
Cycle is ->
Page(Control A) does postback with variable to change control to ControlB in wucPanel(UpdatePanel).
OnLoad LoadRequested Wuc.
Current code is
protected void Load_Page(object sender, EventArgs e)
{
//Code to decide which wuc to load.
UserControl wucc = (UserControl)Page.LoadControl(sFilePath);
ParentControl.ContentTemplateContainer.Controls.Add(wucc);
}
I've tried several fixes like adding diffrent ids to the wuc, but this either disabels the internal functions of control like handlers etc or generates the same viewstate error.
One solution i found was to load ControlA and then just removing it and then load ControlB. But this disabled the scripts for my 3rd party controller (Telerik).
I've also read about having diffrent PlaceHolders for each typof but since i expect havign up to 50 diffrent Controls I don't feel this is gonna help me.
And moving from Page_Load -> Page_Init generated the same error.
Error:
Failed to load viewstate. The control tree into which viewstate is being loaded must match the control tree that was used to save viewstate during the previous request. For example, when adding controls dynamically, the controls added during a post-back must match the type and position of the controls added during the initial request.
In your case Anders, you still need to add the old control to your page in the init method along with the new control that you now want to add. Keep a reference to this old control that you have just added in a class level variable. So something like
Control _oldControl = null;
protected void Init_Page(object sender, EventArgs e)
{
//Code to decide which wuc to load.
UserControl wucc = (UserControl)Page.LoadControl(sFilePath);
ParentControl.ContentTemplateContainer.Controls.Add(wucc);
_oldControl = wucc as Control;
//Now add the new control here.
}
//override the LoadViewState method and remove the control from the control's collection once you page's viewstate has been loaded
protected override void LoadViewState(object savedState)
{
base.LoadViewState(savedState);
ParentControl.ContentTemplateContainer.Controls.Remove(_oldControl);
}
Hope this helps. If it did, please check the checkbox next to this answer to accept it and vote it up if you like :)
In order to avoid ViewState related errors please make absolutely sure that in Page_Init you create the same control tree that was created the previous time ViewState was saved i.e. the previous postback. Simple page life cycle:
Page Init - create the control tree
- View State is loaded and applied here
Page Load - already loaded view state, you can do modifications to the control tree here
- Save View State
Page PreRender
For what it’s worth I recently had the same problem.
My scenario was as follows.
A fixed panel of filters (dropdown lists and textboxes) which built a search SQL string. On submission of the search consequent results were displayed in an editable gridview beneath.
On editing the gridview I cold effectively change the state of a database record thus removing it from the gridview under the filters previously chosen. In some cases this resulted in no results being returned thus causing me to hide the gridview.
I then found that if I used the new state of the record in the filter and resubmitted the search that error sometimes occurred.
The problem I eventually found had nothing to do with enabled viewstates etc but simply that the empty gridview, though no longer visible (changed programmatically), had not been rebound to a null datasource.
This appeared to cause the conflict and the error.
So it appears as though in my case the viewstate issue arose from a non-visible gridview that contained non-refreshed data.
I'm creating user controls that i will put into an update panel and make them visible only when required using triggers. Once visible it will float in a dialog box so it has a close button which will just hide the control on client side.
The controls have multiple post back states, like a wizard, i'm using a multi view control to accomplish that. My problem is that once the user is at step number two in the control, if the user closes the dialog, than opens the dialog again, (note that the control is made visible on the server and reloaded by updating the updatepanel) the second step will still be displayed. The reason is . because whenever there is a postback the control will load its state using the viewstate, even if EnableViewState is false it will still load it using the LoadControlState method. so my quesion is how can i force a user control to load fresh with its default values without any postback data.
We had a similar circumstance and the only way we found to reset our control was to do that in the code behind of the page. Not in an ajax call but on submit of the page because then we could set the properties and have them go into viewstate.
i'm pretty sure that that will break your design though.
Have you considered writing the page as RESTful? At least then you can ditch viewstate and read and write to a meaningful data store.
ViewState is probably the number one reason I went over to MVC and now avoid WebForms like the plague.
If its a wizard that uses a dialog and each step is required, dont have a close button.
If the user closes it you could refresh the whole page so the user has to start again?
I had so many issue like this with WebForms, where I was only using the UpdatePanel for ajax as it "looks" like an easy option. MVC sounds like a bit of a learning curve and it is, however you can acheive things by building pages with MVC and jQuery without MS ajax and the hassle of all the events in a page that constantly fighting with each other. Its difficult to make this step without knowing MVC and geetting your hand dirty, but its worth it.
Its is possible.
I found a secret method in the control class, called ClearChildState(), this is a protected method and will clear the viewstate and the controlstate for all childcontrols.
So in my example, i created a class that inherits from panel
namespace MyControls
{
public class Panel:System.Web.UI.WebControls.Panel
{
public void Reset()
{
ClearChildState();
}
}
}
In my page initialize event i check for request[flag] to reset the control
public partial class Test : System.Web.UI.Page
{
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (!string.IsNullOrEmpty(Request["Reset"]) && Request["Reset"] == "1")
{
pnlCreateAccountDialog.Reset();
}
}
}
OnClient side i have a Reset() function that i can call whenever i want the next postback to load a clean control
<script type="text/javascript">
function AddReset() {
$('#Reset').val('1');
}
function RemoveReset() {
$('#Reset').val('');
}
</script>
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);
}
}
Is MasterPage -> Page -> UserControl the loading order of an ASP .NET request?
Is there a situation where an UserControl loads before the Page loads ?
I have private messages for my users and in every page they will see a message like this "You have 3 unread messages."
When users view an unread message I want to change the message to this "You have 2 unread messages."
This can be easily done with a Request.Redirect, but I want to avoid this.
message 1 -> click on it will goes to MarkAsRead.aspx?id=x wich redirects me to ViewMessage.aspx?id=x
Instead of doing this I want to mark the message as read from ViewMessage.aspx?id=x then decrease my Session variable "unreadMessages", then let my UserControl display the new number.
But my concern is that UserControls may not always load last.
The event progression in ASP.NET is as follows:
Page.OnPreInit()
UserControl.OnInit()
MasterPage.OnInit()
Page.OnInit()
Page.OnInitComplete()
Page.OnLoad()
MasterPage.OnLoad()
UserControl.OnLoad()
Page.OnLoadComplete()
(page event handlers fired here)
Page.OnPreRender()
MasterPage.OnPreRender()
UserControl.OnPreRender()
Page.OnPreRenderComplete()
Page.OnSaveStateComplete()
UserControl.OnUnload()
MasterPage.OnUnload()
Page.OnUnload()
To accomplish what your trying to do, handle your message count update in your Page.Init event, then display that count during the UserControl.Load event. I've provided the full progression of events above, which may provide you more options in case the above scenario isn't adequate. (For example, you could update the count in Page.Load, and render it in UserControl.PreRender, if for some reason Page.Init was too soon.)
check this diagram for asp.net event order.
my suggestion: implement a public void UpdateInfo() in your UserControl which is called from both the UserControl's OnLoad() event and from the OnLoad() event in ViewMessage.aspx after it adjusts the counter for unread messages.
I would agree with the above answer - it can become very tricky when you rely on child user control load events. Consider how all the MS databound controls work - you need to tell them to Bind before they will perform their function.