Use Microsoft httpclient factory

I have…

I’m submitting a…

  • [ ] Regression (a behavior that stopped working in a new release)
  • [ ] Bug report
  • [ ] Performance issue
  • [ X] Documentation issue or request

Current behavior

In Squidex.ClientLibrary.SquidexOptions you provide the option to override IHttpClientFactory, which is a custom interface [Squidex.ClientLibrary.Configuration]

In ASP.NET Core there is an interface IHttpClient which uses the factory pattern to manage httpclients
see https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-6.0 and https://learn.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/implement-http-call-retries-exponential-backoff-polly

It also adds support for typed client to the di container like serviceClient.AddHttpClient()

Due to the client factory we get goodies like logging, retry, backoff etc.

My question for you is wether we can use the Microsoft variant of the interface, or if you have a working version of your interface that loops it to the microsoft one.

Expected behavior

I wanted to do this since a while now, but the code generator does not allow that:

I guess we could create a custom template for that (PR is welcome) :wink:

Shameless self promotion here, https://kaylumah.nl/2021/05/23/generate-csharp-client-for-openapi.html
have some experience with NSwag, I think the default here is that HttpClient gets injected via the constructor. Thus as I mention in the closing thoughts it works out ot the box via the model (need to do a write up on that still)

So, where do we go from here? I did not mention it in the article, but in every generated client, we need to inject System.Net.HttpClient , which means we can combine it with HttpClientFactory and all the options it provides. Alas, that is also a topic for another day.

When is the other day? :wink:

1 Like

I have found a way to adjust the template. I will provide a test branch soon.

I think https://github.com/RicoSuter/NSwag/issues/1097#issuecomment-463535334 these settings trigger the behavior.

The screenshot has a Client(Httpclient httpClient) {}

This means using https://www.nuget.org/packages/Microsoft.Extensions.Http/6.0.0 you can then register it. I guess this is what is already done in AppsClient etc.

Let me think if I can work around

/// <inheritdoc />
    public HttpClient CreateHttpClient(bool appendApi = true)
    {
      Uri baseUri = new Uri(this.Options.Url, UriKind.Absolute);
      if (appendApi)
        baseUri = new Uri(baseUri, "api/");
      HttpMessageHandler httpMessageHandler = this.CreateHttpMessageHandler();
      HttpClient httpClient = this.Options.ClientFactory.CreateHttpClient(httpMessageHandler) ?? new HttpClient(httpMessageHandler, false);
      httpClient.BaseAddress = baseUri;
      httpClient.Timeout = this.Options.HttpClientTimeout;
      this.Options.Configurator.Configure(httpClient);
      return httpClient;
    }

    private HttpMessageHandler CreateHttpMessageHandler()
    {
      HttpClientHandler httpClientHandler = new HttpClientHandler();
      this.Options.Configurator.Configure(httpClientHandler);
      AuthenticatingHttpMessageHandler httpMessageHandler = new AuthenticatingHttpMessageHandler(this.Options.Authenticator);
      httpMessageHandler.InnerHandler = (HttpMessageHandler) httpClientHandler;
      HttpMessageHandler inner = (HttpMessageHandler) httpMessageHandler;
      return this.Options.ClientFactory.CreateHttpMessageHandler(inner) ?? inner;
    }

If I can get AuthenticationgHttpMessageHandler to play nice with https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-6.0#outgoing-request-middleware I think I can make it work.

Let me get back to you on that

That was fast :slight_smile:
If I understand your design correctly I need to implement IHttpClientProvider to use the microsoft thingy and then configure authentication etc?

Yes, I would like to add authentication by default, but I am not sure if it a good idea, because the order of the message handlers really matter and I don’t want to destroy that.

Do you have a better idea?

Yeah you are right, the order is import. So I guess the StaticHttpClientProvider is the “example” code regarding registration order.

Not sure API wise people will understand that there are now two custom interfaces.

If you have a preview nuget package I can try (tomorrow) to make it work with the Microsoft package and circle back here

I have created a second project for the integration to IServiceCollection:

So if you can AddSquidexClient yourself you can make all customizations yourself.

It would be great if you can test that: Just clone the code and then add a reference to the project itself. Then you can easily make fixes if you find issues.

I have made a few changes, at least it resolves the client manager now. I have not tested it further.

Great, thank you, will test this tomorrow and get back to you :slight_smile:

1 Like

Fixed it locally for now [not sure you want me to commit stuff in your PR] but ) is missing on line 52 of SquidexClientLibraryServiceExtensions

Hi Sebastian,

I validated your solution and everything appears to be working as expected.
I have tried both manually creating ISquidexClientManager and using the extension methods.

For the manual one I used the new HttpClientProvider and the default StaticHttpClientProvider. The former I get logging like “Sending HTTP request GET …”, the latter I get no logging just as before.

As described in https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-6.0#logging I can get even more logging using

"System.Net.Http.HttpClient.SquidexClient.ClientHandler": "Trace"

So I am happy to report that with the current version I can use HttpClient.

I do have a few suggestions / comments

  1. SquidexClientLibraryServiceExtensions -> SetSquidexAuthenticatorAsPrimaryHandler is unused
  2. SquidexServiceOptions is registered in the DI container, so AuthenticatingHttpMessageHandler could be as well [line 162], but I think you need this ne because of ConfigureHttpMessageHandlerBuilder instead of AddHttpMessageHandler<>() https://learn.microsoft.com/en-us/aspnet/core/fundamentals/http-requests?view=aspnetcore-6.0#outgoing-request-middleware
  3. HttpClientProvider is internal, so if I want to deviate from the default AddSquidex (say for example a custom factory with additional logging or polly) I also need a own HttpClientProvider
  4. Not clear to me what HttpClientProvider.Return() is meant to do since it disposes the client?
  5. SquidexClientLibraryServiceExtensions line 33 could use a Action<SquidexServiceOptions>? configure = null
  6. I think AddSquidexClient could be split into two methods, one where the main client is configured and one that adds all the typed client like services.AddSingleton(
    c => c.GetRequiredService().CreateAppsClient()); that way if I choose to do a custom registration of ISquidexClientManager I still get the reusable register client method

Also just noticed, Authenticator : IAuthenticator uses the old squidex clientfactory,

so there logging would still be missing. But not sure if that is an issue, or if there are other places that should now use the ClientProvider instead of the ClientFactory

Yes, I added it in case someone needs it.

I don’t see any value in that.

Can change that.

It is a typical pattern in pooling.

Usually you should define your settings here, therefore I decided not to add = null

I can just use TryRegister instead, then you can just override everything.

Will be changed.

I have implemented your feedback, would you give it another try?

just went through the chnges in the PR looks good to me :slight_smile:
Will verify tomorrow on my work PC, but I expect everything to be good to go now.

Thanks for the hard work :slight_smile: