我不明白为什么要向IServiceCollection
添加服务后,尝试使用ServiceProvider
来获取对它的引用时该值为空。
启动
public void ConfigureServices(IServiceCollection services)
{
Startup.ConfigureServicesForDebugging?.Invoke(services);
try
{
services.AddTransient<HttpWrapper>();
ServiceProvider prov = services.BuildServiceProvider();
HttpWrapper returned=prov.GetRequiredService<HttpWrapper>();
if (returned == null)
{
Console.WriteLine("is null");
}
else Console.WriteLine(returned.Test);
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
HttpWrapper
public class HttpWrapper
{
public HttpClient cl = new HttpClient();
public string Test = "aaa";
}
为什么返回的服务为空?我首先想到的是它的原因是它的Transient
是按请求的。.so在那个阶段没有请求-> null。
但是当使用AddSingleton
时,它仍然返回null。
为什么该服务为空?
PS 我需要的是:
public class TransientService{
public HttpClient sharedProperty;
}
public void ConfigureServices(IServiceCollection services)
{
HttpClient client=new HttpClient();
services.AddTransient( new TransientService{ sharedProperty=client});
}
我需要一个瞬态服务,但其所有实例共享一个字段(在我的情况下为HttpClient
)
PS 2 当我从GetService
更改为GetRequiredService
后,我得到InvalidOperationException
该服务未注册。为什么会这样?我还从插入更改为非lambda插入:
services.AddTransient()仍然存在相同的问题。
答案 0 :(得分:3)
我无法重现问题。
如果尚未注册请求的服务, GetService
将返回null。如果调用GetRequiredService
,它将引发异常。
此代码中有两个问题。
首先,这一行:
services.AddTransient(y =>wrapper);
注册一个工厂函数,该函数返回相同的HttpWrapper实例,而不是HttpWrapper实例本身。该函数将返回wrapper
包含的内容。
第二,AddTransient
说这是一个临时服务,但是每次都会返回相同的实例。
如果每次都使用相同的服务,则注册应使用AddSingleton
:
services.AddSingleton<HttpWrapper>();
如果每次都需要一个 new 实例,则该实例应为:
services.AddTransient<HttpWrapper>();
测试代码
我使用此代码测试了问题,无法重现:
static void Main(string[] args)
{
IServiceCollection services = new ServiceCollection();
try
{
HttpWrapper wrapper = new HttpWrapper();
services.AddTransient(provider => wrapper);
ServiceProvider prov = services.BuildServiceProvider();
HttpWrapper returned = prov.GetRequiredService<HttpWrapper>();
Console.WriteLine("No problem");
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
该类将被创建而没有任何问题。失败的唯一方法是使wrapper
为null。如果找不到注册,我使用GetRequiredService
强制例外。
键入的Http客户端
从注释中可以看出, real 问题是如何创建正确使用HttpClient的服务。重用同一实例将提高性能,因为它将现有的TCP连接保留在连接池中。尽管它仍然需要定期回收以处理不断变化的DNS地址。使用临时HttpClient不好,但是使用单个HttpClient实例也是如此。
HttpClientFactory通过池化并回收实际上为每个HttpClient执行HTTP请求的 HttpClientHandler 实例来解决这两个问题。文章Use HttpClientFactory to implement resilient HTTP requests解释了这是如何工作的,但总之,总之,只需添加:
services.AddHttpClient<ICatalogService, CatalogService>();
将确保CatalogService
的每个实例都将得到正确处理的HttpClient
。 CatalogService
要做的就是在其构造函数中接受HttpClient
并在其方法中使用它:
public class CatalogService : ICatalogService
{
private readonly HttpClient _httpClient;
public CatalogService(HttpClient httpClient)
{
_httpClient = httpClient;
}
public async Task<Catalog> GetCatalogItems(int page, int take,
int? brand, int? type)
{
....
var responseString = await _httpClient.GetStringAsync(uri);
...
}
}
Microsoft.Extensions.Http软件包中提供了AddHttpClient
扩展方法
对此进行调整,HttpWrapper
应该在其构造函数中接受HttpClient参数:
public class HttpWrapper
{
private readonly HttpClient _cl;
public HttpWrapper(HttpClient client)
{
_cl=client;
}
public string TestUrl = "aaa";
}
并在AddHttpClient
中注册:
services.AddHttpClient<HttpWrapper>();
之后,可以使用:
解决var returned = prov.GetRequiredService<HttpWrapper>();