简体   繁体   中英

Service is not registered in the servicecollection

I do not understand why after i am adding a service to IServiceCollection , when i try to get back a reference to it using ServiceProvider the value is null.

Startup

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";
}

Why is the returned service null? I first thought that its because its Transient so it would be per request..so no request at that stage -> null.
But it still returns null when using AddSingleton too. Why is that service null?

PS What i need is :

public class TransientService{
public HttpClient sharedProperty;
}

public void ConfigureServices(IServiceCollection services)
{
  HttpClient client=new HttpClient();
  services.AddTransient( new TransientService{ sharedProperty=client});
}

I need a service that is transient but all its instances share a field ( HttpClient in my case)

PS 2 After i have changed from GetService to GetRequiredService i get the InvalidOperationException that the service was not registered.Why would that happen ? I have also changed from the to the non lambda insertion:
services.AddTransient() still with the same problem.

I can't reproduce the problem.

GetService will return null if the requested service hasn't been registered. If GetRequiredService was called, it would throw an exception.

There are two problems in this code.

First, this line :

services.AddTransient(y =>wrapper);

registers a factory function that returns the same HttpWrapper instance, not the HttpWrapper instance itself. That function will return whatever wrapper contains.

Second, AddTransient says this is a transient service and yet, the same instance will be returned every time.

If the same service is used every time, the registration should use AddSingleton :

services.AddSingleton<HttpWrapper>();

If a new instance is needed each time, it should be :

services.AddTransient<HttpWrapper>();

Test Code

I used this code to test the problem and can't reproduce it :

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);
    }
}

The class will be created without any issues. The only way for this to fail is for wrapper to be null. I use GetRequiredService to force an exception if no registration is found.

Typed Http Clients

From the comments it appears that the real issue is how to create a service that uses HttpClient properly. Reusing the same instance will improve performance as it keeps existing TCP connections in a connection pool. It still needs to be recycled periodically though to handle changing DNS addresses. Using a transient HttpClient is bad but so is using a single HttpClient instance.

The HttpClientFactory takes care of both concerns by pooling and recycling the HttpClientHandler instances that actually perform the HTTP requests for each HttpClient. The article Use HttpClientFactory to implement resilient HTTP requests explains how this works but long story short, simply adding :

services.AddHttpClient<ICatalogService, CatalogService>();

Will ensure that every instance of CatalogService will get a properly handled HttpClient . All CatalogService needs to do is accept an HttpClient in its constructor and use it in its methods :

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);
        ...
    }
}

The AddHttpClient extension method is available in the Microsoft.Extensions.Http package

Adapting this to the question, HttpWrapper should accept an HttpClient parameter in its constructor :

public class HttpWrapper 
{
    private readonly HttpClient _cl;
    public HttpWrapper(HttpClient client)
    {
        _cl=client;
    }

    public string TestUrl = "aaa";

}

And get registered with AddHttpClient :

services.AddHttpClient<HttpWrapper>();

After that, it can be resolved with :

var returned = prov.GetRequiredService<HttpWrapper>();

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM