Skip to content

2 Consume an HTTP API

confidia edited this page Dec 1, 2023 · 2 revisions

So you have to connect to an API in your .NET 6 application? Let's walk through how to use this package to make your life (and those who follow in your footsteps) a little easier.

Check for existing support

Step 1 before you dive into code, is to check that your API doesn't already have a supported .NET client! Don't rewrite an API client if your supplier already supports one.

Follow the samples

If you're a copy-paste kind of person, head over to the samples where I have built out an API Server and Client that interacts with some tasty recipes. It may help to have this open anyway, but please note it's just a silly sample API, don't blindly copy this into production there are definitely other things you will want to do in your Program.cs.

Create a new project

If there's no .NET support or this is a new API you have created, we need to create a new .NET client for it. Create a new class library project in visual studio. As a suggested convention, the project namespace should be something like NameOfApi.ApiClient. It's important you keep this project self-contained and not mingled with your other application code. This way you will be able to distribute / reuse it if necessary. I'd suggest any client you create should be deployed into nuget, whether public or private is up to you.

Install package

Install the nuget package:

Confidia.ApiAbstractions.Http

Create your client interface

Create an interface for how you will call your API. Your request and response models should be shared by your server side, if you control it. This may mean you need to also publish your models as a separate package. See below from our Recipe sample:

public interface IRecipeApiClient
{
    Task<IApiResponse> GetAllAsync();
    Task<IApiResponse> GetAsync(int id);
    Task<IApiResponse> AddAsync(RecipeRequest request);
    Task<IApiResponse> UpdateAsync(RecipeRequest request);
    Task<IApiResponse> DeleteAsync(int id);
}

Implement the interface

Now you should implement the interface in a class inheriting from HttpApiClientBase. See below from the sample project (since we only care about JSON we are inheriting the DefaultJsonHttpApiClient. Ensure you override the ApiIdentifier as this is used by IConfiguration to apply the correct config. Don't be too overwhelmed by this, we'll go into detail in the following pages. For now maybe just copy a GET request:

public class RecipeApiClient : DefaultJsonHttpApiClient, IRecipeApiClient
{
    private const string BasePath = "/api/recipes";

    public RecipeApiClient(
        ILogger<RecipeApiClient> logger, 
        HttpClient httpClient, 
        IServiceProvider serviceProvider) : 
        base(logger, httpClient, serviceProvider)
    {
    }

    public override string ApiIdentifier => "RecipeApi";

    public async Task<IApiResponse> GetAllAsync()
    {
        HttpApiGetRequest request = CreateGetRequest<RecipeListResponse>(BasePath);

        return await SendAsync(request).ConfigureAwait(false);
    }

    public async Task<IApiResponse> GetAsync(int id)
    {
        HttpApiGetRequest request = CreateGetRequest<RecipeResponse>($"{BasePath}/{id}");

        return await SendAsync(request).ConfigureAwait(false);
    }

    public async Task<IApiResponse> AddAsync(RecipeRequest recipeRequest)
    {
        HttpApiPostRequest request = CreatePostRequest<RecipeResponse>($"{BasePath}", recipeRequest);

        return await SendAsync(request).ConfigureAwait(false);
    }

    public async Task<IApiResponse> UpdateAsync(RecipeRequest recipeRequest)
    {
        HttpApiPutRequest request = CreatePutRequest<RecipeResponse>($"{BasePath}", recipeRequest);

        return await SendAsync(request).ConfigureAwait(false);
    }

    public async Task<IApiResponse> DeleteAsync(int id)
    {
        HttpApiDeleteRequest request = CreateDeleteRequest<HttpApiMessageBase>($"{BasePath}/{id}");

        return await SendAsync(request).ConfigureAwait(false);
    }

}

Add a startup helper

It's useful to give your consumers a ServiceCollection extension method to add the necessary services to DI. Do that now:

public static class HttpApiAbstractionsBuilderExtensions
{
    public static HttpApiAbstractionsBuilder AddRecipeApiClient(this HttpApiAbstractionsBuilder builder)
    {
        builder.Services.AddScoped<IRecipeApiClient, RecipeApiClient>();

        return builder;
    }
}

Your consumers will now initialize your client in startup like so:

services
    .AddApiAbstractions(Configuration)
    .AddHttp()
    .AddRecipeApiClient();

Test it

Now create some unit/integration tests to check your client is functioning correctly! See the tests for examples of how you might test with xUnit.

Add your appsettings.json file

To the app where you want to call your new ApiClient methods, you'll need some configuration:

{
  "ApiOptions": {
    "Http": {
      "RecipeApi": {
        "BaseUri": "http://tastyrecipes.com"
      }
    }
  }
}

Congratulations

That's probably as basic as we can go, the following pages go into more detail about the extensibility and more advanced options available.

Clone this wiki locally