# Refit in .NET: Building Robust API Clients in C#

> Refit turns your HTTP API into a strongly typed C# interface, so you stop hand-writing HttpClient boilerplate. I'll cover the setup, query and route parameters, headers and authentication, JSON serializer options, and handling raw HTTP responses.

Published: 2024-09-07. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/refit-in-dotnet-building-robust-api-clients-in-csharp

Refit turns an HTTP API into a C# interface.
You declare methods with attributes like `[Get]` and `[Post]`, and Refit generates the client that handles the requests and JSON serialization.
Register it with `AddRefitClient<T>()` and inject the interface wherever you need it.

As a .NET developer, I've spent countless hours working with external APIs.
It's a crucial part of modern software development, but let's be honest - it can be a real pain sometimes.

We've all been there, wrestling with [**`HttpClient`**](https://milanjovanovic.tech/blog/the-right-way-to-use-httpclient-in-dotnet), writing repetitive code, and hoping we didn't miss a parameter or header somewhere.

That's why I want to introduce you to **Refit**, a library that's been a game-changer for me.

Imagine turning your API into a live interface - sounds too good to be true, right?
But that's exactly what Refit does.
It handles all the HTTP heavy lifting, letting you focus on what matters: your application logic.

In this article, I'll explain how Refit can transform the way you work with APIs in your .NET projects.

## What is Refit?

[Refit](https://github.com/reactiveui/refit) is a type-safe REST library for .NET.
It allows you to define your API as an interface, which Refit then implements for you.
This approach reduces boilerplate code and makes your API calls more readable and maintainable.

You describe your API endpoints using method signatures and attributes, and Refit takes care of the rest.

Let me break down why I find Refit so powerful:

- **Automatic serialization and deserialization**:
  You won't have to convert your objects to JSON and back.
  Refit handles all of that for you.
- **Strongly-typed API definitions**:
  Refit helps you catch errors early.
  If you mistype a parameter or use the wrong data type, you'll know at compile time, not when your app crashes in production.
- **Support for various HTTP methods**:
  GET, POST, PUT, PATCH, DELETE - Refit has you covered.
- **Request/response manipulations**:
  You can add custom headers or handle specific content types in a straightforward way.

But what I appreciate most about Refit is how it promotes clean, readable code.
Your API calls become self-documenting.
Anyone reading your code can quickly understand what each method does without diving into implementation details.

## Setting Up and Using Refit in Your Project

Let's set up Refit and see it in action using the [JSONPlaceholder API](https://jsonplaceholder.typicode.com/).
We'll implement a full CRUD interface and demonstrate its usage in a [**Minimal API application**](https://milanjovanovic.tech/blog/automatically-register-minimal-apis-in-aspnetcore).

First, install the required NuGet packages:

```powershell
Install-Package Refit
Install-Package Refit.HttpClientFactory
```

Now, let's create our Refit interface:

```csharp
using Refit;

public interface IBlogApi
{
    [Get("/posts/{id}")]
    Task<Post> GetPostAsync(int id);

    [Get("/posts")]
    Task<List<Post>> GetPostsAsync();

    [Post("/posts")]
    Task<Post> CreatePostAsync([Body] Post post);

    [Put("/posts/{id}")]
    Task<Post> UpdatePostAsync(int id, [Body] Post post);

    [Delete("/posts/{id}")]
    Task DeletePostAsync(int id);
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public int UserId { get; set; }
}
```

We define our `IBlogApi` interface with methods for all CRUD operations: GET (single and list), POST, PUT, and DELETE.
The `Post` class represents the structure of our blog posts.

Then you have to register Refit in your dependency injection container:

```csharp {4}
using Refit;

builder.Services
    .AddRefitClient<IBlogApi>()
    .ConfigureHttpClient(c => c.BaseAddress = new Uri("https://jsonplaceholder.typicode.com"));
```

Finally, we can use `IBlogApi` in our Minimal API endpoints:

```csharp
app.MapGet("/posts/{id}", async (int id, IBlogApi api) =>
    await api.GetPostAsync(id));

app.MapGet("/posts", async (IBlogApi api) =>
    await api.GetPostsAsync());

app.MapPost("/posts", async ([FromBody] Post post, IBlogApi api) =>
    await api.CreatePostAsync(post));

app.MapPut("/posts/{id}", async (int id, [FromBody] Post post, IBlogApi api) =>
    await api.UpdatePostAsync(id, post));

app.MapDelete("/posts/{id}", async (int id, IBlogApi api) =>
    await api.DeletePostAsync(id));
```

What I love about this setup is its simplicity.
We've created a fully functional API that communicates with an external service, all in just a few lines of code.
No manual HTTP requests, no raw JSON handling - Refit takes care of all that for us.

## Query Parameters and Route Binding

When working with APIs, you often need to send data as part of the URL, either in the route or as query parameters.
Refit makes this process simple and type-safe.

Let's extend our `IBlogApi` interface with some more complex scenarios:

```csharp {6,8}
public interface IBlogApi
{
    // Other methods omitted for brevity

    [Get("/posts")]
    Task<List<Post>> GetPostsAsync([Query] PostQueryParameters parameters);

    [Get("/users/{userId}/posts")]
    Task<List<Post>> GetUserPostsAsync(int userId);
}

public class PostQueryParameters
{
    public int? UserId { get; set; }
    public string? Title { get; set; }
}
```

Let's break this down:

- `GetPostsAsync` uses an object to represent query parameters.
  This approach is excellent for endpoints with many optional parameters.
  Refit will automatically convert this object into a query string.
- `GetUserPostsAsync` demonstrates passing in route parameters (`userId`) directly.

Using an object for query parameters makes your code type-safe and [**refactoring-friendly**](https://milanjovanovic.tech/blog/5-awesome-csharp-refactoring-tips).
If you need to add a new query parameter, you just add a property to `PostQueryParameters`.
Your existing code won't break, and your IDE can help you discover the new options.

## Dynamic Headers and Authentication

Another common requirement when integrating with APIs is including custom headers or authentication tokens with your requests.
Refit provides several ways to handle this, from simple static headers to dynamic, request-specific authentication.

Let's explore some scenarios:

```csharp
public interface IBlogApi
{
    [Headers("User-Agent: MyAwesomeApp/1.0")]
    [Get("/posts")]
    Task<List<Post>> GetPostsAsync();

    [Get("/secure-posts")]
    Task<List<Post>> GetSecurePostsAsync([Header("Authorization")] string bearerToken);

    [Get("/user-posts")]
    Task<List<Post>> GetUserPostsAsync([Authorize(scheme: "Bearer")] string token);
}
```

- You can add a static header to all requests by using the `Headers` attribute
- With the `Header` attribute, you can pass a header value dynamically as a parameter
- The `Authorize` attribute is a convenient way to add Bearer token authentication

But what if you need to add the same dynamic header to all requests?

That's where `DelegatingHandler` comes in handy.

You can learn more about [**using delegating handlers in this article**](https://milanjovanovic.tech/blog/extending-httpclient-with-delegating-handlers-in-aspnetcore).

I've found delegating handlers especially helpful in providing API keys, which are typically static.

## JSON Serialization Options

Refit gives you flexibility when choosing and configuring your JSON serializer.
By default, Refit uses `System.Text.Json`, is the built-in JSON serializer in modern .NET versions.

However, you can easily switch to `Newtonsoft.Json` if you need its features.

Here's how you can configure Refit to use it.

First, install the Newtonsoft.Json support package:

```powershell
Install-Package Refit.Newtonsoft.Json
```

Then, configure Refit to use Newtonsoft.Json:

```csharp {7-11}
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
using Refit;

builder.Services.AddRefitClient<IBlogApi>(new RefitSettings
{
    ContentSerializer = new NewtonsoftJsonContentSerializer(new JsonSerializerSettings
    {
        ContractResolver = new CamelCasePropertyNamesContractResolver(),
        NullValueHandling = NullValueHandling.Ignore
    })
})
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://jsonplaceholder.typicode.com"));
```

This setup uses camel case for property names and ignores null values when serializing.

`System.Text.Json` is faster and uses less memory, making it a great default choice.
However, `Newtonsoft.Json` offers more features and might be necessary for compatibility with older systems or specific serialization needs.

## Handling HTTP Responses

While Refit's default behavior of automatically deserializing responses into your defined types is convenient, there are times when you need more control over the HTTP response.

Refit provides two options for these scenarios:
[`HttpResponseMessage`](https://learn.microsoft.com/en-us/dotnet/api/system.net.http.httpresponsemessage) and `ApiResponse<T>`.

Let's update the `IBlogApi` to use these types:

```csharp
public interface IBlogApi
{
    [Get("/posts/{id}")]
    Task<HttpResponseMessage> GetPostRawAsync(int id);

    [Get("/posts/{id}")]
    Task<ApiResponse<Post>> GetPostWithMetadataAsync(int id);

    [Post("/posts")]
    Task<ApiResponse<Post>> CreatePostAsync([Body] Post post);
}
```

Now, let's break down how to use these, starting with `HttpResponseMessage`:

```csharp {1}
HttpResponseMessage response = await blogApi.GetPostRawAsync(1);

if (response.IsSuccessStatusCode)
{
    var content = await response.Content.ReadAsStringAsync();
    var post = JsonSerializer.Deserialize<Post>(content);
    Console.WriteLine($"Retrieved post: {post.Title}");
}
else
{
    Console.WriteLine($"Error: {response.StatusCode}");
}
```

This approach gives you full control over the HTTP response. You can access status codes, headers, and the raw content.
But you will have to deal with deserialization manually.

`ApiResponse<T>` is a Refit-specific type that wraps the deserialized content and response metadata.
It's a great middle ground when you need the typed response and access to headers or status codes.

Here's a more complex example using `ApiResponse<T>` for creating a post:

```csharp {3}
var newPost = new Post { Title = "New Post", Body = "Content", UserId = 1 };

ApiResponse<Post> createResponse = await blogApi.CreatePostAsync(newPost);

if (createResponse.IsSuccessStatusCode)
{
    var createdPost = createResponse.Content;
    var locationHeader = createResponse.Headers.Location;
    Console.WriteLine($"Created post with ID: {createdPost.Id}");
    Console.WriteLine($"Location: {locationHeader}");
}
else
{
    Console.WriteLine($"Error: {createResponse.Error.Content}");
    Console.WriteLine($"Status: {createResponse.StatusCode}");
}
```

This approach allows you to access the created resource, check specific headers like `Location`, and handle errors gracefully.

## Takeaway

Refit transforms the way we interact with APIs in .NET applications.
Converting your API into a strongly typed interface simplifies your code, enhances type safety, and improves maintainability.

The key Refit benefits we've explored include:

- Simplified API calls with automatic serialization and deserialization
- Flexible parameter handling for complex queries
- Easy management of headers and authentication
- Options for JSON serialization to fit your project's needs
- Granular control over HTTP responses when required

In my experience, Refit shines in projects of all sizes.
I've used it in small applications and large-scale [**microservices architectures**](https://milanjovanovic.tech/blog/microservices-dotnet-getting-started).
It eliminates boilerplate code, reduces the risk of errors, and allows you to focus on your application's core logic rather than the intricacies of HTTP communication.

Remember, while Refit makes API interactions more straightforward, it's not a substitute for understanding the underlying principles of RESTful communication and HTTP.

That's all for today. Stay awesome, and I'll see you next week.

**P.S.** You can find the source code for this example in [**this repository**](https://github.com/m-jovanovic/refit-client-example).

---

## Frequently asked questions

### What is Refit in .NET?

Refit is a type-safe REST library for .NET. You describe an API as a C# interface using method signatures and attributes, and Refit implements it for you, handling the HTTP calls and JSON serialization. That removes boilerplate and makes API calls readable and maintainable.

### How do you set up Refit in a .NET project?

Install the Refit and Refit.HttpClientFactory NuGet packages, define an interface with attributes like Get and Post on its methods, then register it with AddRefitClient and configure the base address on the underlying HttpClient. The generated client is then available through dependency injection.

### How does Refit handle query and route parameters?

Route parameters in the template, like a post id, bind directly to method parameters. For query strings you can pass an object marked with the Query attribute, and Refit converts its properties into query parameters, which is type-safe and works well for endpoints with many optional parameters.

### How do you add authentication or custom headers with Refit?

Use the Headers attribute for static headers, the Header attribute to pass a value like an Authorization token as a method parameter, or the Authorize attribute for Bearer tokens. To add the same dynamic header to every request, use a DelegatingHandler, which also suits API keys.

### Which JSON serializer does Refit use?

By default Refit uses System.Text.Json, which is faster and uses less memory. You can switch to Newtonsoft.Json by installing the Refit.Newtonsoft.Json package and supplying a NewtonsoftJsonContentSerializer through RefitSettings when you need its extra features or compatibility with older systems.

### How do you access status codes and headers from a Refit call?

Return HttpResponseMessage for full control over the raw response, at the cost of manual deserialization, or ApiResponse<T>, a Refit type that wraps the deserialized content together with the status code and headers. ApiResponse<T> is the middle ground for typed responses plus metadata.
