Reputation: 3288
Is there a way to force methods to be accessible only during certain events during the page life cycle. For example, I have a extension to System.Web.UI.Page
that adds a PrependTitle
method.
I also have a masterpage that embeds another masterpage. The first masterpage sets the base title (Google), the next masterpage prepends the title (Calendar), and a page also prepends the title (21 May 2011).
The result should be:
21 May 2011 :: Calendar :: Google
And this is the case when the PrependTitle
is run during the Page_Init
event. However, when the method is run during Page_Load
the following the results:
Google
So, that brings me to the question: How can it be enforced that a method only be accessible during specified life cycle events?
// The Method Mentioned
public static class PageExtensions
{
public static void PrependTitle(this Page page, string newTitle)
{
page.Title = newTitle + " " + Global.TITLE_DELIMITER + " " + page.Title;
}
}
Upvotes: 3
Views: 235
Reputation: 2968
If you want to brute force ensure that the method is being called from Init, you can inspect the call stack. Something like this:
public static bool CalledFromInit()
{
//Grab the current Stack Trace and loop through each frame
foreach(var callFrame in new StackTrace().GetFrames())
{
//Get the method in which the frame is executing
var method = callFrame.GetMethod();
//Check if the method is Control.OnInit (or any other method you want to test for)
if(method.DeclaringType == typeof(Control) && method.Name == "OnInit")
//If so, return right away
return true;
}
//Otherwise, we didn't find the method in the callstack
return false;
}
Then you would use it like:
public static void PrependTitle(this Page page, string newTitle)
{
//If we aren't called from Init, do something
if (!CalledFromInit())
{
//We could either return to silently ignore the problem
return;
//Or we could throw an exception to let the developer know they
// did something wrong
throw new ApplicationException("Invalid call to PrependTitle");
}
//Do the normally processing
page.Title = newTitle + " " + Global.TITLE_DELIMITER + " " + page.Title;
}
However, I'd caution that the stack trace isn't the most reliable thing. In release, code could get optimized such that the Control.OnInit method is inlined so your code wouldn't be able to see it in the call stack. You could wrap this check in an #if DEBUG
block so it only executes during development. Depending on your use case, it might be good enough to catch this problem while in DEBUG and not bother doing the check in RELEASE. But that's up to you.
Another option...building on Tommy Hinrichs answer, if all your pages inherit from a base class, you'll be able to do it a bit more reliably. I'd suggest something like this:
public abstract class BasePage : Page
{
private bool _executingInit;
protected internal override void OnPreInit(EventArgs e)
{
_executingInit = true;
base.OnPreInit(e);
}
protected internal override void OnInitComplete(EventArgs e)
{
base.OnInitComplete(e);
_executingInit = true;
}
public void PrependTitle(string newTitle)
{
if (!_executingInit)
throw new ApplicationException("Invalid call to PrependTitle.");
Title = newTitle + " " + Global.TITLE_DELIMITER + " " + Title;
}
}
That way, PrependTitle will throw an exception unless it's called between PreInit and InitComplete (which sounds like exactly what you want).
As one last option, you could be sneaky and use reflection to access the Control.ControlState
property (which is a confusing name because it's not related to Control State - the thing similar to View State). That property tracks the Control as it goes throw its lifecycle - and it has the following values:
internal enum ControlState
{
Constructed,
FrameworkInitialized,
ChildrenInitialized,
Initialized,
ViewStateLoaded,
Loaded,
PreRendered
}
You'll notice that Enum is internal. So is the Control.ControlState property. But with Reflection, you could use that - and you could even use it from an extension method that is external to the Page.
Hope one of those ways will work for you!
Upvotes: 1
Reputation: 121
I think this can be done similar to the following. The general idea is declare the method as private, declare the ones that should have access to it as sealed
class AppsBasePage : Page
{
abstract void PrependTitle(string title);
}
class PageWithTitlePrepended : AppsBasePage
{
private void PrependTitle(string title)
{
Title = String.Format("{0} {1} {2}", newTitle, Global.TITLE_DELIMITER, Title);
}
protected sealed override void Page_Init(object sender, EventArgs e)
{
PrependTitle("This is a title")
}
}
class ActualPageInApp: PageWithTitlePrepended
{
override void Page_Load(object s, EventArgs e)
{
// can't access PrependTitle here
}
}
This solves your question in bold, but I'm not convinced this situation is what is causing your problem with PrependTitle specifically. I think more code / context would be needed to solve your actual problem
Upvotes: 2
Reputation: 2493
It seems that the issue is in the prependTitle method, it should append the text to the page title not replace it.
Just call the PrependTitle method in the page_load of each mashterpage and page and append the text to the title.
Upvotes: 0
Reputation: 6111
Your best bet is probably to use the Handles Keyword to attach the method to the event.
You might have to create a subclass of System.Web.UI.Page to ensure this is enforced.
Upvotes: 0