MartinT
MartinT

Reputation: 19

unable to make httpclientfactory working from a .net framework 4.7.2 winform project

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?

Upvotes: 0

Views: 3278

Answers (1)

Peter Csala
Peter Csala

Reputation: 22829

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

Upvotes: 3

Related Questions