Building the Web Pages

The next step in the application is to build the Web pages. There are several pages required for this application, but we make the best use of the profile editor by using it for both adding users and editing existing users.

Creating the Login Page

Because the Web pages are interconnected via the security scheme and the users table, it's probably best to start with the login page's code first. The page is named login.aspx, and the front-end code is shown in Listing 15.9.

Listing 15.9. login.aspx—Front-End Code for the Login Page
<%@ Page language="vb" AutoEventWireup="false" Inherits="WS_Ch15.Login" TargetSchema="http
://schemas.microsoft.com/intellisense/ie5" CodeBehind="login.aspx.vb" %>
<%@ Register TagPrefix="Portal" Tagname="Header" Src="header.ascx" %>
							<%@ Register TagPrefix="Portal" Tagname="Footer" Src="footer.ascx" %>
							<Portal:Header id="PageHeader" runat="server" Title="Login" /></P>
<asp:label id="lblMessage" runat="server" class="text">
Please enter your user name (or e-mail address) and password in the form below.</p>
</asp:label>
<P class="text">Not a registered user yet?
 <a href="profile.aspx">Register today!</a></P>
<form runat="server">
  <table cellspacing="5">
    <tr class="text">
      <td align="right"><b>User Name:</b></td>
      <td>
        <asp:textbox id="txtUserName" columns="40"
           maxlength="100" runat="server" />
      </td>
    </tr>
    <tr class="text">
      <td align="right"><b>Password:</b></td>
      <td>
        <asp:textbox id="txtPassword" textmode="Password"
           columns="20" maxlength="20" runat="server" />
      </td>
    </tr>
    <tr class="text">
      <td align="right"><asp:CheckBox id="chkSavePassword"
         runat="server" /></td>
      <td class="text">Save password on this computer?</td>
    </tr>
    <tr class="text">
      <td colspan="2">&nbsp;</td>
    </tr>
    <tr class="text">
      <td colspan="2" align="middle">
        <input type="submit" id="btnLogin" runat="server"
           value="Log In">
        <input type="reset" id="btnReset" runat="server"
           value="Clear">
      </td>
    </tr>
  </table>
</form>
<Portal:Footer id="PageFooter" runat="server" />
						

For the most part, this page is a standard data entry form, with the exception of the highlighted code. The code marked in this listing is used to register and use the Web User Controls in the page. The Register directives name the controls and provide links to the source code for each control. When registered, the controls can be used by specifying the type of control, the ID with which they are accessed, and that they should “runat” the server. In the Header control, the Title is also specified as an attribute of the tag. This attribute maps directly to the public variable that you created as part of the Header control. At the bottom of the page, the Footer tag is referenced and is displayed on the page. Because the footer doesn't have any public variables, no additional declarations are required here.

The code behind this login page is shown in Listing 15.10.

Listing 15.10. login.aspx.vb—Code Behind for the Login Page
Imports System.Data.SqlClient
Imports System.Web.Security

Public Class Login
  Inherits System.Web.UI.Page
  Protected WithEvents lblMessage As System.Web.UI.WebControls.Label
  Protected WithEvents txtUserName As System.Web.UI.WebControls.TextBox
  Protected WithEvents txtPassword As System.Web.UI.WebControls.TextBox
  Protected WithEvents chkSavePassword As System.Web.UI.WebControls.CheckBox
  Protected WithEvents btnLogin As System.Web.UI.HtmlControls.HtmlInputButton
  Protected WithEvents btnReset As System.Web.UI.HtmlControls.HtmlInputButton
  Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)
 Handles MyBase.Load
    Dim strUserID As String
    If (Page.IsPostBack) Then
      Dim db As New Database()
      Dim dr As SqlDataReader = db.GetDataReader("sp_GetUser '" & txtUserName.Text & "',
 '" & txtPassword.Text & "'", True)
      If dr.Read() Then
        strUserID = dr("pkUserID")
        dr.Close()
        FormsAuthentication.RedirectFromLoginPage(strUserID, chkSavePassword.Checked)
      Else
        dr.Close()
        lblMessage.Text = "ERROR: The user name and/or password you entered were incorrect."
        lblMessage.CssClass = "errortext"
      End If
    End If
  End Sub
End Class
						

After the server-side controls have been declared, the next code takes care of the case in which the user has entered data in the form and clicked the Login button. The username and password go into a stored procedure call to verify that the user name/password combination exists. The GetDataReader normally reads the first record to “prime” the reader so you don't have to do a read to get the first record. In this case, we tell the GetDataReader to omit that initial read so that the code here can check the result of the Read method. If it returns a false, no records were returned and the user name/password combination was invalid. In this case, the lblMessage control's text is changed and the style class is changed to be errortext, which is a bold red font.

If the username and password are good, the routine reads the user ID value, closes the data reader, and stores the ID value as the value of the authentication cookie. This gives us easy access to that ID value in other pages in the system. The RedirectFromLoginPage has two functions—set the authentication cookie and mark it as persistent or not, and to send the user on his/her way to the page that was originally requested.

Creating the Profile Editor

The next page you need to build is the profile editor, which is used for both creating new users and editing existing users. When it is being used in “new user” mode, it looks like Figure 15.3.

Figure 15.3. Profile editor screen for the portal application.


In profile editor mode, only the ZIP code and stock boxes are available, because the application does not allow the username to be changed. Changing the password is tricky, because ASP.NET does not allow password text fields to be assigned values. This is probably due to security concerns, but I couldn't find any documentation on the actual reasoning. Typically, I would create a form that has an Old Password and then New Password and Confirm Password boxes. This bypasses the issue by making the user type in the old and new passwords, eliminating the need to populate the boxes.

The HTML code for this page is shown in Listing 15.11.

Listing 15.11. profile.aspx —HTML Code for the Profile Editor
<%@ Page Language="vb" AutoEventWireup="false"
  Codebehind="profile.aspx.vb" Inherits="WS_Ch15.profile"%>
<%@ Register TagPrefix="Portal" Tagname="Header" Src="header.ascx" %>
<%@ Register TagPrefix="Portal" Tagname="Footer" Src="footer.ascx" %>
<Portal:Header id="PageHeader" runat="server" Title="Profile Editor" />
<p class="text">Your profile allows you to store your current ZIP code for the
  local weather in your area, as well as an unlimited number of stock symbols
  that you wish to track. Enter the symbols in the box, separated by at least one
  space.
</p>
<form runat="server" ID="frmUser">
  <asp:ValidationSummary id="valSummary" runat="server"
     CssClass="errortext"
     HeaderText=
       "ERROR: The following problems were found and need to be corrected:">
  </asp:ValidationSummary>
  <table cellspacing="5">
    <tr class="text" id="trUserName" runat="server">
      <td align="right" class="boldtext">User Name:</td>
      <td>
        <asp:textbox id="txtUserName" columns="20" maxlength="20"
           runat="server" Text='<%# dr("UserName") %>' />
        <asp:RequiredFieldValidator id="valReqUserName" runat="server"
           ErrorMessage="User name is a required field."
           ControlToValidate="txtUserName" Display="None">
        </asp:RequiredFieldValidator>
      </td>
    </tr>
    <tr class="text" runat="server" id="trPassword">
      <td align="right" class="boldtext">Password:</td>
      <td>
        <asp:textbox id="txtPassword" textmode="Password"
           columns="20" maxlength="20" runat="server"
           Text='<%# dr("Password") %>' />
        <asp:RequiredFieldValidator id="valReqPassword" runat="server"
           ErrorMessage="Password is a required field."
           ControlToValidate="txtPassword" Display="None">
        </asp:RequiredFieldValidator>
      </td>
    </tr>
    <tr class="text" runat="server" id="trConfirmPassword">
      <td align="right" class="boldtext">Confirm Password:</td>
      <td>
        <asp:textbox id="txtConfirmPassword" textmode="Password"
          columns="20" maxlength="20" runat="server"
          Text='<%# dr("Password") %>' />
        <asp:RequiredFieldValidator id="valReqConfirm" runat="server"
           ErrorMessage="Please confirm the password you entered."
           ControlToValidate="txtConfirmPassword" Display="None">
        </asp:RequiredFieldValidator>
        <asp:CompareValidator id="valCompConfirm" runat="server"
           ErrorMessage="The passwords you entered do not match."
           ControlToValidate="txtConfirmPassword"
           ControlToCompare="txtPassword" Display="None">
        </asp:CompareValidator>
      </td>
    </tr>
    <tr class="text">
      <td align="right" class="boldtext">ZIP Code:</td>
      <td>
        <asp:textbox id="txtZIP" columns="10" maxlength="50"
           runat="server"  Text='<%# dr("ZipCode") %>' />
        <asp:RequiredFieldValidator id="valReqZip" runat="server"
           ErrorMessage="ZIP code is a required field."
           ControlToValidate="txtZIP" Display="None">
        </asp:RequiredFieldValidator>
      </td>
    </tr>
    <tr class="text">
      <td align="right" class="boldtext">Stock Symbol(s):</td>
      <td>
        <asp:textbox id="txtStocks" TextMode="MultiLine" Wrap="True"
           Rows="10" Columns="60" runat="server" />
      </td>
    </tr>
    <tr class="text">
      <td colspan="2">&nbsp;</td>
    </tr>
    <tr class="text">
      <td colspan="2" align="middle">
        <input type="submit" id="btnSave" runat="server" value="Save"
          NAME="btnSave"> <input type="reset" id="btnReset" value="Clear">
      </td>
    </tr>
  </table>
</form>
<Portal:Footer id="PageFooter" runat="server" />
						

To handle validation on this page, you can use the various validation controls included with .NET. If you're building this page in the development environment, you may need to add the controls by right-clicking your Toolbox and selecting the Customize menu choice. In my installation, these controls were not included in the Toolbox at startup.

The following are the basic validation rules:

  • Username, password, password confirmation, and ZIP code are required.

  • Password and password confirmation must match.

  • The stock box is optional.

Each of the validation controls handles one of these rules. Each field marked as required has a required field validator linked to it. For the password boxes, a CompareValidator makes sure that the boxes match.

When all the validation is complete, a ValidationSummary control displays all the errors in a single list instead of individual nagging prompts, as many sites often do. The ValidationSummary control can be configured to display a message box in addition to putting the text on the page, but when it does, the error is consolidated into a single message.

The one validation we are not doing is verifying that the user ID does not already exist. This could be done using a custom validation control, which can also be added to the page. If you decide to add this code, you may also want to validate that the e-mail address does not already exist either.

The code behind this page is shown in Listing 15.12.

Listing 15.12. profile.aspx.vb—Code Behind for the Profile Editor
Imports System.Data.SqlClient
Imports System.Web.Security

Public Class profile
  Inherits System.Web.UI.Page
  Protected WithEvents txtUserName As System.Web.UI.WebControls.TextBox
  Protected WithEvents txtPassword As System.Web.UI.WebControls.TextBox
  Protected WithEvents txtConfirmPassword As System.Web.UI.WebControls.TextBox
  Protected WithEvents txtZIP As System.Web.UI.WebControls.TextBox
  Protected WithEvents txtStocks As System.Web.UI.WebControls.TextBox
  Protected WithEvents btnSave As System.Web.UI.HtmlControls.HtmlInputButton
  Protected WithEvents trUserName As System.Web.UI.HtmlControls.HtmlTableRow
  Protected WithEvents trPassword As System.Web.UI.HtmlControls.HtmlTableRow
  Protected WithEvents trConfirmPassword As System.Web.UI.HtmlControls.HtmlTableRow
  Protected dr As SqlDataReader
  Protected WithEvents valReqUserName As System.Web.UI.WebControls.RequiredFieldValidator
  Protected WithEvents valReqPassword As System.Web.UI.WebControls.RequiredFieldValidator
  Protected WithEvents valReqConfirm As System.Web.UI.WebControls.RequiredFieldValidator
  Protected WithEvents valCompConfirm As System.Web.UI.WebControls.CompareValidator
  Protected WithEvents valReqZip As System.Web.UI.WebControls.RequiredFieldValidator
  Protected WithEvents valSummary As System.Web.UI.WebControls.ValidationSummary
  Private db As Database
#Region " Web Form Designer Generated Code "

  'This call is required by the Web Form Designer.
  <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()

  End Sub

  Private Sub Page_Init(ByVal sender As System.Object, ByVal e As System.EventArgs)
 Handles MyBase.Init
    'CODEGEN: This method call is required by the Web Form Designer
    'Do not modify it using the code editor.
    InitializeComponent()
  End Sub

#End Region

  Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)
 Handles MyBase.Load
    db = New Database()
    Dim strStocks As String
    If Not Page.IsPostBack Then
      If User.Identity.Name <> "" Then
        trUserName.Visible = False
        trPassword.Visible = False
        trConfirmPassword.Visible = False
        dr = db.GetDataReader("sp_GetUserByID " & User.Identity.Name)
        Page.DataBind()
        dr.Close()
        dr = db.GetDataReader("sp_GetUserStocks " & User.Identity.Name, True)
        Do While dr.Read()
          strStocks += dr("pkStockID") + " "
        Loop
        txtStocks.Text = strStocks
        dr.Close()
      Else
        trUserName.Visible = True
        trPassword.Visible = True
        trConfirmPassword.Visible = True
      End If
    End If
  End Sub

  Private Sub btnSave_ServerClick(ByVal sender As System.Object, ByVal e As System
.EventArgs) Handles btnSave.ServerClick
    Dim strLoop As String
    If User.Identity.Name = "" Then
      If Page.IsValid Then
        db.Execute("INSERT INTO tblUsers (UserName, Password, ZipCode) " & "VALUES " _
          & "('" & txtUserName.Text & "', '" & txtPassword.Text & "', '" & txtZIP.Text & "')")
        Dim dr As SqlDataReader = db.GetDataReader("sp_GetUser '" & txtUserName.Text & "',
 '" & txtPassword.Text & "'")
        FormsAuthentication.SetAuthCookie(dr("pkUserID"), False)
        dr.Close()

        For Each strLoop In txtStocks.Text.Split(" ")
          db.Execute("INSERT INTO tblUserStocks (pkStockID, fkUserID) " & "VALUES ('" &
 strLoop & "', " & User.Identity.Name & ")")
        Next
        Response.Redirect("default.aspx")
      End If
    Else
      If Page.IsValid Then
        db.Execute("UPDATE tblUsers SET ZipCode = '" & txtZIP.Text & "' WHERE pkUserID = "
 & User.Identity.Name)
        db.Execute("DELETE FROM tblUserStocks WHERE fkUserID = " & User.Identity.Name)
        For Each strLoop In txtStocks.Text.Split(" ")
          db.Execute("INSERT INTO tblUserStocks (pkStockID, fkUserID) " & "VALUES ('" &
 strLoop & "', " & User.Identity.Name & ")")
        Next
        Response.Redirect("default.aspx")
      End If
    End If
    db.Close()
  End Sub
End Class
						

Because this page is used for both adding and editing, we can use the User.Identity.Name object to tell us if the user is logged in or not. Remember the user ID that we stored in the authentication cookie? That's the value that will be in the User.Identity.Name field. If the user is not logged in, User.Identity.Name will be an empty string. This gives us an easy way to tell which mode we're in. Note that this page will not run properly if you're not logged in, because it uses the User.Identity.Name to update profile information.

If the user is just starting out and isn't logged in, we only run code in this portion of the page if the page is valid. This property is set by the validation controls on the form. If any errors exist, this property will be set to False. When we know that the data meets our validation rules, the new user is inserted into the table, and then the new user ID number is retrieved and stored in the authentication cookie using the SetAuthCookie method. The SetAuthCookie method works in a similar way to RedirectFromLoginPage, just without the redirection to another page. Calling SetAuthCookie saves the user from having to log in after they register.

After the user ID has been retrieved, the stock text box is split wherever a space is found, and each row is inserted into the tblUserStocks table.

In cases where we are editing a user's profile, we have to populate the form and update the ZIP and the stock table. If you go back to the previous listing, you will see the controls on the page look like the following:

<asp:textbox id="txtConfirmPassword"
  textmode="Password"
  columns="20"
  maxlength="20"
  runat="server"
  Text='<%# dr("Password") %>' />

The Text property is being bound to an object named dr, which happens to be a SqlDataReader. When we call Page.DataBind(), the controls on the ASP.NET page are bound to the properties of the SqlDataReader and the data currently there is filled into the box. If the DataBind method is never called, this code is ignored. This saves us the trouble of populating these fields manually. We still have to fill the stock list, but that is easily done using another SqlDataReader object and a simple loop.

When we're ready to save the data, we write an UPDATE SQL statement instead of an INSERT. Other than that, the update code is basically the same as the new record.

Writing the Portal Page

The next page you need to create is the portal page itself. This page requires the use of four Web Services that were available at the time of writing. If any of these services go away, you can find new ones at XMethods.net. The four services that should be added as Web References are

  • NewsService>http://www.xmlme.com/WSCustNews.asmx?WSDL

  • StockServicehttp://ws.cdyne.com/delayedstockquote/delayedstockquote.asmx?wsdl

  • WeatherServicehttp://www.vbws.com/services/weatherretriever.asmx?WSDL

  • ZipCodeServicehttp://www.alethea.net/webservices/ZipCode.asmx?WSDL

The HTML code for the page is somewhat lengthy due to the two Repeater controls being used for data formatting. The HTML code is shown in Listing 15.13.

Listing 15.13. default.aspx—HTML Code for the Portal Page
<%@ Register TagPrefix="Portal" Tagname="Footer" Src="footer.ascx" %>
<%@ Register TagPrefix="Portal" Tagname="Header" Src="header.ascx" %>
<%@ Page Language="vb" AutoEventWireup="false"
    Codebehind="default.aspx.vb" Inherits="WS_Ch15.HomePage"%>
<Portal:Header id="PageHeader" runat="server" Title="My Home Page" />
<p class="text"><b>Actions:</b> [
<a href="profile.aspx">Edit My Profile</a> | <a href="logout.aspx">
    Log Out</a> ]</p>
<hr noshade>
<p class="subheading">Current Weather</p>
<table cellspacing="4" cellpadding="4" id="tblWeather" runat="server">
  <tr>
    <td valign="center">
      <img id="imgGraphic" runat="server">
    </td>
    <td valign="top">
      <asp:Label id="lblCityState" runat="server" class="largetext" />
      <table cellpadding="0" cellspacing="0">
        <tr>
          <td class="text">Temperature:</td>
          <td class="text"><asp:Label id="lblTemperature"
             runat="server" class="text" /></td>
        </tr>
        <tr>
          <td class="text">Conditions:</td>
          <td class="text"><asp:Label id="lblConditions"
             runat="server" class="text" /></td>
        </tr>
        <tr>
          <td class="text">Humidity:</td>
          <td class="text"><asp:Label id="lblHumidity"
              runat="server" class="text" /></td>
        </tr>
      </table>
    </td>
  </tr>
</table>

<ASP:Repeater id="rptStocks" runat="server">
  <HeaderTemplate>
    <hr noshade>
    <table cellpadding="4" cellspacing="0" width="100%">
    <tr>
      <td colspan="2" class="subheading">Stock Watcher</td>
    </tr>
    <tr class="tableheading">
      <td width="30%" align="center">Company Name</td>
      <td width="10%" align="center">Last Price</td>
      <td width="10%" align="center">Change</td>
      <td width="10%" align="center">% Change</td>
      <td width="20%" align="center">Time</td>
      <td width="20%" align="center">Actions</td>
    </tr>
  </HeaderTemplate>
  <ItemTemplate>
    <tr class="tabletext">
      <td><%# DataBinder.Eval(Container.DataItem, "CompanyName") %></td>
      <td align="right">
      <%# DataBinder.Eval(Container.DataItem, "LastTradeAmount") %></td>
      <td align="right">
      <%# DataBinder.Eval(Container.DataItem, "StockChange") %></td>
      <td align="center">
      <%# DataBinder.Eval(Container.DataItem, "ChangePercent") %></td>
      <td align="center">
      <%# DataBinder.Eval(Container.DataItem, "LastTradeDateTime") %></td>
      <td align="center">
      <a href="http://quote.yahoo.com/q?d=v1&s=<%# DataBinder.Eval
      (Container.DataItem, "StockSymbol") %>" target=_blank>View&nbsp;
       Details</a>
      </td>
    </tr>
  </ItemTemplate>
  <AlternatingItemTemplate>
    <tr class="tabletext_gray">
      <td><%# DataBinder.Eval(Container.DataItem, "CompanyName") %></td>
      <td align="right"><%# DataBinder.Eval(Container.DataItem,
         "LastTradeAmount") %></td>
      <td align="right"><%# DataBinder.Eval(Container.DataItem,
      "StockChange") %></td>
      <td align="center"><%# DataBinder.Eval(Container.DataItem,
      "ChangePercent") %></td>
      <td align="center"><%# DataBinder.Eval(Container.DataItem,
         "LastTradeDateTime") %></td>
      <td align="center">
      <a href="http://quote.yahoo.com/q?d=v1&s=<%# DataBinder.Eval
      (Container.DataItem, "StockSymbol") %>" target=_blank>
       View&nbsp;Details</a>
      </td>
    </tr>
  </AlternatingItemTemplate>
  <FooterTemplate>
    </table>
  </FooterTemplate>
</ASP:Repeater>

<ASP:Repeater id="rptArticles" runat="server">
  <HeaderTemplate>
    <hr noshade>
    <table cellpadding="4" cellspacing="0" width="100%">
    <tr>
      <td colspan="2" class="subheading">News Headlines</td>
    </tr>
    <tr class="tableheading">
      <td width="60%" align="center">Article Title</td>
      <td width="30%" align="center">Date/Time</td>
      <td width="10%" align="center">Actions</td>
    </tr>
  </HeaderTemplate>
  <ItemTemplate>
    <tr class="tabletext">
      <td valign="top">
      <%# DataBinder.Eval(Container.DataItem, "headline_text") %><br>
      <i><%# DataBinder.Eval(Container.DataItem, "source") %></i>
      </td>
      <td valign="top" align="center">
      <%# DateTime.Parse(DataBinder.Eval(Container.DataItem,
      "harvest_time")).ToString("MM/dd/yyyy hh:mm tt") %>
      </td>
      <td valign="top" align="center">
      <a href="<%# DataBinder.Eval(Container.DataItem, "url") %>"
         target=_blank>View</a>
      </td>
    </tr>
  </ItemTemplate>
  <AlternatingItemTemplate>
    <tr class="tabletext_gray">
      <td valign="top">
      <%# DataBinder.Eval(Container.DataItem, "headline_text") %><br>
      <i><%# DataBinder.Eval(Container.DataItem, "source") %></i>
      </td>
      <td valign="top" align="center">
      <%# DateTime.Parse(DataBinder.Eval(Container.DataItem,
      "harvest_time")).ToString("MM/dd/yyyy hh:mm tt") %>
      </td>
      <td valign="top" align="center">
      <a href="<%# DataBinder.Eval(Container.DataItem, "url") %>"
         target=_blank>View</a>
      </td>
    </tr>
  </AlternatingItemTemplate>
  <FooterTemplate>
    </table>
  </FooterTemplate>
</ASP:Repeater>


<Portal:Footer id="PageFooter" runat="server" />

There are three distinct sections on this page—the weather display, the stock ticker display, and the headline viewer. The code behind will make this clearer, because each section is populated via a separate function. The code-behind is shown in Listing 15.14.

Listing 15.14. default.aspx.vb—Code Behind for the Portal Page
Imports System.Data.SqlClient
Imports System.IO

Public Class HomePage
    Inherits System.Web.UI.Page
    Protected WithEvents lblCityState As System.Web.UI.WebControls.Label
    Protected WithEvents lblTemperature As System.Web.UI.WebControls.Label
    Protected WithEvents lblConditions As System.Web.UI.WebControls.Label
    Protected WithEvents lblHumidity As System.Web.UI.WebControls.Label
    Protected WithEvents tblHeadlines As System.Web.UI.HtmlControls.HtmlTable
    Protected WithEvents imgGraphic As System.Web.UI.HtmlControls.HtmlImage
    Protected WithEvents rptArticles As Repeater
    Protected WithEvents rptStocks As Repeater
    Private RowCount As Integer = 0
    Private SS As StockService.DelayedStockQuote

    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)
 Handles MyBase.Load
      Dim db As New Database()
      Dim dr As SqlDataReader = db.GetDataReader("sp_GetUserByID " & User.Identity.Name)

      RetrieveWeather(dr("ZipCode"))
      RetrieveNews()
      dr.Close()
      RetrieveStocks(db)
    End Sub

    Private Sub RetrieveWeather(ByVal ZipCode As String)
      Dim cs As New ZipCodeService.ZipCode()
      Dim ws As New WeatherService.WeatherRetriever()
      Dim wi As WeatherService.CurrentWeather = ws.GetWeather(ZipCode)
      Try
        lblCityState.Text = cs.ZipCodeToCityState(ZipCode)(0)
      Catch e As Exception
        lblCityState.Text = "ZIP Code " & ZipCode
      End Try
      imgGraphic.Src = wi.IconUrl
      lblTemperature.Text = wi.CurrentTemp.ToString("#0") & "&deg; F"
      lblConditions.Text = wi.Conditions
      lblHumidity.Text = (wi.Humidity * 100).ToString("#0") & "%"
      cs = Nothing
      ws = Nothing
      wi = Nothing
    End Sub

    Private Sub RetrieveNews()
      Dim ds As New DataSet()

      Dim ns As New NewsService.GetCustomNews()
      Dim strNews As String = ns.GetCustomNews("AP")
      Dim fn As String = Server.MapPath("news" & DateTime.Now.ToString("yyyymmdd-hhmmss")
 + ".xml")
      Dim sr As New StreamWriter(fn, False)
      sr.Write(strNews)
      sr.Close()
      ds.ReadXml(fn)
      File.Delete(fn)

      Dim dt As DataTable = ds.Tables(0)
      Dim i As Integer
      Dim dtLimited As DataTable

      If dt.Rows.Count > 10 Then
        dtLimited = ds.Tables(0).Clone()
        For i = 0 To 9
          dtLimited.ImportRow(dt.Rows(i))
        Next
        rptArticles.DataSource = dtLimited.DefaultView
        rptArticles.DataBind()
      Else
        rptArticles.DataSource = ds.Tables(0).DefaultView
        rptArticles.DataBind()
      End If
    End Sub

    Private Sub RetrieveStocks(ByVal db As Database)
      Dim strStocks As String
      Dim alStockInfo As New ArrayList()
      Dim sd As StockService.QuoteData
      Dim md As MyStockData
      SS = New StockService.DelayedStockQuote()
      Dim dr As SqlDataReader = db.GetDataReader("sp_GetUserStocks " & User.Identity.Name,
 True)
      Do While dr.Read()
        sd = SS.GetQuote(dr("pkStockID"), "0")
        md = New MyStockData(sd)
        alStockInfo.Add(md)
      Loop
      If alStockInfo.Count = 0 Then
        rptStocks.Visible = False
      Else
        rptStocks.DataSource = alStockInfo
        rptStocks.DataBind()
      End If
      dr.Close()
    End Sub

End Class

The RetrieveWeather routine calls the WeatherService Web Service to retrieve the weather for the local area. It uses the ZipCodeService to get the name of the city and state, but if this service is not available (which it was occasionally while I was testing), the ZIP code is displayed by itself. If the data does come back, a graphic URL as well as the weather information is filled into the table, with a little formatting as necessary.

The RetrieveNews routine is next and it uses a service that was a little difficult to figure out. It appeared that if you put in a keyword (I used “Wall Street Journal”) it would return articles that had that reference. However, there wasn't a good way to tell if the keywords you were using were actually getting good results, or it just ignored them. As time goes on, I'm sure the big news providers (CNN, MSNBC, CNBC) will create their own Web Services that you'll be able to use directly.

In the meantime, the data coming back from the NewsService is a large XML document. I chose to store that file on disk temporarily and then read it into the DataSet. There may be an easier way to do this all in memory, but the documentation I found was sketchy and didn't lend itself easily to doing this. After the data has been read into the DataSet, we have an additional problem. The document contains far more stories (around 30, at least) than we want to display. Because we are binding the data to the Repeater, we want to limit the rows we get back. The easiest way I found to do this was to create a copy of the DataSet and then add only the first ten rows into the copy. After this is done, the copy is bound to the Repeater control. If there are less than 10 stories, the DataSet is bound directly to the Repeater.

The last function, RetrieveStocks, is even more tricky. The object and service being used returns the data in an object called a QuoteData object. Unfortunately, the structure of this object does not bind properly to the Repeater control. You can add all the QuoteData objects to the ArrayList, but when the binding occurs, there were errors indicating that the object didn't have properties that the data binding engine could use. The easy solution is to create a wrapper that used read-only properties for each of the fields of the QuoteData object that you want to use. After doing this, the data binding works perfectly. The only downside of this particular service is that it works with a single stock at a time. This means that it can take a while to get all the data back. To help prevent your page from drawing too many resources, you may want to use output caching on the web page, with a 10–20 minute refresh rate.

NOTE

The Web Service providing the stock quotes allows you to get quotes in testing mode, using a license key (the zero in quotes) they made available. However, if you're planning to use this for commercial purposes, they require fees to be paid on a per-quote basis. The rest of the Web Services used were all freely available.


Building the Log Out Page

When the user is done admiring your work, he/she will want to log out of the system. The HTML for this page is shown in Listing 15.15, and the code behind is shown in Listing 15.16.

Listing 15.15. logout.aspx—HTML Code for the Log Out Page
<%@ Page Language="vb" AutoEventWireup="false"
    Codebehind="logout.aspx.vb" Inherits="WS_Ch15.Logout"%>
<%@ Register TagPrefix="Portal" Tagname="Header" Src="header.ascx" %>
<%@ Register TagPrefix="Portal" Tagname="Footer" Src="footer.ascx" %>
<Portal:Header id="PageHeader" runat="server" Title="Log Out" />
<p class="text">You have been logged out of
    the My Portal web site. <a href="login.aspx">
    Click here</a> to log in again.
</p>
<Portal:Footer id="PageFooter" runat="server" />

Listing 15.16. logout.aspx.vb—Code Behind for the Log Out Page
Imports System.Web.Security
Public Class Logout
    Inherits System.Web.UI.Page


    Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs)
 Handles MyBase.Load
      FormsAuthentication.SignOut()
    End Sub

End Class

The SignOut method of the FormsAuthentication object takes care of removing the authentication cookie that was placed on the user's machine, effectively logging them out. Attempting to go to any protected page at this point will send the user back to the initial login screen.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.137.217.95