My Notebook: Refit - A type-safe REST library

As the official documentation states,

Refit is a library heavily inspired by Square’s Retrofit library, and it turns your REST API into a live interface

Suppose, you have a REST API hosted somewhere else. The only way you can consume it from any other .NET-based application is to use the built-in HttpClient or HttpClientFactory. In a typical scenario that would look something like this,  

class Program
{
    static async Task Main(string[] args)
    {
        using var client = new HttpClient();
        var content = await client.GetStringAsync("https://jsonplaceholder.typicode.com/posts");

        Console.WriteLine(content);
    }
}

Working with HttpClient is a kind of low-level stuff. Refit eases that problem for us. It creates an abstraction over HttpClient and allows us to work with an interface representing REST endpoints,

A RESTful API for weather forecast service might look like,

[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
    private static readonly string[] Summaries = new[]
    {
        "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching"
    };

    private readonly ILogger<WeatherForecastController> _logger;

    public WeatherForecastController(ILogger<WeatherForecastController> logger)
    {
        _logger = logger;
    }

    [HttpGet]
    public IEnumerable<WeatherForecast> Get()
    {
        var rng = new Random();
        return Enumerable.Range(1, 5).Select(index => new WeatherForecast
        {
            Date = DateTime.Now.AddDays(index),
            TemperatureC = rng.Next(-20, 55),
            Summary = Summaries[rng.Next(Summaries.Length)]
        })
        .ToArray();
    }
}

The GET endpoint can be represented in Refit by the means of the following interface,

public interface IWeatherAPI
{
    [Get("/weatherforcast")]
    Task<IEnumerable<WeatherForecast>> Get();
}

Console

Basic usage of Refit to make API calls is shown below in a .NET console app,

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

namespace RefitConsole.cs
{
    class Program
    {
        static async Task Main(string[] args)
        {
            var weatherAPI = RestService.For<IWeatherAPI>("http://localhost:5001");
            var forecasts = await weatherAPI.Get();
            forecasts.ToList().ForEach(f => Console.WriteLine($"Date: {f.Date}, Celsius: {f.TemperatureC}, Fahrenheit: {f.TemperatureF}, Summary: {f.Summary} \n"));
        }
    }

    public interface IWeatherAPI
    {
        [Get("/weatherforecast")]
        Task<IEnumerable<WeatherForecast>> Get();
    }

    public class WeatherForecast
    {
        public DateTime Date { get; set; }
        public int TemperatureC { get; set; }
        public string Summary { get; set; }
        public int TemperatureF { get; set; }
    }
}

Behind the abstraction, the RestService class generates an implementation of IWeatherAPI that uses HttpClient to make its calls.

Blazor

We can inject the interface and use it to consume the API as well. For a Blazor client application, it might look like the following,

public class Program
{
    public static async Task Main(string[] args)
    {
        var builder = WebAssemblyHostBuilder.CreateDefault(args);
        builder.RootComponents.Add<App>("app");

        builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });

        builder.Services.AddRefitClient<IWeatherAPI>().ConfigureHttpClient(c => c.BaseAddress = new Uri("http://localhost:5001"));

        await builder.Build().RunAsync();
    }
}
Program.cs
@page "/fetchdata"
@using Refit;
@using ViewModels; 
@inject BlazorAppDemo.Services.IWeatherAPI WeatherAPI

<h1>Weather forecast</h1>

<p>This component demonstrates fetching data from the server.</p>

@if (forecasts == null)
{
    <p><em>Loading...</em></p>
}
else
{
    <table class="table">
        <thead>
            <tr>
                <th>Date</th>
                <th>Temp. (C)</th>
                <th>Temp. (F)</th>
                <th>Summary</th>
            </tr>
        </thead>
        <tbody>
            @foreach (var forecast in forecasts)
            {
                <tr>
                    <td>@forecast.Date.ToShortDateString()</td>
                    <td>@forecast.TemperatureC</td>
                    <td>@forecast.TemperatureF</td>
                    <td>@forecast.Summary</td>
                </tr>
            }
        </tbody>
    </table>
}

@code {
    private IEnumerable<WeatherForecast> forecasts;

    protected override async Task OnInitializedAsync()
    {
        forecasts = await WeatherAPI.Get();
    }
}
FetchData.razor

Additional

Extend six built-in annotations: Get, Post, Put, Delete, Patch, and Head request and add additional options through different attributes. For example, if a POST request requires multipart data,

public interface IUploadAPI
{
    [Multipart]
    [Post("/upload")]
    Task UploadPhoto([AliasAs("avatar")] StreamPart stream);
}
@page "/upload"
@using BlazorInputFile
@using Refit;
@inject BlazorAppDemo.Services.IUploadAPI UploadAPI

<h1>Upload</h1>

<InputFile OnChange="HandleSelection" />

@code {
    async Task HandleSelection(IFileListEntry[] files)
    {
        var file = files.FirstOrDefault();
        if (file != null)
        {
            await UploadAPI.UploadPhoto(new StreamPart(file.Data, "avatar.png", "image/png"));
        }
    }
}
Upload.razor
public async Task<IActionResult> Post([FromForm(Name = "avatar")] IFormFile file)
{
    var path = Path.Combine(_environment.ContentRootPath, "Uploads", file.FileName);

    using (var stream = System.IO.File.Create(path))
    {
        await file.CopyToAsync(stream);
    }

    return Ok();
}
UploadController.cs

If a request requires headers to be set,

public interface IUserAPI
{
    [Get("/users/{user}")]
	Task<User> GetUser(string user, [Header("Authorization")] string authorization);
}

Replace the default JSON content serializer using RefitSettings,

var weatherAPI = RestService.For<IWeatherAPI>("http://localhost:5001", new RefitSettings
{
    ContentSerializer = new XmlContentSerializer()
});

Use Alias if the name of your parameter doesn't match the name in the URL segment

[Get("/group/{id}/users")]
Task<List<User>> GroupList([AliasAs("id")] int groupId);
Refit has first-class support for HttpClientFactory

In my honest opinion, it is best suited for Microservices communication over the HTTP channel.

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

  • UWP
  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS
  • Desktop .NET 4.6.1
  • .NET Core
  • Uno Platform
reactiveui/refit
The automatic type-safe REST library for .NET Core, Xamarin and .NET. Heavily inspired by Square’s Retrofit library, Refit turns your REST API into a live interface. - reactiveui/refit

https://github.com/fiyazbinhasan/RefitDemo