Why Master pages is not required to confirm the initialization completion ?
Although, Master Page can have controls and need initialization and like in content page, confirmation comes in Init-Complete event.
InitComplete is not fired when the child controls of a control finish initialization, but when all the controls on the page are done.
The MasterPage being a control itself, loaded in the page, cannot detect by itself when all the other controls have completed initialization.
If any control, including the master page needs to know when the page initialization is over, it could subscribe to the InitComplete event of the Page.
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
Page.InitComplete += Page_InitComplete;
}
void Page_InitComplete(object sender, EventArgs e)
{
//initialization complete
//take necessary action
}
Related
I'm a bit puzzled by the behavior of the default ASP.NET Authentication controls, by its lifecycle to be precise.
In my MasterPage, I added a LoginView Control which displays the nice [Login] or [Logout] links. When I am logged in and click on [logout], I set up the control to perform a redirection to the homepage of the application.
Internally, when a click on "logout" happens, a postback is triggered. The following steps happen (among others of course):
The page that fired the postback is reinitialized
The page that fired the postback is reloaded
The LoggingOut event is fired
The LoggedOut event is fired
The page that fired the postback is PreRendered
The redirection happens
The target page is loaded (LoggedOut.aspx in my case)
On most of the pages, this works fine. But some pages expect some data to be initialized for their rendering to happens correctly. When this loggout postback occurs, the data isn't correctly initialized, but the page is still PreRendered which leads to some... "unexpected behavior" >_<
My question is thus twofold:
Why does this rendering step happens since the page won't be displayed at all?
Is there a way to prevent the rendering to happen?
Thanks a lot.
Tim
PS: here's a small VS2010 sample project showing you the call sequence & page lifecycle if you want to try it out for yourself http://dl.dropbox.com/u/11764136/LoginTest.7z
There is a way to prevent the actual rendering of the page.
Stop processing the current request when you redirect the page. This can be done by giving a true parameter to the Response.Redirect method:
Response.Redirect("http://somewhere", true);
You can also do this manually by calling Response.Close();
Are you using if(!isPostBack) test to control what should be rendered/re-initialized and what shouldn't?
Venemo's answer gave me an idea that seems to be working.
Instead of relying on the LoginStatus component to perform the redirection, I registered the MasterPage hosting the LoginStatus components to the LoginStatus.LoggedOut event and fire the redirection "per hand" before the PreRender step can be called.
protected void Page_Load(object sender, EventArgs e)
{
MasterLoginStatus.LoggedOut += new EventHandler(OnUserLoggedOut);
}
private void OnUserLoggedOut(object sender, EventArgs e)
{
Response.Redirect("~/LoggedOut.aspx", true);
}
I was concerned the LoginStatus component might remain dirty by doing this but sofar I haven't found any issue with it e.g. "works until proven otherwise".
Remains the question of "why the component behaves like this" but I guess it was a design decision that will remain unanswered.
Edit: this works fine until you get the same problem for the "login" action. Haven't found a way around this one yet.
I've got a serious problem with a DevExpress control (The report DocumentMap) which sometimes requests the whole report, bypassing the caching mechanism, when the end-user clicks on the logout link on the LoginStatus control. I've tried a lot of approaches to stop dead the "logout" postback so that the report won't get generated (some reports took 5 minutes to render, so the logout action sometimes took that long). I think this is similar to your problem: you don't want to do any heavy processing if the user is logging out. So I've tried a different approach: why didn't I recognize that the postback is indeed a logging out postback? All my pages inherit from a base page, so I've set this code in the base page:
public bool IsLoggingOut { get; private set; }
protected override void OnPreInit(EventArgs e)
{
base.OnPreInit(e);
var eventTarget = Request.Params.Get("__EVENTTARGET");
IsLoggingOut = eventTarget != null && eventTarget.Contains("HeadLoginView$HeadLoginStatus");
}
Now all I need to do in my page is to surround any heavy processing with a test of !IsLoggingOut... You could even do you redirect to the LoggedOut page without having to handle any events, just like this:
protected override void OnLoad(EventArgs e)
{
if (IsLoggingOut)
Response.Redirect("~/LoggedOut.aspx", true);
}
Even if you prefer to use the event handler to do that redirect, being able to know that the postback is indeed due to a logout click is a nice thing!
In my case, I was having this problem with the LoginStatus control. I can't see why it is a useful design to post back and Render the page when the user has clicked "logout". Through some tests, I found that I had to let the page go through its entire lifecycle, so Reponse.End() and Response.Transfer() did not work.
My solution was to add event handlers for the LoginStatus LoggedOut event, and then override the Render() method in the master page to do nothing if the user has logged out. I actually had the LoginStatus nested inside a user control that was then in the master page, so I had to bubble the event.
In my user control containing the LoginStatus control, I added an event handler for the LoggedOut event. In the UserStatus.aspx file:
<asp:LoginStatus runat="server" ID="loginStatusDefault" OnLoggedOut="loginStatusDefault_LoggedOut" ... />
Then in the code-behind:
public event EventHandler LoggedOut;
protected void loginStatusDefault_LoggedOut(object sender, EventArgs e)
{
if (this.LoggedOut != null)
this.LoggedOut(sender, e);
}
Now in the master page default.master, I have already included the UserStatus control:
<c:userstatus ID="ctlUserStatus" runat="server" />
and in the code-behind:
protected void Page_Init(object sender, EventArgs e)
{
ctlUserStatus.LoggedOut += ctlUserStatus_LoggedOut;
...
}
bool IsLoggedOut { get; set; }
void ctlUserStatus_LoggedOut(object sender, EventArgs e)
{
IsLoggedOut = true;
}
protected override void Render(HtmlTextWriter writer)
{
if (!IsLoggedOut)
base.Render(writer);
}
For me, the page rendering is what was bombing out when the user clicked "logout", so this took care of the problem for all pages.
What I did was have the logout link or onloggingout control just redirect to another page, "Logout.aspx" which then handles the log out code. Works great actually.
protected void LoginStatus1_LoggingOut(object sender, EventArgs e)
{
Response.Redirect("~/Logout.aspx");
}
It is difficult to explain but here it goes
I have a custom control in my asp.net page, i have two files which i pass page by page to the control, the user does some editing on that file's page (loaded in the control) and when it reaches the end i want the control to some how let the page know that the end of the page has been reached, now load a new page in the control,
How should i achieve this and which is the best practice?
You can bubble up the event from user control to the parent.
ParentAddUser.aspx
<uc1:AddUser ID="AddUser1" runat="Server" OnUserCreated="AddUser1_UserCreated"></uc1:AddUser>
ParentAddUser.aspx.cs
protected void AddUser1_UserCreated(object sender, CommandEventArgs e)
{
// User was created successfully. Do Something here.
}
AddUser.ascx.cs
public event CommandEventHandler UserCreated;
protected void Button_Click(object sender, EventArgs e)
{
// User was created successfully. Bubble up the event to parent.
UserCreated(this, new CommandEventArgs("UserId", userId.ToString()));
}
I am puzzling over an event plumbing problem on my page. I have a single ASP.NET UserControl that monitors some browser-side events and raises them as UpdatePanel async-post-backs to the server. Let's call it EventPump. It includes some JavaScript and server-side controls to manage the events. It works great.
I have several other controls on the same page that don't know about each other but would like to subscribe to some of the events raised by the EventPump. Normally one control would subscribe to another's events by including that control in its markup and wiring the events. But in this case that would give me multiple instances of EventPump, which I don't want.
How can I have several UserControls subscribe to events of the common EventPump control?
A few additional options I can think of:
Code to interfaces, so the EventPump control implements IEventPump which has a various public events. The containing page implements IEventPumpContainer with one property called EventPump which allows all other user controls to register like so:
((IEventPumpContainer)Page).EventPump.MyEvent += MyEventHandler;
If the page is aware of the controls that need to subscribe you could have it call appropriate methods on the controls when events fire. For example:
EventPump.MyEvent += (s, e) => SomeControl.SomeMethod();
Alternatively and arguably better is to make the events first class citizens and have an event dispatcher that can be used to subscribe to and raise events. For example:
Events.Register<MyEvent>(e => TextBox.Text = "MyEvent Happened");
...
Events.Raise(new MyEvent());
A couple of options:
Put the EventPump on a master page and create a property that references it (by making the master page "strongly typed" through the #MasterType directive)
Add a static helper method that takes a Page instance as a parameter, and finds the EventPump instance on the page. Instead of using FindControl for this, which will be slow on large pages, you can simply register the control in Page.Items. This approach is best combined with code in the EventPump control that ensures that only one EventPump instance exists on the page.
Make an EventPumpProxy control in the style of ScriptManagerProxy. This is somewhat kludgy, but nice if you prefer to have declarative markup locally instead of a lot of code-behind. I don't know the exact implementation details, but you should be able to see them by disassembling AJAX framework sources.
Create event handlers in the user controls, and subscribe all of the events to the same handler on the page.
UserControl1:
public event EventHandler SomethingChanged;
protected void DropDownList1_SelectedIndexChanged(object sender, EventArgs e)
{
//do OnSelectedIndexChanged logic
if (this.SomethingChanged != null)
this.SomethingChanged(this, e);
}
protected void CheckBox1_CheckedChanged(object sender, EventArgs e)
{
//do OnCheckedChanged logic
if (this.SomethingChanged != null)
this.SomethingChanged(this, e);
}
UserControl2:
public event EventHandler SomethingElseChanged;
protected void TextBox1_TextChanged(object sender, EventArgs e)
{
//do OnTextChanged logic
if (this.SomethingElseChanged != null)
this.SomethingElseChanged(this, e);
}
protected void Button1_Click(object sender, EventArgs e)
{
//do OnClick logic
if (this.SomethingElseChanged != null)
this.SomethingElseChanged(this, e);
}
Page:
<uc:UserControl1 ID="UserControl1" runat="server" SomethingChanged="UserControl_Changed" ... />
<uc:UserControl2 ID="UserControl2" runat="server" SomethingElseChanged="UserControl_Changed" ... />
Code-behind:
protected void UserControl_Changed(object sender, EventArgs e)
{
Label1.Text = "Stuff has changed...";
}
EDIT
See this article for invoking events across user controls:
http://www.codeproject.com/KB/aspnet/EventsAcrossUCs.aspx
With AutoEventWireUp set to false, I would normally set a Page_Finalize like this:
Include(Self.LoadComplete, Self.Page_Finalize); //Delphi
LoadComplete however, doesn't seem to be available on a MasterPage and my Page_Finalize obviously doesn't get called.
What am I meant to do to free objects in the master page?
Thanks.
LoadComplete method is simply not a member of MasterPage, but of Page.
There are several reasons for this, including the fact that the orchestrator of page life cycle is Page class itself.
It has three event-related methods: PreLoad, Load, LoadComplete. During Load, the Load event of children controls is triggered.
While a master page contains (by means of layout) the contents of the page, the page contains the MasterPage by means of objects, since the Page is the IHttpHandler that responds to HTTP requests in ASP.NET.
Briefly, you cannot override (I don't know Delphi, is that some kind of override?) OnLoadComplete in MasterPage as it's not defined. Only OnLoad
A bit late, but adding a method to the Page.LoadComplete event from the master page seems to work.
Within the master page:
protected void Page_Load(object sender, EventArgs e) {
Page.LoadComplete += Page_LoadComplete;
}
protected void Page_LoadComplete(object sender, EventArgs e) {
// do stuff on LoadComplete in the master page
}
I have a control (.ascx) that sits on a page (.aspx). Within that control there's a asp.net update panel that encompasses everything for that control. When the control does a post back it automatically raises all the events from the control plus all the events from the page it sits on. This is causing significant performance problems.
Is there any way to stop the events from being raised that reside on the page that the control is sitting on?
Thanks
Check the ScriptManager.IsInAsyncPostBack Property.
Here is the sample code:
protected void Page_Load(object sender, EventArgs e)
{
if (Page.IsPostBack)
{
// get a reference to ScriptManager and check if we are in a partial postback
if (ScriptManager.GetCurrent(this.Page).IsInAsyncPostBack)
{
// partial (asynchronous) postback occured
// insert Ajax custom logic here
}
else
{
// regular full page postback occured
// custom logic accordingly
}
}
}