Getting Started | Guides | API Documentation | Changelog | NuGet
Note: This repository's default branch is the
dev
branch. Switch tomaster
for the latest release.
ReqRest allows you to easily turn a RESTful Web API into a fully typed C# library by using a declarative, fluent syntax. API clients written with ReqRest have full IntelliSense and compiler support and thus make your REST API feel like plain C#.
An example API call demonstrating ReqRest's IntelliSense support.
- Features
- A Minimal Working Example
- Installation
- Getting Started
- Documentation
- Contributing
- Build, Code and Repository Information
ReqRest makes it incredibly easy to declare how your API behaves for specific requests. Even special API endpoints can easily be wrapped with ReqRest.
For example, the fictional API endpoint GET /todos?page={number}&pageSize={number}
endpoint returns
a List<TodoItem>
for the status code 200
and an ErrorMessage
for status codes between 400
and 599
. It can be declared like this:
public ApiRequest<List<TodoItem>, ErrorMessage> Get(int? page, int? pageSize) =>
BuildRequest()
.Get()
.ConfigureRequestUri(url =>
url & ("page", $"{page}") & ("pageSize", $"{pageSize}")
)
.Receive<List<TodoItem>>().AsJson(forStatusCodes: 200)
.Receive<ErrorMessage>().AsJson(forStatusCodes: (400, 599));
When interacting with a REST API, dealing with status code is required, but tedious. ReqRest tackles this and makes your life easier by abstracting status codes away for you.
For example, the code in the section above declares that the API returns a List<TodoItem>
or an
ErrorMessage
, depending on the status code.
Because ReqRest now has all these information, it can do the matching for you when interacting with
the response:
var (response, resource) = await client.Todos().Get(page: 1, pageSize: 50).FetchAsync();
// Match(...) is only one of many ways to get the actual object that you are interested in.
// Other possible methods are, for example: GetValue, TryGetValue, GetValueOr, ...
resource.Match(
todoItems => Console.WriteLine($"There are {todoItems.Count} items!"),
errorMsg => Console.WriteLine($"There was an error: {errorMsg.Message}."),
() => Console.WriteLine($"Received an unexpected status code: {response.StatusCode}")
);
It can always happen that a user of your API client will have some special needs for specific requests. For example, a user may have to send a special HTTP header with his request. The way that ReqRest is designed makes it incredibly simple to do exactly that.
As an example, you can send the request from above with a custom header like this:
var response = await client.Todos().Get(page: 1, pageSize: 50).AddHeader("x-foo", "bar").FetchResponseAsync();
As a matter of fact, users have access to all methods that are available during the request building phase. You can create standard requests in your API client while still allowing users to configure every request to their needs!
Nearly every part of ReqRest can be extended depending on your needs. In fact, ReqRest's code base mainly consists of extension methods. If we can extend it this way, so can you!
Say that your API offers the special CUSTOM
HTTP Method - you can easily add such functionality to ReqRest:
public static T Custom<T>(this T builder) where T : IHttpMethodBuilder =>
builder.SetMethod(new HttpMethod("CUSTOM"));
// Use it like this:
BuildRequest()
.Custom()
.Receive<Foo>().AsJson(forStatusCode: 200);
One of ReqRest's core philosophies is to simply be a wrapper around .NET's HttpClient
API.
Anything that you can do with an HttpClient
is also possible with ReqRest.
For example, the ApiRequest
class is nothing else but a decorator around .NET's HttpRequestMessage
class. As a result, you have full access to the underlying properties. No magic, no nonsense.
ReqRest supports Nullable Reference Types throughout the whole library.
Every public method of ReqRest is extensively documented via XML comments.
This section shows what it takes to write a minimal API client with ReqRest.
This API client will interact with the JSON Placeholder API,
specifically the GET https://jsonplaceholder.typicode.com/todos
endpoint.
Because this is supposed to just be an example, the code is not explained in detail. Consider checking out the Getting Started guide to learn more about what is happening in this code.
ℹ️ Note:
For brevity, the example doesn't show the
TodoItem
class, because it is simply a DTO with four properties.
public class JsonPlaceholderClient : RestClient
{
// For simplicity, define a static config. This should be user-configurable in a real client.
private static readonly RestClientConfiguration DefaultConfig = new RestClientConfiguration
{
BaseUrl = new Uri("https://jsonplaceholder.typicode.com"),
};
public JsonPlaceholderClient() : base(DefaultConfig) { }
public TodosInterface Todos() => new TodosInterface(this);
}
public class TodosInterface : RestInterface
{
internal TodosInterface(JsonPlaceholderClient restClient) : base(restClient) { }
// baseUrl here is the BaseUrl configured in the client above.
protected override UrlBuilder BuildUrl(UrlBuilder baseUrl) =>
baseUrl / "todos";
public ApiRequest<IList<TodoItem>> Get() =>
BuildRequest()
.Get()
.Receive<IList<TodoItem>>().AsJson(200);
}
A lot is happening in this small piece of code. The explanation can be found in the Getting Started guide. The important part is that this is all that you need for creating a client with ReqRest:
JsonPlaceholderClient client = new JsonPlaceholderClient();
var (response, resource) = await client.Todos().Get().FetchAsync();
// TryGetValue is one of many ways to get to the value that the API returned.
if (resource.TryGetValue(out IList<TodoItem> todoItems))
{
Console.WriteLine($"The API returned {todoItems.Count} items!");
}
else
{
Console.WriteLine($"The API returned no items. The status code was: {response.StatusCode}.");
}
In theory, this client could (once extended) be published as a library via NuGet. Everything is statically typed and users can simply use your code like any other C# library.
The library is available on NuGet. Install it via:
Install-Package ReqRest
Install-Package ReqRest.Serializers.NewtonsoftJson # Optional, but desired in most cases.
--or--
dotnet add package ReqRest
dotnet add package ReqRest.Serializers.NewtonsoftJson # Optional, but desired in most cases.
Depending on your needs, you may want to install additional packages which enhance the base functionality of ReqRest. In most cases, you will want to install an extra package for JSON support. These packages are not shipped with ReqRest by default, because they depend on external libraries or are not compatible with .NET Standard 2.0.
There are multiple ways for getting started with ReqRest.
You should definitely check out the Getting Started Guide in the official documentation.
Afterwards, you can have a look at the Guides. In there, you will find in depth tutorials and explanations regarding the usage of ReqRest.
Last but not least, you can always have a look at the API Documentation both online and via Visual Studio's IntelliSense. I spend a lot of time on the XML documentation - it is definitely worth your time to have a look into it.
The public documentation is available at https://reqrest.github.io.
This documentation is generated as part of the build process and stored in a separate repository available at https://github.com/ReqRest/ReqRest-Documentation. If you are interested in updating the documentation, be sure to have a look into that repository.
Any kind of contribution is welcome! If you have found a bug or have a question or feature request, be sure to open an issue. Alternatively, depending on the problem, you can create a new Pull Request and manually make your desired changes.
⚠️ Important:If you intend to make any (larger) changes to the code, be sure to open an issue to talk about what you are going to do before starting your work, so that we can ensure that the changes are going to have a chance of actually being accepted.
For development, C# 8.0 is used. As a result, you will have to download the .NET Core 3.0 SDK and at least Visual Studio 2019.
When writing code for the library, please be sure to:
- Follow the established code style of the existing code.
- Always write XML documentation for public members.
- Create test cases for new code (there may be exceptions for this point, but view it as a guideline).
As long as the library is still in the initial development (i.e. on version 0.x.x
) any
version increment may signify a breaking change.
Large changes will definitly lead to a minor version increment.
Small breaking changes may only increment the patch number though.
As soon as the library reaches version 1.0.0
it will follow Semantic Versioning.
The repository's main branch is the dev
branch. On this branch, changes are accumulated until
the release of a new version is justified. Once that happens, the current state of dev
will be
merged into master
and then automatically be built and deployed to NuGet.
As a result, master
will always represent the state of the latest release.
See the LICENSE file for details.