Refitter is a CLI tool for generating a C# REST API Client using the Refit library from OpenAPI specifications.
$ dotnet add package RefitterRefitter is a CLI tool for generating a C# REST API Client using the Refit library. Refitter can generate the Refit interface from OpenAPI specifications
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
$ refitter --help
USAGE:
refitter [URL or input file] [OPTIONS]
EXAMPLES:
refitter ./openapi.json
refitter https://petstore3.swagger.io/api/v3/openapi.yaml
refitter ./openapi.json --settings-file ./openapi.refitter --output ./GeneratedCode.cs
refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --output ./GeneratedCode.cs
refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --internal
refitter ./openapi.json --output ./IGeneratedCode.cs --interface-only
refitter ./openapi.json --use-api-response
refitter ./openapi.json --cancellation-tokens
refitter ./openapi.json --no-operation-headers
refitter ./openapi.json --no-accept-headers
refitter ./openapi.json --use-iso-date-format
refitter ./openapi.json --additional-namespace "Your.Additional.Namespace" --additional-namespace "Your.Other.Additional.Namespace"
refitter ./openapi.json --multiple-interfaces ByEndpoint
refitter ./openapi.json --tag Pet --tag Store --tag User
refitter ./openapi.json --match-path '^/pet/.*'
refitter ./openapi.json --no-deprecated-operations
refitter ./openapi.json --operation-name-template '{operationName}Async'
refitter ./openapi.json --optional-nullable-parameters
ARGUMENTS:
[URL or input file] URL or file path to OpenAPI Specification file
OPTIONS:
DEFAULT
-h, --help Prints help information
-s, --settings-file Path to .refitter settings file. Specifying this will ignore all other settings (except for --output)
-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
--no-accept-headers Don't add <Accept> header to output file
--interface-only Don't generate contract types
--use-api-response Return Task<IApiResponse<T>> instead of Task<T>
--internal Set the accessibility of the generated types to 'internal'
--cancellation-tokens Use cancellation tokens
--no-operation-headers Don't generate operation headers
--no-logging Don't log errors or collect telemetry
--additional-namespace Add additional namespace to generated types
--use-iso-date-format Explicitly format date query string parameters in ISO 8601 standard date format using delimiters (2023-06-15)
--multiple-interfaces Generate a Refit interface for each endpoint. May be one of ByEndpoint, ByTag
--match-path Only include Paths that match the provided regular expression. May be set multiple times
--tag Only include Endpoints that contain this tag. May be set multiple times and result in OR'ed evaluation
--skip-validation Skip validation of the OpenAPI specification
--no-deprecated-operations Don't generate deprecated operations
--operation-name-template Generate operation names using pattern. When using --multiple-interfaces ByEndpoint, this is name of the Execute() method in the interface
--optional-nullable-parameters Generate nullable parameters as optional parameters
The following is an example .refitter file
{
"openApiPath": "/path/to/your/openAPI", // Required
"namespace": "Org.System.Service.Api.GeneratedCode", // Optional. Default=GeneratedCode
"naming": {
"useOpenApiTitle": false, // Optional. Default=true
"interfaceName": "MyApiClient" // Optional. Default=ApiClient
},
"generateContracts": true, // Optional. Default=true
"generateXmlDocCodeComments": true, // Optional. Default=true
"addAutoGeneratedHeader": true, // Optional. Default=true
"addAcceptHeaders": true, // Optional. Default=true
"returnIApiResponse": false, // Optional. Default=false
"generateOperationHeaders": true, // Optional. Default=true
"typeAccessibility": "Public", // Optional. Values=Public|Internal. Default=Public
"useCancellationTokens": false, // Optional. Default=false
"useIsoDateFormat": false, // Optional. Default=false
"multipleInterfaces": "ByEndpoint", // Optional. May be one of "ByEndpoint" or "ByTag"
"generateDeprecatedOperations": false, // Optional. Default=true
"operationNameTemplate": "{operationName}Async", // Optional. Must contain {operationName} when multipleInterfaces != ByEndpoint
"optionalParameters": false, // Optional. Default=false
"outputFolder": "../CustomOutput" // Optional. Default=./Generated
"additionalNamespaces": [ // Optional
"Namespace1",
"Namespace2"
],
"includeTags": [ // Optional. OpenAPI Tag to include when generating code
"Pet",
"Store",
"User"
],
"includePathMatches": [ // Optional. Only include Paths that match the provided regular expression
"^/pet/.*",
"^/store/.*"
],
"dependencyInjectionSettings": {
"baseUrl": "https://petstore3.swagger.io/api/v3", // Optional. Leave this blank to set the base address manually
"httpMessageHandlers": [ // Optional
"AuthorizationMessageHandler",
"TelemetryMessageHandler"
],
"usePolly": true, // Optional. Set this to true, to configure Polly with a retry policy that uses a jittered backoff. Default=false
"pollyMaxRetryCount": 3, // Optional. Default=6
"firstBackoffRetryInSeconds": 0.5 // Optional. Default=1.0
}
}openApiPath - points to the OpenAPI Specifications file. This can be the path to a file stored on disk, relative to the .refitter file. This can also be a URL to a remote file that will be downloaded over HTTP/HTTPSnamespace - the namespace used in the generated code. If not specified, this defaults to GeneratedCodenaming.useOpenApiTitle - a boolean indicating whether the OpenApi title should be used. Default is truenaming.interfaceName - the name of the generated interface. The generated code will automatically prefix this with I so if this set to MyApiClient then the generated interface is called IMyApiClient. Default is ApiClientgenerateContracts - a boolean indicating whether contracts should be generated. A use case for this is several API clients use the same contracts. Default is truegenerateXmlDocCodeComments - a boolean indicating whether XML doc comments should be generated. Default is trueaddAutoGeneratedHeader - a boolean indicating whether XML doc comments should be generated. Default is trueaddAcceptHeaders - a boolean indicating whether to add accept headers [Headers("Accept: application/json")]. Default is truereturnIApiResponse - a boolean indicating whether to return IApiResponse<T> objects. Default is falsegenerateOperationHeaders - a boolean indicating whether to use operation headers in the generated methods. Default is truetypeAccessibility - the generated type accessibility. Possible values are Public and Internal. Default is PublicuseCancellationTokens - Use cancellation tokens in the generated methods. Default is falseuseIsoDateFormat - Set to true to explicitly format date query string parameters in ISO 8601 standard date format using delimiters (for example: 2023-06-15). Default is falsemultipleInterfaces - Set to ByEndpoint to generate an interface for each endpoint, or ByTag to group Endpoints by their Tag (like SwaggerUI groups them).outputFolder - a string describing a relative path to a desired output folder. Default is ./GeneratedadditionalNamespaces - A collection of additional namespaces to include in the generated file. A use case for this is when you want to reuse contracts from a different namespace than the generated code. Default is emptyincludeTags - A collection of tags to use a filter for including endpoints that contain this tag.includePathMatches - A collection of regular expressions used to filter paths.generateDeprecatedOperations - a boolean indicating whether deprecated operations should be generated or skipped. Default is trueoperationNameTemplate - Generate operation names using pattern. This must contain the string {operationName}. An example usage of this could be {operationName}Async to suffix all method names with Async. When using multipleIinterfaces=ByEndpoint, This is name of the Execute() method in the interfaceoptionalParameters - Generate non-required parameters as nullable optional parametersdependencyInjectionSettings - Setting this will generated extension methods to IServiceCollection for configuring Refit clients
baseUrl - Used as the HttpClient base address. Leave this blank to manually set the base URLhttpMessageHandlers - A collection of HttpMessageHandler that is added to the HttpClient pipelineusePolly - Set this to true to configure the HttpClient to use Polly using a retry policy with a jittered backoffpollyMaxRetryCount - This is the max retry count used in the Polly retry policy. Default is 6firstBackoffRetryInSeconds - This is the duration of the initial retry backoff. Default is 1 secondTo 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
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 partial 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)] IEnumerable<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, [Header("api_key")] string api_key);
[Post("/pet/{petId}/uploadImage")]
Task<ApiResponse> UploadFile(long petId, [Query] string additionalMetadata, 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] IEnumerable<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-responseusing Refit;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace Your.Namespace.Of.Choice.GeneratedCode.WithApiResponse
{
public partial 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] Status? status);
/// <summary>
/// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
/// </summary>
[System.Obsolete]
[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] string name, [Query] string status);
[Delete("/pet/{petId}")]
Task DeletePet(long petId, [Header("api_key")] string api_key);
[Post("/pet/{petId}/uploadImage")]
Task<IApiResponse<ApiResponse>> UploadFile(long petId, [Query] string additionalMetadata, StreamPart 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] string username, [Query] 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);
}
}Here's an example generated output from the Swagger Petstore example configured to generate an interface for each endpoint
CLI Tool
$ refitter ./openapi.json --namespace "Your.Namespace.Of.Choice.GeneratedCode" --multiple-interfaces ByEndpointOutput
/// <summary>
/// Update an existing pet
/// </summary>
public partial interface IUpdatePetEndpoint
{
/// <summary>
/// Update an existing pet by Id
/// </summary>
[Put("/pet")]
Task<Pet> Execute([Body] Pet body);
}
/// <summary>
/// Add a new pet to the store
/// </summary>
public partial interface IAddPetEndpoint
{
/// <summary>
/// Add a new pet to the store
/// </summary>
[Post("/pet")]
Task<Pet> Execute([Body] Pet body);
}
/// <summary>
/// Finds Pets by status
/// </summary>
public partial interface IFindPetsByStatusEndpoint
{
/// <summary>
/// Multiple status values can be provided with comma separated strings
/// </summary>
[Get("/pet/findByStatus")]
Task<ICollection<Pet>> Execute([Query] Status? status);
}
/// <summary>
/// Finds Pets by tags
/// </summary>
public partial interface IFindPetsByTagsEndpoint
{
/// <summary>
/// Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.
/// </summary>
[Get("/pet/findByTags")]
Task<ICollection<Pet>> Execute([Query(CollectionFormat.Multi)] IEnumerable<string> tags);
}
/// <summary>
/// Find pet by ID
/// </summary>
public partial interface IGetPetByIdEndpoint
{
/// <summary>
/// Returns a single pet
/// </summary>
[Get("/pet/{petId}")]
Task<Pet> Execute(long petId);
}
/// <summary>
/// Updates a pet in the store with form data
/// </summary>
public partial interface IUpdatePetWithFormEndpoint
{
[Post("/pet/{petId}")]
Task Execute(long petId, [Query] string name, [Query] string status);
}
/// <summary>
/// Deletes a pet
/// </summary>
public partial interface IDeletePetEndpoint
{
[Delete("/pet/{petId}")]
Task Execute(long petId, [Header("api_key")] string api_key);
}
/// <summary>
/// uploads an image
/// </summary>
public partial interface IUploadFileEndpoint
{
[Post("/pet/{petId}/uploadImage")]
Task<ApiResponse> Execute(long petId, [Query] string additionalMetadata, StreamPart body);
}
/// <summary>
/// Returns pet inventories by status
/// </summary>
public partial interface IGetInventoryEndpoint
{
/// <summary>
/// Returns a map of status codes to quantities
/// </summary>
[Get("/store/inventory")]
Task<IDictionary<string, int>> Execute();
}
/// <summary>
/// Place an order for a pet
/// </summary>
public partial interface IPlaceOrderEndpoint
{
/// <summary>
/// Place a new order in the store
/// </summary>
[Post("/store/order")]
Task<Order> Execute([Body] Order body);
}
/// <summary>
/// Find purchase order by ID
/// </summary>
public partial interface IGetOrderByIdEndpoint
{
/// <summary>
/// For valid response try integer IDs with value <= 5 or > 10. Other values will generated exceptions
/// </summary>
[Get("/store/order/{orderId}")]
Task<Order> Execute(long orderId);
}
/// <summary>
/// Delete purchase order by ID
/// </summary>
public partial interface IDeleteOrderEndpoint
{
/// <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 Execute(long orderId);
}
/// <summary>
/// Create user
/// </summary>
public partial interface ICreateUserEndpoint
{
/// <summary>
/// This can only be done by the logged in user.
/// </summary>
[Post("/user")]
Task Execute([Body] User body);
}
/// <summary>
/// Creates list of users with given input array
/// </summary>
public partial interface ICreateUsersWithListInputEndpoint
{
/// <summary>
/// Creates list of users with given input array
/// </summary>
[Post("/user/createWithList")]
Task<User> Execute([Body] IEnumerable<User> body);
}
/// <summary>
/// Logs user into the system
/// </summary>
public partial interface ILoginUserEndpoint
{
[Get("/user/login")]
Task<string> Execute([Query] string username, [Query] string password);
}
/// <summary>
/// Logs out current logged in user session
/// </summary>
public partial interface ILogoutUserEndpoint
{
[Get("/user/logout")]
Task Execute();
}
/// <summary>
/// Get user by user name
/// </summary>
public partial interface IGetUserByNameEndpoint
{
[Get("/user/{username}")]
Task<User> Execute(string username);
}
/// <summary>
/// Update user
/// </summary>
public partial interface IUpdateUserEndpoint
{
/// <summary>
/// This can only be done by the logged in user.
/// </summary>
[Put("/user/{username}")]
Task Execute(string username, [Body] User body);
}
/// <summary>
/// Delete user
/// </summary>
public partial interface IDeleteUserEndpoint
{
/// <summary>
/// This can only be done by the logged in user.
/// </summary>
[Delete("/user/{username}")]
Task Execute(string username);
}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
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"
}.NET 6.0 (LTS)
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 ☕