readme-WebAPI.txt ---------- 20200415 Generate C# Client API for ASP.NET Web API https://www.codeproject.com/Articles/1074039/Generate-Csharp-Client-API-for-ASP-NET-Web-API ---------- 20191117 todo: ASP.Net 多文件上傳實現教學 https://y907372002.pixnet.net/blog/post/29674608 [筆記][WebAPI][Upload] WebAPI 檔案上傳範例 https://dotblogs.com.tw/topcat/2017/07/26/101457 ---------- 20191114 小心.NET HttpClient https://dotblogs.com.tw/jakeuj/2019/01/25/httpclient 背景 在過去.NET(4.5之前),向HTTP服務器發送請求可以通過使用WebClient或在較低級別通過HttpWebRequest。在2009年,作為REST入門套件(RSK)的一部分,出現了一種新的抽象概念,稱為HttpClient; 直到.NET 4.5發布才開始,因為它可供更廣泛的觀眾使用。 此抽象提供了更簡單的方法,可以在完全異步中與HTTP服務器通信,並允許為每個請求設置默認標頭。這一切都很棒,但是關於這個課程有一些黑暗的秘密,如果沒有解決這個問題可能會導致嚴重的性能,有時甚至會出現令人難以置信的錯誤!在本文中,我們將探討在使用該類時需要注意的問題 細微之處HttpClient。 那有什麼不對? 的HttpClient類實現IDisposable提示此類型的任何對象必須加以處置使用後; 考慮到這一點,讓我們看看如何使用這個類假設我不知道這個問題: var endpoint = new Uri("http://localhost:1234/"); for (int i = 0; i < 10; i++) { using (var client = new HttpClient()) { var response = await client.GetStringAsync(endpoint); Console.WriteLine(response); } } 所以這裡我們按順序向端點發送10個請求,並假設有一個監聽器在端口1234或您選擇命中的任何其他端點上提供請求,您將看到10個響應寫入Console; 一切順利,對吧?錯! 讓我們運行命令:netstat -abn在應該返回的CMD上(取決於您點擊的端點): ... TCP [::1]:40968 [::1]:1234 TIME_WAIT TCP [::1]:40969 [::1]:1234 TIME_WAIT TCP [::1]:40970 [::1]:1234 TIME_WAIT TCP [::1]:40971 [::1]:1234 TIME_WAIT TCP [::1]:40972 [::1]:1234 TIME_WAIT TCP [::1]:40973 [::1]:1234 TIME_WAIT TCP [::1]:40975 [::1]:1234 TIME_WAIT TCP [::1]:40976 [::1]:1234 TIME_WAIT TCP [::1]:40977 [::1]:1234 TIME_WAIT TCP [::1]:40978 [::1]:1234 TIME_WAIT ... 你問的是什麼?好吧,這向我們展示了我們的應用程序向服務器開放了10個套接字,因此每個請求都有一個,但更重要的是,即使我們的應用程序現已結束,操作系統仍有10個套接字仍處於佔用狀態且處於TIME__WAIT狀態。 這是由於TCP / IP被設計為工作的方式,因為連接沒有立即關閉以允許數據包在連接關閉後無序到達或重新傳輸。TIME WAIT表示本地端點(我們這邊的端點)已關閉連接,但保持連接,以便可以正確處理任何延遲的數據包。一旦發生這種情況,連接將在4分鐘的超時時間後被刪除。請記住,我們向同一個端點發送了10個請求,但我們仍有10個單獨的插槽仍在忙碌至少4分鐘! 上面的例子是一個過於簡化的例子,但看看這個: public class ProductController : ApiController { public async Task GetProductAsync(string id) { using (var httpClient = new HttpClient()) { var result = await httpClient.GetStringAsync("http://somewhere/api/..."); return new Product { Name = result }; } } } 每次傳入請求這樣做最終會導致SocketException,不相信我?只需運行負載測試,坐下來觀察在套接字用完之前可以提供多少請求! 我們能做什麼? 好吧,首先想到的是重新使用我們的客戶端而不是為每個請求創建一個新客戶端,但正如您將在後面看到的那樣,這可能會導致另一個問題。在我們談到這一點之前,讓我們先了解一下我們是否可以重複使用單個實例。是HTTPClient線程安全的?答案是肯定的,至少以下方法已被證明是線程安全的: CancelPendingRequests DeleteAsync GetAsync GetByteArrayAsync GetStreamAsync GetStringAsync PostAsync PutAsync SendAsync 但是,以下內容不是線程安全的,並且在第一次請求完成後無法更改: BaseAddress, Timeout, MaxResponseContentBufferSize 事實上,在同一文檔頁面的備註部分,它解釋了: HttpClient旨在實例化一次,並在應用程序的整個生命週期中重複使用。為每個請求實例化一個HttpClient類將耗盡重負載下可用的套接字數量。這將導致SocketException錯誤。 Darrel Miller自己也重申了這一點: ... HTTPClient確實實現了IDisposable,但是,我不建議在Using塊中創建一個HttpClient來發出單個請求。當HttpClient被釋放時,它也會導致底層連接被關閉。這意味著下一個請求必須重新打開該連接。你應該嘗試重用你的HttpClient實例.... 好的,是嗎?創建並重用我們客戶的單個實例和快樂的日子?好吧,不!您可能面臨另一個非常微妙但嚴重的問題。 Singleton HttpClient不尊重DNS更改! 重新使用HttpClient它持有套接字的實例直到它被關閉,所以如果你在服務器上發生DNS記錄更新,客戶端將永遠不會知道,直到該套接字關閉並讓我告訴你DNS記錄變化為不同原因一直是,例如故障轉移場景只是其中之一(雖然在這種情況下連接/套接字會出現故障和關閉)或者在交換不同實例時進行Azure部署,例如生產/暫存,在這種情況下是您的客戶端仍然會擊中舊的實例!事實上,dotnet / corefx repo中存在關於此行為的問題。 HTTPClient(出於正當理由)在連接打開時不檢查DNS記錄,那麼我們如何解決這個問題呢?一個天真的 簡單解決方法是將keep-alive標頭設置為false每次請求後套接字將被關閉,這顯然會導致性能不佳,但如果你不關心,那麼就有你的答案; 但是,我認為我們可以做得更好。 有一個鮮為人知的ServicePoint類,它解決了我們的問題。此類負責管理TCP連接的不同屬性,其中一個屬性是ConnectionLeaseTimeout。顧名思義,這個人指定TCP套接字可以保持打開的時間長度(以毫秒為單位)。默認情況下,此屬性的值設置為-1,導致套接字無限期地保持打開(相對而言),因此我們所要做的就是將其設置為更實際的值: ServicePointManager.FindServicePoint(endpoint) .ConnectionLeaseTimeout = (int)TimeSpan.FromMinutes(1).TotalMilliseconds; 上述覆蓋需要為每個端點應用一次。請注意,該方法僅關注主機,架構和端口,其他一切都被忽略。 差不多了... 到目前為止,我們已經註意在一段時間後強行關閉連接但這只是第一部分。如果我們的單例客戶端打開另一個連接,它可能仍然指向舊服務器,為什麼你問?以及所有DNS條目都被緩存,默認情況下不會刷新2分鐘。所以我們還需要通過DnsRefreshTimeout在ServicePointManager類上設置這樣來減少我們可以做的緩存超時: ServicePointManager.DnsRefreshTimeout = (int)1.Minutes().TotalMilliseconds; 我希望有一個更好的抽象,而不必記住在每個請求上都做這一切,我還希望抽象實現一個接口,用於我的服務之間的依賴注入。 RESTClient實現 RestClient是一個線程安全的包裝器HttpClient,內部保留已發送請求的端點緩存,如果要求它向其緩存中沒有的端點發送請求,則更新該ConnectionLeaseTimeout端點。這是一個簡單的用法示例: // This is to show that IRestClient implements IDisposable // just like HttpClient, you should not dispose it per request. using (IRestClient client = new RestClient()) { client.SendAsync(new HttpRequestMessage(HttpMethod.Get, new Uri("http://localhost/api"))); } 現在,您可以安全地抓住客戶端和/或使用您最喜歡的IOC容器進行註冊,並將其註入您需要的任何位置。 該類支持相同的構造函數,HttpClient並且還提供了一種設置其默認屬性的安全方法: var defaultHeaders = new Dictionary { {"Accept", "application/json"}, {"User-Agent", "foo-bar"} }; using (IRestClient client = new RestClient(defaultHeaders, timeout: 15.Seconds(), maxResponseContentBufferSize: 10)) { client.DefaultRequestHeaders.Count.ShouldBe(defaultHeaders.Count); client.DefaultRequestHeaders["Accept"].ShouldBe("application/json"); client.DefaultRequestHeaders["UserAgent"].ShouldBe("foo-bar"); client.Endpoints.ShouldBeEmpty(); client.MaxResponseContentBufferSize.ShouldBe((uint)10); client.Timeout.ShouldBe(15.Seconds()); } 代碼在GitHub上,可以在NuGet 上獲得,作為我在其他項目中使用的Easy.Common庫的一部分。 更新2019年 從.NET Core 2.1開始,Microsoft通過提供解決了本文中涉及的一些問題HttpClientFactory。儘管本課程提供了各種功能,但在我看來,使用此類型還涉及太多的儀式,我們仍然需要處理自己設置DNS刷新超時; 因此,我仍然喜歡RestClient在我的項目中使用。 HttpClient在.NET Core 2.1中也進行了大修,重寫了一個HttpMessageHandler調用SocketsHttpHandler,這導致了顯著的性能改進,它還引入了PooledConnectionLifetime允許我們設置連接超時的屬性,而無需ConnectionLeaseTimeout為每個端點設置。 由於版本Easy.Common 3.0.0中,RestClient不再需要設置ConnectionLeaseTimeout上運行時.NET核心2.1或更高版本。 玩得開心和快樂REST ING。 ---------- 20191114 HttpClient 有以下方法 DeleteAsync, GetAsync, PostAsync, PutAsync, SendAsync // Summary: // Provides a base class for sending HTTP requests and receiving HTTP responses // from a resource identified by a URI. public HttpClient(); public HttpClient(HttpMessageHandler handler); public HttpClient(HttpMessageHandler handler, bool disposeHandler); public Uri BaseAddress { get; set; } public HttpRequestHeaders DefaultRequestHeaders { get; } public long MaxResponseContentBufferSize { get; set; } public TimeSpan Timeout { get; set; } public void CancelPendingRequests(); public Task DeleteAsync(string requestUri); public Task DeleteAsync(Uri requestUri); public Task DeleteAsync(string requestUri, CancellationToken cancellationToken); public Task DeleteAsync(Uri requestUri, CancellationToken cancellationToken); public Task GetAsync(string requestUri); public Task GetAsync(Uri requestUri); public Task GetAsync(string requestUri, HttpCompletionOption completionOption); public Task GetAsync(Uri requestUri, HttpCompletionOption completionOption); public Task GetAsync(string requestUri, CancellationToken cancellationToken); public Task GetAsync(Uri requestUri, CancellationToken cancellationToken); public Task GetAsync(Uri requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken); public Task GetAsync(string requestUri, HttpCompletionOption completionOption, CancellationToken cancellationToken); public Task GetByteArrayAsync(string requestUri); public Task GetByteArrayAsync(Uri requestUri); public Task GetStreamAsync(Uri requestUri); public Task GetStreamAsync(string requestUri); public Task GetStringAsync(string requestUri); public Task GetStringAsync(Uri requestUri); public Task PostAsync(string requestUri, HttpContent content); public Task PostAsync(Uri requestUri, HttpContent content); public Task PostAsync(string requestUri, HttpContent content, CancellationToken cancellationToken); public Task PostAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken); public Task PutAsync(string requestUri, HttpContent content); public Task PutAsync(Uri requestUri, HttpContent content); public Task PutAsync(string requestUri, HttpContent content, CancellationToken cancellationToken); public Task PutAsync(Uri requestUri, HttpContent content, CancellationToken cancellationToken); public Task SendAsync(HttpRequestMessage request); public override Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken); public Task SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption); public Task SendAsync(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationToken cancellationToken); protected override void Dispose(bool disposing); ---------- 20191114 When to use Post, PUT, PATCH and Delete https://stackoverflow.com/questions/54863759/rest-api-when-to-use-post-put-patch-and-delete Should I use PATCH in case of Verify (or anyother action where just recordId and versionNo send to server to change some fields) or it is OK to use PUT. In RESTful API designs, PUT requests are generally used to add or replace an entire resource, whereas a PATCH should be just used to update an existing resource. A PUT request is called "idempotent" - no matter how many times you send a PUT response, you should get the same result. A PATCH is not idempotent. example: PATCH /Cars/vauxhall-astra/engine --> This request would be used to only update the engine of my already existing vauxhall astra PUT /Cars/renault-clio --> This request would create a new Renault Clio or, if it already exists, replace the entire Clio using the data specified in my request. A Clio would then be guaranteed to exist after my request is successful, regardless of whether it existed or not before. Although, it a restful api but specific application which would be used by an angular application, So should I return data in response of POST/PUT requests. Totally up to you, returning data from a POST/PUT is fine - especially if it saves you having to make extra GET api requests. Just always make sure you are only ever returning the data you need from a response. To make uniformity, should I send data in body of delete request as I need recordId and versionNo to delete a record. Again it's totally up to you. Whether you use query parameters (e.g. DELETE cars?id=123) or a request body is just your preference, there's nothing in REST that has rules for this. ---------- 20190711 ref: 如何使用 WebRequest,HttpWebRequest 來存取(GET,POST,PUT,DELETE,PATCH)網路資源 https://blog.yowko.com/webrequest-and-httpwebrequest/ https://blog.yowko.com/webrequest-and-httpwebrequest/ https://blog.yowko.com/webclient/ https://blog.yowko.com/httpclient/ 適用版本: .NET 4.5 以下, 建議使用 WebClient. .NET 4.5 以上(含), 建議使用 HttpClient. .NET 4.6.1 以上, 建議使用 HttpClientFactory. (需另外安裝 Microsoft.Extensions.Http NuGet 套件) ---------- 20190712 HttpWebRequest vs WebClient vs HttpClient. ref: https://www.diogonunes.com/blog/webclient-vs-httpclient-vs-httpwebrequest/ https://stackoverflow.com/questions/20530152/deciding-between-httpclient-and-webclient/27737601#27737601 https://blog.yowko.com/webrequest-and-httpwebrequest/ https://blog.yowko.com/webclient/ https://blog.yowko.com/httpclient/