views:

1823

answers:

4

Calling a function from the MasterPage in a Page is quite straigt forward but how do I call it for a UserControl:

Adding <%@ MasterType VirtualPath="~/MasterPage.master" %>, doesn't work on UserControls.

So this.Page.Master.MyFunction() fails :(

+7  A: 

You have to cast the this.Page.Master first as the Master property of the Page is of type System.Web.UI.MasterPage.

e.g.

((MyMaster)this.Page.Master).MyFunction();

You could check the type of the underlying master page by adding a property to the code behind of the user control:

    public string MType
    {
        get { return this.Page.Master.GetType().FullName; }
    }

and print the result out in the User control markup, e.g. add this line to make it print out as a comment in the source code:

<!-- <%= MType %> //-->
JDunkerley
I get an error saying that it can't find type or namespace _Master??
Niels Bosma
you can try checking the type of the master page at RunTime. Will update the answer in a second
JDunkerley
The type is ASP.masterpage_master but is not available in compile time.
Niels Bosma
Do you have an Inherits entry on the <%@ Master %> tag in the mark up for the MasterPage? I just tried on my ASP.Net site and the code works fine.
JDunkerley
+4  A: 

You are couppling your code very thighly if you call a function on the masterpage from within your user control.

The Control can only be used on pages that are based on that master. I think this is usually a bad design, and it will violate at least the law of demeter.

What exactly do you want to accomplish in your control?

Heiko Hatzfeld
+1  A: 

Niels,

Leverage reflection (as suggested by JDunkerley) is one approach to the problem. Another you might consider is implementing an interface:

  1. Create an interface that includes your method.
  2. Implement the interface in your master page
  3. From your control, reference this.Page.Master via the interface type.
  4. Call your method.

This is a better OO approach, leads to less coupling, and will certainly perform better than runtime reflection.

I hope this helps!

Sean McDonough
I used this approach and it worked fine! Thanks
Niels Bosma
Glad to be of assistance! Happy coding :-)
Sean McDonough
+1  A: 

JDunkerley has it right. But allow me to explain how to decouple it using MVP so you can work toward avoiding the design issue Heiko Hatzfeld is talking about.

Basically, implement the MVP pattern for both your control and your master page. See here for instructions on how to do that. Declare the method you want to call in the master's interface (IMasterView). Next create a class that will control the relationship between the two components; we'll call it the PageController class. Place an instance of this class in request state for each request by adding the following line to global.asax.cs:

/* global.asax.cs */
protected void Application_BeginRequest(object sender, EventArgs e)
{
    // ...
    HttpContext.Current.Items["Controller"] = new PageController();
    // ...
}

You can then access this instance from each of the presenters (master and control) via the following line of code:

var controller = HttpContext.Current.Items["Controller"] as PageController;

You can then implement an event or some other mechanism to allow the control to invoke the method on the master in a decoupled manner through this shared object. For example:

/* PageController.cs */
public event EventHandler SomeEvent;

protected virtual void OnSomeEvent(EventArgs e)
{
    Debug.Assert(null != e);

    var handler = this.SomeEvent;
    if (null != handler)
        handler(this, e);
}

public void FireSomeEvent()
{
   this.OnSomeEvent(EventArgs.Empty);
}

/* ControlPresenter.cs */
public ControlPresenter(IControlView view)
    : base()
{
    view.EventFired += (sender, e) =>
    {
        var controller = HttpContext.Current.Items["Controller"] as PageController;
        controller.FireSomeEvent();
    };
}

/* MasterPresenter.cs */
public MasterPresenter (IMasterView view)
    : base()
{
    var controller = HttpContext.Current.Items["Controller"] as PageController;
    controller.SomeEvent += (sender, e) => view.MyFunction();
}

Make sure the "EventFired" event is declared in your control's interface (IControlView) and implemented in the control. Then all you have to do to affect the master (call its method), is fire this event and the MVP + the PageContoller will take care of the rest.

Cheers

Travis Heseman