Chapter 31. Network Programming

Just as it is difficult to live your life without talking with people, your applications also need to communicate, perhaps with other programs or perhaps with hardware devices. As you have seen throughout this book, you can use a variety of techniques to have your program communicate, including .NET Remoting, Web Services, and Enterprise Services. This chapter looks at yet another way to communicate: using the basic protocols on which the Internet and many networks have been built. You will learn how the classes in System.Net can provide a variety of techniques for communicating with existing applications such as web or FTP servers, or how you can use them to create your own network applications.

Before getting started on writing applications using these classes, however, it would be good to get some background on how networks are bolted together, and how machines and applications are identified.

Protocols, Addresses, and Ports

No discussion of a network is complete without a huge number of acronyms, seemingly random numbers, and the idea of a protocol. For example, the World Wide Web runs using a protocol called HTTP or Hypertext Transfer Protocol. Similarly, there are File Transfer Protocol (FTP), Network News Transfer Protocol (NNTP), and Gopher, also a protocol. Each application you run on a network communicates with another program using a defined protocol. The protocol is simply the expected messages each program will send the other, in the order they should be sent. For a real-world example, consider a scenario in which you want to go see a movie with a friend. A simplified conversation could look like this:

You: Dials phone
Friend: Hears phone ringing, answers phone. "Hello?"
You: "Hello. Want to go see 'Freddie and Jason Escape from New York, Part 6'?"
Friend: "No, I saw that one already. What about 'Star Warthogs'?"
You: "OK, 9:30 showing downtown?"
Friend: "Yes."
You: "Later."
Friend: "See you," hangs up

Apart from a bad taste in movies, you can see a basic protocol here. Someone initiates a communication channel. The recipient accepts the channel and signals the start of the communication. The initial caller then sends a series of messages to which the recipient replies, either to signify they have been received or as either a positive or a negative response. Finally, one of the messages indicates the end of the communication channel, and the two disconnect.

Similarly, network applications have their own protocols, defined by the application writer. For example, sending an e-mail using SMTP (Simple Mail Transfer Protocol) could look like this:

220 schroedinger Microsoft ESMTP MAIL Service, Version: 6.0.2600.2180 ready at Wed,
6 Oct 2004 15:58:28 −0700
HELLO
250 schroedinger Hello [127.0.0.1]
FOO
500 5.3.3 Unrecognized command
MAIL FROM: me
250 2.1.0 [email protected] OK
RCPT TO: him
250 2.1.5 him@schroedinger
DATA
354 Start mail input; end with <CRLF>.<CRLF>
subject: Testing SMTP

Hello World, via mail.
.
250 2.6.0 <SCHROEDINGERKaq65r500000001@schroedinger> Queued mail for delivery
QUIT
221 2.0.0 schroedinger Service closing transmission channel


Connection to host lost.

In this case, lines beginning with numbers are coming from the server, while the items in uppercase (and the message itself) were sent from the client. If the client sends an invalid message (such as the FOO message in the preceding example), then it receives a gentle rebuff from the server, while correct messages receive the equivalent of an "OK" or "Go on" reply. Traditionally, for SMTP and many other protocols (including HTTP), the reply is a three-digit number (see the following table) identifying the result of the request. The text after the number, such as 2.1.0 me@schroedinger . . . Sender OK, isn't really needed, and many servers attempt to be overly cute or clever here, so it isn't a good idea to assume anything about this text. The return values for the services generally fall into one of five ranges. Each range identifies a certain family of responses.

Range

Description

100–199

Message is good, but the server is still working on the request

200–299

Message is good, and the server has completed acting on the request

300–399

Message is good, but the server needs more information to work on the request

400–499

Message is good, but the server could not act on the request. You may try the request again to see whether it works in the future.

500–599

The server could not act on the request. Either the message was bad or an error occurred. It likely won't work next time.

Other protocols use this technique as well (leading to the infamous HTTP 404 error for "Page not found"), but they don't have to. Having a good reference is key to your success, and the best reference for existing protocols is the Request for Comments (RFC) for the protocol. These are the definitions that are used by protocol authors to create their implementation of the standard. Many of these RFCs are available at the IETF (www.ietf.org) and the World Wide Web Consortium (www.w3.org) websites.

Addresses and names

The next important topic necessary to a thorough understanding of network programming is the relationship between the names and addresses of each of the computers involved. Each form of network communication (such as TCP/IP networks such as the Internet) has its own way of mapping the name of a computer (or host) to an address. The reason for this is simple: computers deal with numbers better than text, and humans can remember text better than numbers (generally). Therefore, while you may have named your computer something clever like "l33t_#4x0R," applications and other computers know it by its IP (Internet Protocol) address. This address is a 32-bit value, usually written in four parts (each one a byte that is a number from 0 to 255), such as 192.168.1.39. This is the standard the Internet has operated on for many years. However, as only about four billion unique addresses are possible using this method, another standard, IPv6, has been proposed. It is called IPv6 because it is the sixth recommendation in the series (the older 32-bit addresses are often called IPv4 to differentiate them from this new standard). With IPv6, a 128-bit address is used, leading to a maximum number of about 3 × 1028 unique addresses, which would be more than enough for every Internet-enabled toaster.

This IP address (whether IPv4 or IPv6) must uniquely identify each host on a network (actually subnetwork, but I'm getting ahead of myself). If not, then messages will not be routed to their destination properly, and chaos ensues. The matter gets more complicated when another 32-bit number, the subnet mask, enters the picture. This is a value that is masked (using a Boolean AND operation) over the address to identify the subnetwork of the network on which the computer resides. All addresses on the same subnetwork must be unique. Two subnetworks may have the same address, however, as long as their subnet masks are different.

Many common subnetworks use the value 255.255.255.0 for the subnet mask. When this is applied to the network address, as shown in the following example, only the last address is considered significant. Therefore, the subnetwork can include only 254 unique addresses (0 and 255 are used for other purposes).

Network address:        192.168.  1.107
Subnet Mask:            255.255.255.  0
Result:                 192.168.  1.  0

Because computers and humans use two different means of identifying computers, there must be some way to relate the two. The term for this process is name resolution. In the case of the Internet, a common means of name resolution is yet another protocol, the Domain Naming System (DNS). A computer, when faced with an unknown text-based name, will send a message to the closest DNS server. It then determines whether it knows the IP address of that host. If it does, it passes this back to the requester. If not, it asks another DNS server it knows. This process continues until either the IP address is found or you run out of DNS servers. After the IP address is found, all of the servers (and the original computer) store that number for a while in case they are asked again.

Keeping in mind the problems that can ensue during name resolution can often solve many development problems. For example, if you are having difficulty communicating with a computer that should be responding, then it may be that your computer simply can't resolve the name of the remote computer. Try using the IP address instead. This removes any name-resolution problems from the equation, and may allow you to continue developing while someone else fixes the name-resolution problem.

Ports: they're not just for ships

As described earlier, each computer or host on a network is uniquely identified by an address. How does your computer realize which of possibly many applications running are meant to receive a given message arriving on the network? This is determined by the port at which the message is targeted. The port is another number, in this case an integer value from 1 to 32,767. The unique combination of address and port identifies the target application.

For example, assume you currently have a web server (IIS) running, as well as an SMTP server, and a few browser windows open. When a network message comes in, how does the operating system "know" which of these applications should receive the packet? Each of the applications (either client or server) that may receive a message is assigned a unique port number. In the case of servers, this is typically a fixed number, whereas client applications, such as your Web browser, are assigned a random available port.

To make communication with servers easier, they typically use a well-known assigned port. In the case of web servers, this is port 80, while SMTP servers use port 25. You can see a list of common servers and their ports in the file %windows%sudhasystem32sudhadriverssudhaetcsudhaservices.

If you're writing a server application, then you can either use these common port numbers (and you should if you're attempting to write a common type of server) or choose your own. If you're writing a new type of server, then you should choose a port that has not been assigned to another server; choosing a port higher than 1024 should prevent any conflicts, as these are not assigned. When writing a client application, there is typically no need to assign a port, as a dynamic port is assigned to the client for communication with a server.

Ports below 1024 should be considered secure ports, and applications that use them should have administrative access.

Firewalls: can't live with them, can't live without them

Many people have a love-hate relationship with firewalls. While they are invaluable in today's network, sometimes it would be nice if they got out of the way. A firewall is a piece of hardware or software that monitors network traffic, either incoming, outgoing, or both. It can be configured to allow only particular ports or applications to transmit information beyond the firewall. Firewalls protect against hackers or viruses that may attempt to connect to open ports, leveraging them to their own ends. They protect against spyware applications that may attempt to communicate out from your machine. However, they also "protect" against any network programming you may attempt to do. You must invariably cooperate with your network administrators, working within their guidelines for network access. If they make only certain ports available, then your applications should use only those ports. Alternately, you may be able to get them to configure the firewalls involved to permit the ports needed by your applications.

Thankfully, creating network messages is a bit easier with Visual Basic 2008. The following sections demonstrate how.

The System.Net Namespace

Most of the functionality used when writing network applications is contained within the System.Net and System.Net.Sockets namespaces. This chapter covers the following main classes in these namespaces:

  • WebRequest and WebResponse, and their subclasses, including FtpWebRequest

  • WebClient, the simplified WebRequest for common scenarios

  • HttpListener, which enables you to create your own web server

    Additional classes, methods, properties, and events were added to the System.Net and System.Net.Sockets namespaces in the .NET Framework 2.0. You can locate the updated reference for these namespaces at http://msdn2.microsoft.com/library/system.net.aspx as of this writing.

Web requests (and responses)

When most people think of network programming these days, they're really thinking of communication via a web server or client. Therefore, it shouldn't be surprising that there is a set of classes for this communication need. In this case, it is the abstract WebRequest class and the associated WebResponse. These two classes represent the concept of a request/response communication with a web server, or similar server. As these are abstract classes — that is, MustInherit classes — they cannot be created by themselves. Instead, you create the subclasses of WebRequest that are optimized for specific types of communication.

The most important properties and methods of the WebRequest class are shown in the following table:

Member

Description

Create

Method used to create a specific type of WebRequest. This method uses the URL (either as a string or as an Uri class) passed to identify and create a subclass of WebRequest.

GetRequestStream

Method that allows access to the outgoing request. This enables you to add additional information, such as POST data, to the request before sending.

GetResponse

Method used to perform the request and retrieve the corresponding WebResponse

Credentials

Property that enables you to set the user ID and password for the request if they are needed to perform it

Headers

Property that enables you to change or add to the headers for the request

Method

Property used to identify the action for the request, such as GET or POST. The list of available methods is specific to each type of server.

Proxy

Property that enables you to identify a proxy server for the communication if needed. You generally don't need to set this property, as Visual Basic 2008 detects the settings for Internet Explorer and uses them by default.

Timeout

Property that enables you to define the duration of the request before you "give up" on the server

Each subclass of WebRequest supports these methods, providing a very consistent programming model for communication with a variety of server types. The basic model for working with any of the subclasses of WebRequest can be written in the following pseudo-code:

Declare variables as either WebRequest and WebResponse, or the specific child classes
Create the variable based on the URL
Make any changes to the Request object you may need
Use the GetResponse method to retrieve the response from the server
Get the Stream from the WebResponse
Do something with the Stream

If you decide to change the protocol (e.g., from HTTP to a file-based protocol), then you only need to change the URL used to retrieve the object.

Working with FileWebRequest and HttpWebRequest

The first two types of WebRequest that became available were FileWebRequest and HttpWebRequest. FileWebRequest is used less frequently; it represents a request to a local file, using the "file://" URL format. You have likely seen this type of request if you attempted to open a local file using your Web browser, such as Internet Explorer, Firefox, or Navigator. Generally, however, the subclass most developers will use is HttpWebRequest. This class enables you to make HTTP requests to a web server without requiring a browser. This could enable you to communicate with a web server, or, using the time-honored tradition of "screen scraping," to retrieve data available on the Web.

One hurdle many developers encounter when first working with HttpWebRequest is that there is no available constructor. Instead, you must use the WebRequest.Create method (or the Create method of your desired subclass) to create new instances of any of the subclasses. This method uses the URL requested to create the appropriate subtype of WebRequest. For example, this would create a new HttpWebRequest:

Dim req As HttpWebRequest = WebRequest.Create("http://msdn.microsoft.com")

Note that if you have Option Strict turned on (and you should), the preceding code will produce an error. Instead, you should explicitly cast the return value of Create to the desired type:

Dim req As HttpWebRequest = _
  DirectCast(WebRequest.Create("http://msdn.microsoft.com"), _
  System.Net.HttpWebRequest)

Putting It Together

In order to demonstrate how to use WebRequest/WebResponse, the following example shows how to wrap a Web call into a Visual Basic class. In this case, we'll wrap Google's define: keyword, which enables you to retrieve a set of definitions for a word (e.g., www.google.com/search?q=define%3A+protocol), and then use that in a sample application (see Figure 31-1.)

Figure 31-1

Figure 31.1. Figure 31-1

  1. Create a new Windows application named "DefinePad."

  2. Add a new class to the project. This will hold the actual WebRequest code. Call it GoogleClient.

  3. Add a reference to the System.Web DLL, as you will need access to some of its functionality later.

  4. In the GoogleClient.vb file, add Imports statements to make the coding a little briefer:

    Imports System.IO
    Imports System.Net
    Imports System.Web
    Imports System.Collections.Generic
  5. The main function in GoogleClient will be a Define function that returns an array of strings. Each string will be one definition returned by Google:

    Public Function Define(ByVal word As String) As String()
        Dim req As HttpWebRequest = Nothing
        Dim resp As HttpWebResponse
        Dim query As String
        Dim result As New List(Of String)
    
        query = "http://www.google.com/search?q=define%3A" & _
          HttpUtility.UrlEncode(word)
    
        Try
            req = DirectCast(WebRequest.Create(query), HttpWebRequest)
            With req
                .Method = "GET"
                resp = req.GetResponse
                If resp.StatusCode = HttpStatusCode.OK Then
                    ParseResponse(resp.GetResponseStream, result)
                Else
                    MessageBox.Show("Error calling definition service")
                End If
            End With
        Catch ex As Exception
    
        End Try
    
        Return result.ToArray()
    
    End Function

    The first task is to guarantee that no invalid characters appear in the query string when you send the request, such as a space, an accented character, or other non-ASCII characters. The System.Web.HttpUtility class has a number of handy shared methods for encoding strings, including the UrlEncode method. This replaces characters with a safe representation of the character that looks like %value, where the value is the Unicode code for the character. For example, in the definition of the query variable above, the %3A is actually the colon character (":"), which has been encoded. Any time you retrieve a URL based on user input, encode it because there is no guarantee the resulting URL is safe to send.

    Once the query is ready, you create the WebRequest. As the URL is for an HTTP resource, an HttpWebRequest is created. While the default method for WebRequest is a GET, it's still good practice to set it. You'll create the ParseResponse method shortly to process the stream returned from the server.

    One other piece of code worth mentioning is the return value for this method, and how it is created. In order to return arrays of a specific type (rather than return actual collections from a method), you must either know the actual size to initialize the array or use the List generic type or the older ArrayList. These classes behave like the Visual Basic 6.0 Collection class, which enables you to add items, and grows as needed. They also have a handy method that enables you to convert the array into an array of any type; you can see this in the return statement. The ArrayList requires you to do a bit more work. If you want to use an ArrayList for this method, then you must identify the type of array you'd like to return. The resulting return statement would look like this using an ArrayList:

    Return result.ToArray(GetType(String))
  6. The ProcessRequest method parses the stream returned from the server and converts it into an array of items. Note that this is slightly simplified; in a real application, you would likely want to return an array of objects, where each object provides access to the definition and the URL of the site providing it:

    Private Sub ParseResponse (ByVal input As System.IO.Stream, _
      ByRef output As List(Of String))
            'definitions are in a block beginning with <p>Definitions for...
            'then are marked with <li> tags
            'yes, I should use Regular Expressions for this
            'this format will also likely change in the future.
            Dim reader As New StreamReader(input)
            Dim work As String = reader.ReadToEnd
            Dim blockStart As String = "<p>Definitions of"
            Dim pos As Integer = work.IndexOf(blockStart)
            Dim posEnd As Integer
            Dim temp As String
    
        Do
            pos = work.IndexOf("<li>", pos + 1)
            If pos > 0 Then
                posEnd = work.IndexOf("<br>", pos)
                temp = work.Substring(pos + 4, posEnd - pos - 4)
                output.Add(ParseDefinition(temp))
                pos = posEnd + 1
            End If
        Loop While pos > 0
    
    End Sub

    The code is fairly simple, using the time-honored tradition of screen scraping — processing the HTML of a page to find the section you need and then removing the HTML to produce the result.

  7. The last part of the GoogleClient class is the ParseDefinition method that cleans up the definition, removing the link and other HTML tags:

    Private Function ParseDefinition(ByVal input As String) As String
        Dim result As String = ""
            Dim lineBreak As Integer
    
            lineBreak = input.IndexOf("<br>")
            If lineBreak > 0 Then
                result = input.Substring(0, input.IndexOf("<br>"))
            Else
    result = input
            End If
            Return result.Trim
    End Function
  8. Now, with the class in hand, you can create a client to use it. In this case, you'll create a simple text editor that adds the capability to retrieve definitions for words. Go back to the Form created for the application and add controls as shown in Figure 31-2.

    Figure 31-2

    Figure 31.2. Figure 31-2

  9. The user interface for DefinePad is simple: a TextBox and a ContextMenuStrip.

    Control

    Property

    Value

    TextBox

    Name

    TextField

     

    Multiline

    True

     

    Dock

    Fill

     

    ContextMenuStrip

    DefinitionMenu

    ContextMenuStrip

    Name

    DefinitionMenu

  10. The only code in the Form is for the Opening event of the ContextMenuStrip. Here, you add the definitions to the menu. Add the following code to the handler for the Opening event:

    Private Sub DefinitionMenu_Opening(ByVal sender As Object, _
      ByVal e As System.ComponentModel.CancelEventArgs) _
      Handles DefinitionMenu.Opening
        Dim svc As New GoogleClient
    Dim definitions() As String
        Dim definitionCount As Integer
    
        DefinitionMenu.Items.Clear()
    
        Try
            'define the currently selected word
            If TextField.SelectionLength > 0 Then
                definitions = svc.Define(TextField.SelectedText)
    
                'build context menu of returned definitions
                definitionCount = definitions.Length
                If definitionCount > 6 Then
                    definitionCount = 6
                ElseIf definitionCount = 0 Then
                    'we can't do any more, so exit
                    Dim item As New ToolStripButton
                    item.Text = "Sorry, no definitions available"
                    DefinitionMenu.Items.Add(item)
                    Exit Sub
                End If
    
                For i As Integer = 1 To definitionCount
                    Dim item As New ToolStripButton
                    item.Text = definitions(i)
                    DefinitionMenu.Items.Add(item)
                Next
            End If
    
        Catch ex As Exception
            MessageBox.Show(ex.Message, "Error getting definitions", _
                MessageBoxButtons.OK, MessageBoxIcon.Error)
    
        End Try
    End Sub

    The bulk of the code in this event is to limit the number of items displayed in the menu. The actual functional part of the routine is the call to the Define method of the GoogleClient. If you trace through the code as you run, you'll see the WebRequest generated, the call made, and the resulting response stream parsed into the individual items as desired. Finally, you can use the returned list to create a set of menu items (that don't actually do anything), and display the "menu." Clicking on any definition closes the menu.

  11. To test the application, run it. Type or copy some text into the text box, select a word, and right-click on it. After a brief pause, you should see the definitions for the word (Figure 31-3 shows definitions of "protocol").

While it isn't as sexy as Web services, using this technique (WebRequest, screen scraping of the resulting HTML) can provide access to a great deal of the functionality of the Internet for your applications.

Working with FtpWebRequest

The .NET Framework includes another useful version of WebRequest — the FtpWebRequest. This class, and the related FtpWebResponse, is used to communicate with FTP servers. While the HttpWebRequest/Response can be used for simple file uploading and retrieving, the FtpWebRequest adds the capability to browse or create directories, delete files, and more. The following table describes some of the added functionality of the FtpWebRequest:

Figure 31-3

Figure 31.3. Figure 31-3

Member

Description

Abort

Used when performing an asynchronous operation. This command terminates the current operation.

Binary

A Boolean value that determines whether the data transfer should be treated as binary or text. Set to true when you are transferring a binary file, and text otherwise.

Method

While not new, the behavior of this method is quite important with the FtpWebRequest as it defines the action to perform. See the section below on WebRequestMethods.Ftp that defines the possible values.

Passive

Boolean value that determines how the client and server should communicate. When set to true, the server does not initiate communication back to the client. Instead, it waits until the client initiates the communication. This is typically needed when communicating through a firewall that might not allow the server to open a connection to the client machine.

WebRequestMethods.Ftp

As described above, the actual request made by the FtpWebRequest is identified by the Method property. This is a string property that can be set to any value recognized by your FTP server, but you will often want to set it to one of the values in the WebRequestMethods.Ftp structure.

Field

Description

AppendFile

Adds content to an existing file

DeleteFile

Deletes a file from the server (if you have permission)

DownloadFile

Retrieves a file from the FTP server

GetDateTimeStamp

Gets the date and time the file was last modified

GetFileSize

Gets the size of the file on the FTP server

ListDirectory

Gets the file and directory names for a directory on the FTP server. The data returned is a list of the files, each on a line (that is, separated by CRLF characters). This method doesn't provide an easy way to determine which of the items returned are directories or files.

ListDirectoryDetails

Gets the file and directory information for a directory on the FTP server. This method returns a good deal of information about each item, including attributes, permissions, date of last modification, and size. Like the ListDirectory method, each file's (or directory's) information is on a single line.

MakeDirectory

Creates a directory on the server

PrintWorkingDirectory

Gets the current path on the FTP server

RemoveDirectory

Removes a directory from the server (if you have permission)

UploadFile

Uploads a file to the FTP server

UploadFileWithUniqueName

Similar to UploadFile, but this method ensures that the new file has a unique filename. This is great when you allow the user to upload files but don't want possible name collisions to occur, or if you don't really care what name the file has (e.g., when the file contents just need processing but not saving).

Creating an FTP Client

In order to demonstrate using the FtpWebRequest, this section covers how to create a simple FTP server browser. The application will enable you to connect to a server, browse the available files, and download files (see Figure 31-4).

Even though this application is a Windows Forms application, we separate the FTP handling to a class for use in other applications:

  1. Create a new Windows application called "FTP Browser."

  2. Before creating the user interface, define the class that will provide the functionality. Add a new class to the project, called FtpClient.vb. This class will be used to create wrapper functionality to make working with FtpWebRequest easier. First, add the Imports statements for later use:

    Imports System.IO
    Imports System.Net
    Imports System.Text
    Imports System.Collections.Generic
    Figure 31-4

    Figure 31.4. Figure 31-4

  3. Add two properties to the class. This is for the user ID and password that will be used by the FtpClient:

    Private _user As String
    Private _pwd As String
    
    Public Property UserId() As String
        Get
            Return _user
        End Get
        Set(ByVal value As String)
            _user = value
        End Set
    End Property
    
    Public Property Password() As String
        Get
            Return _pwd
        End Get
        Set(ByVal value As String)
            _pwd = value
        End Set
    End Property
  4. The form will use two methods: GetDirectories and GetFiles. These two methods are basically identical:

    Public Function GetDirectories(ByVal url As String) As String()
        'line should look like:
        '[DIRECTORY]  developr .  .    [Feb  1  2006]
        Return GetDirectoryEntries(url, "[DIRECTORY]")
    End Function
    
    Public Function GetFiles(ByVal url As String) As String()
        'line should look like:
        '[BINARY]     211SP2EI.EXE .  .    [Feb 26  1997]      5M
        Return GetDirectoryEntries(url, "[BINARY]")
    End Function
  5. Obviously, both GetDirectories and GetFiles simply return the result of another helper routine, GetDirectoryEntries. The only difference between the information returned for a file and a directory is that directories have the directory attribute set to "d," whereas files have a blank ("-") in that position:

    Private Function GetDirectoryEntries(ByVal url As String, _
        ByVal directoryAttribute As String) As String()
    
        Dim result As New List(Of String)
        Dim str As Stream = Nothing
        Dim temp As String
        Dim words() As String
        Dim splitChars() As Char = {"<"c, ">"c}
    
        DoFtpRequest(url, _
            WebRequestMethods.Ftp.ListDirectoryDetails, _
            False, str)
        Try
            Using reader As StreamReader = New StreamReader(str)
                Do
                    temp = reader.ReadLine
    
                    If Not String.IsNullOrEmpty(temp) Then
                        'split into component parts
                        If temp.StartsWith(directoryAttribute) Then
                            words = temp.Split(splitChars, _
                              StringSplitOptions.RemoveEmptyEntries)
                            If String.Compare(words(2), _
                              "Parent Directory", True) <> 0 Then
                                result.Add(words(2))
                            End If
    
                        End If
                    End If
                Loop While temp <> Nothing
            End Using
        Catch ex As Exception
    MessageBox.Show(ex.Message, "Error getting files from " & url)
        End Try
    
        Return result.ToArray()
    
    End Function

    The GetDirectoryEntries method uses another helper method you'll create shortly to execute the WebRequestMethods.Ftp.ListDirectoryDetails method on the FTP server. This method returns the resulting response stream in the str parameter. The code then loops through the returned content. Each of the directory entries appears on a separate line, so ReadLine is perfect here. The line is split on spaces, and then added to the return value if it has the desired value for the first character (which represents it if it's a directory or a file).

  6. The GetDirectoryEntries method calls a helper method that does the actual FtpWebRequest. This method returns the resulting stream by way of a ByRef parameter:

    Private Function DoFtpRequest(ByVal url As String, _
      ByVal method As String, ByVal useBinary As Boolean, _
      ByRef data As Stream) As FtpStatusCode
        Dim result As FtpStatusCode
    
    
        Dim req As FtpWebRequest
        Dim resp As FtpWebResponse
        Dim creds As New NetworkCredential(UserId, Password)
    
        req = DirectCast(WebRequest.Create(url), FtpWebRequest)
    
        With req
            .Credentials = creds
            .UseBinary = useBinary
            .UsePassive = True
            .KeepAlive = True
    
            'make initial connection
            .Method = method
            Try
                resp = .GetResponse()
            Catch ex As Exception
                MessageBox.Show(ex.Message)
            End Try
    
            If resp IsNot Nothing Then
                data = resp.GetResponseStream
                result = resp.StatusCode
            End If
    
        End With
    
        Return result
    End Function

    The appropriate type of WebRequest is created, the properties are set, and the final request is sent.

  7. With the class created, we can move our attention back to the user interface. Return to the form and add MenuStrip and SplitContainer controls. Leave the names and other properties of these controls at their defaults. Create three items under the File menu: Connect, Download, and Exit. You may also want to add an ImageList control and populate it with appropriate graphics for open and closed folders. The following table lists the properties set on the ImageList in the sample project:

    Property

    Value

    TransparentColor

    Transparent

    Images - open

    Uses the OpenFold.ico graphic from the Visual Studio 2008 Image Library (located in the iconssudhaWin9x directory)

    Images - closed

    Uses the ClsdFold.ico graphic from the Visual Studio 2008 Image Library (located in the iconssudhaWin9x directory)

  8. Add a TreeView control to the left side of the SplitContainer, and a ListView to the right side. Set the properties as shown in the following tables:

    Table 31.1. TreeView

    Property

    Value

    Name

    DirectoryTree

    Dock

    Fill

    PathSeparator

    /

    ImageList

    The name of your ImageList control

    SelectedImageKey

    The open image's name

    ImageKey

    The closed image's name

    Table 31.2. ListView

    Property

    Value

    Name

    FileList

    Dock

    Fill

    MultiSelect

    False

    View

    List

  9. Open the Code view for the form. First, add a few private variables to the Form class.

    Private ftp As New FtpClient
    Private baseUrl As String
    Private downloadPath As String
  10. Add a handler for the Form Load event. This will initialize the TreeView and FtpClient objects:

    Private Sub MainForm_Load(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles Me.Load
        'initialize form
        With Me.DirectoryTree
            .Nodes.Add("/")
        End With
        'initialize ftp client
        With ftp
            .UserId = My.Settings.user
            .Password = My.Settings.email
        End With
        downloadPath = My.Settings.downloadPath
    
    End Sub
  11. Notice the calls to My.Settings when initializing the FtpClient. The Settings collection is available to the My object when you have created settings values in the My Project dialog. Open the Solution Explorer and double-click on the My Project item. Select the Settings tab and add the three values there (see Figure 31-5).

    Figure 31-5

    Figure 31.5. Figure 31-5

  12. You can now return to adding the code to the form. The next step is to enable connecting to the FTP server and retrieving the initial list of directories to add to the TreeView. Add this to the Connect menu item:

    Private Sub ConnectToolStripMenuItem_Click(ByVal sender As
    System.Object, _
         ByVal e As System.EventArgs) Handles ConnectToolStripMenuItem.Click
           'makes a new connection to an FTP server
    
           baseUrl = InputBox("Enter FTP site to open", "FTP Browser", __
             "ftp://ftp.microsoft.com")
           Me.DirectoryTree.Nodes.Clear()
           'add the base node
           Me.DirectoryTree.Nodes.Add("/")
           AddNodes(Me.DirectoryTree.Nodes(0), baseUrl)
    
        End Sub
  13. The event prompts the user for the address of the FTP server to connect with, and then adds it to the TreeView via a helper subroutine, AddNodes:

    Private Sub AddNodes(ByVal parent As TreeNode, ByVal url As String)
        Dim dirs() As String
    
        Me.Cursor = Cursors.WaitCursor
    
        dirs = ftp.GetDirectories(url)
        For Each dir As String In dirs
            With parent.Nodes.Add(dir)
                .Nodes.Add("NoNodeHere", "empty")
            End With
        Next
    
        Me.Cursor = Cursors.Default
    End Sub

    The AddNodes method retrieves the list of directories for the selected URL. In this, the first call for an FTP server, it retrieves the root directory. Later, the same method is used to retrieve subdirectories by requesting a URL containing the full path. Notice the addition of a fake node to each of the directories (the "NoNodeHere" item). This ensures that each of the directories added has the plus symbol next to it in the TreeView, implying that there is content below it. We will remove the empty node later when we request the actual subdirectories.

  14. Initially, each of the directories is empty except for the "NoNodeHere" item. You can use the presence of this node to determine whether you need to request subdirectories. If it still exists, then you need to call AddNodes when the user attempts to expand the TreeView node:

    Private Sub DirectoryTree_BeforeExpand(ByVal sender As Object, _
      ByVal e As System.Windows.Forms.TreeViewCancelEventArgs) _
      Handles DirectoryTree.BeforeExpand
        Dim thisNode As TreeNode
    
        thisNode = e.Node
        If thisNode.Nodes.ContainsKey("NoNodeHere") Then
            'we haven't retrieved this nodes children yet
            'remove the empty node
            thisNode.Nodes("NoNodeHere").Remove()
    'get the real children now
            AddNodes(thisNode, baseUrl + thisNode.FullPath)
        End If
    
    End Sub

    If "NoNodeHere" still exists, then you remove it and call the AddNodes method again, passing this node and its path. This calls the FTP server again, retrieving the child directories of the selected directory. You perform this before the node is expanded, so before the user can see the "NoNodeHere" node. If the subdirectories have already been requested, then the "NoNodeHere" node won't be in the TreeView anymore, and so the code to call the FTP server won't be called again.

  15. After the node has been expanded, it is selected. At this time, retrieve the list of files in that directory to display in the ListView control:

    Private Sub DirectoryTree_AfterSelect(ByVal sender As System.Object, _
      ByVal e As System.Windows.Forms.TreeViewEventArgs) _
      Handles DirectoryTree.AfterSelect
        Dim thisNode As TreeNode
        Dim files() As String
    
        thisNode = e.Node
    
        'we don't want to do this for the root node
        If thisNode.Text <> "/" Then
            'get files for this directory
            Me.Cursor = Cursors.WaitCursor
            'clear the current list
            Me.FileList.Items.Clear()
            files = ftp.GetFiles(baseUrl + thisNode.FullPath)
            For Each fil As String In files
                Me.FileList.Items.Add(fil)
            Next
    
            Me.Cursor = Cursors.Default
        End If
    End Sub

    This code is fairly simple. First, the ListView is cleared of existing files. Then the FtpClient is called, retrieving the list of files in the selected directory. These are then added to the ListView.

  16. You should now be able to run the application and browse an FTP server (see Figure 31-6). Note that because we haven't added any credentials, only anonymous FTP servers can be browsed. If you want to connect to FTP servers that require authentication, then set the UserId and Password as appropriate, or query them from the user.

  17. For a few finishing touches, set the Download menu item to be usable only if a file is selected, and add the code for the Exit menu item. Set the initial value for Enabled to False for the download menu item, and add the following code to the handler for the ListView's SelectedIndexChanged event:

    Private Sub FileList_SelectedIndexChanged(ByVal sender As
    System.Object, _
    ByVal e As System.EventArgs) Handles FileList.SelectedIndexChanged
        Me.DownloadToolStripMenuItem.Enabled = _
          CBool(Me.FileList.SelectedItems.Count)
    End Sub

    When an item is selected, the Count will be > 0, which converts to True. If 0 items are selected, then this will be False.

    Figure 31-6

    Figure 31.6. Figure 31-6

  18. The code for the Exit menu item is simple enough:

    Private Sub ExitToolStripMenuItem_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles ExitToolStripMenuItem.Click
        Me.Close()
    End Sub
  19. Finally, add the code for the Download menu item:

    Private Sub DownloadToolStripMenuItem_Click(ByVal sender As Object, _
      ByVal e As System.EventArgs) Handles DownloadToolStripMenuItem.Click
       'download currently selected file (but only if something is selected)
    
       ftp.DownloadFile(baseUrl & _
         Me.DirectoryTree.SelectedNode.FullPath & _
         "/" & Me.FileList.SelectedItems(0).Text, _
         downloadPath & Me.FileList.SelectedItems(0).Text)
    End Sub
  20. Obviously, we need to add the DownloadFile method to the FtpClient class:

    Public Sub DownloadFile(ByVal url As String, _
      ByVal destination As String)
        Dim str As Stream = Nothing
    
        DoFtpRequest(url, _
    WebRequestMethods.Ftp.DownloadFile, _
                True, _
                str)
    
            Using reader As StreamReader = New StreamReader(str)
                Using writer As StreamWriter = _
                  New StreamWriter(File.OpenWrite(destination))
                    writer.Write(reader.ReadToEnd)
                End Using
            End Using
    
        End Sub

Note the repeat use of the DoFtpRequest method. However, this time, we pass True for the binary, just in case the file we're transferring is not a text-based file. Using the Using block, we create a new StreamReader around the output stream of the response, and a new StreamWriter to a local output file. By adding the Using block, we guarantee that the associated readers, writers, and streams will all be closed when we're done using them. The Using block is functionally identical to the following .NET Framework 1.1 code:

Dim reader As StreamReader
Try
    reader = New StreamReader(str)
    ...
Finally
    reader.Flush()
    reader.Close()
    reader = Nothing
End Try

Now you can test out the new download code. Run the application again, connect to an FTP server, select a file, and then select Download from the File menu. You should see the newly created file appear in your download directory (see Figure 31-7).

Figure 31-7

Figure 31.7. Figure 31-7

While creating a full-blown FTP client would still be a fair bit more work, it is hoped that you can see that the functionality of the FtpWebRequest and FtpWebResponse classes makes communicating with an FTP server much easier than before, let alone writing the core functionality yourself using sockets.

Simplifying common Web requests with WebClient

When I first saw a demo of WebRequest in early 2000, I was delighted. Here was the capability to easily access Internet resources. However, one of the other attendees of the demo asked, "Why is that so difficult? You need to do so much to get it to work." The next time I saw the same WebRequest demo, the presenter concluded with, "For those of you doing the common scenarios, we have an even easier way." He then went on to show us how to use System.Net.WebClient.

For those times when you just want to send a GET or POST request and download a file or the resulting data, you can forget about WebRequest/WebResponse. WebClient abstracts away all of the little details of making Web requests, and makes it amazingly easy to grab data from the Web. The important methods and properties of the WebClient class are described in the following table:

Member

Description

DownloadData

Returns a byte array of data from the server. This is essentially the same as if you had called the Re4ad method on the stream returned from GetResponseStream. You could then save this to a binary file, or convert to text using an appropriate Encoding. However, see DownloadFile and DownloadString below for two easier ways to perform these tasks.

DownloadFile

Retrieves a file from the server and saves it locally

DownloadString

Returns a block of text from the server

OpenRead

Returns a stream providing data from the server. This is essentially the same stream returned from the call to GetResponseStream.

OpenWrite

Returns a stream you can use to write to the server. This is essentially the same as creating a WebRequest and writing to the GetResponse stream.

UploadData

Sends a byte array of data to the server. See UploadFile, UploadString, and UploadValues for easier ways to perform this task.

UploadFile

Sends a local file up to the server for processing

UploadString

POSTs a string to the server. This is very handy when you are simulating HTML form input.

UploadValues

Sends a set of name-value pairs to the server. This is similar to the format used by QueryString values, and this method is quite useful for simulating HTML input.

BaseAddress

The base URL the WebClient will access — e.g., www.example.com.

Credentials

Credentials that will be used when performing any request. You can either create a new NetworkCredential to use this or, alternately, set the UseDefaultCredentials property to true to use the credentials the user has logged in as.

Headers

Collection of headers that will be used for the request

Proxy

Overrides the proxy settings from Internet Explorer if set. You should never need to set this property, as the normal proxy settings are chosen by default.

QueryString

Collection of name-value pairs that will be sent with the request. This represents the values after the ? on a request.

ResponseHeaders

Collection of headers returned by the server after the request is completed

All of the DownloadX and UploadX methods also support an asynchronous version of the method, called DownloadXAsync, such as DownloadFileAsync or UploadValuesAsync. These methods perform the actual request on a background thread, and fire an event when the task is completed. If your application has some form of user interface, such as a form, then you should generally use these methods to keep your application responsive.

As WebClient uses the WebRequest classes to actually perform its magic, it can greatly simplify network coding. For example, just replace the code used in the WebRequest sample created earlier.

Before:

Public Function Define(ByVal word As String) As String()
    Dim req As HttpWebRequest = Nothing
    Dim resp As HttpWebResponse
    Dim query As String
    Dim result As New List(Of String)

    query = "http://www.google.com/search?q=define%3A" & _
      HttpUtility.UrlEncode(word)

    Try
        req = DirectCast(WebRequest.Create(query), HttpWebRequest)
        With req
            .Method = "GET"
            resp = req.GetResponse
            If resp.StatusCode = HttpStatusCode.OK Then
                ParseResponse(resp.GetResponseStream, result)
            Else
                MessageBox.Show("Error calling definition service")
            End If
        End With
    Catch ex As Exception

    End Try

    Return result.ToArray()

End Function

After:

Public Function Define(ByVal word As String) As String()
    Dim client As New WebClient
    Dim query As String
    Dim result As New List(Of String)

    query = "http://www.google.com/search?q=define%3A" & _
      HttpUtility.UrlEncode(word)

    Try
        result = ParseResponse(client.DownloadString(query))
    Catch ex As Exception

    End Try

    Return result.ToArray()

End Function

WebClient avoids all of the stream handling required for WebRequest. However, you should still know how WebRequest operates, as this knowledge is directly relatable to WebClient.

Creating your own web server with HttpListener

One exciting feature of the .NET Framework 2.0 was the new HttpListener class (and related classes). This class enables you to very easily create your own web server. While it likely wouldn't be a replacement for IIS, it enables you to add web server functionality to other applications. For example, rather than use remoting or MSMQ to create a communication channel between two applications, why not use HTTP? Each instance could host its own little web server, and then you could use HttpWebRequest or WebClient to communicate between them. Alternately, many applications and hardware devices now provide a built-in Web application, enabling you to configure the device or application via a Web browser.

The fine print: The HttpListener class relies on the new Http.sys functionality built into IIS 6.0, so you must be using an operating system that includes http.sys as a systemwide HTTP service. Only Windows Vista, Windows Server 2003, and Windows XP SP2 (and future versions of the operating system) include this functionality. This is yet another reason to upgrade and install Service Packs. Future operating systems should all provide this functionality.

HttpListener works by registering one or more "prefixes" with http.sys. Once this is done, any requests intercepted by the HTTP subsystem will be passed on to the registered listener. An HttpListenerContext object is created and passed to your listener. This context contains properties for the Request and Response objects, just as the Context object in ASP.NET does. Again, similar to Web applications, you read the request from the Request property, and write the response to the Response property. Closing the Response sends the resulting page to the user's browser. The following table describes the important members of HttpListener:

Member

Description

Abort

Shuts down the server, without finishing any existing requests

Close

Shuts down the server, after finishing handling any existing requests

Start

Starts the listener receiving requests

Stop

Stops the listener from receiving requests

IsListening

Property that determines whether the listener is currently receiving requests

Prefixes

Collection of the types of requests that this listener will respond to. These are the "left-hand side" of the URL, such as http://localhost:8080/ or http://serverName:1234/vrootName/. Note that you must end the prefix in a slash, or you will receive a runtime error. If you have IIS installed on the same server, then you can use port 80, as long as a vroot with the same name is not already defined by IIS.

Creating Your Web Server

To demonstrate using HttpListener, this section describes how to create a Windows Service to host its functionality. This could simulate a management or monitoring interface to a Windows Service that would enable authenticated individuals to use the Windows Service remotely or get other information out of it.

  1. Create a new Windows Service application called "MiniServer." The server won't do much on its own, but it will host an HttpListener.

  2. From the Components section of the toolbox, add a BackgroundWorker component and call it BackgroundWork. The other properties can remain at their defaults. This BackgroundWorker will be used to process HTTP requests on a background thread, simplifying the handling of the threads.

  3. Switch to Code view for the service. Add the Imports statements you need to the top of the file. In addition, add a reference to the System.Web DLL:

    Imports System.Net
    Imports System.IO
    Imports System.Web
    Imports System.Text
  4. Add the private members to the class. In addition, add a constant to identify the port number the service will use for listening. Select a port that currently isn't in use. The example uses 9090:

    Private listener As New HttpListener()
    Private theService As String
    
    Private Const PORT As Integer = 9090
  5. In the OnStart method, set up the list of prefixes to which the server will respond. This can be as simple as adding a port address to the URL, or it can include specific vroots. The sample provides examples of each:

    Protected Overrides Sub OnStart(ByVal args() As String)
        Dim machineName As String
    
        machineName = System.Environment.MachineName
        theService = HttpUtility.UrlEncode(Me.ServiceName)
    
        Me.EventLog.WriteEntry("Service Name: " & Me.ServiceName)
    
        With listener
            .Prefixes.Add(String.Format("http://{0}:{1}/", _
              "localhost", PORT.ToString))
            .Prefixes.Add(String.Format("http://{0}:{1}/", _
              machineName, PORT.ToString))
            .Prefixes.Add(String.Format("http://{0}/{1}/", _
              "localhost", theService))
            .Prefixes.Add(String.Format("http://{0}/{1}/", _
              machineName, theService))
            .Start()
        End With
        'start up the background thread
        Me.BackgroundWork.RunWorkerAsync()
    
    End Sub

    In this case, the server will respond to a prefix in any of the formats (the sample computer is called Tantalus):

    http://localhost:9090/
    http://tantalus:9090/
    http://localhost/sampleservice/
    http://tantalus/sampleservice/

    Note

    Keep one important point in mind as you add prefixes: They must end in a slash ("/") character. Otherwise, you will get a runtime error when the listener attempts to add that prefix.

    If you already have a web server listening on port 80, such as IIS, then you shouldn't include the last two prefixes. As only a single application can listen to each port, this service will not be able to start if the other service is already monitoring port 80.

    After initializing the Prefixes collection, calling the Start method binds the listener to the appropriate ports and vroots and starts it accepting requests. However, we don't want to actually receive the requests in the OnStart handler. Remember that the service doesn't actually start until after this method has completed, so having a lot of processing in the OnStart will actually prevent the service from completing. Therefore, we use another feature of Visual Basic 2008, the BackgroundWorker component, to handle the requests. Call its RunWorkerAsync to start the background task (in our case, the HttpListener).

  6. The OnStop method serves to shut down the HttpListener:

    Protected Overrides Sub OnStop()
        With listener
            .Stop()
            .Close()
        End With
    End Sub
  7. The background task performed by the BackgroundWorker component can be any process that you don't want to interfere with the normal application's processing. If this were a Windows Forms application, having a long-running loop or other process running might prevent the application from drawing, or responding to, the user's requests. Beyond that, we can do anything we want in the background task, with one exception: because a Windows Forms application works in a single foreground task, one can't directly access the controls on the form from the background task. Instead, if the background task must change properties on the controls, then it should fire events. The controls can then subscribe to those events, where you can access the properties. This Windows Service has no such user interface, so that problem is avoided.

    The actual work you want the BackgroundWorker to perform is in the DoWork event handler:

    Private Sub BackgroundWork_DoWork(ByVal sender As System.Object, _
      ByVal e As System.ComponentModel.DoWorkEventArgs) Handles
    BackgroundWork.DoWork
        Dim context As HttpListenerContext
        Dim path As String
        Dim defaultPage As String
    
        'this is where we actually process requests
        While listener.IsListening
            context = listener.GetContext
            path = context.Request.Url.AbsolutePath.ToLower
    
            'strip out the serviceName if you're using the URL format:
            'http://server/servicename/path
            If path.StartsWith("/" & theService.ToLower) Then
                path = path.Substring(theService.Length + 1)
            End If
            Me.EventLog.WriteEntry("Received request for " & path)
    
    
            Select Case path
                Case "/"
                    'this would probably be a resource
                    defaultPage = "Available pages<ul>" & _
                        "<li><a href='/time'>Current server time</a></li>" & _
                        "<li><a href='/date'>Current date</a></li>" & _
                        "<li><a href='/random'>Random number</a></li></ul>"
                    SendPage(context.Response, defaultPage)
                Case "/time"
                    SendPage(context.Response, DateTime.Now.ToLongTimeString)
                Case "/date"
                    SendPage(context.Response, DateTime.Now.ToLongDateString)
    Case "/random"
                    SendPage(context.Response, New Random().Next.ToString)
                Case Else
                    'if we don't understand the request, send a 404
                    context.Response.StatusCode = 404
            End Select
    
        End While
    End Sub

    The background task performs its work in a loop as long as the HttpListener is actively listening. Every developer knows that performing a set of tasks in a (relatively) tight loop is dangerous, possibly leading to computer or application lockup. However, the BackgroundWorker performs this on another thread, leaving our application responsive.

    For this application, we first get access to the context for the listener. The context groups together one client's set of communication with our listener. Similar to the HttpContext in ASP.NET, the HttpListenerContext provides access to the HttpListenerRequest and HttpListenerResponse objects, so the first step in handling a request should always be to get this context. Next, the code uses a very simple means of determining the request URL. In a more full-featured implementation, this could be more complex, separating any query values from the path requested, etc. For this sample, the listener only responds to three main paths, '/time', '/date', and '/random', to receive the current (server) time or date, or a random Integer value. If the user requests anything else, then we return a 404.

  8. The SendPage subroutine simply writes out a basic HTML page and the value determined:

    Private Sub SendPage(ByVal response As HttpListenerResponse, _
      ByVal message As String)
        Dim sb As New StringBuilder
    
        'build string
        With sb
            .Append("<html><body>")
            .AppendFormat("<h3>{0}</h3>", message)
            .Append("</body></html>")
        End With
    
        Me.EventLog.WriteEntry(sb.ToString)
    
        'set up content headers
        With response
            .ContentType = "text/html"
            .ContentEncoding = Encoding.UTF8
            .ContentLength64 = sb.ToString.Length
            Me.EventLog.WriteEntry(sb.ToString.Length.ToString)
    
            Try
                Using writer As New StreamWriter(.OutputStream)
                    With writer
                        .Write(sb.ToString)
                        .Flush()
                    End With
                End Using
    Catch ex As Exception
                Me.EventLog.WriteEntry(ex.Message, EventLogEntryType.Error)
            Finally
                'close the response to end
                .Close()
            End Try
        End With
    End Sub

    It is hoped that there aren't any surprises in this code. Using a StringBuilder, a response is built. Then the content is written back to the browser (see Figure 31-8) using a StreamWriter that is created on top of the Response.OutputStream. Remember to close the Response, or the request will never close until it times out.

    Figure 31-8

    Figure 31.8. Figure 31-8

  9. Before you can test your Windows Service, however, it must be installed. Right-click on the designer and select Add Installer (see Figure 31-9). This adds a new file to the project called ProjectInstaller.vb, and adds two components to the file: ServiceInstaller1 and ServiceProcessInstaller1. You can either keep these names or change them. In addition, set the properties as shown in the following table:

    Component

    Property

    Value

    ServiceInstaller1

    Description

    Sample Service from Wrox Professional Visual Basic 2008

     

    DisplayName

    Sample Service

     

    ServiceName

    SampleService

    ServiceProcessInstaller1

    Account

    LocalSystem

    Most of these properties only affect the display values for the Windows Service. However, the Account property of the ServiceProcessInstaller deserves special mention. Windows Services run on behalf of the user. Therefore, they can actually run under another user account. By setting the Account property to LocalSystem, you are setting the resulting Windows Service to run under the local system account. This account has a lot of access to the system, so you may want to instead use an account with more limited system rights; however, you would have to create this account separately.

    Figure 31-9

    Figure 31.9. Figure 31-9

  10. Build the Windows service. Unfortunately, if you attempt to run the service directly from Visual Basic, you will get an error message (see Figure 31-10).

    Figure 31-10

    Figure 31.10. Figure 31-10

    A Windows Service can only run if it has been installed into the system, and this task is performed using a command-line utility, InstallUtil.exe. Open the Visual Studio command prompt and navigate to the directory where you have built MiniServer.exe. Run installutil miniserver.exe. It is hoped that you'll be greeted with a success message (see Figure 31-11).

    Figure 31-11

    Figure 31.11. Figure 31-11

    If you are running Windows Vista, then you need to run the Visual Studio command prompt as an administrator. To do so, right-click on the Visual Studio 2008 Command Prompt icon and select Run As Administrator.

  11. Finally, you can start your new service. Open the Services application from Start

    Figure 31-11
    Figure 31-12

    Figure 31.12. Figure 31-12

    Figure 31-13

    Figure 31.13. Figure 31-13

    Just to confirm that all of the prefixes work, you can also request one of the values using the vroot, rather than using the port (see Figure 31-14).

    Figure 31-14

    Figure 31.14. Figure 31-14

    The HttpListener adds yet another powerful way for your applications to communicate. It gives you the ability to extend the reach of your applications out to Web browser clients, without requiring the additional administrative and management overhead of IIS to your deployment.

Summary

Programming directly to the network provides a great deal of power and flexibility. Of course, all of that power and flexibility comes at a cost. Many of the services provided by higher-level technologies, such as Web services or remoting, aren't available, and must often be recreated. However, in those situations where you must communicate with an existing application, or when you need the ultimate in control and speed, using the classes in System.Net makes life easier than it would be otherwise.

This chapter looked at many of the classes that expose network programming. You've learned how to make Web requests without a browser so you could use the data on the Internet in your applications; you've seen how you can leverage the bare sockets layer to write your own communication protocols, and you've been introduced to some of the classes in Visual Basic 2008 for creating FTP clients and web servers.

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

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