CHAPTER 4

image

HttpClient

During the previous two chapters we’ve learned some important basics about asynchronous programming in .NET and the HTTP protocol. This is the first chapter that makes use of both foundations by introducing the HttpClient class; this class provides a way to handle HTTP requests and responses in .NET asynchronously on the client side (a server could also be a client). Before using HttpClient itself, let’s look at some new classes being used within both the HttpClient class and the ASP.NET Web API framework.

Commonalities: HTTP in ASP.NET Web API and HttpClient

You learned in Chapter 3 that HTTP, a network transfer protocol, handles the communication between servers and clients. As was said above, the HttpClient class represents the client-side implementation in .NET, and ASP.NET Web API represents the server-side implementation. But as shown in Chapter 3, HTTP mostly is not about servers or clients—it’s about both sides’ exchange of requests and responses. So regardless of whether you’re implementing an HTTP client or an ASP.NET Web API service (hence, the server), you need to be able to access the content of HTTP requests. In the client side case, you need to set the URI relative to the server root, the HTTP headers, and so on, in order to get data from the server or modify data on the server. On the server side, you need to access incoming requests to be able to understand what the client wants done—that is, creating, delivering, or modifying existing data or starting subsequent processes, like sending e-mails. As no one wants to deal with requests using native objects, Microsoft has introduced a .NET class that draws away that complexity. This class is named HttpRequestMessage, and its implementation and usage are examined in the next section.

HttpRequestMessage

In an HTTP-based scenario, everything starts with an HTTP request being sent from the client to the server; so let’s start there . HTTP requests in HttpClient and ASP.NET Web API are represented by the HttpRequestMessage class, which resides in the System.Net.Http namespace in .NET 4.5. If you’re using .NET 4.0, you won’t have access to that namespace. To use HttpRequestMessage anyway, you can install the NuGet package Microsoft.Net.Http, the backported version of the System.Net.Http namespace for .NET 4.0. The implementation of HttpRequestMessage (Listing 4-1 shows its public signature) conforms to HTTP messages, as defined in RFC 2616 by the IETF.

Listing 4-1.  The Public Signature of the HttpRequestMessage Class in the System.Net.Http Namespace

public class HttpRequestMessage : IDisposable {

    public HttpContent Content { get; set; }
    public HttpRequestHeaders Headers { get; }
    public HttpMethod Method { get; set; }
    public IDictionary<string,object> Properties { get; }
    public Uri RequestUri { get; set; }
    public Version Version { get; set; }
    
    public HttpRequestMessage();
    public HttpRequestMessage(HttpMethod method, String requestUri);
    public HttpRequestMessage(HttpMethod method, Uri requestUri);
    
    protected virtual void Dispose(Boolean disposing);
    public override string ToString();
    public void Dispose();
}

Let’s break this signature down to its components. HttpRequestMessage has six properties:

  • The first one is Content, which is of type HttpContent. This property represents the body content of the incoming request. The HttpContent type is an abstract class; we’ll discuss it in detail later in this chapter.
  • The HttpRequestHeaders collection type of the Headers property is a typed collection of the request header; we’ll come back to it a bit later.
  • The Method property is of type HttpMethod. It contains the HTTP method, like GET or POST, that the request should use (on the client side) or has been using (on the server side).
  • IDictionary<string,object> Properties allows storage of contextual state information that can be used during the request lifetime in your Web API application or HttpClient instance. There is more on this property in Chapter 14.
  • The RequestUri property allows setting or getting the URI of the request.
  • The Version property of type Version refers to the HTTP version; by default it is 1.1. The only reasonable nondefault value is 1.0 at this time.

HttpRequestMessage has three constructors:

  • The first is parameterless. When creating an instance using this constructor, the Method property of the request is set to HttpMethod.Get. The Uri property remains unset; it has to be set after the HttpRequestMessage instance is created.
  • The second constructor accepts two parameters, method and requestUri, which are the HTTP method and the URI of the request as a string.
  • The third constructor is quite similar to the second one, but the requestUri parameter now is of type Uri instead of String; this allows access to the parts of the URI in a typed way.

HttpRequestMessage also exposes three methods. Both Dispose methods concern disposing the HttpRequestMessage instance. The ToString method is overridden and produces some useful information that can be used for debugging or tracing purposes. Listing 4-2 shows the instantiation of an HttpRequestMessage writing the ToString output to the console window, as can be seen in Figure 4-1.

Listing 4-2.  Instantiation of an HttpRequestMessage

private static void Main(string[] args) {
    var request =
        new HttpRequestMessage(HttpMethod.Get,
                                new Uri("http://apress.com"));
    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
    Console.WriteLine(request.ToString());
    Console.ReadLine();

}

9781430247258_Fig04-01.jpg

Figure 4-1. Output of the ToString method of the HttpRequestMessage class

After creating the request instance pointing to http://apress.com to get the content of the Apress web site, the Accept header is set in a strongly typed manner to text/html. After that, the string representation of the request is written to the console, as shown in Figure 4-1.

As HttpRequestMessage has no complex dependencies, it can be unit-tested easily (see Chapter 14 for more).

In Listing 4-2, besides setting the request’s Method property to HTTP GET and pointing it to the URI http://apress.com, the HTTP Accept request header is set to text/html in a typed manner. As was already said, the Headers property of the HttpRequestMessage class is of type HttpRequestHeaders. This class encapsulates the HTTP request headers according to RFC 2616. Listing 4-3 shows the public signature of the HttpRequestHeaders class.

Listing 4-3.  Signature of the HttpRequestHeaders Class

public sealed class HttpRequestHeaders : HttpHeaders {
    public HttpHeaderValueCollection<MediaTypeWithQualityHeaderValue> Accept { get; }
    public HttpHeaderValueCollection<StringWithQualityHeaderValue> AcceptCharset { get; }
    public HttpHeaderValueCollection<StringWithQualityHeaderValue> AcceptEncoding { get; }
    public HttpHeaderValueCollection<StringWithQualityHeaderValue> AcceptLanguage { get; }
    public AuthenticationHeaderValue Authorization { get; set; }
    public CacheControlHeaderValue CacheControl { get; set; }
    public HttpHeaderValueCollection<string> Connection { get; }
    public bool? ConnectionClose { get; set; }
    public DateTimeOffset? Date { get; set; }
    public HttpHeaderValueCollection<NameValueWithParametersHeaderValue> Expect { get; }
    public bool? ExpectContinue { get; set; }
    public string From { get; set; }
    public string Host { get; set; }
    public HttpHeaderValueCollection<EntityTagHeaderValue> IfMatch { get; }
    public DateTimeOffset? IfModifiedSince { get; set; }
    public HttpHeaderValueCollection<EntityTagHeaderValue> IfNoneMatch { get; }
    public RangeConditionHeaderValue IfRange { get; set; }
    public DateTimeOffset? IfUnmodifiedSince { get; set; }
    public int? MaxForwards { get; set; }
    public HttpHeaderValueCollection<NameValueHeaderValue> Pragma { get; }
    public AuthenticationHeaderValue ProxyAuthorization { get; set; }
    public RangeHeaderValue Range { get; set; }
    public Uri Referrer { get; set; }
    public HttpHeaderValueCollection<TransferCodingWithQualityHeaderValue> TE { get; }
    public HttpHeaderValueCollection<string> Trailer { get; }
    public HttpHeaderValueCollection<TransferCodingHeaderValue> TransferEncoding { get; }
    public bool? TransferEncodingChunked { get; set; }
    public HttpHeaderValueCollection<ProductHeaderValue> Upgrade { get; }
    public HttpHeaderValueCollection<ProductInfoHeaderValue> UserAgent { get; }
    public HttpHeaderValueCollection<ViaHeaderValue> Via { get; }
    public HttpHeaderValueCollection<WarningHeaderValue> Warning { get; }
}

The HttpRequestHeaders class derives from the HttpHeaders class, which mainly implements logic to handle HTTP headers easily from within a derived class such as HttpRequestHeaders. The properties of HttpRequestMessage shown in Listing 4-3 represent the HTTP request headers defined in RFC 2616. As can be seen, certain headers, like Accept, allow multiple values using collections, whereas others, like Host, allow string and other simple value types.

Besides the HTTP header properties in Listing 4-3, HttpRequestHeaders exposes a few methods derived from the underlying HttpHeaders class. Methods like Add, Remove, and Clear allow you to modify the list of HTTP headers in the HttpRequestHeaders instance. Listing 4-4, which shows a modified version of Listing 4-2, sets a custom request header not defined in RFC 2616.

Listing 4-4.  Adding a Custom Header to a Request

private static void Main(string[] args) {
    var request =
        new HttpRequestMessage(HttpMethod.Get,
                                new Uri("http://apress.com"));
    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/html"));
    request.Headers.Add("X-Name", "Microsoft");
    Console.WriteLine(request.ToString());
    Console.ReadLine();
}

Figure 4-2 confirms that the custom header has been added to the headers collection.

9781430247258_Fig04-02.jpg

Figure 4-2. Custom HTTP header in a HttpRequestMessage instance

Having now learned the most important details about the HTTP request abstraction for HttpClient and ASP.NET Web API, let’s go to its counterpart and dig into the HttpResponseMessage that represents the HTTP request.

HttpResponseMessage

After receiving a request on the server side from the client, you can process the request and return its results to the client. The means of transportation for these results is, according to RFC 2616, an HTTP response. Along with HTTP requests, HTTP responses are abstracted for easier use with the .NET CLR. The implementation can be found in the HttpResponseMessage class, a neighbor of the HttpRequestMessage in the System.Net.Http namespace.

As can be seen in Listing 4-5, its public signature is comparable with the one of HttpRequestMessage.

Listing 4-5.  HttpResponseMessage Signature

public class HttpResponseMessage : IDisposable {

    public HttpContent Content { get; set; }
    public HttpResponseHeaders Headers { get; }
    public bool IsSuccessStatusCode { get; }
    public string ReasonPhrase { get; set; }
    public HttpRequestMessage RequestMessage { get; set; }
    public HttpStatusCode StatusCode { get; set; }
    public Version Version { get; set; }

    public HttpResponseMessage();
    public HttpResponseMessage(HttpStatusCode statusCode);

    protected virtual void Dispose(Boolean disposing);
    public void Dispose();
    public override string ToString();
    public HttpResponseMessage EnsureSuccessStatusCode();
}

HttpResponseMessage has seven public properties:

  • As responses, like requests, have a content body, HttpResponseMessage owns an HttpContent property similar to HttpRequestMessage; HttpContent will be dissected later in this chapter.
  • The Headers property of HttpResponseMessage provides a collection of HTTP response headers of the type HttpResponseHeaders; we’ll look at them shortly.
  • The Boolean IsSuccessStatusCode indicates whether the request has been successful. We’ll come back to that detail in the section on HttpClient.
  • The ReasonPhrase property corresponds to the reason phrase element of the HTTP specification and represents the human-readable part of the status line. A reason phrase example is “Not Found” for the HTTP status code 404.
  • The RequestMessage property contains the HttpRequestMessage that caused the initial response.
  • The StatusCode property contains the HTTP status code in a typed manner.
  • The Version property is the same as for HttpRequestMessage; it permits specifying the HTTP version, which by default is 1.1.

HttpResponseMessage has two constructors. The first, parameterless constructor creates an instance whose StatusCode is set to 200 OK. The second constructor expects an HttpStatusCode instance as a parameter.

HttpResponseMessage also exposes four methods, where both Dispose methods concern disposal of the HttpResponseMessage instance. As with HttpRequestMessage, the ToString method is overloaded and produces useful debugging information. The EnsureSuccessStatusCode method, related to the IsSuccessStatusCode, will be explained in detail in the “HttpClient” section.

This section has shown how HTTP responses are represented in ASP.NET Web API and HttpClient. The HttpResponseMessage class is an HTTP response message implementation according to RFC 2616 and allows typed access to the properties of an HTTP response. As was seen in the sections on HttpRequestMessage and HttpResponseMessage, they have a common property, one that represents the request or response body: HttpContent. Let’s explore that next.

HttpContent

In Chapter 3, you learned that content sent from the server to the client, such as an HTML page or a representation of a customer object, is contained in the body part of the HTTP response. Furthermore, when sending data from the client to the server (for example, when sending a file using HTTP POST or updating a customer using HTTP PUT), the data are also contained in the body part of the HTTP request. The sections on HttpRequestMessage and HttpResponseMessage have shown that the body part of the HTTP requests and responses are represented by a class named HttpContent. First, let’s look at the HttpContent class and then consider a custom implementation.

The HttpContent Class

HttpContent is an abstract base class in the System.Net.Http namespace. Its signature is shown in Listing 4-6.

Listing 4-6.  Abstract Base Class HttpContent

public abstract class HttpContent : IDisposable {

    protected HttpContent();

    public HttpContentHeaders Headers { get; }

    public Task CopyToAsync(Stream stream, TransportContext context);
    public Task CopyToAsync(Stream stream);
    protected virtual Task CreateContentReadStreamAsync();
    public Task LoadIntoBufferAsync();
    public Task LoadIntoBufferAsync(long maxBufferSize);
    protected abstract Task SerializeToStreamAsync(Stream stream, TransportContext context);
    protected internal abstract bool TryComputeLength(out long length);
    public Task<byte[]> ReadAsByteArrayAsync();
    public Task<Stream> ReadAsStreamAsync();
    public Task<string> ReadAsStringAsync();

    public void Dispose();
    protected virtual void Dispose(bool disposing);
}

Besides a parameterless default constructor, HttpContent exposes a public property, Headers, which is of type HttpContentHeaders. Regarding the request and response headers, this also conforms with RFC 2616. HttpContentHeaders implements an abstraction of the so-called entity header fields, which define metainformation about the entity body (that is, the request or response body) or, if no body is present, about the resource identified by the request.

Listing 4-7 shows the public interface of the HttpContentHeaders class.

Listing 4-7.  HttpContentHeaders Class

public sealed class HttpContentHeaders : HttpHeaders {

    public ICollection<string> Allow { get; }
    public ContentDispositionHeaderValue ContentDisposition { get; set; }
    public ICollection<string> ContentEncoding { get; }
    public ICollection<string> ContentLanguage { get; }
    public long? ContentLength { get; set; }
    public Uri ContentLocation { get; set; }
    public byte[] ContentMD5 { get; set; }
    public ContentRangeHeaderValue ContentRange { get; set; }
    public MediaTypeHeaderValue ContentType { get; set; }
    public DateTimeOffset? Expires { get; set; }
    public DateTimeOffset? LastModified { get; set; }
}

The HttpContentHeaders class includes a number of properties:

  • The Allow property contains a collection listing the methods supported by the resource identified by the initial request URI.
  • The ContentEncoding property allows provision of additional information about the media type of the content. Common content encodings used are gzip and Deflate (you’ll see a sample later).
  • ContentLanguage allows you to set the intended audience’s natural language in the representation of the resource returned in the response body.
  • The ContentLength property describes the length of the content in bytes.
  • The next property, ContentLocation, may contain an alternative URI to the one by which the content has been requested. This can be useful if your server provides specific URIs for different media types for the same resource. For an example of a request, see Listing 4-8.

Listing 4-8.  Requesting a Customer Representation

GET http://localhost/api/customer/1
Accept: application/json

A possible response to the request from Listing 4-8, with the Content-Location header set, is shown in Listing 4-9.

Listing 4-9.  Response Containing a Content-Location Header

200 OK
Content-Type: application/json
Content-Location: http://localhost/api/customer/1.json

{ "id" : 1, "name" : "Microsoft" }

  • Be aware that Content-Location should not be used for linking when a restful API using hypermedia is being created.
  • The ContentMD5 property, which provides an MD5 digest for the content, can be used for integrity checks.
  • Another property exposed by the HttpContentHeaders class is ContentRange, which is set if the content of the body represents only a part of an entity. ContentRange defines the position where the content of the body should be placed in the complete entity.
  • The ContentType property defines the content-type header already seen in Chapter 3; it specifies the type of the content in the body. An example of a content type is text/html.
  • The Expires property returns either the date or time (or both) after which the response is considered stale.
  • The last property of the HttpContentHeader class is LastModified, which indicates the date and time at which the sender claims the entity was last modified.

With this little excursion into HTTP entity headers and their representation in .NET now done, let’s go back to the HttpContent class in Listing 4-6. All methods exposed by the HttpContent base class—along with the inherited methods for disposing, as well as the TryComputeLength method—are asynchronous.

The CopyToAsync methods of the HttpContent class serialize the HttpContent into a stream and then copy it to a stream passed in as a parameter. Besides that stream parameter, the second CopyToAsync method expects a parameter of type TransportContext, which provides support for authentication scenarios. The TransportContext instance can be retrieved from the GetRequestStream method of the HttpWebRequest class, for example.

The virtual CreateContentReadStreamAsync method can be overridden by a concrete implementation of HttpContent; it serializes the HTTP content into a memory stream asynchronously. Both LoadIntoBufferAsync methods serialize the HTTP content to a memory buffer as an asynchronous operation; the second one expects a long value as maximum buffer size.

The abstract SerializeToStreamAsync method has to be overridden in a derived class, and it has to serialize the HTTP content into a stream being passed in as a parameter. The TransportContext parameter of the SerializeToStreamAsync method is the same as the one for the CopyToAsync method already described.

The last three methods allow you to return the data of the HttpContent as a byte array, stream, or string. Listing 4-10 shows the use of StringContent deriving from HttpContent.

Listing 4-10.  Use of StringContent

class Program
{
    static void Main(string[] args)
    {
        var stringContent = new StringContent("Hello World");
        stringContent.ReadAsStringAsync().ContinueWith(
            (t) => Console.WriteLine(t.Result)
            );
        Console.ReadLine();
    }
}

The result of Listing 4-10 is shown in Figure 4-3.

9781430247258_Fig04-03.jpg

Figure 4-3. The output of a StringContent instance

As was said, HttpContent is an abstract base class. System.Net.Http provides five concrete implementations of HttpContent out of the box; see Figure 4-4.

9781430247258_Fig04-04.jpg

Figure 4-4. Classes deriving from HttpContent base class

We have already used one implementation of HttpContent in Listing 4-10: StringContent. The StringContent class derives, not from HttpContent, but from ByteArrayContent instead. ByteArrayContent provides HTTP content based on a byte array. Another class deriving directly from HttpContent is StreamContent, which provides HTTP content based on a stream. The last two implementations of HttpContent are MultipartContent, which is derived by MultitpartFormatDataContent. MultipartContent (shown in Figure 4-4) not only derives from HttpContent but also implements the IEnumerable<HttpContent> interface, as well as the IEnumerable interface. That way, it is possible to send or receive multipart content. Multipart messages have multiple body parts split by a boundary. An example of a multipart message is multipart/form-data, which allows you to send the input of an HTML form and several files within a single request.

As with almost all frameworks, most of the time the default implementations work well. But sometimes there are scenarios where you need to add functionality that is not contained in the box. That’s what we will do next: create a custom HttpContent implementation.

Custom HttpContent Implementation: CompressedContent

Though we live in a world of high-speed Internet connections, the connection always feels too slow. One way to reduce the amount of data transferred, thus increasing your Web API application’s speed and responsiveness, is to compress your response body before sending it to the client. In HTTP two compression standards have been established: gzip and Deflate.

A client able to read gzip- or Deflate-compressed content can communicate that to the server by setting the Accept-Encoding header to gzip or deflate or both separated by a comma. If a server is serving gzip- or Deflate-compressed content, it communicates that to the client, setting the Content-Encoding header accordingly. Note that Microsoft’s Internet Information Services (IIS) supports HTTP compression; you might want to implement your own compression, as it isn’t available out of the box when using the self-hosting option of ASP.NET Web API. A viable way to support HTTP compression is to implement an HttpContent class that can create compressed content from an existing HttpContext instance. Listing 4-11 shows the implementation of the CompressedContent class.

Listing 4-11.  An Example Console App Using the CompressedContent Class

class Program
{
    static void Main(string[] args)
    {
        var httpContent = new StringContent(@"
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
");
        httpContent.ReadAsStringAsync().ContinueWith(
            t =>
                {
                    Console.WriteLine(t.Result);
                    Console.WriteLine("Initial size: {0} bytes",
                        httpContent.Headers.ContentLength);
                }
            );

        var compressedContent = new CompressedContent(httpContent, "gzip");
        compressedContent.ReadAsStringAsync().ContinueWith(
            t =>
                {
                    var result = t.Result
                    Console.WriteLine("Compressed size: {0} bytes",
                    compressedContent.Headers.ContentLength);
                }
            );

        Console.ReadLine();
    }
}

The CompressedContent class has a constructor that expects an HttpContent parameter and a string parameter called contentEncoding. After two null value checks, the content parameter is copied to a private field, _initialContent. The contentEncoding parameter is copied to a private field, _contentEncoding. If a Content-Encoding header setting other than gzip or Deflate has been passed as a contentEncoding parameter, an exception with a meaningful description is thrown. If everything went fine until now, all HTTP headers except the Content-Length header from the initial content are copied to the headers collection of the CompressedContent instance. When the SerializeToStreamAsync method is called, a GZipStream or DeflateStream, depending on the _contentEncoding field value, is created. The initial content is copied to the compressedStream instance and gets compressed that way. The continuation of the CopyToAsync task disposes the compressed stream (if any) in the end. Listing 4-12 shows an example to prove the function of the CompressedContent class.

Listing 4-12.  Sample Usage of the CompressedContent Class

class Program {
    static void Main(string[] args) {
        var httpContent = new StringContent(@"
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet.
");
        Console.WriteLine(httpContent.ReadAsStringAsync().Result);
        Console.WriteLine("Initial size: {0} bytes", httpContent.Headers.ContentLength);

        var compressedContent = new CompressedContent(httpContent, "gzip");
        var result = compressedContent.ReadAsStringAsync().Result;
        
        Console.WriteLine("Compressed size: {0} bytes",
            compressedContent.Headers.ContentLength);
        Console.ReadLine();
    }
}

In Listing 4-12, an instance of StringContent is first created. That content, as well as its size in bytes, is written to the console output. After that, a CompressedContent instance is created with the StringContent and gzip encoding as parameters. The size of the compressed content then is also written to the console output.

When running that program at the console, what is output is similar to Figure 4-5.

9781430247258_Fig04-05.jpg

Figure 4-5. Regular content size versus compressed content size

In order to make the CompressedContent work in your ASP.NET Web API application, you need to register the HttpCompressionHandler shown in Listing 4-13. Don’t worry if you don’t understand it now; message handlers will be explained in depth in Chapter 10.

Listing 4-13.  HttpCompressionHandler for Use with ASP.NET Web API and CompressedContent

public class CompressHandler : DelegatingHandler {
    protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken) {
        return base.SendAsync(request, cancellationToken)
                .ContinueWith<HttpResponseMessage>((responseToCompleteTask) => {
                    var response = responseToCompleteTask.Result;

                    if (response.RequestMessage != null &&
                        response.RequestMessage.Headers.AcceptEncoding.Any()) {

                        var contentEncoding =
                            response.RequestMessage.Headers.AcceptEncoding
                                    .First().Value;

                        if (null != response.Content) {
                            response.Content =
                                new CompressedContent(response.Content,
                                    contentEncoding);
                        }
                    }
                    return response;
                },
                TaskContinuationOptions.OnlyOnRanToCompletion);
    }
}

The HttpCompressionHandler implementation checks the requests for Accept-Encoding headers for values. If it contains at least one value, an instance of CompressedContent is created with the initial content of the response. The updated response with the compressed content is then returned.

With some necessary foundations—including HttpRequestMessage, HttpResponseMessage, their HTTP headers, and the HttpContent class—explained, we can finally start using HttpClient itself.

HttpClient

You’ve already seen that HttpClient is a class that allows you to handle HTTP requests and responses in .NET. HttpClient has been introduced in a rewritten version with .NET 4.5 and resides in the System.Net.Http namespace. It is also available in the NuGet package Microsoft.Net.Http for .NET 4.0. A previous version (2009) of HttpClient was introduced by Microsoft when it released the REST Starter Kit, but it never became part of the core .NET Framework before .NET 4.5. Before HttpClient, handling HTTP requests and responses in .NET was sort of an awkward task. Since .NET Framework 1.1, the choice was to use the HttpWebRequest and HttpWebResponse classes or the WebClient class, but none of these provided the streamlined concept that HttpClient does. Furthermore, there was no easy way to handle HTTP content or headers, as previous sections have shown. But enough of the advance praise for HttpClient! Let’s see how to work with HTTP on the client side in .NET.

Introducing HttpClient

To start, take a look at the public signature of HttpClient in Listing 4-14.

Listing 4-14.  HttpClient Public Signature

public class HttpClient : HttpMessageInvoker {
    public HttpRequestHeaders DefaultRequestHeaders { get; }
    public Uri BaseAddress { get; set; }
    public TimeSpan Timeout { get; set; }
    public long MaxResponseContentBufferSize { get; set; }

    public HttpClient();
    public HttpClient(HttpMessageHandler handler);
    public HttpClient(HttpMessageHandler handler, bool disposeHandler);

    public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request);
    public override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken);
    public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        HttpCompletionOption completionOption);
    public Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        HttpCompletionOption completionOption, CancellationToken cancellationToken);

    public Task<HttpResponseMessage> GetAsync(string requestUri);
    public Task<HttpResponseMessage> GetAsync(Uri requestUri);
    public Task<HttpResponseMessage> GetAsync(string requestUri,
        HttpCompletionOption completionOption);
    public Task<HttpResponseMessage> GetAsync(Uri requestUri,
        HttpCompletionOption completionOption)
    public Task<HttpResponseMessage> GetAsync(string requestUri,
        CancellationToken cancellationToken);
    public Task<HttpResponseMessage> GetAsync(Uri requestUri,
        CancellationToken cancellationToken);
    public Task<HttpResponseMessage> GetAsync(string requestUri,
        HttpCompletionOption completionOption, CancellationToken cancellationToken);
    public Task<HttpResponseMessage> GetAsync(Uri requestUri,
        HttpCompletionOption completionOption,
        CancellationToken cancellationToken)

    public Task<string> GetStringAsync(string requestUri);
    public Task<string> GetStringAsync(Uri requestUri);

    public Task<byte[]> GetByteArrayAsync(string requestUri);
    public Task<byte[]> GetByteArrayAsync(Uri requestUri);

    public Task<Stream> GetStreamAsync(string requestUri);
    public Task<Stream> GetStreamAsync(Uri requestUri);

    public Task<HttpResponseMessage> PostAsync(string requestUri,
        HttpContent content);
    public Task<HttpResponseMessage> PostAsync(Uri requestUri,
        HttpContent content);
    public Task<HttpResponseMessage> PostAsync(string requestUri,
        HttpContent content, CancellationToken cancellationToken);
    public Task<HttpResponseMessage> PostAsync(Uri requestUri,
        HttpContent content, CancellationToken cancellationToken);

    public Task<HttpResponseMessage> PutAsync(string requestUri,
        HttpContent content);
    public Task<HttpResponseMessage> PutAsync(Uri requestUri,
        HttpContent content);
    public Task<HttpResponseMessage> PutAsync(string requestUri,
        HttpContent content, CancellationToken cancellationToken);
    public Task<HttpResponseMessage> PutAsync(Uri requestUri,
        HttpContent content, CancellationToken cancellationToken);

    public Task<HttpResponseMessage> DeleteAsync(string requestUri);
    public Task<HttpResponseMessage> DeleteAsync(Uri requestUri);
    public Task<HttpResponseMessage> DeleteAsync(string requestUri,
        CancellationToken cancellationToken);
    public Task<HttpResponseMessage> DeleteAsync(Uri requestUri,
        CancellationToken cancellationToken)

    public void CancelPendingRequests();

    protected override void Dispose(bool disposing);
}

At first sight, the HttpClient signature seems huge. Don’t worry; it will feel small and handy once we’ve dissected it.

HttpClient contains four properties:

  • DefaultRequestHeaders of type HttpRequestHeaders. We inspected them earlier in this chapter.
  • BaseAddress. This property is self-explanatory: it contains a common URI to which all request URIs are relative. An example of a BaseAddress is http://localhost/api.
  • Timeout. This property defines a TimeSpan in milliseconds, after which the request times out.
  • HttpClient. This last property is the MaxResponseContentBufferSize, which defines the maximum number of bytes to buffer when reading the response content. It defaults to 64 kilobytes.

The simplest way to create an instance of HttpClient is to call its parameterless default constructor. To provide some context for the next two constructors of HttpClient, we’ll look at the implementation of the parameterless constructor in Listing 4-15.

Listing 4-15.  Parameterless Constructor Implementation of HttpClient

public HttpClient() : this((HttpMessageHandler) new HttpClientHandler()) {
}

The only thing that happens in this constructor is that it creates an instance of HttpClient, with a new instance of HttpClientHandler as a parameter. HttpClientHandler itself derives from HttpMessageHandler. So calling the parameterless constructor of HttpClient is similar to calling its second constructor in the way shown in Listing 4-16.

Listing 4-16.  Creating an Instance of HttpClient Using the Second Constructor

var httpClient = new HttpClient(new HttpClientHandler());

Digging deeper into the implementation details of HttpClient reveals that HttpClient itself contains no code that can handle requests or responses. Instead, it delegates the complete workload to the HttpMessageHandler implementation, which is by default HttpClientHandler (message handlers will be covered in depth in Chapter 10). The HttpMessageHandler base class contains only one method, called SendAsync. Its implementation in the HttpClientHandler class under the hood uses HttpWebRequest and HttpWebResponse classes. Thanks to the shown abstractions of HTTP requests and HTTP responses, there is no longer a need to work with them directly. You don’t have to touch HttpClient itself to change its implementation; instead, just implement a new HttpMessageHandler and pass it to the constructor of HttpClient as a parameter.

The third constructor of HttpClient expects another parameter, called disposeHandler of type bool. This parameter’s background is as follows: if you create an HttpMessageHandler instance and pass it into the HttpClient as a parameter when HttpClient is disposed, the passed-in HttpMessageHandler instance will be disposed, too. If the disposeHandler parameter of HttpClient is set to false, the passed-in HttpMessageHandler instance won’t be disposed.

With respect to the constructors in Listing 4-14, there are four variations of the SendAsync method. SendAsync overrides the SendAsync method of the HttpMessageInvoker class from which HttpClient derives. The SendAsync method of the HttpMessageInvoker is the one responsible for calling the SendAsync method on the HttpMessageHandler used in the HttpClient implementation. Basically, all variations of the SendAsync method of HttpClient expect a HttpRequestMessage as the first parameter. The second parameter, HttpCompletionOption, of some overloads of SendAsync allows the decision whether to receive the complete response, including all headers and the complete content, before doing further processing or to just read the headers without reading the complete content. This can be useful when the server content received is huge.

The methods after the SendAsync methods in Listing 4-14—including GetAsync, PostAsync, PutAsync, and DeleteAsync (as well as their overloads)—are helper methods; they wrap the SendAsync method to abstract the details of creating instances of HttpRequestMessage, HttpResponseMessage, and HttpContent when sending requests. Basically, all these helper methods expect a URI to which the request should be addressed. With the PostAsync and PutAsync methods, it is also mandatory to pass in an instance of HttpContent according to RFC 2616.

The last method of HttpClient worth mentioning is CancelPendingRequests, which allows cancellation of all pending requests independent of their current processing state.

Now that we have handled the theory of HttpClient, it’s time to fire some requests and see what happens.

Getting Data Using HttpClient

When using an HTTP client, you’ll mostly want to get data as a string from a server. As you may have noticed, three methods—GetStringAsync, GetByteArrayAsync, and GetStreamAsync (and their overloads)—were kept back when the methods of HttpClient were explained. These methods are helper methods around the GetAsync method of HttpClient. With these methods, getting data from a server using the HttpClient class is a no-brainer, as Listing 4-17 verifies.

Listing 4-17.  Getting Data from a URI as a String

var uri = "https://raw.github.com/AlexZeitler/HttpClient/master/README.md";
var httpClient = new HttpClient();
httpClient.GetStringAsync(uri).ContinueWith(
    t => Console.WriteLine(t.Result));
Console.ReadLine();

After creating an instance of HttpClient, the uri instance defined before is passed as a parameter to the GetStringAsync method of the HttpClient instance. The result of this Task (which is a string) can be written directly to the console output. The result of Listing 4-14 is seen in Figure 4-6.

9781430247258_Fig04-06.jpg

Figure 4-6. Output of the GetStringAsync method of HttpClient

The GetByteArrayAsync and GetStreamAsync methods are akin in use. To get data from a Web API instead of a web site, you might want to pass it into a local variable, like an instance of a Customer class. Thanks to some extension methods for the HttpClient class, this can be done almost effortlessly. First let’s take a look at the customer representation we’ll receive from our Web API (see Listing 4-18).

Listing 4-18.  Customer Representation in JSON

{
    "id" : 1,
    "name" : "Apress"
}

As Listing 4-19 shows, the Customer class implementation on the client side is pretty straightforward.

Listing 4-19.  Customer Class Implementation

public class Customer {
    public int Id { get; set; }
    public string Name { get; set; }
}

To deserialize a Customer instance from the JSON shown in Listing 4-18, you would have to access the response content, determine the type you want to deserialize it to, and find an appropriate MediaTypeFormatter. (This topic, a huge one, is handled in Chapter 12.) After that, you have to invoke the ReadFromStreamAsync method of the MediaTypeFormatter—and keep your fingers crossed. Thanks to the HttpContentExtensions class in the System.Net.Http namespace, you don’t have to do any of this work. Instead, simply use the code shown in Listing 4-20.

Listing 4-20.  Deserializing HttpContent into CLR Types

var client = new HttpClient();
client.GetAsync("http://localhost:3739/api/customer/1").ContinueWith(
    tr => tr.Result.Content.ReadAsAsync<Customer>()
        .ContinueWith(
            tc => Console.WriteLine("Id: {0}, Name: {1}", tc.Result.Id, tc.Result.Name)
        )
    );
Console.ReadLine();

The result of Listing 4-20 is shown in Figure 4-7.

9781430247258_Fig04-07.jpg

Figure 4-7. The output of the ReadAsAsync<T> extension method

In order to use the HttpContentExtensions, you’ll need to reference System.Net.Http.Formatting in .NET 4.5, or else you’ll need to install the NuGet package Microsoft.AspNet.WebApi.Client when using .NET 4.0.

Now that requests have been sent using HttpClient to GET data from a Web API and a web site, we’re ready to do something more difficult. Let’s try to POST some data to a Web API now.

Sending Data Using HttpClient

As you saw during the overview of the members that HttpClient exposes, there are several PostAsync methods that allow you to send data to a Web API. Besides these methods, the Content-Type header has to be set to tell the server side which media type is being used to send the new entity over the wire. Normally you’d have to create an instance of HttpRequestMessage, assign its content with a new instance of the appropriate HttpContent implementation, and set the contents request headers accordingly. The signature of the PostAsync methods shows that there is no overload of PostAsync, which allows a parameter of type HttpRequestMessage. This is not a mistake; the PostAsync method provides a much nicer way to get the work done. Listing 4-21 shows the solution’s implementation.

Listing 4-21.  Posting Data Using PostAsync

var customer = new Customer()
{
    Name = "Microsoft"
};

var client = new HttpClient();
client.PostAsync(
    "http://localhost:3739/api/customer",
    customer,
    new JsonMediaTypeFormatter())
        .ContinueWith(
        t =>
            {
            if (t.Result.StatusCode == HttpStatusCode.Created)
            {
                t.Result.Content.ReadAsAsync<Customer>().ContinueWith(
                    tc => Console.WriteLine("Id: {0}, Name: {1}",
                        tc.Result.Id, tc.Result.Name)
                    );
            }
            });

First, let’s create a new instance of the Customer class without assigning an ID, as this is the responsibility of the server side. After instantiating a new HttpClient, call its PostAsync method with the URI of the Web API that creates the new instance, the Customer instance, on the server side. The last parameter is an instance of JsonMediaTypeFormatter; it handles serialization of the Customer instance into JSON. After getting the response as a result, you can evaluate the HTTP status code, which should be “201 Created”. If this is the case, deserialize the entity returned as JSON into the Customer instance again. The console output is similar to the one in Figure 4-7. If you don’t want to mess around with media-type formatters, you can use another extension method, one that simplifies use of PostAsync even more (see Listing 4-22).

Listing 4-22.  Using PostAsJsonAsync Instead of PostAsync

var response = client.PostAsJsonAsync("http://localhost:3739/api/customer", customer).Result;

As sending data using the HTTP POST method has not been much more difficult than receiving data, let’s now try using the SendAsync method of HttpClient directly to achieve a more difficult goal.

Using ASP.NET Forms Authentication from HttpClient

As a last sample for HttpClient, consider the following: authenticate a request against a Web API that has been implemented using ASP.NET MVC. This API is protected using Forms Authentication. Forms Authentication works as follows: in a POST request you send username and password using application/x-www-form-urlencoded media type. If your credentials can be authenticated at the server side, you get a response containing a Set-Cookie header with the authentication cookie. This string value has to be sent as the Cookie header during subsequent requests to authenticate yourself against the Web API. Listing 4-23 shows the complete implementation of the client side.

Listing 4-23.  Using Forms Authentication with HttpClient

private static void Main(string[] args) {

    Console.WriteLine("Username:");
    var username = Console.ReadLine();
    Console.WriteLine("Password:");
    var password = Console.ReadLine();

    var client = new HttpClient();
            
    // setup initial authentication request
    var authRequest = new HttpRequestMessage() {
        Method = HttpMethod.Post,
        RequestUri = new Uri("http://localhost:3915/Account/Login"),
        Content = new FormUrlEncodedContent(
            new List<KeyValuePair<string, string>> {
                new KeyValuePair<string, string>("Username", username),
                new KeyValuePair<string, string>("Password", password)
        })
    };

    // try to authenticate
    var authResponse = client.SendAsync(authRequest).Result;
    IEnumerable<string> values;
    authResponse.Headers.TryGetValues("Set-Cookie", out values);

 
    if (null == values || string.IsNullOrEmpty(values.First())) {
        Console.WriteLine("Username and password must equal.");
        Console.ReadLine();
        return;
    }
            
    var cookie = values.First();
            
    // setup request to retrieve data from the server
    var request = new HttpRequestMessage() {
        RequestUri = new Uri("http://localhost:3915/customer/get/1")
    };
            
    // assign cookie
    request.Headers.Add("Cookie", cookie);
    request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            
    var response = client.SendAsync(request).Result;
    Console.WriteLine("Customer: {0}", response.Content.ReadAsAsync<Customer>().Result.Name);
    Console.ReadLine();
}

After requesting username and password from the console and wiring up a new HttpClient instance, a new HttpRequestMessage is created. According to the Forms Authentication requirements, the method of the request is set to POST, and the credentials are assigned to a FormUrlEncodedContent instance. The request is POSTed against the login URI of the Web API. If authentication has been successful on the server side, the cookie is returned, parsed from the response, and reassigned to the request header of the subsequent request, which then retrieves the required data. The output is written to the console again. Figure 4-8 shows the workflow with valid credentials in the console window, whereas Figure 4-9 shows a failing scenario.

9781430247258_Fig04-08.jpg

Figure 4-8. Successful login

9781430247258_Fig04-09.jpg

Figure 4-9. Login failed

Also, note that the last sample made no use of any helper or extensions methods; it’s been quite easy to implement the requirements using the SendAsync method of HttpClient. There might be more complex scenarios, but in the end the use of HttpClient, the requests and responses, and the content sent or retrieved remains mostly the same.

Summary

With the introduction of the System.Net.Http namespace in .NET framework 4.5 and its backport to 4.0, available via NuGet, handling HTTP in .NET became handier and more straightforward to use. Thanks to the abstractions of HTTP requests, responses, and body content, they can be accessed in a typed manner. HttpClient itself provides a modern and robust handler for HTTP in the .NET framework, which additionally provides useful extensibility points.

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

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