3

My Notebook: Refit - A type-safe REST library

 2 years ago
source link: https://www.fiyazhasan.me/my-notebook-refit/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
September 21, 2020

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,  

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 Http channel.

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

  • Xamarin.Android
  • Xamarin.Mac
  • Xamarin.iOS
  • Desktop .NET 4.6.1
  • .NET Core
  • Uno Platform
2327577?s=400&v=4

Source Code:

Repository Link


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK