Understand the Refit in .NET Core

Introduction

We have a type-safe wrapper for communicating with HTTP-based APIs thanks to the C# Refit framework. We can build an interface that represents the API we want to interact with, rather than utilizing the HttpClient that ASP.NET Core provides.

If you want to learn more about the Refit library, then please refer to this link for more understanding.

You can find the GitHub repository of the Refit here.

We specify the endpoints (GET, POST, and PUT) that our API has with this interface, as well as any route or body parameters. We can also add headers to the interface, like the Authorization headers.

Components of Refit Client

Let's examine some of the key elements of a Refit client before building an application to illustrate Refit.

HTTP Methods

Every time we communicate with an API over HTTP, we need to understand the various HTTP methods that are available to us and how they operate. Refit offers a collection of characteristics that let us customize our interface functions:

[Get("/api/employee")]
Task<IEnumerable<Employee>> GetEmployees();

We inform Refit that this is an HTTP GET method to the /employees endpoint by adorning the GetEmployees() method with [Get("/API/employee")].

Refit offers properties for every widely used HTTP protocol.

Route Parameters

An endpoint like /employee/369f8eb0-7d8f-4f48-a2c8-884829d60de4, which we would anticipate to return an employee with id 369f8eb0-7d8f-4f48-a2c8-884829d60de4, is frequently seen when working with RESTful APIs that adhere to appropriate routing standards. Similar to ASP.NET Core, Refit makes advantage of attribute routing, which makes it simple to build routes using parameters:

[Get("/api/employee/{id}")]
Task<Employee> GetEmployee(Guid id);

We inform Refit that the id parameter in the GetEmployee() method is a dynamic parameter by enclosing it in curly braces ({ and }) in the route.

Request and Response Serialization

Data is most commonly sent via HTTP as JSON serialized and added to the request body. This is automatically provided for us by Refit.

This enables us to designate classes as the return type that we anticipate receiving from the API in addition to passing them in as parameters to a Refit method:

[Put("/employee/{id}")]
Task<Employee> UpdateEmployee(Guid id, Employee employee);

When sending the request, Refit will automatically serialize the employee parameter to JSON. It will then try to deserialize the response into an Employee object.

Instantiating a Refit Client

Refit gives us two options for creating clients: either register the Refit client with HttpClientFactory and inject the interface into a class constructor, or use the RestService class that Refit supplies.

To the project, add the Refit and Refit.HttpClientFactory Nuget packages:

Nuget packages

Assume we have both a Refit interface and an API for communicating with employees:

public interface IEmployeeClient
{
    [Get("/api/employee")]
    Task<IEnumerable<Employee>> GetEmployees();
}

Initially, we may use the RestService class to instantiate the client:

var employeeClient = RestService.For<IEmployeeClient>("https://localhost:7061");

var employees = await employeeClient.GetEmployees();

Additionally, we can register the client using the ASP.NET Core-provided HttpClientFactory:

services
    .AddRefitClient<IEmployeeClient>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://localhost:7061"));

These are two legitimate methods for signing up and using Refit clients.

Nevertheless, registering the client with HttpClientFactory and injecting it into the necessary class constructors is the way to go if we want to make our code more tested and manageable. This spares us from depending on any implementation details of HttpClient or the Refit library, and makes it simple to inject a mock of the interface for testing reasons.

Creating Refit Demo Web Application

Additionally, we need to add NuGet's Refit library. Since we'll be registering using the HttpClientFactory function, we must include the following two packages:

  1. Refit
  2. Refit.HttpClientFactory

We will develop an employee model now that we have selected the employee’s resources:

public class Employee
{
    public Guid EmpId { get; set; }
    public string Name { get; set; }
    public string Address { get; set; }

    public override string ToString() =>
    string.Join(Environment.NewLine, $"{nameof(EmpId)}: {EmpId}, {nameof(Name)}: {Name}, {nameof(Address)}: {Address}");
}

To make it easier to display the employees retrieved from the API in the log, we override the ToString() method.

We can now design our Refit user interface.

Implementing Refit Client

First, we define a GetAll method and create an interface:

public interface IEmployeeClient
{
    [Get("/api/employee")]
    Task<IEnumerable<Employee>> GetEmployees();
}

We add the Get property to the GetAll() method and define the route as /employee to transform this interface into a Refit client. The function return type is an IEnumerable<Employee> since the API will return a list of employees.

This is sufficient to get things going.

Let’s register the Refit client in the Program class:

builder.Services
           .AddRefitClient<IEmployeeClient>()
           .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://localhost:7061"));

Let’s illustrate the refit demo.

Refit demo

HTTP

Responses

This illustrates how easy it is to abstract HTTP calls using a Refit client. We call a method that yields our Employee model that has been filled up.

Now, let's extend Refit's functionality by giving IEmployeeClient additional methods.

Extending IEmployeeClient

For our API, let's implement the following fundamental CRUD (Create, Read, Update, Delete) operations:

public interface IEmployeeClient
{
    [Get("/api/employee")]
    Task<IEnumerable<Employee>> GetEmployees();

    [Get("/api/employee/{id}")]
    Task<Employee> GetEmployeeById(Guid id);

    [Post("/api/employee")]
    Task<Employee> CreateEmployee([Body] EmployeeRequest employee);

    [Put("/api/employee/{id}")]
    Task<Employee> UpdateEmployee(Guid id, [Body] EmployeeRequest employee);

    [Delete("/api/employee/{id}")]
    Task<object> DeleteEmployee(Guid id);
}
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
    private readonly IEmployeeClient _employeeClient;

    public ValuesController(IEmployeeClient employeeClient)
    {
        _employeeClient = employeeClient;
    }

    [HttpGet]
    public async Task<IActionResult> GetEmployeesAsync()
    {
        var employees = await _employeeClient.GetEmployees();
        return Ok(employees);
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> GetByIdAsync(Guid id)
    {
        var employee = await _employeeClient.GetEmployeeById(id);
        return Ok(employee);
    }

    [HttpPost]
    public async Task<IActionResult> CreateAsync(EmployeeRequest employeeRequest)
    {
        var employee = await _employeeClient.CreateEmployee(employeeRequest);
        return Ok(employee);
    }

    [HttpPut("{id}")]
    public async Task<IActionResult> UpdateAsync(Guid id, EmployeeRequest employeeRequest)
    {
        var employee = await _employeeClient.UpdateEmployee(id, employeeRequest);
        return Ok(employee);
    }

    [HttpDelete("{id}")]
    public async Task<IActionResult> DeleteAsync(Guid id)
    {
        var employee = await _employeeClient.DeleteEmployee(id);
        return Ok(employee);
    }
}

Swagger

GET

Delete

Summary

This post has taught us how to use Refit to create a basic API interface and isolate interaction with HTTP-based APIs. This allowed us to concentrate on the essential logic for our applications rather than handling complicated HTTP logic, including generating request messages and deserializing answers.

We learned the new technique and evolved together.

Happy coding!