Какова цель HttpClient.BaseAddress и почему я не могу изменить его после первого запроса
Таким образом, большинство из нас, вероятно, читали, что мы должны повторно использовать экземпляры HttpClient
Вместо того, чтобы использовать using
и создание новых. Это означает, что я могу просто создать один экземпляр HttpClient
в моей программе и звоните GetAsync
используя полную строку URI для запроса. Это приводит меня к BaseAddress
собственностью HttpClient
, Рассмотрим следующий код:
HttpClient microsoftClient = new HttpClient() { BaseAddress = new Uri("https://www.microsoft.com/") };
HttpClient stackruClient = new HttpClient() { BaseAddress = new Uri("https://stackru.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 = stackruClient.GetAsync("company/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access stackru.com/company/about from the stackru client");
response = stackruClient.GetAsync("https://www.microsoft.com/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access microsoft.com/about from the stackru client");
microsoftClient.BaseAddress = new Uri("https://stackru.com");
response = microsoftClient.GetAsync("company/about").Result;
Console.WriteLine($"I {((response.IsSuccessStatusCode) ? "can" : "cannot")} access stackru.com/company/about from the microsoft client, after changing the BaseAddress");
Вплоть до последнего блока этот код работает нормально, даже при использовании клиента со стековым потоком BaseAddress
чтобы получить доступ к Microsoft. Однако этот код бросает InvalidOperationException
в начале последнего блока, при переназначении BaseAddress
заявляя
'This instance has already started one or more requests. Properties can only be modified before sending the first request.'
Это приводит меня к следующим вопросам:
- В чем выгода использования
BaseAddress
совсем? Я мог бы всегда использовать полный адрес в моемGetAsync
вызов. Это просто для удобства / производительности, когда нет необходимости создавать полную строку запроса? Я предполагал, что это создаст только одинServicePoint
внутренне как описано в первом параграфе этого сообщения в блоге (или что-то подобное, поскольку сообщение довольно старое). - Что происходит внутри, что мы не можем изменить свойство
HttpClient
, особенноBaseAddress
после отправки первого запроса? Это кажется довольно неудобным, если использование этого свойства действительно приносит выгоды.
2 ответа
Для (1) распространенным вариантом использования будет клиент, который взаимодействует только с одним сервером. Может быть, это внутренний API, который этот клиент был создан для использования. Точные данные будут сохранены в файле конфигурации, который клиент читает во время запуска.
Мы могли бы засорять наш код прямым доступом к конфигурации или вводить строку, считанную из конфигурации, в каждое место, где требуется создать полный URL-адрес. Или мы могли бы просто настроить BaseAddress
из HttpClient мы помещаем в наш контейнер Dependency Injection и просто позволяем потребителям вводить этот объект. Это для меня несколько ожидаемый вариант использования.
Для (2), я не думаю, что есть техническое ограничение. Я думаю, что это больше, чтобы спасти людей от самих себя. С настройкой BaseAddress
и вызывая фактический запрос, чтобы выйти через, например, GetAsync
это отдельные действия, было бы небезопасно, если бы две отдельные части кода делали это одновременно - вы могли бы легко получить гонки. Поэтому проще рассуждать о многопоточных программах, которые могут совместно использовать один экземпляр HttpClient
если такие гонки не разрешены в первую очередь.
2 цели:
Удобство. Если вы вызываете множество конечных точек с одного хоста и управляете сегментами базового адреса и конечной точки как отдельными строками (очень часто), это поможет вам избежать уродливой конкатенации строк при каждом вызове.
Поощрение лучших практик. Хотя звонить через
GetAsync
и т. д. является потокобезопасным,HttpClient
имеет несколько свойств в дополнение кBaseAddress
, такие какDefaultRequestHeaders
Это не так. Как правило, вы хотите, чтобы они были одинаковыми для вызовов на один и тот же хост, но не для вызовов на разные. По этой причинеHttpClient
экземпляр для каждого вызываемого хоста на самом деле очень хорошая практика. Если вы не вызываете тысячи разных хостов, вам не нужно беспокоиться о печально известной проблеме исчерпания сокетов. (И даже если вы используете одноэлементный пакет, базовый сетевой стек в любом случае должен будет открыть другой сокет для каждого хоста.)
Так почему же указание полного адреса на HttpClient
назвать вообще работу вообще? Опять удобство. Адрес может исходить от внешнего источника или пользовательского ввода, и вам не нужно разбивать его на части, чтобы использовать его. Но в этом случае вы находитесь на крючке для обеспечения безопасности потоков, и эти свойства, не связанные с потоками, вероятно, следует просто полностью избегать.