- Notifications
You must be signed in to change notification settings - Fork11
The automatic type-safe-reflectionless REST API client library for .Net Standard
License
letsar/RestLess
Folders and files
Name | Name | Last commit message | Last commit date | |
---|---|---|---|---|
Repository files navigation
RestLess is another type-safe REST API client library.It is heavily inspired byRefit, which is inspired byRetrofit, but does not works exactly the same way.
In factRestLess is built by keeping in mind that reflection is slow. When we build beautiful apps we don't want them to be slow down because of an external library, that's why all theRestLess REST clients are fully generated during the compilation.
This library fully supports the uri template defined by theRFC6570 thanks toDoLess.UriTemplates.
The main goal ofRestLess is to be fast. So I created a little benchmark project that can be run on Android.I benchmarked Refit againstRestLess for now, on two devices (time is in ms):
This is an old device running under Android 4.4.
Samsung 2017 flagship runnind under Android 7.0.
RestLess is really fast, especially at startup time. On an old device, with Refit, the startup time is more than one second whileRestLess is 9 times faster with 120ms!On the request time side,RestLess is faster thant Refit, but I don't think this is very relevant since the network will be the bottleneck.
Install the NuGet package calledRestLess and one of the extra package (likeRestLess.JsonNet) into your project.ARestLess folder with a file named RestClient.g.dl.rest.cs will be inserted in your project.This file will allow you to create the Rest client from your interface without reflection.
As in Refit, you have to create an interface representing your REST API and use attributes to indicate what to do.During the project compilation, all REST clients will be generated.
Warning: In Visual Studio for Mac, with versions prior to 0.7.1, the intellisense does not detectRestClient
, but it will compile nonetheless.From 0.7.1, you will have to close Visual Studio for Mac after adding this package and open it again. This is because Visual Studio for Mac keeps some project properties in cache.
Available on NuGet.
InstallRestLess
InstallRestLess.JsonNet if you want to serialize/deserialize usingJson.Net
[Header("User-Agent","RestLess")]publicinterfaceIGitHubApi{[Get("users{/userId}")]Task<User>GetUserAsync(stringuserId);}
IGitHubApigitHubApi=RestClient.For<IGitHubApi>("https://api.github.com");
Useruser=awaitgitHubApi.GetUserAsync("lestar");
You can change the way the API works with two things: TheHttpClient
that can be passed to theFor
method, or with theRestSettings
TheFor
method accepts aHttpClient
, so you can initialize it with aHttpMessageHandler
.With this, you can set default headers with a runtime value that will be the same accross all calls. You can also create aDelegatingHandler
for authentification, an other one for logging, etc.
These settings are used directly by the requests generated byRestLess.
You can set custom parameters or formatters but the defaultRestSettings
does not come with useful formatters (Because we don't want the core package to have a lot of dependencies).
For example, if you want to serialize/deserialize JSON content withJson.Net you'll have to get theRestLess.JsonNet NuGet package.Of course, you can write your own, if what you are looking for does not yet exists.
Custom parameters can be set at runtime but they are common to the entire REST client. These parameters can be used insideHeaderAttributes
(set theisCustomParameter
parameter to true) or inside the uri templates defined into theHTTP Method attributes
settings.CustomParameters.Add("api_key","bXlhcGlrZXk=");
The formatters can change the way an object is serialized/deserialized. There must be a default formatter for each kind of formatter.
As opposed toRefit you can use a specific formatter for a method and use the default one for the others.
These formatters are used to serialize/deserialize the body of a HTTP request/response from/into an object.
You can set a method-specific MediaTypeFormatter like this:
RestSettingsrestSettings=newRestSettings();restSettings.MediaTypeFormatters.Set("MediaTypeJsonFormatter",newMyMediaTypeJsonFormatter());...[Get("/whatever")][MediaTypeFormatter("MediaTypeJsonFormatter")]Task<HttpResponseMessage>GetAsync();
These formatters are used to serialize an object into a string. This string will be used to expand the variable in the uri template.
You can set a method-specific UrlParameterFormatter like this:
RestSettingsrestSettings=newRestSettings();restSettings.UrlParameterFormatters.Set("UrlParameterFormatter",newMyUrlParameterFormatter());...[Get("/whatever{?obj}")][UrlParameterFormatter("UrlParameterFormatter")]Task<HttpResponseMessage>GetAsync(MyObjectobj);
These formatters are used to serialize an object into aIEnumerable<KeyValuePair<string,string>>
that will be used to create an encoded url form content.
You can set a method-specific FormFormatter like this:
RestSettingsrestSettings=newRestSettings();restSettings.FormFormatters.Set("FormFormatter",newMyFormFormatterFormatter());...[Post("/whatever")][FormFormatter("FormFormatter")]Task<HttpResponseMessage>PostAsync([FormUrlEncodedContent]MyObjectobj);
In order to be identified as a REST interface, all methods of the interface must have aHTTP Method attribute that provides the request method and relative URL.
There are 8 built-in attributes:Delete,Get,Head,Options,Patch,Post,Put andTrace.The relative URL of the resource must be specified as the argument of this attribute and it have to respect theRFC6570 Uri template specification.
Note: You can use a literal string, or any expression (like a constant) as the attribute argument.
[Get("/users{/userId}")][Get("/users{?since}")]
A request URL can be updated dynamically with the values of the method parameters or through theCustomParameters of theRestSettings
class.The parameter names arecase-insensitive, so it will work correctly in this case:
[Get("/users{/userid}")]Task<HttpResponseMessage>GetUserAsync(stringuserId);
You can use theName
attribute to override the name of the parameter that will be used by the UriTemplate:
[Get("/users{/userId}")]Task<HttpResponseMessage>GetUserAsync([Name("userId")]stringid);
If the REST API always starts with a common string, let's say a version number for example, you can put it into aUriTemplatePrefixAttribute
Alternatively you can do the same if you REST API always ends with the same string, using theUriTemplateSuffixAttribute
There are three ways to set a header value:
If you have a header with a constant value you can use theHeaderAttribute
that way:
[Header("User-Agent","AppAgentForAllMethods")]publicinterfaceIRestApi{[Header("User-Agent","AppAgentForThisMethodOnly")]Task<User>GetUserAsync();// .. Some code...}
You can also set a global header value at runtime for all the methods of your REST client:
RestSettingsrestSettings=newRestSettings();restSettings.CustomParameters.Add("apiKey",ApiKey);...[Header("User-Agent","apiKey",true)]publicinterfaceIRestApi{// .. Some code...}
If the content of the header can change between calls, you can apply aHeaderValueAttribute
to a parameter:
[Get("api/posts")]Task<User>GetUserAsync([HeaderValue("User-Token")]stringtoken);
Redefining a header will replace it in the following order of precedence:
Header
attribute on the interface (lowest priority)Header
attribute on the methodHeaderValue
attribute on a method parameter (highest priority)
You can add a body content to your request by applying theContent
or theFormUrlEncodedContent
attribute to a method parameter.
With theContent
attribute, the parameter can have one the following types:
HttpContent
Stream
string
byte[]
object
=> This will use the specifiedMediaTypeFormatter (or the default one if not set)FileInfo
=> This trigger creates aMultipartFormDataContent
even if it is the only parameter
With theFormUrlEncodedContent
attribute, the parameter can have one the following types:
IEnumerable<KeyValuePair<string, string>>
object
=> This will use the specifiedFormFormatter (or the default one if not set)
You can decorate multiple parameters with theContent
or theFormUrlEncodedContent
attribute. The body of the request will be aMultipartFormDataContent
.When you want to create a multipart request, the default name for each parameter will be the parameter name. You can ovveride this behavior with theName
attribute.You can also set an optional fileName or contentType through the attribute parameters.
Example:
[Post("api/posts")]Task<HttpResponseMessage>PostMultipartContent03Async([Content]stringcontent,[Name("firstName")][Content("f","text/plain")]stringcontent2);
Will create a content like this:
--RestLessBoundaryContent-Type: text/plain; charset=utf-8Content-Disposition: form-data; name=contentdoe--RestLessBoundaryContent-Type: text/plainContent-Disposition: form-data; name=firstName; filename=f; filename*=utf-8''fjohn--RestLessBoundary--
All the REST client methods must return aTask
.
You can set a generic parameter type to theTask
, the valid ones are:
HttpResponseMessage
Stream
string
bool
=> This will return if the response has a success code without throwing an errorbyte[]
object
=> This will use the specifiedMediaTypeFormatter (or the default one if not set)
Sometimes REST APIS return useful information in the headers of the response.You can get them by setting theHeaderWriter
property of theRestSettings
:
publicclassHeaderWriter:IHeaderWriter{publicvoidWrite(HttpResponseHeadersheaders,objectobj){if(objisIPagedResponsepagedResponse){if(headers.TryGetValue(PaginationPage,outintpage)){pagedResponse.Page=page;}if(headers.TryGetValue(PaginationPageCount,outintpageCount)){pagedResponse.PageCount=pageCount;}}}}...mockHttp.Expect(HttpMethod.Get,url).Respond(x=>{varresponse=newHttpResponseMessage(HttpStatusCode.OK){Content=newStringContent("[{'firstName':'A','lastName':'AA'},{'firstName':'B','lastName':'BB'}]",Encoding.UTF8,"application/json")};response.Headers.Add(PaginationPage,"1");response.Headers.Add(PaginationPageCount,"2");returnresponse;});varsettings=newJsonRestSettings(){HttpMessageHandlerFactory=()=>mockHttp,HeaderWriter=newHeaderWriter()};IApi09restClient=RestClient.For<IApi09>(url,settings);varpeople=awaitrestClient.GetPagedPeopleAsync();people.Page.ShouldBeEquivalentTo(1);people.PageCount.ShouldBeEquivalentTo(2);
Unlike Refit, the core ofRestLess does not use reflection at runtime (For MediaFormatters it depends of the implementation). All the REST methods are generated during compile-time.
TheRestLess package does not have any dependencies to another third-party library (exceptDoLess.UriTemplates). In order to read/write Json, you need to referenceRestLess.JsonNet for example, but you can also write your own formatting strategies.
RestLess supports:
- Generic methods
- Method polymorphism
- The use of constants inside the attributes
- UriTemplates: see theSpec
- Method specific formatters
- Getting response headers in the returned object
Right now,RestLess does not supports:
- Interface inheritance
About
The automatic type-safe-reflectionless REST API client library for .Net Standard