Consuming APIs over HTTP is a very common scenario when building applications. In this article, we are going to explore Refit to consume APIs in C#.

Let’s dive in.

What is Refit?

The Refit library for C# provides us with a type-safe wrapper for interacting with HTTP-based APIs. Instead of using HttpClient, which is provided for us by ASP.NET Core, we can define an interface that represents the API we want to interact with.

With this interface, we define the endpoints (GET, POST, PUT) our API contains, along with any route or body parameters. Also, we can include headers in the interface, such as ones for Authorization.

Components of a Refit Client

Before creating an application to demonstrate Refit, let’s explore some of the main components that make up a Refit client.

HTTP Methods

Any time we interact with an API over HTTP, we must be familiar with the different HTTP methods available to us, and how they work. Refit provides a set of attributes that allow us to decorate our interface methods:

[Get("/users")]
Task<IEnumerable<User>> GetUsers();

By decorating the GetUsers() method with [Get("/users")], we tell Refit this is an HTTP GET method, to the /users endpoint.

Refit provides attributes for all the common HTTP methods.

Route Parameters

When working with RESTful APIs that follow good routing conventions, we’ll often see an endpoint like /users/1, which we would expect to return us a user with id 1. Refit uses attribute routing, the same as ASP.NET Core, that allows us to easily define routes that contain parameters:

[Get("/users/{id}")]
Task<User> GetUser(int id);

By adding { and } around id in the route, we tell Refit that this is a dynamic parameter that comes from the id parameter in the GetUser() method.

Request and Response Serialization

The most common way to send data over HTTP is by serializing it as JSON and adding it to the request body. Refit provides this automatically for us.

This allows us to provide classes as parameters to a Refit method, and also specify them as the return type that we expect to be returned from the API:

[Put("/users/{id}")]
Task<User> UpdateUser(int id, User user);

Refit will automatically serialize the user parameter to JSON when sending the request and will attempt to deserialize the response into a User object.

Instantiating a Refit Client

Refit provides us with two ways to instantiate a client, either by using the RestService class provided by Refit, or by registering the Refit client with HttpClientFactory, and injecting the interface into a class constructor.

Let’s assume we have an API for interacting with users, along with a Refit interface:

public interface IUsersClient
{
    [Get("/users")]
    Task<IEnumerable<User>> GetUsers();
}

First, we can instantiate the client using the RestService class:

var usersClient = RestService.For<IUsersClient>("https://mywebapi.com");
var users = await usersClient.GetUsers();

We can also register the client with  HttpClientFactory provided by ASP.NET Core:

services
    .AddRefitClient<IUsersClient>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://mywebapi.com"));

Both of these are valid ways to register and use Refit clients.

However, if we want to make our code more maintainable and testable, registering the client with HttpClientFactory and injecting it into the required class constructors is the way to go. This allows us to easily inject a mock of the interface for testing purposes, without having to rely on any of the implementation details of either HttpClient or the Refit library.

Because of this, we will opt for the latter method for the rest of this article.

Setting up an API

Instead of setting up a new API from scratch, we can use JSONPlaceholder. It is a free, fake API that can be used for testing, and fits our needs perfectly. It provides various resources to interact with, but for this demo, we’ll use the users resource.

Creating Console Application

With our API solution chosen, let’s create a console application, either through the Visual Studio template or by using dotnet new console.

We must also add the Refit library from NuGet. As we will be using the HttpClientFactory registration method, we need to add two packages:

  • Refit
  • Refit.HttpClientFactory

As we have chosen the users resource, we’ll create a User model:

public class User
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }

    public override string ToString() =>
        string.Join(Environment.NewLine, $"Id: {Id}, Name: {Name}, Email: {Email}");
}

We override the ToString() method so we can easily display the users retrieved from the API in the console.

Now we can create our Refit interface.

Implementing Refit Client

We start by creating an interface and defining a GetAll method:

public interface IUsersClient
{
    [Get("/users")]
    Task<IEnumerable<User>> GetAll();
}

To turn this interface into a Refit client, we add the Get attribute to the GetAll() method, and define the route as /users. As the API will return us a list of users, the method return type is an IEnumerable<User>.

This is enough to get us started.

Consuming API Data

As we’ve opted to register our Refit client with the ASP.NET Core dependency injection framework, we need to add the Microsoft.Extensions.Hosting NuGet package to our console application.

With this done, let’s register the Refit client in the Program class:

using IHost host = Host.CreateDefaultBuilder(args)
    .ConfigureServices((_, services) =>
    {
        services
            .AddRefitClient<IUsersClient>()
            .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://jsonplaceholder.typicode.com/"));
    }).Build();

We use the AddRefitClient() extension method to register the IUsersClient interface, and then configure the HttpClient, setting the BaseAddress to the JSONPlaceholder address.

With our service registration complete, we can retrieve an instance of IUsersClient, and retrieve some users:

var usersClient = host.Services.GetRequiredService<IUsersClient>();
var users = await usersClient.GetAll();

foreach (var user in users)
{
    Console.WriteLine(user);
}

First, we retrieve an IUsersClient from the service collection, and call the GetAll() method to retrieve a list of users, which we then print to the console.

This demonstrates how simple it is to use a Refit client to abstract HTTP calls. We make a method call that returns our populated User model.

Next, let’s explore some of the further capabilities of Refit, by adding more methods to IUsersClient.

Extending IUsersClient

Let’s add some basic CRUD (Create, Read, Update, Delete) operations for our API:

public interface IUsersClient
{
    [Get("/users")]
    Task<IEnumerable<User>> GetAll();

    [Get("/users/{id}")]
    Task<User> GetUser(int id);

    [Post("/users")]
    Task<User> CreateUser([Body] User user);

    [Put("/users/{id}")]
    Task<User> UpdateUser(int id, [Body] User user);

    [Delete("/users/{id}")]
    Task DeleteUser(int id);
}

Firstly, we add the GetUser() method, which takes an id parameter to identify the user we want to retrieve. We decorate this method with the Get attribute, and in the route we define a dynamic parameter using { and }.

Next up is the CreateUser() method, which takes a User as a parameter, and because we want this to be passed in the HTTP request body, we decorate the parameter with the Body attribute. This time, it’s a Post request that the API expects.

To update a user, we need a Put method, combining both a route parameter, id, and body content, which is the User we want to update.

Finally, to delete a user, we make a Delete request providing the id of the user to delete.

This gives us CRUD functionality on the Users API. Now we can test this out.

Testing CRUD Functionality

Back in the Program class, let’s start by creating a new user:

var user = new User
{
    Name = "John Doe",
    Email = "[email protected]"
};

var usersClient = host.Services.GetRequiredService<IUsersClient>();
var userId = (await usersClient.CreateUser(user)).Id;

Console.WriteLine($"User with Id: {userId} created");

Initially, we create a new User object. With this user, we call CreateUser(), which will return a User object, giving us the Id of the newly created user, which we log to the console.

Next, we can retrieve an existing user using the GetUser() method:

var existingUser = await usersClient.GetUser(1);

With this user, let’s update the Email:

existingUser.Email = "[email protected]";
var updatedUser = await usersClient.UpdateUser(existingUser.Id, existingUser);

Console.WriteLine($"User email updated to {updatedUser.Email}");

Here, we use the UpdateUser() method, passing in the Id of the user, along with the updated user object.

The final step is to delete the user:

await usersClient.DeleteUser(userId);

We simply call DeleteUser(), providing the userId to delete.

This covers the basic CRUD functionality and shows how simply we can create an interface to interact with an API, without the need of handling complex HTTP logic with an HttpClient.

Conclusion

In this article, we’ve learned how we can abstract interaction with HTTP-based APIs by using Refit and creating a simple interface for our API. This allowed us to avoid dealing with complex HTTP logic, such as creating request messages and deserializing responses and instead focus on the core logic relating to our applications.

Tagged in: