In Focus

Forecast the Weather with a Custom Control in VB.NET

This article describes the construction of a custom control used to display a three day weather forecast based upon a designated zip code. The control is driven by a public, free web service that returns the seven forecast for any area in the United States by zip code or location. This demonstration only uses the first three days of the seven day forecast and it implements only the zip code based request for forecast data.

Introduction:

This article describes the construction of a custom control used to display a three day weather forecast based upon a designated zip code. The control is driven by a public, free web service that returns the seven forecast for any area in the United States by zip code or location. This demonstration only uses the first three days of the seven day forecast and it implements only the zip code based request for forecast data.

In addition to returning the weather forecast, the web service also returns the place name (e.g., City), State, and the latitude/longitude pair for the zip code. It also returns a few other things that may be of interest such the FIPS code for the location.

Another interesting feature of the web service is that it also returns a path to an image that reflects the forecast (e.g., a picture of it raining outside, or sunny, etc.). With forecast images involving precipitation, the percentage of precipitation is also shown as an addition to the forecast image. This path is used to dynamically load the image when the web service's web method "GetWeatherByZipCode" is evoked.

In using the control, if you were to retain a user's zip code or persist it on the user's machine by stashing it into a cookie, the user see the forecast for their particular geographic location when returning to the site. In the demonstration project, examples are provided of the control initializing with preset zip code and changing the zip code on the fly are both demonstrated.

The associated download includes both the source of the control itself and for a demonstration web site. The public US Forecast web service may be found at this address: http://www.webservicex.net/WS/WSDetails.aspx?CATID=12&WSID=68

WeatherForecast1-in-vb.net.gif

Figure 1: Weather Forecast Custom Control in Use

Getting Started:

The files included with this project include a web control library project and a demonstration web site. In order to get started, open the included zip file and install the two projects onto your file system. Open IIS and create a virtual directory for the web application. Open the solution into Visual 2005 and make any changes necessary to bring both projects into the solution. Once properly configured, your solution explorer should show these projects, references, and files:

WeatherForecast2-in-vb.net.gif

Figure 2: Solution Explorer with Web App and Control Library

In examining the solution, note that the "WeatherReport" control library contains only a single control and that control is called "Forecast". This project also includes a web reference that points to the http://www.webservicex.net site; this public site supplies the web service used to capture the US Weather Forecast information displayed by the control.

The web application contains only a single web page (default.aspx) and includes a reference to the "WeatherReport" DLL.
The web application serves as a container used to test the custom control; the default.aspx page contains a single Forecast control along with some controls used to change the zip code applied to the forecast. The calendar displayed on the web page is just there for eyewash and the hyperlink will open up a new window displaying the US Postal Service's Zip Code finder page.

The Code: Forecast

The "Forecast" custom control is constructed to retrieve the information from the web service upon initialization and to use that information to display the first seven days of the weather forecast. In this demo, I maintain the zip code in view state but I resupply the forecast data each time the control initializes. It would be better to maintain all of the forecast values in view state and only update them in response to a post back event after the zip code has been updated. To keep the code short, I opted not to do that in this demo.

The web service returns the requested data as a class called WeatherForecasts, and the weather details for each day are each included as a collection of subordinate classes called WeatherDetails. The WeatherForecasts object contains the information about the place (city, state, latitude, longitude, etc.) while WeatherDetails contains the date, minimum and maximum temperatures (degrees F and degrees C), and the path to the appropriate weather forecast image.

In examining the code, note that, only the default imports are included in the project. The class itself inherits from the WebControl class.

Imports System

Imports System.Collections.Generic

Imports System.ComponentModel

Imports System.Text

Imports System.Web

Imports System.Web.UI

Imports System.Web.UI.WebControls

Imports System.Xml

 

<ToolboxData("<{0}:Forecast runat=server></{0}:Forecast>")> _

Public Class Forecast

Inherits WebControl

Following the class declaration, a region entitled "Declarations" is created and within that region are the declarations for the private member variables used within the control.

#Region "Declarations"

 

Private mForecast As net.webservicex.www.WeatherForecast

Private WxDetails() As net.webservicex.www.WeatherData

Private Wx As net.webservicex.www.WeatherForecasts

 

#End Region

After the variable declarations, there is another region defined (Methods) and within that region is the code used to capture the data from the web service and populate the mForecast member variable. The initialization handler calls a subroutine called "GetWeather" each time the control is initialized. GetWeather accepts a single argument in the form of a string containing the five digit zip code.

Inside GetWeather, mForecast object is defined as new instance of the web services weather forecast class. From this class, the weather report and weather details are captured and assigned to the appropriate variables. These variables are used directly during rendering to define the contents of the control.

The code contained in the Methods region is as follows:

#Region "Methods"

 

Private Sub Forecast_Init(ByVal sender As Object, ByVal e As System.EventArgs)

Handles Me.Init

 

If Not String.IsNullOrEmpty(ZipCode) Then

GetWeather(ZipCode)

Else

GetWeather("36201")

End If

 

End Sub

 

Public Sub GetWeather(ByVal zip As String)

 

Try

mForecast = New net.webservicex.www.WeatherForecast

Wx = mForecast.GetWeatherByZipCode(zip)

WxDetails = Wx.Details

Catch

Exit Sub

End Try

 

End Sub

 

#End Region

The next region defined in the code is called "Properties"; this section contains the properties used by the control. In this case, aside from what was passed down through the inheritance of the WebControl class, the only property to define is a string value used to contain the zip code and this value is stashed in view state.

To make this a little more efficient, it would be better to stash the weather forecast and weather details into view state or control state as well.

The properties region and its single property are defined as follows:

#Region "Properties"

 

<Category("Weather"), Description("Set Forecast Zip Code"), Browsable(True)> _

Property ZipCode() As String

Get

Dim s As String = CStr(ViewState("ZipCode"))

If s Is Nothing Then

Return String.Empty

Else

Return s

End If

End Get

 

Set(ByVal Value As String)

ViewState("ZipCode") = Value

End Set

End Property

#End Region

The attributes of category, browsable, and description are used to provide design time support for the custom control. The category and description text will be displayed in the IDE's property editor whenever this control is selected by the developer using the control.

Having captured the values necessary for the control through the web service, the only thing left to do is to actually render the control on the page.

The code used to render the control is pretty simple; the HtmlTextWriter is used to define a table and set up its characteristics (cell padding in this example), each row of the table contains one cell, within the cells, text is written out to label the value, and the value itself is added. Once all of the data has been written into the table, the ending tag is rendered and the control is complete.

Each section of the table definition is annotated and empty lines break the table rendering code up into specific sections. If you follow the annotation and the breaks, you should see how the control is rendered easily enough. The process following is basically to define a row, add a cell, add contents to the cell, close the cell, close the row, and move onto the next row.

Naturally, you can change the configuration of the table or remove some of the data returned from the web service by making changes in the definition of the HTML as defined through the HtmlTextWriter. The RenderContents subroutine is overridden and the HTML is formatted within this subroutine through the use of the HtmlTextWriter.

If you wanted to make the control more useful, it might be interesting to build it with a vertical and a horizontal layout option and use the select case statement in the renderer to lay the table out all on one row or as I did it in the following (all in one column). It might also be nice to allow the developer to specify the numbers of days (1 to 7) and use that value to determine how many days to show in the weather report.

#Region "Rendering"

 

Protected Overrides Sub RenderContents(ByVal output As HtmlTextWriter)

 

' the web service actually returns seven days, I am just using the

' first three days to make a 3 day forecast but the additional days

' could be added in a similar manner

 

Try

' set padding and start the table

output.AddStyleAttribute(HtmlTextWriterStyle.Padding, "3")

output.RenderBeginTag(HtmlTextWriterTag.Table)

 

' display location information based on zip code

' in first row of the table

output.RenderBeginTag(HtmlTextWriterTag.Tr)

output.RenderBeginTag(HtmlTextWriterTag.Td)

output.Write("<b>Location: </b>" & Wx.PlaceName.ToString() & ", " & _

Wx.StateCode.ToString() & "<br/>")

output.Write("<b>Zip Code: </b>" & ZipCode & "<br/>")

output.Write("<b>Lat/Long: </b>" & Wx.Latitude.ToString() & _

"/" & Wx.Longitude.ToString() & "<br/>")

output.RenderEndTag()

output.RenderEndTag()

 

' display highs and lows for day 1

output.RenderBeginTag(HtmlTextWriterTag.Tr)

output.RenderBeginTag(HtmlTextWriterTag.Td)

output.Write("<hr/>")

output.Write("<b> Day: </b>" & WxDetails(0).Day.ToString() &

"<br/>")

output.Write("<b> High/Low: </b>" &

WxDetails(0).MaxTemperatureF.ToString() & _

"/" & WxDetails(0).MinTemperatureF.ToString() & "<br/><br/>")

output.RenderEndTag()

output.RenderEndTag()

 

' get weather service image and add it to control

output.AddAttribute(HtmlTextWriterAttribute.Align, "center")

output.RenderBeginTag(HtmlTextWriterTag.Tr)

output.RenderBeginTag(HtmlTextWriterTag.Td)

Dim img As New Image()

img.ImageUrl = WxDetails(0).WeatherImage.ToString()

img.BorderStyle = WebControls.BorderStyle.Inset

img.BorderWidth = 2

img.RenderControl(output)

output.RenderEndTag()

output.RenderEndTag()

 

' display highs and lows for day 2

output.RenderBeginTag(HtmlTextWriterTag.Tr)

output.RenderBeginTag(HtmlTextWriterTag.Td)

output.Write("<hr/>")

output.Write("<b>Day: </b>" & WxDetails(1).Day.ToString() &

"<br/>")

output.Write("<b>High/Low: </b>" &

WxDetails(1).MaxTemperatureF.ToString() & _

"/" & WxDetails(1).MinTemperatureF.ToString() & "<br/><br/>")

output.RenderEndTag()

output.RenderEndTag()

 

' get weather service image and add it to control

output.AddAttribute(HtmlTextWriterAttribute.Align, "center")

output.RenderBeginTag(HtmlTextWriterTag.Tr)

output.RenderBeginTag(HtmlTextWriterTag.Td)

Dim img2 As New Image()

img2.ImageUrl = WxDetails(1).WeatherImage.ToString()

img2.BorderStyle = WebControls.BorderStyle.Inset

img2.BorderWidth = 2

img2.RenderControl(output)

output.RenderEndTag()

output.RenderEndTag()

 

' display highs and lows for day 3

output.RenderBeginTag(HtmlTextWriterTag.Tr)

output.RenderBeginTag(HtmlTextWriterTag.Td)

output.Write("<hr/>")

output.Write("<b>Day: </b>" & WxDetails(2).Day.ToString() &

"<br/>")

output.Write("<b>High/Low: </b>" &

WxDetails(2).MaxTemperatureF.ToString() & _

"/" & WxDetails(2).MinTemperatureF.ToString() & "<br/><br/>")

output.RenderEndTag()

output.RenderEndTag()

 

' get weather service image and add it to control

output.AddAttribute(HtmlTextWriterAttribute.Align, "center")

output.RenderBeginTag(HtmlTextWriterTag.Tr)

output.RenderBeginTag(HtmlTextWriterTag.Td)

Dim img3 As New Image()

img3.ImageUrl = WxDetails(2).WeatherImage.ToString()

img3.BorderStyle = WebControls.BorderStyle.Inset

img3.BorderWidth = 2

img3.RenderControl(output)

output.Write("<br/><br/>")

output.RenderEndTag()

output.RenderEndTag()

 

' close the table

output.RenderEndTag()

 

Catch

 

' the control will not render without contacting the web service

' so just display text if the data is unavailable or the web

' service web method has not be evoked

output.Write("Weather Report Control")

 

End Try

 

End Sub

#End Region

 

The Code: The Demo Site's Default Page

The default.aspx page contained within the demo site serves only a test container for the control. The page contains a table that is laid out such that three rows exist in the left hand column while the right hand column contains one row (three merged cells). In the right hand column, a label was added and set to display "Your 3-day Forecast". A single copy of the custom forecast control was dropped beneath the label. The control's zip code property was set to "36201" which is a valid zip code in the State of Alabama.

On the left hand side of the table, the first cell contains a textbox and a button used to update the zip code applied to the custom control. The cell also contains a hyperlink used to open up the US Postal Service's Zip Code Finder web site. I dropped a calendar control into the middle cell but it does not serve any useful purpose other than to display the date. The bottom cell in the left hand column is empty.

WeatherForecast3-in-vb.net.gif

Figure 3: Setting the Forecast Control Properties at Design Time

There is not a lot of code to speak of in the default.aspx page; the button click event handler used to update the zip code is the only of interest. In this code, the textbox is checked for content and for the presence of letters, if the checks are passed, the custom control's zip code property is updated and the control's public "GetWeather" subroutine is evoked. Once the zip code property has been changed, the GetWeather subroutine will force an update of the custom control's weather information and display the new data in the control.

The click event handler's code is as follows:

Protected Sub Button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button1.Click

 

If Not String.IsNullOrEmpty(txtZipCode.Text.ToString()) Then

 

Try

 

Dim chr() As Char = txtZipCode.Text.ToCharArray()

Dim iLoop As Integer

For iLoop = 0 To chr.Length - 1

If Char.IsLetter(chr(iLoop)) Then

txtZipCode.Text = "INVALID"

Exit Sub

End If

Next

 

Forecast1.ZipCode = txtZipCode.Text

Forecast1.GetWeather(txtZipCode.Text)

 

Catch ex As Exception

 

txtZipCode.Text = "ERROR"

End Try

 

End If

 

End Sub

Summary

This project was intended to describe a useful, easy to build custom control. While this demonstration was limited to describing the Forecast custom control, the same approach applied herein would work with a variety of other custom controls.