DotNetSlackers: ASP.NET News for lazy Developers

Saturday, January 8, 2011

Mastering Page-UserControl Communication


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:
  • How to access values from a page in a user control?
  • How to access values from one user control in another user control?
  • How to access values from a user control in a page?
  • And other similar questions.
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:
 Collapse
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:  }
 Collapse
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_Loadevents 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, whichSystem.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 ourSamplePage 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:
 Collapse
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:  }
 Collapse
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 fromSystem.Web.UI.Page [5,6]. We then see how both SamplePage and System.Web.UI.Page inherit the ToString() function fromSystem.Object - which all objects in .NET inherit from. Things get more interesting when we play with the Title property. First, since theSystem.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 Titleproperty [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 fromSystem.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:
 Collapse
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
 Collapse
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:
 Collapse
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:
 Collapse
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:
 Collapse
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
 Collapse
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:
 Collapse
1:  //C#

   2:  string pageTitle = null;
   3:  if (Page is SamplePage){
   4:     pageTitle = ((SamplePage)Page).Title;
   5:  }else{
   6:     pageTitle = "unknown";
   7:  }
 Collapse
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 aSystem.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 andResultHeader user controls:
 Collapse
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:
 Collapse
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:  }
 Collapse
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, theResultHeader 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 ResultHeaderwould 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 ReadOnlyproperty which in turn exposes our other interface:
 Collapse
1:  //C#

   2:  public interface IResultContainer{
   3:   IResult Result { get; }
   4:  }
 Collapse
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:
 Collapse
1:  //C#

   2:  public interface IResult { 
   3:   DataTable Info { get; }
   4:  }
 Collapse
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:
 Collapse
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:
 Collapse
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 IResultContainerinterface:
 Collapse
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:
 Collapse
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:  }
 Collapse
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 thatPageClick 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:
 Collapse
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:  }
 Collapse
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
 Collapse
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 theIResultContainer 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 Resultswhere 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:
 Collapse
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).

No comments:

Post a Comment