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.
VIDEO: Refit - Great API Client To Turn Your REST API Into a Live Interface.
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://myapi.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://myapi.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.