從 .NET 用戶端呼叫 Web API (C#)

此內容適用于舊版 .NET。 新的開發應該使用ASP.NET Core。 如需使用 ASP.NET Core Web API 的詳細資訊,請參閱:

下載已完成的專案

本教學課程說明如何使用System.Net.Http.HttpClient從 .NET 應用程式呼叫 Web API。

在本教學課程中,會撰寫取用下列 Web API 的用戶端應用程式:

動作 HTTP method 相對 URI
依照識別碼取得產品 GET /api/products/id
建立新的產品 POST /api/products
更新產品 PUT /api/products/id
刪除產品 刪除 /api/products/id

若要瞭解如何使用 ASP.NET Web API實作此 API,請參閱建立支援 CRUD 作業的 Web API

為了簡單起見,本教學課程中的用戶端應用程式是 Windows 主控台應用程式。 Windows Phone和 Windows 市集應用程式也支援HttpClient。 如需詳細資訊,請參閱 使用可攜式程式庫撰寫多個平臺的 Web API 用戶端程式代碼

注意: 如果您以硬式編碼值的形式傳遞基底 URL 和相對 URI,請留意使用 HttpClient API 的規則。 屬性 HttpClient.BaseAddress 應設定為結尾斜線 (/) 位址。 例如,將硬式編碼的資源 URI 傳遞至 HttpClient.GetAsync 方法時,請勿包含前置斜線。 若要依識別碼取得 Product

  1. 設置 client.BaseAddress = new Uri("https://localhost:5001/");
  2. Product要求 。 例如:client.GetAsync<Product>("api/products/4");

建立主控台應用程式

在 Visual Studio 中,建立名為 HttpClientSample 的新 Windows 主控台應用程式,並貼上下列程式碼:

using System;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;

namespace HttpClientSample
{
    public class Product
    {
        public string Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Category { get; set; }
    }

    class Program
    {
        static HttpClient client = new HttpClient();

        static void ShowProduct(Product product)
        {
            Console.WriteLine($"Name: {product.Name}\tPrice: " +
                $"{product.Price}\tCategory: {product.Category}");
        }

        static async Task<Uri> CreateProductAsync(Product product)
        {
            HttpResponseMessage response = await client.PostAsJsonAsync(
                "api/products", product);
            response.EnsureSuccessStatusCode();

            // return URI of the created resource.
            return response.Headers.Location;
        }

        static async Task<Product> GetProductAsync(string path)
        {
            Product product = null;
            HttpResponseMessage response = await client.GetAsync(path);
            if (response.IsSuccessStatusCode)
            {
                product = await response.Content.ReadAsAsync<Product>();
            }
            return product;
        }

        static async Task<Product> UpdateProductAsync(Product product)
        {
            HttpResponseMessage response = await client.PutAsJsonAsync(
                $"api/products/{product.Id}", product);
            response.EnsureSuccessStatusCode();

            // Deserialize the updated product from the response body.
            product = await response.Content.ReadAsAsync<Product>();
            return product;
        }

        static async Task<HttpStatusCode> DeleteProductAsync(string id)
        {
            HttpResponseMessage response = await client.DeleteAsync(
                $"api/products/{id}");
            return response.StatusCode;
        }

        static void Main()
        {
            RunAsync().GetAwaiter().GetResult();
        }

        static async Task RunAsync()
        {
            // Update port # in the following line.
            client.BaseAddress = new Uri("http://localhost:64195/");
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(
                new MediaTypeWithQualityHeaderValue("application/json"));

            try
            {
                // Create a new product
                Product product = new Product
                {
                    Name = "Gizmo",
                    Price = 100,
                    Category = "Widgets"
                };

                var url = await CreateProductAsync(product);
                Console.WriteLine($"Created at {url}");

                // Get the product
                product = await GetProductAsync(url.PathAndQuery);
                ShowProduct(product);

                // Update the product
                Console.WriteLine("Updating price...");
                product.Price = 80;
                await UpdateProductAsync(product);

                // Get the updated product
                product = await GetProductAsync(url.PathAndQuery);
                ShowProduct(product);

                // Delete the product
                var statusCode = await DeleteProductAsync(product.Id);
                Console.WriteLine($"Deleted (HTTP Status = {(int)statusCode})");

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

            Console.ReadLine();
        }
    }
}

上述程式碼是完整的用戶端應用程式。

RunAsync 會執行 和 封鎖,直到完成為止。 大部分 的 HttpClient 方法是非同步方法,因為它們會執行網路 I/O。 所有非同步工作都是在 內 RunAsync 完成。 應用程式通常不會封鎖主執行緒,但此應用程式不允許任何互動。

static async Task RunAsync()
{
    // Update port # in the following line.
    client.BaseAddress = new Uri("http://localhost:64195/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));

    try
    {
        // Create a new product
        Product product = new Product
        {
            Name = "Gizmo",
            Price = 100,
            Category = "Widgets"
        };

        var url = await CreateProductAsync(product);
        Console.WriteLine($"Created at {url}");

        // Get the product
        product = await GetProductAsync(url.PathAndQuery);
        ShowProduct(product);

        // Update the product
        Console.WriteLine("Updating price...");
        product.Price = 80;
        await UpdateProductAsync(product);

        // Get the updated product
        product = await GetProductAsync(url.PathAndQuery);
        ShowProduct(product);

        // Delete the product
        var statusCode = await DeleteProductAsync(product.Id);
        Console.WriteLine($"Deleted (HTTP Status = {(int)statusCode})");

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

    Console.ReadLine();
}

安裝 Web API 用戶端程式庫

使用 NuGet 套件管理員來安裝 Web API 用戶端程式庫套件。

從 [工具] 功能表中,選取 [NuGet 封裝管理員]>[封裝管理員主控台]。 在 [套件管理員主控台] (PMC) 中,輸入下列命令:

Install-Package Microsoft.AspNet.WebApi.Client

上述命令會將下列 NuGet 套件新增至專案:

  • Microsoft.AspNet.WebApi.Client
  • Newtonsoft.Json

Newtonsoft.Json (也稱為 Json.NET) 是適用于 .NET 的熱門高效能 JSON 架構。

新增模型類別

檢查 Product 類別:

public class Product
{
    public string Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public string Category { get; set; }
}

這個類別符合 Web API 所使用的資料模型。 應用程式可以使用 HttpClient 從 HTTP 回應讀取 Product 實例。 應用程式不需要撰寫任何還原序列化程式碼。

建立和初始化 HttpClient

檢查靜態 HttpClient 屬性:

static HttpClient client = new HttpClient();

HttpClient 旨在具現化一次,並在整個應用程式的生命週期中重複使用。 下列條件可能會導致 SocketException 錯誤:

  • 為每個要求建立新的 HttpClient 實例。
  • 負載過重的伺服器。

為每個要求建立新的 HttpClient 實例可能會耗盡可用的通訊端。

下列程式碼會初始化 HttpClient 實例:

static async Task RunAsync()
{
    // Update port # in the following line.
    client.BaseAddress = new Uri("http://localhost:64195/");
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(
        new MediaTypeWithQualityHeaderValue("application/json"));

上述程式碼:

  • 設定 HTTP 要求的基底 URI。 將埠號碼變更為伺服器應用程式中使用的埠。 除非使用伺服器應用程式的埠,否則應用程式將無法運作。
  • 將 Accept 標頭設定為 「application/json」。 設定此標頭會告知伺服器以 JSON 格式傳送資料。

傳送 GET 要求以擷取資源

下列程式碼會傳送產品的 GET 要求:

static async Task<Product> GetProductAsync(string path)
{
    Product product = null;
    HttpResponseMessage response = await client.GetAsync(path);
    if (response.IsSuccessStatusCode)
    {
        product = await response.Content.ReadAsAsync<Product>();
    }
    return product;
}

GetAsync方法會傳送 HTTP GET 要求。 當方法完成時,它會傳回包含 HTTP 回應的 HttpResponseMessage 。 如果回應中的狀態碼是成功碼,回應本文會包含產品的 JSON 標記法。 呼叫 ReadAsAsync ,將 JSON 承載還原序列化為 Product 實例。 ReadAsAsync方法是非同步,因為回應本文可以任意大。

當 HTTP 回應包含錯誤碼時,HttpClient不會擲回例外狀況。 相反地,如果狀態為錯誤碼, IsSuccessStatusCode 屬性為 false 。 如果您想要將 HTTP 錯誤碼視為例外狀況,請在回應物件上呼叫 HttpResponseMessage.EnsureSuccessStatusCodeEnsureSuccessStatusCode 如果狀態碼落在範圍 200–299 之外,就會擲回例外狀況。 請注意, HttpClient 可能會基於其他原因擲回例外狀況,例如,如果要求逾時。

Media-Type格式子還原序列化

在沒有參數的情況下呼叫 ReadAsAsync 時,它會使用一組預設的 媒體格式器 來讀取回應本文。 預設格式器支援 JSON、XML 和 Form-url 編碼的資料。

您可以使用 ReadAsAsync 方法的格式子清單,而不是使用預設格式器。 如果您有自訂媒體類型格式器,使用格式器清單會很有用:

var formatters = new List<MediaTypeFormatter>() {
    new MyCustomFormatter(),
    new JsonMediaTypeFormatter(),
    new XmlMediaTypeFormatter()
};
resp.Content.ReadAsAsync<IEnumerable<Product>>(formatters);

如需詳細資訊,請參閱ASP.NET Web API 2 中的媒體格式器

傳送 POST 要求以建立資源

下列程式碼會傳送 POST 要求,其中包含 Product JSON 格式的實例:

static async Task<Uri> CreateProductAsync(Product product)
{
    HttpResponseMessage response = await client.PostAsJsonAsync(
        "api/products", product);
    response.EnsureSuccessStatusCode();

    // return URI of the created resource.
    return response.Headers.Location;
}

PostAsJsonAsync方法:

  • 將物件序列化為 JSON。
  • 在 POST 要求中傳送 JSON 承載。

如果要求成功:

  • 它應該會傳回 201 (Created) 回應。
  • 回應應該會在 [位置] 標頭中包含所建立資源的 URL。

傳送 PUT 要求以更新資源

下列程式碼會傳送 PUT 要求來更新產品:

static async Task<Product> UpdateProductAsync(Product product)
{
    HttpResponseMessage response = await client.PutAsJsonAsync(
        $"api/products/{product.Id}", product);
    response.EnsureSuccessStatusCode();

    // Deserialize the updated product from the response body.
    product = await response.Content.ReadAsAsync<Product>();
    return product;
}

PutAsJsonAsync方法的運作方式類似PostAsJsonAsync,不同之處在于它會傳送 PUT 要求,而不是 POST。

傳送 DELETE 要求以刪除資源

下列程式碼會傳送 DELETE 要求來刪除產品:

static async Task<HttpStatusCode> DeleteProductAsync(string id)
{
    HttpResponseMessage response = await client.DeleteAsync(
        $"api/products/{id}");
    return response.StatusCode;
}

如同 GET,DELETE 要求沒有要求本文。 您不需要使用 DELETE 來指定 JSON 或 XML 格式。

測試範例

若要測試用戶端應用程式:

  1. 下載 並執行伺服器應用程式。 確認伺服器應用程式正常運作。 例如, http://localhost:64195/api/products 應該傳回產品清單。

  2. 設定 HTTP 要求的基底 URI。 將埠號碼變更為伺服器應用程式中使用的埠。

    static async Task RunAsync()
    {
        // Update port # in the following line.
        client.BaseAddress = new Uri("http://localhost:64195/");
        client.DefaultRequestHeaders.Accept.Clear();
        client.DefaultRequestHeaders.Accept.Add(
            new MediaTypeWithQualityHeaderValue("application/json"));
    
  3. 執行用戶端應用程式。 此時會產生下列輸出:

    Created at http://localhost:64195/api/products/4
    Name: Gizmo     Price: 100.0    Category: Widgets
    Updating price...
    Name: Gizmo     Price: 80.0     Category: Widgets
    Deleted (HTTP Status = 204)