Git Product home page Git Product logo

Comments (7)

martincostello avatar martincostello commented on June 24, 2024

I've seen a similar issue with service activation throwing the same exception, but I'm not sure whether or not that's from using IHttpClientFactory or not. It's happened probably a handful of times in the last few months. If my memory serves correctly, IOptions<T> was involved.

That said, looking at your code, using an IEnumerable<T> all the way through has the potential for performance issues if you have to keep re-walking the enumerator if it's backed by a lazy implementation. I'd suggest materialising it into an IList<T> near the top of the method using options.ToList() so that you're operating on a set of a fixed size, and then you can use Count rather than Count() and an indexer rather than ElementAt().

from httpclientfactory.

ceccomil avatar ceccomil commented on June 24, 2024

@martincostello Thanks for the answer.

I've seen a similar issue with service activation throwing the same exception

I'll have a look at the implementation of IOptions to see what could be wrong with it.

I'd suggest materialising it into an IList

I don't think it would be really different in my case since options is always of type KCAHttpClientOptions[] but, it won't cause any harm so I will follow your suggestion.

from httpclientfactory.

ceccomil avatar ceccomil commented on June 24, 2024

I did try a different implementation of the constructor

internal KCAHttpClients(IServiceCollection services, IEnumerable<IOptions<KCAHttpClientOptions>> options)

and I also did what @martincostello suggested...

try
{
    HttpClients = new Dictionary<string, IKCAHttpClient>();
    var optList = options.ToList();
    ....

But the error still happens. As I said before, It's not incredibly frequent, but it happens. The same middleware it's in use in apps deployed in Azure and in IIS 8.5 on-prem. Same random behavior. 🤷‍♂️

from httpclientfactory.

slang25 avatar slang25 commented on June 24, 2024

I would watch out for calling services.BuildServiceProvider(), you'll build your provider twice, which might be where some of the weirdness is coming from.

Remember that services.AddSingleton( and other overloads take a func that has the service provider, so try using that it this example.

Something like this maybe:

internal KCAHttpClients(IServiceCollection services, IEnumerable<KCAHttpClientOptions> options)
{
    try
    {
        HttpClients = new Dictionary<string, IKCAHttpClient>();
        
        //https://github.com/aspnet/HttpClientFactory/blob/master/src/Microsoft.Extensions.Http/DependencyInjection/HttpClientFactoryServiceCollectionExtensions.cs

        int optList = options as List<KCAHttpClientOptions> ?? options.ToList();
        foreach(var opt in optList)
        {
            //https://github.com/aspnet/HttpClientFactory/blob/master/src/Microsoft.Extensions.Http/DependencyInjection/HttpClientBuilderExtensions.cs
            services.Configure<HttpClientFactoryOptions>(opt.ClientName, httpClientConf => httpClientConf.HttpClientActions.Add((c) =>
            {
                c.BaseAddress = new Uri(opt.ClientBaseAddress);
                if (opt.PermanentHeaders != null)
                {
                    foreach (KCAHeader h in opt.PermanentHeaders)
                    {
                        c.DefaultRequestHeaders.Add(h.HeaderKey, h.HeaderValue);
                    }
                }
            }));

            IKCAHttpClient CreateKCAHttpClient(ServiceProvider sp)
            {
                var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
                var typedClientFactory = sp.GetRequiredService<ITypedHttpClientFactory<KCAHttpClient>>();

                var httpClient = httpClientFactory.CreateClient(opt.ClientName);
                var client = typedClientFactory.CreateClient(httpClient);
                HttpClients.Add(opt.ClientName, client);
                return client;
            }

            if (opt.RequiredToBeSingleton)
            {
                services.AddSingleton<IKCAHttpClient>(CreateKCAHttpClient);
            }
            else
            {
                services.AddTransient<IKCAHttpClient>(CreateKCAHttpClient);
            }
        }
    }
    catch (Exception ex)
    {
        //This is where the "collection changed" error is thrown
        Log.Logger.Error("Error during KCAHttpClients initialization!", ex);
        throw ex;
    }
}

from httpclientfactory.

rynowak avatar rynowak commented on June 24, 2024

Sorry I'm not sure I understand what's happening here. What are you trying to accomplish?

from httpclientfactory.

ceccomil avatar ceccomil commented on June 24, 2024

@rynowak Thanks for your reply. Basically, what I wanted to achieve was:

  1. Having a middleware which can be initialized with options (from 1 to N HttpClients configurations such as base addresses, headers settings, etc... )
  2. I wanted to have all the HttpClients in the collection created as ITypedHttpClientFactory

So, for example, let's assume a controller needs 3 different HttpClients with the same behavior but different configurations. I'll have the configuration of the middleware in the startup class, then in the controller's constructor signature, I'll only need to include the IClientsCollection and not all the typed clients required and most important I don't have to define different "ITypedClient(s)"

Btw, @slang25 pointed out something which I knew, the reason I was calling services.BuildServiceProvider() was because I couldn't get the required services otherwise and that was because of an error in my original implementation so... I've moved the "services.Configure(...." before initializing the ClientsCollection. I've also realized that for no reason I should have had to add each Client as single service ("services.Add...") since the collection itself has been already added by the time being.

Now my AddService extension looks like this below and everything is working like a charm.

public static IServiceCollection AddKCAHttpClientsTransient(this IServiceCollection services,
IEnumerable<IOptions<KCAHttpClientOptions>> options)
{
  services.ConfigureClientFactoryOptions(options);
  return services.AddTransient<IKCAHttpClients, KCAHttpClients>(sp =>
  {
    var httpClientFactory = sp.GetRequiredService<IHttpClientFactory>();
    var typedClientFactory = sp.GetRequiredService<ITypedHttpClientFactory<KCAHttpClient>>();

    return new KCAHttpClients(httpClientFactory, typedClientFactory, options);
  });
}

You can close the issue, which wasn't an HttpClientFactory issue afterall.
Many thanks.

from httpclientfactory.

rynowak avatar rynowak commented on June 24, 2024

Ok, glad you figured it out!

from httpclientfactory.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.