roydukkey
roydukkey

Reputation: 3288

Force Method to Run During Event

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

Answers (4)

Stephen McDaniel
Stephen McDaniel

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

Tommy Hinrichs
Tommy Hinrichs

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

Khaled Musaied
Khaled Musaied

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

Frazell Thomas
Frazell Thomas

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

Related Questions