Chapter 10. Networking

The Microsoft .NET Framework includes a full set of classes for network programming. These classes support low-level network programming tasks like querying the state of network interfaces and socket-based programming with Transmission Control Protocol/Internet Protocol (TCP/IP) to higher-level tasks like downloading files and HTML pages from the Web over Hypertext Transfer Protocol (HTTP). You can even build fully distributed applications using distributed objects or service-oriented approaches.

Included in the release of .NET Framework 3.0 was Windows Communication Foundation (WCF), a unified programming model for building service-oriented applications. Although earlier technologies are still available, WCF is generally seen as a replacement for technologies like .NET Remoting and ASP.NET Web Services, and also provides a flexible unified interface through which to access many other types of distributed communications, like message queues.

The recipes in this chapter describe how to do the following:

  • Obtain configuration and network statistic information about the network interfaces on a computer as well as detect when network configuration changes occur (recipes 10-1 and 10-2)

  • Download files from File Transfer Protocol (FTP) and HTTP servers (recipes 10-3, 10-4, and 10-6)

  • Respond to HTTP requests from within your application (recipe 10-5)

  • Send e-mail messages with attachments using Simple Mail Transfer Protocol (SMTP) (recipe 10-7)

  • Use the Domain Name System (DNS) to resolve a host name into an Internet Protocol (IP) address (recipe 10-8)

  • Ping an IP address to determine whether it is accessible and calculate round-trip communication speeds by sending it an Internet Control Message Protocol (ICMP) Echo request (recipe 10-9)

  • Communicate between programs through the direct use of TCP in both synchronous and asynchronous communication models (recipes 10-10 and 10-11)

  • Communicate using User Datagram Protocol (UDP) datagrams where connection-oriented and reliable TCP represents unnecessary overhead (recipe 10-12)

  • Create a SOAP-based web service (recipe 10-13)

  • Generate a WCF service proxy dynamically (recipe 10-14)

  • Parse the contents of an Atom or RSS feed (recipe 10-15)

  • Manipulate uniform resource locators (URIs) (recipe 10-16)

Tip

A number of the recipes in this chapter include a client and a server component that must both be running for the recipe to work correctly. Where this is the case, the client and server code are contained in separate projects. To run these recipes from within Visual Studio, set the server project as the startup project and run it normally. Once the server is running, right-click the client project in Solution Explorer, click Debug on the context menu, and then click "Start new instance."

Obtain Information About the Local Network Interface

Problem

You need to obtain information about the network adapters and network configuration of the local machine.

Solution

Call the static method GetAllNetworkInterfaces of the System.Net.NetworkInformation.NetworkInterface class to get an array of objects derived from the abstract class NetworkInterface. Each object represents a network interface available on the local machine. Use the members of each NetworkInterface object to retrieve configuration information and network statistics for that interface.

How It Works

The System.Net.NetworkInformation namespace provides easy access to information about network configuration and statistics. The primary means of retrieving network information are the properties and methods of the NetworkInterface class. You do not instantiate NetworkInterface objects directly. Instead, you call the static method NetworkInterface.GetAllNetworkInterfaces, which returns an array of NetworkInterface objects. Each object represents a single network interface on the local machine. You can then obtain network information and statistics about the interface using the NetworkInterface members described in Table 10-1.

Tip

The System.Net.NetworkInformation.IPGlobalProperties class also provides access to useful information about the network configuration of the local computer.

Table 10.1. Members of the NetworkInterface Class

Member

Description

Properties

 

Description

Gets a string that provides a general description of the interface.

Id

Gets a string that contains the identifier of the interface.

IsReceiveOnly

Gets a bool indicating whether the interface can only receive or can both send and receive data.

Name

Gets a string containing the name of the interface.

NetworkInterfaceType

Gets a value from the System.Net.NetworkInformation.NetworkInterfaceType enumeration that identifies the type of interface. Common values include Ethernet, FastEthernetT, and Loopback.

OperationalStatus

Gets a value from the System.Net.NetworkInformation.OperationalStatus enumeration that identifies the status of the interface. Common values include Down and Up.

Speed

Gets a long that identifies the speed (in bits per second) of the interface as reported by the adapter, not based on dynamic calculation.

SupportsMulticast

Gets a bool indicating whether the interface is enabled to receive multicast packets.

Methods

 

GetIPProperties

Returns a System.Net.NetworkInformation.IPInterfaceProperties object that provides access to the TCP/IP configuration information for the interface. Properties of the IPInterfaceProperties object provide access to WINS, DNS, gateway, and IP address configuration.

GetIPv4Statistics

Returns a System.Net.NetworkInformation.IPv4InterfaceStatistics object that provides access to the TCP/IP v4 statistics for the interface. The properties of the IPv4InterfaceStatistics object provide access to information about bytes sent and received, packets sent and received, discarded packets, and packets with errors.

GetPhysicalAddress

Returns a System.Net.NetworkInformation.PhysicalAddress object that provides access to the physical address of the interface. You can obtain the physical address as a byte array using the method PhysicalAddress.GetAddressBytes or as a string using PhysicalAddress.ToString.

Supports

Returns a bool indicating whether the interface supports a specified protocol. You specify the protocol using a value from the System.Net.NetworkInformation.NetworkInterfaceComponent enumeration. Possible values include IPv4 and IPv6.

The NetworkInterface class also provides two other static members that you will find useful:

  • The static property LoopbackInterfaceIndex returns an int identifying the index of the loopback interface within the NetworkInterface array returned by GetAllNetworkInterfaces.

  • The static method GetIsNetworkAvailable returns a bool indicating whether any network connection is available—that is, has an OperationalStatus value of Up.

The Code

The following example uses the members of the NetworkInterface class to display information about all the network interfaces on the local machine:

using System;
using System.Net.NetworkInformation;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_01
    {
        static void Main()
        {
            // Only proceed if there is a network available.
            if (NetworkInterface.GetIsNetworkAvailable())
            {
                // Get the set of all NetworkInterface objects for the local
                // machine.
                NetworkInterface[] interfaces =
                    NetworkInterface.GetAllNetworkInterfaces();
// Iterate through the interfaces and display information.
                foreach (NetworkInterface ni in interfaces)
                {
                    // Report basic interface information.
                    Console.WriteLine("Interface Name: {0}", ni.Name);
                    Console.WriteLine("    Description: {0}", ni.Description);
                    Console.WriteLine("    ID: {0}", ni.Id);
                    Console.WriteLine("    Type: {0}", ni.NetworkInterfaceType);
                    Console.WriteLine("    Speed: {0}", ni.Speed);
                    Console.WriteLine("    Status: {0}", ni.OperationalStatus);

                    // Report physical address.
                    Console.WriteLine("    Physical Address: {0}",
                        ni.GetPhysicalAddress().ToString());

                    // Report network statistics for the interface.
                    Console.WriteLine("    Bytes Sent: {0}",
                        ni.GetIPv4Statistics().BytesSent);
                    Console.WriteLine("    Bytes Received: {0}",
                        ni.GetIPv4Statistics().BytesReceived);

                    // Report IP configuration.
                    Console.WriteLine("    IP Addresses:");
                    foreach (UnicastIPAddressInformation addr
                        in ni.GetIPProperties().UnicastAddresses)
                    {
                        Console.WriteLine("        - {0} (lease expires {1})",
                            addr.Address,
                            DateTime.Now +
                            new TimeSpan(0, 0, (int)addr.DhcpLeaseLifetime));
                    }

                    Console.WriteLine(Environment.NewLine);
                }
            }
            else
            {
                Console.WriteLine("No network available.");
            }

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Detect Changes in Network Connectivity

Problem

You need a mechanism to check whether changes to the network occur during the life of your application.

Solution

Add handlers to the static NetworkAddressChanged and NetworkAvailabilityChanged events implemented by the System.Net.NetworkInformation.NetworkChange class.

How It Works

The NetworkChange class provides an easy-to-use mechanism that allows applications to be aware of changes to network addresses and general network availability. This allows your applications to adapt dynamically to the availability and configuration of the network.

The NetworkAvailabilityChanged event fires when a change occurs to general network availability. An instance of the NetworkAvailabilityChangedEventHandler delegate is needed to handle this event and is passed a NetworkAvailabilityEventArgs object when the event fires. The NetworkAvailabilityEventArgs.IsAvailable property returns a bool indicating whether the network is available or unavailable following the change.

The NetworkAddressChanged event fires when the IP address of a network interface changes. An instance of the NetworkAddressChangedEventHandler delegate is required to handle these events. No event-specific arguments are passed to the event handler, which must call NetworkInterface.GetAllNetworkInterfaces (discussed in recipe 10-1) to determine what has changed and to take appropriate action.

Note

The NetworkAddressChanged and NetworkAvailabilityChanged events work on Windows 2000 and later operating systems.

The Code

The following example demonstrates how to use handlers that catch NetworkAddressChanged and NetworkAvailabilityChanged events and then display status information to the console. To observe how the code handles changing network conditions, unplug your network cable while the example is running, wait a few seconds, and then plug the cable back in.

using System;
using System.Net.NetworkInformation;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_02
    {
        // Declare a method to handle NetworkAvailabilityChanged events.
        private static void NetworkAvailabilityChanged(
            object sender, NetworkAvailabilityEventArgs e)
        {
            // Report whether the network is now available or unavailable.
            if (e.IsAvailable)
            {
                Console.WriteLine("Network Available");
            }
            else
            {
                Console.WriteLine("Network Unavailable");
            }
        }

        // Declare a method to handle NetworkAdressChanged events.
        private static void NetworkAddressChanged(object sender, EventArgs e)
        {
            Console.WriteLine("Current IP Addresses:");

            // Iterate through the interfaces and display information.
            foreach (NetworkInterface ni in
                NetworkInterface.GetAllNetworkInterfaces())
            {
                foreach (UnicastIPAddressInformation addr
                    in ni.GetIPProperties().UnicastAddresses)
                {
                    Console.WriteLine("    - {0} (lease expires {1})",
                        addr.Address, DateTime.Now +
                        new TimeSpan(0, 0, (int)addr.DhcpLeaseLifetime));
                }
            }
        }

        static void Main(string[] args)
        {
            // Add the handlers to the NetworkChange events.
            NetworkChange.NetworkAvailabilityChanged +=
                NetworkAvailabilityChanged;
            NetworkChange.NetworkAddressChanged +=
                NetworkAddressChanged;
// Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Press Enter to stop waiting for network events");
            Console.ReadLine();
        }
    }
}

Download Data over HTTP or FTP

Problem

You need a quick, simple way to download data from the Internet using HTTP or FTP.

Solution

Use the methods of the System.Net.WebClient class.

How It Works

The .NET Framework provides several mechanisms for transferring data over the Internet. One of the easiest approaches is to use the System.Net.WebClient class. WebClient provides many high-level methods that simplify the transfer of data by specifying the source as a URI; Table 10-2 summarizes them. The URI can specify that a file (file://), FTP (ftp://), or HTTP (http:// or https://) scheme be used to download the resource.

Table 10.2. Data Download Methods of the WebClient Class

Method

Description

OpenRead

Returns a System.IO.Stream that provides access to the data from a specified URI.

OpenReadAsync

Same as OpenRead, but performs the data transfer using a thread-pool thread so that the calling thread does not block. Add an event handler to the OpenReadCompleted event to receive notification that the operation has completed.

DownloadData

Returns a byte array that contains the data from a specified URI.

DownloadDataAsync

Same as DownloadData, but performs the data transfer using a thread-pool thread so that the calling thread does not block. Add an event handler to the DownloadDataCompleted event to receive notification that the operation has completed.

DownloadFile

Downloads data from a specified URI and saves it to a specified local file.

DownloadFileAsync

Same as DownloadFile, but performs the data transfer using a thread-pool thread so that the calling thread does not block. Add an event handler to the DownloadFileCompleted event to receive notification that the operation has completed.

DownloadString

Returns a string that contains the data from a specified URI.

DownloadStringAsync

Same as DownloadString, but performs the data transfer using a thread-pool thread so that the calling thread does not block. Add an event handler to the DownloadStringCompleted event to receive notification that the operation has completed.

The asynchronous download allows you to download data as a background task using a thread from the thread pool (discussed in recipe 4-1). When the download is finished or fails, the thread calls the appropriate OnXXX virtual methods that raise the corresponding event on the WebClient object, which you can handle using a method that matches the signature of the System.ComponentModel.AsyncCompletedEventHandler delegate if you don't want to derive a type from WebClient and override the virtual method. However, the WebClient object can handle only a single concurrent asynchronous download, making a WebClient object suitable for the background download of large single sets of data but not for the download of many files concurrently. (You could, of course, create multiple WebClient objects to handle multiple downloads.) You can cancel the outstanding asynchronous download using the method CancelAsync.

Tip

The WebClient class derives from System.ComponentModel.Component, so you can add it to the Visual Studio Form Designer Toolbox in order to allow you to easily set the properties or define the event handlers in a Windows Forms–based application.

The Code

The following example downloads a specified resource from a URI as a string and, since it is an HTML page, parses it for any fully qualified URLs that refer to GIF files. It then downloads each of these files to the local hard drive.

using System;
using System.IO;
using System.Net;
using System.Text.RegularExpressions;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_03
    {
private static void Main()
        {
            // Specify the URI of the resource to parse.
            string srcUriString = "http://www.apress.com";
            Uri srcUri = new Uri(srcUriString);

            // Create a WebClient to perform the download.
            WebClient client = new WebClient();

            Console.WriteLine("Downloading {0}", srcUri);

            // Perform the download getting the resource as a string.
            string str = client.DownloadString(srcUri);

            // Use a regular expression to extract all HTML <img>
            // elements and extract the path to any that reference
            // files with a gif, jpg, or jpeg extension.
            MatchCollection matches = Regex.Matches(str,
                "<img.*?src\s*=\s*["'](?<url>.*?\.(gif|jpg|jpeg)).*?>",
                RegexOptions.Singleline | RegexOptions.IgnoreCase);

            // Try to download each referenced image file.
            foreach(Match match in matches)
            {
                var urlGrp = match.Groups["url"];

                if (urlGrp != null && urlGrp.Success)
                {
                    Uri imgUri = null;

                    // Determine the source URI.
                    if (urlGrp.Value.StartsWith("http"))
                    {
                        // Absolute
                        imgUri = new Uri(urlGrp.Value);
                    }
                    else if (urlGrp.Value.StartsWith("/"))
                    {
                        // Relative
                        imgUri = new Uri(srcUri, urlGrp.Value);
                    }
                    else
                    {
                        // Skip it.
                        Console.WriteLine("Skipping image {0}", urlGrp.Value);
                    }
if (imgUri != null)
                    {
                        // Determine the local file name to use.
                        string fileName =
                            urlGrp.Value.Substring(urlGrp.Value.LastIndexOf('/')+1);

                        try
                        {
                            // Download and store the file.
                            Console.WriteLine("Downloading {0} to {1}",
                                imgUri.AbsoluteUri, fileName);

                            client.DownloadFile(imgUri, fileName);
                        }
                        catch
                        {
                            Console.WriteLine("Failed to download {0}",
                                imgUri.AbsoluteUri);
                        }
                    }
                }
            }

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Note

The regular expression used in the example is simple and is not designed to cater to all possible URL structures. Recipes 2-5 and 2-6 discuss regular expressions.

Notes

You may also want to upload data to resources specified as a URI, although this technique is not as commonly used. The WebClient class also provides methods for performing uploads that are equivalent to the download methods discussed previously:

  • OpenWrite

  • OpenWriteAsync

  • UploadData

  • UploadDataAsync

  • UploadFile

  • UploadFileAsync

  • UploadString

  • UploadStringAsync

Download a File and Process It Using a Stream

Problem

You need to retrieve a file from a web site, but you do not want or do not have permission to save it directly to the hard drive. Instead, you want to process the data in your application directly in memory.

Solution

Use the System.Net.WebRequest class to create your request, the System.Net.WebResponse class to retrieve the response from the web server, and some form of reader (typically a System.IO.StreamReader for HTML or text data or a System.IO.BinaryReader for a binary file) to parse the response data.

Tip

You could also use the OpenRead method of the System.Net.WebClient class to open a stream. However, the additional capabilities of the WebRequest and WebResponse classes give you more control over the operation of the network request.

How It Works

Opening and downloading a stream of data from the Web using the WebRequest and WebResponse classes takes the following four basic steps:

  1. Use the static method Create of the WebRequest class to specify the page you want. This method returns a WebRequest-derived object, depending on the type of URI you specify. For example, if you use an HTTP URI (with the scheme http:// or https://), you will create an HttpWebRequest instance. If you use a file system URI (with the scheme file://), you will create a FileWebRequest instance. In the .NET Framework 2.0 and later, you can also use an FTP URL (with the scheme ftp://), which will create an FtpWebRequest.

  2. Use the GetResponse method of the WebRequest object to return a WebResponse object for the page. If the request times out, a System.Net.WebException will be thrown. You can configure the timeout for the network request through the WebRequest.Timeout property in milliseconds (the default value is 100000).

  3. Create a StreamReader or a BinaryReader that wraps the stream returned by the WebResponse.GetResponseStream method.

  4. Perform any steps you need to with the stream contents.

The Code

The following example retrieves and displays a graphic and the HTML content of a web page. Figure 10-1 shows the output.

using System;
using System.Net;
using System.IO;
using System.Drawing;
using System.Windows.Forms;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    public partial class Recipe10_04 : Form
    {
        public Recipe10_04()
        {
            InitializeComponent();
        }

        protected override void OnLoad(EventArgs e)
        {
            base.OnLoad(e);

            string picUri = "http://www.apress.com/img/img05/Hex_RGB4.jpg";
            string htmlUri = "http://www.apress.com";

            // Create the requests.
            WebRequest requestPic = WebRequest.Create(picUri);
            WebRequest requestHtml = WebRequest.Create(htmlUri);

            // Get the responses.
            // This takes the most significant amount of time, particularly
            // if the file is large, because the whole response is retrieved.
            WebResponse responsePic = requestPic.GetResponse();
            WebResponse responseHtml = requestHtml.GetResponse();

            // Read the image from the response stream.
            pictureBox1.Image = Image.FromStream(responsePic.GetResponseStream());
// Read the text from the response stream.
            using (StreamReader r =
                new StreamReader(responseHtml.GetResponseStream()))
            {
                textBox1.Text = r.ReadToEnd();
            }
        }

        [STAThread]
        public static void Main(string[] args)
        {
            Application.Run(new Recipe10_04());
        }
    }
}
Downloading content from the Web using a stream

Figure 10.1. Downloading content from the Web using a stream

Respond to HTTP Requests from Within Your Application

Problem

You want your application to be able to respond to HTTP requests programmatically.

Solution

Use the new System.Net.HttpListener class.

Note

Your application must be running on Windows XP Service Pack 2 (or later) or Windows 2003 to use the HttpListener class; otherwise, a System.PlatformNotSupportedException will be thrown when you try to instantiate it. You should check the bool returned by the static property HttpListener.IsSupported to check whether support is available.

How It Works

The HttpListener class provides an easy-to-use mechanism through which your programs can accept and respond to HTTP requests. To use the HttpListener class, follow these steps:

  1. Instantiate an HttpListener object.

  2. Configure the URI prefixes that the HttpListener object will handle using the Prefixes property. The Prefixes property returns a System.Net.HttpListenerPrefixCollection collection to which you can add URI prefixes (as strings) using the Add method. Each prefix must end with a forward slash (/), or else a System.ArgumentException will be thrown. If you specify a URL prefix that is already being handled, a System.Net.HttpListenerException will be thrown. When a client makes a request, the request will be handled by the listener configured with the prefix that most closely matches the client's requested URL.

  3. Start the HttpListener object by calling its Start method. You must call Start before the HttpListener object can accept and process HTTP requests.

  4. Accept client requests using the GetContext method of the HttpListener object. The GetContext method will block the calling thread until a request is received and then returns a System.Net.HttpListenerContext object. Alternatively, you can use the BeginGetContext and EndGetContext methods to listen for requests on a thread-pool thread. When a request is received, the System.AsyncCallback delegate specified as the argument to the BeginGetContext method will be called and passed the HttpListenerContext object. Regardless of how it is obtained, the HttpListenerContext object implements three read-only properties critical to the handling of a client request:

    • The Request property returns a System.Net.HttpListenerRequest through which you can access details of the client's request.

    • The Response property returns a System.Net.HttpListenerResponse through which you can configure the response to send to the client.

    • The User property returns an instance of a type implementing System.Security.Principal.IPrincipal, which you can use to obtain identity, authentication, and authorization information about the user associated with the request.

  5. Configure the HTTP response through the members of the HttpListenerResponse object accessible through the HttpListenerContext.Response property.

  6. Send the response by calling the Close method of the HttpListenerResponse object.

  7. Once you have finished processing HTTP requests, call Stop on the HttpListener object to stop accepting more requests. Call Close to shut down the HttpListener object, which will wait until all outstanding requests have been processed, or call Abort to terminate the HttpListener object without waiting for requests to complete.

The Code

The following example demonstrates how to use the HttpListener class to process HTTP requests. The example starts listening for five requests concurrently using the asynchronous BeginGetContext method and handles the response to each request by calling the RequestHandler method. Each time a request is handled, a new call is made to BeginGetContext so that you always have the capacity to handle up to five requests.

To open a connection to the example from your browser, enter the URL http://localhost:19080/VisualCSharpRecipes/ or http://localhost:20000/Recipe10-05/, and you will see the response from the appropriate request handler.

using System;
using System.IO;
using System.Net;
using System.Text;
using System.Threading;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_05
    {
        // Configure the maximum number of request that can be
        // handled concurrently.
        private static int maxRequestHandlers = 5;

        // An integer used to assign each HTTP request handler a unique
        // identifier.
        private static int requestHandlerID = 0;

        // The HttpListener is the class that provides all the capabilities
        // to receive and process HTTP requests.
        private static HttpListener listener;
// A method to asynchronously process individual requests and send
        // responses.
        private static void RequestHandler(IAsyncResult result)
        {
            Console.WriteLine("{0}: Activated.", result.AsyncState);

            try
            {
                // Obtain the HttpListenerContext for the new request.
                HttpListenerContext context = listener.EndGetContext(result);

                Console.WriteLine("{0}: Processing HTTP Request from {1} ({2}).",
                    result.AsyncState,
                    context.Request.UserHostName,
                    context.Request.RemoteEndPoint);

                // Build the response using a StreamWriter feeding the
                // Response.OutputStream.
                StreamWriter sw =
                   new StreamWriter(context.Response.OutputStream, Encoding.UTF8);

                sw.WriteLine("<html>");
                sw.WriteLine("<head>");
                sw.WriteLine("<title>Visual C# Recipes</title>");
                sw.WriteLine("</head>");
                sw.WriteLine("<body>");
                sw.WriteLine("Recipe 10-5: " + result.AsyncState);
                sw.WriteLine("</body>");
                sw.WriteLine("</html>");
                sw.Flush();

                // Configure the Response.
                context.Response.ContentType = "text/html";
                context.Response.ContentEncoding = Encoding.UTF8;

                // Close the Response to send it to the client.
                context.Response.Close();

                Console.WriteLine("{0}: Sent HTTP response.", result.AsyncState);
            }
            catch (ObjectDisposedException)
            {
                Console.WriteLine("{0}: HttpListener disposed--shutting down.",
                    result.AsyncState);
            }
            finally
            {
// Start another handler if unless the HttpListener is closing.
                if (listener.IsListening)
                {
                    Console.WriteLine("{0}: Creating new request handler.",
                        result.AsyncState);

                    listener.BeginGetContext(RequestHandler, "RequestHandler_" +
                        Interlocked.Increment(ref requestHandlerID));
                }
            }
        }

        public static void Main(string[] args)
        {
            // Quit gracefully if this feature is not supported.
            if (!HttpListener.IsSupported)
            {
                Console.WriteLine(
                    "You must be running this example on Windows XP SP2, ",
                    "Windows Server 2003, or higher to create ",
                    "an HttpListener.");
                return;
            }

            // Create the HttpListener.
            using (listener = new HttpListener())
            {
                // Configure the URI prefixes that will map to the HttpListener.
                listener.Prefixes.Add(
                    "http://localhost:19080/VisualCSharpRecipes/");
                listener.Prefixes.Add(
                    "http://localhost:20000/Recipe10-05/");

                // Start the HttpListener before listening for incoming requests.
                Console.WriteLine("Starting HTTP Server");
                listener.Start();
                Console.WriteLine("HTTP Server started");
                Console.WriteLine(Environment.NewLine);

                // Create a number of asynchronous request handlers up to
                // the configurable maximum. Give each a unique identifier.
                for (int count = 0; count < maxRequestHandlers; count++)
                {
                    listener.BeginGetContext(RequestHandler, "RequestHandler_" +
                        Interlocked.Increment(ref requestHandlerID));
                }

                // Wait for the user to stop the HttpListener.
                Console.WriteLine("Press Enter to stop the HTTP Server");
                Console.ReadLine();
// Stop accepting new requests.
                listener.Stop();

                // Terminate the HttpListener without processing current requests.
                listener.Abort();
            }

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Get an HTML Page from a Site That Requires Authentication

Problem

You need to retrieve a file from a web site, but the web site requires that you provide credentials for the purpose of authentication.

Solution

Use the System.Net.WebRequest and System.Net.WebResponse classes as described in recipe 10-4. Before making the request, configure the WebRequest.Credentials and WebRequest.Certificates properties with the necessary authentication information.

Tip

You could also use the System.Net.WebClient class (discussed in recipe 10-3), which also has Credentials and Certificates properties that allow you to associate user credentials with a web request.

How It Works

Some web sites require user authentication information. When connecting through a browser, this information might be submitted transparently (for example, on a local intranet site that uses Windows integrated authentication), or the browser might request this information with a login dialog box. When accessing a web page programmatically, your code needs to submit this information. The approach you use depends on the type of authentication implemented by the web site:

  • If the web site is using basic or digest authentication, you can transmit a username and password combination by manually creating a new System.Net.NetworkCredential object and assigning it to the WebRequest.Credentials property. With digest authentication, you may also supply a domain name.

  • If the web site is using Windows integrated authentication, you can take the same approach and manually create a new System.Net.NetworkCredential object. Alternatively, you can retrieve the current user login information from the System.Net.CredentialCache object using the DefaultCredentials property.

  • If the web site requires a client certificate, you can load the certificate from a file using the System.Security.Cryptography.X509Certificates.X509Certificate2 class and add that to the HttpWebRequest.ClientCertificates collection.

  • You can load an X.509 certificate from a certificate store using the class System.Security.Cryptography.X509Certificates.X509Store defined in the System.Security.dll assembly. You can either find a certificate in the store programmatically using the X509Store.Certificates.Find method or present the user with a Windows dialog box and allow them to select the certificate. To present a dialog box, pass a collection of X.509 certificates contained in an X509Certificate2Collection object to the SelectFromCollection method of the System.Security.Cryptography.X509Certificates.X509Certificate2UI class.

The Code

The following example demonstrates all four of the basic approaches described previously. Note that you need to add a reference to the System.Security.dll assembly.

using System;
using System.Net;
using System.Security.Cryptography.X509Certificates;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_06
    {
        public static void Main()
        {
            // Create a WebRequest that authenticates the user with a
            // username and password combination over basic authentication.
            WebRequest requestA = WebRequest.Create("http://www.somesite.com");
            requestA.Credentials = new NetworkCredential("userName", "password");
            requestA.PreAuthenticate = true;

            // Create a WebRequest that authenticates the current user
            // with Windows integrated authentication.
            WebRequest requestB = WebRequest.Create("http://www.somesite.com");
            requestB.Credentials = CredentialCache.DefaultCredentials;
            requestB.PreAuthenticate = true;
// Create a WebRequest that authenticates the user with a client
            // certificate loaded from a file.
            HttpWebRequest requestC =
                (HttpWebRequest)WebRequest.Create("http://www.somesite.com");
            X509Certificate cert1 =
                X509Certificate.CreateFromCertFile(@"....TestCertificate.cer");
            requestC.ClientCertificates.Add(cert1);

            // Create a WebRequest that authenticates the user with a client
            // certificate loaded from a certificate store. Try to find a
            // certificate with a specific subject, but if it is not found,
            // present the user with a dialog so they can select the certificate
            // to use from their personal store.
            HttpWebRequest requestD =
                (HttpWebRequest)WebRequest.Create("http://www.somesite.com");
            X509Store store = new X509Store();
            X509Certificate2Collection certs =
                store.Certificates.Find(X509FindType.FindBySubjectName,
                "Allen Jones", false);

            if (certs.Count == 1)
            {
                requestD.ClientCertificates.Add(certs[0]);
            }
            else
            {
                certs = X509Certificate2UI.SelectFromCollection(
                    store.Certificates,
                    "Select Certificate",
                    "Select the certificate to use for authentication.",
                    X509SelectionFlag.SingleSelection);

                if (certs.Count != 0)
                {
                    requestD.ClientCertificates.Add(certs[0]);
                }
            }

            // Now issue the request and process the responses...
        }
    }
}

Send E-mail Using SMTP

Problem

You need to send e-mail using an SMTP server.

Solution

Use the SmtpClient and MailMessage classes in the System.Net.Mail namespace.

Note

In versions 1.0 and 1.1 of the .NET Framework, you would send SMTP mail using the SmtpMail and MailMessage classes in the System.Web.Mail namespace from the System.Web.dll assembly. The SmtpClient and MailMessage classes discussed in this recipe were added to the System.dll assembly in the .NET Framework 2.0, and both simplify and extend the functionality provided by earlier versions.

How It Works

An instance of the SmtpClient class provides the mechanism through which you communicate with the SMTP server. You configure the SmtpClient using the properties described in Table 10-3.

Table 10.3. Properties of the SmtpClient Class

Property

Description

ClientCertificates

Gets a System.Security.Cryptography.X509Certificates.X509CertificatesCollection to which you add the certificates to use for communicating with the SMTP server (if required).

Credentials

Gets or sets an implementation of the System.Net.ICredentialsByHost interface that represents the credentials to use to gain access to the SMTP server. The CredentialCache and NetworkCredential classes implement the ICredentialsByHost interface. Use NetworkCredential if you want to specify a single set of credentials and CredentialCache if you want to specify more than one.

EnableSsl

Gets or sets a bool value that indicates whether the SmtpClient should use Secure Sockets Layer (SSL) to communicate with the SMTP server.

Host

Gets or sets a string containing the host name or IP address of the SMTP server to use to send e-mail.

Port

Gets or sets an int value containing the port number to connect to on the SMTP server. The default value is 25.

Timeout

Gets or sets an int value containing the timeout in milliseconds when attempting to send e-mail. The default is 100 seconds.

UseDefaultCredentials

Gets or sets a bool value indicating whether the default user credentials are used when communicating with the SMTP server. If true, the credentials passed to the SMTP server are automatically obtained from the static property CredentialCache.DefaultCredentials.

Tip

You can specify default settings for the SmtpClient in the <mailSettings> section of your machine or application configuration files. Configurable default values include the host, port, username, and password.

Mail messages are represented by MailMessage objects, which you instantiate and then configure using the members summarized in Table 10-4.

Tip

For simple mail messages, the MailMessage class provides a constructor that allows you to specify the from, to, subject, and body information for the mail message as string arguments—allowing you to create a complete mail message in a single call.

Table 10.4. Properties of the MailMessage Class

Property

Description

Attachments

Gets or sets a System.Net.Mail.AttachmentCollection containing the set of attachments for the e-mail message. A System.Net.Mail.Attachment object represents each attachment. You can create Attachment objects from files or streams, and you can configure the encoding and content type for each attachment.

Bcc

Gets or sets a System.Net.Mail.MailAddressCollection containing the blind carbon copy addresses for the e-mail message. The MailAddressCollection contains one or more MailAddress objects.

Body

Gets or sets a string value that contains the body text of the e-mail message.

BodyEncoding

Gets or sets a System.Text.Encoding object that specifies the encoding for the body of the e-mail message. The default value is null, resulting in a default encoding of US-ASCII, which is equivalent to the Encoding object returned by the static property Encoding.ASCII.

CC

Gets or sets a System.Net.Mail.MailAddressCollection containing the carbon copy addresses for the e-mail message. The MailAddressCollection contains one or more MailAddress objects.

From

Gets or sets a System.Net.Mail.MailAddress containing the from address for the e-mail message.

IsBodyHtml

Gets or sets a bool value identifying whether the body of the e-mail message contains HTML.

ReplyTo

Gets or sets a System.Net.Mail.MailAddress containing the reply address for the e-mail message.

Subject

Gets or sets a string containing the subject for the e-mail message.

SubjectEncoding

Gets or sets a System.Text.Encoding object that specifies the encoding used to encode the body of the e-mail subject. The default value is null, resulting in a default encoding of US-ASCII, which is equivalent to the Encoding object returned by the static property Encoding.ASCII.

To

Gets or sets a System.Net.Mail.MailAddressCollection containing the destination addresses for the e-mail message. The MailAddressCollection contains one or more MailAddress objects.

Once you have configured the SmtpClient, you can send your MailMessage objects using the SmtpClient.Send method, which will cause your code to block until the send operation is completed or fails. Alternatively, you can send mail using a thread from the thread pool by calling the SendAsync method. When you call SendAsync, your code will be free to continue other processing while the e-mail is sent. Add an event handler to the SendCompleted event to receive notification that the asynchronous send has completed.

Note

Remember that you can't use SMTP to retrieve e-mail. For this task, you need the Post Office Protocol 3 (POP3) or the Internet Message Access Protocol (IMAP), neither of which is exposed natively in the .NET Framework.

The Code

The following example demonstrates how to use the SmtpClient class to send an e-mail message with multiple attachments to a set of recipients whose e-mail addresses are specified as command-line arguments:

using System;
using System.Net;
using System.Net.Mail;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_07
    {
        public static void Main(string[] args)
        {
            // Create and configure the SmtpClient that will send the mail.
            // Specify the host name of the SMTP server and the port used
            // to send mail.
            SmtpClient client = new SmtpClient("mail.somecompany.com", 25);

            // Configure the SmtpClient with the credentials used to connect
            // to the SMTP server.
            client.Credentials =
                new NetworkCredential("[email protected]", "password");

            // Create the MailMessage to represent the e-mail being sent.
            using (MailMessage msg = new MailMessage())
            {
                // Configure the e-mail sender and subject.
                msg.From = new MailAddress("[email protected]");
                msg.Subject = "Greetings from Visual C# Recipes";

                // Configure the e-mail body.
                msg.Body = "This is a message from Recipe 10-07 of" +
                    " Visual C# Recipes. Attached is the source file " +
                    " and the binary for the recipe.";

                // Attach the files to the e-mail message and set their MIME type.
                msg.Attachments.Add(
                    new Attachment(@"....Recipe10-07.cs","text/plain"));
                msg.Attachments.Add(
                    new Attachment(@".Recipe10-07.exe",
                    "application/octet-stream"));

                // Iterate through the set of recipients specified on the
                // command line. Add all addresses with the correct structure as
                //  recipients.
                foreach (string str in args)
                {
// Create a MailAddress from each value on the command line
                    // and add it to the set of recipients.
                    try
                    {
                        msg.To.Add(new MailAddress(str));
                    }
                    catch (FormatException ex)
                    {
                        // Proceed to the next specified recipient.
                        Console.WriteLine("{0}: Error -- {1}", str, ex.Message);
                        continue;
                    }
                }

                // Send the message.
                client.Send(msg);
            }

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Resolve a Host Name to an IP Address

Problem

You want to determine the IP address for a computer based on its fully qualified domain name by performing a DNS query.

Solution

Use the method GetHostEntry of the System.Net.Dns class, and pass the computer's fully qualified domain name as a string parameter.

Note

In versions 1.0 and 1.1 of the .NET Framework, you should use the method GetHostByName of the Dns class, but it is marked as obsolete as of version 2.0.

How It Works

On the Internet, the human-readable names that refer to computers are mapped to IP addresses, which is what TCP/IP requires in order to communicate between computers. For example, the name www.apress.com might be mapped to the IP address 66.211.109.45. It is not unusual for the IP address of computers to change while their name remains constant, meaning that it is usually better to reference computers with their name, not their IP address. To determine the current IP address for a given name, the computer contacts a DNS server. The name or IP address of the DNS server contacted is configured as part of a computer's network configuration.

The entire process of name resolution is transparent if you use the System.Net.Dns class, which allows you to retrieve the IP address for a host name by calling GetHostEntry.

Tip

The Dns class also provides the BeginGetHostEntry and EndGetHostEntry methods, which allow you to resolve IP addresses asynchronously. Also, the static method GetHostName returns the computer name of the local machine.

The Code

The following example retrieves the IP addresses of all computers whose fully qualified domain names are specified as command-line arguments:

using System;
using System.Net;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_08
    {
        public static void Main(string[] args)
        {
            foreach (string comp in args)
            {
                try
                {
                    // Retrieve the DNS entry for the specified computer.
                    IPAddress[] addresses = Dns.GetHostEntry(comp).AddressList;

                    // The DNS entry may contain more than one IP address. Iterate
                    // through them and display each one along with the type of
                    // address (AddressFamily).
                    foreach (IPAddress address in addresses)
                    {
Console.WriteLine("{0} = {1} ({2})",
                            comp, address, address.AddressFamily);
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine("{0} = Error ({1})", comp, ex.Message);
                }
            }

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Usage

Running the example with the following command line:

recipe10-08 www.apress.com www.microsoft.com localhost somejunk

will produce output similar to the following. Notice that multiple IP addresses can be returned for some host names.

www.apress.com = 65.19.150.100 (InterNetwork)

www.microsoft.com = 207.46.198.30 (InterNetwork)

www.microsoft.com = 207.46.20.30 (InterNetwork)

www.microsoft.com = 207.46.20.60 (InterNetwork)

www.microsoft.com = 207.46.18.30 (InterNetwork)

www.microsoft.com = 207.46.19.30 (InterNetwork)

www.microsoft.com = 207.46.19.60 (InterNetwork)

www.microsoft.com = 207.46.199.30 (InterNetwork)

www.microsoft.com = 207.46.198.60 (InterNetwork)

localhost = 127.0.0.1 (InterNetwork)

somejunk = Error (No such host is known)

Ping an IP Address

Problem

You want to check whether a computer is online and accessible and gauge its response time.

Solution

Send a ping message. This message is sent using the ICMP, accessible through the Send method of the System.Net.NetworkInformation.Ping class.

Note

The Ping class was introduced in the .NET Framework 2.0. To send a ping message in earlier versions of the .NET Framework, you had to undertake significant effort to manually create an ICMP request message using raw sockets and lengthy code.

How It Works

A ping message contacts a device at a specific IP address, passing it a test packet, and requests that the remote device respond by echoing back the packet. To gauge the connection latency between two computers, you can measure the time taken for a ping response to be received.

Warning

Many commercial web sites do not respond to ping requests because they represent an unnecessary processing overhead and are often used in denial-of-service attacks. The firewall that protects the site will usually filter them out before they reach the specified destination. This will cause your ping request to time out.

The Ping class allows you to send ping messages using the Send method. The Send method provides a number of overloads, which allow you to specify some or all of the following:

  • The IP address or host name of the target computer. You can specify this as a string or a System.Net.IPAddress object.

  • The number of milliseconds to wait for a response before the request times out (specified as an int) with the default set to 5,000.

  • A byte array of up to 65,500 data bytes that is sent with the ping request and that should be returned in the response.

  • A System.Net.NetworkInformation.PingOptions object that specifies time-to-live and fragmentation options for the transmission of the ping message.

The Send method will return a System.Net.NetworkInformation.PingReply object. The Status property of the PingReply will contain a value from the System.Net.NetworkInformation.IPStatus enumeration from which you can determine the result of the ping request. The most common values will be Success and TimedOut. If the host name you pass to the Send method cannot be resolved, Send will throw an exception, but you must look at the InnerException to determine the cause of the problem.

The Ping class also provides a SendAsync method that performs the ping request using a thread-pool thread so that the calling thread does not block. When the ping is finished or fails because of a timeout, the thread raises the PingCompleted event on the Ping object, which you can handle using a method that matches the signature of the System.Net.NetworkInformation.PingCompletedEventHandler delegate. However, the Ping object can handle only a single concurrent request; otherwise, it will throw a System.InvalidOperationException.

Tip

The Ping class derives from System.ComponentModel.Component, so you can add it to the Visual Studio Form Designer Toolbox in order to allow you to easily set the properties or define the event handlers in a Windows Forms–based application.

The Code

The following example pings the computers whose domain names or IP addresses are specified as command-line arguments:

using System;
using System.Net.NetworkInformation;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_09
    {
        public static void Main(string[] args)
        {
            // Create an instance of the Ping class.
            using (Ping ping = new Ping())
            {
                Console.WriteLine("Pinging:");

                foreach (string comp in args)
                {
                    try
                    {
                        Console.Write("    {0}...", comp);

                        // Ping the specified computer with a timeout of 100 ms.
                        PingReply reply = ping.Send(comp, 100);
if (reply.Status == IPStatus.Success)
                        {
                          Console.WriteLine("Success - IP Address:{0} Time:{1}ms",
                              reply.Address, reply.RoundtripTime);
                        }
                        else
                        {
                            Console.WriteLine(reply.Status);
                        }
                    }
                    catch (Exception ex)
                    {
                        Console.WriteLine("Error ({0})",
                            ex.InnerException.Message);
                    }
                }
            }

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Usage

Running the example with the following command line:

recipe10-09 www.apress.com www.google.com localhost somejunk

will produce output similar to the following:

Pinging:

www.apress.com...TimedOut

www.google.com...Success - IP Address:216.239.59.104 Time:42ms

localhost...Success - IP Address:127.0.0.1 Time:0ms

somejunk...Error (No such host is known)

Communicate Using TCP

Problem

You need to send data between two computers on a network using a TCP/IP connection.

Solution

One computer (the server) must begin listening using the System.Net.Sockets.TcpListener class. Another computer (the client) connects to it using the System.Net.Sockets.TcpClient class. Once a connection is established, both computers can communicate using the System.Net.Sockets.NetworkStream class.

How It Works

TCP is a reliable, connection-oriented protocol that allows two computers to communicate over a network. It provides built-in flow control, sequencing, and error handling, which makes it reliable and easy to program.

To create a TCP connection, one computer must act as the server and start listening on a specific endpoint. (An endpoint is a combination of an IP address and a port number.) The other computer must act as a client and send a connection request to the endpoint on which the first computer is listening. Once the connection is established, the two computers can take turns exchanging messages. .NET makes this process easy through its stream abstraction. Both computers simply write to and read from a System.Net.Sockets.NetworkStream to transmit data.

Note

Even though a TCP connection always requires a server and a client, an individual application could be both. For example, in a peer-to-peer application, one thread is dedicated to listening for incoming requests (acting as a server), and another thread is dedicated to initiating outgoing connections (acting as a client). In the examples provided with this chapter, the client and server are provided as separate applications and are placed in separate subdirectories.

Once a TCP connection is established, the two computers can send any type of data by writing it to the NetworkStream. However, it's a good idea to begin designing a networked application by defining the application-level protocol that clients and servers will use to communicate. This protocol includes constants that represent the allowable commands, ensuring that your application code doesn't include hard-coded communication strings.

The Code

In this example, the defined protocol is basic. You would add more constants depending on the type of application. For example, in a file transfer application, you might include a client message for requesting a file. The server might then respond with an acknowledgment and return file details such as the file size. These constants should be compiled into a separate class library assembly, which must be referenced by both the client and server. Here is the code for the shared protocol:

namespace Apress.VisualCSharpRecipes.Chapter10
{
    public class Recipe10_10Shared
    {
        public const string AcknowledgeOK = "OK";
        public const string AcknowledgeCancel = "Cancel";
        public const string Disconnect = "Bye";
        public const string RequestConnect = "Hello";
    }
}

The following code is a template for a basic TCP server. It listens on a fixed port, accepts the first incoming connection, and then waits for the client to request a disconnect. At this point, the server could call the TcpListener.AcceptTcpClient method again to wait for the next client, but instead it simply shuts down.

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    public class Recipe10_10Server
    {
        public static void Main()
        {
            // Create a new listener on port 8000.
            TcpListener listener =
                new TcpListener(IPAddress.Parse("127.0.0.1"), 8000);

            Console.WriteLine("About to initialize port.");
            listener.Start();
            Console.WriteLine("Listening for a connection...");

            try
            {
                // Wait for a connection request, and return a TcpClient
                // initialized for communication.
                using (TcpClient client = listener.AcceptTcpClient())
                {
                    Console.WriteLine("Connection accepted.");
// Retrieve the network stream.
                    NetworkStream stream = client.GetStream();

                    // Create a BinaryWriter for writing to the stream.
                    using (BinaryWriter w = new BinaryWriter(stream))
                    {
                        // Create a BinaryReader for reading from the stream.
                        using (BinaryReader r = new BinaryReader(stream))
                        {
                            if (r.ReadString() ==
                                Recipe10_10Shared.RequestConnect)
                            {
                                w.Write(Recipe10_10Shared.AcknowledgeOK);
                                Console.WriteLine("Connection completed.");

                                while (r.ReadString() !=
                                    Recipe10_10Shared.Disconnect) { }

                                Console.WriteLine(Environment.NewLine);
                                Console.WriteLine("Disconnect request received.");
                            }
                            else
                            {
                                Console.WriteLine("Can't complete connection.");
                            }
                        }
                    }
                }

                Console.WriteLine("Connection closed.");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.ToString());
            }
            finally
            {
                // Close the underlying socket (stop listening for new requests).
                listener.Stop();
                Console.WriteLine("Listener stopped.");
            }

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Main method complete. Press Enter");
            Console.ReadLine();
       }
    }
}

The following code is a template for a basic TCP client. It contacts the server at the specified IP address and port. In this example, the loopback address (127.0.0.1) is used, which always points to the local computer. Keep in mind that a TCP connection requires two ports: one at the server end and one at the client end. However, only the server port to connect to needs to be specified. The outgoing client port can be chosen dynamically at runtime from the available ports, which is what the TcpClient class will do by default.

using System;
using System.IO;
using System.Net;
using System.Net.Sockets;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    public class Recipe10_10Client
    {
        public static void Main()
        {
            TcpClient client = new TcpClient();

            try
            {
                Console.WriteLine("Attempting to connect to the server ",
                    "on port 8000.");
                client.Connect(IPAddress.Parse("127.0.0.1"), 8000);
                Console.WriteLine("Connection established.");

                // Retrieve the network stream.
                NetworkStream stream = client.GetStream();

                // Create a BinaryWriter for writing to the stream.
                using (BinaryWriter w = new BinaryWriter(stream))
                {
                    // Create a BinaryReader for reading from the stream.
                    using (BinaryReader r = new BinaryReader(stream))
                    {
                        // Start a dialog.
                        w.Write(Recipe10_10Shared.RequestConnect);

                        if (r.ReadString() == Recipe10_10Shared.AcknowledgeOK)
                        {
                            Console.WriteLine("Connected.");
                            Console.WriteLine("Press Enter to disconnect.");
                            Console.ReadLine();
                            Console.WriteLine("Disconnecting...");
                            w.Write(Recipe10_10Shared.Disconnect);
                        }
else
                        {
                            Console.WriteLine("Connection not completed.");
                        }
                    }
                }
            }
            catch (Exception err)
            {
                Console.WriteLine(err.ToString());
            }
            finally
            {
                // Close the connection socket.
                client.Close();
                Console.WriteLine("Port closed.");
            }

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Main method complete. Press Enter");
            Console.ReadLine();
        }
    }
}

Usage

Here's a sample connection transcript on the server side:

About to initialize port.
Listening for a connection...
Connection accepted.
Connection completed.

Disconnect request received.
Connection closed.
Listener stopped.

And here's a sample connection transcript on the client side:

Attempting to connect to the server on port 8000.
Connection established.
Connected.
Press Enter to disconnect.

Disconnecting...
Port closed.

Create a Multithreaded TCP Server That Supports Asynchronous Communications

Problem

You need to handle multiple network requests concurrently or perform a network data transfer as a background task while your program continues with other processing.

Solution

Use the method AcceptTcpClient of the System.Net.Sockets.TcpListener class to accept connections. Every time a new client connects, start a new thread to handle the connection. Alternatively, use the TcpListener.BeginAcceptTcpClient to accept a new client connection on a thread-pool thread using the asynchronous execution pattern (discussed in recipe 4-2).

To start a background task to handle the asynchronous sending of data, you can use the BeginWrite method of the System.Net.Sockets.NetworkStream class and supply a callback method—each time the callback is triggered, send more data.

How It Works

A single TCP endpoint (IP address and port) can serve multiple connections. In fact, the operating system takes care of most of the work for you. All you need to do is create a worker object on the server that will handle each connection on a separate thread. The TcpListener.AcceptTcpClient method returns a TcpClient when a connection is established. This should be passed off to a threaded worker object so that the worker can communicate with the remote client.

Alternatively, call the TcpListener.BeginAcceptTcpClient method to start an asynchronous operation using a thread-pool thread that waits in the background for a client to connect. BeginAcceptTcpClient follows the asynchronous execution pattern, allowing you to wait for the operation to complete or specify a callback that the .NET runtime will call when a client connects. (See recipe 4-2 for details on the options available.) Whichever mechanism you use, once BeginAcceptTcpClient has completed, call EndAcceptTcpClient to obtain the newly created TcpClient object.

To exchange network data asynchronously, you can use the NetworkStream class, which includes basic support for asynchronous communication through the BeginRead and BeginWrite methods. Using these methods, you can send or receive a block of data on one of the threads provided by the thread pool, without blocking your code. When sending data asynchronously, you must send raw binary data (an array of bytes). It's up to you to choose the amount you want to send or receive at a time.

One advantage of this approach when sending files is that the entire content of the file does not have to be held in memory at once. Instead, it is retrieved just before a new block is sent. Another advantage is that the server can abort the transfer operation easily at any time.

The Code

The following example demonstrates various techniques for handling network connections and communications asynchronously. The server (Recipe10-11Server) starts a thread-pool thread listening for new connections using the TcpListener.BeginAcceptTcpClient method and specifying a callback method to handle the new connections. Every time a client connects to the server, the callback method obtains the new TcpClient object and passes it to a new threaded ClientHandler object to handle client communications.

The ClientHandler object waits for the client to request data and then sends a large amount of data (read from a file) to the client. This data is sent asynchronously, which means ClientHandler could continue to perform other tasks. In this example, it simply monitors the network stream for messages sent from the client. The client reads only a third of the data before sending a disconnect message to the server, which terminates the remainder of the file transfer and drops the client connection.

Here is the code for the shared protocol:

namespace Apress.VisualCSharpRecipes.Chapter10
{
    public class Recipe10_11Shared
    {
        public const string AcknowledgeOK = "OK";
        public const string AcknowledgeCancel = "Cancel";
        public const string Disconnect = "Bye";
        public const string RequestConnect = "Hello";
        public const string RequestData = "Data";
    }
}

Here is the server code:

using System;
using System.IO;
using System.Net;
using System.Threading;
using System.Net.Sockets;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    public class Recipe10_11Server
    {
        // A flag used to indicate whether the server is shutting down.
        private static bool terminate;
        public static bool Terminate { get { return terminate; } }

        // A variable to track the identity of each client connection.
        private static int ClientNumber = 0;

        // A single TcpListener will accept all incoming client connections.
        private static TcpListener listener;

        public static void Main()
        {
// Create a 100KB test file for use in the example. This file will be
            // sent to clients that connect.
            using (FileStream fs = new FileStream("test.bin", FileMode.Create))
            {
                fs.SetLength(100000);
            }

            try
            {
                // Create a TcpListener that will accept incoming client
                // connections on port 8000 of the local machine.
                listener = new TcpListener(IPAddress.Parse("127.0.0.1"), 8000);

                Console.WriteLine("Starting TcpListener...");

                // Start the TcpListener accepting connections.
                terminate = false;
                listener.Start();

                // Begin asynchronously listening for client connections. When a
                // new connection is established, call the ConnectionHandler
                // method to process the new connection.
                listener.BeginAcceptTcpClient(ConnectionHandler, null);

                // Keep the server active until the user presses Enter.
                Console.WriteLine("Server awaiting connections. " +
                    "Press Enter to stop server.");
                Console.ReadLine();
            }
            finally
            {
                // Shut down the TcpListener. This will cause any outstanding
                // asynchronous requests to stop and throw an exception in
                // the ConnectionHandler when EndAcceptTcpClient is called.
                // More robust termination synchronization may be desired here,
                // but for the purpose of this example ClientHandler threads are
                // all background threads and will terminate automatically when
                // the main thread terminates. This is suitable for our needs.
                Console.WriteLine("Server stopping...");
                terminate = true;
                if (listener != null) listener.Stop();
            }

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Server stopped. Press Enter");
            Console.ReadLine();
        }

        // A method to handle the callback when a connection is established
        // from a client. This is a simple way to implement a dispatcher
// but lacks the control and scalability required when implementing
        // full-blown asynchronous server applications.
        private static void ConnectionHandler(IAsyncResult result)
        {
            TcpClient client = null;

            // Always end the asynchronous operation to avoid leaks.
            try
            {
                // Get the TcpClient that represents the new client connection.
                client = listener.EndAcceptTcpClient(result);
            }
            catch (ObjectDisposedException)
            {
                // Server is shutting down and the outstanding asynchronous
                // request calls the completion method with this exception.
                // The exception is thrown when EndAcceptTcpClient is called.
                // Do nothing and return.
                return;
            }

            Console.WriteLine("Dispatcher: New connection accepted.");

            // Begin asynchronously listening for the next client
            // connection.
            listener.BeginAcceptTcpClient(ConnectionHandler, null);

            if (client != null)
            {
                // Determine the identifier for the new client connection.
                Interlocked.Increment(ref ClientNumber);
                string clientName = "Client " + ClientNumber.ToString();

                Console.WriteLine("Dispatcher: Creating client handler ({0})."
                    , clientName);

                // Create a new ClientHandler to handle this connection.
                new ClientHandler(client, clientName);
            }
        }
    }

    // A class that encapsulates the logic to handle a client connection.
    public class ClientHandler
    {
        // The TcpClient that represents the connection to the client.
        private TcpClient client;

        // An ID that uniquely identifies this ClientHandler.
        private string ID;
// The amount of data that will be written in one block (2KB).
        private int bufferSize = 2048;

        // The buffer that holds the data to write.
        private byte[] buffer;

        // Used to read data from the local file.
        private FileStream fileStream;

        // A signal to stop sending data to the client.
        private bool stopDataTransfer;

        internal ClientHandler(TcpClient client, string ID)
        {
            this.buffer = new byte[bufferSize];
            this.client = client;
            this.ID = ID;

            // Create a new background thread to handle the client connection
            // so that we do not consume a thread-pool thread for a long time
            // and also so that it will be terminated when the main thread ends.
            Thread thread = new Thread(ProcessConnection);
            thread.IsBackground = true;
            thread.Start();
        }

        private void ProcessConnection()
        {
            using (client)
            {
                // Create a BinaryReader to receive messages from the client. At
                // the end of the using block, it will close both the BinaryReader
                // and the underlying NetworkStream.
                using (BinaryReader reader = new BinaryReader(client.GetStream()))
                {
                    if (reader.ReadString() == Recipe10_11Shared.RequestConnect)
                    {
                        // Create a BinaryWriter to send messages to the client.
                        // At the end of the using block, it will close both the
                        // BinaryWriter and the underlying NetworkStream.
                        using (BinaryWriter writer =
                            new BinaryWriter(client.GetStream()))
                        {
                            writer.Write(Recipe10_11Shared.AcknowledgeOK);
                            Console.WriteLine(ID + ": Connection established.");

                            string message = "";
while (message != Recipe10_11Shared.Disconnect)
                            {
                                try
                                {
                                    // Read the message from the client.
                                    message = reader.ReadString();
                                }
                                catch
                                {
                                    // For the purpose of the example, any
                                    // exception should be taken as a
                                    // client disconnect.
                                    message = Recipe10_11Shared.Disconnect;
                                }

                                if (message == Recipe10_11Shared.RequestData)
                                {
                                    Console.WriteLine(ID + ": Requested data. ",
                                        "Sending...");

                                    // The filename could be supplied by the
                                    // client, but in this example a test file
                                    // is hard-coded.
                                    fileStream = new FileStream("test.bin",
                                        FileMode.Open, FileAccess.Read);

                                    // Send the file size--this is how the client
                                    // knows how much to read.
                                    writer.Write(fileStream.Length.ToString());

                                    // Start an asynchronous send operation.
                                    stopDataTransfer = false;
                                    StreamData(null);
                                }
                                else if (message == Recipe10_11Shared.Disconnect)
                                {
                                    Console.WriteLine(ID +
                                        ": Client disconnecting...");
                                    stopDataTransfer = true;
                                }
                                else
                                {
                                    Console.WriteLine(ID + ": Unknown command.");
                                }
                            }
                        }
                    }
else
                    {
                        Console.WriteLine(ID +
                            ": Could not establish connection.");
                    }
                }
            }

            Console.WriteLine(ID + ": Client connection closed.");
        }

        private void StreamData(IAsyncResult asyncResult)
        {
            // Always complete outstanding asynchronous operations to avoid leaks.
            if (asyncResult != null)
            {
                try
                {
                    client.GetStream().EndWrite(asyncResult);
                }
                catch
                {
                    // For the purpose of the example, any exception obtaining
                    // or writing to the network should just terminate the
                    // download.
                    fileStream.Close();
                    return;
                }
            }

            if (!stopDataTransfer && !Recipe10_11Server.Terminate)
            {
                // Read the next block from the file.
                int bytesRead = fileStream.Read(buffer, 0, buffer.Length);

                // If no bytes are read, the stream is at the end of the file.
                if (bytesRead > 0)
                {
                    Console.WriteLine(ID + ": Streaming next block.");

                    // Write the next block to the network stream.
                    client.GetStream().BeginWrite(buffer, 0, buffer.Length,
                        StreamData, null);
                }
                else
                {
                    // End the operation.
                    Console.WriteLine(ID + ": File streaming complete.");
                    fileStream.Close();
                }
            }
else
            {
                // Client disconnected.
                Console.WriteLine(ID + ": Client disconnected.");
                fileStream.Close();
            }
        }
    }
}

And here is the client code:

using System;
using System.Net;
using System.Net.Sockets;
using System.IO;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    public class Recipe10_11Client
    {
        private static void Main()
        {
            using (TcpClient client = new TcpClient())
            {
                Console.WriteLine("Attempting to connect to the server ",
                    "on port 8000.");

                // Connect to the server.
                client.Connect(IPAddress.Parse("127.0.0.1"), 8000);

                // Retrieve the network stream from the TcpClient.
                using (NetworkStream networkStream = client.GetStream())
                {
                    // Create a BinaryWriter for writing to the stream.
                    using (BinaryWriter writer = new BinaryWriter(networkStream))
                    {
                        // Start a dialog.
                        writer.Write(Recipe10_11Shared.RequestConnect);

                        // Create a BinaryReader for reading from the stream.
                        using (BinaryReader reader =
                            new BinaryReader(networkStream))
                        {
                            if (reader.ReadString() ==
                                Recipe10_11Shared.AcknowledgeOK)
                            {
                                Console.WriteLine("Connection established." +
                                    "Press Enter to download data.");

                                Console.ReadLine();
// Send message requesting data to server.
                                writer.Write(Recipe10_11Shared.RequestData);

                                // The server should respond with the size of
                                // the data it will send. Assume it does.
                                int fileSize = int.Parse(reader.ReadString());

                                // Only get part of the data, then carry out a
                                // premature disconnect.
                                for (int i = 0; i < fileSize / 3; i++)
                                {
                                    Console.Write(networkStream.ReadByte());
                                }

                                Console.WriteLine(Environment.NewLine);
                                Console.WriteLine("Press Enter to disconnect.");
                                Console.ReadLine();
                                Console.WriteLine("Disconnecting...");
                                writer.Write(Recipe10_11Shared.Disconnect);
                            }
                            else
                            {
                                Console.WriteLine("Connection not established.");
                            }
                        }
                    }
                }
            }

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Connection closed. Press Enter");
            Console.ReadLine();
        }
    }
}

Communicate Using UDP

Problem

You need to send data between two computers on a network using a UDP stream.

Solution

Use the System.Net.Sockets.UdpClient class and use two threads: one to send data and the other to receive it.

How It Works

UDP is a connectionless protocol that doesn't include any flow control or error checking. Unlike TCP, UDP shouldn't be used where reliable communication is required. However, because of its lower overhead, UDP is often used for "chatty" applications where it is acceptable to lose some messages. For example, imagine you want to create a network in which individual clients send information about the current temperature at their locations to a server every few seconds. You might use UDP in this case because the communication frequency is high and the damage caused by losing a packet is trivial (because the server can just continue to use the last received temperature reading).

The Code

The application shown in the following code uses two threads: one to receive messages and one to send them. The application stops when the user presses the Enter key without any text to send. Notice that UDP applications cannot use the NetworkStream abstraction that TCP applications can. Instead, they must convert all data to a stream of bytes using an encoding class, as described in recipe 2-2.

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Threading;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_12
    {
        private static int localPort;

        private static void Main()
        {
            // Define endpoint where messages are sent.
            Console.Write("Connect to IP: ");
            string IP = Console.ReadLine();
            Console.Write("Connect to port: ");
            int port = Int32.Parse(Console.ReadLine());

            IPEndPoint remoteEndPoint =
                new IPEndPoint(IPAddress.Parse(IP), port);

            // Define local endpoint (where messages are received).
            Console.Write("Local port for listening: ");
            localPort = Int32.Parse(Console.ReadLine());

            // Create a new thread for receiving incoming messages.
            Thread receiveThread = new Thread(ReceiveData);
            receiveThread.IsBackground = true;
            receiveThread.Start();
UdpClient client = new UdpClient();

            Console.WriteLine("Type message and press Enter to send:");

            try
            {
                string text;

                do
                {
                    text = Console.ReadLine();

                    // Send the text to the remote client.
                    if (text.Length != 0)
                    {
                        // Encode the data to binary using UTF8 encoding.
                        byte[] data = Encoding.UTF8.GetBytes(text);

                        // Send the text to the remote client.
                        client.Send(data, data.Length, remoteEndPoint);
                    }
                } while (text.Length != 0);
            }
            catch (Exception err)
            {
                Console.WriteLine(err.ToString());
            }
            finally
            {
                client.Close();
            }

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Main method complete. Press Enter");
            Console.ReadLine();
        }

        private static void ReceiveData()
        {
            UdpClient client = new UdpClient(localPort);

            while (true)
            {
                try
                {
                    // Receive bytes.
                    IPEndPoint anyIP = new IPEndPoint(IPAddress.Any, 0);
                    byte[] data = client.Receive(ref anyIP);
// Convert bytes to text using UTF8 encoding.
                    string text = Encoding.UTF8.GetString(data);

                    // Display the retrieved text.
                    Console.WriteLine(">> " + text);
                }
                catch (Exception err)
                {
                    Console.WriteLine(err.ToString());
                }
            }
        }
    }
}

To test this application, load two instances at the same time. On computer A, specify the IP address for computer B. On computer B, specify the address for computer A. You can then send text messages back and forth at will. You can test this application with clients on the local computer using the loopback alias 127.0.0.1, provided you use different listening ports. For example, imagine a situation with two UDP clients, client A and client B. Here's a sample transcript for client A:

Connect to IP: 127.0.0.1
Connect to port: 8001
Local port for listening: 8080

Hi there!

And here's the corresponding transcript for client B (with the received message):

Connect to IP: 127.0.0.1
Connect to port: 8080
Local port for listening: 8001

>> Hi there!

Create a SOAP-Based Web Service

Problem

You need to expose functionality as a SOAP-based web service so that it can be accessed across the Internet.

Solution

Declare an interface that contains the methods you want your web service to expose. Identify this interface as a Windows Communication Foundation (WCF) service contract by applying the ServiceContractAttribute attribute to the interface and the OperationContractAttribute attribute to each of the methods you want exposed by the web service. The ServiceContractAttribute and OperationContractAttribute classes are members of the System.ServiceModel namespace.

Define any complex data types passed to and from the service and identify them as WCF data contracts by applying the DataContractAttribute attribute to the class and the DataMemberAttribute attribute to the members that need to be passed across the network. The DataContractAttribute and DataMemberAttribute classes are members of the System.Runtime.Serialization namespace.

Implement the service contract functionality by implementing the interface on a class, configure the configuration settings that control the behavior and protocols used by the service, and host an instance of the service class in a service host.

How It Works

WCF allows you to quickly create web services that are accessible across the Internet and that offer many choices in terms of protocols, security, and communication models. To create a simple SOAP-based WCF web service, you need the following key ingredients:

  • A service contract: This defines the functionality exposed by the web service and is usually in the form of a C# interface, where the interface is annotated with the ServiceContractAttribute attribute and the web service methods are annotated with the OperationContractAttribute attribute.

  • A service implementation: This object implements the service contract interface and defines what each of the web service methods does when called by a client.

  • A service host: The service host is a process that controls the life cycle of the web service. This can be a custom program that loads your service (called self-hosting), Internet Information Server (IIS), or Windows Activation Services (WAS).

There is potentially a lot of configuration information associated with a WCF web service, including things like network addresses, protocol selection, and security settings. But the beauty of WCF is that you really only need to worry about those bits of functionality that you are actively using and ignore the rest. You can also choose whether to manage your configuration in code or through the application config files. However, unless you need to make configuration decisions at runtime, it is usually best to use declarative configuration so that you can change settings without having to change your code.

Once you have created a SOAP-based web service, the easiest way to consume it is to automatically generate a proxy class using Visual Studio, or use the ServiceModel Metadata Utility Tool (svcutil.exe), which is part of the Windows SDK. In some circumstances, you can also generate proxy classes dynamically (see recipe 10-14 for details).

The Code

The following example demonstrates the creation of a simple SOAP-based web service that allows you to create, update, find, and delete employee records. The example is self-hosted, but could be moved to IIS or WAS without changes to the service code. The IEmployeeService interface defines the service contract.

using System;
using System.ServiceModel;

namespace Apress.VisualCSharpRecipes.Chapter10
{
[ServiceContract(Namespace = "Apress.VisualCSharpRecipes.Chapter10")]
    public interface IEmployeeService
    {
        [OperationContract]
        Employee CreateEmployee(Employee newEmployee);

        [OperationContract]
        bool DeleteEmployee(int employeeId);

        [OperationContract(Name="GetEmployeeById")]
        Employee GetEmployee(int employeeId);

        [OperationContract(Name = "GetEmployeeByName")]
        Employee GetEmployee(string employeeName);

        [OperationContract]
        Employee UpdateEmployee(Employee updatedEmployee);
    }
}

Here is the class that declares the Employee data contract representing the data that is passed between the client and the service:

using System;
using System.Runtime.Serialization;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    [DataContract]
    public class Employee
    {
        [DataMember]
        public DateTime DateOfBirth { get; set; }

        [DataMember]
        public int Id { get; set; }

        [DataMember]
        public string Name { get; set; }
    }
}

The EmployeeService class implements the IEmployeeService interface and provides the actual logic of the web service.

using System;
using System.Collections.Generic;
using System.Linq;

namespace Apress.VisualCSharpRecipes.Chapter10
{
public class EmployeeService : IEmployeeService
    {
        private Dictionary<int, Employee> employees;

        public EmployeeService()
        {
            employees = new Dictionary<int, Employee>();
        }

        // Create an Employee based on the contents of a provided
        // Employee object. Return the new Employee object.
        public Employee CreateEmployee(Employee newEmployee)
        {
            // NOTE: Should validate new employee data.
            newEmployee.Id = employees.Count + 1;

            lock (employees)
            {
                employees[newEmployee.Id] = newEmployee;
            }

            return newEmployee;
        }

        // Delete an employee by the specified Id and return true
        // or false depending on if an Employee was deleted.
        public bool DeleteEmployee(int employeeId)
        {
            lock(employees)
            {
                return employees.Remove(employeeId);
            }
        }

        // Get an employee by the specified Id and return null if
        // the employee does not exist.
        public Employee GetEmployee(int employeeId)
        {
            Employee employee = null;

            lock (employees)
            {
                employees.TryGetValue(employeeId, out employee);
            }

            return employee;
        }
// Get an employee by the specified Name and return null if
        // the employee does not exist.
        public Employee GetEmployee(string employeeName)
        {
            Employee employee = null;

            lock (employees)
            {
                employee = employees.Values.FirstOrDefault
                    (e => e.Name.ToLower() == employeeName.ToLower());
            }

            return employee;
        }

        // Update an employee based on the contents of a provided
        // Employee object. Return the updated Employee object.
        public Employee UpdateEmployee(Employee updatedEmployee)
        {
            Employee employee = GetEmployee(updatedEmployee.Id);

            // NOTE: Should validate new employee data.
            if (employee != null)
            {
                lock (employees)
                {
                    employees[employee.Id] = updatedEmployee;
                }
            }

            return updatedEmployee;
        }
    }
}

The following code shows the simple service host created to run the service:

using System;
using System.ServiceModel;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    public static class Recipe10_13Service
    {
        static void Main(string[] args)
        {
            ServiceHost host = new ServiceHost(typeof(EmployeeService));
            host.Open();
// Wait to continue.
            Console.WriteLine("Service host running. Press Enter to terminate.");
            Console.ReadLine();
        }
    }
}

The following shows the configuration information used by the service host:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="EmployeeServiceBehavior" >
          <serviceMetadata httpGetEnabled="true" />
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <services>
      <service name="Apress.VisualCSharpRecipes.Chapter10.EmployeeService"
               behaviorConfiguration="EmployeeServiceBehavior">
        <endpoint address="http://localhost:8000/EmployeeService"
                  binding="wsHttpBinding"
                  contract="Apress.VisualCSharpRecipes.Chapter10.IEmployeeService" />
        <endpoint address="http://localhost:8000/EmployeeService/mex"
                  binding="mexHttpBinding"
                  contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://localhost:8000/" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
</configuration>

Finally, the following simple client code demonstrates how to interact with the service via a proxy:

using System;
using Apress.VisualCSharpRecipes.Chapter10.Services;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_13Client
    {
        private static string FormatEmployee(Employee emp)
        {
return String.Format("ID:{0}, NAME:{1}, DOB:{2}",
                emp.Id, emp.Name, emp.DateOfBirth);
        }

        static void Main(string[] args)
        {
            // Create a service proxy.
            using (EmployeeServiceClient employeeService
                = new EmployeeServiceClient())
            {
                // Create a new Employee.
                Employee emp = new Employee()
                {
                    DateOfBirth = DateTime.Now,
                    Name = "Allen Jones"
                };

                // Call the EmployeeService to create a new Employee record.
                emp = employeeService.CreateEmployee(emp);

                Console.WriteLine("Created employee record - {0}",
                    FormatEmployee(emp));

                // Update the existing Employee.
                emp.DateOfBirth = new DateTime(1950, 10, 13);
                emp = employeeService.UpdateEmployee(emp);

                Console.WriteLine("Updated employee record - {0}",
                    FormatEmployee(emp));

                // Wait to continue.
                Console.WriteLine(Environment.NewLine);
                Console.WriteLine("Main method complete. Press Enter");
                Console.ReadLine();
            }
        }
    }
}

Call a WCF Service Using a Dynamically Generated Service Proxy

Problem

You need to call the methods of a WCF service but can't or don't want to generate a static proxy class as described in recipe 10-13.

Solution

If you have access to classes that represent the service and data contracts exposed by the service, you can create a dynamic service proxy using the System.ServiceModel.ChannelFactory class.

How It Works

The ChannelFactory class is a generic class that allows you to create proxies dynamically based on WCF service contracts. When instantiating a ChannelFactory, in addition to identifying the type of the service proxy you want to create, you must provide details of the WCF endpoint to which you want to connect. This includes the binding you want to use and the address of the service you want to communicate with.

Once you have instantiated a properly configured ChannelFactory, you call its CreateChannel method, which will return a service proxy in the form of an instance of the service contract type. You can then use this proxy to make calls against the service endpoint identified in the ChannelFactory constructor.

The Code

The following code demonstrates the use of a dynamic service proxy to communicate with a WCF service. The service used in this example is basically the same as that used in recipe 10-13. Instead of using Visual Studio to generate a proxy class, the client project contains a reference to the service project. The reference gives the client code access to the service and data contract classes of the service, enabling the use of the ChannelFactory to create dynamic service proxies at runtime.

using System;
using System.ServiceModel;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_14Client
    {
        static void Main(string[] args)
        {
            string serviceUri = "http://localhost:8000/EmployeeService";

            // Create the ChannelFactory that is used to generate service
            // proxies.
            using (ChannelFactory<IEmployeeService> channelFactory =
                new ChannelFactory<IEmployeeService>(new WSHttpBinding(), serviceUri))
            {
                // Create a dynamic proxy for IEmployeeService.
                IEmployeeService proxy = channelFactory.CreateChannel();

                // Create a new Employee.
                Employee emp = new Employee()
                {
                    DateOfBirth = DateTime.Now,
                    Name = "Allen Jones"
                };
// Call the EmployeeService to create a new Employee record.
                emp = proxy.CreateEmployee(emp);

                Console.WriteLine("Created employee record - {0}", emp);

                // Update an existing Employee record.
                emp.DateOfBirth = new DateTime(1950, 10, 13);
                emp = proxy.UpdateEmployee(emp);

                Console.WriteLine("Updated employee record - {0}", emp);

                // Wait to continue.
                Console.WriteLine(Environment.NewLine);
                Console.WriteLine("Main method complete. Press Enter");
                Console.ReadLine();
            }
        }
    }
}

Process the Content of an Atom or RSS Feed

Problem

You need to process the content of an Atom 1.0 or RSS 2.0 feed to extract details of the feed, or the items it contains.

Solution

Parse the feed data using one of the format-specific classes derived from System.ServiceModel.Syndication.SyndicationFeedFormatter. Once parsed, the SyndicationFeedFormatter.Feed property returns a System.ServiceModel.Syndication.SyndicationFeed object whose properties provide access to the attributes of the feed and the items it contains. Use the SyndicationFeedFormatter.Items property to access a collection of System.ServiceModel.Syndication.SyndicationItem objects that represent the items in the feed. The properties of each SyndicationItem object expose the attributes of the individual feed items.

How It Works

The SyndicationFeed and SyndicationFeedItem classes provide a generic abstraction of Atom 1.0 and RSS 2.0 feeds and feed items, and present a common interface to simplify the processing of both feed types. The Rss20FeedFormatter class allows you to create a SyndicationFeed object from an RSS 2.0 feed, and the Atom10FeedFormatter class provides equivalent support for Atom 1.0 feeds. Both classes are members of the System.ServiceModel.Syndication namespace.

To load feed data for processing, use the SyndicationFeedFormatter.ReadFrom method and pass it a System.Xml.XmlReader that provides access to the feed data. Once loaded with feed data, the SyndicationFeedFormatter.Feed property provides access to a SyndicationFeed object allowing you to use the properties listed in Table 10-5 to access the feed attributes.

Table 10.5. Properties of the SyndicatedFeed Class

Property

Description

Authors

Gets a collection of authors from the feed

BaseUri

Gets the base URI from the feed

Categories

Gets a collection of categories from the feed

Description

Gets the description from the feed

Id

Gets the ID from the feed

ImageUrl

Gets the image URL from the feed

Items

Gets the collection of items contained in the feed

Language

Gets the language from the feed

LastUpdatedTime

Gets the last updated time from the feed

Links

Gets a collection of links associated with the feed

Title

Gets the title from the feed

The SyndicationFeed.Items property provides access to the set of items contained in the feed. Each item is represented by a SyndicationItem. Table 10-6 lists the properties that provide access to the attributes of a feed item.

Table 10.6. Properties of the SyndicationItem Class

Property

Description

Authors

Gets a collection of authors from the feed item

BaseUri

Gets the base URI from the feed item

Categories

Gets a collection of categories from the feed item

Content

Gets the content from the feed item

Id

Gets the ID from the feed item

Language

Gets the language from the feed item

LastUpdatedTime

Gets the last updated time from the feed item

Links

Gets a collection of links associated with the feed

Summary

Gets the summary from the feed item

Title

Gets the title from the feed item

The Code

The following example takes the URL of a feed as a command-line argument, downloads the feed, determines whether it is an RSS or Atom feed, and parses it using the appropriate SyndicationFeedFormatter. The output from running the example contains the title and description of the overall feed, and then the title, summary, and publication date of each item in the feed.

using System;
using System.Net;
using System.ServiceModel.Syndication;
using System.Xml.Linq;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_15
    {
        static void Main(string[] args)
        {
            Uri feedUrl = null;
if (args.Length == 0 || String.IsNullOrEmpty(args[0])
                || !Uri.TryCreate(args[0], UriKind.RelativeOrAbsolute,
                    out feedUrl))
            {
                // Error and wait to continue.
                Console.WriteLine("Invalid feed URL. Press Enter.");
                Console.ReadLine();
                return;
            }

            // Create the web request based on the URL provided for the feed.
            WebRequest req = WebRequest.Create(feedUrl);

            // Get the data from the feed.
            WebResponse res = req.GetResponse();

            // Simple test for the type of feed: Atom 1.0 or RSS 2.0.
            SyndicationFeedFormatter formatter = null;
            XElement feed = XElement.Load(res.GetResponseStream());

            if (feed.Name.LocalName == "rss")
            {
                formatter = new Rss20FeedFormatter();
            }
            else if (feed.Name.LocalName == "feed")
            {
                formatter = new Atom10FeedFormatter();
            }
            else
            {
                // Error and wait to continue.
                Console.WriteLine("Unsupported feed type: "
                    + feed.Name.LocalName);
                Console.ReadLine();
                return;
            }

            // Read the feed data into the formatter.
            formatter.ReadFrom(feed.CreateReader());

            // Display feed level data:
            Console.WriteLine("Title: " + formatter.Feed.Title.Text);
            Console.WriteLine("Description: "
                + formatter.Feed.Description.Text);
            Console.Write(Environment.NewLine);
            Console.WriteLine("Items: ");
// Display the item data.
            foreach (var item in formatter.Feed.Items)
            {
                Console.WriteLine("	Title: " + item.Title.Text);
                Console.WriteLine("	Summary: " + item.Summary.Text);
                Console.WriteLine("	Publish Date: " + item.PublishDate);
                Console.Write(Environment.NewLine);
            }

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}

Manipulate URIs

Problem

You need to construct a well-formed URI or extract the component parts of a provided URI.

Solution

Use the System.Uri class to extract the component parts of an existing URI and the System.UriBuilder class to construct a new well-formed URI.

How It Works

When doing web or network programming, you will regularly need to manipulate URIs and their closely related derivatives: URNs and URLs. For example, you may need to construct URLs that represent servers and network resources you want to access, or extract information like the host, port, or protocol specified in an externally provided URI.

The System.Uri class provides an object representation of a URI and implements properties that allow you to extract the various elements that constitute the URI. To create a Uri, pass the string representing the URI you want to parse to the Uri constructor. A number of constructor overloads allow you to handle both absolute and relative URIs. However, if the URI string is invalid, the constructor will throw a System.UriFormatException. To avoid this, you can use the static method Uri.TryCreate, which returns true if the parse was successful and false otherwise.

Once you have a Uri object, you can use its properties to extract specific components of the URI. Table 10-7 contains some commonly used properties of the Uri class.

Table 10.7. Commonly Used Members of the Uri Class

Property

Description

AbsolutePath

Gets a string that contains the absolute path of the URI

AbsoluteUri

Gets a string representation of the full URI as an absolute address

Host

Gets a string containing the host name specified in the URI

IsDefaultPort

Gets a bool indicating whether the specified port is the default port for the URI scheme

OriginalString

Gets the original string used to construct the Uri

Port

Gets an int containing the port number specified in the URI

Query

Gets a string containing the query string specified in the URI

Scheme

Gets a string containing the scheme specified in the URI

The Uri class is read-only, so if you want to create a new well-formed URI, you should use the UriBuilder class. You can specify the key elements of the URI in various overloads of the UriBuilder constructor, or you can configure a new UriBuilder object via its properties. Table 10-8 describes the properties of the UriBuilder class.

Table 10.8. Properties of the UriBuilder Class

Property

Description

Fragment

Gets or sets a string specifying the fragment element of the URI. This is the part after the hash symbol (#) found at the end of URLs.

Host

Gets or sets a string specifying the host element of the URI.

Password

Gets or sets a string specifying the password to use with the URI.

Path

Gets or sets a string specifying the path element of the URI.

Port

Gets or sets an int specifying the port element of the URI.

Query

Gets or sets a string specifying the query string element of the URI.

Scheme

Gets or sets a string specifying the scheme element of the URI.

UserName

Gets or sets a string containing the username to use with the URI.

Once you have configured the UriBuilder, you obtain an appropriately configured Uri object representing the URI via the UriBuilder.Uri property. Many methods that require URIs take Uri instances, but if you need a string representation of the URI you can use the Uri.AbsolutePath property.

The Code

The following code demonstrates the use of the Uri and UriBuilder classes.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace Apress.VisualCSharpRecipes.Chapter10
{
    class Recipe10_16
    {
        private static string defualtUrl
            = "http://www.apress.com:80/book/view/9781430225256";

        static void Main(string[] args)
        {
            Uri uri = null;

            // Extract information from a string URL passed as a
            // command line argument or use the default URL.
            string strUri = defualtUrl;

            if (args.Length > 0 && !String.IsNullOrEmpty(args[0]))
            {
                strUri = args[0];
            }

            // Safely parse the url
            if (Uri.TryCreate(strUri, UriKind.RelativeOrAbsolute, out uri))
            {
                Console.WriteLine("Parsed URI: " + uri.OriginalString);
                Console.WriteLine("	Scheme: " + uri.Scheme);
                Console.WriteLine("	Host: " + uri.Host);
                Console.WriteLine("	Port: " + uri.Port);
                Console.WriteLine("	Path and Query: " + uri.PathAndQuery);
            }
else
            {
                Console.WriteLine("Unable to parse URI: " + strUri);
            }

            // Create a new URI.
            UriBuilder newUri = new UriBuilder();
            newUri.Scheme = "http";
            newUri.Host = "www.apress.com";
            newUri.Port = 80;
            newUri.Path = "book/view/9781430225256";

            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Created URI: " + newUri.Uri.AbsoluteUri);

            // Wait to continue.
            Console.WriteLine(Environment.NewLine);
            Console.WriteLine("Main method complete. Press Enter.");
            Console.ReadLine();
        }
    }
}
..................Content has been hidden....................

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