C# cơ bản .NET Core
Lập trình C# (C Sharp)

Giới thiệu HttpClient

Lớp HttpClient được sử dụng để gửi truy vấn HTTP (Http Request Message - Request) và nhận phản hồi Response (Http Response Message) từ các truy vấn đó. Lớp này thuộc namespace System.Net.Http, namespace này chứa các lớp giúp tạo ra sự liên lạc giữa client và server. Để làm việc với HttpClient dùng những namespace sau:

using System;
using System.Linq;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading;
using System.Threading.Tasks;
using System.IO;
using System.Text;

Phần này thực hiện làm việc với giao thức HTTP nên trước tiên cần hiểu rõ về giao thức này, hãy đọc kỹ phần Giao thức HTTP và địa chỉ Uri, Url ở phần Cấu trúc Uri, Url, Lớp Uri trong C#

Tạo truy vấn GET bất đồng bộ với HttpClient

Để tạo ra truy vấn GET tới một địa chỉ URL, thực hiện phương thức GetAsync(url), đây là phương thức async khi kết thúc nó trả về đối tượng HttpResponseMessage. Từ đối tượng này ta sẽ biết kết quả truy vấn, và ta có thể đọc được dữ liệu tải về.

// Khởi tạo http client
using var httpClient = new HttpClient();

// Thiết lập các Header nếu cần
httpClient.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml+json");

// Thực hiện truy vấn GET
HttpResponseMessage response = await httpClient.GetAsync(url);

Khi có đối tượng lớp HttpResponseMessage có thể thực hiện các tác vụ đọc dữ liệu, tham khảo một số thuộc tính và phương thức:

HttpResponseHeaders
Một số phương thức, thuộc tính lớp HttpResponseMessage
EnsureSuccessStatusCode() Phát sinh Exception nếu truy vấn có mã trả về không thành công (khi thuộc tính IsSuccessStatusCodefalse)
IsSuccessStatusCode Nhận true khi mã trả về thành công, ví dụ mã trạng thái StatusCode nhận giá trị 200
StatusCode Thuộc tính có kiểu enum HttpStatusCode cho biết mã trạng thái của kết quả (Như 301 - MovedPermanently, 200 - OK, 404 - NotFound ...), để chuyển sang số nguyên (int)HttpStatusCode
ReasonPhrase Đoạn text, mô tả thông tin cho mã trạng thái như OK, MovedPermanently ...
Headers
Thuộc tính kiểu HttpResponseHeaders chứa tập hợp các header (xem thêm các header của HTTP Message) của Response. HttpResponseHeaders kế thừa từ HttpHeaders là tập hợp mà mỗi phần tử có kiểu KeyValuePair<String,IEnumerable<String>> (biểu diễn một header httpd - key là tên header và value là tập hợp các chuỗi giá trị của header)

Ví dụ xây dựng phương thực để hiện thị tất cả các header

/// In ra thông tin các Header của HTTP Response
public static void ShowHeaders(HttpHeaders headers)
{
    Console.WriteLine("CÁC HEADER:");
    foreach (var header in headers)
    {
        foreach (var value in header.Value)
        {
            Console.WriteLine($"{header.Key,25} : {value}");
        }
     }
    Console.WriteLine();
 }

Ngoài ra nó còn có một số thuộc tính là các header thông dụng HTTP Respone như Age, Etag, Server ... mà nó đã phân tích các chuỗi header trả về thành các đối tượng tương ứng như TimeSpan, EntityTagHeaderValue, HttpHeaderValueCollection ... xem chi tiết các thuộc tính này tại các header response

Content Thuộc tính kiểu HttpContent, trong đó chứa nội dung (content) và các header liên quan đến content nếu có (Headers như Content-Type, Content-Length ...), một số phương thức để lấy content:
  • ReadAsStringAsync đọc nội dung (content) HTTP trả về chuỗi (có encoding). Đối với phương thức này, một số server trả về mã CharSet mà .NET Core không nhận ra sẽ gây lỗi, để encoding được cho trường hợp này thì trước khi thi hành thiết lập cho nó:
    Content.Headers.ContentType.CharSet = @"utf-8"; //ISO-8859-1
  • ReadAsStreamAsync trả về đối tượng Stream, từ stream dùng kỹ thuật đọc luồng để đọc nội dung: xem thêm Đọc streams Dữ liệu đọc được là các byte, bạn có thể lưu chúng ra file hoặc convert thành chuỗi
  • ReadAsByteArrayAsync đọc trả về mảng byte, từ mảng byte này có thể lưu nó ra file. Hoặc encoding nó thành chuỗi, ví dụ encoding utf-8
    string content = System.Text.Encoding.UTF8.GetString(byteArray);

Ví dụ, tạo truy vấn GET, tải về trang Web - hiện thị các header và trả về content kết quả

/// In ra thông tin các Header của HTTP Response
public static void ShowHeaders(HttpHeaders headers)
{
    Console.WriteLine("CÁC HEADER:");
    foreach (var header in headers)
    {
        foreach (var value in header.Value)
        {
            Console.WriteLine($"{header.Key,25} : {value}");

        }
     }
    Console.WriteLine();
 }

// Tải về trang web và trả về chuỗi nội dung
public static async Task<string> GetWebContent(string url)
{
    // Khởi tạo http client
    using var httpClient = new HttpClient();

    // Thiết lập các Header nếu cần
    httpClient.DefaultRequestHeaders.Add("Accept", "text/html,application/xhtml+xml+json");
    try {
        // Thực hiện truy vấn GET
        HttpResponseMessage response = await httpClient.GetAsync(url);

        // Hiện thị thông tin header trả về
        ShowHeaders(response.Headers);

        // Phát sinh Exception nếu mã trạng thái trả về là lỗi
        response.EnsureSuccessStatusCode();

        Console.WriteLine($"Tải thành công - statusCode {(int)response.StatusCode} {response.ReasonPhrase}");

        Console.WriteLine("Starting read data");

        // Đọc nội dung content trả về - ĐỌC CHUỖI NỘI DUNG
        string htmltext = await response.Content.ReadAsStringAsync();
        Console.WriteLine($"Nhận được {htmltext.Length} ký tự");
        Console.WriteLine();
        return htmltext;

    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
        return null;
    }
}

Khi sử dụng, như trong phương thức Main (chú ý chuyển sang bất đồng bộ)

static async Task Main(string[] args)
{
    var c = await GetWebContent("https://www.google.com/search?q=xuanthulab");
    Console.WriteLine(c);
}

Mã nguồn tham khảo tại HttpClientExample hoặc tải về tại ex029-httpclient

ReadAsStreamAsync và ReadAsByteArrayAsync đọc nội dung

Ở ví dụ trên, đã dùng ReadAsStringAsync để đọc content convert thành chuỗi (string). Ngoài ra, cũng có thể sử dụng ReadAsStreamAsync để tạo stream để đọc kết quả và sử dụng ReadAsByteArrayAsync để đọc hết các byte (mảng các byte) trong content.

Ví dụ sử dụng ReadAsByteArrayAsync

Xây dựng phương thức đọc tài nguyên từ một url và trả về mảng byte là content

var url = "https://raw.githubusercontent.com/xuanthulabnet/jekyll-example/master/images/jekyll-01.png";
byte[] bytes = await DownloadDataBytes(url);

string filepath = "anh1.png";
using (var stream = new FileStream (filepath, FileMode.Create, FileAccess.Write, FileShare.None))
{
    stream.Write (bytes, 0, bytes.Length);
    Console.WriteLine("save " + filepath);
}

Khi sủ dụng, có thể lưu toàn bộ dữ liệu mảng byte ra file. Ví dụ tải một file ảnh về:

static async Task Main(string[] args)
{
    var url = "https://raw.githubusercontent.com/xuanthulabnet/jekyll-example/master/images/jekyll-01.png";
    byte[] bytes = await DownloadDataBytes(url);

    string filepath = "anh1.png";
    using (var stream = new FileStream (filepath, FileMode.Create, FileAccess.Write, FileShare.None))
    {
        stream.Write (bytes, 0, bytes.Length);
        Console.WriteLine("save " + filepath);
    }
}

Ví dụ sử dụng DownloadDataStream

DownloadDataStream trả về một Stream để đọc Content, ví dụ xây dựng phương thức tải một file ảnh từ url cho trước.

// Tải từ url, trả về stream để đọc dữ liệu (xem bài về stream)
public static async Task DownloadDataStream (string url, string filename) {
    var httpClient = new HttpClient();
    Console.WriteLine ($"Starting connect {url}");
    try {
        HttpResponseMessage response = await httpClient.GetAsync (url);
        response.EnsureSuccessStatusCode ();

        // Lấy Stream để đọc content
        using var stream =  await response.Content.ReadAsStreamAsync();

        // THỰC HIỆN ĐỌC Content
        int SIZEBUFFER = 500;
        using var streamwrite = File.OpenWrite (filename);  // Mở stream để lưu file
        byte[] buffer = new byte[SIZEBUFFER];               // tạo bộ nhớ đệm lưu dữ liệu khi đọc stream

        bool endread = false;
        do                                                  // thực hiện đọc các byte từ stream và lưu ra streamwrite
        {
            int numberRead = await stream.ReadAsync(buffer, 0, SIZEBUFFER);
            Console.WriteLine(numberRead);
            if (numberRead == 0)
            {
                endread = true;
            }
            else
            {
                await streamwrite.WriteAsync(buffer, 0, numberRead);
            }

        } while (!endread);
        Console.WriteLine ("Download success");

    }
    catch (Exception e) {
        Console.WriteLine (e.Message);
        throw e;
    }
}

Khi áp dụng

static async Task Main(string[] args)
{
    var url = "https://raw.githubusercontent.com/xuanthulabnet/linux-centos/master/docs/samba1.png";
    await DownloadDataStream(url, "anh2.png");
}

Tham khảo mã nguồn HttpClientExampleRead (git) hoặc tải về ex029-2

Tạo request với SendAsync

Ngoài phương thức GetAsync gửi Request với phương thức GET ở trên ra, có thể dùng phương thức SendAsync (hoặc Send nếu sử dụng code đồng bộ synchronous).

Phương thức này có tham số kiểu HttpRequestMessage chứa các thông tin về một HTTP Request sẽ gửi đi (xem thêm HTTP Request), giúp tùy biến, thêm được nhiều thông tin khi gửi request hơn. Ví dụ tạo Request:

var httpRequestMessage = new HttpRequestMessage();
httpRequestMessage.Method = HttpMethod.Post;
httpRequestMessage.RequestUri = new Uri("https://xuanthulab.net");
/...

Sau khi tạo được HttpRequestMessage có thể thiết lập nhiều thông tin như, thiết lập method với thuộc tính Method (giá trị GET, POST, DELETE ...), thiết lập địa chỉ truy vấn với thuộc tính RequestUri, thiết lập Content của truy vấn với Content ...

Sau đó có thể thực hiện truy vấn để trả về HttpResponseMessage

var response = await httpClient.SendAsync(request);

Sử dụng FormUrlEncodedContent

Để thiết lập Content trong HttpRequestMessage để gửi đến server thì thiết lập thuộc tính Content bằng các đối tượng thuộc các lớp như: FormUrlEncodedContent, StringContent,

Với FormUrlEncodedContent bạn có thể tạo Content tương ứng như một Form HTML, nó chứa các giá trị (key/value) sẽ Post đến Server. Ví dụ sau, nó post đến server hai giá trị tương ứng key và value là key1/value1 (có thể hiểu tương ứng với phần tử HTML Input có name là key1 và value là value1) và key2/value2 (trường hợp này chứa nhiều giá trị, tương ứng với HTML Multi Select)

static async Task Main(string[] args)
{
        var httpClient = new HttpClient();

        var httpRequestMessage = new HttpRequestMessage();
        httpRequestMessage.Method = HttpMethod.Post;
        httpRequestMessage.RequestUri = new Uri("https://postman-echo.com/post");

        var parameters = new List<KeyValuePair<string,string>>();
        parameters.Add(new KeyValuePair<string,string>("key1","value1"));

        parameters.Add(new KeyValuePair<string,string>("key2","value2-1"));
        parameters.Add(new KeyValuePair<string,string>("key2","value2-2"));

        // Thiết lập Content
        var content =  new FormUrlEncodedContent(parameters);
        httpRequestMessage.Content = content;

        // Thực hiện Post
        var response = await httpClient.SendAsync(httpRequestMessage);

        var responseContent = await response.Content.ReadAsStringAsync();
        Console.WriteLine(responseContent);
// Khi chạy kết quả trả về cho biết Server đã nhận được dữ liệu Post đến
}

Sử dụng StringContent

Có thể gán Content bằng đối tượng kiểu StringContent chứa chuỗi nội dung (sử dụng cách này để tạo truy vấn Webservice API với Content là JSON)

static async Task Main(string[] args)
{
    var httpClient = new HttpClient();

    var httpRequestMessage = new HttpRequestMessage();
    httpRequestMessage.Method = HttpMethod.Post;
    httpRequestMessage.RequestUri = new Uri("https://postman-echo.com/post");

    // Tạo StringContent
    string jsoncontent = "{\"value1\": \"giatri1\", \"value2\": \"giatri2\"}";
    var httpContent = new StringContent(jsoncontent, Encoding.UTF8, "application/json");
    httpRequestMessage.Content = httpContent;

    var response = await httpClient.SendAsync(httpRequestMessage);
    var responseContent = await response.Content.ReadAsStringAsync();

    Console.WriteLine(responseContent);
}

Sử dụng MultipartFormDataContent

Bạn có thể gán Content bằng đối tượng kiểu MultipartFormDataContent, để post dữ liệu nhiều thành phần (multipart/form-data), vừa có dữ liệu như các phần tử của HTML Form vừa có thể kèm file.

Ví dụ, tạo ra truy vấn tương tự form HTML post các dữ liệu: có file đính kèm, có hai phần tử

var httpClient = new HttpClient();

var httpRequestMessage = new HttpRequestMessage();
httpRequestMessage.Method = HttpMethod.Post;
httpRequestMessage.RequestUri = new Uri("https://postman-echo.com/post");


// Tạo đối tượng MultipartFormDataContent
var content = new MultipartFormDataContent();

// Tạo StreamContent chứa nội dung file upload, sau đó đưa vào content
Stream fileStream = System.IO.File.OpenRead("Program.cs");
content.Add(new StreamContent(fileStream), "fileupload", "abc.xyz");

// Thêm vào MultipartFormDataContent một StringContent
content.Add(new StringContent("value1"), "key1");
// Thêm phần tử chứa mạng giá trị (HTML Multi Select)
content.Add(new StringContent("value2-1"), "key2[]");
content.Add(new StringContent("value2-2"), "key2[]");


httpRequestMessage.Content = content;
var response = await httpClient.SendAsync(httpRequestMessage);
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine(responseContent);

Tham khảo mã nguồn HttpClientExampleSendAsync (git) hoặc tải về ex031


Đăng ký nhận bài viết mới