3 minute read

As an integration developer, one of the most common requirements I deal with is consuming APIs to integrate with different systems. There is still a wide variety of API types from older SOAP services to modern GraphQL and gRPC services. The most common style of API I use currently is RESTful HTTP APIs documented using Swagger/OpenAPI documents.

Code Sample

For this post, I’ve prepared a sample solution which demonstrates consuming an API as a service reference and then injecting the client into a worker service.

What is OpenAPI?

As a brief history of OpenAPI, it began as an open-source project known as Swagger in 2010. This project was eventually taken over by SmartBear in 2015 with the specification of the Swagger project split out into a new open-source initiative known as OpenAPI backed by various tech organisations. Today, both Swagger and OpenAPI are used synonymously to refer to the documentation of RESTful APIs.

OpenAPI is a specification for defining the functionality of a RESTful API including:

  • operations available
  • data structures schemas
  • authentication
  • versioning
  • etc

All of this is to make developing and consuming these APIs easier. An OpenAPI document is the equivalent of a WSDL for documenting the operations and data structures of a SOAP API.

Adding a Service Reference

This post is focused on consuming an API using the service references functionality in Visual Studio to generate a proxy client as it is my preferred option and works great in most cases. However, there are other ways of consuming APIs such as using the HttpClient manually or RestSharp which I use if an OpenAPI document is unavailable or if some operations are particularly complex.

image1

Service References are a long-standing feature of Visual Studio. Depending on the type of project, the method of managing and adding service references varies, however, the above steps generally work across most project types. To manage existing references, close the add service reference window.

image2

As part of adding a reference to an OpenAPI service, we need to point to where the definition is hosted, whether that be a URL or a local file. Although it’s not demonstrated above, the class and namespace of the client can be specified to organise the autogenerated code.

openapi: 3.0.0
info:
  title: Cat Facts API
  version: '1.0'
servers: 
  - url: 'https://catfact.ninja/'
paths:
  /breeds:
    # OpenAPI doc continues

The OpenAPI specification allows for defining API servers and base URLs to allow an API doc to not only define the functionality but also where the API is hosted. If the service reference is added with a servers section, the generated client will include a BaseUrl property which defaults to the server property instead of the base URL of the HttpClient. If the servers section is missing, the constructor of the client requires the base URL to be provided. The /UseBaseUrl:false argument has been added so that the BaseAddress of the HttpClient is used instead which I prefer as it fits better with the Microsoft.Extensions.Http patterns.

Injecting the Proxy Client

IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices(services =>
    {
      // Register other services

        services.AddHttpClient<ICatFactsClient, CatFactsClient>((sp, client) =>
        {
            var options = sp.GetRequiredService<IOptions<MyOptions>>();

            client.BaseAddress = options.Value.CatFactsBaseUrl;
        });
    })

With the API client now being generated, the next step is to register the client with dependency injection to start making use of the client. As mentioned in adding a service reference I prefer to make use of Microsoft.Extensions.Http due to it being a well-established pattern as well as many other benefits including:

  • Centralising the definition and naming of HttpClients for different services/OpenAPI clients
  • Automatically handling the lifetime of the HttpClient including pooling
  • Readily available Polly extensions to improve resiliency
public class Worker : BackgroundService
{
    private readonly ICatFactsClient _catFactsClient;
    private readonly ILogger<Worker> _logger;

    public Worker(ILogger<Worker> logger, ICatFactsClient catFactsClient)
    {
        _catFactsClient = catFactsClient;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            var fact = await _catFactsClient.GetRandomFactAsync(null);
            _logger.LogInformation("Did you know {fact}", fact.Fact.ToLower());
            await Task.Delay(10000, stoppingToken);
        }
    }
}

The registered API client can then be injected into your services like above. In the sample solution the /GenerateClientInterfaces:true argument has also been added so that an interface can be injected instead of a concrete class to improve unit testing by being able to mock the interface.

Sum up

For this post, we’ve walked through the steps of adding an OpenAPI service reference as well as how to consume and use that API. As mentioned, there are many other ways of consuming an API, however, this approach keeps the custom code to a minimum which also makes getting started quicker. The dependency injection of the HttpClient has also been kept standard to take full advantage of Microsoft.Extensions.Http. As always, I hope this has been useful.

References

Comments