Refit is an awesome tool that I only just recently discovered and started using in systems that involve a lot of HTTP API integration points. With Refit, I can keep my API client code as simple as an interface with some magic sauce behind the scenes.

For those aren’t familiar with Refit, it is an automatic type-safe REST library for .NET. Refit is heavily inspired by Square’s Retrofit library for Android and Java. Refit turns your REST API into a live interface.

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

  • .NET Framework 4.6.1 and above
  • .NET 5.0 and above
  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS
  • Blazor
  • Uno Platform
  • UWP

Given that Refit will actually do most of the work, you still need to create the Refit interface and the contracts that are used by the HTTP API your system is calling. A Refit interface could be something as simple as:

public interface IFooApi
{
    [Get("/foo/{id}")]
    Task<Foo> GetFoo(string id);
}

Using the interface above would be the equivalent of performing GET https://foo.api.somesystem.com/foo/12345 which returns a deserialized Foo resource

Introducing Refitter

I’m generally a lazy person who hates repeating the same tasks more than once and recently I thought that working with Refit could be done easier, so I created Refitter.

Refitter is an open source CLI tool for generating a C# REST API Client using the Refit library. Refitter can generate the Refit interface (and contracts using NSwag) from OpenAPI specifications. Refitter is also available as a library for other IDE extension and develop tool authors to integrate with

Refitter is available in different forms as a CLI Tool and from the REST API Client Code Generator extension that supports the following IDE:

CLI Tool

The CLI tool is packaged as a .NET Tool and is published to nuget.org. You can install the latest version of this tool like this:

dotnet tool install --global Refitter

Refitter provides some --help information for getting started

$ refitter --help
USAGE:
    refitter [input file] [OPTIONS]

EXAMPLES:
    refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --output ./Output.cs

ARGUMENTS:
    [input file]    Path to OpenAPI Specification file

OPTIONS:
                                      DEFAULT                                                          
    -h, --help                                         Prints help information                         
    -n, --namespace                   GeneratedCode    Default namespace to use for generated types    
    -o, --output                      Output.cs        Path to Output file                             
        --no-auto-generated-header                     Don't add <auto-generated> header to output file
        --interface-only                               Don't generate contract types                   
        --use-api-response                             Return Task<IApiResponse<T>> instead of Task<T>             

To generate code from an OpenAPI specifications file, run the following:

$ refitter [path to OpenAPI spec file] --namespace "[Your.Namespace.Of.Choice.GeneratedCode]"

This will generate a file called Output.cs which contains the Refit interface and contract classes generated.

REST API Client Code Generator

Since most of us, including me, spend most of our time in IDE´s, we have a lot of tools in our toolbox. I love Swagger and OpenAPI, which was the reason as to why I built the REST API Client Code Generator. This tool allows me to right click on a solution and select Add New REST API Client and prompts me to enter the URI to where the OpenAPI specifications can be downloaded from

In Visual Studio 2019 and 2022 that looks like this:

and in Visual Studio for Mac it looks like this:

From the context menu, select Generate with Refitter and get this a prompt that looks like this:

This will result in the file being OpenAPI (Swagger) specifications file to be downloaded, included in your project, and configured to use a custom tool that generates a code behind file upon any changes on the OpenAPI specifications file

Example generated code

Here’s an example generated output from the Swagger Petstore example

using Refit;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace Your.Namespace.Of.Choice.GeneratedCode
{
    public interface ISwaggerPetstore
    {
        /// <summary>
        /// Update an existing pet by Id
        /// </summary>
        [Put("/pet")]
        Task<Pet> UpdatePet([Body]Pet body);

        /// <summary>
        /// Add a new pet to the store
        /// </summary>
        [Post("/pet")]
        Task<Pet> AddPet([Body]Pet body);

        /// <summary>
        /// Multiple status values can be provided with comma separated strings
        /// </summary>
        [Get("/pet/findByStatus")]
        Task<ICollection<Pet>> FindPetsByStatus([Query]Status? status);

        /// <summary>
        /// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
        /// </summary>
        [Get("/pet/findByTags")]
        Task<ICollection<Pet>> FindPetsByTags([Query(CollectionFormat.Multi)]ICollection<string> tags);

        /// <summary>
        /// Returns a single pet
        /// </summary>
        [Get("/pet/{petId}")]
        Task<Pet> GetPetById(long? petId);

        [Post("/pet/{petId}")]
        Task UpdatePetWithForm(long? petId, [Query]string name, [Query]string status);

        [Delete("/pet/{petId}")]
        Task DeletePet(long? petId);

        [Post("/pet/{petId}/uploadImage")]
        Task<ApiResponse> UploadFile(long? petId, [Query]string additionalMetadata, [Body]StreamPart body);

        /// <summary>
        /// Returns a map of status codes to quantities
        /// </summary>
        [Get("/store/inventory")]
        Task<IDictionary<string, int>> GetInventory();

        /// <summary>
        /// Place a new order in the store
        /// </summary>
        [Post("/store/order")]
        Task<Order> PlaceOrder([Body]Order body);

        /// <summary>
        /// For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
        /// </summary>
        [Get("/store/order/{orderId}")]
        Task<Order> GetOrderById(long? orderId);

        /// <summary>
        /// For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
        /// </summary>
        [Delete("/store/order/{orderId}")]
        Task DeleteOrder(long? orderId);

        /// <summary>
        /// This can only be done by the logged in user.
        /// </summary>
        [Post("/user")]
        Task CreateUser([Body]User body);

        /// <summary>
        /// Creates list of users with given input array
        /// </summary>
        [Post("/user/createWithList")]
        Task<User> CreateUsersWithListInput([Body]ICollection<User> body);

        [Get("/user/login")]
        Task<string> LoginUser([Query]string username, [Query]string password);

        [Get("/user/logout")]
        Task LogoutUser();

        [Get("/user/{username}")]
        Task<User> GetUserByName(string username);

        /// <summary>
        /// This can only be done by the logged in user.
        /// </summary>
        [Put("/user/{username}")]
        Task UpdateUser(string username, [Body]User body);

        /// <summary>
        /// This can only be done by the logged in user.
        /// </summary>
        [Delete("/user/{username}")]
        Task DeleteUser(string username);
    }
}

The CLI tool can also be used to generate a Refit interface that is configured to wrap the return type in IApiResponse<T>

$ refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --use-api-response

The command above produces an interface that might look like this:

using Refit;
using System.Threading.Tasks;
using System.Collections.Generic;

namespace Your.Namespace.Of.Choice.GeneratedCode.WithApiResponse
{
    public interface ISwaggerPetstore
    {
        /// <summary>
        /// Update an existing pet by Id
        /// </summary>
        [Put("/pet")]
        Task<IApiResponse<Pet>> UpdatePet([Body] Pet body);

        /// <summary>
        /// Add a new pet to the store
        /// </summary>
        [Post("/pet")]
        Task<IApiResponse<Pet>> AddPet([Body] Pet body);

        /// <summary>
        /// Multiple status values can be provided with comma separated strings
        /// </summary>
        [Get("/pet/findByStatus")]
        Task<IApiResponse<ICollection<Pet>>> FindPetsByStatus([Query(CollectionFormat.Multi)] Status? status);

        /// <summary>
        /// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
        /// </summary>
        [Get("/pet/findByTags")]
        Task<IApiResponse<ICollection<Pet>>> FindPetsByTags([Query(CollectionFormat.Multi)] IEnumerable<string> tags);

        /// <summary>
        /// Returns a single pet
        /// </summary>
        [Get("/pet/{petId}")]
        Task<IApiResponse<Pet>> GetPetById(long petId);

        [Post("/pet/{petId}")]
        Task UpdatePetWithForm(long petId, [Query(CollectionFormat.Multi)] string name, [Query(CollectionFormat.Multi)] string status);

        [Delete("/pet/{petId}")]
        Task DeletePet(long petId);

        [Post("/pet/{petId}/uploadImage")]
        Task<IApiResponse<ApiResponse>> UploadFile(long petId, [Query(CollectionFormat.Multi)] string additionalMetadata, [Body(BodySerializationMethod.UrlEncoded)] Dictionary<string, object> body);

        /// <summary>
        /// Returns a map of status codes to quantities
        /// </summary>
        [Get("/store/inventory")]
        Task<IApiResponse<IDictionary<string, int>>> GetInventory();

        /// <summary>
        /// Place a new order in the store
        /// </summary>
        [Post("/store/order")]
        Task<IApiResponse<Order>> PlaceOrder([Body] Order body);

        /// <summary>
        /// For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
        /// </summary>
        [Get("/store/order/{orderId}")]
        Task<IApiResponse<Order>> GetOrderById(long orderId);

        /// <summary>
        /// For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
        /// </summary>
        [Delete("/store/order/{orderId}")]
        Task DeleteOrder(long orderId);

        /// <summary>
        /// This can only be done by the logged in user.
        /// </summary>
        [Post("/user")]
        Task CreateUser([Body] User body);

        /// <summary>
        /// Creates list of users with given input array
        /// </summary>
        [Post("/user/createWithList")]
        Task<IApiResponse<User>> CreateUsersWithListInput([Body] IEnumerable<User> body);

        [Get("/user/login")]
        Task<IApiResponse<string>> LoginUser([Query(CollectionFormat.Multi)] string username, [Query(CollectionFormat.Multi)] string password);

        [Get("/user/logout")]
        Task LogoutUser();

        [Get("/user/{username}")]
        Task<IApiResponse<User>> GetUserByName(string username);

        /// <summary>
        /// This can only be done by the logged in user.
        /// </summary>
        [Put("/user/{username}")]
        Task UpdateUser(string username, [Body] User body);

        /// <summary>
        /// This can only be done by the logged in user.
        /// </summary>
        [Delete("/user/{username}")]
        Task DeleteUser(string username);
    }
}

Using the Refit interface

Refit provides two ways to use the interface:

  • Resolve the interface via the RestService class
  • Register the Refit interface with HttpClientFactory and use it through dependency injection

RestService

Here’s an example usage of the generated code above

using Refit;
using System;
using System.Threading.Tasks;

namespace Your.Namespace.Of.Choice.GeneratedCode;

internal class Program
{
    private static async Task Main(string[] args)
    {
        var client = RestService.For<ISwaggerPetstore>("https://petstore3.swagger.io/api/v3");
        var pet = await client.GetPetById(1);

        Console.WriteLine("## Using Task<T> as return type ##");
        Console.WriteLine($"Name: {pet.Name}");
        Console.WriteLine($"Category: {pet.Category.Name}");
        Console.WriteLine($"Status: {pet.Status}");
        Console.WriteLine();

        var client2 = RestService.For<WithApiResponse.ISwaggerPetstore>("https://petstore3.swagger.io/api/v3");
        var response = await client2.GetPetById(2);

        Console.WriteLine("## Using Task<IApiResponse<T>> as return type ##");
        Console.WriteLine($"HTTP Status Code: {response.StatusCode}");
        Console.WriteLine($"Name: {response.Content.Name}");
        Console.WriteLine($"Category: {response.Content.Category.Name}");
        Console.WriteLine($"Status: {response.Content.Status}");
    }
}

The RestService class generates an implementation of ISwaggerPetstore that uses HttpClient to make its calls.

The code above when run will output something like this:

## Using Task<T> as return type ##
Name: Gatitotototo
Category: Chaucito
Status: Sold

## Using Task<IApiResponse<T>> as return type ##
HTTP Status Code: OK
Name: Gatitotototo
Category: Chaucito
Status: Sold

ASP.NET Core and HttpClientFactory

Here’s an example Minimal API with the Refit.HttpClientFactory library:

using Refit;
using Your.Namespace.Of.Choice.GeneratedCode;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services
    .AddRefitClient<ISwaggerPetstore>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://petstore3.swagger.io/api/v3"));

var app = builder.Build();
app.MapGet(
        "/pet/{id:long}",
        async (ISwaggerPetstore petstore, long id) =>
        {
            try
            {
                return Results.Ok(await petstore.GetPetById(id));
            }
            catch (Refit.ApiException e)
            {
                return Results.StatusCode((int)e.StatusCode);
            }
        })
    .WithName("GetPetById")
    .WithOpenApi();

app.UseHttpsRedirection();
app.UseSwaggerUI();
app.UseSwagger();
app.Run();

.NET Core supports registering the generated ISwaggerPetstore interface via HttpClientFactory

The following request to the API above

$ curl -X 'GET' 'https://localhost:5001/pet/1' -H 'accept: application/json'

Returns a response that looks something like this:

{
  "id": 1,
  "name": "Special_char_owner_!@#$^&()`.testing",
  "photoUrls": [
    "https://petstore3.swagger.io/resources/photos/623389095.jpg"
  ],
  "tags": [],
  "status": "Sold"
}

For those of you who never tried Refit, I think that you should definitely check it out. It’s very easy to use