Refitter 0.4.0
See the version list below for details.
dotnet tool install --global Refitter --version 0.4.0
dotnet new tool-manifest # if you are setting up this repo dotnet tool install --local Refitter --version 0.4.0
#tool dotnet:?package=Refitter&version=0.4.0
nuke :add-package Refitter --version 0.4.0
Refitter
Refitter is a CLI tool for generating a C# REST API Client using the Refit library. Refitter can generate the Refit interface from OpenAPI specifications
Installation:
The tool is packaged as a .NET Tool and is published to nuget.org. You can install the latest version of this tool like this:
dotnet tool install --global Refitter
Usage:
$ refitter --help
USAGE:
refitter [input file] [OPTIONS]
EXAMPLES:
refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --output ./Output.cs
ARGUMENTS:
[input file] Path to OpenAPI Specification file
OPTIONS:
DEFAULT
-h, --help Prints help information
-n, --namespace GeneratedCode Default namespace to use for generated types
-o, --output Output.cs Path to Output file
--no-auto-generated-header Don't add <auto-generated> header to output file
--interface-only Don't generate contract types
--use-api-response Return Task<IApiResponse<T>> instead of Task<T>
To generate code from an OpenAPI specifications file, run the following:
$ refitter [path to OpenAPI spec file] --namespace "[Your.Namespace.Of.Choice.GeneratedCode]"
This will generate a file called Output.cs
which contains the Refit interface and contract classes generated using NSwag
Using the generated code
Here's an example generated output from the Swagger Petstore example using the default settings
$ refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode"
using Refit;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace Your.Namespace.Of.Choice.GeneratedCode
{
public interface ISwaggerPetstore
{
/// <summary>
/// Update an existing pet by Id
/// </summary>
[Put("/pet")]
Task<Pet> UpdatePet([Body]Pet body);
/// <summary>
/// Add a new pet to the store
/// </summary>
[Post("/pet")]
Task<Pet> AddPet([Body]Pet body);
/// <summary>
/// Multiple status values can be provided with comma separated strings
/// </summary>
[Get("/pet/findByStatus")]
Task<ICollection<Pet>> FindPetsByStatus([Query]Status? status);
/// <summary>
/// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
/// </summary>
[Get("/pet/findByTags")]
Task<ICollection<Pet>> FindPetsByTags([Query(CollectionFormat.Multi)]ICollection<string> tags);
/// <summary>
/// Returns a single pet
/// </summary>
[Get("/pet/{petId}")]
Task<Pet> GetPetById(long? petId);
[Post("/pet/{petId}")]
Task UpdatePetWithForm(long? petId, [Query]string name, [Query]string status);
[Delete("/pet/{petId}")]
Task DeletePet(long? petId);
[Post("/pet/{petId}/uploadImage")]
Task<ApiResponse> UploadFile(long? petId, [Query]string additionalMetadata, [Body]StreamPart body);
/// <summary>
/// Returns a map of status codes to quantities
/// </summary>
[Get("/store/inventory")]
Task<IDictionary<string, int>> GetInventory();
/// <summary>
/// Place a new order in the store
/// </summary>
[Post("/store/order")]
Task<Order> PlaceOrder([Body]Order body);
/// <summary>
/// For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
/// </summary>
[Get("/store/order/{orderId}")]
Task<Order> GetOrderById(long? orderId);
/// <summary>
/// For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
/// </summary>
[Delete("/store/order/{orderId}")]
Task DeleteOrder(long? orderId);
/// <summary>
/// This can only be done by the logged in user.
/// </summary>
[Post("/user")]
Task CreateUser([Body]User body);
/// <summary>
/// Creates list of users with given input array
/// </summary>
[Post("/user/createWithList")]
Task<User> CreateUsersWithListInput([Body]ICollection<User> body);
[Get("/user/login")]
Task<string> LoginUser([Query]string username, [Query]string password);
[Get("/user/logout")]
Task LogoutUser();
[Get("/user/{username}")]
Task<User> GetUserByName(string username);
/// <summary>
/// This can only be done by the logged in user.
/// </summary>
[Put("/user/{username}")]
Task UpdateUser(string username, [Body]User body);
/// <summary>
/// This can only be done by the logged in user.
/// </summary>
[Delete("/user/{username}")]
Task DeleteUser(string username);
}
}
Here's an example generated output from the Swagger Petstore example configured to wrap the return type in IApiResponse<T>
$ refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --use-api-response
using Refit;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace Your.Namespace.Of.Choice.GeneratedCode.WithApiResponse
{
public interface ISwaggerPetstore
{
/// <summary>
/// Update an existing pet by Id
/// </summary>
[Put("/pet")]
Task<IApiResponse<Pet>> UpdatePet([Body] Pet body);
/// <summary>
/// Add a new pet to the store
/// </summary>
[Post("/pet")]
Task<IApiResponse<Pet>> AddPet([Body] Pet body);
/// <summary>
/// Multiple status values can be provided with comma separated strings
/// </summary>
[Get("/pet/findByStatus")]
Task<IApiResponse<ICollection<Pet>>> FindPetsByStatus([Query(CollectionFormat.Multi)] Status? status);
/// <summary>
/// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
/// </summary>
[Get("/pet/findByTags")]
Task<IApiResponse<ICollection<Pet>>> FindPetsByTags([Query(CollectionFormat.Multi)] IEnumerable<string> tags);
/// <summary>
/// Returns a single pet
/// </summary>
[Get("/pet/{petId}")]
Task<IApiResponse<Pet>> GetPetById(long petId);
[Post("/pet/{petId}")]
Task UpdatePetWithForm(long petId, [Query(CollectionFormat.Multi)] string name, [Query(CollectionFormat.Multi)] string status);
[Delete("/pet/{petId}")]
Task DeletePet(long petId);
[Post("/pet/{petId}/uploadImage")]
Task<IApiResponse<ApiResponse>> UploadFile(long petId, [Query(CollectionFormat.Multi)] string additionalMetadata, [Body(BodySerializationMethod.UrlEncoded)] Dictionary<string, object> body);
/// <summary>
/// Returns a map of status codes to quantities
/// </summary>
[Get("/store/inventory")]
Task<IApiResponse<IDictionary<string, int>>> GetInventory();
/// <summary>
/// Place a new order in the store
/// </summary>
[Post("/store/order")]
Task<IApiResponse<Order>> PlaceOrder([Body] Order body);
/// <summary>
/// For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
/// </summary>
[Get("/store/order/{orderId}")]
Task<IApiResponse<Order>> GetOrderById(long orderId);
/// <summary>
/// For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors
/// </summary>
[Delete("/store/order/{orderId}")]
Task DeleteOrder(long orderId);
/// <summary>
/// This can only be done by the logged in user.
/// </summary>
[Post("/user")]
Task CreateUser([Body] User body);
/// <summary>
/// Creates list of users with given input array
/// </summary>
[Post("/user/createWithList")]
Task<IApiResponse<User>> CreateUsersWithListInput([Body] IEnumerable<User> body);
[Get("/user/login")]
Task<IApiResponse<string>> LoginUser([Query(CollectionFormat.Multi)] string username, [Query(CollectionFormat.Multi)] string password);
[Get("/user/logout")]
Task LogoutUser();
[Get("/user/{username}")]
Task<IApiResponse<User>> GetUserByName(string username);
/// <summary>
/// This can only be done by the logged in user.
/// </summary>
[Put("/user/{username}")]
Task UpdateUser(string username, [Body] User body);
/// <summary>
/// This can only be done by the logged in user.
/// </summary>
[Delete("/user/{username}")]
Task DeleteUser(string username);
}
}
RestService
Here's an example usage of the generated code above
using Refit;
using System;
using System.Threading.Tasks;
namespace Your.Namespace.Of.Choice.GeneratedCode;
internal class Program
{
private static async Task Main(string[] args)
{
var client = RestService.For<ISwaggerPetstore>("https://petstore3.swagger.io/api/v3");
var pet = await client.GetPetById(1);
Console.WriteLine("## Using Task<T> as return type ##");
Console.WriteLine($"Name: {pet.Name}");
Console.WriteLine($"Category: {pet.Category.Name}");
Console.WriteLine($"Status: {pet.Status}");
Console.WriteLine();
var client2 = RestService.For<WithApiResponse.ISwaggerPetstore>("https://petstore3.swagger.io/api/v3");
var response = await client2.GetPetById(2);
Console.WriteLine("## Using Task<IApiResponse<T>> as return type ##");
Console.WriteLine($"HTTP Status Code: {response.StatusCode}");
Console.WriteLine($"Name: {response.Content.Name}");
Console.WriteLine($"Category: {response.Content.Category.Name}");
Console.WriteLine($"Status: {response.Content.Status}");
}
}
The RestService
class generates an implementation of ISwaggerPetstore
that uses HttpClient
to make its calls.
The code above when run will output something like this:
## Using Task<T> as return type ##
Name: Gatitotototo
Category: Chaucito
Status: Sold
## Using Task<IApiResponse<T>> as return type ##
HTTP Status Code: OK
Name: Gatitotototo
Category: Chaucito
Status: Sold
ASP.NET Core and HttpClientFactory
Here's an example Minimal API with the Refit.HttpClientFactory
library:
using Refit;
using Your.Namespace.Of.Choice.GeneratedCode;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services
.AddRefitClient<ISwaggerPetstore>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://petstore3.swagger.io/api/v3"));
var app = builder.Build();
app.MapGet(
"/pet/{id:long}",
async (ISwaggerPetstore petstore, long id) =>
{
try
{
return Results.Ok(await petstore.GetPetById(id));
}
catch (Refit.ApiException e)
{
return Results.StatusCode((int)e.StatusCode);
}
})
.WithName("GetPetById")
.WithOpenApi();
app.UseHttpsRedirection();
app.UseSwaggerUI();
app.UseSwagger();
app.Run();
.NET Core supports registering the generated ISwaggerPetstore
interface via HttpClientFactory
The following request to the API above
$ curl -X 'GET' 'https://localhost:5001/pet/1' -H 'accept: application/json'
Returns a response that looks something like this:
{
"id": 1,
"name": "Special_char_owner_!@#$^&()`.testing",
"photoUrls": [
"https://petstore3.swagger.io/resources/photos/623389095.jpg"
],
"tags": [],
"status": "Sold"
}
System requirements
.NET 7.0
For tips and tricks on software development, check out my blog
If you find this useful and feel a bit generous then feel free to buy me a coffee ☕
Product | Versions Compatible and additional computed target framework versions. |
---|---|
.NET | net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 was computed. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. |
This package has no dependencies.
Version | Downloads | Last updated |
---|---|---|
1.4.1 | 85,733 | 11/20/2024 |
1.4.1-preview.62 | 150 | 11/4/2024 |
1.4.0 | 354,722 | 10/14/2024 |
1.4.0-preview.61 | 101 | 10/7/2024 |
1.3.2 | 325,937 | 9/23/2024 |
1.3.2-preview.60 | 48 | 9/23/2024 |
1.3.1 | 48,961 | 9/20/2024 |
1.3.0 | 96,809 | 9/14/2024 |
1.2.1-preview.59 | 60 | 9/13/2024 |
1.2.1-preview.58 | 71 | 9/11/2024 |
1.2.1-preview.57 | 54 | 9/11/2024 |
1.2.1-preview.56 | 68 | 9/9/2024 |
1.2.1-preview.55 | 3,228 | 9/2/2024 |
1.2.1-preview.54 | 4,710 | 8/29/2024 |
1.2.0 | 559,881 | 8/12/2024 |
1.2.0-preview.53 | 5,178 | 8/4/2024 |
1.2.0-preview.52 | 1,840 | 7/29/2024 |
1.1.3 | 359,833 | 7/19/2024 |
1.1.3-preview.51 | 50 | 7/19/2024 |
1.1.2 | 25,452 | 7/17/2024 |
1.1.2-preview.50 | 47 | 7/16/2024 |
1.1.2-preview.49 | 62 | 7/11/2024 |
1.1.1 | 188,752 | 7/6/2024 |
1.1.1-preview.48 | 67 | 7/4/2024 |
1.1.1-preview.47 | 62 | 7/1/2024 |
1.1.1-preview.46 | 59 | 6/28/2024 |
1.1.0.45-preview | 98 | 6/25/2024 |
1.0.2 | 238,547 | 6/13/2024 |
1.0.1 | 48,339 | 6/7/2024 |
1.0.0 | 244,776 | 5/3/2024 |
0.9.9.44-preview | 88 | 4/29/2024 |
0.9.9 | 8,093 | 3/7/2024 |
0.9.8 | 3,688 | 2/27/2024 |
0.9.7 | 71,988 | 2/7/2024 |
0.9.6 | 254 | 1/29/2024 |
0.9.5 | 38,850 | 1/15/2024 |
0.9.4.43-preview | 122 | 1/15/2024 |
0.9.4 | 32,081 | 1/12/2024 |
0.9.3.42-preview | 154 | 1/10/2024 |
0.9.2 | 20,112 | 1/10/2024 |
0.9.1 | 3,895 | 1/9/2024 |
0.9.0 | 3,098 | 1/9/2024 |
0.8.7.41-preview | 158 | 1/3/2024 |
0.8.7.40-preview | 134 | 12/20/2023 |
0.8.7 | 55,031 | 12/18/2023 |
0.8.6.39-preview | 151 | 12/14/2023 |
0.8.6.38-preview | 167 | 12/14/2023 |
0.8.6 | 3,544 | 12/11/2023 |
0.8.5 | 54,770 | 11/23/2023 |
0.8.4 | 390 | 11/7/2023 |
0.8.3 | 249 | 10/31/2023 |
0.8.2 | 436 | 10/9/2023 |
0.8.1 | 893 | 10/4/2023 |
0.8.0 | 1,746 | 9/23/2023 |
0.7.5 | 2,727 | 9/7/2023 |
0.7.4 | 347 | 9/6/2023 |
0.7.3.37-preview | 217 | 8/25/2023 |
0.7.3.36-preview | 239 | 8/25/2023 |
0.7.3.35-preview | 253 | 8/21/2023 |
0.7.3.34-preview | 255 | 8/15/2023 |
0.7.3.33-preview | 236 | 8/12/2023 |
0.7.3 | 2,025 | 8/26/2023 |
0.7.2.32-preview | 215 | 8/7/2023 |
0.7.2 | 3,819 | 8/7/2023 |
0.7.1.31-preview | 250 | 8/2/2023 |
0.7.1.30-preview | 186 | 8/2/2023 |
0.7.1.29-preview | 252 | 8/1/2023 |
0.7.1 | 700 | 8/3/2023 |
0.7.0.28-preview | 177 | 7/28/2023 |
0.7.0.27-preview | 281 | 7/28/2023 |
0.7.0.26-preview | 259 | 7/27/2023 |
0.7.0.23-preview | 242 | 7/27/2023 |
0.7.0.22-preview | 295 | 7/27/2023 |
0.7.0.21-preview | 233 | 7/27/2023 |
0.7.0.20-preview | 277 | 7/27/2023 |
0.7.0 | 418 | 7/31/2023 |
0.6.3 | 1,160 | 7/22/2023 |
0.6.2 | 12,176 | 6/22/2023 |
0.6.1 | 334 | 6/20/2023 |
0.6.0 | 707 | 6/15/2023 |
0.5.30 | 491 | 6/12/2023 |
0.5.29 | 147 | 6/12/2023 |
0.5.28 | 158 | 6/10/2023 |
0.5.27 | 213 | 5/24/2023 |
0.5.26 | 278 | 5/11/2023 |
0.5.25 | 197 | 5/10/2023 |
0.5.3 | 186 | 5/5/2023 |
0.5.2 | 151 | 5/2/2023 |
0.5.1 | 166 | 5/2/2023 |
0.5.0 | 211 | 4/28/2023 |
0.4.2 | 204 | 4/24/2023 |
0.4.1 | 430 | 4/3/2023 |
0.4.0 | 310 | 3/24/2023 |
0.3.17 | 7,427 | 3/24/2023 |
0.3.16 | 255 | 3/22/2023 |
0.3.4 | 215 | 3/22/2023 |
0.3.3 | 302 | 3/17/2023 |
0.3.2 | 269 | 3/16/2023 |
0.3.1 | 244 | 3/14/2023 |
0.3.0 | 236 | 3/14/2023 |
0.2.4-alpha | 270 | 3/1/2023 |
0.2.3-alpha | 179 | 2/27/2023 |
0.2.2-alpha | 221 | 2/25/2023 |
0.2.1-alpha | 247 | 2/25/2023 |
0.2.0-alpha | 196 | 2/24/2023 |
0.1.5-alpha | 251 | 2/18/2023 |
0.1.4-alpha | 292 | 2/17/2023 |
0.1.3-alpha | 272 | 2/17/2023 |
0.1.2-alpha | 241 | 2/17/2023 |