views:

224

answers:

2

My apologies in advance for posting such a lengthy question. Believe it or not, what you see here actually represents a fairly condensed version of the problem/code at hand. And while I would appreciate any pointers on a better or different approach, I would also very much like to get the bottom of this so that I can sleep at night :)

I came across a requirement to pass confirmation messages between distinct aspx pages. I opted against using a query string variable since query string values "are" sticky (i.e. they persist on all subsequent postbacks) and I didn't want to deal with adding a bunch of conditional logic around this.

Anyway, I came up with a very simple class that uses Session to associate notifications with specific URLs. I then hooked my master page Page_Load event to query this class for any notifications that should be displayed for the current URL. If it finds any, it dynamically loads a NotificationMessage user control and displays the message content.

Everything works as expected when trying to pass Notifications between different aspx pages. Predictably, things don't work when a content page attempts to add a notification to itself (i.e. "The data you entered is not valid, try again"). The reason is pretty clear: by the time a content page adds a Notification for itself, the Page_Load event of the master page has already fired, so it's too late in the page lifecycle to do any good. The relevant code is pasted below.

    public class MyMasterPage:MasterPage{

    protected void Page_Load(object sender, EventArgs e)
    {
        LoadNotifications(this.Request.Url.ToString());
    }


    private void LoadNotifications(string url)
    {
        //look for a notification
        Notification? notification = NotificationManager.Instance.RetrieveNotification(url);

        //there are no notifications, nothing to see here 
        if (!notification.HasValue)
        {
            return;
        }

        //there is a Notification for this url, so load it into a user control
        NotificationMessage notificationMessageControl = (NotificationMessage)LoadControl("~/App_UserControls/NotificationMessage.ascx");
        notificationMessageControl.ID = "notificationMessage";
        notificationMessageControl.Notification = notification;
        notificationMessageControl.Visible = true;

        //find the placeholder on the master page
        PlaceHolder placeHolder = (PlaceHolder)PageUtils.FindControlRecursive(this, "NotificationPlaceholder");

        if (placeHolder == null)
        {
            throw new ApplicationException("NotificationPlaceholder control not found.");
        }

        //insert into control
        placeHolder.Controls.Add(notificationMessageControl);
        placeHolder.Visible = true;

        //remove the notification so it doesn't show up next time
        NotificationManager.Instance.RemoveNotification(url);


    }

}

Given the lifecycles issued alluded to above, I modified the NotificationManager class so that it raises an event whenever a notification has been added for the current page. The master page intercepts that event, and if the Page_Load has already fired, it kicks off the LoadNotifications method all over again.

 //bind the event on the page constructor
    public MyMasterPage()
    {
        NotificationManager.Instance.NotificationAdded += this.NotificationAdded;

    }

    private void NotificationAdded(string forUrl)
    {
        if (_pageLoaded){
           LoadNotifications(forUrl);
        }
    }

Unfortunately, this doesn't work. I have stepped through this code numerous times, and despite the fact that the master page loads the NotificationMessage UserControl and adds it to the appropriate placeholder without incident, the final aspx HTML never includes the markup for that UserControl. I've put breakpoints inside the Page_Load of the UserControl and verified that they are indeed being hit during execution.

If I dynamically load the UserControl from inside the content page and bypass the Master page altogether, it renders without a hitch:

public partial class MyContentPage:Page
{

    public void DoSomethingCool(object sender, EventArgs e)
    {
        if (MyServiceLayer.Save(foo)==false){
            Notification notification = new Notification(NotificationType.Error, "We’re sorry, your document was not saved.");
            NotificationMessage notificationMessage = (NotificationMessage)LoadControl("~/App_UserControls/NotificationMessage.ascx");
            notificationMessage.Notification = notification;
            notificationMessage.Visible = true;
            PlaceHolder holder = (PlaceHolder)PageUtils.FindControlRecursive(this, "NotificationPlaceholder");
            holder.Controls.Add(notificationMessage);
        }
    }
}

For the record, I stripped out the dynamic loading of the UserControl, opting instead for a a static declaration in the master page markup and a code based toggle of the control's Visible property; still no dice!

If someone could shed some light on this conundrum, I would be much obliged.

+1  A: 

I've tried this sort of thing before and I was never fully comfortable with it. What I did instead, was put my ASCX on every page (or on the masterpage), and let the ASCX control its state rather than letting the ASPX control my ASCX.

I'm not sure this will help for your situation, though.

JMP
+1 for good design
David Stratton
Sounds intriguing. Could you elaborate with an example perhaps?
Dirk
A: 

It seems like you want your information to show up after your control events fire. You might consider letting those messages aggregate until all control events have fired and then pull out all of the messages from your NotificationManager in OnPreRender, rather than Page_Load. That way you can get rid of the events (like NotificationAdded), etc that are probably complicating matters.

Not 100% what the problem is, though. It sometimes helps to know that the MasterPage is actually a control on the Page, rather than the other way around like you would think. It is going to be subject to the limitations any control would have on a Page.

HTH, Anderson

Anderson Imes