Jer*_*der 5 c# dotnet-httpclient
所以我们大多数人可能已经读过我们应该重用 的实例,HttpClient而不是使用using和创建新的实例。这意味着我可以只HttpClient在我的程序中创建一个实例,并GetAsync使用每个请求的完整 uri 字符串进行调用。这使我的BaseAddress财产HttpClient。考虑以下代码:
HttpClient microsoftClient = new HttpClient() { BaseAddress = new Uri("https://www.microsoft.com/") };
HttpClient stackoverflowClient = new HttpClient() { BaseAddress = new Uri("https://stackoverflow.com/") };
var response = microsoftClient.GetAsync("about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access microsoft.com/about from the microsoft client");
response = microsoftClient.GetAsync("trademarks").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access microsoft.com/trademarks from the microsoft client");
response = stackoverflowClient.GetAsync("company/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access stackoverflow.com/company/about from the stackoverflow client");
response = stackoverflowClient.GetAsync("https://www.microsoft.com/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access microsoft.com/about from the stackoverflow client");
microsoftClient.BaseAddress = new Uri("https://stackoverflow.com");
response = microsoftClient.GetAsync("company/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access stackoverflow.com/company/about from the microsoft client, after changing the BaseAddress");
Run Code Online (Sandbox Code Playgroud)
直到最后一个块,此代码运行良好,即使在使用带有 stackoverflow 的客户端BaseAddress访问 Microsoft 时也是如此。然而,这段代码InvalidOperationException在最后一个块的开头抛出一个,当重新分配 时BaseAddress,说明
'This instance has already started one or more requests. Properties can only be modified before sending the first request.'
Run Code Online (Sandbox Code Playgroud)
这使我想到以下问题:
BaseAddress?我总是可以在GetAsync通话中使用完整地址。是否只是为了方便/性能而不必构建完整的请求字符串?我的猜测是,它只会在ServicePoint内部创建一个,如本博文第一段中所述(或类似帖子很旧的内容)。HttpClient,尤其是BaseAddress在内部会发生什么?如果使用此属性实际上会产生好处,这似乎很不方便。对于(1),一个常见的用例是一个与一个服务器交互的客户端。也许这是构建此客户端使用的后端 API。确切的详细信息将存储在客户端在启动期间读取的配置文件中。
我们可以通过直接访问配置来处理我们的代码,或者将从配置中读取的字符串注入需要构建完整 URL 的每个地方。或者我们可以只配置BaseAddressHttpClient 放入依赖注入容器中,然后让消费位置注入该对象。这对我来说是一个有点预期的用例。
对于(2),我认为没有技术限制。我认为这更多是为了拯救人们。由于设置 aBaseAddress并导致通过 eg 发出实际请求GetAsync是单独的操作,因此两个单独的代码段同时执行这样的操作是不安全的 - 您很容易发生竞争。因此,更容易推断多线程程序可能会共享一个实例,HttpClient如果这种竞争首先是不允许的。
2个目的:
方便。如果您在单个主机上调用多个端点,并且将基地址和端点段作为单独的字符串进行管理(非常常见),它可以帮助您避免在每次调用时都进行丑陋的字符串连接。
鼓励最佳实践。尽管通过GetAsync等进行调用是线程安全的,HttpClient但除了 之外还有几个属性BaseAddress,例如DefaultRequestHeaders,则不是。通常,您希望对同一主机的调用相同,但对于不同主机的调用则不希望这些相同。出于这个原因,每个主机调用一个HttpClient实例实际上是一个很好的做法。除非您正在呼叫数千个不同的主机,否则您无需担心这里臭名昭著的套接字耗尽问题。(即使您使用的是单例,底层网络堆栈也需要为每个主机打开一个不同的套接字。)
那么为什么在HttpClient呼叫中指定完整地址甚至起作用呢?再说方便。地址可能来自外部来源或用户输入,您不希望为了使用它而将其分成几部分。但在这种情况下,您需要注意线程安全,而那些非线程安全的属性可能应该完全避免。