Universal App

Universal App - 操作 Windows.Web.Http.HttpClient


过去在撰写 WP 的 App 时很常用到 WebClient 这个类来操作网络资源或是事务,

我也写过相关的介绍

但开始学习怎么撰写一个 Universal App 时,发现无法再使用 WebClient,而改用 HttpClient 这新的类。

已写过 Store App 对它应蛮熟悉,不过我是第一次使用,所以来了解怎么操作它吧。

HttpClient

    利用 URI 负责传送 HTTP request 与接收 HTTP response。它实践了:GET、POST、DELETE、PUT 方法,支持异步的 Task。

这篇讨论的是使用:Windows.Web.Http 下的 HttpClient,与过去 Win8 使用的 HttpClient (System.Net.Http.HttpClient) 不一样。

重要属性、方法与事件:

类型 名称 说明
Methods DeleteAsync Send a DELETE request to the specified Uri as an asynchronous operation.
  Dispose [C#, VB] Performs tasks associated with freeing, releasing, or resetting unmanaged resources.
  GetAsync(Uri) Send a GET request to the specified Uri as an asynchronous operation.
  GetAsync(Uri, HttpCompletionOption) Send a GET request to the specified Uri with an HTTP completion option as an asynchronous operation.
  GetBufferAsync Send a GET request to the specified Uri and return the response body as a buffer in an asynchronous operation.
  GetInputStreamAsync Send a GET request to the specified Uri and return the response body as a stream in an asynchronous operation.
  GetStringAsync Send a GET request to the specified Uri and return the response body as a string in an asynchronous operation.
  PostAsync Send a POST request to the specified Uri as an asynchronous operation.
  PutAsync Send a PUT request to the specified Uri as an asynchronous operation.
  SendRequestAsync(HttpRequestMessage) Send an HTTP request as an asynchronous operation.
  SendRequestAsync(HttpRequestMessage, HttpCompletionOption) Send an HTTP request with an HTTP completion option as an asynchronous operation.
Properties DefaultRequestHeaders Read-only. Gets a collection of headers that should be sent with each request.

〉HttpClient class instance 扮演像似 session 负责传递 request 与接收 response。

〉HttpClient instance 是个设定集合,适用于所有由这个 instance 所发出的 reuqest。

每一个 HttpClient 使用各自的 connection pool,隔离开各自的 request,彼此 HttpClient 不会互相影响

〉HttpClient 可搭配 filters 为多种特定的 HTTP clients,例如:HttpClientFilter 可用于提供额外的方法给指定的 social network service;

〉使用 HttpClient 或相关在 Windows.Web.Http 下的类下载大型文件(超过 50MB),需改用 Stream 而不是使用默认的 Buffering

    =>因为 default buffering 使用的是 client memory,如果文件太大会造成性能不佳的状况;

〉HTTP Method 支持:DELETE, GET, PUT, and POST

〉Support for common authentication settings and patterns

〉Access to Secure Sockets Layer (SSL) details on the transport;

〉Ability to include customized filters in advanced apps;

〉Ability to get, set, and delete cookies

〉HTTP Request progress info available on asynchronous methods

简单的范例如下:

using System;
using Windows.Foundation;
using Windows.Web.Http;
 
var uri = new Uri("http://example.com/datalist.aspx");
var httpClient = new HttpClient();
 
// Always catch network exceptions for async methods
try 
{
    var result = await httpClient.GetStringAsync(uri);
}
catch 
{
    // Details in ex.Message and ex.HResult.       
}
 
// Once your app is done using the HttpClient object call dispose to 
// free up system resources (the underlying socket and memory used for the object)
httpclient.Dispose();


提供的程序里面有写到“httpclient.Dispose()”,提示当 App 如对于 httpclient 已不需要时可调用 Dispose(),通知系统回收资源;

上述列表中列出调用 HttpClient 的重要方法,里面有一些特殊的类与枚举,以下分别加以介绍:

HttpRequestMessage

    呈现 HTTP Request 送出的内容;(HTTP messages are defined in RFC 2616 by the IETF)。

重要的属性:

名称 说明
Content Read/write,Gets or sets the HTTP content to send to the server on the HttpRequestMessage object.
Headers Read-only,Gets the collection of the HTTP request headers associated with the HttpRequestMessage.
取得 HttpRequestHeaderCollection 集合,借由 Add、Remove、Keys 来操作指定的 Value;
Method Read/write,Gets or sets the HTTP method to be performed on the request URI.
Properties Read-only,Gets a set of properties on the HttpRequestMessage instance that are for use by the developer.
RequestUri Read/write,Gets or sets the Uri used for the HttpRequestMessage object.
TransportInformation Read-only,Get information about the underlying transport socket used by an HTTP connection.

HttpRequestMessage 包含:headers、HTTP verb 与 potentially data。通常它被用于需要为 HTTP Request 增加额外的参数或控制,例如:

‧执行 SSL/TLS transport information;

‧使用 less-common HTTP method;

‧额外对 HttpRequestMessage 设定的属性;

HttpResponseMessage

    接收来自 HTTP Request 执行后所回传的 HTTP Response message,里面包括:headers、status code 与 data ;(HTTP messages are defined in RFC 2616 by the IETF)。

名称 说明
Content Read/write,Gets or sets the content of the HTTP response message on the HttpResponseMessage object.
Headers Read-only,Gets the collection of HTTP response headers associated with the HttpResponseMessage that were sent by the server.
IsSuccessStatusCode Read-only,Gets a value that indicates whether the HTTP response was successful.
ReasonPhrase Read/write,Gets or sets the reason phrase which typically is sent by servers together with the status code.
RequestMessage Read/write,Gets or sets the request message which led to this response message.
Source Read/write,Gets the source of the data received in the HttpResponseMessage.
StatusCode Read/write,Gets or sets the status code of the HTTP response.
Version Read/write,Gets or sets the HTTP protocol version used on the HttpResponseMessage object.

HttpResponseMessage 通常来自 HttpClient 使用 DeleteAsync, GetAsync, PostAsync , PutAsync, or SendRequestAsync 这些方法。

了解 HttpRequestMessage 与 HttpResponseMessage后,以下范例说明如何使用:

private async void SendHttpRequestMessage()
{
    // 建立 HttpRequestMessage
    HttpRequestMessage requestMsg = new HttpRequestMessage(HttpMethod.Get, new Uri(""));
    // 修改 headers
    requestMsg.Headers.Add("myapp-deviceId", "univsersal app");
 
    HttpClient client = new HttpClient();
 
    // 利用 SendRequestAsync() 送出 HttpRequestMessage
    // await 后取得 HttpResponseMessage
    var response = await client.SendRequestAsync(requestMsg);
    String result = String.Empty;
    try
    {
        // 利用 EnsureSuccessStatusCode 确认是否成功
        // 如果失败,EnsureSuccessStatusCode 会送出 Exception
        response.EnsureSuccessStatusCode();
 
        // 将 Http Content 转换成 文字
        result = await response.Content.ReadAsStringAsync();
    }
    catch (Exception ex)
    {
        result = ex.Message;
    }
}


上述范例 HttpMethod 是 GET 如果您需要在 POST 也修改其 HttpRequestMessage,只需换成 HttpMethod.Post 即可 。

需注意,如果要送出 HttpRequestMessage 时,HttpClient 需要换成 SendRequestAsync();另外,上述有操作到 Http Content 的部分,往下便说有几种 Content。

IHttpContent

    描述了 HTTP entity body、headers 与 cookies,这些内容做用于 HTTP Request 或 Response。利用 HttpClient 与网络服务事务时会得到 IHttpContent。

它是个 interface 定义了实践它的类需要提供的方法:

类型 名称 说明
Methods BufferAllAsync Serialize the HTTP content into memory as an asynchronous operation.
  ReadAsBufferAsync Serialize the HTTP content to a buffer as an asynchronous operation.
  ReadAsInputStreamAsync Serialize the HTTP content and return an input stream that represents the content as an asynchronous operation.
  ReadAsStringAsync Serialize the HTTP content to a String as an asynchronous operation.
  TryComputeLength Determines whether the HTTP content has a valid length in bytes.
  WriteToStreamAsync Write the HTTP content to an output stream as an asynchronous operation.
Properties Headers Read-only,Gets the collection of the HTTP request headers associated with the HttpRequestMessage.

Http Content 分有多种类型,其内容类型如下:

a. HttpBufferContent:利用 Buffer 呈现 HTTP content;

b. HttpStreamContent:利用 Stream 呈现 HTTP content,常使用于 HTTP GET 来接收数据与 HTTP POST 上传数据。

c. HttpStringContent:利用 String 呈现内容,从 HTTP GET Request 取得的 HTTP Response 文字内容。

d. HttpFormUrlEncodedContent:使用 name/value tuples encoded 的方式呈现内容,并搭配特定的 application/x-www-form-urlencoded MIME type;

                                                         这也是一边 POST Form 所使用的类型;

e. HttpMultipartContent:利用 multipart/* MIME type 代表 HTTP content;

f. HttpMultipartFormDataContent:利用 multipart/form-data MIME type 代表 HTTP content;与 HttpMultipartContent 相似,但它可以增加多个 IHttpContent,

                                                            属于一个集合。

上述这几种类型,常用的就是 HttpStringContent、HttpBufferContent 与 HttpStreamContent。

Windows.Web.Http.Headers

    支持 HTTP headers 与 cookies 的操作,header / cookies 的操作会影响 HttpRequestMessage 与 HttpResponseMessage;

常用的类有:

‧HttpRequestHeaderCollection:a collection of the HTTP headers associated with an HTTP request.

                                                 例如:HttpRequestMessage.Headers 或 HttpClient.DefaultRequestHeaders;

‧HttpResponseHeaderCollection:a collection of the HTTP headers associated with an HTTP response.

                                                   例如:HttpResponseMessage.Headers;

‧HttpContentHeaderCollection:a collection of the HTTP headers associated with the content, which can be used on an HTTP request or response.

                                                 例如:实践 IHttpContent 的相关类均是,如:HttpStringContent、HttpStreamContent…等;

Windows.Web.Http.Filters

   提供类来发送 HTTP request 与 interface 建立筛选器针目目标 HTTP 和 REST service。常见是实践 IHttpFilter 界面。

Windows.Web.Http、Windows.Web.Http.Headers 与 Windows.Web.Http.Filters 为  Windows store apps 提供 HTTP Programming interface 来连接 HTTP services。

HttpBaseProtocolFilter:基本 HttpClient 使用的来传送与接收数据的 Filter。详细可参考:Windows.Web.Http.Filters。

CancellationTokenIProgress 界面

    CancellationToken:散布通知,表示作业应被取消。详细可参考,如果想要管理 Task 在特定情况下做取消的话,即

                                  需要实例化这个类,并且将该 Token 指定给 Task,这样在调用取消时才能统将该 Token 注册的 Task 一并取消;

    IProgress 界面:定义进度更新的提供者。T 代表要进度更新值的类型。以 HttpClient 来说即是 HttpProgress。建构子需要一个 Action

                                    Handler 为参数,才能在进度状态变化时通知处理预计要的任务。

HttpCookie class & HttpCookieCollection class & HttpCookieManager class

    操作 HTTP service 一定会遇到的 Cookie 处理,在 Windows.Web.Http 提供了几个类来处理:

‧HttpCookie:提供属性集合与方法的集合负责管理一个 HTTP cookie。为一个小单位;

名称 说明
Domain Read-only,Get the domain for which the HttpCookie is valid.
Expires Read/write,Get or set the expiration date and time for the HttpCookie.
HttpOnly Read/write,Get or set a value that controls whether a script or other active content can access this HttpCookie.
Name Read-only,Get the token that represents the HttpCookie name.
Path Read-only,Get the URI path component to which the HttpCookie applies.
=>Path:代表对于应 URI 中那一个路径可使用这个 Cookie;
=>Path 属性指定 Uri 的子集此 Cookie 适用的源服务器上
    如果未指定此属性,则此 Cookie 将送交起源或多个服务器上的所有页面。
=>举例来说:http://www.dotblogs.com.tw/,在设定它的 Path 可以是 /dotblogs/;
Secure Read/write,Get or set the security level for the HttpCookie.
Value Read/write,Get or set the value for the HttpCookie.

‧HttpCookieCollection:集合多个 HttpCookie,由 HttpCookieManager.GetCookies 取得;

‧HttpCookieManager: 负责管理所有的 HttpCookies;

借由下方的程序范例来说明:

a) 设定 Cookie:

private void SetCookie()
{
    HttpCookie cookie = new HttpCookie("userId", "dotblogs.com.tw", "http://www.dotblogs.com.tw");
    cookie.Value = "Pou";
    cookie.Expires = DateTime.UtcNow.Date.AddMinutes(3);
    cookie.HttpOnly = true;
    cookie.Secure = true;
 
    // 取得 HttpClient 默认使用的 HttpBaseProtocolFilter 元素
    HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
    // 放入 Cookie
    bool replaced = filter.CookieManager.SetCookie(cookie, false);
 
    HttpClient client = new HttpClient(filter);
}


b) 取得 Cookie:

private void GetCookie()
{          
    HttpBaseProtocolFilter filter = new HttpBaseProtocolFilter();
    HttpCookieCollection cookieCollection = filter.CookieManager.GetCookies(
                                new Uri("http://www.dotblogs.com.tw"));
    foreach (var item in cookieCollection)
    {
        String value = String.Format("key: {0}, value: {1}", item.Name, item.Value);
    }
}


HttpProgress structure

    呈现 HttpClient 执行过程中的状态资讯。具有以下值:

Field Data type Description
BytesReceived System.UInt64

The total number of bytes received.

This value includes bytes received as response headers.

If the operation was restarted, this value may be smaller than in the previous progress report.

BytesSent System.UInt64

The total number of bytes sent.

This value includes bytes sent as request headers.

If the operation was restarted, this value may be smaller than in the previous progress report.

Retries System.UInt32 The number of retries.
Stage HttpProgressStage The step in the progress of an HTTP connection.
TotalBytesToReceive System.Nullable

The total number of data bytes to receive.

If the number is unknown, this value is 0.

TotalBytesToSend System.Nullable

The total number of data bytes to send.

If the number is unknown, this value is 0.

HttpTransportInformation class

    提供有关 HTTP 连接所使用的底层传输资讯。该类主用于 SSL Connection 取得 SSL 资讯。

重要属性如下:

名称 说明
ServerCertificate Read-only,Gets the certificate from the server with the SSL information.
ServerCertificateErrors Read-only,Gets the list of errors that occurred making an SSL connection.
ServerCertificateErrorSeverity Read-only,Gets the category of an error on an SSL connection.
ServerIntermediateCertificates Read-only,Gets the intermediate certificates sent by the server during SSL negotiation on this HttpTransportInformation object.

在操作 HttpClient 时相关的枚举值:

HttpCompletionOption

    用于提示 HttpClient 在异步执行时是已完成所有 response 的读取或是当 header 已被读取的消息枚举。

Member Description
ResponseContentRead 0,The operation should complete after reading the entire response including the content. This is the default value.
ResponseHeadersRead 1,The operation should complete as soon as a response is available and headers are read. The content is not read yet.

HttpVersion enumeration

    呈现 HTTP Protocol Version。

Member Description
None 0,This value may be returned by third party filters.
Http10 1,HTTP 1.0.
Http11 2,HTTP 1.1.

[范例]

a. 实践 GET 方法来取得远端音乐文件

public async void LoadRemoteMusicFile(String id, String pwd)
{
    HttpRequestMessage requestMsg = new HttpRequestMessage(
                                        HttpMethod.Get, 
                                        new Uri(""));
    requestMsg.Headers.Add("uid", WebUtility.UrlEncode(id));
    requestMsg.Headers.Add("pwd", WebUtility.UrlEncode(pwd));
    // 利用 HttpRequestMessage 与 HttpResponseMessage 来操作
    HttpClient client = new HttpClient();
    var responseMsg = await client.SendRequestAsync(requestMsg);
    try
    {
        responseMsg.EnsureSuccessStatusCode();
        // 在 Local Folder 建立要保存的文件
        StorageFile musicFile = await GetMusicFile("temp.mp3");
        // 将 StorageFile 开启可写入的 Stream
        IRandomAccessStream writeStream = await musicFile.OpenAsync(FileAccessMode.ReadWrite);
        // 将 Http response 收到的 http content 写入 Stream
        ulong writeLengths = await responseMsg.Content.WriteToStreamAsync(writeStream);
 
        ulong length = 0;
        responseMsg.Content.TryComputeLength(out length);
 
        // 判断是否完整写入
        if (writeLengths == length)
        {
            MediaElement media = new MediaElement();
            media.Source = new Uri("ms-appdata:///local/temp.mp3");
            media.AutoPlay = true;                    
            content.Children.Add(media);
        }
    }
    catch (Exception ex)
    {
        // handle exception
    }
 
}
 
private async Task GetMusicFile(String name)
{
    StorageFolder local = ApplicationData.Current.LocalFolder;
    StorageFile file = null;
    try
    {
        file = await local.GetFileAsync(name);
    }
    catch (FileNotFoundException) { }
    if (file == null)
    {
        file = await local.CreateFileAsync(name, CreationCollisionOption.ReplaceExisting);
    }
    return file;
}


需注意要写入时,使用 Stream 的方式所以要将 StorageFile 先开启写入的 IRandomAccessStream,再将 HTTP content 的内容写入。

b. 实践 POST 方法来事务数据

b-1. POST HttpStringContent:

private async void SendStringPostData()
{
    // 传送 文字 内容
    HttpStringContent strContent = new HttpStringContent("hello world");
 
    HttpClient client = new HttpClient();
    var response = await client.PostAsync(new Uri("http://localhost:4347/WebForm1.aspx"), strContent);
    try
    {
        response.EnsureSuccessStatusCode();
        // 取得回传结果
        String result = await response.Content.ReadAsStringAsync();
    }
    catch (Exception)
    {
        // handle exception
    }
}


b-2. POST HttpMultipartFormDataContent:

private async void SendMultiFormContent()
{
    try
    {
        // 利用 HttpMultipartFormDataContent 逐一加入要传送的参数
        HttpMultipartFormDataContent form = new HttpMultipartFormDataContent();
        form.Add(new HttpStringContent("Pou"), "key");
        form.Add(new HttpStringContent("Windows Phone"), "value");
 
        HttpClient client = new HttpClient();
        HttpResponseMessage response = await client.PostAsync(new Uri("http://localhost:4347/WebForm1.aspx"), form);
 
        response.EnsureSuccessStatusCode();
 
        String result = await response.Content.ReadAsStringAsync();
    }
    catch (TaskCanceledException)
    {
    }
    catch (Exception ex)
    {
    }
    finally
    {
    }
}


b-3. POST HttpStreamContent:

private async void PostStreamData()
{
    try
    {
        Uri uri = new Uri("http://localhost:4347/WebForm1.aspx");
 
        // 在 Local Folder 建立要保存的文件
        StorageFile musicFile = await GetMusicFile("temp.mp3");
        Stream stream = await musicFile.OpenStreamForReadAsync();
 
        // 利用 HttpStreamContent 包装要送出的 Stream
        HttpStreamContent streamContent = new HttpStreamContent(stream.AsInputStream());
 
        HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post, uri);
        request.Content = streamContent;
 
        // Do an asynchronous POST.
        HttpClient client = new HttpClient();
        HttpResponseMessage response = await client.SendRequestAsync(request);
    }
    catch (Exception)
    {
 
    }
}


如果您要 POST 的 HTTP Content 是自订的话,可实践 IHttpContent 界面来加以支持;

c. 修改 Http Header

private void ChangeHttpHeader()
{
    HttpClient httpClient = new HttpClient();
 
    // Add a user-agent header
    var headers = httpClient.DefaultRequestHeaders;
 
    // 加入需要的 Header
    headers.UserAgent.ParseAdd("ie");
    headers.UserAgent.ParseAdd("Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.2; WOW64; Trident/6.0)");
}


e. 实践 Progress 的呈现

public async void LoadRemoteMusicFile(String id, String pwd)
{
    HttpRequestMessage requestMsg = new HttpRequestMessage(
                                        HttpMethod.Get, 
                                        new Uri("https://dl.dropboxusercontent.com/u/18944013/illa%20illa%20.mp3"));
    requestMsg.Headers.Add("uid", WebUtility.UrlEncode(id));
    requestMsg.Headers.Add("pwd", WebUtility.UrlEncode(pwd));
 
    // 加入 Progress Handler
    Progress progressCallback = new Progress(OnSendRequestProgress);
    var tokenSource = new CancellationTokenSource();
 
    // 利用 HttpRequestMessage 与 HttpResponseMessage 来操作
    HttpClient client = new HttpClient();
    var responseMsg = await client.SendRequestAsync(requestMsg).AsTask(tokenSource.Token, progressCallback);
 
    try
    {
        responseMsg.EnsureSuccessStatusCode();
        // 在 Local Folder 建立要保存的文件
        StorageFile musicFile = await GetMusicFile("temp.mp3");
        // 将 StorageFile 开启可写入的 Stream
        IRandomAccessStream writeStream = await musicFile.OpenAsync(FileAccessMode.ReadWrite);
        // 将 Http response 收到的 http content 写入 Stream
        ulong writeLengths = await responseMsg.Content.WriteToStreamAsync(writeStream);
 
        ulong length = 0;
        responseMsg.Content.TryComputeLength(out length);
 
        // 判断是否完整写入
        if (writeLengths == length)
        {
            MediaElement media = new MediaElement();
            media.Source = new Uri("ms-appdata:///local/temp.mp3");
            media.AutoPlay = true;                    
            content.Children.Add(media);
        }
    }
    catch (Exception ex)
    {
        // handle exception
    }
}
 
private void OnSendRequestProgress(HttpProgress obj)
{
    // 更新画面中的 ProgressBar
    this.Dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.High,
        () => {
            btnBar.Minimum = 0;
            btnBar.Value = obj.BytesReceived;
            if (obj.TotalBytesToReceive != null)
                btnBar.Maximum = (double)obj.TotalBytesToReceive;
        });
}


主要实践一个 Progress 处理函数,来接收把 SendRequestAsync 转换成一个 Task,并将 ProgressHandler 指定给它。

更多的范例参考<HttpClient sample>的内容

[范例文件]

[补充]

1) System.Net.Http.HttpClientWindows.Web.Http.HttpClient 的差别

    Win8 操作 HttpClient 使用 System.Net.Http.HttpClient,Win8.1 后改用 Windows.Web.Http.HttpClient;

    Windows.Web.Http.HttpClient 取代了三种旧的 namespace:

    a. WinJS.xhr for JavaScript;

    b. System.Net.Http.HttpClient in the System.Net.Http namespace for C# and VB.

        (Not supported on Windows Phone. WP8.1 之前不支持 HttpClient)

    c. XML HTTP Extended Request (IXMLHTTPRequest2) for C++;

    详细请参考

2) 处理相关 Exception 可参考

======

这篇写到后来有点把 HttpClient 我觉得会操作到的内容都写上了,主要是因为之前在写 WP8 的时候,

我很习惯使用 HttpWebRequest 来操作网络服务,所以就把在里面遇到过的问题与疑惑都在撰写 HttpClient 时都补上,

希望有帮助到大家,谢谢。如果有问题,也欢迎指教与讨论。谢谢。

Referecnes

〉Connecting to networks and web services (XAML) (重要)

〉How to connect to an HTTP server using Windows.Web.Http.HttpClient (XAML) (重要)

〉Connecting to an HTTP server using System.Net.Http.HttpClient (Windows Store apps using C#/VB and XAML)

〉How to enable loopback and debug network isolation

〉How to configure network isolation capabilities

〉Handling exceptions in network apps (重要)

〉How to set timeouts on socket operations (重要)

〉Connecting to web services & Web authentication sample

〉HttpClient Sample (重要)

〉Downloading files in Windows 8 apps using Background Transfer feature

〉Connecting using XML HTTP Request and IXMLHTTPRequest2 (Windows Runtime apps using C++ and XAML)

〉How to connect to Bing Maps using Windows::Web::Http::HttpClient (Windows Runtime apps using C++ and XAML)

〉C# HttpClient & [Web API] WinForm 使用 HttpClient 调用 Web API

Dotblogs Tags: Universal App ,WP8.1 ,Windows Phone ,WebClient ,HttpClient