2017年10月16日 星期一

C# HttpClient WebAPI : 2. 最簡單的方式使用 HttpClient 類別

若你正在觀看此篇文章,則應該會對於 使用 HttpClient 進行 JWT 身分驗證與呼叫需要授權的 API 和重新更新 Token 權杖的程式設計範例 這篇文章更感興趣。

了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式



在這裡,我們將會說明如何非常簡單的來使用 HttpClient 類別所產生的物件,進行呼叫遠端的 Web API,在這裡,我們將需要呼叫 http://vulcanwebapi.azurewebsites.net/api/values/777 這個 URL,取得回傳的 JSON 文字內容。
首先,您需要產生一個 HttpClient 類別的物件,client,接著,就可以使用執行個體方法 GetStringAsync這個非同步方法,將上述的 URL 當作引述傳送過去,就可以取得這個 URL 執行完畢的執行結果,JSON 文字內容,然後,我們就把執行結果輸出到螢幕上。
你可以從專案 SimpleHttpClient 中看到如下的測試程式碼。
static async Task Main(string[] args)
{
    HttpClient client = new HttpClient();
    var fooResult = await client.GetStringAsync("http://vulcanwebapi.azurewebsites.net/api/values/777");
    Console.WriteLine($"{fooResult}");
    Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
    Console.ReadKey();
}
上面的程式碼執行結果如下所示,因為我們使用的是 GetStringAsync 這個非同步方法,使用 Get 動作來呼叫遠端 Web API;另外,從方法名稱你就可以看的出來,我們這裡得到的執行結果內容,將會是字串。
{"success":true,"message":"透過 Get 方法,接收到 Id=777","payload":{"id":777,"name":"Vulcan01","filename":null}}
Press any key to Exist...
讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
  • 請求 (Request)
GET http://vulcanwebapi.azurewebsites.net/api/values/777 HTTP/1.1
Host: vulcanwebapi.azurewebsites.net
Connection: Keep-Alive
  • 反應 (Response)
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Wed, 18 Oct 2017 15:46:58 GMT

78
{"success":true,"message":"透過 Get 方法,接收到 Id=777","payload":{"id":777,"name":"Vulcan01","filename":null}}
0
HttpClient 類別,提供了很多 Get 動作呼叫的方法,這些方法與相關的多載方法定義,將為回傳不同 Web API 執行結果的內容。
  • GetAsync(String)
    以非同步作業的方式,將 GET 要求傳送至指定的 URI。
  • GetByteArrayAsync(String)
    將 GET 要求傳送至指定的 URI,並透過非同步作業,以位元組陣列形式傳回回應內容。
  • GetStreamAsync(String)
    將 GET 要求傳送至指定的 URI,並透過非同步作業,以資料流形式傳回回應內容。
現在,我們替換 URL 成為一個不存在的 Web API URL
http://vulcanwebapi.azurewebsites.net/api/XXX/777
讓我們來看看會有甚麼結果,底下是我們要測試的程式碼
這裡的專案原始碼,可以從 BadUrl 專案中找到
HttpClient client = new HttpClient();
var fooResult = await client.GetStringAsync("http://vulcanwebapi.azurewebsites.net/api/XXX/777");
Console.WriteLine($"{fooResult}");
Console.WriteLine($"Press any key to Exist...{Environment.NewLine}");
Console.ReadKey();
此時,Visual Studio 將會出現這樣的畫面與文字內容
未處理的例外狀況:System.Net.Http.HttpRequestException: '回應狀態碼未表示成功: 404 (Not Found)。'
Not Found 404
很明顯的,若當我們呼叫 HttpClient.GetStringAsync 方法,所提供的 URL 是個不正確的 URL,此時,HttpClient 類別物件就會發出例外異常的訊息出來,並且,您的程式也就中斷了。
讓我們來看看,這個 Web API 的呼叫動作中,在請求 (Request) 與 反應 (Response) 這兩個階段,會在網路上傳送了那些 HTTP 資料
  • 請求 (Request)
GET http://vulcanwebapi.azurewebsites.net/api/XXX/777 HTTP/1.1
Host: vulcanwebapi.azurewebsites.net
Connection: Keep-Alive
  • 反應 (Response)
HTTP/1.1 404 Not Found
Content-Length: 0
Server: Kestrel
X-Powered-By: ASP.NET
Set-Cookie: ARRAffinity=9d3635139ab6649f453417d1e9047b7ed7a79b7bef031b04afeb6a2c58b33d4e;Path=/;HttpOnly;Domain=vulcanwebapi.azurewebsites.net
Date: Wed, 18 Oct 2017 15:51:03 GMT
那麼,該要如何處理這樣問題呢?接下來的文章中,將會有更多這方面的程式開發建議。
最後,讓我們來看看這個遠端 Web API 的程式碼:
[HttpGet("{id}")]
public APIResult Get(int id)
{
    APIResult foo;
    if (id == 777)
    {
        foo = new APIResult()
        {
            Success = true,
            Message = "透過 Get 方法,接收到 Id=777",
            Payload = new APIData()
            {
                Id = 777,
                Name = "Vulcan01"
            }
        };
    }
    else
    {
        foo = new APIResult()
        {
            Success = false,
            Message = "無法發現到指定的 ID",
            Payload = null
        };
    }
    return foo;
}
在這個測試範例中,將會執行上述的要求動作方法,從這個方法宣告中,我們可以看到,將會要得到一個 id 參數,代表要查看的物件代碼編號,接著,在程式碼中,將會檢查這個 id 是否為 777,並且根據檢查結果,回傳不同的 APIResult 物件。
在這個 Get(int id) 方法中,我們看到他的回傳型別為 APIResult,而在 ASP.NET Core Web API 系統中,預設將回傳的物件,序列化成為 JSON 格式,所以,在用戶端的程式碼中,你將會得到類似這樣的文字字串。
{"success":true,"message":"透過 Get 方法,接收到 Id=777","payload":{"id":777,"name":"Vulcan01","filename":null}}

相關文章索引

C# HttpClient WebAPI 系列文章索引

了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式

關於 Xamarin 在台灣的學習技術資源

Xamarin 實驗室 粉絲團
歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
Xamarin.Forms @ Taiwan
歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
Xamarin 實驗室 部落格
Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
Xamarin.Forms 系列課程
Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程


C# HttpClient WebAPI : 1. 研究專題介紹

這是一篇關於如何在用戶端 (Client) 使用 HttpClient 類別的說明系列文章,在這裡我們將會說明如何使用 HttpClient 所建立的物件,進行遠端 Web API 的 Get / Post / Put / Delete 等動作的呼叫與接收回傳結果,當然,我們也會說明如何使用 QueryString / Form-Data / Multi-Part / JSON 等不同格式,將用戶端的資料,傳遞到遠端 Web API 的使用方式;除了一般純文字的 Web API 內容呼叫與回傳,我們在這裡也會進行如何處理 Binary 二進位的資料上傳與取得,我們將會說明如何上傳圖片檔案到遠端 Web API 伺服器上與如何使用 HttpClient 取得遠端 Web Server 上的圖片檔案,接著儲存到本機上。
當然,我們也需要了解如何從用戶端傳送 Http Header 與 Cookie 到遠端 Web API上,接著,如何在用戶端中,取得 Web API回報的 Cookie 與 Header 資料;尤其,我們也會展示如何顯示 HttpClient 下載大量資料的時候的進度回報事件設計方式與如何取消較長時間的 HttpClient 呼叫,最後,我們將會設定一個逾時時間,當執行 HttpClient 的 Web API 執行期間,花費時間超過我們所設定的時間,將會終止這樣的 Web API 呼叫之程式寫法。
這篇系列文章的所有使用到的專案原始碼,您可以從 https://github.com/vulcanlee/CSharpNotes/tree/master/WebAPI 取得

了解更多關於 [HttpClient Class] 的使用方式
了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式


HttpClient 系列文章索引


      1. +
      2. C# HttpClient WebAPI : 13. 上傳本機圖片檔案到遠端伺服器上
      3. C# HttpClient WebAPI : 17. 有趣的 HttpClient 管道,自訂 HttpMessage Handler
      4. C# HttpClient WebAPI : 18. 套用 ProgressMessageHandler ,訂閱請求與回應的資料傳輸事件

        後端 Web API 的原始碼

        底下將會是我們要使用到的 Web API 專案原始碼,在這裡,我們採用的是 ASP.NET Core Web API 類型專案來開發,並且將這個 Web API 專案佈署到 Azure 平台上。在每個 HttpClient 研究文章中,將也會針對所使用的後端 Web API 方法,進行進一步的說明。
        底下是絕大部分的 Web API 控制器 ValuesController 的程式碼:
        namespace VulcanWebAPI.Controllers
        {
            [Route("api/[controller]")]
            public class ValuesController : Controller
            {
                // http://vulcanwebapi.azurewebsites.net/api/values
                [HttpGet]
                public APIResult Get()
                {
                    APIResult foo = new APIResult()
                    {
                        Success = true,
                        Message = "成功得所有資料集合",
                        Payload = new List<APIData>()
                        {
                            new APIData()
                            {
                                Id =777,
                                Name = "Vulcan01"
                            },
                            new APIData()
                            {
                                Id =234,
                                Name ="Vulcan02"
                            }
                        }
                    };
                    return foo;
                }
        
                [HttpGet("QueryStringGet")]
                public APIResult QueryStringGet([FromQuery] APIData value)
                {
                    APIResult foo;
                    if (value.Id == 777)
                    {
                        foo = new APIResult()
                        {
                            Success = true,
                            Message = "透過 Get 方法,接收到 Id=777",
                            Payload = new APIData()
                            {
                                Id = 777,
                                Name = "Vulcan by QueryStringGet"
                            }
                        };
                    }
                    else
                    {
                        foo = new APIResult()
                        {
                            Success = false,
                            Message = "無法發現到指定的 ID",
                            Payload = null
                        };
                    }
                    return foo;
                }
        
                [HttpGet("GetException")]
                public APIResult GetException()
                {
                    APIResult foo = new APIResult();
                    throw new Exception("喔喔,我發生錯誤了");
                    return foo;
                }
        
                [HttpGet("GetExceptionFilter")]
                [CustomExceptionFilter]
                public APIResult GetExceptionFilter()
                {
                    APIResult foo = new APIResult();
                    throw new Exception("喔喔,我發生錯誤了");
                    return foo;
                }
        
                // http://vulcanwebapi.azurewebsites.net/api/values/777
                [HttpGet("{id}")]
                public APIResult Get(int id)
                {
                    APIResult foo;
                    if (id == 777)
                    {
                        foo = new APIResult()
                        {
                            Success = true,
                            Message = "透過 Get 方法,接收到 Id=777",
                            Payload = new APIData()
                            {
                                Id = 777,
                                Name = "Vulcan01"
                            }
                        };
                    }
                    else
                    {
                        foo = new APIResult()
                        {
                            Success = false,
                            Message = "無法發現到指定的 ID",
                            Payload = null
                        };
                    }
                    return foo;
                }
        
                // http://vulcanwebapi.azurewebsites.net/api/Values
                // 使用 JSON 格式
                [HttpPost]
                public APIResult Post([FromBody]APIData value)
                {
                    APIResult foo;
        
                    if (value.Id == 777)
                    {
                        foo = new APIResult()
                        {
                            Success = true,
                            Message = "透過 post 方法,接收到 Id=777 資料",
                            Payload = value
                        };
                    }
                    else
                    {
                        foo = new APIResult()
                        {
                            Success = false,
                            Message = "無法發現到指定的 ID",
                            Payload = null
                        };
                    }
                    return foo;
                }
        
                // http://vulcanwebapi.azurewebsites.net/api/Values/FormUrlencodedPost
                // 使用 FormUrlEncodedContent
                [HttpPost("FormUrlencodedPost")]
                public APIResult FormUrlencodedPost([FromForm]APIData value)
                {
                    APIResult foo;
        
                    if (value.Id == 777)
                    {
                        foo = new APIResult()
                        {
                            Success = true,
                            Message = "透過 post 方法,接收到 Id=777 資料",
                            Payload = value
                        };
                    }
                    else
                    {
                        foo = new APIResult()
                        {
                            Success = false,
                            Message = "無法發現到指定的 ID",
                            Payload = null
                        };
                    }
                    return foo;
                }
        
                [HttpPut]
                public APIResult Put(int id, [FromBody]APIData value)
                {
                    APIResult foo;
        
                    if (value.Id == 777)
                    {
                        foo = new APIResult()
                        {
                            Success = true,
                            Message = "透過 Put 方法,接收到 Id=777 資料",
                            Payload = value
                        };
                    }
                    else
                    {
                        foo = new APIResult()
                        {
                            Success = false,
                            Message = "無法發現到指定的 ID",
                            Payload = null
                        };
                    }
                    return foo;
                }
        
                [HttpDelete("{id}")]
                public APIResult Delete(int id)
                {
                    APIResult foo;
        
                    if (id == 777)
                    {
                        foo = new APIResult()
                        {
                            Success = true,
                            Message = "Id=777 資料 已經刪除了",
                            Payload = null
                        };
                    }
                    else
                    {
                        foo = new APIResult()
                        {
                            Success = false,
                            Message = "無法發現到指定的 ID",
                            Payload = null
                        };
                    }
                    return foo;
                }
        
                // http://vulcanwebapi.azurewebsites.net/api/values/HeaderPost
                [HttpPost("HeaderPost")]
                public APIResult HeaderGet([FromBody]LoginInformation loginInformation)
                {
                    APIResult foo;
                    StringValues VerifyCode = "";
        
                    this.HttpContext.Request.Headers.TryGetValue("VerifyCode", out VerifyCode);
                    if (StringValues.IsNullOrEmpty(VerifyCode))
                    {
                        foo = new APIResult()
                        {
                            Success = false,
                            Message = "驗證碼沒有發現",
                            Payload = null
                        };
                    }
                    else
                    {
                        if (VerifyCode != "123")
                        {
                            foo = new APIResult()
                            {
                                Success = false,
                                Message = "驗證碼不正確",
                                Payload = null
                            };
                        }
                        else
                        {
                            if (loginInformation.Account == "Vulcan" &&
                                loginInformation.Password == "123")
                            {
                                foo = new APIResult()
                                {
                                    Success = true,
                                    Message = "這個帳號與密碼正確無誤",
                                    Payload = null
                                };
                            }
                            else
                            {
                                foo = new APIResult()
                                {
                                    Success = false,
                                    Message = "這個帳號與密碼不正確",
                                    Payload = null
                                };
                            }
                        }
                    }
                    return foo;
                }
        
                [HttpPost("Login")]
                public async Task<APIResult> Login([FromBody]LoginInformation loginInformation)
                {
                    APIResult foo;
        
                    if (loginInformation.Account == "Vulcan" &&
                        loginInformation.Password == "123")
                    {
                        var claims = new List<Claim>() {
                        new Claim(ClaimTypes.Name, "Herry"),
                        new Claim(ClaimTypes.Role, "Users")
                    };
                        var claimsIdentity = new ClaimsIdentity(claims, "myTest");
                        var principal = new ClaimsPrincipal(claimsIdentity);
                        try
                        {
                            await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme,
                                principal,
                                new AuthenticationProperties
                                {
                                    ExpiresUtc = DateTime.UtcNow.AddMinutes(20),
                                    IsPersistent = true,
                                    AllowRefresh = true
                                });
                            foo = new APIResult()
                            {
                                Success = true,
                                Message = "這個帳號與密碼正確無誤",
                                Payload = null
                            };
                        }
                        catch (Exception ex)
                        {
                            Console.WriteLine(ex.Message);
                            foo = new APIResult()
                            {
                                Success = false,
                                Message = "這個帳號與密碼不正確",
                                Payload = null
                            };
                        }
                    }
                    else
                    {
                        foo = new APIResult()
                        {
                            Success = false,
                            Message = "這個帳號與密碼不正確",
                            Payload = null
                        };
                    }
        
                    return foo;
                }
        
                [Authorize]
                [HttpGet("LoginCheck")]
                public APIResult LoginCheck()
                {
                    APIResult foo = new APIResult()
                    {
                        Success = true,
                        Message = "成功得所有資料集合",
                        Payload = new List<APIData>()
                        {
                            new APIData()
                            {
                                Id =777,
                                Name = "Vulcan01"
                            },
                            new APIData()
                            {
                                Id =234,
                                Name ="Vulcan02"
                            }
                        }
                    };
                    return foo;
                }
        
                [HttpGet("LongTimeGet")]
                public async Task<APIResult> LongTimeGet()
                {
                    APIResult foo;
        
                    await Task.Delay(5000);
                    foo = new APIResult()
                    {
                        Success = true,
                        Message = "透過 Get 方法",
                        Payload = new APIData()
                        {
                            Id = 777,
                            Name = "Vulcan01"
                        }
                    };
                    return foo;
                }
        
            }
        }
        
        底下將是圖片上傳的 UploadController 控制器原始碼:
        namespace VulcanWebAPI.Controllers
        {
            [Route("api/[controller]")]
            public class UploadController : Controller
            {
                APIResult fooAPIResult = new APIResult();
                private IHostingEnvironment _HostingEnvironment;
        
                public UploadController(IHostingEnvironment hostingEnvironment)
                {
                    _HostingEnvironment = hostingEnvironment;
                }
        
                [HttpPost]
                public async Task<APIResult> Post(List<IFormFile> files)
                {
                    // https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads
        
                    string webDatasRoot = Path.Combine(_HostingEnvironment.WebRootPath, "Datas");
        
                    long size = files.Sum(f => f.Length);
        
                    // full path to file in temp location
        
                    if (files.Count > 0)
                    {
                        foreach (var formFile in files)
                        {
                            if (formFile.Length > 0)
                            {
                                var filePath = Path.Combine(webDatasRoot, formFile.FileName);
                                using (var stream = new FileStream(filePath, FileMode.Create))
                                {
                                    await formFile.CopyToAsync(stream);
                                }
        
                                fooAPIResult.Success = true;
                                fooAPIResult.Message = "檔案上傳成功";
                                fooAPIResult.Payload = new APIData
                                {
                                    Id = 3000,
                                    Name = "Your Name",
                                    Filename = formFile.FileName
                                };
                            }
                        }
                    }
                    else
                    {
                        fooAPIResult.Success = false;
                        fooAPIResult.Message = "沒有任何檔案上傳";
                        fooAPIResult.Payload = null;
                    }
        
                    return fooAPIResult;
                }
        
        
                [HttpPost("FileAndData")]
                public async Task<APIResult> FileAndData(List<IFormFile> files, LoginInformation loginInformation)
                {
                    // https://docs.microsoft.com/en-us/aspnet/core/mvc/models/file-uploads
        
                    string webDatasRoot = Path.Combine(_HostingEnvironment.WebRootPath, "Datas");
        
                    long size = files.Sum(f => f.Length);
        
                    // full path to file in temp location
                    if (files.Count > 0)
                    {
                        foreach (var formFile in files)
                        {
                            if (formFile.Length > 0)
                            {
                                using (var memoryStream = new MemoryStream())
                                {
                                    await formFile.CopyToAsync(memoryStream);
                                    memoryStream.Seek(0, SeekOrigin.Begin);
                                    var streamReader = new StreamReader(memoryStream);
                                    var fooContent = streamReader.ReadToEnd();
        
                                    fooAPIResult.Success = true;
                                    fooAPIResult.Message = "檔案上傳成功";
                                    fooAPIResult.Payload = new LoginInformation
                                    {
                                        Account = $">> {loginInformation.Account}",
                                        Password = $">> {loginInformation.Account}",
                                        VerifyCode = fooContent
                                    };
                                }
                            }
                        }
                    }
                    else
                    {
                        fooAPIResult.Success = false;
                        fooAPIResult.Message = "沒有任何檔案上傳";
                        fooAPIResult.Payload = null;
                    }
        
                    return fooAPIResult;
                }
            }
        }

        了解更多關於 [HttpClient Class] 的使用方式
        了解更多關於 [使用 async 和 await 進行非同步程式設計] 的使用方式

        關於 Xamarin 在台灣的學習技術資源

        Xamarin 實驗室 粉絲團
        歡迎加入 Xamarin 實驗室 粉絲團,在這裡,將會經常性的貼出各種關於 Xamarin / Visual Studio / .NET 的相關消息、文章、技術開發等文件,讓您可以隨時掌握第一手的 Xamarin 方面消息。
        Xamarin.Forms @ Taiwan
        歡迎加入 Xamarin.Forms @ Taiwan,這是台灣的 Xamarin User Group,若您有任何關於 Xamarin / Visual Studio / .NET 上的問題,都可以在這裡來與各方高手來進行討論、交流。
        Xamarin 實驗室 部落格
        Xamarin 實驗室 部落格 是作者本身的部落格,這個部落格將會專注於 Xamarin 之跨平台 (Android / iOS / UWP) 方面的各類開技術探討、研究與分享的文章,最重要的是,它是全繁體中文。
        Xamarin.Forms 系列課程
        Xamarin.Forms 系列課程 想要快速進入到 Xamarin.Forms 的開發領域,學會各種 Xamarin.Forms 跨平台開發技術,例如:MVVM、Prism、Data Binding、各種 頁面 Page / 版面配置 Layout / 控制項 Control 的用法等等,千萬不要錯過這些 Xamarin.Forms 課程