Porting old pages to use masterpages - asp.net

I have hundreds of legacy webform pages adding header and footer via a BasePage overriding Render
protected override void Render(HtmlTextWriter writer)
{
RenderHeader(writer);
base.Render(writer);
RenderFooter(writer);
}
New pages uses a MasterPage for the default behavior.
I would like to know if it's possible to add the asp:content control from the BasePage without changing every *.aspx?
I made a small test that's working as long there's no content in the aspx
public partial class OldPage : Page
{
private MainContentTemplate mainContentTemplate;
protected override void OnPreInit(EventArgs e)
{
Page.MasterPageFile = "~/Site.Master";
mainContentTemplate = new MainContentTemplate();
AddContentTemplate("Main", mainContentTemplate);
base.OnPreInit(e);
}
}
public class MainContentTemplate : ITemplate
{
#region ITemplate Members
void ITemplate.InstantiateIn(Control container)
{
container.Controls.Add(new LiteralControl("Test string"));
}
#endregion
}
But as soon as i add something to the code in front I will receive:
Content controls have to be top-level controls in a content page or a nested master page that references a master page.
I'm not sure but I think that overriding ControlCollection Controls might help, but I haven't find a solution.

This is no easy fix but I ended up with doing a massive search and replace in the old code base. It took a couple of days, but I think it was worth the effort since every page is now running against the same code base. It will always remind me that keeping the front end DRY is quite as important as taking care of the back end.

Related

Using CsQuery in MasterPage code-behind to modify HTML output?

What's the "skeleton" code for using CsQuery in the code-behind of a MasterPage in order to modify the HTML output? I need to be able to modify everything in the <body> of the HTML?
I'm hoping to use CsQuery to "touch-up" the HTML output of a Dynamic Data website without rewriting / messing with the default code.
Just looking for sample code specific to MasterPage code-behind? Thanks.
There is an example in the CsQuery project that shows how to do this (which I just made sure was working right!) in the CsQuery.WebFormsApp project.
The core of the usage looks like this. You must override the Render method in a class that inherits Page, and use this instead of Page as the base class for the codebehind in an aspx page:
public class CsQueryPage: System.Web.UI.Page
{
public CQ Doc { get; protected set; }
protected override void Render(System.Web.UI.HtmlTextWriter writer)
{
// most of the work is done for you with the
// `CsQuery.WebForms.CreateFromRender` method
var csqContext = WebForms.CreateFromRender(this, base.Render, writer);
// if you are using update panels, this lets you also manipulate that
// HTML, otherwise you don't need the IsAsync part
if (csqContext.IsAsync)
{
foreach (var item in csqContext.AsyncPostbackData) {
Cq_RenderUpdatePanel(item.Dom,item.ID);
}
}
else
{
Doc = csqContext.Dom;
Cq_Render();
}
// writes the altered content to the base HtmlTextWriter
csqContext.Render();
}
protected virtual void Cq_Render()
{ }
protected virtual void Cq_RenderUpdatePanel(CQ doc, string updatePanelId)
{ }
}
The two virtual methods are where you can alter the dom, which is populated in the Doc property of the CsQueryPage object - the intent of leaving them unimplemented here is that each aspx page that inherits CsQueryPage can optionally override them and make changes to the DOM.
To see how this works in practice just pull down the CsQuery code from github and run the example.
The same technique can be used for a UserControl which is also shown in the example. I don't actually show how to do it with MasterPage but it's very much the same-- MasterPage derives from UserControl, you just override it's Render method same as the other situations.

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.

Accessing a Master Page using custom base class causes Page not to render

I have a really weird problem using ASP.NET/C# 4.0 that I haven't been able to find an answer to. I have a custom base master page for my child master pages, and a custom base page class for my pages. My problem is that if I access the Master member from any of my pages, the page content itself doesn't get rendered, only the master pages.
So, to recap in a different way:
"Master Page" inherits System.Web.UI.MasterPage
"Nested Master" using "Master Page" inherits System.Web.UI.MasterPage
"WebForm" using "Nested Master", WebForm is inheriting CustomPageBase which inherits System.Web.UI.Page
When CustomBaseMaster accesses this.Master it will not render the webform's content, only the "Nested Master" and "Master Page" content. I have tried it with only reading a member, writing a member, and both reading and writing, and not even accessing a member, just calling Debug.WriteLine(this.Master)
If it helps any, I am using a Page Decorator on "WebForm" that needs to update the controls on the "Master Page"
Unfortunately, my google searching queries show up with results on how to access public members of the Master Page. But nothing I could find would help explain why, when accessing the master page, it results in the page's content not displaying.
Has anybody seen anything like that and can offer any suggestions? Google hasn't been much help, been searching for a couple of hours now.
Ok, turns out when using the Page decorator at the top of the class it assigns the properties of the page class before PreInit completes. I used ILSpy to look at what happens when you access the Master property of the page, if it is accessed before the Master page has been created (internally null) and before all of the PreInit methods are called, it will create a new master page and uses that content. Kind of annoying. Anyways, my solution was to not make the properties bubble up the values to the parent master pages on getting and setting them, but overloaded the OnLoad event in my base page and base master classes, and set the properties on the parents there.
So, instead of doing this:
namespace MyWebsite
{
public abstract class MyPageBase : Page
{
public int PropertyName
{
get
{
return ((MyMaster)this.Master).RatePageId;
}
set
{
((MyMaster)this.Master).RatePageId;
}
}
}
}
I did this:
namespace MyWebsite
{
public abstract class MyPageBase : Page
{
public int? PropertyName
{
get;
set;
}
protected override void OnInit(EventArgs e)
{
base.OnInit(e);
if (PropertyName != null)
{
((MyMaster)this.Master).PropertyName = PropertyName.Value;
}
}
}
}

Testing to see if ContentPlaceHolder content has been overridden by a child page?

I'm currently migrating a .net 1.1 application to .net 3.5.
The .net 1.1 application has a number of number of page + usercontrol's that I would like migrated to masterpages.
My issue is trying to test progmatically to see if the masterpage's contentplaceholders content has been overridden by a child page.
Is it possible?
Does anyone have samples or references that I could take a look at?
Thanks in advance.
A page can communicate with the master page but not vice versa since the content in the contentplaceholder does not belong to the master page. The quickest way of setting up a page "registering" itself to the master page is to declare a class that inherits from the .NET MasterPage and expose communication functionality in that class.
public abstract class MyMaster : System.Web.UI.MasterPage
{
public MyMaster() { }
public abstract void TellMeSomethingAboutTheContent(SomeArgs args);
}
Then in your page that uses the master you can do something like:
protected void Page_Load(object sender, EventArgs e)
{
MyMaster master = Page.Master as MyMaster;
if (master == null)
return;
master.TellMeSomethingAboutTheContent(args);
}
Assuming of course that you have a SomeArgs class that contains the data you want the master page to know about.

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