2

i read about everything i could find about HttpClientFactory and after a few days struggling with this, i am about to give up, but taking a chance here in case anyone could help. I am just trying to implement HttpClientFactory in my .net 4.7.2 Winforms app for a rest-api client.

I tried implementing both Typed and Named client, but i am getting a null reference each time i am trying to instantiate it in my code. SO here what i did so far:

For the Typed Client, i created a class:

Imports System.Net.Http
Imports Microsoft.Extensions.Http
Imports Microsoft.Extensions.DependencyInjection

Public Class TypedCustomHTTPClient

    Public Property _Client() As HttpClient
    Public Sub New(ByVal httpClient As HttpClient)
        'httpClient.BaseAddress = New Uri("https://api.google.com/")
        httpClient.DefaultRequestHeaders.Add("Accept", "application/json")
        httpClient.DefaultRequestHeaders.Add("User-Agent", "HttpClientFactory-Sample")
        _Client = httpClient

    End Sub


End Class

then my Project Main Sub i am registering my Typed Client an also a Named client (i am not sure that i am doing it the correct way)

Imports Microsoft.Extensions.DependencyInjection
Imports Microsoft.Extensions.Hosting
Imports Microsoft.Extensions.Http
Imports Polly


Module MainModule


    Public Sub Main()


        Dim seviceColl As New ServiceCollection

        '
        
        '--------Registering and injecting HttpClients (by Name)
        seviceColl.AddHttpClient("s2sfHTTPClient", Sub(c)
                                                            'c.BaseAddress = New Uri("https://api.google.com/")
                                                            c.DefaultRequestHeaders.Add("Accept", "application/json")
                                                        End Sub).AddPolicyHandler(PolHolder.httpRetryWithReauthorizationPolicy())
        seviceColl.AddHttpClient("GitHub", Sub(httpClient)
                                                    httpClient.BaseAddress = New Uri("https://api.github.com/")

                                                    ' using Microsoft.Net.Http.Headers;
                                                    ' The GitHub API requires two headers.
                                                    ''httpClient.DefaultRequestHeaders.Add(HeaderNames.Accept, "application/vnd.github.v3+json")
                                                    ''httpClient.DefaultRequestHeaders.Add(HeaderNames.UserAgent, "HttpRequestsSample")
                                                End Sub)


       'Registering and injecting HttpClients (by Type)

        seviceColl.AddHttpClient(Of TypedCustomHTTPClient)().SetHandlerLifetime(TimeSpan.FromMinutes(5)).AddPolicyHandler(PolHolder.httpRetryWithReauthorizationPolicy()) 'Set lifetime to five minutes
       
        'Building Service Provider
        Dim serviceprovider = servicecoll.BuildServiceProvider
        '
        '
        '
        Application.Run(New Form1()) ''//Use your main form here
    End Sub

End Module

when i try to either use the typed client or the named client (with the .CreateClient method as it should) i am getting a Null reference error on the CreateClient Line.

  Private Property _httpClientFactory As IHttpClientFactory
Public Function TestQuery2(ByVal soqlQuery As String) As String
        Dim customclient = _httpClientFactory.CreateClient("s2sfHTTPClient")
        'Using customclient._Client '= New HttpClient()
        '
        Dim restRequest As String = InstanceUrl + API_ENDPOINT & "query/?q=" & soqlQuery
            Dim request = New HttpRequestMessage(HttpMethod.[Get], restRequest)
            request.Headers.Add("Authorization", "Bearer " & AuthToken)
            request.Headers.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))
            request.Headers.Add("X-PrettyPrint", "1")
        Dim response = customclient.SendAsync(request).Result
        Return response.Content.ReadAsStringAsync().Result
        'End Using
    End Function

Any idea? what am i doing wrong?

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
MartinT
  • 39
  • 7
  • If you receive a NullReferenceException at the CreateClient line that means the IHttpClientFactory is null. – Peter Csala Sep 23 '22 at 11:01
  • Based on the shared code I can't spot where do you call BuildServiceProvider on the ServiceCollection. – Peter Csala Sep 23 '22 at 11:03
  • 2
    @PeterCsala Thanks for the reply. I figured that out too shortly after posting, but even if i add the line .BuildServiceProvider to my Main Sub (see updated code in my Question), i cant seem to get a reference to the HttpclientFactory i am creating, from anywhere else in my code. Why is that? I even tried to use a SimpleInjector container , but still no luck. How can i get a reference to my httpclientfactory i an creating from the Main sub? All the documentation found on the Net about this says that the above should work, but all the infos found mainly refers to .net core, not full framework – MartinT Sep 26 '22 at 10:45

1 Answers1

2

In order to be able to use IHttpClientFactory in a .NET Framework console application you need the following things:

(I haven't used Visual Basic for long time that's why I share the sample code in C#.)

using System;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;

class Program
{
    private static readonly ServiceProvider serviceProvider;

    static Program()
    {
        serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
    }

    public static void Main(string[] args)
    {
        var factory = serviceProvider.GetService<IHttpClientFactory>();
        Console.WriteLine(factory == null); //False
    }
}
  • We create a ServiceCollection and we call the AddHttpClient extension method on it
    • It registers the DefaultHttpClientFactory as singleton for IHttpClientFactory
  • We build a serviceProvider from the serviceCollection
  • We retrieve the registered IHttpClientFactory from the serviceProvider

If you prefer to use SimpleInjector then the code should look like this:

using System;
using System.Net.Http;
using Microsoft.Extensions.DependencyInjection;
using SimpleInjector;

class Program
{
    private static readonly Container container = new Container();

    static Program()
    {
        var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
        container.RegisterInstance(serviceProvider.GetService<IHttpClientFactory>());

        //These are not mandatory calls but highly recommended
        //container.ContainerScope.RegisterForDisposal(serviceProvider);
        //container.Verify();
    }

    public static void Main(string[] args)
    {
        var factory = container.GetInstance<IHttpClientFactory>();
        Console.WriteLine(factory == null); //False
    }
}
  • Here we retrieve the registered IHttpClientFactory implementation from the MS DI
  • Then we re-register it inside the SimpleInjector's container

UPDATE #1 Using named client

class Program
{
    private static readonly Container container = new Container();
    private const string ClientName = "namedClient";
    static Program()
    {
        var serviceCollection = new ServiceCollection().AddHttpClient();
        serviceCollection.AddHttpClient(ClientName, c => c.BaseAddress = new Uri("https://httpstat.us/"));
        var serviceProvider = serviceCollection.BuildServiceProvider();
        container.RegisterInstance(serviceProvider.GetService<IHttpClientFactory>());
    }

    public static void Main()
    {
        var factory = container.GetInstance<IHttpClientFactory>();
        var client = factory.CreateClient(ClientName);
        Console.WriteLine( client.BaseAddress); // https://httpstat.us/
    }
}

UPDATE #2 Using typed client

class Program
{
    private static readonly Container container = new Container();
    private const string ClientName = "namedClient";

    static Program()
    {
        var serviceCollection = new ServiceCollection().AddHttpClient();
        serviceCollection.AddHttpClient(ClientName, c => c.BaseAddress = new Uri("https://httpstat.us/"));
        serviceCollection.AddHttpClient<ITestClient, TestClient>();
        var serviceProvider = serviceCollection.BuildServiceProvider();

        container.RegisterInstance(serviceProvider.GetService<IHttpClientFactory>());
        container.RegisterInstance(serviceProvider.GetService<ITypedHttpClientFactory<TestClient>>());
    }

    public static void Main(string[] args)
    {
        var namedFactory = container.GetInstance<IHttpClientFactory>();
        var typedFactory = container.GetInstance<ITypedHttpClientFactory<TestClient>>();
        var client = typedFactory.CreateClient(namedFactory.CreateClient(ClientName));
        Console.WriteLine(client == null); // False
    }
}

Please note that the type parameter of ITypedHttpClientFactory is the concrete type (TestClient), not the interface (ITestClient)


UPDATE #3

I just need to keep a reference to the container to retrieve the factory later from anywhere in the code and call the CreateClient method which work fine.

This Dependency Injection package was designed for (and as a part of) ASP.NET Core. A .NET Framework Console/WinForms application can't really harvest its true power.

Both GetService and GetRequiredService calls are acting as service locator rather than as dependency injector. ASP.NET Core can use this library to inject dependencies either via constructor or via property.

The only thing I don't understand is why in your code, for the Typed client example, you also make use of the NamedClient and its CreateClient method?

That's a good question. The typedFactory.CreateClient call anticipates a HttpClient instance. If you would pass a brand new instance

var client = typedFactory.CreateClient(new HttpClient()); 
//OR
var client = namedFactory.CreateClient(); //without specifying a name       

then the pre-configured BaseAddress (, c => c.BaseAddress = new Uri("https://httpstat.us/"));) won't be available there.

So, if you have a pre-configured named client then you can retrieve it and used it to create a typed client with that. (The end result will a named typed client.)

Peter Csala
  • 17,736
  • 16
  • 35
  • 75
  • i have seen this code example somewhere, maybe another post you made on Stack, but it doesnt answer the problems\question pertaining to my scenario where from the Main Subi want to register one custom Typed and One Custom Named HttpClient (using the AddHttpClient method), and cannot seem to be able to get a non-null reference to the factory i am building from my MAIn procedure. The code you are providing just do exactly what you describe (register default HttpclientFactory) and then later you retrieve it using GetService, but not help with what i want to do unfortunately – MartinT Oct 03 '22 at 19:24
  • @MartinT I've updated my post to include two examples to show how to create a named and a typed client. Are these covering your use case? – Peter Csala Oct 04 '22 at 06:49
  • Thank you so much for the code. I ended up using only Named Client. I just need to keep a reference to the container to retrieve the factory later from anywhere anywhere in the code and call the Createclient method which work fine. The only thing i dont understand is why in your code, for the Typed client example, you also make use of the NamedClient and its CreateClient method? in the following link i used to try learning all this DI and httpclientfactory concept, it says: "The typed client can be injected and consumed directly:" (comparing their code and your code confuses me . – MartinT Oct 07 '22 at 11:01
  • here is the link i was refering to: [link] (https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-6.0) _italic_ **bold** – MartinT Oct 07 '22 at 11:01
  • @MartinT I've amended my post to reflect on your questions. Please check it! – Peter Csala Oct 07 '22 at 11:50
  • 1
    thank you Peter! It makes more sense to me now. Your code exemple were really helpful to understand how to implement this in full framework app. I learned a lot again! I also Read Your other Stack post about implementing Token refresh with Polly and learned a lot from it (was able to implent it in my app) – MartinT Oct 08 '22 at 12:41
  • @MartinT I'm glad to read that my posts were useful. :) If you want to support my work, please consider to upvote my other posts as well. – Peter Csala Oct 08 '22 at 13:36