Building “Atlas” Mash-ups with C1UpdateSplitter
Applies To:
Studio Enterprise
Author:
John Juback
Published On:
10/23/2006
Microsoft ASP.NET AJAX (formerly code-named "Atlas") is an extension of ASP.NET 2.0 that incorporates Asynchronous JavaScript and XML (AJAX) for creating rich, interactive Web applications that feel more like desktop applications, where users are accustomed to immediate responses.
This article describes a sample "mash-up" application that combines data from three different Web services and displays the results using a variety of Studio Enterprise components, including collapsible topic bar groups, master-detail grids, and the newly released C1UpdateSplitter server control, which extends the functionality of the "Atlas" UpdatePanel by implementing dual content panels separated by a resizable splitter bar.
Using the sample application
To view the sample application in action, click the following link:
[http://helpcentral.componentone.com/c1atlasdemo](http://helpcentral.componentone.com/c1atlasdemo)
Enter a zip code into the text box, then click the green circle with the arrow. You should see the following changes:
- The label below the text box now displays the corresponding city and state.
- The Weather box displays today's forecast.
- The topmost grid displays a list of nearby movie theaters.
Now try interacting with the form as follows:
- Select the Weekly Forecast radio button. Notice that additional weather data and a vertical scroll bar appear, but the grids do not flicker.
- Select a row in the Theater grid. Notice that the other rows do not repaint. The Movie grid is updated based on the selected row.
- Click the dotted image in the vertical splitter bar to collapse the left panel. Click it again to restore the left panel.
- Click the dotted image in the horizontal splitter bar to collpase the top panel. Click it again to restore the top panel.
- Grab either splitter bar outside of its dotted image (you should see a double-arrow cursor instead of a finger), then drag it to a different position. The panels resize accordingly, and their scroll bars are shown or removed as needed to accommodate the inner contents.
To download the full source code (in C#) for the sample, which requires the April CTP or later (Microsoft.Web.Atlas.dll build 2.0.50727.60406), click the following link:
[http://helpcentral.componentone.com/c1kb/upload/c1atlasdemo.zip](//cdn.mescius.io/assets/developer/blogs/legacy/c1/2006/10/c1atlasdemo.zip)
What's a mash-up?
The term "mash-up" is used to describe Web applications that combine content from multiple sources such as RSS feeds, Web services, or other public interfaces. The sample application described in this article uses three distinct Web services, each of which returns different information based on a zip code:
- City and state
- Weather forecasts
- Movie theaters and show times
To the end user, all of the data appears to come from a single source, even though there are three separate data sources "mashed-up" into one application.
AJAX support in Studio Enterprise
The 2006 v2 release of Studio Enterprise introduced support for AJAX in conventional ASP.NET 2.0 (that is, without the "Atlas" extensions). For example, the C1WebGrid control added a CallbackOptions property for specifying which operations should use the AJAX mechanism. For details about the AJAX implementation in specific components, please see the following article:
[http://helpcentral.componentone.com/article.aspx?id=1506](http://helpcentral.componentone.com/article.aspx?id=1506)
The 2006 v3 release of Studio Enterprise introduced compatibility for the "Atlas" extensions. This means that any ComponentOne ASP.NET component can safely reside within an "Atlas" UpdatePanel and participate in partial-page rendering without any special coding or property settings.
This release also introduced a new product, ComponentOne WebSplitter for ASP.NET, that implements two container controls with resizable panels, similar to the Windows Forms SplitContainer control:
- C1WebSplitter, for conventional ASP.NET 2.0
- C1UpdateSplitter, for ASP.NET AJAX ("Atlas")
In fact, the C1UpdateSplitter control creates an "Atlas" UpdatePanel behind the scenes. For this reason, you may need to copy Microsoft.Web.Atlas.dll to the Studio Enterprise bin directory before you can add C1UpdateSplitter to the Toolbox in the Visual Studio IDE.
Although the concept of Web services is not new, it takes on greater importance with the release of ASP.NET AJAX, since it is easy to compartmentalize different types of content within distinct screen regions. To learn more about the kinds of Web services that are available, visit the following sites:
[http://www.xmethods.net](http://www.xmethods.net/) [http://www.strikeiron.com](http://www.strikeiron.com/)
All of the Web services used in the mash-up sample are free, and were found on the xmethods.net site. Let's take a detailed look at how to consume a Web service in an ASP.NET application.
Adding a Web service reference
Web services are identified by a URL that references a file with an extension of .asmx. For example, the Web service that returns movie theater information has the following URL:
http://www.ignyte.com/webservices/ignyte.whatsshowing.webservice/moviefunctions.asmx
To reference a Web service in an ASP.NET application (with or without AJAX), use the Add Web Reference command, which is located on the shortcut menu in Solution Explorer, and also on the Website menu in the Visual Studio IDE. Enter the URL, then click the Go button to view a list of available services.
Click the Service Description link to view the WSDL (Web Services Description Language) summary, which lists method signatures and return types.
Clicking the Add Reference button creates the necessary files and folders within the project's App_WebReferences subfolder. Once you have referenced a Web service in this manner, you can write code to create and manipulate strongly-typed objects exposed by the service, or you can associate it with an ObjectDataSource control for data-binding purposes.
The mash-up sample illustrates both techniques. The master grid that displays theaters and addresses is bound to a results list obtained from an ObjectDataSource control, while the weather forecast data is derived by creating a WeatherForecast object, calling its GetWeatherByZipCode method, and iterating over the array of detail records that the method returns.
Configuring an ObjectDataSource control
To associate an ObjectDataSource control with a Web service, use its Configure Data Source verb, which opens a dialog box similar to this one:
You may need to clear the data components check box to see the complete list of available business objects. Note that this list contains some extraneous types, including event arguments and event handlers for asynchronous calls, as well as return value data types (Movie, Theater, and UpcomingMovie in this example).
After selecting the desired business object, choose a method to associate with the SELECT operation. For applications that need to modify Web service data, use the UPDATE, INSERT, and DELETE tabs.
Most Web service methods have one or more parameters. Use the parameter source combo box to specify one of the following options:
- None
- Cookie
- Control
- Form
- Profile
- QueryString
- Session
For all options except None, you will be prompted for a string that will be used to look up the appropriate element. If the parameter source is None, then the DefaultValue string is used.
In this example, the zipCode parameter has a default value of 00000, which is a sentinel value used to ensure that the application displays an empty form upon start-up. The radius parameter has a default value of 10, which tells the Web service to search for theaters within a 10-mile radius of the specified zip code.
Creating and using a Web service in code
The URL for the Web service that retrieves place name information from a zip code is as follows:
http://www.jasongaylord.com/webservices/zipcodes.asmx
The ZipCodes Web service implements four methods as shown in the following figure. The only one that is used in the mash-up is ZipCodeToDetails.
Unlike the MovieInformation service shown earlier, this WSDL view does not display any return types. However, using Intellisense, you can observe that the method in question returns a DataSet. (To determine the names of the data columns, you can either poke around in the Watch window or bind the first DataTable to a GridView or C1WebGrid control.) The following is a simplified version of the code used to set the text of a Label control that displays the city and state corresponding to a zip code, or an error message if the zip code is invalid:
using com.jasongaylord.www;
// Create the ZipCodes web service
ZipCodes zips = new com.jasongaylord.www.ZipCodes();
// Pass in the zip code to obtain a DataSet, then get the first table
DataSet ds = zips.ZipCodeToDetails("15206");
DataTable dt = ds.Tables[0];
// If a data row exists, format the label with the city/state
if (dt.Rows.Count > 0)
{
DataRow dr = dt.Rows[0];
label.Text = String.Format("{0}, {1}", dr["city"].ToString(), dr["state"].ToString());
}
// Otherwise, the zip code is invalid
else
{
label.Text = strBadZipCode;
}
Now let's turn our attention to the visual aspects of the form.
The main visual element of the mash-up application is a C1UpdateSplitter control with vertical orientation. The left panel contains a C1WebTopicBar control, which provides collapsible groups that act as containers for arbitrary content. The right panel contains another C1UpdateSplitter control, this time with horizontal orientation. The top and bottom panels of this inner splitter each contain a C1WebGrid control. The two grids form a master-detail relationship between theaters and movies. The following figure depicts the hierarchy of controls on the form:
The form also contains three non-visual components: a ScriptManager with its PartialRendering property set to True, an ObjectDataSource for binding the master grid to the list of movie theaters, and an UpdateProgress component for displaying animated feedback during partial-page rendering, as shown here.
Using the C1UpdateSplitter control
At design time, C1UpdateSplitter allows you to drag arbitrary components onto either one of its panels. The following figure shows a splitter in its default vertical orientation with a C1WebTopicBar component added to its left panel.
You can also nest C1UpdateSplitter controls on the design surface. In the next figure, a second C1UpdateSplitter has been added to the right panel of the first splitter. The orientation of the inner splitter was changed to horizontal, and a C1WebGrid component was added to each inner panel.
These five components form the overall structure of the mash-up page. For the outermost (vertical) splitter, C1UpdateSplitter1, the following properties were set:
AutoResize
True
BorderColor
White
Height
500px
SplitterDistance
256
Width
800px
The SplitterDistance value denotes the initial position of the splitter bar, in pixels. In addition, the following sub-object properties were set for C1UpdateSplitter1:
Bar.CollapsedCssClass
SplitterCollapsedVertical
Bar.CollapseHoverImageUrl
~/splitter_colhover_vert.gif
Bar.CollapseImageUrl
~/splitter_col_vert.gif
Bar.CssClass
Splitter
Bar.DragCssClass
SplitterDrag
Bar.ExpandHoverImageUrl
~/splitter_exphover_vert.gif
Bar.ExpandImageUrl
~/splitter_exp_vert.gif
Bar.HoverCssClass
SplitterHover
Panel1.BackColor
122, 160, 230
Panel2.ScrollBars
None
Note that scroll bars are disabled for the second panel, since scrolling will be handled by the nested horizontal splitter. The CssClass properties reference the following styles, which are defined in an inline style sheet in Default.aspx:
.Splitter
{
border: 0px solid #333333;
background-color: #F1F1F1;
}
.SplitterDrag
{
background-color: #245BCB;
filter:progid:DXImageTransform.Microsoft.Alpha(opacity=60);
opacity: 0.6;
}
.SplitterHover
{
background-color: #AFD8FF;
filter:progid:DXImageTransform.Microsoft.Alpha(opacity=60);
opacity: 0.6;
}
.SplitterCollapsedHorizontal
{
border: 0px solid #000000;
border-bottom-width: 0px;
}
.SplitterCollapsedVertical
{
border: 0px solid #000000;
border-right-width: 0px;
}
For the innermost (horizontal) splitter, C1UpdateSplitter2, the following properties were set:
AutoResize
True
BorderColor
#7AA0E6
BorderStyle
Solid
BorderWidth
1px
Height
100%
Orientation
Horizontal
SplitterDistance
240
Width
100%
Note that the dimensions are specified as 100% instead of absolute units. In addition, the following sub-object properties were set for C1UpdateSplitter2:
Bar.CollapsedCssClass
SplitterCollapsedHorizontal
Bar.CollapseHoverImageUrl
~/splitter_colhover_horz.gif
Bar.CollapseImageUrl
~/splitter_col_horz.gif
Bar.CssClass
Splitter
Bar.DragCssClass
SplitterDrag
Bar.ExpandHoverImageUrl
~/splitter_exphover_horz.gif
Bar.ExpandImageUrl
~/splitter_exp_horz.gif
Bar.HoverCssClass
SplitterHover
The C1WebTopicBar and C1WebGrid controls were configured using a combination of Auto Format commands and visual designers. (For the sake of brevity, the exact steps are not outlined here.) After performing all customizations, the design surface looks like this:
The following statements were added to reference Web services and C1 assemblies:
// Web services
using net.webservicex.www;
using com.jasongaylord.www;
using com.ignyte.www;
// C1 components
using C1.Web.C1Input;
using C1.Web.C1WebGrid;
using C1.Web.Command;
When the page first loads, it passes a bogus zip code to a private member that updates the master-detail grids. This is done to force the grid headers to be displayed even though there are no records to display.
protected void Page_Load(object sender, EventArgs e)
{
// Clear theater/movie grids on start-up
if (!IsPostBack)
UpdateTheaters("00000");
}
When the user clicks the GO button, the following event handler retrieves the zip code string from the C1WebMaskEdit control, then passes it to three utility routines, each of which deals with a different Web service.
protected void Button1_Click(object sender, EventArgs e)
{
// Get the masked edit control contained in the topic bar
C1WebMaskEdit edit = C1WebTopicBar1.FindControl("C1WebMaskEdit1") as C1WebMaskEdit;
if (edit != null)
{
// Update form elements with the new zip code
string zip = edit.Text;
UpdateDetails(zip);
UpdateWeather(zip);
UpdateTheaters(zip);
}
}
UpdateDetails uses the ZipCodes Web service to convert zip code strings to place names. It also changes the mailbox icon in the topic bar header to reflect valid or invalid input.
private void UpdateDetails(string zipCode)
{
// Get the label control contained in the topic bar
Label label = C1WebTopicBar1.FindControl("Label1") as Label;
// Create the ZipCodes web service
ZipCodes zips = new com.jasongaylord.www.ZipCodes();
// Pass in the zip code to obtain a DataSet, then get the first table
DataSet ds = zips.ZipCodeToDetails(zipCode);
DataTable dt = ds.Tables[0];
string img;
// If a data row exists, format the label with the city/state
if (dt.Rows.Count > 0)
{
DataRow dr = dt.Rows[0];
label.Text = String.Format("{0}, {1}", dr["city"].ToString(), dr["state"].ToString());
img = "~/mailbox_full.gif";
}
// Otherwise, the zip code is invalid
else
{
label.Text = strBadZipCode;
img = "~/mailbox_empty.gif";
}
// Change the topic bar header icon for valid/invalid data
C1WebTopicBarGroup zipGroup = C1WebTopicBar1.Groups[0] as C1WebTopicBarGroup;
zipGroup.CollapsedHeaderStyle.ImageUrl = img;
zipGroup.ExpandedHeaderStyle.ImageUrl = img;
zipGroup.MouseOverCollapsedHeaderStyle.ImageUrl = img;
zipGroup.MouseOverExpandedHeaderStyle.ImageUrl = img;
}
UpdateWeather uses the WeatherForecast Web service to retrieve date, temperature, and weather image information for a zip code. It correctly handles special cases where the service may return null values or empty image URLs. The selected value of the RadioButtonList control determines whether to display all records or today's forecast only.
private void UpdateWeather(string zipCode)
{
// Create the WeatherForecast web service and get data for this zip code
WeatherForecast wf = new net.webservicex.www.WeatherForecast();
WeatherForecasts wfs = wf.GetWeatherByZipCode(zipCode);
// Get the second topic bar group (zero-based)
C1WebCustomGroup weatherGroup = C1WebTopicBar1.Groups[1];
// Remove all items from this group except the first one (RadioButtonList)
while (weatherGroup.Items.Count > 1)
weatherGroup.Items.RemoveAt(1);
// If the details array is null, just display a message in a new topic bar item
if (wfs.Details == null)
{
string msg = (wfs.PlaceName == null) ? strNoResults : strNoWeather;
C1WebTopicBarItem item = new C1WebTopicBarItem(msg);
weatherGroup.Items.Add(item);
return;
}
// Check the radio buttons for the selected view (today or weekly)
RadioButtonList options = C1WebTopicBar1.FindControl("RadioButtonList1") as RadioButtonList;
bool today = (options == null || options.SelectedValue == "Today");
// Process all non-null detail records
foreach (WeatherData wd in wfs.Details)
{
if (wd.Day != null)
{
// Create a new topic bar item with date/temperature information
C1WebTopicBarItem item = new C1WebTopicBarItem(FormatWeatherData(wd));
string img = wd.WeatherImage;
// Use the image URL if one was given
if (img != null && img.Length > 0)
item.ItemStyle.ImageUrl = img;
// Always show the first item; hide the others if not in weekly view
item.Visible = (!today || weatherGroup.Items.Count == 1);
weatherGroup.Items.Add(item);
}
}
}
The following function reformats the information in a WeatherData object for display within a C1WebTopicBarItem. The string "\xb0" denotes the degree symbol.
private string FormatWeatherData(WeatherData wd)
{
// WeatherData.Day values use this format: Wednesday, October 18, 2006
// Split at commas, discard the year, and include temperature data
char[] sep = { ',' };
string[] arr = wd.Day.Split(sep);
StringBuilder sb = new StringBuilder();
sb.AppendFormat("{0}
{1}
", arr[0].Trim(), arr[1].Trim());
sb.AppendFormat("Hi: {0}\\xb0", wd.MaxTemperatureF);
sb.Append("F");
sb.AppendFormat(" ({0}\\xb0", wd.MaxTemperatureC);
sb.Append("C)
");
sb.AppendFormat("Lo: {0}\\xb0", wd.MinTemperatureF);
sb.Append("F");
sb.AppendFormat(" ({0}\\xb0", wd.MinTemperatureC);
sb.Append("C)");
return sb.ToString();
}
When the user toggles the radio buttons, this event handler changes the Visible property on the appropriate C1WebTopicBarItem objects, which have already been created.
protected void RadioButtonList1_SelectedIndexChanged(object sender, EventArgs e)
{
// Check the radio buttons for the selected view (today or weekly)
RadioButtonList options = C1WebTopicBar1.FindControl("RadioButtonList1") as RadioButtonList;
bool today = (options == null || options.SelectedValue == "Today");
// Get the second topic bar group (zero-based)
C1WebCustomGroup weatherGroup = C1WebTopicBar1.Groups[1];
// Make all items visible in weekly view
for (int i = 2; i < weatherGroup.Items.Count; i )
{
C1WebTopicBarItem item = weatherGroup.Items[i] as C1WebTopicBarItem;
item.Visible = !today;
}
}
UpdateTheaters changes the zip code parameter on the ObjectDataSource control, then rebinds the master grid to the results of the Select method, which returns an object that implements the IEnumerable interface. The bindings of the detail grid are cleared, pending row selection in the master grid.
private void UpdateTheaters(string zipCode)
{
// Update the zip code parameter in the data source control
ObjectDataSource1.SelectParameters[0].DefaultValue = zipCode;
// Re-bind the master grid and deselect all rows
C1WebGrid1.DataSource = ObjectDataSource1.Select();
C1WebGrid1.DataBind();
C1WebGrid1.SelectedIndex = -1;
// Clear the contents of the detail grid
C1WebGrid2.DataSource = null;
C1WebGrid2.DataBind();
}
Finally, the following event handler is called whenever the user selects a different row in the master grid. Since the DataKeyField of the master grid is set to Movies, indexing the DataKeys collection returns an array of Movie objects. After checking for null values, the array is used as the data source for the detail grid.
protected void C1WebGrid1_SelectedIndexChanged(object sender, EventArgs e)
{
// Use the DataKeys array to obtain a Movie array for the selected row
Movie[] list = C1WebGrid1.DataKeys[C1WebGrid1.SelectedIndex] as Movie[];
// Convert any null values to empty strings
foreach (Movie m in list)
{
if (m.Rating == null)
m.Rating = "";
if (m.RunningTime == null)
m.RunningTime = "";
if (m.ShowTimes == null)
m.ShowTimes = "";
}
// Re-bind the detail grid to the array of Movie objects
C1WebGrid2.DataSource = list;
C1WebGrid2.DataBind();
}
Note that the mash-up code contains no special "Atlas" logic. The same code would also work within a conventional ASP.NET 2.0 application without AJAX. However, the ASP.NET AJAX version is far more fluid and responsive, especially with the added value of the C1UpdateSplitter control.
Visit the official Microsoft ASP.NET AJAX site for the latest information, downloads, documentation, and community forums:
[http://ajax.asp.net](http://ajax.asp.net/)