HomeNikola Knezevic
Banner

API Clients with Refit in .NET

13 Mar 2025
6 min

Sponsor Newsletter

Integration with external APIs is a common requirement.

HTTP requests contain all the necessary details sent to the server, such as the URL, headers and parameters.

Once the server receives the request, it processes it and returns a response. This response includes a status code and may also contain resource data.

In the .NET ecosystem, you can use HttpClient to send HTTP requests efficiently.

However, integrating with external APIs can be even easier with Refit.

Refit

Refit is a type-safe REST library for .NET, inspired by Retrofit library.

With Refit, you don’t need to manage HttpClient directly. Instead, you can define an interface that represents the API you want call.

Each method in this interface corresponds to an API route. By adding attributes, you can easily set the route and HTTP method for each endpoint.

Refit also supports custom headers, complex query parameters and automatically serializes and deserializes objects.

Getting Started

To get started with Refit, you'll first need to install the necessary NuGet package. You can do this via the NuGet Package Manager or by running the following command in the Package Manager Console:

bash
Install-Package Refit

Once the package is added, we can create our first Refit interface. This interface will represent the external service you wish to interact with:

csharp
public interface IProductApi
{
}

Inside this interface, you'll declare methods that correspond to the specific routes of the external service you are integrating with.

To use the Refit client in your application, you need to register it with the dependency injection container:

csharp
builder.Services
    .AddRefitClient<IProductApi>()
    .ConfigureHttpClient(client =>
        client.BaseAddress = new Uri("https://localhost:7106"));

By using ConfigureHttpClient, you can easily configure HTTP client settings such as setting the base address for your API.

This approach allows you to centralize your configuration while maintaining flexibility when making requests to various endpoints.

For the purposes of this post, I’ve created a simple API that will serve as our external service with the following endpoints:

csharp
app.MapGet("products/{id}", (Guid id) => Results.Ok(GetProduct(id)));

app.MapGet("products", (int page, int itemsPerPage) => Results.Ok(GetProducts()));

app.MapPost("products", (CreateProductRequest request) => Results.Ok(Guid.NewGuid()));

app.MapPut("products/{id}", (Guid id) => Results.NoContent());

app.MapDelete("products/{id}", (Guid id) => Results.NoContent());

Now that the setup is complete, we can start defining methods in the IProductApi interface to interact with our endpoints, enabling type-safe API calls.

Implementing Refit Client

Let's start by fetching a product by its identifier:

csharp
[Get("/products/{id}")]  
Task<ProductResponse> GetProduct(Guid id);

[Get("/products/{id}")] attribute, provided by Refit, defines that this method will send an HTTP GET request to the endpoint /products/{id}.

{id} is a route parameter that will be replaced with the actual value of id when calling GetProduct.

The method returns a Product object once the API response is received and deserialized.

Just like that, we've successfully integrated with the external API.

HTTP Method Attributes

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
  • Head

Using Query Parameters

You can also specify query parameters in the URL:

csharp
[Get("/products?page=1&itemsPerPage=10")]  
Task<List<ProductResponse>> GetPagedList();

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

csharp
[Get("/products")]
Task<List<ProductResponse>> GetPagedList(
    int page,
    [AliasAs("itemsPerPage")]int perPage);

Using Body

Now, let's create a method for adding a new product. We’ll use the [Post] attribute to specify that this method will send an HTTP POST request:

csharp
[Post("/products")]
Task<Guid> Create([Body]CreateProductRequest request);

[Body] attribute is used to specify that the CreateProductRequest object should be serialized and sent as the body of the request.

Working with Headers

In some cases, you may need to include custom headers in your requests. Refit provides a straightforward way to add headers globally or per-method.

To add a header for all methods in the interface:

csharp
[Headers("Custom-Header: MyCustomHeader")]
public interface IProductApi
{
    // ..
}

If you only need a custom header for a specific method, you can apply the [Headers] attribute directly on the method:

csharp
public interface IProductApi
{
    [Headers("Custom-Header: MyCustomHeader")]
    [Post("/products")]
    Task<Guid> Create([Body]CreateProductRequest request);
}

If the content of the header needs to be set at runtime, you can use the [Header] attribute on a parameter:

csharp
public interface IProductApi
{
    [Post("/products")]
    [Post("/products")]
    Task<Guid> Create([Body]CreateProductRequest request, [Header("DynamicHeader")] string header);
}

Handling Responses and Errors

In the previous examples, we’ve been returning objects directly after making API requests. However, what if something goes wrong during the request or response processing?

By default, when using Task<T> with Refit, the library will throw an ApiException if any error occurs while processing the API response.

While this behavior is fine in some cases, it may not always be ideal for handling errors in a more controlled way.

To avoid dealing with exceptions via try-catch blocks in every method, Refit provides the ApiResponse<T> class to handle errors more gracefully:

csharp
[Post("/products")]
Task<ApiResponse<Guid>> Create([Body]CreateProductRequest request);

Instead of directly throwing exceptions, ApiResponse encapsulates any errors within the response and gives you access to the details without throwing an exception.

It's also worth mentioning that if you need to access the raw HttpResponseMessage, Refit has you covered:

csharp
[Post("/products")]
Task<HttpResponseMessage> Create([Body]CreateProductRequest request);

Conclusion

Refit simplifies API integration in .NET by providing a type-safe approach to defining HTTP clients.

Instead of manually managing HttpClient and handling serialization/deserialization, we can define an interface that mirrors the API endpoints using attributes.

By using this approach, you can spend more time focusing on business logic and less on managing HTTP client configurations or parsing responses manually.

If you want to check out examples I created, you can find the source code here:

Source Code

I hope you enjoyed it, subscribe and get a notification when a new blog is up!

Subscribe

Stay tuned for valuable insights every Thursday morning.
Share This Article: