Click here to Skip to main content
Email Password   helpLost your password?

Table of Contents

Introduction

In the previous Mastering article (Mastering ASP.NET DataBinding), we took a detailed look at databinding - one of the most asked about topics in the newsgroups. Today, we continue the series by answering another very common question: how to maximize the communication between a page and its user controls. The actual questions asked typically don't include the words "maximize the communication" in them, but are more along the lines of:

The goal of this tutorial isn't only to answer these questions, but more importantly to build a foundation of understanding around these answers to truly make you a master of page-user control communication.

Understanding the Basics

Before we can answer the above questions, two basic concepts should be understood. As always, these basic concepts not only go beyond the scope of this tutorial, but by really understanding them, you'll be on your way to mastering ASP.NET. Both these concepts, and therefore the answers to the above questions, deal with object oriented principals. The use of solid object oriented methodologies is a reoccurring theme in developing solutions in ASP.NET, but we must be conscious that this can unfortunately be intimidating for some programmers. If you've read this far however, you're willing to do more than simply copy and paste an answer.

A page is a class

This is probably something you already know, but each code-behind file you create is actually compiled into a class. There's a good chance however that you haven't really been taking advantage of that knowledge. Before we get ahead of ourselves, let's look at the shell of a code-behind file:

   1:  //C# 

   2:  public class SamplePage : System.Web.UI.Page { 
   3:   private string title;
   4:   public string Title{
   5:     get { return title; }
   6:   }
   7:   ... 
   8:  }
   1:  'VB.NET

   2:  Public Class SamplePage
   3:   Inherits System.Web.UI.Page
   4:   Private _title As String
   5:   Public ReadOnly Property Title() As String
   6:    Get
   7:     Return _title
   8:    End Get
   9:   End Property
  10:   ...
  11:  End Class

As you can see, it's a class like any other - except that an ASP.NET page always inherits from System.Web.UI.Page. In reality though, there's nothing special about this class, it's just like any other. It's true that ASP.NET pages behave slightly differently from normal classes, for example Visual Studio .NET automatically generates some code for you called Web Form Designer generated code, and you typically use the OnInit or Page_Load events to place your initializing code - instead of a constructor. But these are difference for the ASP.NET framework; from your own point of view, you should treat pages like any other classes.

So what does that really mean? Well, as we'll see, when we start to look at specific answers, the System.Web.UI.Control class, which System.Web.UI.Page and System.Web.UI.UserControl both inherit from, exposes a Page property. This Page property is a reference to the instance of the current page the user is accessing. The reference is pretty useless to the actual page (since it's a reference to itself), but for a user control, it can be quite useful when properly used.

Inheritance

I originally wrote quite a bit about what inheritance was. However, from the start, it felt like the thousands of tutorials try to explain core OO principles with a couple of basic examples and simplified explanations. While inheritance isn't a complicated topic, there's something about trying to teach it so it doesn't seem cheap, which my writing skills just haven't reached yet. Ask Google about C# inheritance if you're really new to the topic.

Instead of talking in depth about inheritance, we'll briefly touch on what we need to know. We can clearly see in the above class shell that our SamplePage class inherits from System.Web.UI.Page (we can especially see this in the more verbose VB.NET example). This essentially means that our SamplePage class provides (at the very least) all the functionality provided by the System.Web.UI.Page class. This guarantees that an instance of SamplePage can always safely be treated as an instance of System.Web.UI.Page (or any classes it might inherit from). Of course, the opposite isn't always true; an instance of System.Web.UI.Page isn't necessarily an instance of SamplePage.

The truly important thing to understand is that our SamplePage extends the functionality of the System.Web.UI.Page by providing a read-only property named Title. The Title property however is only accessible from an instance of SamplePage and not System.Web.UI.Page. Since this is really the key concept, let's look at some examples:

   1:  //C#

   2:  public static void SampleFunction(System.Web.UI.Page page, 
                                         SamplePage samplePage) {
   3:   // IsPostBack property is a member of the Page class, 

   4:   // which all instances of SamplePage inherit

   5:   bool pb1 = page.IsPostBack;          //valid

   6:   bool pb2 = samplePage.IsPostBack;    //valid

   7:
   8:   // The ToString() method is a member

        // of the Object class, which instances

   9:   //of both the Page and SamplePage classes inherit

  10:   string name1 = page.ToString();             //valid

  11:   string name2 = samplePage.ToString();       //valid

  12:
  13:   //Title is specific to the SamplePage class, only it or classes

  14:   //which inherit from SamplePage have the Title property

  15:   string title1 = page.Title;        //invalid, won't compile

  16:   string title2 = samplePage.Title;  //valid

  17:   string title3 = ((SamplePage)page).Title;
  //valid, but might give a run-time error


  18:   string title4 = null;
  19:   if (page is SamplePage){
  20:    title4 = ((SamplePage)page).Title;
  21:   }else{
  22:    title4 = "unknown";
  23:   }
  24:  }
   1:  'VB.NET

   2:  Public Shared Sub SampleFunction(ByVal page As System.Web.UI.Page, _
                                        ByVal samplePage As SamplePage)
   3:   'IsPostBack property is a member of the Page class, which all instances

   4:   'of SamplePage inherit

   5:   Dim pb1 As Boolean = page.IsPostBack         'valid

   6:   Dim pb2 As Boolean = samplePage.IsPostBack   'valid

   7:
   8:   'The ToString() method is a member of the Object class, which instances

   9:   'of both the Page and SamplePage classes inherit

  10:   Dim name1 As String = page.ToString()         'valid

  11:   Dim name2 As String = samplePage.ToString()   'valid

  12:
  13:   'Title is specific to the SamplePage class, only it or classes

  14:   'which inherit from SamplePage have the Title property

  15:   Dim title1 As String = page.Title           'invalid, won't compile

  16:   Dim title2 As String = samplePage.Title     'valid

  17:   Dim title3 As String = CType(page, SamplePage).Title 
        'valid, but might give a run-time error


  18:   Dim title4 As String = Nothing
  19:   If TypeOf page Is SamplePage Then
  20:     title4 = CType(page, SamplePage).Title
  21:   Else
  22:     title4 = "unknown"
  23:   End If
  24:  End Sub

The first couple of cases are straightforward. First, we see how our SamplePage class inherits the IsPostBack property from System.Web.UI.Page [5,6]. We then see how both SamplePage and System.Web.UI.Page inherit the ToString() function from System.Object - which all objects in .NET inherit from. Things get more interesting when we play with the Title property. First, since the System.Web.UI.Page class doesn't have a Title property, the first example is totally invalid and thankfully won't even compile [15]. Of course, since our SamplePage class does define it, the second example is perfectly sane [16]. The third and fourth examples are really interesting. In order to get our code to compile, we can simply cast the page instance to the type of SamplePage which then allows us to access the Title property [17]. Of course, if page isn't actually an instance of SamplePage, this will generate an exception. The fourth example illustrates a much safer way to do this: by checking to see if page is an instance of SamplePage [19] and only if it is casting it [20].

To wrap up this [painful] section, the key point to understand is that when you create a new ASPX page, the page itself is a class, which inherits from System.Web.UI.Page. If you have access to an instance of System.Web.UI.Page and you know the actual type (for example, SamplePage), you can cast it to this type and then access its functionality - much like we were able to do with page and get the Title.

Basic Communication

We'll first discuss basic communication strategies between a page and its user controls in all directions. While this section alone will likely answer your questions, the important stuff comes in the following section where we discuss more advanced strategies. For the basic communication, we'll use a single page with two user controls and keep everything fairly simple. We'll use our sample page from above, and these two user controls:

   1:  'VB.NET - Results user control

   2:  Public Class Results
   3:  Inherits System.Web.UI.UserControl
   4:   Protected results As Repeater
   5:   Private info As DataTable
   6:
   7:   Public Property Info() As DataTable
   8:     Get
   9:       Return info
  10:     End Get
  11:     Set
  12:       info = value
  13:     End Set
  14:   End Property
  15:
  16:   Private Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
  17:     If Not Page.IsPostBack AndAlso Not (info Is Nothing) Then
  18:       results.DataSource = info
  19:       results.DataBind()
  20:     End If
  21:   End Sub
  22:  End Class
   1:  'VB.NET - ResultsHeader user control

   2:  Public Class ResultHeader
   3:  Inherits System.Web.UI.UserControl
   4:   Private Const headerTemplate As String = "Page {1} of {2}"
   5:   Protected header As Literal
   6:   Private currentPage As Integer
   7:   Private recordsPerPage As Integer
   8:
   9:   Public Property CurrentPage() As Integer
  10:     Get
  11:       Return currentPage
  12:     End Get
  13:     Set
  14:       currentPage = value
  15:     End Set
  16:   End Property
  17:
  18:   Public Property RecordsPerPage() As Integer
  19:     Get
  20:       Return recordsPerPage
  21:     End Get
  22:     Set
  23:       recordsPerPage = value
  24:     End Set
  25:   End Property
  26:
  27:   Private Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)
  28:     header.Text = headerTemplate
  29:     header.Text = header.Text.Replace("{1}", currentPage.ToString())
  30:     header.Text = header.Text.Replace("{2}", recordsPerPage.ToString())
  31:   End Sub
  32:  End Class

From Page to User Control

While communicating from a page to a user control isn't something frequently asked (because most people know how to do it), it nevertheless seems like the right place to start. When placing a user control on a page (i.e., via the @Control directive), passing values is pretty straightforward for simple types:

   1:  <%@ Register TagPrefix="Result" TagName="Header" Src="ResultHeader.ascx" %>
   2:  <%@ Register TagPrefix="Result" TagName="Results" Src="Results.ascx" %>
   3:  <HTML>
   4:   <body>    
   5:    <form id="Form1" method="post" runat="server">
   6:     <Result:Header id="rh" CurrentPage="1" RecordsPerPage="2" runat="server" />
   7:     <Result:Results id="rr" runat="server" />
   8:    </form>    
   9:   </body>
  10:  </HTML>

We can see that the CurrentPage and RecordsPerPage properties of our ResultHeader user control are assigned a value like any other HTML property [5]. However, since the Results user control's Info property is a more complex type, and must thus be set via code:

   1:  protected Results rr;
   2:  private void Page_Load(object sender, EventArgs e) {
   3:   if (!Page.IsPostBack){
   4:    rr.Info = SomeBusinessLayer.GetAllResults();
   5:   }
   6:  }

When loading a control dynamically, via Page.LoadControl, it's important to realize that an instance of System.Web.UI.Control is returned - not the actual class of the control loaded. Since we know the exact type, we simply need to cast it first:

   1:  //C#

   2:  Control c = Page.LoadControl("Results.ascx");
   3:  c.Info = SomeBusinessLayer.GetAllResults();
       //not valid, Info isn't a member of Control

   4:
   5:  Results r = (Results)Page.LoadControl("Results.ascx");
   6:  r.Info = SomeBusinessLayer.GetAllResults();  //valid
   1:  'VB.NET

   2:  dim c as Control = Page.LoadControl("Results.ascx")
   3:  c.Info = SomeBusinessLayer.GetAllResults()
       'not valid, Info isn't a member of Control

   4:
   5:  dim r as Results = ctype(Page.LoadControl("Results.ascx"), Results)
   6:  r.Info = SomeBusinessLayer.GetAllResults()  'valid

From User Control to Page

Communicating information from a user control to its containing page is not something you'll need to do often. There are timing issues associated with doing this, which tends to make an event-driven model more useful (I'll cover timing issues and using events to communicate, later in this tutorial). Since this provides a nice segue into the far more frequently asked user control to user control question, we'll throw timing issues to the wind and quickly examine it.

As I've already mentioned, pages and user controls eventually inherit from the System.Web.UI.Control class which exposes the Page property - a reference to the page being run. The Page property can be used by user controls to achieve most of the questions asked in this tutorial. For example, if our ResultHeader user control wanted to access our SamplePage's Title property, we simply need to:

   1:  //C#

   2:  string pageTitle = null;
   3:  if (Page is SamplePage){
   4:     pageTitle = ((SamplePage)Page).Title;
   5:  }else{
   6:     pageTitle = "unknown";
   7:  }
   1:  'VB.NET

   2:  Dim pageTitle As String = Nothing
   3:  If TypeOf (Page) Is SamplePage Then
   4:     pageTitle = CType(Page, SamplePage).Title
   5:  Else
   6:     pageTitle = "unknown"
   7:  End If

It's important to check that Page is actually of type SamplePage before trying to cast it [3], otherwise we'd risk of having a System.InvalidCastException thrown.

From User Control to User Control

User control to user control communication is an extension of what we've seen so far. Too often have I seen people trying to find ways to directly link the two user controls, as opposed to relying on common ground - the page. Here's the code-behind for SamplePage containing a Results and ResultHeader user controls:

   1:  Public Class SamplePage
   2:   Inherits System.Web.UI.Page
   3:   Private rr As Results
   4:   Private rh As ResultHeader
   5:   Private _title As String
   6:   Public ReadOnly Property Title() As String
   7:    Get
   8:     Return _title
   9:    End Get
  10:   End Property
  11:   Public ReadOnly Property Results() As Results
  12:    Get
  13:     Return rr
  14:    End Get
  15:   End Property
  16:   Public ReadOnly Property Header() As ResultHeader
  17:    Get
  18:     Return rh
  19:    End Get
  20:   End Property
  21:  ...
  22:  End Class

The code-behind looks like any other page, except a ReadOnly property for our two user controls has been added [11-15,16-20]. This allows a user control to access any other via the appropriate property. For example, if our ResultHeader wanted to make use of the Result's Info property, it could easily access it via:

   1:  //C#

   2:  private void Page_Load(object sender, EventArgs e) {
   3:   DataTable info;
   4:   if (Page is SamplePage){
   5:    info = ((SamplePage)Page).Results.Info;
   6:   }
   7:  }
   1:  'VB.NET

   2:  Private Sub Page_Load(ByVal sender As System.Object, _
               ByVal e As System.EventArgs) Handles MyBase.Load
   3:   Dim info As DataTable
   4:   If TypeOf (Page) Is SamplePage Then
   5:    info = CType(Page, SamplePage).Results.Info
   6:   End If
   7:  End Sub

This is identical to the code example above - where a user control accessed a page value. In reality, this is exactly what's happening, the ResultHeader is accessing the Results property of SamplePage and then going a level deeper and accessing its Info property.

There's no magic. We are using public properties in classes to achieve our goals. A page sets a user control's value via a property, or vice versa which can be done to any depth. Simply be aware that pages and user controls are actual classes you can program against; create the right public interface (properties and methods) and basic communication becomes rather bland (this isn't always a bad thing).

Accessing Methods

Methods are accessed the same way we've done properties. As long as they are marked Public, a page can easily access one of its user control's methods, or a user control can use the page as a broker to access another user control's method.

Advanced Communication

While the above section aimed at giving you the knowledge to implement a solution to [most of] the questions related to this tutorial, here we'll concentrate on more advanced topics with a strong focus on good design strategies.

Basic Communication is Bad Design?

While the code and methods discussed in the above sections will work, and are even at times the right approach, consider if they are truly the right approach for your situation. Why? you ask. Because if they aren't bad design as-is, they will lead to it unless you are vigilant. Take, for example, the last little blurb about accessing methods. If these are utility/common/static/shared methods, consider moving the function to your business layer instead.

Another example of bad design is the dependency such communication creates between specific pages and user controls. All of our example user controls above would either work very differently or cease to work entirely if they were used on a page other than SamplePage. User controls are meant to be reused, and for the most part (this isn't a 100% rule), shouldn't require other user controls or a specific page to work. The next two sections look at ways of improving this.

Making Use of Interfaces

We can leverage interfaces to reduce the dependency created by such communication. In the last example, the ResultHeader user control accessed the Info property of the Results user control. This is actually a pretty valid thing to do as it avoids having to re-hit the database in order to access the total number of records (although there are certainly alternatives to this approach). The problem with the above approach is that ResultHeader would only work with SamplePage and Results. Making good use of interfaces can actually make ResultHeader work for any page which displays a result (whatever that might be).

What is an interface? An interface is a contract which a class must fulfill. When you create a class and say that it implements a certain interface, you must (otherwise your code won't compile) create all the functions/properties/events/indexers defined in the interface. Much like you are guaranteed that a class which inherits from another will have all of the parent class' functionality, so too are you guaranteed that a class which implements an interface will have all of the interface's members defined. You can read Microsoft's definition, or this tutorial, but I think the couple example below will give you the exposure you need.

To get the most flexibility, we'll create two interfaces. The first will be used by pages which display results and will force them to expose a ReadOnly property which in turn exposes our other interface:

   1:  //C#

   2:  public interface IResultContainer{
   3:   IResult Result { get; }
   4:  }
   1:  'VB.NET

   2:  Public Interface IResultContainer
   3:   ReadOnly Property Result() As IResult
   4:  End Interface

The second interface, IResult, exposes a DataTable - the actual results:

   1:  //C#

   2:  public interface IResult { 
   3:   DataTable Info { get; }
   4:  }
   1:  'VB.Net

   2:  Public Interface IResult
   3:   ReadOnly Property Info() As DataTable
   4:  End Interface

If you are new to interfaces, notice how no implementation (no code) is actually provided. That's because classes which implement these interfaces must provide the code (as we'll soon see).

Next, we make SamplePage implement IResultContainer and implement the necessary code:

   1:  Public Class SamplePage
   2:   Inherits System.Web.UI.Page
   3:   Implements IResultContainer
   4:
   5:   Private rr As Results
   6:   Public ReadOnly Property Result() As IResult _
               Implements IResultContainer.Result
   7:    Get
   8:     Return rr
   9:    End Get
  10:   End Property
  11:  ...
  12:  End Class

The last step before we can make use of this is to make Results implement IResult:

   1:  public class Results : UserControl, IResult {
   2:   private DataTable info;
   3:   public DataTable Info { //Implements IResult.Info

   4:    get { return info; }
   5:   }
   6:  ...
   7:  }

With these changes in place, ResultHeader can now decouple itself from SamplePage and instead tie itself to the broader IResultContainer interface:

   1:  Dim info As DataTable
   2:  If TypeOf (Page) Is IResultContainer Then
   3:   info = CType(Page, IResultContainer).Result.Info
   4:  Else
   5:   Throw New Exception("ResultHeader user control must be used" & _ 
                            " on a page which implements IResultContainer")
   6:  End If

There's no denying that the code looks a lot as it did before. But instead of having to be placed on SamplePage, it can now be used with any page which implements IResultContainer. The use of IResult also decouples the page from the actual Results user control and instead allows it to make use of any user control which implements IResult.

All of this might seem like a lot of work in the name of good design. And if you have a simple site which will only display a single result, it might be overkill. But the minute you start to add different results, interfaces will pay off both in lower development time and, more importantly, by making your code easily readable and maintainable. And if you don't use interfaces to decouple your communication links, keep an open mind for where else you might be able to use them because you'll probably find a ton.

Event Driven Communication

One of the questions I haven't answered yet is how to make a page (or another user control) aware of an event which occurred in a user control. While it's possible to use the communication methods described above, creating your own events totally decouples the user control from the page. In other words, the user control raises the event and doesn't care who (if anyone) is listening. Besides, it's fun to do!

For our example, we'll create a third user control ResultPager which displays paging information for our results. Whenever one of the page numbers is clicked, our user control simply raises an event which the page, or other user controls, can catch and do what they will with it:

   1:  //C#

   2:  public class ResultPaging : UserControl {
   3:   private Repeater pager;
   4:   public event CommandEventHandler PageClick;
   5:
   6:   private void Page_Load(object sender, EventArgs e) {
   7:    //use the other communication methods to figure out how many pages

   8:    //there are and bind the result to our pager repeater

   9:   }
  10:
  11:   private void pager_ItemCommand(object source, 
                                       RepeaterCommandEventArgs e) {
  12:    if (PageClick != null){
  13:     string pageNumber = (string)e.CommandArgument;
  14:     CommandEventArgs args = new CommandEventArgs("PageClicked", 
                                                              pageNumber);
  15:     PageClick(this, args);
  16:    }
  17:   }
  18:  }
   1:  'VB.NET

   2:  Public Class ResultPaging
   3:   Inherits System.Web.UI.UserControl
   4:   Private pager As Repeater
   5:   Public Event PageClick As CommandEventHandler
   6:   Private Sub Page_Load(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles MyBase.Load
   7:    'use the other communication methods to figure out how many pages

   8:    'there are and bind the result to our pager repeater

   9:   End Sub
  10:
  11:   Private Sub pager_ItemCommand(ByVal source As Object, _
                            ByVal e As RepeaterCommandEventArgs)
  12:
  13:    Dim pageNumber As String = CStr(e.CommandArgument)
  14:    Dim args As New CommandEventArgs("PageClicked", pageNumber)
  15:    RaiseEvent PageClick(Me, args)
  16:
  17:   End Sub
  18:  End Class

With our PageClick event declared of type CommandEventHandler [5], we are able to notify anyone who's interested when a page number is clicked. The general idea behind the control is to load a Repeater with the page numbers, and to raise our PageClick event when an event fires within this Repeater. As such, the user control handles the Repeater's ItemCommand [11], retrieves the CommandArgument [13], repackages it into a CommandEventArgs [14], and finally raises the PageClick event [15]. The C# code must do a little extra work by making sure that PageClick isn't null [12] before trying to raise it, whereas VB.NET's RaiseEvent takes care of this (the event will be null/Nothing if no one is listening).

SamplePage can then take advantage of this by hooking into the PageClick event like any other:

   1:  //C#

   2:  protected ResultPaging rp;
   3:  private void Page_Load(object sender, EventArgs e) {
   4:   rp.PageClick += new 
           System.Web.UI.WebControls.CommandEventHandler(rp_PageClick);
   5:  }
   6:  private void rp_PageClick(object sender, 
               System.Web.UI.WebControls.CommandEventArgs e) {
   7:   //do something

   8:  }
   1:  'VB.Net WithEvents solution

   2:  Protected WithEvents rp As ResultPaging
   3:  Private Sub rp_PageClick(ByVal sender As Object, _
               ByVal e As CommandEventArgs) Handles rp.PageClick
   4:   'do something

   5:  End Sub
   1:  'VB.Net AddHandler solution

   2:  Private rp As ResultPaging
   3:  Private Sub Page_Load(ByVal sender As System.Object, _
               ByVal e As System.EventArgs) Handles MyBase.Load
   4:   AddHandler rp.PageClick, AddressOf rp_PageClick
   5:  End Sub
   6:  Private Sub rp_PageClick(ByVal sender As Object, _
               ByVal e As CommandEventArgs)
   7:   'do something

   8:  End Sub

More likely though, the Results user control would take advantage of this event through SamplePage, or better yet by expanding the IResultContainer interface.

Timing

One of the difficulties which arises from communicating between page and user control has to do with when events happen. For example, if Results where to try and access ResultHeader's RecordsPerPage property before it was set, you would get unexpected behavior. The best weapon against such difficulties is knowledge.

When loading controls declaratively (via the @Control directive), the Load event of the page will fire first, followed by the user controls in the order in which they are placed on the page.

Similarly, controls loaded programmatically (via Page.LoadControl) will have their Load event fired in the order that they are added to the control tree (not when the call to LoadControl is actually made). For example, given the following code:

   1:  Control c1 = Page.LoadControl("Results.ascx");
   2:  Control c2 = Page.LoadControl("ResultHeader.ascx");
   3:  Control c3 = Page.LoadControl("ResultPaging.ascx");
   4:  Page.Controls.Add(c2);
   5:  Page.Controls.Add(c1);

c2's Load event will fire first followed by c1's. c3's Load event will never fire because it isn't added to the control tree.

When both types of controls exist (declarative and programmatic), the same rules apply, except all declarative controls are loaded first, then the programmatic ones. This is even true if controls are programmatically loaded in Init instead of Load.

The same holds true for custom events as with built-in ones. In our event example above, the following is the order of execution when a page number is clicked (assuming no control is on the page except ResultPaging):

  1. SamplePage's OnLoad event.
  2. ResultPaging's OnLoad event.
  3. ResultPaging's pager_ItemCommand event handler.
  4. SamplePage's rp_PageClick event handler.

The real difficulties arise when dealing with programmatically created controls within events - such as adding a user control to the page when a button is clicked. The problem is that such things happen after the page loads the viewstate, which, depending on what you are doing, might cause you to miss events within your user controls or cause seemingly odd behavior. As always, there are workarounds to such things, but they are well outside the scope of this tutorial. One solution might be Denis Bauer's DynamicControlsPlaceholder control (I haven't tried it yet, but looks very promising).

Conclusion

It seems like good practice to conclude by visiting the key points, but really, the key points are to use what you can and try and understand as much as possible. Try to keep your designs clean, your pages flexible, and above all your code readable. Pages are classes and should be treated as such, namely by understanding how inheritance works with respect to casting/ctyping and public properties and methods.

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralNeed a little help to get user control to use sub on main page
Linda Techmeier
6:42 12 Feb '09  
This is my first interface attempt, and I'm very close (I think).
I've added the interface to the main page but can't get the user control to be able to use it - get not declared error. I just need to call a sub - no parms or anything...
Thanks for any help you can offer.

My setup:
Partial Class CustomerInfo
Inherits BasePage
Implements ILoadAddressControls

Public Interface ILoadAddressControls 'Used to allow a uc to call a page sub from it
Sub LoadAddressControls()
End Interface

Public Sub LoadAddressControls() Implements ILoadAddressControls.LoadAddressControls
'my code I want to run from UC here
End Sub

Then in the user control: in one of my subs
Protected Sub lvwCustomers_SelectedIndexChanging(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.ListViewSelectEventArgs) Handles lvwCustomers.SelectedIndexChanging

'****** this can't be found !!!!
'iLoadAddressControls()

'Dim pageTitle As String = Nothing
'If TypeOf (Page) Is customerInfo Then
' pageTitle = CType(Page, SamplePage).Title
'Else
' pageTitle = "unknown"
'End If
QuestionUserControl to UserControl Event Driven Communication
Member 2776710
9:18 12 Aug '08  
The Page instance contains a ResultPaging instance: rp.

What if I want the event to be handle by another UserControl whitout coding in the Page? Whoud I have to use Page.FindControl() in the Page_Load of the UserControl?


public partial class myUserControl : System.Web.UI.UserControl
{
protected void Page_Load(object sender, EventArgs e)
{
myOtherUserControl x = Page.FindControl( /* some reference */ );
if(x != null)
{
x.SomeEvent += myEventHandler;
}
}
protected void myEventHandler(object sender, EventArgs e)
{
// do something
}
}

GeneralExcellent Article
AWERT1232003
11:48 21 Feb '08  
Thak you very much!!!
GeneralGreat
srinath g nath
23:25 16 Dec '07  
Can you please expain with master page, page and user control. Does all these works with the master page also...
GeneralGreat
Rouh
4:17 27 Nov '07  
It's great....
Thank you.

Rouh Wa Raihan

QuestionCan you update for VS 2005
Matthew Hile
3:31 5 Oct '07  
I found this a really great article. However, as pointed out in one of the comments, not all of it works in VS 2005. I read that comment, and your response, but there was not enough information there for me to be able to get it to work.

What about an update of the whole article for VS 2005. Or a new section updating and showing how things are dealt with in the newer versions.

Thanks!

Matthew
QuestionEvent not firing
CC1967
11:57 25 Jul '07  
I'm just starting out w/ .NET and came across your article -- which is great, BTW -- and tried to apply your samples.   I think I'm missing something here because the method in the web form is not being triggered when the event is raised.   I'm using ASP.NET 2.0.

Basically, I have a button in a user control.   When clicked, a label is being set on the web form.

Hope you're still checking your messages...and thanks in advance!


WEBFORM.ASPX
============
<%@ Page Language="VB" AutoEventWireup="false" CodeFile="WebForm.aspx.vb" Inherits="WebForm" %>
<%@ Register tagprefix="_ucButton" Tagname="ucButton" src="~/WebUserControl.ascx"%>

<html>
<body>
      <form id="Form1" runat="server">
            <asp:Label ID="lblTest" runat="server"></asp:Label>
            <asp:PlaceHolder ID="plUserControl" Runat="server"></asp:PlaceHolder>
      </form>
</body>
</html>


WEBFORM.ASPX.VB
===============
Partial Class WebForm
      Inherits System.Web.UI.Page

      Protected WithEvents ucButtonClick As New WebUserControl

      Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
            Dim myButtonControl As New Control
            myButtonControl = CType(Page.LoadControl("WebUserControl.ascx"), Control)
            plUserControl.Controls.Add(myButtonControl)

            AddHandler ucButtonClick.ClickButton, AddressOf uc_ButtonClick
      End Sub

      Protected Sub uc_ButtonClick(ByVal sender As Object, ByVal e As CommandEventArgs) Handles ucButtonClick.ClickButton
            lblTest.Text = "The button was clicked!"
      End Sub
End Class


WEBUSERCONTROL.ASCX
===================
<%@ Control Language="VB" AutoEventWireup="false" CodeFile="WebUserControl.ascx.vb" Inherits="WebUserControl" %>

<asp:Button ID="btnClick" runat="server" Text="Click Me" />


WEBUSERCONTROL.ASCX.VB
======================
Public Class WebUserControl
      Inherits System.Web.UI.UserControl

      Public Event ClickButton As CommandEventHandler

      Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
      End Sub

      Protected Sub btnClick_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnClick.Click
            Dim args As New CommandEventArgs("ButtonClicked", "1")
            RaiseEvent ClickButton(Me, args)
      End Sub
End Class

Generalnewbie question
djscratch
13:57 13 Jan '07  
i enjoyed your article, but because im new to c# and NET i ran into some trouble. i tried to follow your code with radiobuttonlists instead of repeater. for some reason, i couldnt get it to work. please review my code and help if you are available.

here is my code. again, thanks for the help

page _________________________________________________

public class myRadio:Page
{
protected PlaceHolder plh_radioOne;
protected myRadioOne mROne = new myRadioOne();

void Page_Load(object s, EventArgs e)
{
mROne.myRadioChg += new myRadioOne.chgRadio(mROne_myRadioChg);

Control ctl = new Control();
ctl = LoadControl("myRadioOne.ascx");
plh_radioOne.Controls.Add(ctl);
}

void mROne_myRadioChg(object s, EventArgs e)
{
Response.Write("caught radio event");
//throw new Exception("The method or operation is not
implemented.");
}

}

usercontrol ___________________________________________
public class myRadioOne:UserControl
{
public delegate void chgRadio(object s, EventArgs e);
public event chgRadio myRadioChg;

public void chgValue(object s, EventArgs e)
{
RadioButtonList r = (RadioButtonList)s;
Response.Write("Selected Value : " + r.SelectedValue);

if(myRadioChg!=null)
{
myRadioChg(s, e);
}
}

}

again thanks for your help
GeneralRe: newbie question
djscratch
10:52 15 Jan '07  
with the help of an o'reilly book, i figure out what i was doing wrong. here's my attempt to explain the solution

Main ASPX file -
<%@ Page Language="C#" AutoEventWireup="true" Inherits="myRadio" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml" >
<head runat="server">
<title>Radio Button List</title>
</head>
<body>
<form id="form1" runat="server">

<asp:PlaceHolder ID="plh_radioOne" runat="server" />



<asp:Literal ID="lit_MyChange" runat="server" />


<asp:PlaceHolder ID="plh_Results" runat="server" />
</form>
</body>
</html>
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Main ASPX file code-behind file myRadio.cs
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

public class myRadio:Page
{
protected PlaceHolder plh_radioOne;
protected PlaceHolder plh_Results;
protected myRadioOne mROne = new myRadioOne();
protected Literal lit_MyChange;

void Page_Load(object s, EventArgs e)
{
myRadioOne r = (myRadioOne)Page.LoadControl("myRadioOne.ascx");
plh_radioOne.Controls.Add(r);

r.myRadioChg += new myRadioOne.chgRadio(r_myRadioChg);

myResults myR = (myResults)Page.LoadControl("myResults.ascx");
plh_Results.Controls.Add(myR);

r.myRadioChg += new myRadioOne.chgRadio(myR.chg_label);

}

public void r_myRadioChg(object s, EventArgs e)
{
RadioButtonList rbl = (RadioButtonList)s;
lit_MyChange.Text = "Button Clicked from User Control : value = " + rbl.SelectedValue;
//throw new Exception("The method or operation is not implemented.");
}
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++++
First User Control - myRadioOne.ascx
<%@ Control Language="C#" AutoEventWireup="true" Inherits="myRadioOne" %>

<asp:RadioButtonList ID="rbl_radioOne"
OnSelectedIndexChanged="chgValue"
AutoPostBack="true"
runat="server">
<asp:ListItem Text="Option 1" Value="1" />
<asp:ListItem Text="Option 2" Value="2" />
<asp:ListItem Text="Option 3" Value="3" />



<asp:Label ID="lab_radioValue" runat="server" />
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
First User Control's code-behind myRadioOne.cs
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

public class myRadioOne:UserControl
{
protected Label lab_radioValue;
public delegate void chgRadio(object s, EventArgs e);
public event chgRadio myRadioChg;

public void chgValue(object s, EventArgs e)
{
RadioButtonList r = (RadioButtonList)s;
lab_radioValue.Text = "Current Option : " + r.SelectedValue;

if(myRadioChg!=null)
{
myRadioChg(object s, EventArgs e);
}
}
}
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Second User Control File - myResults.ascx
<%@ Control Language="C#" AutoEventWireup="true" Inherits="myResults" %>
<asp:Label ID="lab_Results" runat="server" />
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Second User Control code-behind - myResults.cs
using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

public class myResults : UserControl
{
protected Label lab_Results;

public void chg_label(object s, EventArgs e)
{
RadioButtonList r = (RadioButtonList)s;
lab_Results.Text = "Second User Control : Value from 1st User Control " + r.SelectedValue;
}
}
Questioncapture user control existing event
kuido20
1:42 14 Aug '06  
How to capture use control already existing event ?
If I have the DropDownList
control in the user control then how to capture this DropDownList SelectedIndexChanged event ?
AnswerRe: capture user control existing event
Karl Seguin
3:13 14 Aug '06  
You need to capture it in the user control, and raise the user control's own event.

Alternatively, if you have the DropDownList public, you're page can hook right into it.

Karl
Questiongridview user control in placeholder disappears when clicked for sorting in
OladejoAdebola
15:29 24 Mar '06  
Hello,
I have a user control consisting of a gridview and some textboxes. When i drop the control on page and bind to it, i can sort with no problems. However, if I dynamically add it to a placeholder, then it disappears each time I click on it. Any ideas?

Thanks
AnswerRe: gridview user control in placeholder disappears when clicked for sorting in
Karl Seguin
6:54 25 Mar '06  
The control needs to be re-added during postback on or before Page_Load.

You can use Denis Bauer's Free Dynamica PlaceHolder control which automatically remembers what dynamic controls were placed into it:
http://www.denisbauer.com/ASPNETControls/DynamicControlsPlaceholder.aspx


Karl
GeneralRe: gridview user control in placeholder disappears when clicked for sorting in
OladejoAdebola
8:58 25 Mar '06  
Hi Karl, tried it, but it crashed with errors. Is there a way of doing it without an added tool
GeneralRe: gridview user control in placeholder disappears when clicked for sorting in
Karl Seguin
9:26 25 Mar '06  
well, here's one way to do things:

You'd call LoadGridView to initially load your gridview...dunoo when ur dynamically loading it, but it could be anywhere.

public void LoadGridView(Control containerControl)
{
GridView view = new GridView();
//do stuff
//add view to the page
containerControl.Controls.Add(view);
//add the view
ViewState.Add("GridViewLoaded", true);
}


Note that we've added a value to the viewstate..


then, in page load you can do:

if (IsPostBack)
{
   if (ViewState["GridViewLoaded"] != null)
   {
               LoadGridView(SameControlYouUsedInitiall);
   }
}

Karl
GeneralRe: gridview user control in placeholder disappears when clicked for sorting in
OladejoAdebola
11:56 26 Mar '06  
Thanks Karl! It worked. I figured out the C#, applied it appropriately and it's working perfectly. You're the man.
QuestionVS2005 Issue?
tbetz
21:56 1 Jan '06  
Awesome article, Karl. I always enjoy finding articles that don't just give some cut and paste code, but instead help you to actually understand what you're doing.

I too, have been trying to have 2 user controls on a page try to communicate. In a Windows Forms app, I don't have any problem with this. I learned long ago to use the common ground (the parent form) between them as the access path. Now I'm trying to apply my knowledge to ASP.NET and I'm getting frustrated.

I'm not sure if the issue lies with Visual Studio 2005 and the .NET 2.0 framework, or if I'm totally overlooking something here. I first tried your methods in my own app, then made a test app using your code. They both fail in the same place... trying to get the current page and casting to the known (or expected) page.

          string pageTitle = null;
          if (Page is SamplePage)
          {
               pageTitle = ((SamplePage)Page).Title;
          }
          else
          {
               pageTitle = "unknown";
          }


The error is asking for a missing reference, and I haven't figured out how to create a reference to a specific page.

Any ideas?

Thanks!
AnswerRe: VS2005 Issue?
Karl Seguin
7:49 2 Jan '06  
It probably does have something to do with vs.net 2005. The compilation mechanism has changed quite a bit. Each page/control is compiled into it's own assembly...so SamplePage doesn't exist as far as ur control is concerned.

One solution is to place
<%@ Reference page="~/samplepage.aspx" %> on the top of the .ascx portion. I hate this solution because it ties your user control to a specific page, hence nullifying the benefits of user controls.

The other solution, and as far as I know the only good one even though it's a slight pita, is to place an interface within your app_code file and make your page inherit it.

if you do:

public interface Blah
{
string Title {get;}
}

and make ur page implement Blah, ur user control should be able to cast Page to (Blah). This is actually the way you are supposed to do it, so I guess that's why it's forced on you in 2005 Smile


As a final alternative, there's an early beta of a new web project available for 2005 which mimics 2003. I haven't tried it...
http://webproject.scottgu.com/

Karl
AnswerRe: VS2005 Issue?
tbetz
17:20 2 Jan '06  
Thanks for your suggestions. I did some late-night scrounging through the MS newsgroups and found the same suggestion to create a class in the app_code directory, and have my pages inherit from it. Problem is that when I try it, I get a compile error saying that I can't inherit from more than one source. ?? Confused I thought you could inherit from 2 sources.

I also tried the idea with creating a reference to the page within the user control. That time it gacked a hair ball, complaining about a circular reference. Makes sense.

I've even been trying to use a class with static properties, thinking I could pass variables around like that, but they don't stick. Same with Session objects. I think I'm missing something basic here, but can't seem to nail it. Probably too used to "classic" ASP. I love the idea of use controls and object oriented programming for a webapp, but I'm having a tough time with client-side / server-side / session state / etc in ASP.NET. Time to take a step back and dig through more books and newsgroups. Maybe even try some of those beginner how-to's.

Thanks for the effort, and keep up the great work on the articles.

Tom

GeneralRe: VS2005 Issue?
Aaron Edwards
21:18 6 Jul '06  
Oh I have figured out a much better solution for this. You can use the parent's Page.AppRelativeVirtualPath property to get a reference to the title of the page you are on. Better than nothing.

Aaron
GeneralRe: VS2005 Issue?
Aaron Edwards
5:59 6 Apr '06  
This is a little Kludgy, but I just check the Title property of the parent page, as in

if (Page.Title == "Welcome to SimplePage")
{
//do something
}
else
{
//do something else
}

You have to make sure your <title> element in your parent page is server-accessible by placing "runat=Server" in it (or if the page is a Content Page that has a Master, it will already be server-accessible, and the title will be a property in the @Page directive).

I know it's a little cheesy, but so far it's the only convenient way I've found to let a User Control know what page is its parent.

Aaron
GeneralSimple way of communication between UserControl and Page
Shamshuddin
3:40 9 Jun '05  
Hi All,

I am not an expert in .NET, so know a little.
Read the article "Mastering Page-UserControl Communication" but was not able to set the same code on my machine, may be i was doing something wrong.

Finally i found another way of communicating between a User control and Page

I have a User Control Name RTSearchTypes.ascx. In which i have a button named btnNameSearch. Below is the code which i have placed in RTSearchType.ascx file

Private Sub lbtnName_Command(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.CommandEventArgs) Handles lbtnName.Command
Dim obj As Object
obj = Page
obj.NameSearchClicked()
obj = Nothing
End Sub


In my aspx file i.e., RTSearchMain.aspx file i have written following lines

Public Sub NameSearchClicked()
'do something
End Sub


Found that this works perfectly fine.

Can anyone of you please tell me whats the disadvantage in following above simple approach.

Shamshuddin
GeneralRe: Simple way of communication between UserControl and Page
Karl Seguin
13:00 15 Jun '05  
Sham:
you are using late-binding..which is a feature of vb.net which let's you use type-unsafe code at compile time.

You see, in your code Page is actually exposed as me.Page (it's the page the user control is on)...and it's of type System.Web.UI.Page. However, the base page type doesn't have a function named "NameSearchClick". If you were to use this code in c# or vb.net with option strict on, you'd get a compile-time error.

however, with late binding, vb.net waits until run-time at which point Page is actually a reference to RTSearchMain which DOES have a function named NameSearchClicked() and so it works fine.

In other words, at compile time VB.Net has no idea what type Page really is...all it knows for sure is that it's of type System.Web.UI.Page. At runtime however, Page is actually of type RTSearchMain (assumption that your class name is the same as the .aspx page name (which it is by default)).

The downside of your code is that you don't get compile-time checking and the code runs a little slower. PErsonally I wouldn't worry about the performance hit, but I would about the compile time check.

You could do something like obj.ParseInt() and the code would compile, even though it woudln't run - not good if avoidable.

You should try:

dim obj as RTSearchMain = ctype(Page, RTSSearchMain)
obj.NmameSearchClicked()

or something

Karl

P.S. - sorry for the delay, was on vacation Smile
GeneralCascaded User Control
mgaerber
21:29 8 May '05  
Hi,

very good article.
I have a situation though, where I can't get the events properly to work:

I have a WebUserControl (UC1) which exposes a custom event (OnSearchPerformed).

UC1 is added onto another WebUserControl (UC2) at design time.
UC2 is listening for the OnSearchPerformed event.

UC2 is loaded from an ASPX-Page at OnInit

The problem is, UC1 encapsulates a ButtonClick but my custom event only
fires on every second click



Code for UC1

public class SearchCtrl : System.Web.UI.UserControl
{


public event SearchEventHandler OnSearchPerformed;

private void Page_Load(object sender, System.EventArgs e)
{

this.btnSearch.Click += new System.EventHandler(this.btnSearch_Click);
}

private void SearchPerformed(System.EventArgs e)
{
SearchEventArgs args = new SearchEventArgs(this.txtBoxSearch.Text);
this.OnSearchPerformed(this, args);

}


protected void btnSearch_Click(object sender, System.EventArgs e)
{
this.SearchPerformed(e);
}//end method



Code for UC2


public class EditorialModule : System.Web.UI.UserControl
{
protected System.Web.UI.WebControls.Label Label1;
protected Syncrovet.Web.Controls.SearchCtrl SearchCtrl1;


private void Page_Load(object sender, System.EventArgs e)
{

this.SearchCtrl1.OnSearchPerformed+=new SearchEventHandler(SearchCtrl1_OnSearchPerformed);

protected void SearchCtrl1_OnSearchPerformed(object sender, SearchEventArgs e)
{
this.Label1.Text = e.SearchString;
}


GeneralRe: Cascaded User Control
mgaerber
23:47 8 May '05  
Now it works (the code above actually doesn't contain errors).

The Button which ClickEvent was encapulated by the custom Event happend
to be placed next to a TextBox with AutoPostback set to true.

So entering the TextBox would cause a PostBack and somehow the Event for
the Button got lost.

Anybody knows what this is all about?


Last Updated 7 Feb 2005 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010