Dynamic Templates

Finish Your DataGrid Templates Dynamically

CoreCoder

LANGUAGES: VB.NET

ASP.NET VERSIONS: 1.x | 2.0

 

Dynamic Templates

Finish Your DataGrid Templates Dynamically

 

By Dino Esposito

 

I receive many questions in my e-mail inbox that, although couched in different contexts and scenarios, often center around the same theme. One of these oft-asked questions revolves around dynamically built templates for advanced data-bound controls, such as DataGrid and DataList, and in ASP.NET 2.0, GridView and FormView. As you know, data-bound templated controls let developers make up portions of their user interface using a set of HTML elements grouped in a template. In most cases, the template is statically declared in the ASPX source and is compiled along with the page that hosts the control. There s nothing really dynamic in this model.

 

In other cases, the template is loaded from an external file usually a user control ASCX file. This scenario is a bit more dynamic than the previous one, but is still far from what readers who submit queries really wish to obtain. Loading a template from an external file entails setting the control s template property with a managed object that the LoadControl method (from the Page class) returns. Sure, you can dynamically choose which template to load, but if the combination of controls to render is not predictable, and depends on run-time conditions and user actions, you re often lost.

 

In the latest variation of the question, a reader requested advice on how to finish off the template at run time by adding some dynamically created controls (and more importantly) bound to click handlers. In this article, I ll first take the simple route (which is usually the best way to go in most situations), and then take the plunge into the abyss of the page lifecycle events.

 

Outline a Simple Scenario

Imagine you have a DataGrid control with a template column. The column is expected to display a button or a message depending on certain run-time conditions determined by user actions and user input. The button in the template is obviously an interactive element and must post back and execute some code when clicked. The simplest route you can take to resolve this problem is to define a static template that contains all the possible controls you may ever need. Then in the handler of the ItemCreated or ItemDataBound events, perform a dynamic check and decide which control to hide and which one to display. Figure 1 shows the source code of an ItemDataBound event handler designed to address a simple scenario.

 

Sub ItemDataBound(sender As Object,

 e As DataGridItemEventArgs)

 ' Make sure the item you process is item/alternating

 If Not CheckItem(e) Then

   Return

 End If

 ' Retrieve the input data for the page

 Dim countryFilter, country As String

 countryFilter = CountryName.Text

 country = DataBinder.Eval(e.Item.DataItem, "country")

 ' Retrieve controls in THIS DataGrid item

 Dim b As Button

 b = CType(e.Item.FindControl("ActionButton"), Button)

 Dim msg As Label

 msg = CType(e.Item.FindControl("ActionMsg"), Label)

 If country.ToLower() = countryFilter.ToLower() Then

   b.Visible = True

   msg.Visible = False

 Else

   b.Visible = False

   msg.Visible = True

 End If

End Sub

Figure 1: Show the button only if the country names match.

 

The user indicates the name of a country, then clicks to post back and update the grid. The grid is redesigned and the code in Figure 1 is invoked at binding time. Row after row, the handler compares the name of the country specified by the user to the Country field in the currently bound record. If the two names match, the handler enables the button statically defined in the template; otherwise, the button is hidden and the label is enabled to display a default message. Here s a possible layout for the column s template:

 

 

  

      text="No actions available." />

  

      text="Edit" onclick="BeenClicked" />

 

 

Figure 2 shows a screenshot that illustrates this point. The Edit button appears only on the records whose Country field equals USA. The Edit button is statically bound to a handler; if you click on it, the code executes and does whatever it is supposed to do. What s wrong, or at least insufficient to some scenarios, with this approach?

 


Figure 2: Create different templates based on run-time conditions and user input.

 

The model is not fully dynamic. The actual content of the template is effectively determined at run time, but the combination of controls to display is forced to pick up controls out of a fixed set hard-coded in the ASPX source. Aside from application-specific considerations about the displayed set of controls, one thing to notice is this: All controls statically declared in the ASPX source are instantiated at run time when the page is being processed. If you need to implement a sophisticated and highly variable layout, you may need to put in several controls, only two or three of which will be used per row. This may result in an unacceptable waste of resources and memory. Don t be fooled by the fact that you can set Visible to false and prevent additional markup to slip into the response. You should be worried just because you can set the Visible property to anything you want there s no clearer evidence that all controls are up and running and consuming resources.

 

Outline a More Complex Scenario

Let s switch to a more sophisticated scenario. The idea is to create a button on the fly whose title and click handler are determined based on run-time conditions and any previously entered user input. The column template is similar to the one you saw above, except that it lacks the button. The button, in fact, will be created on the fly, as shown in Figure 3.

 

Sub ItemDataBound(sender As Object,

 e As DataGridItemEventArgs)

 ' Make sure the item you process is item/alternating

 If Not CheckItem(e) Then

    Return

 End If

 ' Retrieve the input data

 Dim countryFilter, country As String

 countryFilter = CountryName.Text

 country = DataBinder.Eval(e.Item.DataItem, "country")

 ' Retrieve item controls

 Dim msg As Label

 msg = CType(e.Item.FindControl("ActionMsg"), Label)

 If country.ToLower() = countryFilter.ToLower() Then

AddNewButton(ButtonName.Text, e.Item)

     msg.Visible = False

 Else

     msg.Visible = True

 End If

End Sub

Sub AddNewButton(title As String, item As DataGridItem)

 Dim btn As New Button

 btn.Text = title

 AddHandler btn.Click, AddressOf BeenClicked

 item.Cells(2).Controls.Add(btn)

 ViewState("DynamicControlsCreated") = True

End Sub

Figure 3: Create a button on the fly.

 

The internal method AddNewButton creates and configures a new Button control. Next, it adds the control to the corresponding cell of the record being processed:

 

' Add to the third column

Dim btn As New Button

btn.Text = title

AddHandler btn.Click, AddressOf BeenClicked

item.Cells(2).Controls.Add(btn)

 

This code compiles and works just fine; at least it does as long as you keep yourself from clicking. If you do that, something unexpected occurs: dynamically added controls disappear and the click handler apparently doesn t execute. What s wrong with the click action in this context?

 

To be honest, the problem here is not with the click action itself; neither is it connected to the click handler and the way (or the time) you bind it to the dynamic button control. The rub lies in the postback event caused by the click action and how the page handles it. Simply put, ASP.NET doesn t know anything about dynamically created controls. To serve an incoming ASPX request, the ASP.NET runtime instantiates the page class the class compiled on the first hit to the page and invokes the ProcessRequest method on it. That s it. The class builds the page s control tree, but takes into account the version of the page hard-coded in the ASPX source. When the page posts back, ASP.NET gets the ID of the clicked control correctly. However, it can t match it to an existing control in the page because that control has been dynamically added and, as such, is not automatically recreated on postback. Guess what happens next? That control the button in this example isn t rendered and the postback code gets lost and doesn t execute.

 

Solve the Issue

Loyal readers may remember Manage Dynamically Created Controls. In that article, you ll find essential information and explanations to fix the problem with dynamic controls in control templates. Let s recap the key points and outline a solution.

 

Any dynamically added control is a true part of the page when the postback is generated. Among other things, this means that any information that relates to these controls is correctly packed into the HTTP request and received by ASP.NET. Next, the ASP.NET handler in charge of the request the ProcessRequest method of the page class instantiates any statically declared control and configures them with viewstate information. Next, it further configures the controls with postback data, overwriting viewstate information, if necessary. At this point, if the postback data references a dynamically created control, ASP.NET simply skips over it and temporarily copies the data in a helper collection of still-to-process postback data. At the end of the initialization step, the ASP.NET page handler fires the Page_Load event. This is a great time, and a formidable hook, for you to manually recreate any dynamically added controls that need be there to correctly process postback data.

 

In our example, you extend the Page_Load event handler and make it ensure that dynamically created controls are recreated:

 

Sub Page_Load(sender As Object, e As EventArgs)

 If Not IsPostBack Then

  LoadData()

  BindData()

 End If

 EnsureDynamicControls()

End Sub

 

After Page_Load returns, the ASP.NET page handler makes a second try on unprocessed postback data. It scans the helper collection to see if the stored data can be matched to an existing control. If you effectively added any missing control in the meantime, the processing goes smoothly and seamlessly and delivers the expected final behavior.

 

A good question to ask is, How can I know which dynamic controls are to be created in this instance of the postback? Especially if you implement a very dynamic user interface, the combination of dynamic controls required by the particular instance of the postback may not be known in advance. A possible workaround is writing some of the IDs of the controls to create in the viewstate (again, see Figure 3).

 

In Figure 3, I simply wrote a general flag, meaning that all controls should be recreated. In a more realistic scenario, you would write the ID of the control(s). Take a look at the outline of EnsureDynamicControls:

 

Sub EnsureDynamicControls

 Dim temp As Object

 temp = ViewState("DynamicControlsCreated")

 If Not(temp Is Nothing) Then

   ' Check info and recreate controls here

   BindData()

 End If

End Sub

 

In this particular example, you need to create a dynamic control within a DataGrid template. Originally, the control is created at binding time in the ItemDataBound event handler. This means that in Page_Load you miss essential information to create the controls; for example, the DataGridItem object. The best workaround is rebinding the DataGrid to its data, which will fire the ItemDataBound event and fix the page structure.

 

An even easier approach is calling BindData straight in the Page_Load event. This is neither elegant nor free of potential hassles. A good programming practice is to call only the methods you need. And in this context, you don t need to bind data if the grid has not been affected by the postback. For this reason, BindData should be called only if strictly necessary. EnsureDynamicControls, along with a proper viewstate setting, does the trick.

 

Conclusion

Anybody who states that there s no support for dynamic controls in ASP.NET is wrong. When processing postback data, ASP.NET makes a second try just to account for dynamically added controls. Turn on tracing and examine the log to see exactly when and where the second try occurs. ASP.NET lacks a built-in and generic mechanism to facilitate the rebuilding of dynamic controls. However, with a little help from the viewstate, and knowing exactly the sequence of events in the postback processing of the page, you can easily overcome this obstacle.

 

The sample code accompanying this article is available for download.

 

Dino Esposito is a Wintellect trainer and consultant who specializes in ASP.NET and ADO.NET. Author of Programming Microsoft ASP.NET and the recently released Introducing ASP.NET 2.0 (both from Microsoft Press), Dino also helped several companies architect and build effective products for ASP.NET developers. Dino is the cofounder of http://www.VB2TheMax.com, a popular portal for VB and .NET programmers. Write to him at mailto:[email protected] or join the blog at http://weblogs.asp.net/despos.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish