Chapter 3. The AJAX Application Server: Windows Communication Foundation’s Web Programming Model

After completing this chapter, you will

  • Understand the Web programming model for Windows Communication Foundation (WCF) in the Microsoft .NET 3.5 Framework.

  • Understand how to use WebGet and WebInvoke to provide Web-style services.

  • Understand HTTP verbs, headers, and status codes.

  • Understand the architectural principles of Representational State Transfer (REST).

  • Understand how to use WCF syndication services to generate syndicated feeds.

  • Understand how to implement HTTP handlers to process the raw HTTP context.

WCF and the Web Programming Model

WCF was introduced in the .NET 3.0 Framework. It was designed to provide a unified programming model for communications, whether peer to peer, server to server, or even distributed .NET applications. WCF in the .NET 3.5 Framework adds support for the Web programming model through the System.ServiceModel.Web assembly, enabling the use of AJAX applications and Web client technologies such as the ASP.NET AJAX JavaScript client, Microsoft Silverlight, Adobe Flash, and syndicated feed consumers. The Web programming model is built on top of the HTTP protocol, and it is much simpler than the SOAP protocol because it is based on standard HTTP requests. And because the Web programming model is based on standard HTTP, it is by nature accessible to the widest variety of clients, including Web browsers with no additional client framework.

Note

Note

The Web programming model refers to a simple request and response model that uses the HTTP protocol without the semantics of SOAP.

The WCF Web programming model exposed through the System.ServiceModel.Web assembly consists of classes that implement Web-style services that use HTTP verbs and scripting behaviors for JavaScript integration. Web-style services are Web services that are accessible through pure browser technology. They can be called using the URL-based protocol, as opposed to a SOAP-based framework. In WCF documentation, the use of Web-style services is also known as the Web programming model. Although the System.ServiceModel.Web assembly provides additional functionality, including a robust framework for syndication, its main purpose is to support Web services through the Web programming model. This simplicity is central to the AJAX application frameworks that you’ll develop using the service-based approach introduced in this book. Table 3-1 lists the key classes and types defined in the System.ServiceModel.Web assembly. We’ll look at these classes and types in depth throughout the chapter.

Table 3-1. Key System.ServiceModel.Web Types

Type

Description

WebGetAttribute

Maps GET requests to WCF service methods.

WebInvokeAttribute

Maps HTTP verbs other than GET to WCF service methods.

WebOperationContext

Wraps the WCF OperationContext object and provides access to the HTTP context through a service-oriented programming model.

Provides access to instances of IncomingWebRequestContext, IncomingWebResponseContext, OutgoingWebRequestContext, and OutgoingWebResponseContext.

IncomingWebRequestContext

Provides access to the HTTP context of the incoming request through a service-oriented programming model.

OutgoingWebResponseContext

Provides access to the HTTP context of the outgoing response through a service-oriented programming model.

Tip

Tip

OutgoingWebRequestContext and IncomingWebResponseContext are used only by WCF client applications and are not used in the WCF service implementation.

Enabling the Web Programming Model

The Web programming model is enabled through the WCF behavior WebHttpBehavior and the WCF binding WebHttpBinding. Because WCF uses a configuration-based programming model, the behavior and binding are applied to the endpoint through web.config settings, as demonstrated in Example 3-1.

Tip

Tip

Code for this book is available online at http://www.microsoft.com/mspress/companion/9780735625914. The code for this chapter is in the file Chapter 3.zip.

Example 3-1. WebHttpBehavior and WebHttpBinding enable the Web programming model.

<system.serviceModel>
  <serviceHostingEnvironment aspNetCompatibilityEnabled="true" />
  <behaviors>
    <endpointBehaviors>
      <behavior name="RestBehavior">
        <webHttp />
      </behavior>
    </endpointBehaviors>
  </behaviors>

  <services>
    <service name="KnowledgeBase.DataService">
      <endpoint address="rest" behaviorConfiguration="RestBehavior"
                binding="webHttpBinding"
                contract="KnowledgeBase.IRestDataService" />
    </service>
  </services>
</system.serviceModel>

The WebGet and WebInvoke attributes enable the service to be exposed through the Web programming model. Both WebGetAttribute and WebInvokeAttribute are IOperationBehavior attributes. WCF attributes that implement IOperationBehavior are passive and have no effect on the service on their own—the attributes only define metadata on the service method that can be consumed through behaviors. A behavior, such as WebHttpBehavior, that consumes the IOperationBehavior attributes can process this metadata. This technique lets you use WebGet and WebInvoke on service interface methods without tying the implementation to the Web programming model. The same Web service can be exposed through WebHttpBinding and through "traditional" SOAP-based Web services by using wsHttpBinding, as discussed in Chapter 2. I’ll talk more about WebGet and WebInvoke throughout this chapter because they are central to the Web programming model.

Accessing the Current Web Context from WCF

The Web context gives you full access to the incoming request and outgoing response, including HTTP headers, status, and verbs. To access the current Web context from a WCF operation, you need to access the property WebOperationContext.Current.WebOperationContext exposes the current Web request and response and is the preferred method of working with the HTTP context through the WCF programming model. Because the WCF programming model is based on contracts and messages, you generally don’t have direct access to the HTTP response, as you would with a "raw" HTTP handler in ASP.NET. If you need direct access to the response, you can implement a stream-based handler to return text or binary data such as images, but this is an exception to the rule and shouldn’t be used for data-oriented services.

You need to work with WebOperationContext for direct access to HTTP headers or other properties of the request and response when working with REST services. The WebOperationContext class and its IncomingWebRequestContext and OutgoingWebResponse-Context classes are shown in Figure 3-1.

WebOperationContext provides access to the IncomingWebRequestContext and the OutgoingWebResponseContext classes.

Figure 3-1. WebOperationContext provides access to the IncomingWebRequestContext and the OutgoingWebResponseContext classes.

Tip

Tip

Because the System.ServiceModel.Web WebOperationContext class is oriented toward contract-based service programming, you should use its class heirarchy instead of the ASP.NET HttpContext class when working with WCF services.

To access the HTTP response so that you can set HTTP headers or status, use the Outgoing-WebResponseContext class, which is exposed through the property WebOperationContext.Current.OutgoingResponse. This class does not expose the response stream because you return an object from the service operation, and this object is serialized to the response stream. However, you might want to set HTTP headers or the HTTP status code in a Web application, in which case you would use OutgoingWebResponseContext. The following code sample uses OutgoingWebResponseContext (through the property OutgoingResponse) to set an HTTP header and the HTTP status code of the response:

WebOperationContext.Current.OutgoingResponse.Headers[
        HttpResponseHeader.CacheControl] = "Private";
WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.OK;

Tip

Tip

Even though the WebOperationContext class replaces most of the functionality of HttpContext, you still need to use the HttpContext class to access the User security principal when running WCF in integrated pipeline mode (enabled through ASP.NET compatibility configuration). The User principal is not exposed through WebOperationContext. The property HttpContext.Current.User accesses the authenticated User principal from an ASP.NET integrated service.

The REST Architectural Pattern and WCF

The WCF Web programming model is based on REST (Representational State Transfer) principals, which are a stricter subset of the Web programming model. In many cases in the WCF documentation, the terms Web programming model and REST are interchangeable. In fact, the WCF Web programming model itself was built to support REST architectures.

Tip

Tip

REST is an ideal mechanism for AJAX data retrieval using simple HTTP transports without the need for a SOAP envelope.

The REST architectural pattern is based on the premise that resources are represented by a unique human-readable URL. I introduced the concept of POX (Plain Old XML) Web service endpoints in Chapter 1. POX endpoints are a subset of the full REST pattern. The full REST pattern defines methods for data creation, retrieval, updates, and deletion using standard HTTP verbs. In the REST pattern, the GET verb is always used for read-only data access instead of a Web service POST, which by definition is not cacheable. Because GET requests can take advantage of Web caching technologies, including server caching, browser caching, and even proxy caching, REST-style endpoints for data access have specific advantages over other endpoint implementations, whether you implement the full REST pattern or not. As you build your back-end service architecture, you might find that a mixture of REST services (especially for data retrieval) and SOAP-based Web services is ideal.

Tip

Tip

Internet standard protocols such as the Atom publishing protocol are based entirely on REST services, making REST an essential pattern for the modern Web developer to know.

The REST architectural approach bases resources on URI identifiers and uses the standard HTTP verbs GET, PUT, POST, and DELETE. In a typical REST implementation, GET is used for data retrieval, POST is used to create new items, PUT to update existing items, and DELETE to delete an item.

In Chapter 1 I also introduced the Sys.Net.WebRequest JavaScript class, which is the class you use to access REST Web services using JavaScript code. The Sys.Net.WebRequest JavaScript class is used to perform simple Web requests in an asynchronous pattern. To recap, the following JavaScript code sample (Example 3-2) creates a WebRequest instance using the ASP.NET AJAX library and asynchronously executes a GET request for the WCF service endpoint "example.svc/hello". Upon completion, the method onCompletedHandler, passed in with the add_completed method, is called, which can then be used to process the response asynchronously. The callback handler is called for both successful operations and failed operations.

Example 3-2. The Sys.Net.WebRequest JavaScript type is used to call REST endpoints.

function OnAjaxLoad()
{
    // Creates a new WebRequest object
    var request = new Sys.Net.WebRequest();

    // Used to send arbitrary value-typed data
    var userContext = new Object();
    request.set_userContext = userContext;

    // Sets the request endpoint
    request.set_url('example.svc/hello'),

    // Adds the callback handler for the response
    // The callback handler contains the parameters response and userContext
    request.add_completed(onCompletedHandler);

    // Specifies the HTTP verb (GET is the default verb)
    request.set_httpVerb('GET'),

    // Sets the content type header
    request.get_headers()["Content-Type"] = "application/xml";

    // Invokes the method asynchronously
    // The completed callback handler is called upon completion
    request.invoke();
}

function onCompletedHandler(response, userContext)
{
    // Process the response here
    // response is the WebRequestExecutor that contains the HTTP response
    // userContext is the object passed to request.set_userContext
}

Using WebGet for Data Retrieval

GET requests are mapped to service methods using the WebGet attribute. By applying the WebGet attribute, you indicate that the method is a data retrieval operation and can be exposed through the GET verb. The following code shows the WebGet attribute applied to a data-retrieval interface method, specifying the response format JSON:

[OperationContract]
[WebGet(ResponseFormat=WebMessageFormat.Json)]
DataItem GetData(string catalog, string title);

HTTP GET methods are central to REST architecture. In the REST pattern, the URL requested identifies the resource and the HTTP verb specifies the action. The GET verb is special and should only be used for retrieving data; a GET request should never update data on the server. GET endpoints should be a resource that can be bookmarked and cached, and, ideally, be readable. Content retrieved with GET can (and will) be cached on proxy servers and in the client browser, which offers a significant advantage for AJAX Web applications. Utilizing the client’s Web browser’s cache can dramatically improve AJAX performance by reducing network traffic and load time. We’ll look at implementing cache support in the section "Client-Side Caching with GET" later in this chapter. The WebGet attribute applied to WCF methods also supports the passing of parameters through the URI template, which I’ll discuss in the next section.

Tip

Tip

Methods exposed through WebGet and WebInvoke are not exposed through the AJAX runtime’s JavaScript proxy and must be called through the JavaScript type Sys.Net.WebRequest.

Readable URLs and URI Templates

A core principle of REST is that a URI identifies a resource. A resource is simply requested by its URI, and you can perform actions on the object by applying HTTP verbs to the URI. For example, the URL user.svc/daniel.larson/profile could be used to identify the user profile for Daniel Larson. The URI is built using a readable, path-based approach, as opposed to a query string–based approach, which would be something like user.svc/profile?user=daniel.larson. Note that a URL with query string arguments does not provide a unique URL for the resource, because query strings are unordered pairs. A uniquely identifiable URL is built by using the path that identifies the resource. For example, the following URL could uniquely define the "welcome" entry in the "fundamentals" data catalog:

"feed.svc/fundamentals/topics/welcome"

A URI template is a special type of formatted string that is used to extract variables from the path. You can also use it to build sets of structurally similar URLs that differ only by the resource’s logical primary key—such as a URL that defines items posted by that contains the author variable as part of the path. Named variables are defined in the path in curly braces. The URL consists of path and query segments, each of which can be used within the URI template, with the query attributes being defined as unordered pairs. For example, the following URI templates define a set of URLs that we’ll use to retrieve data from the knowledge base application:

  • "feed.svc/{catalog}/topics/{title}"

  • "feed.svc/{author}/"

  • "feed.svc/{author}/tags/{tag}"

Tip

Tip

Using a URI template with the WebGet or WebInvoke attribute in a service interface that is run through WebScriptBehavior causes a service-activation error. As a result, you might want to place REST-style methods in an isolated interface.

Ideally, the query string (as opposed to the path) should not be used to uniquely identify a resource but should be used only to filter a resource’s data set. For example, the path feed.svc/daniel.larson can be used to identify a feed of items published by Daniel Larson (rather than the query-string based URL of feed.svc?author=daniel.larson), and data filters can further be applied by using query parameters. For example, the URL feed.svc/daniel.larson?filter=today specifies only items published today. The query string is not used to uniquely identify the resource (or list of resources) but only to filter the data.

You won’t need to work directly with the UriTemplate class when writing WCF services using the WebGet and WebInvoke attributes, but you can use the class directly when parsing or building URLs in code. URI templates allow you to parse path information from a URI as method parameters. For example, the path DataService.svc?catalog=Default&title=Hello could be converted to the URI DataService.svc/Default/Hello by applying the following URI template to the WebGet attribute:

[OperationContract]
[WebGet(UriTemplate = "{catalog}/{title}", ResponseFormat = WebMessageFormat.Json)]
DataItem GetData(string catalog, string title);

To use URI templates, you need to define them on an interface that is not exposed through an endpoint configured with the enableWebScript behavior. The Web scripting behavior is not compatible with UriTemplate, which means you must create a dedicated interface for the REST endpoints. Fortunately, you can use the same service class and expose it through the alternative REST interface. In the following examples, I’ve defined endpoints through the IRestDataService interface, which is shown in Example 3-3 and configured through the following web.config endpoint configuration:

<service name="KnowledgeBase.DataService">
    <endpoint address="rest"
        behaviorConfiguration="PoxBehavior" binding="webHttpBinding"
        contract="KnowledgeBase.IRestDataService" />
</service>

Example 3-3. The WebGet attribute with a UriTemplate is used to pass parameters to WCF methods (Knowledgebase/IRestDataService.cs).

using System;
using System.ServiceModel;
using System.ServiceModel.Web;

namespace KnowledgeBase
{
    [ServiceContract(Namespace = "http://knowledgebase/", Name = "Data")]
    interface IRestDataService
    {
        [WebGet(UriTemplate = "{catalog}/{title}"]
        [OperationContract(Action = "Get")]
        DataItem GetData(string catalog, string title);
    }
}

To access the "Hello" data entry in the "Default" catalog, you could simply use the URL http://knowledgebase/DataService.svc/rest/Default/Hello.

Tip

Tip

URI templates are a key component in building REST services using WCF.

Using the UriTemplate Class to Build URLs

The UriTemplate class allows you to build and parse URLs using URI template strings. It is not tied to the WCF networking stack but contained in the assembly System.ServiceModel.Web. UriTemplate defines the BindByName and BindByPosition methods to build templates with named or ordered arguments. The following code demonstrates a URI template that defines a feed with an author and a tag.

string template = "feed.svc/{author}/{tag}";
UriTemplate uriTemplate = new UriTemplate(template);
Uri prefix = new Uri("http://knowledgebase");
Uri url = uriTemplate.BindByPosition(prefix, "daniellarson","ajax");

The UriTemplate class can also be used to parse arguments passed in the URL. You use the Match method to retrieve arguments from the URL, as in the following code:

string template = "feed.svc/{author}/{tag}";
UriTemplate uriTemplate = new UriTemplate(template);

Uri incomingUrl = new Uri("http://knowledgebase/feed.svc/daniellarson/ajax");
UriTemplateMatch results = uriTemplate.Match(
        new Uri(incomingUrl.GetLeftPart(UriPartial.Authority)),
        incomingUrl);
if (results != null){
    string author = results.BoundVariables["author"];
    string tag = results.BoundVariables["tag"];
    Console.WriteLine("{0} : {1}", author, tag);
}

Finally, the UriTemplate class can be used in situations in which you cannot pass arguments by using a UriTemplate parameter, such as WCF services with the Web scripting behavior or when you are building URLs to access remote services.

Supporting REST Service Actions with WebInvoke

As I mentioned earlier, in the Web programming model, the GET verb is special, indicating a read-only data retrieval operation. All other HTTP verbs specify an action and can be mapped to a WCF method by using the WebInvoke attribute. The WebInvoke attribute by default maps to the POST verb, although any HTTP verb other than GET can be specified in the parameter. To map an HTTP verb to WCF, specify the verb in the parameter, as in the following example of an interface method:

[OperationContract]
[WebInvoke(Method = "PUT",
        UriTemplate = "update",
        RequestFormat = WebMessageFormat.Json,
        ResponseFormat = WebMessageFormat.Json,
        BodyStyle = WebMessageBodyStyle.Bare)]
    void Update(DataItem entry);

The WebInvoke attribute includes the following parameters that affect the endpoint. These attribute parameters are also common in the WebGet attribute.

  • BodyStyle. Determines the style of the incoming and outgoing messages using the WebMessageBody enumeration. WebMessageBodyStyle.Bare is the default, which is the behavior you want most often and indicates that the result of the service method is written directly to the body of the response. Other choices are WebMessageBodyStyle.Wrapped, in which the request and response are wrapped, WebMessageBodyStyle.WrappedRequest, and WebMessageBodyStyle.WrappedResponse.

  • RequestFormat. Specifies the request format with the WebMessageFormat enumeration, although the request format can be inferred by the request’s Content-Type HTTP header regardless of the RequestFormat specified in the WebInvoke or WebGet attribute.

  • ResponseFormat. Specifies the response format when invoked using the webHttp-Binding binding and webHttp behavior. The value is either WebMessageFormat.Xml or WebMessageFormat.Json. This value cannot be overridden through the AJAX request or inferred through the Content-Type HTTP header as with RequestFormat.

When calling a service from client code (JavaScript), you must specify the content type using the Content-Type HTTP header; otherwise, the WCF endpoint won’t understand the request and an exception will be thrown by the service. To set the content type, use the get_headers method of the Sys.Net.WebRequest instance that is invoking the method. The Content-Type header is case sensitive in the ASP.NET AJAX library. The WCF content type for XML is "application/xml", and the content type for JSON is "application/json". The following JavaScript code sets the content type as XML by using the WebRequest class’s headers property:

var request = new Sys.Net.WebRequest
request.get_headers()["Content-Type"] = "application/xml";

Tip

Tip

The two choices for supported content types are "application/xml" and "application/json". If you need to support additional content types using WCF services, you must implement a WebContentTypeMapper. For more information, see the MSDN example at http://msdn2.microsoft.com/en-us/library/bb943479.aspx.

Warning

Warning

While HTTP headers are not case sensitive, the Sys.Net.WebRequest Content-Type header is. If you don’t specify the header using the case-sensitive header "Content-Type", the AJAX JavaScript framework will overwrite it before the request.

The POST verb specifies that a Web request is sending data to the server, and it is generally considered the optimal way to update a data source using standard Web browser technology. POST requests send a message payload in the body of the post and are used to post a message from the client to the server. In the following example, we’ll use the JavaScript object Sys.Net.WebRequest to post content to the sample endpoint. By using the set_httpVerb method, you can specify the HTTP POST method and you can also send a message payload in the body of the request. Example 3-4 demonstrates an HTTP POST to a WCF service using XML.

Example 3-4. To post an XML message to WCF, use the XML schema from the data contract and the "application/xml" content type.

// Saves the Item using the XML data contract
function Save(){
    var data = new Object();
    data.Title = $get('TitleDiv').innerHTML;
    data.Body = $get('DataInput').value;

    var xml = String.format(  // - MS JS method similar to c# string.Format()
    '<Item xmlns="http://knowledgebase">
        <Title>{0}</Title>
        <Body>{1}</Body>
        <Format>wiki</Format>
    </Item>', data.Title, data.Body);

    var userContext = new Object();
    var request = new Sys.Net.WebRequest();
    var url = 'DataService.svc/rest/save';
    request.set_url(url);
    request.add_completed(onSaveSuccess);
    request.set_httpVerb('POST'),
    request.set_userContext = userContext;
    request.get_headers()["Content-Type"] = "application/xml";
    request.set_body(xml);
    request.invoke();
}

In most cases, creating a JavaScript object is far easier than creating XML on the client, especially when considering the differences between browsers in the XML DOM. However, JavaScript objects exist only in memory and must be serialized before they can be passed across network services. To serialize the object as a string, you can use the AJAX class Sys.Serialization.JavaScriptSerializer. This JavaScript type defines the methods serialize and deserialize. To serialize the object, simply call Sys.Serialization.JavaScriptSerializer.serialize(object) on the JavaScript object. In most cases you should avoid writing JSON by hand and use the client-side AJAX framework and the server-side WCF framework to handle the serialization.

Note

Note

To post a JavaScript object to a Web service, pass a JSON-serialized object to the request along with the Content-Type header "application/json". The client-side AJAX library provides the class Sys.Serialization.JavaScriptSerializer, which can serialize or deserialize objects to and from JSON.

Example 3-5 shows a Save method that posts a JavaScript object to the WCF service defined at DataService.svc/rest/save.

Example 3-5. Sys.Serialization.JavaScriptSerializer provides JSON serialization for JavaScript objects that can be posted to a REST endpoint.

// Saves the Item using a JavaScript object
function Save(){
    var data = new Object();
    data.Title = $get('TitleDiv').innerHTML;
    data.Body = $get('DataInput').value;
    var json = Sys.Serialization.JavaScriptSerializer.serialize(data);

    var userContext = new Object();
    var request = new Sys.Net.WebRequest();
    var url = 'DataService.svc/rest/save';
    request.set_url(url);
    request.add_completed(onSaveSuccess);
    request.set_httpVerb('POST'),
    request.set_userContext = userContext;
    request.get_headers()["Content-Type"] = "application/json";
    request.set_body(json);
    request.invoke();
}

HTTP Header Processing with the HEAD Verb

The HEAD verb is used only to process HTTP headers and does not receive a response body. Because HEAD requests are only processed on the server by returning HTTP headers, they are the most lightweight Web request that you can make. These requests are useful for simple methods, such as authorization checks or session "keep-alive" methods, for which there is no need to fully process the request or return a response. Implementing a simple "keep alive" service can be useful in AJAX applications to maintain authentication sessions or to check whether the current Web context’s state is still valid. Because one goal of a rich Internet application is to provide client functionality similar to a Windows application, you might want to provide a method that calls a keep-alive method on a set interval so that the user’s authentication session does not expire if his browser is idle but still left open. It can be annoying to replace a desktop application with a Web application only to have it time out on you after a long-running session. You might also use a keep-alive approach to return an HTTP header indicating an invalid state, which might occur if another user deletes the item that is being viewed during a long-running page lifetime. Example 3-6 defines a "keep-alive" service that accepts a HEAD request for the Ping method.

Example 3-6. Use the HEAD method to process a simple request using HTTP headers (ExampleServices/KeepAlive.cs).

using System;
using System.Net;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Activation;

namespace ExampleServices
{
    [ServiceContract]
    interface IKeepAlive
    {
        [WebInvoke(Method = "HEAD", BodyStyle = WebMessageBodyStyle.Bare)]
        [OperationContract]
        void Ping();
    }

    [AspNetCompatibilityRequirements(RequirementsMode =
        AspNetCompatibilityRequirementsMode.Required)]
    public class KeepAlive : ExampleServices.IKeepAlive
    {
        public void Ping()
        {
            WebOperationContext.Current.OutgoingResponse.StatusCode =
                    HttpStatusCode.OK;
            return;
        }
    }
}

To call a WCF HEAD method from the JavaScript client, invoke WebRequest using the HEAD verb. In the callback, check the response status code for 200 (OK). If the status code is not OK, you can perform additional actions. I’ll discuss HTTP status codes in more detail in the next section. For now, it is enough to know that the status code 200 indicates success. Example 3-7 demonstrates a JavaScript WebRequest call that uses the HEAD verb to check for the response status code property.

Example 3-7. The HEAD verb is used for processing HTTP headers and can be used to implement a keep-alive call (Web/KeepAlive.aspx).

var headRequest = new Sys.Net.WebRequest();
headRequest.set_url('KeepAlive.svc/ping'),
headRequest.set_httpVerb('HEAD'),
headRequest.add_completed(headSuccess);
headRequest.invoke();

function headSuccess(response, userContext){
    var OK = 200;
    if (response.get_statusCode() != OK){
        var statusText = response.get_statusText();
        alert(statusText);
    }
}

HTTP Status Codes

HTTP status codes are used to indicate success or failure and include standard error codes used by the HTTP protocol. HTTP status codes are returned as integers and are encapsulated in the .NET Framework enumeration System.Net.HttpStatusCode. The AJAX framework does not include a client-side enumeration of HTTP status codes, so you will usually handle status codes yourself. Although there are more than 50 status codes, the main ones to discuss are OK (200), Forbidden (403), Unauthorized (401), Not Modified (304), and Internal Server Error (500). Status codes are also arranged in ranges: the 200 range indicates success, the 300 range indicates redirection, the 400 range indicates failure or authentication issues, and the 500 range indicates server errors. The most relevant HTTP status codes are listed in Table 3-2.

Table 3-2. Common HTTP Status Codes

Code

Description

2XX Success

Responses in the 200 range indicate success.

200 OK

Request was successful.

201 Created

Indicates that the POST was successful and the item was created on the server.

204 No Content

Indicates success and that the response is intentionally blank.

3XX Redirection

Responses in the 300 range indicate request redirection.

301 Moved

Indicates that the resource has been moved to a new URI. Browsers (including AJAX clients) will redirect the request to the URI indicated in the LOCATION HTTP header.

304 Not Modified

Indicates that the resource has not been modified since the last response, and redirects the client request to the client cache.

4XX Client Errors

Responses in the 400 range indicate an error in the request.

400 Bad Request

Indicates a bad request.

401 Unauthorized

Unauthorized. Returns an AUTHORIZATION HTTP header indicating an authentication protocol that the client can use.

403 Forbidden

Indicates the requested object is forbidden and cannot be accessed by the client. Use Forbidden to explicitly deny access to a resource.

404 Not Found

Indicates there is no resource at the URI requested. This status code is usually returned by the Web server (IIS) and not service code.

410 Gone

Indicates that the resource has been deleted. This status code is usually returned by service code to indicate that the resource requested does not exist.

5XX Server Errors

Responses in the 500 range indicate an error in the response.

500 Internal Server Error

Indicates a generic server error.

501 Not Implemented

Indicates that the request (the URI and HTTP verb combination) is not implemented by server code.

With normal responses, the 200 (OK) value is returned. You can check for the status code 200 (OK) in your AJAX callback functions to determine success for HTTP calls. The 403 (FORBIDDEN) status specifies that the server refuses to process the request for the current security principal, whereas the 401 (UNAUTHORIZED) status specifies an unauthenticated or not properly authorized response. The general purpose 500 (Internal Server Error) status is perhaps the most useful error response for AJAX applications. It usually specifies additional data in the status description. From the WCF service, the status description is returned through the WebOperationContext.Current.OutgoingResponse.StatusDescription property.

Tip

Tip

The AJAX callback will receive a 200 (OK) status code from 304 (NOT MODIFIED) responses as the request is redirected to the client cache response, which has a 200 (OK) status. I’ll talk about NOT MODIFIED and the client cache later in this chapter.

When making Web requests using the JavaScript class Sys.Net.WebRequest, the completed callback handler is used for both success and failure responses. The completed callback handler is added with the add_completed method prior to calling invoke. The callback handler receives the Sys.Net.WebRequestExecutor type as the response and can be used to retrieve the status code, status description, response headers, and the actual response. To indicate success, the server responds with a 200 (OK) status. Any other status is either a failure or requires additional action. To handle the status code on the client, use the get_statusCode method of WebRequestExecutor. This is a different error-handling strategy than is used with Web services exposed through the Web scripting behavior discussed in Chapter 2, where the JavaScript proxy generates a success callback and a failure callback. Instead, all responses are processed in a common callback function. Example 3-8 demonstrates a common error-handling strategy for Web requests. First the status code is retrieved using response.get_ statusCode. If the status code is not 200 (OK), you must implement error handling accordingly.

Example 3-8. The get_statusCode and get_statusText methods of the WebRequestExecutor JavaScript type return the status code and description from the server (Web/Rest.aspx).

function onSaveCompleted(response, obj){
    // Process the status. Could be 200 OK,
    // 403 forbidden, 404 not found, 410 gone
    var status = response.get_statusCode();
    if (status != 200){
        var statusText = response.get_statusText();
        var errMsg = String.format('ERROR: {0} replied "{1}" ({2}).',
            url, statusText, status);
        Sys.Debug.trace(errMsg);

        switch(status){
            case 410:
                alert('Content has been removed.'),
                break;
            case 404:
                alert('Could not find resource.'),
                break;
            case 403:
                alert('Access forbidden.'),
                break;
            default:
                alert(errMsg);
        }
        return;
    }

    var result = response.get_responseData();
    var content = $get('MainContent'),
    if (content != null)
        content.innerHTML = result;
}

Example 3-8 demonstrates error handling within the callback function for a save operation. You might want to further abstract the data retrieval and response handling with a custom networking library implemented on top of Sys.Net.WebRequest. You could use such a library to provide common infrastructure such as friendly error handling and more robust support for client-side caching.

Client-Side Caching with GET

One of the biggest advantages of the HTTP GET verb is its support for client-side caching in the HTTP protocol. Client-side caching is built into all modern Web browsers and can dramatically improve the performance of your AJAX application. Client-side caching is based on the URI of the request—the browser stores a cache file for each URL. For Internet Explorer on Windows Vista, the files are stored in %HOMEPATH%AppDataLocalMicrosoftWindows Temporary Internet Files. These cache files have the attributes Name, Internet Address, Type, Size, Expires, Last Modified, Last Accessed, and Last Checked. The Last Modified and Expires attributes are the only attributes that you can explicitly set on the server.

Tip

Tip

Because caching is unique for each URI, you might want to build a compilation key into your JavaScript application that is unique for each build to prevent the use of data from a previous build. For a related solution, see the ScriptRuntimeControl code sample in Chapter 4.

The GET response can specify additional metadata about the response stream, indicating cache policy; specifying if, how long, and where the content can be cached; and the date that the item was last modified on the server. The Last-Modified HTTP header indicates the date that the item was last changed on the server and is sent from the server to the client and back to the server in the incoming If-Modified-Since header. Because the Last-Modified header is rounded to the second, it cannot be used for precise date and time measurement. The ETag HTTP header is also sent from the server to the client and back to the server. It is an opaque token that is returned with the next request, which makes it a better candidate for a cache key if you can rely on a last-modified date from the data source.

By setting a cache policy and a last-modified date, a Web browser can successfully cache the data on the client. For most data, it is ideal to use the client-side cache but have the browser check with the server for a fresh copy of the data on each request. This enables the server to send a 304 (NOT MODIFIED) response if the server data has not changed, without the risk of having out of date data. The data query for the last updated date in a database table is usually much more efficient than querying the entire data set and processing the response. The server can detect that there is no new data and return a "not modified" response (HTTP status 304), bypassing additional processing and serialization of the data.

Tip

Tip

Client-side caching using the ETag or the Last-Modified header is best used for data that is changed relatively infrequently but requested often.

To force the browser to always ask the server if the server has a fresh copy of the data, expire the data using the Expires HTTP header, as in the following code:

WebOperationContext.Current.OutgoingResponse.Headers
    [HttpResponseHeader.Expires] = DateTime.UtcNow.AddYears(-1).ToString("r");

Tip

Tip

If you do not explicitly expire the response, the browser might cache the data indefinitely, depending on the browser’s cache settings.

To return the date that the server data was last modified, use the Last-Modified header as in the following code, which sets the header to the current universal time. Note that the value used is the local time. The framework will convert this to universal time.

WebOperationContext.Current.OutgoingResponse.LastModified = DateTime.Now;

The Last-Modified header value is returned to the server in the HTTP header If-Modified-Since. This value is the date-time stamp in GMT (universal) format that is passed from the Web service in the Last-Modified header. To get this value from the request, use the following code in the Web service:

string lastModClientString =
    WebOperationContext.Current.IncomingRequest.Headers["if-modified-since"];

To return a "not modified" value to the client, set the HTTP status code to 304 (specified by the .NET type HttpStatusCode.NotModified) as in the following example:

WebOperationContext.Current.OutgoingResponse.StatusCode = HttpStatusCode.NotModified;

When you return a 304 (NOT MODIFIED) response to the client, the browser intercepts the 304 response and returns a 200 (OK) status with a copy of the cached data that is redirected from the browser’s cache. This means that for a simple AJAX client, you won’t be able to tell from the HTTP status alone whether a response is from the browser’s cache or from the server.

The If-Modified-Since header specifies a conditional response, not a partial response. Either the full data set requested should be returned or the 304 (NOT MODIFIED) status with an empty response stream. A subset of data reflecting the if-modified-since date should not be returned. For a subset of data, use an alternative mechanism to request the filtered data set, such as a query string filter. Example 3-9 demonstrates simple caching logic with the LAST-MODIFIED and IF-MODIFIED-SINCE HTTP headers.

Example 3-9. To implement client-side caching, return 304 (HttpStatusCode .NotModified) to utilize the browser cache (ExampleServices/CacheSample.cs).

using System;
using System.ServiceModel;
using System.ServiceModel.Web;
using System.ServiceModel.Activation;
using System.Web;
using System.Globalization;
using System.Net;

namespace ExampleServices
{
    [AspNetCompatibilityRequirements(
        RequirementsMode = AspNetCompatibilityRequirementsMode.Allowed)]
    [ServiceContract]
    public class CacheSample
    {
        [WebGet][OperationContract]
        public string Demo()
        {
            string lastModClientString =
                WebOperationContext.Current.IncomingRequest.Headers
                ["if-modified-since"];
            if (lastModClientString != null)
            {
                DateTime lastMod = DateTime.MinValue;
                DateTime tempDateTime = DateTime.MinValue;
                if (DateTime.TryParse(lastModClientString,
                        CultureInfo.InvariantCulture,
                        DateTimeStyles.AssumeUniversal, out tempDateTime))
                    lastMod = tempDateTime.ToUniversalTime();

                if (DateTime.UtcNow - lastMod < TimeSpan.FromMinutes(1))
                {
                    WebOperationContext.Current.OutgoingResponse.StatusCode =
                        System.Net.HttpStatusCode.NotModified;
                    return null;
                }
            }

            // DO NOT USE HttpContext.Current,
            // instead use WebOperationContext.Current

            WebOperationContext.Current.OutgoingResponse.Headers
                [HttpResponseHeader.CacheControl] = "Private";

            WebOperationContext.Current.OutgoingResponse.LastModified =
                DateTime.Now.AddMinutes(1);

            // Expire the content to force a request
            WebOperationContext.Current.OutgoingResponse.Headers
                [HttpResponseHeader.Expires] =
                DateTime.Now.AddYears(-1).ToString("r");

            return DateTime.Now.ToString("r");
        }
    }
}

Although the code sample in Example 3-9 uses a hard-coded last-modified expiration time of 1 minute, you would ideally check the last-modified date against the server data source’s last-modified date. If you are using a back-end database, you should always use the database time as the last-modified time stamp, which allows you to pass in a last-modified time to the data query.

Tip

Tip

When converting local time to universal time with the .NET Framework, always check that the time to be converted is not in the future. If it is, an ArgumentOutOfRangeException is thrown. This exception is a common bug on distributed systems where the time might not be perfectly synchronized.

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

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