A few weeks ago I was watching this episode of the ASP.NET Community Standup and they had Ryan Nowak on to talk about the new HttpClientFactory coming in the 2.1 release and a question came up about compatibility with Refit. I had been meaning to check out Refit but had honestly forgotten about it. This post is going to be a very basic introduction to Refit.
What is it?
In the author’s (Paul Betts) words, Refit is the automatic type-safe REST library for .NET Core, Xamarin and .NET. Cool, but what does that mean? Basically, Refit allows you to define an interface for an API that your application wants to call and using that is hides way all the HTTP and JSON serialization/deserialization bits.
Sample project creation
To test Refit out I created a very simple .NET Core console application. To do the same open a command prompt in the directory you want the project using the following command.
dotnet new console
For this project, I am using Visual Studio Code as my editor. Since VS Code doesn’t have a NuGet UI build in (maybe there is an extension?) I used the following command to add Refit to the project.
dotnet add package Refit
Or if you prefer you can add the following to your csproj file.
<ItemGroup>
<PackageReference Include="Refit" Version="4.3.0" />
</ItemGroup>
The API
Instead of creating an API I searched the internet for a free one I could use. I ended up using CountryAPI. The following is a sample of what a response from the API looks like.
{
"IsSuccess":true,
"UserMessage":null,
"TechnicalMessage":null,
"TotalCount":1,
"Response":[
{
"Name":"Afghanistan",
"Alpha2Code":"AF",
"Alpha3Code":"AFG",
"NativeName":"افغانستان",
"Region":"Asia",
"SubRegion":"Southern Asia",
"Latitude":"33",
"Longitude":"65",
"Area":652230,
"NumericCode":4,
"NativeLanguage":"pus",
"CurrencyCode":"AFN",
"CurrencyName":"Afghan afghani",
"CurrencySymbol":"؋",
"Flag":"https://api.backendless.com/2F26DFBF-433C-51CC-FF56-830CEA93BF00/473FB5A9-D20E-8D3E-FF01-E93D9D780A00/files/CountryFlags/afg.svg",
"FlagPng":"https://api.backendless.com/2F26DFBF-433C-51CC-FF56-830CEA93BF00/473FB5A9-D20E-8D3E-FF01-E93D9D780A00/files/CountryFlagsPng/afg.png"
}]
}
Classes
Now that we know what the API response looks like classes can be created to match its structure. In this case, I have two classes one for response and one for the actual country data.
public class ApiResponse<T>
{
public bool IsSuccess {get; set;}
public string UserMessage {get; set;}
public string TechnicalMessage {get; set;}
public int TotalCount {get; set;}
public List<T> Response {get; set;}
}
public class Country
{
public string Name { get; set; }
public string Alpha2Code { get; set; }
public string Alpha3Code { get; set; }
public string NativeName { get; set; }
public string Region { get; set; }
public string SubRegion { get; set; }
}
API Interface for Refit
With the classes for the response setup, we can now define the interface that will be used by Refit when calling the API. The following interface defines a function to get all countries and another function that gets countries that speak a specific native language.
public interface ICountryApi
{
[Get("/v1/Country/getCountries")]
Task<ApiResponse<Country>> GetCountries();
[Get("/v1/Country/getCountries")]
Task<ApiResponse<Country>> GetCountriesByLanguage([AliasAs("pNativeLanguage")]string language);
}
The attributes on the functions are part of the magic of Refit. In the cases above both of the calls are HTTP Get requests which is why they are using the Get attribute. Refit does support other verbs as well.
The other thing of note here is the AliasAs on the parameter of the second function call. This attribute can be used to control what gets put in the query string and keeps cryptic names from the API from spreading to other places in your code.
Calling the API
The following is the full code from my Program class that shows the usage of Refit with both of the API calls defined above.
public static async Task Main(string[] args)
{
var api = RestService.For<ICountryApi>(" http://countryapi.gear.host");
var countries = await api.GetCountries();
OutputCountires(countries.Response);
Console.WriteLine("Enter a language to filter by:");
var language = Console.ReadLine();
var filteredCountries = await api.GetCountriesByLanguage(language);
OutputCountires(filteredCountries.Response);
Console.ReadLine();
}
private static void OutputCountires(List<Country> countries)
{
countries.ForEach(c => Console.WriteLine($"{c.Name} - {c.Region} - {c.SubRegion}"));
}
The following line is defining a call to a rest API for a specific interface.
var api = RestService.For<ICountryApi>(" http://countryapi.gear.host");
Now that we have a reference to the API it can be called asynchronously to get the data from the API.
var countries = await api.GetCountries();
The rest of the app is more of the same just using the other API call.
Gotchas
In order to use async Task Main a change is needed to the project file to set the LangVersion. I just set it to latest, but I believe the minimum for this feature is 7.1.
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp2.0</TargetFramework>
<LangVersion>latest</LangVersion>
</PropertyGroup>
If you are using VS Code and are using Console.ReadLine() like I am above then a change will be needed for the launch.json file found in the .vscode directory. Look for the console property and set the value to either integratedTerminal or externalTerminal otherwise, the app will be connected to the debug console which will show the output of the application, but doesn’t allow for input.
Wrapping up
Using Refit to makes using APIs super simple. There is a level of magic that I would like to dig into more. I would also like to see how it handles issues and what sort of hooks are provided to address those issue. Based on the Github page Paul has addressed a wide range of the challenges faced with dealing with an API.
As part of writing this posts, I came across two other posts on Refit that might be helpful, one by Jerrie Pelser and the other from Scott Hanselman.