15.2. The Server Application

In a perfect world, an application such as FooReader.NET would be strictly client-side. JavaScript would be able to retrieve XML feeds across domains with XHR, and there would be no need to make any calls to a server component. Because of JavaScript's security restrictions, however, it is not possible to retrieve data from a different domain; thus, a server-side component is required.

15.2.1. Possible Paradigms

The server's job in FooReader.NET is to retrieve the remote XML feeds for the client to use. Following this model, there are two possible design paths for the server; both have their pros and cons.

The first method is a cached feed architecture. The server program would act as a service, fetching a list of feeds at a certain time interval, caching them, and serving the cached feeds to the client when requested. This option potentially saves bandwidth, but it also risks the reader not having up-to-date feeds. More user action would be required to display the current, up-to-date feeds, which goes against the Ajax ideology.

The second method is a delivery on demand architecture, where the server would retrieve any given feed when the user requests it. This may use more bandwidth, but it ensures the reader will have up-to-date information; moreover, this design is inline with the Ajax concepts and is what the user would expect.

15.2.2. Implementation

The solution implemented in FooReader.NET uses a hybrid approach. The requested feeds are retrieved with the delivery-on-demand model, but the application caches a feed when it is fetched. The cached version is used only in the event that the remote host cannot be contacted, and an up-to-date feed cannot be retrieved. This ensures that the user has something to read, even though it is older data.

Because the server is only responsible for pulling and caching remote feeds, it makes sense to have one ASP.NET page responsible for these operations. This page, called xmlproxy.aspx, will have a code- behind file where the ASP.NET code is contained.

Codebehind is a method for authoring web pages for the ASP.NET platform. Unlike inline programming models, where the server-side code is interspersed with HTML markup (like PHP and ASP), codebehind enables you to remove all logic from the HTML code and place it in a separate class file. This results in a clean separation of HTML and your .NET programming language of choice.

The language of choice for this project is C#, which is the language created specifically for the .NET Framework.

15.2.2.1. Providing Errors

The problem with the approach of providing XML data to the client is handling errors. The client-side component expects all data to be XML data in either RSS or Atom format. While this does present a problem, its solution is rather simple: provide the error message in one of the two formats.

If you're following along with Visual Studio, create a new class file and call it FooReaderError.cs. Visual Studio will ask you if you want to place the file in the App_Code folder. Choose yes.

The FooReaderError class is a static class, and it builds a simple RSS document with data from an Exception instance. It exposes one static method called WriteErrorDocument() that accepts an Exception object as a parameter. The following is the code for the FooReaderError class:

public static class FooReaderError
{
    public static string WriteErrorDocument(Exception exception)
    {
        string xml = "<?xml version="1.0" encoding="utf-8" ?>";
        xml += "<rss version="2.0">";
        xml += "<channel>";
        xml += "<title>FooReader Error</title>";

xml += "<description>FooReader Error</description> ";
        xml += "<link>javascript:void(0);</link>";
        xml += "<item>";
        xml += "<pubDate>Just Now</pubDate>";
        xml += String.Format("<title>FooReader Error: {0}</title>",
                             exception.GetType().ToString());

        xml += "<description>";
        xml += "<![CDATA[";
        xml += "<p>An error occurred.</p>";
        xml += String.Format("<p style='color: red'>{0}</p>", exception.Message);
        xml += "]]>";
        xml += "</description>";
        xml += "<link>javascript:void(0);</link>";
        xml += "</item>";
        xml += "</channel> ";
        xml += "</rss>";

        return xml;
    }
}

This code builds an RSS document to inform the user of two things.

  • The first gives the error's type by using exception.GetType().ToString(). This information is placed into the <item/> element's <title/>.

  • The second piece of information is the error message itself, which is displayed as red text.

The resulting XML file looks like this:

<?xml version="1.0" encoding="utf-8" ?>

<rss version="2.0">
    <channel>
        <title>FooReader Error</title>
        <description>FooReader Error</description>
        <link>javascript: void(0);</link>

        <item>
            <pubDate>Just Now</pubDate>
            <title>FooReader Error: [Error Type]</title>
            <description>
                <![CDATA[
                    <p>An error occurred.</p>
                    <p style='color: red'>[Error Message]</p>
                ]]>
            </description>
            <link>javascript: void(0);</link>
        </item>
    </channel>
</rss>

With the error strategy in place, the proxy code can handle and display errors when needed.

15.2.2.2. Building the Proxy

ASP.NET pages are classes that inherit from the System.Web.UI.Page class, and as such, they have formal class definitions and can have any number of properties and methods. The xmlproxy class, named after the file name, has one private method, aside from the usual protected Page_Load event handler.

If you're following along in Visual Studio, don't forget to add the proper using statement at the top of the class file. They are System.IO and System.Net.

public partial class xmlproxy : System.Web.UI.Page
{
    protected void Page_Load(object sender, EventArgs e)
    {
        //more code here
    }

    private string getLocalFile(string path)
    {
        string contents = String.Empty;
        using (StreamReader file = File.OpenText(path))
        {
            contents = file.ReadToEnd();
        }
        return contents;
    }
}

The getLocalFile() method is a private method for retrieving files located in the server's file system. This method is called to retrieve a cached feed. It uses a System.IO.StreamReader object to open the file and extract and return the file's contents.

15.2.2.3. Setting the Headers

The entry point for the server side is the Page_Load event handler, and all code, with the exception of the getLocalFile() method and the FooReaderError class, is contained in the event handler. The first step is to set two headers. Settings headers in ASP.NET is a simple task:

Response.ContentType = "text/xml";
Response.CacheControl = "No-cache";

//more code here

Headers are set with the Response object, which encapsulates HTTP response information. Setting the MIME content type is imperative to the operation of the application. Mozilla-based browsers will not load an XML file as XML unless the MIME specifies an XML document, and "text/xml" is one of many types that do this.

It is also important to make sure that the XML data retrieved with XHR is not cached. Today's browsers cache all data retrieved with XHR unless explicitly told not to with the CacheControl header. If this header is not set, the browser will use the cached data until the browser's cache is dumped.

15.2.2.4. Getting the Remote Feed

FooReader.NET's server component works as a simple proxy server. The client asks for a specific URL, and the application essentially forwards that request to the remote server. To determine the feed to retrieve, the server relies upon the query string. When requesting a feed, the client component sends a request in the following format:

xmlproxy.aspx?feed=[feed_url]

In ASP.NET, the Request object contains a NameValueCollection called QueryString. Using this collection, you can extract the value of the feed variable like this:

Response.ContentType = "text/xml";
Response.CacheControl = "No-cache";


if (Request.QueryString["feed"] != null)
{
    Uri url = null;

    try
    {
        url = new Uri(Request.QueryString["feed"]);
    }
    catch (UriFormatException exception)
    {
        Response.Write(FooReaderError.WriteErrorDocument(exception));

        Response.End();
    }

    string fileName = String.Format(@"{0}xml{1}.xml",
        Server.MapPath(String.Empty),
        HttpUtility.UrlEncode(url.AbsoluteUri)
    );

    //more code here
}
else
{
    try
    {
        throw new Exception("No feed specified for retrieval.");
    }
    catch (Exception exception)
    {
        Response.Write(FooReaderError.WriteErrorDocument(exception));
    }
}

This new code first checks the existence of the feed variable in the query string and attempts to create a Uri object with its value. If the value of feed is not in the correct format, the ASP.NET runtime throws a UriFormatException exception. If this happens, the UriFormException instance is passed to FooReaderError.WriteErrorDocument(). The resulting XML is written to the page with the Response.Write() method, and the application is terminated with Response.End(). Otherwise, the code continues, and a string called fileName is created. This variable is used as the file name of the cached feed when it is received.

If the feed argument could not be found in the query string, then a new Exception is thrown and caught to display the appropriate message to the user. This error should not occur through the FooReader.NET application because the client-side code will always include feed and its value in the query string.

String.Format(), as its name implies, formats strings (duh!). The first argument passed to the method is the string to format. This string most likely contains characters called format items that look like {0}, {1}, {2}, and so on. These format items are replaced with the corresponding arguments passed to the method. In the example above, {0} is replaced with the resulting string that Server.MapPath(String.Empty) returns.

The @ operator before a string tells the compiler not to process escape strings. The string in the above example could have been written as "{0}\xml\{1}.xml" with the same results.

The .NET Framework provides a variety of classes that can retrieve data from a remote server. In fact, the Weather Widget in Chapter 12 used the System.Net.WebClient class to retrieve data from Weather.com's weather service. The WebClient class offers a simple interface; however, its simplicity can also be a drawback. For example, WebClient does not have any way of setting an amount of time to wait before the request times out. It also lacks the ability to let the remote server know what kind of application is requesting the feed. It is for these reasons that FooReader.NET does not use WebClient, but uses System.Net.HttpWebRequest instead.

The HttpWebRequest class derives from the System.Net.WebRequest abstract class and provides HTTP-specific functionality, so it is best suited for this application. To create an HttpWebRequest object, call the Create() method of the WebRequest class and cast it as HttpWebRequest as the following code does:

Response.ContentType = "text/xml";
Response.CacheControl = "No-cache";

if (Request.QueryString["feed"] != null)
{
    Uri url = null;

    try
    {
        url = new Uri(Request.QueryString["feed"]);
    }
    catch (UriFormatException exception)
    {
        Response.Write(FooReaderError.WriteErrorDocument(exception));

        Response.End();
    }

    string fileName = String.Format(@"{0}xml{1}.xml",
        Server.MapPath(String.Empty),
        HttpUtility.UrlEncode(url.AbsoluteUri)
    );

try
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

        request.UserAgent = "FooReader.NET 1.5 (http://reader.wdonline.com)";
        request.Timeout = 5000;

        //more code here
    }
    catch (WebException exception)
    {
        if (System.IO.File.Exists(fileName))
        {
            Response.Write(getLocalFile(fileName));
        }
        else
        {
            Response.Write(FooReaderError.WriteErrorDocument(exception));
        }
    }
}
else
{
    try
    {
        throw new Exception("No feed specified for retrieval.");
    }
        catch (Exception exception)
    {
        Response.Write(FooReaderError.WriteErrorDocument(exception));
    }
}

After the request object's creation, this code uses the UserAgent property to assign the user-agent HTTP header. It is not necessary to use this property, but it accurately documents what is hitting the remote server. Many conventional aggregators have their own user-agent string; FooReader.NET does, too.

Following the UserAgent line, the Timeout property is set to 5 seconds. If no response is received from the remote server after 5 seconds, the request times out. If this happens, a WebException exception is thrown, in which case control is dropped to the catch block. The first line checks to see if the cached file exists in the file system; if so, it is retrieved with getLocalFile() and written to the page. If not, then the FooReaderError class provides an error document and outputs it to the application's response.

A WebException error can be thrown for any error that is network-related, not just for time outs.

At this point, however, the request has not been sent yet; the code is just ready to handle any web-related errors when it's sent. Sending the request involves invoking the HttpWebRequest object's GetResponse() method, which returns a WebResponse instance. The following new code does this.

Response.ContentType = "text/xml";
Response.CacheControl = "No-cache";

if (Request.QueryString["feed"] != null)

{
    Uri url = null;

    try
    {
        url = new Uri(Request.QueryString["feed"]);
    }
    catch (UriFormatException exception)
    {
        Response.Write(FooReaderError.WriteErrorDocument(exception));

        Response.End();
    }

    string fileName = String.Format(@"{0}xml{1}.xml",
        Server.MapPath(String.Empty),
        HttpUtility.UrlEncode(url.AbsoluteUri)
    );

    try
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

        request.UserAgent = "FooReader.NET 1.5 (http://reader.wdonline.com)";
        request.Timeout = 5000;


        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        {
            using (StreamReader reader =new StreamReader(response.GetResponseStream()))
            {
                string feedXml = reader.ReadToEnd();

                Response.Write(feedXml);

                //more code here
            }
        }
    }
    catch (WebException exception)
    {
        if (System.IO.File.Exists(fileName))
        {
            Response.Write(getLocalFile(fileName));
        }
        else
        {
            Response.Write(FooReaderError.WriteErrorDocument(exception));
        }
    }

    //more code here
}
else
{

try
    {
        throw new Exception("No feed specified for retrieval.");
    }
        catch (Exception exception)
    {
        Response.Write(FooReaderError.WriteErrorDocument(exception));
    }
}

When the request is sent, and the response is received, you can get the contents of the server's response by using the GetResponseStream() method, a member of the HttpWebResponse class. This method returns a Stream object and can be read with a System.IO.StreamReader object, which is created in the second using block. The contents of the stream are then "read" and stored in the feedXml variable. The value contained in feedXml is then written to the page with Response.Write(). Since the using statement is used for the creation of the HttpWebResponse and StreamReader objects, they will be properly disposed of and do not require closing.

15.2.2.5. Caching the Feed

Caching the feed is beneficial to the user in case the remote server cannot be reached. FooReader.NET is a newsreader, and it is desirable for the user to have something to read, even if it is an older version. A System.IO.StreamWriter object is perfect for this job for its ease of use and its default UTF-8 encoding:

Response.ContentType = "text/xml";
Response.CacheControl = "No-cache";

if (Request.QueryString["feed"] != null)
{
    Uri url = null;

    try
    {
        url = new Uri(Request.QueryString["feed"]);
    }
    catch (UriFormatException exception)
    {
        Response.Write(FooReaderError.WriteErrorDocument(exception));

        Response.End();
    }

    string fileName = String.Format(@"{0}xml{1}.xml",
        Server.MapPath(String.Empty),
        HttpUtility.UrlEncode(url.AbsoluteUri)
    );

    try
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

        request.UserAgent = "FooReader.NET 1.5 (http://reader.wdonline.com)";

request.Timeout = 5000;

        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        {
            using (StreamReader reader =new StreamReader(response.GetResponseStream()))
            {
                string feedXml = reader.ReadToEnd();

                Response.Write(feedXml);


                using (StreamWriter writer = new StreamWriter(fileName))
                {
                    writer.Write(feedXml);
                }
            }
        }
    }
    catch (WebException exception)
    {
        if (System.IO.File.Exists(fileName))
        {
            Response.Write(getLocalFile(fileName));
        }
        else
        {
            Response.Write(FooReaderError.WriteErrorDocument(exception));
        }
    }

    catch (IOException exception)
    {
        //do nothing here
    }

    //more code here
}
else
{
    try
    {
        throw new Exception("No feed specified for retrieval.");
    }
        catch (Exception exception)
    {
        Response.Write(FooReaderError.WriteErrorDocument(exception));
    }
}

The fileName variable, created earlier, is passed to the StreamWriter class's constructor. This creates the file or overwrites an existing one with the same name. The XML data contained in feedXml is then written to the file using the Write() method. This will cache the file, but any file system operation can increase the chance of an error. Therefore, it's important to handle a System.IO.IOException exception. This error is not critical. If the application got to the point of caching the file, then the request was successful, and data is available to send to the client-side component.

Only two errors are caught in this stage of the application. However, many others could occur, so it's important to catch every error possible. This can be done by catching a generic Exception like this:

Response.ContentType = "text/xml";
Response.CacheControl = "No-cache";

if (Request.QueryString["feed"] != null)
{
    Uri url = null;

    try
    {
        url = new Uri(Request.QueryString["feed"]);
    }
    catch (UriFormatException exception)
    {
        Response.Write(FooReaderError.WriteErrorDocument(exception));

        Response.End();
    }

    string fileName = String.Format(@"{0}xml{1}.xml",
        Server.MapPath(String.Empty),
        HttpUtility.UrlEncode(url.AbsoluteUri)
    );

    try
    {
        HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url);

        request.UserAgent = "FooReader.NET 1.5 (http://reader.wdonline.com)";
        request.Timeout = 5000;

        using (HttpWebResponse response = (HttpWebResponse)request.GetResponse())
        {
            using (StreamReader reader =new StreamReader(response.GetResponseStream()))
            {
                string feedXml = reader.ReadToEnd();

                Response.Write(feedXml);

                using (StreamWriter writer = new StreamWriter(fileName))
                {
                    writer.Write(feedXml);
                }
            }
        }
    }
    catch (WebException exception)
    {
        if (System.IO.File.Exists(fileName))
        {
            Response.Write(getLocalFile(fileName));
        }

else
        {
            Response.Write(FooReaderError.WriteErrorDocument(exception));
        }
    }
    catch (IOException exception)
    {
        //do nothing here
    }
    catch (Exception exception)
    {
        Response.Write(FooReaderError.WriteErrorDocument(exception));
    }
}
else
{
    try
    {
        throw new Exception("No feed specified for retrieval.");
    }
        catch (Exception exception)
    {
        Response.Write(FooReaderError.WriteErrorDocument(exception));
    }
}

Doing this allows the application to appear responsive to the user because every action (even if it is an error) causes the application to do something. It also allows them to address any issue that may arise because the error and the cause for the error are displayed for the user to see.

It also ensures that the client receives XML data every time it gets a response from the server. This is important because the client-side components depend on data being in RSS or Atom format.

Now that both the client and server components are completed, it is time to set up FooReader.NET on the web server.

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

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