#c# #.net-core #dependency-injection #delegatinghandler
#c# #.net-ядро #внедрение зависимостей #обработчик делегирования
Вопрос:
У меня есть код, который добавляет имя HttpClient
к ServiceCollection
и добавляет DelegatingHandler
к клиенту. Пример кода:
public class MyDelegatingHandler : DelegatingHandler
{
// ...
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureServices((hostContext, services) =>
{
services.AddHttpClient("MyClient")
.AddHttpMessageHandler(provider => new MyDelegatingHandler());
});
Как я могу при написании модульных тестов убедиться, что DelegatingHandler
добавленная реализация была MyDelegatingHanlder
?
Ответ №1:
Эта информация хранится в закрытом поле HttpClient
. Вы можете добраться до этого, но это немного неортодоксально. Итак, вам может быть интересно, а может и нет, но я собираюсь опубликовать это на случай, если вы:
public static class HttpClientExtensions
{
public static bool ContainsDelegatingHandler<TDelegatingHandler>(
this HttpClient httpClient) where TDelegatingHandler: DelegatingHandler
{
if(httpClient == null)
{
throw new ArgumentNullException(nameof(httpClient));
}
var internalHandlerField = typeof(HttpMessageInvoker).GetField(
"_handler", BindingFlags.NonPublic | BindingFlags.Instance);
if(internalHandlerField == null)
{
throw new InvalidOperationException(
"_handler no longer exists as a private instance field of HttpClient");
}
// run down the chain of delegating handlers until there are no more
var delegatingHandler = internalHandlerField.GetValue(httpClient) as DelegatingHandler;
while (delegatingHandler != null)
{
if(delegatingHandler.GetType() == typeof(TDelegatingHandler))
{
return true;
}
delegatingHandler = delegatingHandler.InnerHandler as DelegatingHandler;
}
return false;
}
}
Для тестирования вам сначала нужно создать HttpClient
с использованием factory:
var httpClient = httpClientFactory.CreateClient("MyClient");
Затем запустите его через расширение:
if(!httpClient.ContainsDelegatingHandler<MyDelegatingHandler>())
{
throw new Exception(
"HttpClient with name 'MyClient' does not contain "
$"{nameof(MyDelegatingHandler)} delegating handler.");
}
Комментарии:
1. Всегда есть способ, и вы мне его показали! Спасибо за ваш ответ. Согласен, это довольно далеко от наилучшей практики, но в контексте тестирования я бы сказал, что это, вероятно, приемлемо. Я больше подумал об этом после того, как задал вопрос, и я думаю, что более подходящим подходом для меня было бы создать
DelegatingHandlerFactory
который можно подделать, и использовать это для создания регистрацииMyDelegatingHandler
в качестве обработчика сообщений. Спасибо за вашу помощь.2. @Klicker — я тоже собирался предложить это — но иногда люди против добавления «макетной работы» к своей загрузке. Так что это своего рода «2 зайца / 1 камень». Удачи!
3. Хотя, еще больше подумав об этом, я понимаю, что добавление фабрики позволит мне убедиться, что
MyDelegatingHandler
был создан, но не проверяет, был ли он назначен HttpClient. Хотя, возможно, это не лучшая практика, ваше решение дает мне абсолютную уверенность в примере кода в сообщении.4. @Klicker — абсолютно. После вызова
CreateClient
будет создан экземпляр делегирующего обработчика — но как вы проверяете, был ли он создан? Возможно, вы могли бы создать в своем обработчике поле статического события, которое могло бы запускаться из Ctor для вызова какого-либо делегата во время тестирования, но затем вы смешиваете тестовый код с производственным кодом. Я не знаю, как еще вы могли бы сделать это чисто.