Introduction
This article describes the construction of a custom control used to check stock prices as made available through a public web service. The article includes the source code for this custom control as well as a demonstration site used to test the control.
Figure 1: Stock Tracker Custom Controls 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 Studio 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:
Figure 2: Solution Explorer with Web App and Control Library
In examining the solution, note that the “StockTracker” control library contains only a single control and that control is called “StockDog
”. This project also includes a web reference that points to this site; this public site supports the web service used to capture the stock prices.
The web application contains only a single web page (default.aspx) and includes a reference to the “StockTracker” DLL.
The web application serves as a container used to test the custom control; the default.aspx page contains three separate instances of the custom control. Each of these three instances is directed to retrieve stock information from three separate stocks.
The Code: StockDog
The “StockDog
” custom control is constructed to retrieve the information from the web service upon initialization and to use that information to populate a collection of local member variables; each of the local member variables is set to contain one of the values collected from the web service.
The web service returns the requested data in the form of an XML string; this string is parsed to obtain the individual values used to populate the member variables. These member variables are in turn used to display the stock information when the page is rendered.
In examining the code, note that, aside from the default imports, only the System.XML
class has been added. 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
<DefaultProperty("StockTicker"), _
ToolboxData("<{0}:StockDog " & _
"runat=server></{0}:StockDog>")> _
Public Class StockDog
Inherits WebControl
Following the class declaration, a region entitled “Declarations” is created and within that region are the declarations for all of the private member variables used within the control.
#Region "Declarations"
Private mStocks As net.webservicex.www.StockQuote
Private mXmlDoc As XmlDocument
Private mStockTicker As String
Private mLast As String
Private mDate As String
Private mTime As String
Private mChange As String
Private mOpen As String
Private mHigh As String
Private mLow As String
Private mVolume As String
Private mMktCap As String
Private mPrevClose As String
Private mPercentChng As String
Private mAnnRange As String
Private mEarns As String
Private mPE As String
Private mName As String
#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 to read the XML string returned by that service and use the values to populate the member variables. The init
handler calls a subroutine called “Fetch
” each time the control is initialized. Fetch
accepts a single argument in the form of a string bearing the stock symbol (e.g., MSFT for Microsoft).
Inside Fetch
, a new XML document is created. The mStocks
string variable is used to capture the XML string returned from the web service’s GetQuote
web method. GetQuote
accepts the stock symbol as an argument. The returned string is loaded as XML into the XML document.
After the data is returned and placed inside the XML document, the next windy bit of code locates each specific node and captures the value associated with that node which is in turn used to populate the appropriate local member variable.
The code contained in the Methods region is as follows:
#Region "Methods"
Private Sub StockDog_Init(ByVal sender As Object, _
ByVal e As System.EventArgs) Handles Me.Init
Fetch(StockTicker)
End Sub
Public Sub Fetch(ByVal strStock As String)
mXmlDoc = New XmlDocument()
mStocks = New net.webservicex.www.StockQuote
Dim strQuote As String = mStocks.GetQuote(strStock)
mXmlDoc.LoadXml(strQuote)
Dim lastNodes As XmlNodeList
Dim lastNode As XmlNode
lastNodes = mXmlDoc.GetElementsByTagName("Last")
For Each lastNode In lastNodes
mLast = lastNode.ChildNodes(0).Value
Next
Dim dateNodes As XmlNodeList
Dim dateNode As XmlNode
dateNodes = mXmlDoc.GetElementsByTagName("Date")
For Each dateNode In dateNodes
mDate = dateNode.ChildNodes(0).Value
Next
Dim timeNodes As XmlNodeList
Dim timeNode As XmlNode
timeNodes = mXmlDoc.GetElementsByTagName("Time")
For Each timeNode In timeNodes
mTime = timeNode.ChildNodes(0).Value
Next
Dim changeNodes As XmlNodeList
Dim changeNode As XmlNode
changeNodes = mXmlDoc.GetElementsByTagName("Change")
For Each changeNode In changeNodes
mChange = changeNode.ChildNodes(0).Value
Next
Dim openNodes As XmlNodeList
Dim openNode As XmlNode
openNodes = mXmlDoc.GetElementsByTagName("Open")
For Each openNode In openNodes
mOpen = openNode.ChildNodes(0).Value
Next
Dim highNodes As XmlNodeList
Dim highNode As XmlNode
highNodes = mXmlDoc.GetElementsByTagName("High")
For Each highNode In highNodes
mHigh = highNode.ChildNodes(0).Value
Next
Dim lowNodes As XmlNodeList
Dim lowNode As XmlNode
lowNodes = mXmlDoc.GetElementsByTagName("Low")
For Each lowNode In lowNodes
mLow = lowNode.ChildNodes(0).Value
Next
Dim volumeNodes As XmlNodeList
Dim volumeNode As XmlNode
volumeNodes = mXmlDoc.GetElementsByTagName("Volume")
For Each volumeNode In volumeNodes
mVolume = volumeNode.ChildNodes(0).Value
Next
Dim MrkCapNodes As XmlNodeList
Dim MrkCapNode As XmlNode
MrkCapNodes = mXmlDoc.GetElementsByTagName("MktCap")
For Each MrkCapNode In MrkCapNodes
mMktCap = MrkCapNode.ChildNodes(0).Value
Next
Dim prevCloseNodes As XmlNodeList
Dim prevCloseNode As XmlNode
prevCloseNodes = _
mXmlDoc.GetElementsByTagName("PreviousClose")
For Each prevCloseNode In prevCloseNodes
mPrevClose = prevCloseNode.ChildNodes(0).Value
Next
Dim percentChngNodes As XmlNodeList
Dim percentChngNode As XmlNode
percentChngNodes = _
mXmlDoc.GetElementsByTagName("PercentageChange")
For Each percentChngNode In percentChngNodes
mPercentChng = percentChngNode.ChildNodes(0).Value
Next
Dim annRangeNodes As XmlNodeList
Dim annRangeNode As XmlNode
annRangeNodes = mXmlDoc.GetElementsByTagName("AnnRange")
For Each annRangeNode In annRangeNodes
mAnnRange = annRangeNode.ChildNodes(0).Value
Next
Dim earnsNodes As XmlNodeList
Dim earnsNode As XmlNode
earnsNodes = mXmlDoc.GetElementsByTagName("Earns")
For Each earnsNode In earnsNodes
mEarns = earnsNode.ChildNodes(0).Value
Next
Dim PENodes As XmlNodeList
Dim PENode As XmlNode
PENodes = mXmlDoc.GetElementsByTagName("P-E")
For Each PENode In PENodes
mPE = PENode.ChildNodes(0).Value
Next
Dim nameNodes As XmlNodeList
Dim nameNode As XmlNode
nameNodes = mXmlDoc.GetElementsByTagName("Name")
For Each nameNode In nameNodes
mName = nameNode.ChildNodes(0).Value
Next
End Sub
#End Region
The next region defined in the code is called “Properties”; as you likely guessed, 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 stock symbol. The properties region and its single property are defined as follows:
#Region "Properties"
<Category("Stock Symbol")> _
<Browsable(True)> _
<Description("Enter the stock ticker value.")> _
Public Property StockTicker() As String
Get
Return mStockTicker
End Get
Set(ByVal value As String)
mStockTicker = 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 from the XML string returned from the web service, and having populated the local member variables using the values contained in the XML, the only remaining step is to render the control. A region entitled, “Rendering” follows and it contains the code necessary to 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 and border in this example), each row of the table contains two cells, a label and its associated value are placed into each of those two cells. Once all of the data has been written into the table, the ending tag is rendered and the control is complete.
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
.
#Region "Rendering"
Protected Overrides Sub RenderContents(ByVal output As HtmlTextWriter)
output.AddAttribute(HtmlTextWriterAttribute.Cellpadding, "3")
output.AddAttribute(HtmlTextWriterAttribute.Border, "1")
output.RenderBeginTag(HtmlTextWriterTag.Table)
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Stock: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(StockTicker)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Company: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mName)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Last: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mLast)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Date: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mDate)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Time: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mTime)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Change: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mChange)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Open: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mOpen)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>High: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mHigh)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Low: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mLow)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Volume: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mVolume)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Market Cap: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mMktCap)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Previous Close: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mPrevClose)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Percent Change: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mPercentChng)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Annual Range: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mAnnRange)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>Earnings: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mEarns)
output.RenderEndTag()
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Tr)
output.AddAttribute(HtmlTextWriterAttribute.Align, "left")
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write("<b>P-E: </b>")
output.RenderEndTag()
output.RenderBeginTag(HtmlTextWriterTag.Td)
output.Write(mPE)
output.RenderEndTag()
output.RenderEndTag()
output.RenderEndTag()
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 panel with a centered table. The table contains three columns and one row. A single custom control is added to each of the three columns. Each of the custom controls has its StockTicker
property set through property editor in the IDE. Beneath the table is a button; this button carries the label “Update” and the button only serves to force a post back which in turn will force the update of each of the custom controls.
Figure 3: Setting the StockTicker
Property at Design Time
Summary
This project was intended to describe a useful, easy to build custom control. While this demonstration was limited to describing the StockTracker
custom control, the same approach applied herein would work with a variety of other custom controls.