5

I have NET Core 2 Web API application. During the process i have to invoke Client A's API to get some data. So i am using HttpClient to invoke it. Client A also requires me to pass userid and password in header.

So instead of directly injecting HttpClient i have wrapper around HttpClient something like below

public class ClientA : IClientA
{
    private readonly HttpClient _httpClient;

    public ClientA(HttpClient httpClient)
    {
        _httpClient = httpClient;
    }

    public async Task<string> GetData()
    {
        return await _httpClient.HttpGetAsync("someurl");
    }
 }

Then use ClientA in Service

  public class MyService :IMyService
  {
      private readonly IClientA _clientA

      public MyService(IClientA clientA)
      {
           _clientA= clientA
      }

      public void DoSomethig()
      {
          _clientA.GetData();
      }           
  }

Then i am registering everything in Startup.cs

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddScoped<IMyService, MyService>();           

        services.AddScoped(factory =>
        {
            Func<Task<IClientA>> provider = async () =>
            {
                using (var dbContext = factory.GetService<MyDBContext>())
                {
                    // get userid and password from database here

                    var httpClient = new HttpClient();
                    httpClient.DefaultRequestHeaders.Add("UserId",userid);
                    httpClient.DefaultRequestHeaders.Add("Password",password);
                    return new ClientA(httpClient);
                }
            };

            return provider;
        });
    }

However i am getting error

System.InvalidOperationException: Unable to resolve service for type 'System.Net.Http.HttpClient' while attempting to activate 'XXXXXXXXX.ClientA'. at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, ISet1 callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound) at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, ISet1 callSiteChain)

remaining exception removed for brevity

Notice that during registration i am newing-up instance of HttpClient and passing it to ClientA class because i have to set userid and password.

To get rid the above error I can register HttpClient with UserID and Password with DI framework and i guess that would work.

However, in that case, if have another client, ClientB, that takes HttpClient then DI framework will inject same httpclient that has userid and password. and that will create security issue because ClientB would see ClientA's credentials in request headers.

   public class ClientB(HttpClient client)
   {
        private readonly _httpClient;
        public class ClientB(HttpClient client)
        {
           _httpClient = client;
        }

        public string CallClientB(string url)
        {
            // here ClientB will receive ClientA's credentials
            return await _httpClient.HttpGetAsync(url);
        }
   }
LP13
  • 30,567
  • 53
  • 217
  • 400
  • 1
    Something looks off about that registration. You are registering the function used to create the client, not the client itself. – Nkosi Mar 05 '18 at 23:33
  • yeeeks.. @Nkosi you are correct..copy paste issue on my side. – LP13 Mar 05 '18 at 23:39
  • There is no need to be injecting HttpClient. It's a utility class. You should be manually creating instances as needed. If anything, inject the parameters needed by HttpClient using the options pattern e.g. `IOptions`. – Brad Mar 05 '18 at 23:45

2 Answers2

6

You don't want to be instantiating httpclient in a scoped context, that is creating an instance of httpclient per request which is not the recommended usage pattern for that class. (won't scale well). https://aspnetmonsters.com/2016/08/2016-08-27-httpclientwrong/

Create a singleton with a separate interface per customer (assuming a small number of customers) - possibly with a code access security demand in its implementation, depending on your setup (identity impersonation enabled?)

That will a) scale well b) only run once per customer per application instance/startup and c) enforce an access check for usage.

Also, this answer is connected and relevant to your header requirements - HttpClient single instance with different authentication headers

Nathan
  • 6,095
  • 2
  • 35
  • 61
  • Registering singleton instance of HttpClient may be okay, but in case if you have to use Request and Response Http headers, then you have keep clearing Http headers after every request/response. Otherwise they will get cached since HttpClient in singlton – LP13 Feb 18 '19 at 20:05
  • @LP13 You can have a singleton and then use the SendAsync method with a request which specifically deals with the required headers (keeps the headers separate/decoupled from the singleton). eg: HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Delete, url); request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", userSpecifcAccessToken); HttpResponseMessage response = await client.SendAsync(request); (client being a singleton/static HttpClient instance) – Nathan Feb 19 '19 at 11:43
-5

resolved my issue

   services.AddScoped<IClientA>(factory =>
    {            
            var dbContext = factory.GetService<MyDBContext>();                
                // get userid and password from database here

                var httpClient = new HttpClient();
                httpClient.DefaultRequestHeaders.Add("UserId",userid);
                httpClient.DefaultRequestHeaders.Add("Password",password);
                return new ClientA(httpClient);
    });
LP13
  • 30,567
  • 53
  • 217
  • 400
  • its not the same code. Original code was retuning the function and this one actually returns IClientA – LP13 Mar 06 '18 at 04:50
  • I thought for developers code is more explanatory and than bunch of sentences :) i will keep that in mind – LP13 Apr 25 '18 at 15:38
  • Due to being a scoped registration, this code is still instantiating a http client per user request, which means it will fail under load. (see link in my answer). – Nathan Sep 28 '19 at 06:20
  • This is a serious antipattern and it will destabilize your application if just enough load happens to it! It may work if your application does not handle many requests, but nobody should use this solution if their application handles more than that. – Ravior Jan 14 '20 at 06:39
  • 3
    Pls don't create HttpClient all the type, use IHttpClientFactory to do so. – Daniel Botero Correa Mar 21 '20 at 13:29