How-To: Configure HttpClient with Custom Endpoint Options

It's often necessary to include an API key alongside requests to a web API. This can be done by adding a header to the request. The steps below will show you how to easily specify custom options, such as an access token, when adding an endpoint. You can then configure the associated HttpClient from these options.

Pre-requisites

Step-by-step

Important

This guide assumes you used the template wizard or dotnet new unoapp to create your solution. If not, it is recommended that you follow the Creating an application with Uno.Extensions documentation to create an application from the template.

1. Preparing for custom endpoint options

  • Create a new class called CustomEndpointOptions in the shared project. This class extends EndpointOptions to allow you to specify custom options for the endpoint

    public class CustomEndpointOptions : EndpointOptions
    {
        public string ApiKey { get; set; }
    }
    
  • In this example, we intend to add an access token to the request header. The ApiKey property will be used to store the token.

  • The EndpointOptions class is a base class that provides a Url property. This property is used to specify the URL of the endpoint.

  • Subclassing EndpointOptions will allow you to configure the HttpClient associated with the endpoint — all from a single configuration section.

2. Defining the endpoint

  • Add Http to the <UnoFeatures> property in the Class Library (.csproj) file.

    <UnoFeatures>
        Material;
        Extensions;
    +   Http;
        Toolkit;
        MVUX;
    </UnoFeatures>
    
  • Enable HTTP by calling the UseHttp() method to register a HTTP client with the IHostBuilder:

    protected override void OnLaunched(LaunchActivatedEventArgs args)
    {
        var appBuilder = this.CreateBuilder(args)
            .Configure(hostBuilder =>
            {
                hostBuilder.UseHttp();
            });
        ...
    }
    
  • The UseHttp() extension method accepts a callback for configuring the HTTP services as its argument. We will use this callback to register endpoints with the service collection.

    protected override void OnLaunched(LaunchActivatedEventArgs args)
    {
        var appBuilder = this.CreateBuilder(args)
            .Configure(hostBuilder =>
            {
                hostBuilder.UseHttp((ctx, services) => {
                    // Register endpoints here
                });
            });
        ...
    }
    
    • ctx represents the HostBuilderContext. This can be used to access the configuration of the host.

    • services is an instance of IServiceCollection. This is used to register services with the host.

  • An extension method AddClientWithEndpoint<TInterface, TEndpoint>() is included which allows specifying custom endpoint options when adding a typed client to the service collection.

    • Use this extension method to register a typed client with the service collection and specify custom endpoint options of the type CustomEndpointOptions.

      protected override void OnLaunched(LaunchActivatedEventArgs args)
      {
          var appBuilder = this.CreateBuilder(args)
              .Configure(hostBuilder =>
              {
                  hostBuilder.UseHttp((ctx, services) => {
                      services.AddClientWithEndpoint<HttpEndpointsOneViewModel, CustomEndpointOptions>();
                  });
              });
          ...
      }
      
      • Type parameter TInterface is the service or view model interface that will be used to access the endpoint.

      • Type parameter TEndpoint is the type of the custom endpoint options you define. This type must be a subclass of EndpointOptions.

  • The extension method above allows you to pass arguments for various details such as the HostBuilderContext, an endpoint name (which corresponds to a configuration section), and a callback for configuring the HttpClient associated with this endpoint.

    • Add this information to the method call as shown below:

      protected override void OnLaunched(LaunchActivatedEventArgs args)
      {
          var appBuilder = this.CreateBuilder(args)
              .Configure(hostBuilder =>
              {
                  hostBuilder.UseHttp((ctx, services) => {
                      services.AddClientWithEndpoint<HttpEndpointsOneViewModel, CustomEndpointOptions>(
                          ctx,
                          name: "HttpDummyJsonEndpoint",
                          configure: (builder, options) =>
                          {
                              builder.ConfigureHttpClient(client =>
                              {
                                      // Configure the HttpClient here
                              });
                          }
                      );
                  });
              });
          ...
      }
      
      • We assigned the endpoint a name of HttpDummyJsonEndpoint. This name corresponds to a configuration section in the appsettings.json file. We will add this section in the next section.

      • The configure callback is used to configure the HttpClient associated with the endpoint.

        Tip

        This callback is optional. If you do not need to configure the HttpClient, you can omit this callback.

        • Notice that the callback accepts two arguments: builder and options. options is an instance of CustomEndpointOptions which we defined earlier. We will use this to access the custom options you defined in the previous section.

        • Add an ApiKey to the request headers on the client using the ConfigureHttpClient method.

          protected override void OnLaunched(LaunchActivatedEventArgs args)
          {
              var appBuilder = this.CreateBuilder(args)
                  .Configure(hostBuilder =>
                  {
                      hostBuilder.UseHttp((ctx, services) => {
                          services.AddClientWithEndpoint<HttpEndpointsOneViewModel, CustomEndpointOptions>(
                              ctx,
                              name: "HttpDummyJsonEndpoint",
                              configure: (builder, options) =>
                              {
                                  builder.ConfigureHttpClient(client =>
                                  {
                                      if (options?.ApiKey is not null)
                                      {
                                          client.DefaultRequestHeaders.Add("ApiKey", options.ApiKey);
                                      }
                                  });
                              }
                          );
                      });
                  });
              ...
          }
          
          • The ApiKey header is added to the HttpClient using the DefaultRequestHeaders property.

          • The value of the header is set to the ApiKey property of the CustomEndpointOptions instance.

  • We have successfully registered an endpoint with the service collection. We will now add a configuration section for this endpoint.

3. Adding a configuration section for the endpoint

  • Open the appsettings.json file and add a configuration section for the endpoint:

    {
        "HttpDummyJsonEndpoint": {
            "Url": "https://DummyJson.com",
            "UseNativeHandler": true,
            "ApiKey":  "FakeApiKey"
        }
    }
    
    • The name of the configuration section must match the name of the endpoint you specified in the previous section.

    • The Url property is used to specify the URL of the endpoint.

    • The ApiKey property is used to specify the API key that will be added to the request header.

    • The UseNativeHandler property is used to explicitly specify whether to use the native HTTP handler.

4. Using the endpoint

  • We will now use the endpoint in a view model. Create and a HttpEndpointsOneViewModel class with a constructor that accepts an instance of HttpClient like so:

    public class HttpEndpointsOneViewModel
    {
        private readonly HttpClient _client;
    
        public string? Data { get; internal set;}
    
        public HttpEndpointsOneViewModel(HttpClient client)
        {
            _client = client;
        }
    
        public async Task Load()
        {
            Data = await _client.GetStringAsync("products");
        }
    }
    
    • The HttpClient instance is injected into the view model. This instance is configured with the options we specified in the previous sections.
  • All the details of IHttpClientFactory are abstracted away from the view model. The view model can simply use this HttpClient instance to make requests to the endpoint. The instance can have a managed lifecycle, while a significant amount of ceremony and unintuitive workarounds are avoided.

See also