
In the ever-evolving world of .NET development, efficient API communication is crucial. Today, I’m excited to share a game-changing approach I’ve recently implemented: using Refit to streamline the interaction between two REST APIs over HTTP/S.
While the trusty HttpClient has long been a staple in our toolkit, Refit elevates API integration to new heights of simplicity and elegance. In this post, I’ll walk you through the power of Refit and how it can revolutionize your approach to HTTP client generation.
Get ready to say goodbye to boilerplate code and hello to a more intuitive, type-safe way of handling API requests. Whether you’re a seasoned .NET developer or just starting your journey, this guide will equip you with the knowledge to leverage Refit effectively in your projects.
Let’s dive in and discover how Refit can transform your API communication game!
Introduction
In the world of modern web development, efficient communication between client applications and APIs is crucial. Enter Refit, a powerful library that simplifies the process of consuming RESTful APIs in .NET applications. Refit automatically generates HTTP clients from simple interfaces, reducing boilerplate code and making API interactions more intuitive.
Refit, created by reactiveui, is an open-source library that brings the concept of «interface-first» to API consumption. It allows developers to define their API contracts as .NET interfaces, which Refit then implements at runtime. This approach not only reduces the amount of code you need to write but also makes your API calls more declarative and easier to maintain.
In this basic tutorial, we’ll explore how to use Refit in a .NET application, focusing on exception handling, cancellation, and best practices. We’ll use Microsoft’s Weather API example as our base application to show these concepts.
Setting Up Refit in Your Project
Let’s start by adding Refit to our Weather API project. First, install the necessary NuGet packages:
dotnet add package Refit
dotnet add package Refit.HttpClientFactory
Next, let’s define our API interface. Create a new file called IWeatherApi.cs:
using Refit;
using System.Threading;
using System.Threading.Tasks;
public interface IWeatherApi
{
[Get("/weatherforecast")]
Task<IEnumerable<WeatherForecast>> GetWeatherForecastAsync(CancellationToken cancellationToken = default);
}
Now, let’s register Refit in our Program.cs:
using Refit;
// ... other using statements
var builder = WebApplication.CreateBuilder(args);
// Add Refit client
builder.Services.AddRefitClient<IWeatherApi>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://localhost:5001"));
// ... rest of your Program.cs
Handling Exceptions with Refit
When working with APIs, handling exceptions is crucial for creating robust applications. Refit provides several ways to handle exceptions, primarily through the ApiException class. Let’s explore three main approaches:
1. Try/Catch Block
The most straightforward method is using a try/catch block:
public async Task<IEnumerable<WeatherForecast>> GetWeatherForecastWithTryCatch()
{
try
{
return await _weatherApi.GetWeatherForecastAsync();
}
catch (ApiException ex)
{
Console.WriteLine($"API Error: {ex.StatusCode} - {ex.Message}");
return Enumerable.Empty<WeatherForecast>();
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Network Error: {ex.Message}");
return Enumerable.Empty<WeatherForecast>();
}
}
2. Using IApiResponse
Refit allows you to return IApiResponse or IApiResponse<T> from your API methods, giving you more control over the response:
public interface IWeatherApi
{
[Get("/weatherforecast")]
Task<IApiResponse<IEnumerable<WeatherForecast>>> GetWeatherForecastAsync(CancellationToken cancellationToken = default);
}
public async Task<IEnumerable<WeatherForecast>> GetWeatherForecastWithApiResponse()
{
var response = await _weatherApi.GetWeatherForecastAsync();
if (response.IsSuccessStatusCode)
{
return response.Content;
}
else
{
Console.WriteLine($"API Error: {response.StatusCode} - {response.Error?.Content}");
return Enumerable.Empty<WeatherForecast>();
}
}
3. Custom Exception Handler
You can create a custom DelegatingHandler to centralize error handling:
public class RefitExceptionHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
CancellationToken cancellationToken)
{
try
{
return await base.SendAsync(request, cancellationToken);
}
catch (ApiException ex)
{
return CreateErrorResponse(ex.StatusCode,
WeatherErrors.ServerUnAvailable(ex.Message));
}
catch (OperationCanceledException ex)
{
return CreateErrorResponse(HttpStatusCode.RequestTimeout,
WeatherErrors.ServerUnAvailable($"Operation timeout. {ex.Message}"));
}
catch (Exception ex)
{
return CreateErrorResponse(HttpStatusCode.InternalServerError,
WeatherErrors.ServerUnAvailable(ex.Message));
}
}
private HttpResponseMessage CreateErrorResponse(HttpStatusCode statusCode, string message)
{
return new HttpResponseMessage(statusCode)
{
Content = new StringContent(message),
RequestMessage = new() // This line is crucial for avoiding NullReferenceException
};
}
}
// Add this handler when registering Refit
builder.Services.AddRefitClient<IWeatherApi>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://localhost:5001"))
.AddHttpMessageHandler<RefitExceptionHandler>();
Handling Cancellation with Refit
Cancellation is an important aspect of asynchronous programming. Refit supports cancellation through the use of CancellationToken. Here’s how you can implement it:
public async Task<IEnumerable<WeatherForecast>> GetWeatherForecastWithTimeout(int timeoutMs)
{
using var cts = new CancellationTokenSource(timeoutMs);
try
{
return await _weatherApi.GetWeatherForecastAsync(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine("The request was cancelled due to timeout");
return Enumerable.Empty<WeatherForecast>();
}
}
Common Errors and Solutions
During my exploration of Refit, I found one stupid error. Let’s discuss it and its solution:
NullReferenceException in Exception Handler
When implementing our custom exception handler, we initially encountered a NullReferenceException. This was due to the RequestMessage property being null in the HttpResponseMessage we were creating.
Solution: Add RequestMessage = new() when creating the HttpResponseMessage in your error handling logic. This ensures that the RequestMessage property is not null.
return new HttpResponseMessage(statusCode)
{
RequestMessage = new HttpRequestMessage() // This line is crucial
Content = new StringContent(message),
};
Conclusion
Refit is a powerful tool that can significantly simplify your API consumption code. By following the practices outlined in this tutorial, you can create robust, efficient, and maintainable API clients in your .NET applications.
Remember to handle exceptions appropriately, make use of cancellation tokens for better control over asynchronous operations, and consider implementing a custom exception handler for centralized error management.
References
- Refit GitHub Repository: https://github.com/reactiveui/refit
- Refit Documentation: https://reactiveui.github.io/refit/
- StackOverflow: NullReferenceException when testing API implemented by Refit: https://stackoverflow.com/questions/69266814/nullreferenceexception-when-testing-api-implemented-by-refit