Using Refit in .NET

Sena Kılıçarslan
.NET Core
Published in
9 min readJan 1, 2024

In this post, I will demonstrate how to use Refit library in ASP.NET Core.

We will build an ASP.NET Core Web API that will make external API calls to TMDB API and we will use Refit library while calling this external API.

Our API will have endpoints for the following purposes:

  • Search an actor/actress by name
  • Get the movies of an actor/actress
  • Add a rating to a movie
  • Delete a rating from a movie

At the end, the API will look like this:

The sections of this post will be as follows:

  • What is Refit?
  • Demo Application
    - “Search an actor/actress by name” endpoint (GET)
    - “Get the movies of an actor/actress” endpoint (GET)
    - “Add a rating to a movie” endpoint (POST)
    - “Delete a rating from a movie” endpoint (DELETE)
    - ApiResponse<T> wrapper class

What is Refit?

Refit is an automatic type-safe REST library for .NET. It is heavily inspired by Square’s Retrofit library, Refit turns your REST API into a live interface.

Refit reduces the amount of code required to call APIs by eliminating the need to manually construct HTTP requests and handle responses.

It provides automatic serialization and deserialization of data.

Refit currently supports the following platforms and any .NET Standard 2.0 target:

  • UWP
  • Xamarin
  • Desktop .NET 4.6.1
  • .NET 5 / .NET Core
  • Blazor
  • Uno Platform

The current version of Refit is 7.0.0 and you can see the latest version here.

Refit 6 requires Visual Studio 16.8 or higher, or the .NET SDK 5.0.100 or higher. It can target any .NET Standard 2.0 platform.

Additionally, Refit 6 makes System.Text.Json the default JSON serializer.

You can find the GitHub repository of Refit here.

We will dive into the details of using Refit in our demo project below.

If you are ready, let’s start :)

Demo Application

The API will make external API calls to the TMDb API. If you want to follow along while implementing the application, please go here and get your API Read Access Token to be able to call this API.

Now, let’s start with creating the project.

I will use Visual Studio 2022 and .NET 8 for the demo application.

Open VS 2022 and select Create a new project and then select ASP.NET Core Web API:

Please make sure that Enable OpenAPI support is checked so that we can use Swagger to test our endpoints.

You can delete the default class and the controller (WeatherForecast.cs and WeatherForecastController.cs) as we will not use those.

Add the Refit and Refit.HttpClientFactory Nuget packages to the project:

“Search an actor/actress by name” endpoint (GET)

Now, we will add the implementation for our first endpoint.

First, add Models folder in the project and add Actor and ActorList classes under it:

Next, add Services folder to the project and create ITmdbApi interface with the following code under it:

ITmdbApi.cs

Now, let’s examine specific features related to Refit here.

Every method must have an HTTP attribute that provides the request method and relative URL. There are six built-in annotations: Get, Post, Put, Delete, Patch and Head. The relative URL of the resource is specified in the annotation.

You can also specify query parameters in the URL. A request URL can be updated dynamically using replacement blocks and parameters on the method. A replacement block is an alphanumeric string surrounded by { and }.

We use the GET attribute for our method as follows:

 [Get("/search/person?query={name}")]
Task<ActorList> GetActors(string name);

The complete path to query persons in TMDB API is:

https://api.themoviedb.org/3/search/person?query={name}

Since the part https://api.themoviedb.org/3 is common across all endpoints, this part will be defined as base URL later in Program.cs

The relative URL /search/person?query={name} is specified in the annotation. And {name} is matched with the parameter name in the method definition.

If the name of your parameter doesn’t match the name in the URL path, use the AliasAs attribute.

[Get("/search/person?query={name}")]
Task<ActorList> GetActors([AliasAs("name")] string actorName);

You can set one or more static request headers for a request applying a Headers attribute to the method:

[Headers("accept: application/json",
"Authorization: Bearer")]

As we will use token authentication with TMDB API, we need to add Authorization: Bearer header in the interface. (The rest of the token authentication will be done in Program.cs later)

Note that in Refit unlike in Retrofit, there is no option for a synchronous network request — all requests must be async, either via Task or via IObservable.

So we use Task<ActorList> in the method definition.

Now, we will add the necessary code to Program.cs

Refit has first class support for the ASP.Net Core 2.1 HttpClientFactory. Add a reference to Refit.HttpClientFactory and call the provided extension method in your ConfigureServices method to configure your Refit interface:

builder.Services
.AddRefitClient<ITmdbApi>(refitSettings)
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.themoviedb.org/3"));

We configure the Refit client with the base URL https://api.themoviedb.org/3 here.

Notice that we are passing refitSettings parameter to the AddRefitClient

RefitSettings defines various parameters on how Refit should work.

We can use RefitSettings for many purposes to customize our client. In our case, we will use it to pass our API Read Access Token to the client:

var authToken = "***WRITE YOUR API READ ACCESS TOKEN HERE***";
var refitSettings = new RefitSettings()
{
AuthorizationHeaderValueGetter = (rq, ct) => Task.FromResult(authToken),
};

So, our Program.cs will look like this after those additions:

Program.cs

For TMDB API, access token is static. But if you need to generate a token dynamically, you can do it in a function, say GetTokenAsync , and then you can use it like below:

var refitSettings = new RefitSettings()
{
AuthorizationHeaderValueGetter = (rq, ct) => GetTokenAsync(),
};

Finally, we will add an API Controller to our project and inject ITmdbApi into it.

Add a new folder called Controllers to the project and then add an empty API Controller named MovieDbController to it and add the following code:

MovieDbController.cs

Now, we can test our first endpoint.

Run the project and click Try It Out and then enter the name and/or surname of the actor/actress you want to see and click Execute:

Then we see the results as follows:

Please note the id of the person you want to use as we will use that in the next endpoint.

“Get the movies of an actor/actress” endpoint (GET)

Now, we will implement the endpoint to get the movies of an actor/actress.

First, add the Movie and MovieList classes under the Models folder:

Next, add the following code to ITmdbApi :

 [Get("/person/{actorId}/movie_credits?language=en-US")]
Task<MovieList> GetMovies(int actorId);

The comparison between parameter name and URL parameter is not case-sensitive, so it will work correctly if you name your parameter as actorid

Finally, add the following code to MovieDbController :

[HttpGet("actors/{actorId}/movies")]
public async Task<MovieList> GetMovies(int actorId)
{
var response = await _tmdbApi.GetMovies(actorId);
return response;
}

Now, we can test the endpoint using the id of the actor/actress that we found in the previous endpoint:

Please note the Id of the movie you want to use as we will use that in the next endpoints to add/delete rating.

“Add a rating to a movie” endpoint (POST)

Now, we will add a rating to our favorite movie :)

First, add the Rating and ResponseBody classes under the Models folder:

Next, add the following code to ITmdbApi :

   [Headers("Content-Type: application/json;charset=utf-8")]
[Post("/movie/{movieId}/rating")]
Task<ResponseBody> AddRating(int movieId, [Body] Rating rating);

Now, let’s examine what we added here in detail:

We use Post attribute in the data annotation as this is a POST endpoint.

One of the parameters in your method can be used as the body, by using the Body attribute

We use Body attribute here to pass the Rating object.

And we include a new Headers attribute for the Content-Type header. Please notice that we already have a Header attribute for the interface and now we added a new one for the method.

There can be three ways to define a Headers attribute and below is the priority for those when they are defined for the same header:

Headers attribute on the interface (lowest priority)

Headers attribute on the method

Header attribute or HeaderCollection on a method parameter (highest priority)

This redefining behavior only applies to headers with the same name. Headers with different names are not replaced. So, all headers will be included for our code.

Finally, add the following code to the MovieDbController :

[HttpPost("movies/{movieId}/rating")]
public async Task<ResponseBody> AddRating(int movieId, [FromBody] Rating rating)
{
var response = await _tmdbApi.AddRating(movieId, rating);
return response;
}

Now, we can add our rating to the movie:

And we get the Success message when we execute it:

Also, we can check that the rating is added to our TMDB account:

“Delete a rating from a movie” endpoint (DELETE)

Now, we will implement the endpoint to delete a rating.

Add the following code to ITmdbApi :

  [Delete("/movie/{movieId}/rating")]
Task<ResponseBody> DeleteRating(int movieId);

Then, add the following code to IMovieDbController :

 [HttpDelete("movies/{movieId}/rating")]
public async Task<ResponseBody> DeleteRating(int movieId)
{
var response = await _tmdbApi.DeleteRating(movieId);
return response;
}

Now, we can use the endpoint after running the project:

If we check our account on the TMDB site then we will see the rating is removed:

ApiResponse<T> wrapper class

There is also a generic wrapper class called ApiResponse<T> that can be used as a return type. Using this class as a return type allows you to retrieve not just the content as an object, but also any metadata associated with the request/response. This includes information such as response headers, the http status code and reason phrase (e.g. 404 Not Found), the response version, the original request message that was sent and in the case of an error, an ApiException object containing details of the error.

Lastly, I want to demonstrate how to use ApiResponse<T> wrapper class as I find it very helpful.

We will change our Search an actor/actress by name GET endpoint.

First, change the GetActors method in the ITmdbApi as follows:

 [Get("/search/person?query={name}")]
Task<ApiResponse<ActorList>> GetActors(string name);

Then, change the GetActors method in the MovieDbController as below:

 [HttpGet("actors/")]
public async Task<ActorList> GetActors([FromQuery][Required] string name)
{
var result = new ActorList();
var response = await _tmdbApi.GetActors(name);
if (response.IsSuccessStatusCode)
{
result = response.Content;
}
else
{
// log $"{response.Error}-{response.Error.Content}";
}
return result;
}

Now, you can run the project and see it still functions properly. In this way, you have more information on the request/response that you can use for controls, exception handling, logging, etc.

You can find the source code on this GitHub repository.

That’s the end of the post. I hope you found this post helpful and easy to follow. If you have any questions and/or comments, you can share them in the responses section below.

And if you liked this post, please clap your hands 👏👏👏

Bye!

--

--

Sena Kılıçarslan
.NET Core

A software developer who loves learning new things and sharing these..