How to dynamically change master page's master page? - asp.net

I am trying to change the master page dynamically, and although it's easy to do from a content page (overriding OnPreInit), there is no such event for a master page. Is it possible to introduce this event somehow?
UPDATE: I got halfway there by going via the PreInit of the pages at the bottom of the ladder, turns out you can do things like base.Master.MasterPageFile = "/master.Master";, but for some reason this doesn't load the stuff in the header of the top-most master page, namely stylesheets.

Quoting from: Can I change a nested master page's master dynamically?
Just tested this and it works from the PreInit of the Page that is using the nested MasterPage.
protected void Page_PreInit(object sender, EventArgs e)
{
this.Master.MasterPageFile = "/Site2.Master";
}
Obviously you will need to ensure that the ContentPlaceholderIds are consistent across the pages you are swapping between.

If you overrode the MasterPageClass and added your own onPreInit you might could do it, but I don't think even that would work. There's definitely no construct for it according to Reflector, nothing to even override, altho since it inherits UserControl then there's always OnInit ... alternately you could attempt to override get_Master() but that might not work either ...

Use the masterpage constructor.

Let's say you want to use a different master page without a menu, pass query string NoMenu.
protected void Page_PreInit(object sender, EventArgs e)
{
//You'll go through infinite loop if you do not check if we already have the new master page, this will switch to different master page if requested without a menu for example
if (Request.QueryString["NoMenu"] != null && this.MasterPageFile != "/MasterPageNoMenu.master")
{
this.MasterPageFile = "/MasterPageNoMenu.master";
base.OnPreInit(e);
}
}

Related

Page_Init vs Page_PreRender conflict with rendering LI and CSS

Hi and thanks in advance.
I have an issue where I show some links at the top of page (they are not breadcrumbs) and for different circumstances I need to turn some on/off and for the page the user is on highlight that one. When I originally put the page together the call to the function that displays the links was in the Page_Init like so in the master page:
protected new void Page_Init(object sender, EventArgs e)
{
SetTabs(session);
}
protected void SetTabs(session)
{
makeTab.add(session.param, session.param);
makeTab.generate();
}
protected void add(param, param)
{
Tabs.add(new tab() { link = param, name = param });
}
protected void generate()
{
foreach(tab t in Tabs)
{
liTab.Text = string.Format("<li>{1}", t.name, t.link);
}
}
The links that get created are an unordered list of items displayed inline.
And I have a separate method that is applying a style change to highlight the link that is associated with the page that the user is on that is called from the Page_Load of the page itself:
protected void Page_Load(object sender, EventArgs e)
{
Master.makeTabBlue(nameOfTab);
}
However, in order to make hidden tabs reappear correctly, I had to move SetTabs(session) to the Page_PreRender function so that they would be called after the page was saved, when session is updated but now that I have done that the function that makes the tabs blue no longer has an effect. I've read about the page life cycle and I think that the CSS that's rendered in makeTabBlue is only effective during the Page_PreInit or Page_Init cycles but if I move the makeTabBlue to either of those then the page starts throwing additional errors that the controls can't be found which makes sense because they aren't called until PreRender. And calling it in the save function has no effect either. Where should I be calling makeTabBlue from so that it works correctly?
UPDATE:
In this particular situation, the resolution to my issue was to leave the SetTabs in the Page_Init, leave makeTabBlue in the Page_Load and I added a unique class to the LI and I am hiding, showing the links via javascript by storing values in a hidden field and checking that field when the js loads.
The last time you can do something with the page is on the Page_LoadComplete() (the one before PreRender) method. Changes to the markup will still take effect if you do it here. This should allow you to run some code in your init then run stuff later on whilst still affecting the front end.
Another option would be to add a variable in the code behind:
protected string TabColour = "some default colour";
Then on the front end, do something like:
<div style='color:#<%= TabColour %>'></div>
Where the div example above is your tab.
This would allow you to set TabColour on the prerender event but still show the correct colour on the front end as it's evaluated in the Rendering event. I prefer this method as page life cycle can be tricky and this avoids being constrained by it.

extending ASP.NET Page using MasterPage attributes as properties

I have an authentication roles-based system: different roles are redirected to different folders and in each folder there is a web.config that allows the access only to a particular username.
Few roles have the default page in common with a gridview that react in different ways depending on the role(different columns are shown, events trigger different methods, etc.).
so my problem is that everytime I need to make minor changes to a page I need to copy/paste the same changes to all the others default pages in the other folders.
In terms of code I solved by creating a DefaultFather class which extends System.Web.UI.Page and every other Default class inherits from DefaultFather. In this way, if I dont declare a Page-life-method, the DefaultFather method will be triggered.
but what about the graphic part(html, javascript, asp components, etc...)??
I created a NestedMasterPage just for the Default pages but everytime I need to change the appearance/behaviour of controls(gridview, buttons, linkbuttons) I must use the FindControl() method.
there isnt really another way to solve this problem?
Im thinking of using the Page_Load() method to search for each control with FindControl() and save them into attributes for later usage but it doesnt really look like a good solution.
It would be nice if I could use the masterpage components as properties but I think that in order to do that I should create public properties and I dont know if it will cause some kind of security problem.
any suggestion?
btw, if masterpage is the solution, should I remove the DefaultFather class and place the code directly into the masterpage? or is it a good idea to have another class just for the code?
I'd say there's nothing wrong with having both a master page and a base class for your page. They serve different purposes. The master page is generally all about layout, and the base class would be about page functionality.
If you want to manipulate the markup on your master page, rather than accessing the fields directly, I'd say create a logical function which does what you need it to do, and let the master page do it.
// Site.Master.cs
public void HideSubmitButton()
{
btnSubmit.Visible = false;
}
// Default.aspx.cs
protected void Page_Load(object sender, EventArgs e)
{
((SiteMaster)Master).HideSubmitButton();
}
I'd probably wrap that cast so you can use it more easily - that is something that would belong in your base class:
// DefaultFather.cs
protected new SiteMaster Master { get { return (SiteMaster)base.Master; } }
// Default.aspx.cs
Master.HideSubmitButton();
EDIT
Per your comment about attaching event handlers - if you need to attach events to objects that live on the master (which may not be a good idea - ideally the event handler for something living on the master lives on the master - but if you really need it) you can expose methods to do that as well, like:
// Site.Master.cs
public void AttachEventHandlerToGoButton(EventHandler eventHandler)
{
btnGo.Click += eventHandler;
}
// Default.aspx.cs
Master.AttachEventHandlerToGoButton(DoMyThing);
private void DoMyThing(object sender, EventArgs e) { }
or if you want to get fancy, write a wrapper event:
// Site.Master
<asp:Button ID="btnGo" runat="server" OnClick="btnGo_Click" />
// Site.Master.cs
public event EventHandler GoButtonClick;
protected void btnGo_Click(object sender, EventArgs e) {
if (GoButtonClick != null) {
GoButtonClick(sender, e);
}
}
// Default.aspx.cs
Master.GoButtonClick += DoMyThing;
private void DoMyThing(object sender, EventArgs e) { }
Also see my edit on the Master wrapper - you need the base. there to avoid a stack overflow.

No LoadComplete for a MasterPage?

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
}

Setting a master page's master page programmatically

Is there anyway to set a Master Page's Master Page programmaticly? Would I need to do this on each page? I don't have access to the pre_init event from Master. Hrm...
In the PreInit function place:
this.MasterPageFile = "~/masterPage.master";
Yes, you will need to put this on each page. To get aroud it, have all the pages inherit from a base Page and place put the master page reference there.
void Page_PreInit(Object sender, EventArgs e)
{
this.MasterPageFile = "~/NewMaster.master";
}
Working with ASP.NET Master Pages Programmatically

Can I change a nested master page's master dynamically?

Okay, so we all know about changing a master page dynamically in a page's OnPreInit event.
But what about a nested master page? Can I change a master's master?
There is no OnPreInit event exposed in the MasterPage class.
Any ideas?
Just tested this and it works from the PreInit of the Page that is using the nested MasterPage.
protected void Page_PreInit(object sender, EventArgs e)
{
this.Master.MasterPageFile = "/Site2.Master";
}
Obviously you will need to ensure that the ContentPlaceholderIds are consistent across the pages you are swapping between.
We combine Andy's method with a "BasePage" class - we create a class that inherits from System.Web.UI.Page, and then all our pages inherit from this class.
Then, in our base page class, we can perform the relevant checks to see which root master page should be used - in our case we have a "Presentation" master, and an "Authoring" master - the presentation version has all the navigation and page furniture, along with heavy display CSS, while the authoring master has some extra JS for the authoring tools, lighter CSS, and no navigation (it's what we use when the user is actually authoring a page, rather than modifying the site layout).
This base page can then call Page.Master.MasterPageFile and set it to the Authoring master if that is the correct state for the page.
Just in case anyone stumbles across this and tears their hair out with a "Content controls have to be top-level controls in a content page or a nested master page that references a master page" error when trying Andy's code, get rid of the this.Master. So, the code becomes:
protected void Page_PreInit(object sender, EventArgs e)
{
MasterPageFile = "/Site2.Master";
}
Edit As Zhaph points out below, the code I have ^^ there will only change the current page's master, not the master's master. This is the code Hainesy was talking about when he mentioned "we all know about changing a master page dynamically" (which I didn't, d'oh). If you happen to get to this page by googling "stackoverflow change master page" (which is what I did) then this is possibly the code you're looking for :-)
To add on to the answer of Zhaph - Ben Duguid, (+1 by the way):
Here is example code that sets the master page of the nested master page. All pages inherit from this BasePage, so this code only exists in one place.
public class BasePage : System.Web.UI.Page
{
private void Page_PreInit(object sender, System.EventArgs e)
{
if (Request.Browser.IsMobileDevice)
{
if (Page.MasterPageFile == "~/master/nested.master"))
{
Page.Master.MasterPageFile = "~/master/mobile.master";
}
else
{
MasterPageFile = "~/master/mobile.master";
}
}
}
}

Resources