Today we released the June Atlas CTP (internally known as build M2.2). There are a handful of bug fixes, but one feature in particular, makes this anything but a minor release. UpdatePanels can now be added dynamically into the page at runtime! This opens up a number of scenarios including UpdatePanels inside templates of data-bound controls, UpdatePanels in user controls used as WebParts etc.
UpdatePanels within Templates
As you'd imagine, as a page developer, you can now do something like this:
<asp:DataList runat="server">
<ItemTemplate>
<asp:UpdatePanel runat="server">
...
</asp:UpdatePanel>
</ItemTemplate>
</asp:DataList>
Even though the UpdatePanel appears to be statically declared, in reality it is dynamic. The DataList builds up a set of items dynamically and instantiates its template while enumerating its data. In past builds, you'd see an error indicating that it was too late to register an UpdatePanel; this scenario now works fine.
UpdatePanels within Composite Controls
The implications of dynamic UpdatePanels will likely open up many more control developer scenarios. I can now build a composite control that implements partial rendering within itself simply by including an UpdatePanel as part of its controls collection. This makes partial rendering more "automatic" for page developers using a new breed of Atlas-enabled server controls. I have put together a sample demonstrating the model. It is a simple PhotoViewer composite control that contains an Image control, and a couple of Button controls to implement next/prev functionality.
Here is the relevant portion of code. You can download the full sample here.
public class PhotoAlbumViewer : CompositeControl, IPartialRenderingCompositeControl {
private Image _image;
private Button _nextButton;
private Button _prevButton;
private CompositionUpdatePanel _container;
private int _index;
private string[] _images;
public string[] Images {
get { ... }
set { ... }
}
protected override void CreateChildControls() {
_container = new CompositionUpdatePanel(this);
_container.Mode = UpdatePanelMode.Conditional;
_container.ID = "Container";
Controls.Add(_container);
_image = new Image();
_nextButton = new Button();
_prevButton = new Button();
_nextButton.Text = "Next";
_nextButton.CommandName = "Next";
_prevButton.Text = "Previous";
_prevButton.CommandName = "Prev";
_container.Content.Controls.Add(_image);
_container.Content.Controls.Add(_nextButton);
_container.Content.Controls.Add(_prevButton);
}
protected override bool OnBubbleEvent(object source, EventArgs args) {
bool handled = false;
CommandEventArgs ce = args as CommandEventArgs;
if (ce != null) {
if (ce.CommandName.Equals("Next", StringComparison.Ordinal)) {
_index++;
handled = true;
}
else if (ce.CommandName.Equals("Prev", StringComparison.Ordinal)) {
_index--;
handled = true;
}
}
if (handled) {
if (_index < 0) {
_index = _images.Length - 1;
}
else if (_index >= _images.Length) {
_index = 0;
}
_container.Update();
}
return handled;
}
...
#region IPartialRenderingCompositeControl Members
void IPartialRenderingCompositeControl.RenderUpdatePanel(CompositionUpdatePanel updatePanel, HtmlTextWriter writer) {
if ((_images == null) || (_images.Length == 0)) {
return;
}
int imageCount = _images.Length;
_image.ImageUrl = _images[_index];
if (_index == 0) {
_prevButton.Enabled = false;
}
if (_index == (imageCount - 1)) {
_nextButton.Enabled = false;
}
writer.RenderBeginTag(HtmlTextWriterTag.Table);
writer.RenderBeginTag(HtmlTextWriterTag.Tr);
writer.RenderBeginTag(HtmlTextWriterTag.Td);
_image.RenderControl(writer);
writer.RenderEndTag(); // Td
writer.RenderEndTag(); // Tr
writer.RenderBeginTag(HtmlTextWriterTag.Tr);
writer.RenderBeginTag(HtmlTextWriterTag.Td);
_prevButton.RenderControl(writer);
writer.Write(" ");
_nextButton.RenderControl(writer);
writer.RenderEndTag(); // Td
writer.RenderEndTag(); // Tr
writer.RenderEndTag(); // Table
}
#endregion
}
So what's happening here? I have a control deriving from CompositeControl, and creates its contents by overriding CreateChildControls. The overall rendering is produced through a combination of child controls and layout scaffolding around the controls rendered directly as markup into the writer. The control renders an image and updates the image each time the buttons are clicked by handling bubbled events. Pretty standard stuff so far if you've written server controls.
What's new is the use of the CompositionUpdatePanel control, a derived UpdatePanel control that makes it simple to implement composition by providing a default ContentTemplate which is a required property right now as well as enabling the scenario where some of the content is rendered directly without using child controls. This control is part of a prototype I have put together and included with the sample - I am hoping to get this type of functionality and scenario fully supported in Atlas eventually. Essentially the composite control implements the IPartialRenderingCompositeControl interface (also introduced in the sample), and passes it into the contained UpdatePanel control. This interface allows the UpdatePanel to call back into the outer composite control to render the children and any interspersed markup when it comes time to render the UpdatePanel.
In this particular sample all of the child controls have been added into a single UpdatePanel. A more complex control might have multiple UpdatePanels within, as well as some content outside of UpdatePanels. There are a couple of things to keep in mind as you go down this path. Your control's Render method will not be called in a partial rendering request; only the UpdatePanel's Render method will be called. Therefore, some logic might need refactoring, in terms of how you build your child control hierarchy, or how you might need to move some rendering logic into the RenderUpdatePanel implementation. Secondly, you're more than likely to set your UpdatePanel's Mode property to Conditional, and determine precisely when it should be updated by calling the Update method. In the sample, the UpdatePanel is updated only when the user clicks on one of the buttons, rather than on every request.
A look at the "evolution" of UpdatePanels
So what was the big deal with dynamic UpdatePanels? The answer lies with the Triggers feature. One use of a trigger is to specify which UpdatePanels and when they should be updated as the ScriptManager puts together a response for partial rendering. See my previous post for more information on triggers. For example, a ControlEventTrigger marks an UpdatePanel as needing an update if and when a specified control raises the specified event. This makes things interesting. The UpdatePanel needs to exist in the control tree and register for listening to the event before the event is raised. The event is raised during the post-back processing phase of the page lifecycle. Hence adding UpdatePanels during Load, or PreRender, or PreRenderComplete (that is when some DataBound controls instantiate their templates) is simply too late.
We've relaxed that restriction, but this in turn results in a constraint on how you set up triggers for dynamic UpdatePanels. The control that raises the event to which the ControlEventTrigger points to needs to be in the same naming container as the UpdatePanel. In other words it cannot be in a sibling or parent naming container. This ensures the UpdatePanel can successfully register and handle events, because within a naming container controls are created at the same time (at least that is the case in properly designed composite controls). The reason for this is some subtle aspects of the page life cycle and post-back processing architecture. When the event source control needs to raise an event as a result of some post-back data, the page attempts to find it in the control tree, and in the course of doing so, controls are created on-demand to fill naming containers. With the constraint on triggers that I just mentioned, it is guaranteed that both the event source and the UpdatePanel will have be created before the event is raised.
This constraint actually works reasonably well. For example, a WebPart typically will contain the control that causes post-back as well as the content that needs to be embedded in the UpdatePanel. The same goes with the GridView or DataList scenario. The control that needs to cause an update within a particular row in a data-bound control is quite likely logically placed in the same row. In the case of composite controls, I don't think Triggers will even be that useful, further implying this constraint isn't a big deal. The composite control using the UpdatePanel is responsible for mapping its own semantics into appropriate calls to Update, just like I did in my sample.
Posted on Friday, 6/30/2006 @ 10:45 AM
| #
ASP.NET